2015-10-21 07:15:57 +08:00
/ * *
Licensed to the Apache Software Foundation ( ASF ) under one
or more contributor license agreements . See the NOTICE file
distributed with this work for additional information
regarding copyright ownership . The ASF licenses this file
to you under the Apache License , Version 2.0 ( the
"License" ) ; you may not use this file except in compliance
with the License . You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing ,
software distributed under the License is distributed on an
"AS IS" BASIS , WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND , either express or implied . See the License for the
specific language governing permissions and limitations
under the License .
* /
2022-04-18 09:39:54 +08:00
const fs = require ( 'fs-extra' ) ;
const path = require ( 'path' ) ;
2020-04-16 20:39:22 +08:00
const nopt = require ( 'nopt' ) ;
2020-10-06 15:04:48 +08:00
const glob = require ( 'fast-glob' ) ;
2022-04-18 09:39:54 +08:00
const events = require ( 'cordova-common' ) . events ;
const AndroidManifest = require ( './AndroidManifest' ) ;
const xmlHelpers = require ( 'cordova-common' ) . xmlHelpers ;
const CordovaError = require ( 'cordova-common' ) . CordovaError ;
const ConfigParser = require ( 'cordova-common' ) . ConfigParser ;
const FileUpdater = require ( 'cordova-common' ) . FileUpdater ;
const PlatformJson = require ( 'cordova-common' ) . PlatformJson ;
const PlatformMunger = require ( 'cordova-common' ) . ConfigChanges . PlatformMunger ;
const PluginInfoProvider = require ( 'cordova-common' ) . PluginInfoProvider ;
2020-01-31 21:02:48 +08:00
const utils = require ( './utils' ) ;
2021-07-06 14:38:28 +08:00
const gradleConfigDefaults = require ( './gradle-config-defaults' ) ;
2022-05-18 23:49:52 +08:00
const checkReqs = require ( './check_reqs' ) ;
2018-07-11 11:14:04 +08:00
const GradlePropertiesParser = require ( './config/GradlePropertiesParser' ) ;
2020-04-16 20:39:22 +08:00
function parseArguments ( argv ) {
return nopt ( {
// `jvmargs` is a valid option however, we don't actually want to parse it because we want the entire string as is.
// jvmargs: String
} , { } , argv || [ ] , 0 ) ;
}
2016-04-20 04:28:13 +08:00
module . exports . prepare = function ( cordovaProject , options ) {
2022-04-18 09:39:54 +08:00
const self = this ;
2015-10-21 07:15:57 +08:00
2020-04-16 20:39:22 +08:00
let args = { } ;
if ( options && options . options ) {
args = parseArguments ( options . options . argv ) ;
}
2022-04-18 09:39:54 +08:00
const platformJson = PlatformJson . load ( this . locations . root , this . platform ) ;
const munger = new PlatformMunger ( this . platform , this . locations . root , platformJson , new PluginInfoProvider ( ) ) ;
2016-04-22 21:03:02 +08:00
this . _config = updateConfigFilesFrom ( cordovaProject . projectConfig , munger , this . locations ) ;
2015-10-21 07:15:57 +08:00
2021-07-06 14:38:28 +08:00
// Update Gradle cdv-gradle-config.json
updateUserProjectGradleConfig ( this ) ;
2019-02-13 09:11:32 +08:00
2021-07-06 14:38:28 +08:00
// Update Project's Gradle Properties
updateUserProjectGradlePropertiesConfig ( this , args ) ;
2018-07-11 11:14:04 +08:00
2015-10-21 07:15:57 +08:00
// Update own www dir with project's www assets and plugins' assets and js-files
2022-06-30 09:49:10 +08:00
return Promise . resolve ( updateWww ( cordovaProject , this . locations ) )
2022-06-30 19:00:25 +08:00
. then ( ( ) => warnForDeprecatedSplashScreen ( cordovaProject ) )
2022-06-30 09:49:10 +08:00
. then ( ( ) => updateProjectAccordingTo ( self . _config , self . locations ) )
. then ( function ( ) {
updateIcons ( cordovaProject , path . relative ( cordovaProject . root , self . locations . res ) ) ;
updateFileResources ( cordovaProject , path . relative ( cordovaProject . root , self . locations . root ) ) ;
} ) . then ( function ( ) {
events . emit ( 'verbose' , 'Prepared android project successfully' ) ;
} ) ;
2015-10-21 07:15:57 +08:00
} ;
2021-07-06 14:38:28 +08:00
/** @param {PlatformApi} project */
function updateUserProjectGradleConfig ( project ) {
// Generate project gradle config
const projectGradleConfig = {
... gradleConfigDefaults ,
... getUserGradleConfig ( project . _config )
} ;
2022-05-18 22:18:33 +08:00
// Check if compile sdk is valid.
// The returned result is iggnored and since we do not need and will not throw an error.
// Only using the valid check call for display the warning when target is greater then compiled.
checkReqs . isCompileSdkValid (
projectGradleConfig . COMPILE _SDK _VERSION ,
projectGradleConfig . SDK _VERSION
) ;
2021-07-06 14:38:28 +08:00
// Write out changes
const projectGradleConfigPath = path . join ( project . root , 'cdv-gradle-config.json' ) ;
fs . writeJSONSync ( projectGradleConfigPath , projectGradleConfig , { spaces : 2 } ) ;
}
function getUserGradleConfig ( configXml ) {
const configXmlToGradleMapping = [
{ xmlKey : 'android-minSdkVersion' , gradleKey : 'MIN_SDK_VERSION' , type : Number } ,
{ xmlKey : 'android-maxSdkVersion' , gradleKey : 'MAX_SDK_VERSION' , type : Number } ,
{ xmlKey : 'android-targetSdkVersion' , gradleKey : 'SDK_VERSION' , type : Number } ,
2022-05-18 22:18:33 +08:00
{ xmlKey : 'android-compileSdkVersion' , gradleKey : 'COMPILE_SDK_VERSION' , type : Number } ,
2021-07-06 14:38:28 +08:00
{ xmlKey : 'android-buildToolsVersion' , gradleKey : 'BUILD_TOOLS_VERSION' , type : String } ,
{ xmlKey : 'GradleVersion' , gradleKey : 'GRADLE_VERSION' , type : String } ,
{ xmlKey : 'AndroidGradlePluginVersion' , gradleKey : 'AGP_VERSION' , type : String } ,
{ xmlKey : 'GradlePluginKotlinVersion' , gradleKey : 'KOTLIN_VERSION' , type : String } ,
{ xmlKey : 'AndroidXAppCompatVersion' , gradleKey : 'ANDROIDX_APP_COMPAT_VERSION' , type : String } ,
2021-07-06 22:39:12 +08:00
{ xmlKey : 'AndroidXWebKitVersion' , gradleKey : 'ANDROIDX_WEBKIT_VERSION' , type : String } ,
2021-07-06 14:38:28 +08:00
{ xmlKey : 'GradlePluginGoogleServicesVersion' , gradleKey : 'GRADLE_PLUGIN_GOOGLE_SERVICES_VERSION' , type : String } ,
{ xmlKey : 'GradlePluginGoogleServicesEnabled' , gradleKey : 'IS_GRADLE_PLUGIN_GOOGLE_SERVICES_ENABLED' , type : Boolean } ,
{ xmlKey : 'GradlePluginKotlinEnabled' , gradleKey : 'IS_GRADLE_PLUGIN_KOTLIN_ENABLED' , type : Boolean }
] ;
return configXmlToGradleMapping . reduce ( ( config , mapping ) => {
const rawValue = configXml . getPreference ( mapping . xmlKey , 'android' ) ;
// ignore missing preferences (which occur as '')
if ( rawValue ) {
config [ mapping . gradleKey ] = parseStringAsType ( rawValue , mapping . type ) ;
}
return config ;
} , { } ) ;
}
/** Converts given string to given type */
function parseStringAsType ( value , type ) {
switch ( type ) {
case String :
return String ( value ) ;
case Number :
return parseFloat ( value ) ;
case Boolean :
return value . toLowerCase ( ) === 'true' ;
default :
throw new CordovaError ( 'Invalid type: ' + type ) ;
}
}
function updateUserProjectGradlePropertiesConfig ( project , args ) {
const gradlePropertiesUserConfig = { } ;
// Get the min SDK version from config.xml
if ( args . jvmargs ) gradlePropertiesUserConfig [ 'org.gradle.jvmargs' ] = args . jvmargs ;
const isGradlePluginKotlinEnabled = project . _config . getPreference ( 'GradlePluginKotlinEnabled' , 'android' ) ;
if ( isGradlePluginKotlinEnabled ) {
const gradlePluginKotlinCodeStyle = project . _config . getPreference ( 'GradlePluginKotlinCodeStyle' , 'android' ) ;
gradlePropertiesUserConfig [ 'kotlin.code.style' ] = gradlePluginKotlinCodeStyle || 'official' ;
}
const gradlePropertiesParser = new GradlePropertiesParser ( project . root ) ;
gradlePropertiesParser . configure ( gradlePropertiesUserConfig ) ;
}
2016-04-20 04:28:13 +08:00
module . exports . clean = function ( options ) {
// A cordovaProject isn't passed into the clean() function, because it might have
// been called from the platform shell script rather than the CLI. Check for the
// noPrepare option passed in by the non-CLI clean script. If that's present, or if
// there's no config.xml found at the project root, then don't clean prepared files.
2022-04-18 09:39:54 +08:00
const projectRoot = path . resolve ( this . root , '../..' ) ;
2016-08-29 22:26:49 +08:00
if ( ( options && options . noPrepare ) || ! fs . existsSync ( this . locations . configXml ) ||
2016-05-27 06:46:38 +08:00
! fs . existsSync ( this . locations . configXml ) ) {
2020-01-07 20:22:59 +08:00
return Promise . resolve ( ) ;
2016-04-20 04:28:13 +08:00
}
2022-04-18 09:39:54 +08:00
const projectConfig = new ConfigParser ( this . locations . configXml ) ;
2016-04-20 04:28:13 +08:00
2022-04-18 09:39:54 +08:00
const self = this ;
2020-01-07 20:22:59 +08:00
return Promise . resolve ( ) . then ( function ( ) {
2016-04-20 04:28:13 +08:00
cleanWww ( projectRoot , self . locations ) ;
2016-10-29 04:05:01 +08:00
cleanIcons ( projectRoot , projectConfig , path . relative ( projectRoot , self . locations . res ) ) ;
2016-07-29 03:44:50 +08:00
cleanFileResources ( projectRoot , projectConfig , path . relative ( projectRoot , self . locations . root ) ) ;
2016-04-20 04:28:13 +08:00
} ) ;
} ;
2015-10-21 07:15:57 +08:00
/ * *
* Updates config files in project based on app ' s config . xml and config munge ,
* generated by plugins .
*
* @ param { ConfigParser } sourceConfig A project ' s configuration that will
* be merged into platform ' s config . xml
* @ param { ConfigChanges } configMunger An initialized ConfigChanges instance
* for this platform .
* @ param { Object } locations A map of locations for this platform
*
* @ return { ConfigParser } An instance of ConfigParser , that
* represents current project ' s configuration . When returned , the
* configuration is already dumped to appropriate config . xml file .
* /
2017-06-14 02:42:20 +08:00
function updateConfigFilesFrom ( sourceConfig , configMunger , locations ) {
2016-05-04 07:25:48 +08:00
events . emit ( 'verbose' , 'Generating platform-specific config.xml from defaults for android at ' + locations . configXml ) ;
2015-10-21 07:15:57 +08:00
// First cleanup current config and merge project's one into own
// Overwrite platform config.xml with defaults.xml.
2020-01-29 09:12:55 +08:00
fs . copySync ( locations . defaultConfigXml , locations . configXml ) ;
2015-10-21 07:15:57 +08:00
// Then apply config changes from global munge to all config files
// in project (including project's config)
configMunger . reapply _global _munge ( ) . save _all ( ) ;
2016-05-04 07:25:48 +08:00
events . emit ( 'verbose' , 'Merging project\'s config.xml into platform-specific android config.xml' ) ;
2015-10-21 07:15:57 +08:00
// Merge changes from app's config.xml into platform's one
2022-04-18 09:39:54 +08:00
const config = new ConfigParser ( locations . configXml ) ;
2015-10-21 07:15:57 +08:00
xmlHelpers . mergeXml ( sourceConfig . doc . getroot ( ) ,
2017-06-14 02:42:20 +08:00
config . doc . getroot ( ) , 'android' , /* clobber= */ true ) ;
2015-10-21 07:15:57 +08:00
config . write ( ) ;
return config ;
}
2016-04-20 04:28:13 +08:00
/ * *
* Logs all file operations via the verbose event stream , indented .
* /
2017-06-14 02:42:20 +08:00
function logFileOp ( message ) {
2016-04-20 04:28:13 +08:00
events . emit ( 'verbose' , ' ' + message ) ;
}
2015-10-21 07:15:57 +08:00
/ * *
* Updates platform 'www' directory by replacing it with contents of
* 'platform_www' and app www . Also copies project 's overrides' folder into
* the platform 'www' folder
*
* @ param { Object } cordovaProject An object which describes cordova project .
* @ param { Object } destinations An object that contains destination
* paths for www files .
* /
2017-06-14 02:42:20 +08:00
function updateWww ( cordovaProject , destinations ) {
2022-04-18 09:39:54 +08:00
const sourceDirs = [
2016-04-20 04:28:13 +08:00
path . relative ( cordovaProject . root , cordovaProject . locations . www ) ,
path . relative ( cordovaProject . root , destinations . platformWww )
] ;
2015-10-21 07:15:57 +08:00
// If project contains 'merges' for our platform, use them as another overrides
2022-04-18 09:39:54 +08:00
const merges _path = path . join ( cordovaProject . root , 'merges' , 'android' ) ;
2015-10-21 07:15:57 +08:00
if ( fs . existsSync ( merges _path ) ) {
2016-05-04 07:25:48 +08:00
events . emit ( 'verbose' , 'Found "merges/android" folder. Copying its contents into the android project.' ) ;
2016-04-20 04:28:13 +08:00
sourceDirs . push ( path . join ( 'merges' , 'android' ) ) ;
2015-10-21 07:15:57 +08:00
}
2016-04-20 04:28:13 +08:00
2022-04-18 09:39:54 +08:00
const targetDir = path . relative ( cordovaProject . root , destinations . www ) ;
2016-04-20 04:28:13 +08:00
events . emit (
'verbose' , 'Merging and updating files from [' + sourceDirs . join ( ', ' ) + '] to ' + targetDir ) ;
FileUpdater . mergeAndUpdateDir (
sourceDirs , targetDir , { rootDir : cordovaProject . root } , logFileOp ) ;
}
/ * *
* Cleans all files from the platform 'www' directory .
* /
2017-06-14 02:42:20 +08:00
function cleanWww ( projectRoot , locations ) {
2022-04-18 09:39:54 +08:00
const targetDir = path . relative ( projectRoot , locations . www ) ;
2016-04-20 04:28:13 +08:00
events . emit ( 'verbose' , 'Cleaning ' + targetDir ) ;
// No source paths are specified, so mergeAndUpdateDir() will clear the target directory.
FileUpdater . mergeAndUpdateDir (
[ ] , targetDir , { rootDir : projectRoot , all : true } , logFileOp ) ;
2015-10-21 07:15:57 +08:00
}
/ * *
* Updates project structure and AndroidManifest according to project ' s configuration .
*
* @ param { ConfigParser } platformConfig A project ' s configuration that will
* be used to update project
* @ param { Object } locations A map of locations for this platform
* /
2017-06-14 02:42:20 +08:00
function updateProjectAccordingTo ( platformConfig , locations ) {
2022-06-30 09:49:10 +08:00
updateProjectStrings ( platformConfig , locations ) ;
updateProjectSplashScreen ( platformConfig , locations ) ;
2016-06-08 04:02:18 +08:00
2022-04-18 09:39:54 +08:00
const name = platformConfig . name ( ) ;
2015-10-21 07:15:57 +08:00
2021-08-13 11:08:18 +08:00
// Update app name for gradle project
fs . writeFileSync ( path . join ( locations . root , 'cdv-gradle-name.gradle' ) ,
'// GENERATED FILE - DO NOT EDIT\n' +
'rootProject.name = "' + name . replace ( /[/\\:<>"?*|]/g , '_' ) + '"\n' ) ;
2015-10-21 07:15:57 +08:00
// Java packages cannot support dashes
2022-04-18 09:39:54 +08:00
const androidPkgName = ( platformConfig . android _packageName ( ) || platformConfig . packageName ( ) ) . replace ( /-/g , '_' ) ;
2015-10-21 07:15:57 +08:00
2022-04-18 09:39:54 +08:00
const manifest = new AndroidManifest ( locations . manifest ) ;
const manifestId = manifest . getPackageId ( ) ;
2015-10-21 07:15:57 +08:00
manifest . getActivity ( )
2016-03-18 20:07:16 +08:00
. setOrientation ( platformConfig . getPreference ( 'orientation' ) )
2015-10-21 07:15:57 +08:00
. setLaunchMode ( findAndroidLaunchModePreference ( platformConfig ) ) ;
manifest . setVersionName ( platformConfig . version ( ) )
. setVersionCode ( platformConfig . android _versionCode ( ) || default _versionCode ( platformConfig . version ( ) ) )
2017-07-28 19:05:53 +08:00
. setPackageId ( androidPkgName )
2015-10-21 07:15:57 +08:00
. write ( ) ;
2017-06-28 04:15:04 +08:00
// Java file paths shouldn't be hard coded
2020-01-31 21:02:48 +08:00
const javaDirectory = path . join ( locations . javaSrc , manifestId . replace ( /\./g , '/' ) ) ;
2020-10-06 15:04:48 +08:00
const java _files = glob . sync ( '**/*.java' , { cwd : javaDirectory , absolute : true } ) . filter ( f => {
const contents = fs . readFileSync ( f , 'utf-8' ) ;
return /extends\s+CordovaActivity/ . test ( contents ) ;
2015-10-21 07:15:57 +08:00
} ) ;
if ( java _files . length === 0 ) {
2016-05-04 07:25:48 +08:00
throw new CordovaError ( 'No Java files found that extend CordovaActivity.' ) ;
2017-06-14 02:42:20 +08:00
} else if ( java _files . length > 1 ) {
2016-05-04 07:25:48 +08:00
events . emit ( 'log' , 'Multiple candidate Java files that extend CordovaActivity found. Guessing at the first one, ' + java _files [ 0 ] ) ;
2015-10-21 07:15:57 +08:00
}
2020-01-31 21:02:48 +08:00
const destFile = java _files [ 0 ] ;
2020-01-29 09:12:55 +08:00
2022-05-18 22:11:31 +08:00
// if package name has changed, path to MainActivity.java has to track it
const newDestFile = path . join ( locations . root , 'app' , 'src' , 'main' , 'java' , androidPkgName . replace ( /\./g , '/' ) , path . basename ( destFile ) ) ;
if ( newDestFile . toLowerCase ( ) !== destFile . toLowerCase ( ) ) {
// If package was name changed we need to create new java with main activity in path matching new package name
fs . ensureDirSync ( path . dirname ( newDestFile ) ) ;
events . emit ( 'verbose' , ` copy ${ destFile } to ${ newDestFile } ` ) ;
fs . copySync ( destFile , newDestFile ) ;
utils . replaceFileContents ( newDestFile , /package [\w.]*;/ , 'package ' + androidPkgName + ';' ) ;
events . emit ( 'verbose' , 'Wrote out Android package name "' + androidPkgName + '" to ' + newDestFile ) ;
2015-10-27 14:12:47 +08:00
// If package was name changed we need to remove old java with main activity
2022-05-18 22:11:31 +08:00
events . emit ( 'verbose' , ` remove ${ destFile } ` ) ;
fs . removeSync ( destFile ) ;
2015-10-27 14:12:47 +08:00
// remove any empty directories
2022-05-18 22:11:31 +08:00
let currentDir = path . dirname ( destFile ) ;
2022-04-18 09:39:54 +08:00
const sourcesRoot = path . resolve ( locations . root , 'src' ) ;
2017-06-14 02:42:20 +08:00
while ( currentDir !== sourcesRoot ) {
if ( fs . existsSync ( currentDir ) && fs . readdirSync ( currentDir ) . length === 0 ) {
2015-10-27 14:12:47 +08:00
fs . rmdirSync ( currentDir ) ;
currentDir = path . resolve ( currentDir , '..' ) ;
} else {
break ;
}
}
}
}
2015-10-21 07:15:57 +08:00
2022-06-30 09:49:10 +08:00
/ * *
* Updates project structure and AndroidManifest according to project ' s configuration .
*
* @ param { ConfigParser } platformConfig A project ' s configuration that will
* be used to update project
* @ param { Object } locations A map of locations for this platform
* /
function updateProjectStrings ( platformConfig , locations ) {
// Update app name by editing res/values/strings.xml
const strings = xmlHelpers . parseElementtreeSync ( locations . strings ) ;
const name = platformConfig . name ( ) ;
strings . find ( 'string[@name="app_name"]' ) . text = name . replace ( /'/g , '\\\'' ) ;
const shortName = platformConfig . shortName && platformConfig . shortName ( ) ;
if ( shortName && shortName !== name ) {
strings . find ( 'string[@name="launcher_name"]' ) . text = shortName . replace ( /'/g , '\\\'' ) ;
}
fs . writeFileSync ( locations . strings , strings . write ( { indent : 4 } ) , 'utf-8' ) ;
events . emit ( 'verbose' , 'Wrote out android application name "' + name + '" to ' + locations . strings ) ;
}
2022-06-30 19:00:25 +08:00
function warnForDeprecatedSplashScreen ( cordovaProject ) {
const hasOldSplashTags = (
cordovaProject . projectConfig . doc . findall ( './platform[@name="android"]/splash' ) || [ ]
) . length > 0 ;
if ( hasOldSplashTags ) {
events . emit ( 'warn' , 'The "<splash>" tags were detected and are no longer supported. Please migrate to the "preference" tag "AndroidWindowSplashScreenAnimatedIcon".' ) ;
}
}
2022-06-30 09:49:10 +08:00
/ * *
* @ param { ConfigParser } platformConfig A project ' s configuration that will
* be used to update project
* @ param { Object } locations A map of locations for this platform
* /
function updateProjectSplashScreen ( platformConfig , locations ) {
// res/values/themes.xml
const themes = xmlHelpers . parseElementtreeSync ( locations . themes ) ;
const splashScreenTheme = themes . find ( 'style[@name="Theme.App.SplashScreen"]' ) ;
[
'windowSplashScreenAnimatedIcon' ,
'windowSplashScreenAnimationDuration' ,
'windowSplashScreenBackground' ,
'windowSplashScreenBrandingImage' ,
'windowSplashScreenIconBackgroundColor' ,
'postSplashScreenTheme'
] . forEach ( themeKey => {
const cdvConfigPrefKey = 'Android' + themeKey . charAt ( 0 ) . toUpperCase ( ) + themeKey . slice ( 1 ) ;
const cdvConfigPrefValue = platformConfig . getPreference ( cdvConfigPrefKey , this . platform ) ;
let themeTargetNode = splashScreenTheme . find ( ` item[@name=" ${ themeKey } "] ` ) ;
switch ( themeKey ) {
case 'windowSplashScreenBackground' :
// use the user defined value for "colors.xml"
updateProjectSplashScreenBackgroundColor ( cdvConfigPrefValue , locations ) ;
// force the themes value to `@color/cdv_splashscreen_background`
themeTargetNode . text = '@color/cdv_splashscreen_background' ;
break ;
case 'windowSplashScreenAnimatedIcon' :
// handle here the cases of "png" vs "xml" (drawable)
// If "png":
// - Clear out default or previous set "drawable/ic_cdv_splashscreen.xml" if exisiting.
// - Copy png in correct mipmap dir with name "ic_cdv_splashscreen.png"
// If "xml":
// - Clear out "{mipmap}/ic_cdv_splashscreen.png" if exisiting.
// - Copy xml into drawable dir with name "ic_cdv_splashscreen.xml"
// updateProjectSplashScreenIcon()
// value should change depending on case:
// If "png": "@mipmap/ic_cdv_splashscreen"
// If "xml": "@drawable/ic_cdv_splashscreen"
updateProjectSplashScreenImage ( locations , themeKey , cdvConfigPrefKey , cdvConfigPrefValue ) ;
break ;
case 'windowSplashScreenBrandingImage' :
// display warning only when set.
if ( cdvConfigPrefValue ) {
events . emit ( 'warn' , ` " ${ themeKey } " is currently not supported by the splash screen compatibility library. https://issuetracker.google.com/issues/194301890 ` ) ;
}
updateProjectSplashScreenImage ( locations , themeKey , cdvConfigPrefKey , cdvConfigPrefValue ) ;
// force the themes value to `@color/cdv_splashscreen_icon_background`
if ( ! cdvConfigPrefValue && themeTargetNode ) {
splashScreenTheme . remove ( themeTargetNode ) ;
} else if ( cdvConfigPrefValue ) {
// if there is no current node, create a new node.
if ( ! themeTargetNode ) {
themeTargetNode = themes . getroot ( ) . makeelement ( 'item' , { name : themeKey } ) ;
splashScreenTheme . append ( themeTargetNode ) ;
}
// set the user defined color.
themeTargetNode . text = '@drawable/ic_cdv_splashscreen_branding' ;
}
break ;
case 'windowSplashScreenIconBackgroundColor' :
// use the user defined value for "colors.xml"
updateProjectSplashScreenIconBackgroundColor ( cdvConfigPrefValue , locations ) ;
// force the themes value to `@color/cdv_splashscreen_icon_background`
if ( ! cdvConfigPrefValue && themeTargetNode ) {
// currentItem.remove();
splashScreenTheme . remove ( themeTargetNode ) ;
} else if ( cdvConfigPrefValue ) {
// if there is no current color, create a new node.
if ( ! themeTargetNode ) {
themeTargetNode = themes . getroot ( ) . makeelement ( 'item' , { name : themeKey } ) ;
splashScreenTheme . append ( themeTargetNode ) ;
}
// set the user defined color.
themeTargetNode . text = '@color/cdv_splashscreen_icon_background' ;
}
break ;
case 'windowSplashScreenAnimationDuration' :
themeTargetNode . text = cdvConfigPrefValue || '200' ;
break ;
case 'postSplashScreenTheme' :
themeTargetNode . text = cdvConfigPrefValue || '@style/Theme.AppCompat.NoActionBar' ;
break ;
default :
events . emit ( 'warn' , ` The theme property " ${ themeKey } " does not exist ` ) ;
}
} ) ;
fs . writeFileSync ( locations . themes , themes . write ( { indent : 4 } ) , 'utf-8' ) ;
events . emit ( 'verbose' , 'Wrote out Android application themes to ' + locations . themes ) ;
}
/ * *
* @ param { String } splashBackgroundColor SplashScreen Background Color Hex Code
* be used to update project
* @ param { Object } locations A map of locations for this platform
* /
function updateProjectSplashScreenBackgroundColor ( splashBackgroundColor , locations ) {
if ( ! splashBackgroundColor ) { splashBackgroundColor = '#FFFFFF' ; }
// res/values/colors.xml
const colors = xmlHelpers . parseElementtreeSync ( locations . colors ) ;
colors . find ( 'color[@name="cdv_splashscreen_background"]' ) . text = splashBackgroundColor . replace ( /'/g , '\\\'' ) ;
fs . writeFileSync ( locations . colors , colors . write ( { indent : 4 } ) , 'utf-8' ) ;
events . emit ( 'verbose' , 'Wrote out Android application SplashScreen Color to ' + locations . colors ) ;
}
/ * *
* @ param { String } splashIconBackgroundColor SplashScreen Icon Background Color Hex Code
* be used to update project
* @ param { Object } locations A map of locations for this platform
* /
function updateProjectSplashScreenIconBackgroundColor ( splashIconBackgroundColor , locations ) {
// res/values/colors.xml
const colors = xmlHelpers . parseElementtreeSync ( locations . colors ) ;
// node name
const name = 'cdv_splashscreen_icon_background' ;
// get the current defined color
let currentColor = colors . find ( ` color[@name=" ${ name } "] ` ) ;
if ( ! splashIconBackgroundColor && currentColor ) {
colors . getroot ( ) . remove ( currentColor ) ;
} else if ( splashIconBackgroundColor ) {
// if there is no current color, create a new node.
if ( ! currentColor ) {
currentColor = colors . getroot ( ) . makeelement ( 'color' , { name } ) ;
colors . getroot ( ) . append ( currentColor ) ;
}
// set the user defined color.
currentColor . text = splashIconBackgroundColor . replace ( /'/g , '\\\'' ) ;
}
// write out the changes.
fs . writeFileSync ( locations . colors , colors . write ( { indent : 4 } ) , 'utf-8' ) ;
events . emit ( 'verbose' , 'Wrote out Android application SplashScreen Icon Color to ' + locations . colors ) ;
}
function cleanupAndSetProjectSplashScreenImage ( srcFile , destFilePath , possiblePreviousDestFilePath , cleanupOnly = false ) {
if ( fs . existsSync ( possiblePreviousDestFilePath ) ) {
fs . removeSync ( possiblePreviousDestFilePath ) ;
}
if ( cleanupOnly && fs . existsSync ( destFilePath ) ) {
// Also remove dest file path for cleanup even if previous was not use.
fs . removeSync ( destFilePath ) ;
}
if ( ! cleanupOnly && srcFile && fs . existsSync ( srcFile ) ) {
fs . copySync ( srcFile , destFilePath ) ;
}
}
function updateProjectSplashScreenImage ( locations , themeKey , cdvConfigPrefKey , cdvConfigPrefValue = '' ) {
const SPLASH _SCREEN _IMAGE _BY _THEME _KEY = {
windowSplashScreenAnimatedIcon : 'ic_cdv_splashscreen' ,
windowSplashScreenBrandingImage : 'ic_cdv_splashscreen_branding'
} ;
const destFileName = SPLASH _SCREEN _IMAGE _BY _THEME _KEY [ themeKey ] || null ;
if ( ! destFileName ) throw new CordovaError ( ` ${ themeKey } is not valid for image detection. ` ) ;
// Default paths of where images are saved
const destPngDir = path . join ( locations . res , 'drawable-nodpi' ) ;
const destXmlDir = path . join ( locations . res , 'drawable' ) ;
// Dest File Name and Path
const destFileNameExt = destFileName + '.xml' ;
let destFilePath = path . join ( destXmlDir , destFileNameExt ) ;
let possiblePreviousDestFilePath = path . join ( destPngDir , destFileName + '.png' ) ;
// Default Drawable Source File
2022-06-30 20:35:27 +08:00
let defaultSrcFilePath = null ;
if ( themeKey !== 'windowSplashScreenBrandingImage' ) {
try {
// coming from user project
defaultSrcFilePath = require . resolve ( 'cordova-android/templates/project/res/drawable/' + destFileNameExt ) ;
} catch ( e ) {
// coming from repo test & coho
defaultSrcFilePath = require . resolve ( '../templates/project/res/drawable/' + destFileNameExt ) ;
}
}
2022-06-30 09:49:10 +08:00
if ( ! cdvConfigPrefValue || ! fs . existsSync ( cdvConfigPrefValue ) ) {
let emitType = 'verbose' ;
let emmitMessage = ` The " ${ cdvConfigPrefKey } " is undefined. Cordova's default will be used. ` ;
if ( cdvConfigPrefValue && ! fs . existsSync ( cdvConfigPrefValue ) ) {
emitType = 'warn' ;
emmitMessage = ` The " ${ cdvConfigPrefKey } " value does not exist. Cordova's default will be used. ` ;
}
events . emit ( emitType , emmitMessage ) ;
const cleanupOnly = themeKey === 'windowSplashScreenBrandingImage' ;
cleanupAndSetProjectSplashScreenImage ( defaultSrcFilePath , destFilePath , possiblePreviousDestFilePath , cleanupOnly ) ;
return ;
}
const iconExtension = path . extname ( cdvConfigPrefValue ) . toLowerCase ( ) ;
if ( iconExtension === '.png' ) {
// Put the image at this location.
destFilePath = path . join ( destPngDir , destFileName + '.png' ) ;
// Check for this file and remove.
possiblePreviousDestFilePath = path . join ( destXmlDir , destFileName + '.xml' ) ;
// copy the png to correct mipmap folder with name of ic_cdv_splashscreen.png
// delete ic_cdv_splashscreen.xml from drawable folder
// update themes.xml windowSplashScreenAnimatedIcon value to @mipmap/ic_cdv_splashscreen
cleanupAndSetProjectSplashScreenImage ( cdvConfigPrefValue , destFilePath , possiblePreviousDestFilePath ) ;
} else if ( iconExtension === '.xml' ) {
// copy the xml to drawable folder with name of ic_cdv_splashscreen.xml
// delete ic_cdv_splashscreen.png from mipmap folder
// update themes.xml windowSplashScreenAnimatedIcon value to @drawable/ic_cdv_splashscreen
cleanupAndSetProjectSplashScreenImage ( cdvConfigPrefValue , destFilePath , possiblePreviousDestFilePath ) ;
} else {
// use the default destFilePath & possiblePreviousDestFilePath, no update require.
events . emit ( 'warn' , ` The " ${ cdvConfigPrefKey } " had an unsupported extension. Cordova's default will be used. ` ) ;
cleanupAndSetProjectSplashScreenImage ( defaultSrcFilePath , destFilePath , possiblePreviousDestFilePath ) ;
}
}
2015-10-21 07:15:57 +08:00
// Consturct the default value for versionCode as
// PATCH + MINOR * 100 + MAJOR * 10000
// see http://developer.android.com/tools/publishing/versioning.html
2017-06-14 02:42:20 +08:00
function default _versionCode ( version ) {
2022-04-18 09:39:54 +08:00
const nums = version . split ( '-' ) [ 0 ] . split ( '.' ) ;
let versionCode = 0 ;
2015-10-21 07:15:57 +08:00
if ( + nums [ 0 ] ) {
versionCode += + nums [ 0 ] * 10000 ;
}
if ( + nums [ 1 ] ) {
versionCode += + nums [ 1 ] * 100 ;
}
if ( + nums [ 2 ] ) {
versionCode += + nums [ 2 ] ;
}
2016-05-04 07:25:48 +08:00
events . emit ( 'verbose' , 'android-versionCode not found in config.xml. Generating a code based on version in config.xml (' + version + '): ' + versionCode ) ;
2015-10-21 07:15:57 +08:00
return versionCode ;
}
2017-06-14 02:42:20 +08:00
function getImageResourcePath ( resourcesDir , type , density , name , sourceName ) {
2020-12-17 05:21:35 +08:00
// Use same extension as source with special case for 9-Patch files
const ext = sourceName . endsWith ( '.9.png' )
2022-04-18 09:39:54 +08:00
? '.9.png'
: path . extname ( sourceName ) . toLowerCase ( ) ;
2020-12-17 05:21:35 +08:00
const subDir = density ? ` ${ type } - ${ density } ` : type ;
return path . join ( resourcesDir , subDir , name + ext ) ;
2016-04-20 04:28:13 +08:00
}
2018-06-05 12:43:05 +08:00
function getAdaptiveImageResourcePath ( resourcesDir , type , density , name , sourceName ) {
if ( /\.9\.png$/ . test ( sourceName ) ) {
name = name . replace ( /\.png$/ , '.9.png' ) ;
}
2022-04-18 09:39:54 +08:00
const resourcePath = path . join ( resourcesDir , ( density ? type + '-' + density + '-v26' : type ) , name ) ;
2018-06-05 12:43:05 +08:00
return resourcePath ;
}
2017-06-14 02:42:20 +08:00
function updateIcons ( cordovaProject , platformResourcesDir ) {
2020-01-31 21:02:48 +08:00
const icons = cordovaProject . projectConfig . getIcons ( 'android' ) ;
2015-10-21 07:15:57 +08:00
2018-06-05 12:43:05 +08:00
// Skip if there are no app defined icons in config.xml
2015-10-21 07:15:57 +08:00
if ( icons . length === 0 ) {
events . emit ( 'verbose' , 'This app does not have launcher icons defined' ) ;
return ;
}
2018-06-05 12:43:05 +08:00
// 1. loop icons determin if there is an error in the setup.
// 2. during initial loop, also setup for legacy support.
2020-01-31 21:02:48 +08:00
const errorMissingAttributes = [ ] ;
const errorLegacyIconNeeded = [ ] ;
2018-06-05 12:43:05 +08:00
let hasAdaptive = false ;
icons . forEach ( ( icon , key ) => {
if (
2020-01-31 21:02:48 +08:00
( icon . background && ! icon . foreground ) ||
( ! icon . background && icon . foreground ) ||
( ! icon . background && ! icon . foreground && ! icon . src )
2018-06-05 12:43:05 +08:00
) {
errorMissingAttributes . push ( icon . density ? icon . density : 'size=' + ( icon . height || icon . width ) ) ;
}
if ( icon . foreground ) {
hasAdaptive = true ;
if (
2020-01-31 21:02:48 +08:00
! icon . src &&
(
icon . foreground . startsWith ( '@color' ) ||
path . extname ( path . basename ( icon . foreground ) ) === '.xml'
2018-06-05 12:43:05 +08:00
)
) {
errorLegacyIconNeeded . push ( icon . density ? icon . density : 'size=' + ( icon . height || icon . width ) ) ;
} else if ( ! icon . src ) {
icons [ key ] . src = icon . foreground ;
}
}
} ) ;
2015-10-21 07:15:57 +08:00
2020-01-31 21:02:48 +08:00
const errorMessage = [ ] ;
2018-06-05 12:43:05 +08:00
if ( errorMissingAttributes . length > 0 ) {
errorMessage . push ( 'One of the following attributes are set but missing the other for the density type: ' + errorMissingAttributes . join ( ', ' ) + '. Please ensure that all require attributes are defined.' ) ;
}
if ( errorLegacyIconNeeded . length > 0 ) {
errorMessage . push ( 'For the following icons with the density of: ' + errorLegacyIconNeeded . join ( ', ' ) + ', adaptive foreground with a defined color or vector can not be used as a standard fallback icon for older Android devices. To support older Android environments, please provide a value for the src attribute.' ) ;
}
if ( errorMessage . length > 0 ) {
throw new CordovaError ( errorMessage . join ( ' ' ) ) ;
}
let resourceMap = Object . assign (
{ } ,
mapImageResources ( cordovaProject . root , platformResourcesDir , 'mipmap' , 'ic_launcher.png' ) ,
mapImageResources ( cordovaProject . root , platformResourcesDir , 'mipmap' , 'ic_launcher_foreground.png' ) ,
mapImageResources ( cordovaProject . root , platformResourcesDir , 'mipmap' , 'ic_launcher_background.png' ) ,
mapImageResources ( cordovaProject . root , platformResourcesDir , 'mipmap' , 'ic_launcher_foreground.xml' ) ,
mapImageResources ( cordovaProject . root , platformResourcesDir , 'mipmap' , 'ic_launcher_background.xml' ) ,
mapImageResources ( cordovaProject . root , platformResourcesDir , 'mipmap' , 'ic_launcher.xml' )
) ;
2020-01-31 21:02:48 +08:00
const preparedIcons = prepareIcons ( icons ) ;
2018-06-05 12:43:05 +08:00
if ( hasAdaptive ) {
resourceMap = updateIconResourceForAdaptive ( preparedIcons , resourceMap , platformResourcesDir ) ;
}
resourceMap = updateIconResourceForLegacy ( preparedIcons , resourceMap , platformResourcesDir ) ;
events . emit ( 'verbose' , 'Updating icons at ' + platformResourcesDir ) ;
FileUpdater . updatePaths ( resourceMap , { rootDir : cordovaProject . root } , logFileOp ) ;
}
function updateIconResourceForAdaptive ( preparedIcons , resourceMap , platformResourcesDir ) {
2020-01-31 21:02:48 +08:00
const android _icons = preparedIcons . android _icons ;
const default _icon = preparedIcons . default _icon ;
2018-06-05 12:43:05 +08:00
2022-06-30 09:49:10 +08:00
// The source paths for icons are relative to
2018-06-05 12:43:05 +08:00
// project's config.xml location, so we use it as base path.
let background ;
let foreground ;
let targetPathBackground ;
let targetPathForeground ;
2020-01-31 21:02:48 +08:00
for ( const density in android _icons ) {
2018-06-05 12:43:05 +08:00
let backgroundVal = '@mipmap/ic_launcher_background' ;
let foregroundVal = '@mipmap/ic_launcher_foreground' ;
background = android _icons [ density ] . background ;
foreground = android _icons [ density ] . foreground ;
2020-06-18 20:58:42 +08:00
if ( ! background || ! foreground ) {
// This icon isn't an adaptive icon, so skip it
continue ;
}
2018-06-05 12:43:05 +08:00
if ( background . startsWith ( '@color' ) ) {
// Colors Use Case
backgroundVal = background ; // Example: @color/background_foobar_1
} else if ( path . extname ( path . basename ( background ) ) === '.xml' ) {
// Vector Use Case
targetPathBackground = getAdaptiveImageResourcePath ( platformResourcesDir , 'mipmap' , density , 'ic_launcher_background.xml' , path . basename ( android _icons [ density ] . background ) ) ;
resourceMap [ targetPathBackground ] = android _icons [ density ] . background ;
} else if ( path . extname ( path . basename ( background ) ) === '.png' ) {
// Images Use Case
targetPathBackground = getAdaptiveImageResourcePath ( platformResourcesDir , 'mipmap' , density , 'ic_launcher_background.png' , path . basename ( android _icons [ density ] . background ) ) ;
resourceMap [ targetPathBackground ] = android _icons [ density ] . background ;
}
if ( foreground . startsWith ( '@color' ) ) {
// Colors Use Case
foregroundVal = foreground ;
} else if ( path . extname ( path . basename ( foreground ) ) === '.xml' ) {
// Vector Use Case
targetPathForeground = getAdaptiveImageResourcePath ( platformResourcesDir , 'mipmap' , density , 'ic_launcher_foreground.xml' , path . basename ( android _icons [ density ] . foreground ) ) ;
resourceMap [ targetPathForeground ] = android _icons [ density ] . foreground ;
} else if ( path . extname ( path . basename ( foreground ) ) === '.png' ) {
// Images Use Case
targetPathForeground = getAdaptiveImageResourcePath ( platformResourcesDir , 'mipmap' , density , 'ic_launcher_foreground.png' , path . basename ( android _icons [ density ] . foreground ) ) ;
resourceMap [ targetPathForeground ] = android _icons [ density ] . foreground ;
}
// create an XML for DPI and set color
const icLauncherTemplate = ` <?xml version="1.0" encoding="utf-8"?>
< adaptive - icon xmlns : android = "http://schemas.android.com/apk/res/android" >
< background android : drawable = "` + backgroundVal + `" / >
< foreground android : drawable = "` + foregroundVal + `" / >
< / a d a p t i v e - i c o n > ` ;
2020-01-31 21:02:48 +08:00
const launcherXmlPath = path . join ( platformResourcesDir , 'mipmap-' + density + '-v26' , 'ic_launcher.xml' ) ;
2018-06-05 12:43:05 +08:00
// Remove the XML from the resourceMap so the file does not get removed.
delete resourceMap [ launcherXmlPath ] ;
fs . writeFileSync ( path . resolve ( launcherXmlPath ) , icLauncherTemplate ) ;
}
// There's no "default" drawable, so assume default == mdpi.
if ( default _icon && ! android _icons . mdpi ) {
let defaultTargetPathBackground ;
let defaultTargetPathForeground ;
if ( background . startsWith ( '@color' ) ) {
// Colors Use Case
targetPathBackground = default _icon . background ;
} else if ( path . extname ( path . basename ( background ) ) === '.xml' ) {
// Vector Use Case
defaultTargetPathBackground = getAdaptiveImageResourcePath ( platformResourcesDir , 'mipmap' , 'mdpi' , 'ic_launcher_background.xml' , path . basename ( default _icon . background ) ) ;
resourceMap [ defaultTargetPathBackground ] = default _icon . background ;
} else if ( path . extname ( path . basename ( background ) ) === '.png' ) {
// Images Use Case
defaultTargetPathBackground = getAdaptiveImageResourcePath ( platformResourcesDir , 'mipmap' , 'mdpi' , 'ic_launcher_background.png' , path . basename ( default _icon . background ) ) ;
resourceMap [ defaultTargetPathBackground ] = default _icon . background ;
}
if ( foreground . startsWith ( '@color' ) ) {
// Colors Use Case
targetPathForeground = default _icon . foreground ;
} else if ( path . extname ( path . basename ( foreground ) ) === '.xml' ) {
// Vector Use Case
defaultTargetPathForeground = getAdaptiveImageResourcePath ( platformResourcesDir , 'mipmap' , 'mdpi' , 'ic_launcher_foreground.xml' , path . basename ( default _icon . foreground ) ) ;
resourceMap [ defaultTargetPathForeground ] = default _icon . foreground ;
} else if ( path . extname ( path . basename ( foreground ) ) === '.png' ) {
// Images Use Case
defaultTargetPathForeground = getAdaptiveImageResourcePath ( platformResourcesDir , 'mipmap' , 'mdpi' , 'ic_launcher_foreground.png' , path . basename ( default _icon . foreground ) ) ;
resourceMap [ defaultTargetPathForeground ] = default _icon . foreground ;
}
}
return resourceMap ;
}
function updateIconResourceForLegacy ( preparedIcons , resourceMap , platformResourcesDir ) {
2020-01-31 21:02:48 +08:00
const android _icons = preparedIcons . android _icons ;
const default _icon = preparedIcons . default _icon ;
2018-06-05 12:43:05 +08:00
2022-06-30 09:49:10 +08:00
// The source paths for icons are relative to
2018-06-05 12:43:05 +08:00
// project's config.xml location, so we use it as base path.
2022-04-18 09:39:54 +08:00
for ( const density in android _icons ) {
const targetPath = getImageResourcePath ( platformResourcesDir , 'mipmap' , density , 'ic_launcher' , path . basename ( android _icons [ density ] . src ) ) ;
2018-06-05 12:43:05 +08:00
resourceMap [ targetPath ] = android _icons [ density ] . src ;
}
// There's no "default" drawable, so assume default == mdpi.
if ( default _icon && ! android _icons . mdpi ) {
2022-04-18 09:39:54 +08:00
const defaultTargetPath = getImageResourcePath ( platformResourcesDir , 'mipmap' , 'mdpi' , 'ic_launcher' , path . basename ( default _icon . src ) ) ;
2018-06-05 12:43:05 +08:00
resourceMap [ defaultTargetPath ] = default _icon . src ;
}
return resourceMap ;
}
function prepareIcons ( icons ) {
2015-10-21 07:15:57 +08:00
// http://developer.android.com/design/style/iconography.html
2018-06-05 12:43:05 +08:00
const SIZE _TO _DENSITY _MAP = {
2015-10-21 07:15:57 +08:00
36 : 'ldpi' ,
48 : 'mdpi' ,
72 : 'hdpi' ,
96 : 'xhdpi' ,
144 : 'xxhdpi' ,
192 : 'xxxhdpi'
} ;
2018-06-05 12:43:05 +08:00
2020-01-31 21:02:48 +08:00
const android _icons = { } ;
2018-06-05 12:43:05 +08:00
let default _icon ;
2015-10-21 07:15:57 +08:00
// find the best matching icon for a given density or size
// @output android_icons
2022-04-18 09:39:54 +08:00
const parseIcon = function ( icon , icon _size ) {
2015-10-21 07:15:57 +08:00
// do I have a platform icon for that density already
2022-04-18 09:39:54 +08:00
const density = icon . density || SIZE _TO _DENSITY _MAP [ icon _size ] ;
2015-10-21 07:15:57 +08:00
if ( ! density ) {
// invalid icon defition ( or unsupported size)
return ;
}
2022-04-18 09:39:54 +08:00
const previous = android _icons [ density ] ;
2015-10-21 07:15:57 +08:00
if ( previous && previous . platform ) {
return ;
}
android _icons [ density ] = icon ;
} ;
// iterate over all icon elements to find the default icon and call parseIcon
2022-04-18 09:39:54 +08:00
for ( let i = 0 ; i < icons . length ; i ++ ) {
const icon = icons [ i ] ;
let size = icon . width ;
2018-06-05 12:43:05 +08:00
2015-10-21 07:15:57 +08:00
if ( ! size ) {
size = icon . height ;
}
2018-06-05 12:43:05 +08:00
2015-10-21 07:15:57 +08:00
if ( ! size && ! icon . density ) {
if ( default _icon ) {
2020-01-31 21:02:48 +08:00
const found = { } ;
const favor = { } ;
2018-06-05 12:43:05 +08:00
// populating found icon.
if ( icon . background && icon . foreground ) {
found . background = icon . background ;
found . foreground = icon . foreground ;
}
if ( icon . src ) {
found . src = icon . src ;
}
if ( default _icon . background && default _icon . foreground ) {
favor . background = default _icon . background ;
favor . foreground = default _icon . foreground ;
}
if ( default _icon . src ) {
favor . src = default _icon . src ;
}
events . emit ( 'verbose' , 'Found extra default icon: ' + JSON . stringify ( found ) + ' and ignoring in favor of ' + JSON . stringify ( favor ) + '.' ) ;
2015-10-21 07:15:57 +08:00
} else {
default _icon = icon ;
}
} else {
parseIcon ( icon , size ) ;
}
}
2018-06-05 12:43:05 +08:00
return {
android _icons : android _icons ,
default _icon : default _icon
} ;
2015-10-21 07:15:57 +08:00
}
2017-06-14 02:42:20 +08:00
function cleanIcons ( projectRoot , projectConfig , platformResourcesDir ) {
2022-04-18 09:39:54 +08:00
const icons = projectConfig . getIcons ( 'android' ) ;
2016-04-20 04:28:13 +08:00
2018-06-05 12:43:05 +08:00
// Skip if there are no app defined icons in config.xml
if ( icons . length === 0 ) {
events . emit ( 'verbose' , 'This app does not have launcher icons defined' ) ;
return ;
2016-04-20 04:28:13 +08:00
}
2018-06-05 12:43:05 +08:00
2020-01-31 21:02:48 +08:00
const resourceMap = Object . assign (
2018-06-05 12:43:05 +08:00
{ } ,
mapImageResources ( projectRoot , platformResourcesDir , 'mipmap' , 'ic_launcher.png' ) ,
mapImageResources ( projectRoot , platformResourcesDir , 'mipmap' , 'ic_launcher_foreground.png' ) ,
mapImageResources ( projectRoot , platformResourcesDir , 'mipmap' , 'ic_launcher_background.png' ) ,
mapImageResources ( projectRoot , platformResourcesDir , 'mipmap' , 'ic_launcher_foreground.xml' ) ,
mapImageResources ( projectRoot , platformResourcesDir , 'mipmap' , 'ic_launcher_background.xml' ) ,
mapImageResources ( projectRoot , platformResourcesDir , 'mipmap' , 'ic_launcher.xml' )
) ;
events . emit ( 'verbose' , 'Cleaning icons at ' + platformResourcesDir ) ;
// No source paths are specified in the map, so updatePaths() will delete the target files.
FileUpdater . updatePaths ( resourceMap , { rootDir : projectRoot , all : true } , logFileOp ) ;
2016-04-20 04:28:13 +08:00
}
/ * *
* Gets a map containing resources of a specified name from all drawable folders in a directory .
* /
2017-06-14 02:42:20 +08:00
function mapImageResources ( rootDir , subDir , type , resourceName ) {
2020-01-31 21:02:48 +08:00
const pathMap = { } ;
2020-12-04 16:14:19 +08:00
const globOptions = { cwd : path . join ( rootDir , subDir ) , onlyDirectories : true } ;
glob . sync ( type + '-*' , globOptions ) . forEach ( drawableFolder => {
2020-10-06 15:04:48 +08:00
const imagePath = path . join ( subDir , drawableFolder , resourceName ) ;
2016-04-20 04:28:13 +08:00
pathMap [ imagePath ] = null ;
2015-10-21 07:15:57 +08:00
} ) ;
2016-04-20 04:28:13 +08:00
return pathMap ;
2015-10-21 07:15:57 +08:00
}
2017-06-14 02:42:20 +08:00
function updateFileResources ( cordovaProject , platformDir ) {
2022-04-18 09:39:54 +08:00
const files = cordovaProject . projectConfig . getFileResources ( 'android' ) ;
2016-07-29 03:44:50 +08:00
// if there are resource-file elements in config.xml
if ( files . length === 0 ) {
events . emit ( 'verbose' , 'This app does not have additional resource files defined' ) ;
return ;
}
2022-04-18 09:39:54 +08:00
const resourceMap = { } ;
2017-06-14 02:42:20 +08:00
files . forEach ( function ( res ) {
2022-04-18 09:39:54 +08:00
const targetPath = path . join ( platformDir , res . target ) ;
2016-07-29 03:44:50 +08:00
resourceMap [ targetPath ] = res . src ;
} ) ;
events . emit ( 'verbose' , 'Updating resource files at ' + platformDir ) ;
FileUpdater . updatePaths (
resourceMap , { rootDir : cordovaProject . root } , logFileOp ) ;
}
2017-06-14 02:42:20 +08:00
function cleanFileResources ( projectRoot , projectConfig , platformDir ) {
2022-04-18 09:39:54 +08:00
const files = projectConfig . getFileResources ( 'android' , true ) ;
2016-07-29 03:44:50 +08:00
if ( files . length > 0 ) {
events . emit ( 'verbose' , 'Cleaning resource files at ' + platformDir ) ;
2022-04-18 09:39:54 +08:00
const resourceMap = { } ;
2017-06-14 02:42:20 +08:00
files . forEach ( function ( res ) {
2022-04-18 09:39:54 +08:00
const filePath = path . join ( platformDir , res . target ) ;
2016-07-29 03:44:50 +08:00
resourceMap [ filePath ] = null ;
} ) ;
FileUpdater . updatePaths (
2020-04-15 11:36:40 +08:00
resourceMap , { rootDir : projectRoot , all : true } , logFileOp ) ;
2016-07-29 03:44:50 +08:00
}
}
2015-10-21 07:15:57 +08:00
/ * *
* Gets and validates 'AndroidLaunchMode' prepference from config . xml . Returns
* preference value and warns if it doesn ' t seems to be valid
*
* @ param { ConfigParser } platformConfig A configParser instance for
* platform .
*
* @ return { String } Preference ' s value from config . xml or
* default value , if there is no such preference . The default value is
* 'singleTop'
* /
2017-06-14 02:42:20 +08:00
function findAndroidLaunchModePreference ( platformConfig ) {
2022-04-18 09:39:54 +08:00
const launchMode = platformConfig . getPreference ( 'AndroidLaunchMode' ) ;
2015-10-21 07:15:57 +08:00
if ( ! launchMode ) {
// Return a default value
return 'singleTop' ;
}
2022-04-18 09:39:54 +08:00
const expectedValues = [ 'standard' , 'singleTop' , 'singleTask' , 'singleInstance' ] ;
const valid = expectedValues . indexOf ( launchMode ) >= 0 ;
2015-10-21 07:15:57 +08:00
if ( ! valid ) {
// Note: warn, but leave the launch mode as developer wanted, in case the list of options changes in the future
events . emit ( 'warn' , 'Unrecognized value for AndroidLaunchMode preference: ' +
launchMode + '. Expected values are: ' + expectedValues . join ( ', ' ) ) ;
}
return launchMode ;
}