[ https://issues.apache.org/jira/browse/CB-13685?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=16596611#comment-16596611 ]
ASF GitHub Bot commented on CB-13685: ------------------------------------- dpogue closed pull request #448: CB-13685 android: Adaptive Icon Support URL: https://github.com/apache/cordova-android/pull/448 This is a PR merged from a forked repository. As GitHub hides the original diff on merge, it is displayed below for the sake of provenance: As this is a foreign pull request (from a fork), the diff is supplied below (as it won't show otherwise due to GitHub magic): diff --git a/bin/templates/cordova/lib/prepare.js b/bin/templates/cordova/lib/prepare.js index ac63f8a68..9dc85ff3a 100644 --- a/bin/templates/cordova/lib/prepare.js +++ b/bin/templates/cordova/lib/prepare.js @@ -265,6 +265,14 @@ function getImageResourcePath (resourcesDir, type, density, name, sourceName) { return resourcePath; } +function getAdaptiveImageResourcePath (resourcesDir, type, density, name, sourceName) { + if (/\.9\.png$/.test(sourceName)) { + name = name.replace(/\.png$/, '.9.png'); + } + var resourcePath = path.join(resourcesDir, (density ? type + '-' + density + '-v26' : type), name); + return resourcePath; +} + function updateSplashes (cordovaProject, platformResourcesDir) { var resources = cordovaProject.projectConfig.getSplashScreens('android'); @@ -314,20 +322,197 @@ function cleanSplashes (projectRoot, projectConfig, platformResourcesDir) { } function updateIcons (cordovaProject, platformResourcesDir) { - var icons = cordovaProject.projectConfig.getIcons('android'); + let icons = cordovaProject.projectConfig.getIcons('android'); - // if there are icon elements in config.xml + // 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; } - var resourceMap = mapImageResources(cordovaProject.root, platformResourcesDir, 'mipmap', 'icon.png'); + // 1. loop icons determin if there is an error in the setup. + // 2. during initial loop, also setup for legacy support. + let errorMissingAttributes = []; + let errorLegacyIconNeeded = []; + let hasAdaptive = false; + icons.forEach((icon, key) => { + if ( + (icon.background && !icon.foreground) + || (!icon.background && icon.foreground) + || (!icon.background && !icon.foreground && !icon.src) + ) { + errorMissingAttributes.push(icon.density ? icon.density : 'size=' + (icon.height || icon.width)); + } + + if (icon.foreground) { + hasAdaptive = true; + + if ( + !icon.src + && ( + icon.foreground.startsWith('@color') + || path.extname(path.basename(icon.foreground)) === '.xml' + ) + ) { + errorLegacyIconNeeded.push(icon.density ? icon.density : 'size=' + (icon.height || icon.width)); + } else if (!icon.src) { + icons[key].src = icon.foreground; + } + } + }); - var android_icons = {}; - var default_icon; + let errorMessage = []; + 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') + ); + + let preparedIcons = prepareIcons(icons); + + 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) { + let android_icons = preparedIcons.android_icons; + let default_icon = preparedIcons.default_icon; + + // The source paths for icons and splashes are relative to + // project's config.xml location, so we use it as base path. + let background; + let foreground; + let targetPathBackground; + let targetPathForeground; + + for (let density in android_icons) { + let backgroundVal = '@mipmap/ic_launcher_background'; + let foregroundVal = '@mipmap/ic_launcher_foreground'; + + background = android_icons[density].background; + foreground = android_icons[density].foreground; + + 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 + `" /> +</adaptive-icon>`; + + let launcherXmlPath = path.join(platformResourcesDir, 'mipmap-' + density + '-v26', 'ic_launcher.xml'); + + // 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) { + let android_icons = preparedIcons.android_icons; + let default_icon = preparedIcons.default_icon; + + // The source paths for icons and splashes are relative to + // project's config.xml location, so we use it as base path. + for (var density in android_icons) { + var targetPath = getImageResourcePath(platformResourcesDir, 'mipmap', density, 'ic_launcher.png', path.basename(android_icons[density].src)); + resourceMap[targetPath] = android_icons[density].src; + } + + // There's no "default" drawable, so assume default == mdpi. + if (default_icon && !android_icons.mdpi) { + var defaultTargetPath = getImageResourcePath(platformResourcesDir, 'mipmap', 'mdpi', 'ic_launcher.png', path.basename(default_icon.src)); + resourceMap[defaultTargetPath] = default_icon.src; + } + + return resourceMap; +} + +function prepareIcons (icons) { // http://developer.android.com/design/style/iconography.html - var sizeToDensityMap = { + const SIZE_TO_DENSITY_MAP = { 36: 'ldpi', 48: 'mdpi', 72: 'hdpi', @@ -335,11 +520,15 @@ function updateIcons (cordovaProject, platformResourcesDir) { 144: 'xxhdpi', 192: 'xxxhdpi' }; + + let android_icons = {}; + let default_icon; + // find the best matching icon for a given density or size // @output android_icons var parseIcon = function (icon, icon_size) { // do I have a platform icon for that density already - var density = icon.density || sizeToDensityMap[icon_size]; + var density = icon.density || SIZE_TO_DENSITY_MAP[icon_size]; if (!density) { // invalid icon defition ( or unsupported size) return; @@ -355,12 +544,34 @@ function updateIcons (cordovaProject, platformResourcesDir) { for (var i = 0; i < icons.length; i++) { var icon = icons[i]; var size = icon.width; + if (!size) { size = icon.height; } + if (!size && !icon.density) { if (default_icon) { - events.emit('verbose', 'Found extra default icon: ' + icon.src + ' (ignoring in favor of ' + default_icon.src + ')'); + let found = {}; + let favor = {}; + + // 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) + '.'); } else { default_icon = icon; } @@ -369,36 +580,35 @@ function updateIcons (cordovaProject, platformResourcesDir) { } } - // The source paths for icons and splashes are relative to - // project's config.xml location, so we use it as base path. - for (var density in android_icons) { - var targetPath = getImageResourcePath( - platformResourcesDir, 'mipmap', density, 'icon.png', path.basename(android_icons[density].src)); - resourceMap[targetPath] = android_icons[density].src; - } - - // There's no "default" drawable, so assume default == mdpi. - if (default_icon && !android_icons.mdpi) { - var defaultTargetPath = getImageResourcePath( - platformResourcesDir, 'mipmap', 'mdpi', 'icon.png', path.basename(default_icon.src)); - resourceMap[defaultTargetPath] = default_icon.src; - } - - events.emit('verbose', 'Updating icons at ' + platformResourcesDir); - FileUpdater.updatePaths( - resourceMap, { rootDir: cordovaProject.root }, logFileOp); + return { + android_icons: android_icons, + default_icon: default_icon + }; } function cleanIcons (projectRoot, projectConfig, platformResourcesDir) { var icons = projectConfig.getIcons('android'); - if (icons.length > 0) { - var resourceMap = mapImageResources(projectRoot, platformResourcesDir, 'mipmap', 'icon.png'); - 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); + // 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; } + + let resourceMap = Object.assign( + {}, + 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); } /** diff --git a/bin/templates/project/AndroidManifest.xml b/bin/templates/project/AndroidManifest.xml index e0c53aaab..9c36963a9 100644 --- a/bin/templates/project/AndroidManifest.xml +++ b/bin/templates/project/AndroidManifest.xml @@ -30,7 +30,7 @@ <uses-permission android:name="android.permission.INTERNET" /> - <application android:icon="@mipmap/icon" android:label="@string/app_name" + <application android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:hardwareAccelerated="true" android:supportsRtl="true"> <activity android:name="__ACTIVITY__" android:label="@string/activity_name" diff --git a/bin/templates/project/res/mipmap-hdpi-v26/ic_launcher.xml b/bin/templates/project/res/mipmap-hdpi-v26/ic_launcher.xml new file mode 100644 index 000000000..be316184e --- /dev/null +++ b/bin/templates/project/res/mipmap-hdpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@mipmap/ic_launcher_background" /> + <foreground android:drawable="@mipmap/ic_launcher_foreground" /> +</adaptive-icon> \ No newline at end of file diff --git a/bin/templates/project/res/mipmap-hdpi-v26/ic_launcher_background.png b/bin/templates/project/res/mipmap-hdpi-v26/ic_launcher_background.png new file mode 100644 index 000000000..021da864f Binary files /dev/null and b/bin/templates/project/res/mipmap-hdpi-v26/ic_launcher_background.png differ diff --git a/bin/templates/project/res/mipmap-hdpi/icon.png b/bin/templates/project/res/mipmap-hdpi-v26/ic_launcher_foreground.png similarity index 100% rename from bin/templates/project/res/mipmap-hdpi/icon.png rename to bin/templates/project/res/mipmap-hdpi-v26/ic_launcher_foreground.png diff --git a/bin/templates/project/res/mipmap-hdpi/ic_launcher.png b/bin/templates/project/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..538426124 Binary files /dev/null and b/bin/templates/project/res/mipmap-hdpi/ic_launcher.png differ diff --git a/bin/templates/project/res/mipmap-ldpi-v26/ic_launcher.xml b/bin/templates/project/res/mipmap-ldpi-v26/ic_launcher.xml new file mode 100644 index 000000000..be316184e --- /dev/null +++ b/bin/templates/project/res/mipmap-ldpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@mipmap/ic_launcher_background" /> + <foreground android:drawable="@mipmap/ic_launcher_foreground" /> +</adaptive-icon> \ No newline at end of file diff --git a/bin/templates/project/res/mipmap-ldpi-v26/ic_launcher_background.png b/bin/templates/project/res/mipmap-ldpi-v26/ic_launcher_background.png new file mode 100644 index 000000000..da47a979e Binary files /dev/null and b/bin/templates/project/res/mipmap-ldpi-v26/ic_launcher_background.png differ diff --git a/bin/templates/project/res/mipmap-ldpi-v26/ic_launcher_foreground.png b/bin/templates/project/res/mipmap-ldpi-v26/ic_launcher_foreground.png new file mode 100644 index 000000000..203223aff Binary files /dev/null and b/bin/templates/project/res/mipmap-ldpi-v26/ic_launcher_foreground.png differ diff --git a/bin/templates/project/res/mipmap-ldpi/icon.png b/bin/templates/project/res/mipmap-ldpi/ic_launcher.png similarity index 100% rename from bin/templates/project/res/mipmap-ldpi/icon.png rename to bin/templates/project/res/mipmap-ldpi/ic_launcher.png diff --git a/bin/templates/project/res/mipmap-mdpi-v26/ic_launcher.xml b/bin/templates/project/res/mipmap-mdpi-v26/ic_launcher.xml new file mode 100644 index 000000000..be316184e --- /dev/null +++ b/bin/templates/project/res/mipmap-mdpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@mipmap/ic_launcher_background" /> + <foreground android:drawable="@mipmap/ic_launcher_foreground" /> +</adaptive-icon> \ No newline at end of file diff --git a/bin/templates/project/res/mipmap-mdpi-v26/ic_launcher_background.png b/bin/templates/project/res/mipmap-mdpi-v26/ic_launcher_background.png new file mode 100644 index 000000000..74e56e5b0 Binary files /dev/null and b/bin/templates/project/res/mipmap-mdpi-v26/ic_launcher_background.png differ diff --git a/bin/templates/project/res/mipmap-mdpi-v26/ic_launcher_foreground.png b/bin/templates/project/res/mipmap-mdpi-v26/ic_launcher_foreground.png new file mode 100644 index 000000000..8ca658e72 Binary files /dev/null and b/bin/templates/project/res/mipmap-mdpi-v26/ic_launcher_foreground.png differ diff --git a/bin/templates/project/res/mipmap-mdpi/icon.png b/bin/templates/project/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from bin/templates/project/res/mipmap-mdpi/icon.png rename to bin/templates/project/res/mipmap-mdpi/ic_launcher.png diff --git a/bin/templates/project/res/mipmap-xhdpi-v26/ic_launcher.xml b/bin/templates/project/res/mipmap-xhdpi-v26/ic_launcher.xml new file mode 100644 index 000000000..be316184e --- /dev/null +++ b/bin/templates/project/res/mipmap-xhdpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@mipmap/ic_launcher_background" /> + <foreground android:drawable="@mipmap/ic_launcher_foreground" /> +</adaptive-icon> \ No newline at end of file diff --git a/bin/templates/project/res/mipmap-xhdpi-v26/ic_launcher_background.png b/bin/templates/project/res/mipmap-xhdpi-v26/ic_launcher_background.png new file mode 100644 index 000000000..d0cb62dde Binary files /dev/null and b/bin/templates/project/res/mipmap-xhdpi-v26/ic_launcher_background.png differ diff --git a/bin/templates/project/res/mipmap-xhdpi-v26/ic_launcher_foreground.png b/bin/templates/project/res/mipmap-xhdpi-v26/ic_launcher_foreground.png new file mode 100644 index 000000000..ae324f2d3 Binary files /dev/null and b/bin/templates/project/res/mipmap-xhdpi-v26/ic_launcher_foreground.png differ diff --git a/bin/templates/project/res/mipmap-xhdpi/icon.png b/bin/templates/project/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from bin/templates/project/res/mipmap-xhdpi/icon.png rename to bin/templates/project/res/mipmap-xhdpi/ic_launcher.png diff --git a/bin/templates/project/res/mipmap-xxhdpi-v26/ic_launcher.xml b/bin/templates/project/res/mipmap-xxhdpi-v26/ic_launcher.xml new file mode 100644 index 000000000..be316184e --- /dev/null +++ b/bin/templates/project/res/mipmap-xxhdpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@mipmap/ic_launcher_background" /> + <foreground android:drawable="@mipmap/ic_launcher_foreground" /> +</adaptive-icon> \ No newline at end of file diff --git a/bin/templates/project/res/mipmap-xxhdpi-v26/ic_launcher_background.png b/bin/templates/project/res/mipmap-xxhdpi-v26/ic_launcher_background.png new file mode 100644 index 000000000..ec38d4032 Binary files /dev/null and b/bin/templates/project/res/mipmap-xxhdpi-v26/ic_launcher_background.png differ diff --git a/bin/templates/project/res/mipmap-xxhdpi-v26/ic_launcher_foreground.png b/bin/templates/project/res/mipmap-xxhdpi-v26/ic_launcher_foreground.png new file mode 100644 index 000000000..7983ef454 Binary files /dev/null and b/bin/templates/project/res/mipmap-xxhdpi-v26/ic_launcher_foreground.png differ diff --git a/bin/templates/project/res/mipmap-xxhdpi/icon.png b/bin/templates/project/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from bin/templates/project/res/mipmap-xxhdpi/icon.png rename to bin/templates/project/res/mipmap-xxhdpi/ic_launcher.png diff --git a/bin/templates/project/res/mipmap-xxxhdpi-v26/ic_launcher.xml b/bin/templates/project/res/mipmap-xxxhdpi-v26/ic_launcher.xml new file mode 100644 index 000000000..be316184e --- /dev/null +++ b/bin/templates/project/res/mipmap-xxxhdpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> + <background android:drawable="@mipmap/ic_launcher_background" /> + <foreground android:drawable="@mipmap/ic_launcher_foreground" /> +</adaptive-icon> \ No newline at end of file diff --git a/bin/templates/project/res/mipmap-xxxhdpi-v26/ic_launcher_background.png b/bin/templates/project/res/mipmap-xxxhdpi-v26/ic_launcher_background.png new file mode 100644 index 000000000..d652c0840 Binary files /dev/null and b/bin/templates/project/res/mipmap-xxxhdpi-v26/ic_launcher_background.png differ diff --git a/bin/templates/project/res/mipmap-xxxhdpi-v26/ic_launcher_foreground.png b/bin/templates/project/res/mipmap-xxxhdpi-v26/ic_launcher_foreground.png new file mode 100644 index 000000000..6857bef7b Binary files /dev/null and b/bin/templates/project/res/mipmap-xxxhdpi-v26/ic_launcher_foreground.png differ diff --git a/bin/templates/project/res/mipmap-xxxhdpi/icon.png b/bin/templates/project/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from bin/templates/project/res/mipmap-xxxhdpi/icon.png rename to bin/templates/project/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/spec/unit/prepare.spec.js b/spec/unit/prepare.spec.js new file mode 100644 index 000000000..3b133c152 --- /dev/null +++ b/spec/unit/prepare.spec.js @@ -0,0 +1,757 @@ +/** + 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. +*/ + +var rewire = require('rewire'); +var path = require('path'); +var CordovaError = require('cordova-common').CordovaError; + +const PATH_RESOURCE = path.join('platforms', 'android', 'app', 'src', 'main', 'res'); + +/** + * Creates blank resource map object, used for testing. + * + * @param {String} target specific resource item + */ +function createResourceMap (target) { + let resources = {}; + + [ + 'mipmap-ldpi', + 'mipmap-mdpi', + 'mipmap-hdpi', + 'mipmap-xhdpi', + 'mipmap-xxhdpi', + 'mipmap-xxxhdpi', + 'mipmap-ldpi-v26', + 'mipmap-mdpi-v26', + 'mipmap-hdpi-v26', + 'mipmap-xhdpi-v26', + 'mipmap-xxhdpi-v26', + 'mipmap-xxxhdpi-v26' + ].forEach((mipmap) => { + if (!target || target === 'ic_launcher.png') resources[path.join(PATH_RESOURCE, mipmap, 'ic_launcher.png')] = null; + if (!target || target === 'ic_launcher_foreground.png') resources[path.join(PATH_RESOURCE, mipmap, 'ic_launcher_foreground.png')] = null; + if (!target || target === 'ic_launcher_background.png') resources[path.join(PATH_RESOURCE, mipmap, 'ic_launcher_background.png')] = null; + if (!target || target === 'ic_launcher_foreground.xml') resources[path.join(PATH_RESOURCE, mipmap, 'ic_launcher_foreground.xml')] = null; + if (!target || target === 'ic_launcher_background.xml') resources[path.join(PATH_RESOURCE, mipmap, 'ic_launcher_background.xml')] = null; + + if ( + !mipmap.includes('-v26') && + (!target || target === 'ic_launcher.xml') + ) { + resources[path.join(PATH_RESOURCE, mipmap, 'ic_launcher.xml')] = null; + } + }); + + return resources; +} + +/** + * Create a mock item from the getIcon collection with the supplied updated data. + * + * @param {Object} data Changes to apply to the mock getIcon item + */ +function mockGetIconItem (data) { + return Object.assign({}, { + src: undefined, + target: undefined, + density: undefined, + platform: 'android', + width: undefined, + height: undefined, + background: undefined, + foreground: undefined + }, data); +} + +describe('updateIcons method', function () { + // Rewire + let prepare; + + // Spies + let updateIconResourceForAdaptiveSpy; + let updateIconResourceForLegacySpy; + let emitSpy; + let updatePathsSpy; + + // Mock Data + let cordovaProject; + let platformResourcesDir; + + beforeEach(function () { + prepare = rewire('../../bin/templates/cordova/lib/prepare'); + + cordovaProject = { + root: '/mock', + projectConfig: { + path: '/mock/config.xml', + cdvNamespacePrefix: 'cdv' + }, + locations: { + plugins: '/mock/plugins', + www: '/mock/www' + } + }; + platformResourcesDir = PATH_RESOURCE; + + emitSpy = jasmine.createSpy('emit'); + prepare.__set__('events', { + emit: emitSpy + }); + + updatePathsSpy = jasmine.createSpy('updatePaths'); + prepare.__set__('FileUpdater', { + updatePaths: updatePathsSpy + }); + + // mocking initial responses for mapImageResources + prepare.__set__('mapImageResources', function (rootDir, subDir, type, resourceName) { + if (resourceName.includes('ic_launcher.png')) { + return createResourceMap('ic_launcher.png'); + } else if (resourceName.includes('ic_launcher_foreground.png')) { + return createResourceMap('ic_launcher_foreground.png'); + } else if (resourceName.includes('ic_launcher_background.png')) { + return createResourceMap('ic_launcher_background.png'); + } else if (resourceName.includes('ic_launcher_foreground.xml')) { + return createResourceMap('ic_launcher_foreground.xml'); + } else if (resourceName.includes('ic_launcher_background.xml')) { + return createResourceMap('ic_launcher_background.xml'); + } else if (resourceName.includes('ic_launcher.xml')) { + return createResourceMap('ic_launcher.xml'); + } + }); + }); + + it('Test#001 : Should detect no defined icons.', function () { + const updateIcons = prepare.__get__('updateIcons'); + + // mock data. + cordovaProject.projectConfig.getIcons = function () { + return []; + }; + + updateIcons(cordovaProject, platformResourcesDir); + + // The emit was called + expect(emitSpy).toHaveBeenCalled(); + + // The emit message was. + let actual = emitSpy.calls.argsFor(0)[1]; + let expected = 'This app does not have launcher icons defined'; + expect(actual).toEqual(expected); + }); + + it('Test#002 : Should detech incorrect configrations for adaptive icon and throws error.', function () { + const updateIcons = prepare.__get__('updateIcons'); + + // mock data. + cordovaProject.projectConfig.getIcons = function () { + return [mockGetIconItem({ + density: 'mdpi', + background: 'res/icon/android/mdpi-background.png' + })]; + }; + + expect(function () { + updateIcons(cordovaProject, platformResourcesDir); + }).toThrow( + new CordovaError('One of the following attributes are set but missing the other for the density type: mdpi. Please ensure that all require attributes are defined.') + ); + }); + + it('Test#003 : Should detech incorrect configrations (missing foreground) for adaptive icon and throw an error.', function () { + const updateIcons = prepare.__get__('updateIcons'); + + // mock data. + cordovaProject.projectConfig.getIcons = function () { + return [mockGetIconItem({ + density: 'mdpi', + background: 'res/icon/android/mdpi-background.png' + })]; + }; + + expect(function () { + updateIcons(cordovaProject, platformResourcesDir); + }).toThrow( + new CordovaError('One of the following attributes are set but missing the other for the density type: mdpi. Please ensure that all require attributes are defined.') + ); + }); + + it('Test#004 : Should detech incorrect configrations (missing background) for adaptive icon and throw an error.', function () { + const updateIcons = prepare.__get__('updateIcons'); + + // mock data. + cordovaProject.projectConfig.getIcons = function () { + return [mockGetIconItem({ + density: 'mdpi', + foreground: 'res/icon/android/mdpi-foreground.png' + })]; + }; + + expect(function () { + updateIcons(cordovaProject, platformResourcesDir); + }).toThrow( + new CordovaError('One of the following attributes are set but missing the other for the density type: mdpi. Please ensure that all require attributes are defined.') + ); + }); + + it('Test#005 : Should detech incorrect configrations and throw an error.', function () { + const updateIcons = prepare.__get__('updateIcons'); + + // mock data. + cordovaProject.projectConfig.getIcons = function () { + return [mockGetIconItem({density: 'mdpi'})]; + }; + + expect(function () { + updateIcons(cordovaProject, platformResourcesDir); + }).toThrow( + new CordovaError('One of the following attributes are set but missing the other for the density type: mdpi. Please ensure that all require attributes are defined.') + ); + }); + + it('Test#006 : Should display incorrect configuration with density in message.', function () { + const updateIcons = prepare.__get__('updateIcons'); + + // mock data. + cordovaProject.projectConfig.getIcons = function () { + return [mockGetIconItem({density: 'mdpi'})]; + }; + + expect(function () { + updateIcons(cordovaProject, platformResourcesDir); + }).toThrow( + new CordovaError('One of the following attributes are set but missing the other for the density type: mdpi. Please ensure that all require attributes are defined.') + ); + }); + + it('Test#007 : Should display incorrect configuration with size in message from height.', function () { + const updateIcons = prepare.__get__('updateIcons'); + + // mock data. + cordovaProject.projectConfig.getIcons = function () { + return [mockGetIconItem({height: '192'})]; + }; + + expect(function () { + updateIcons(cordovaProject, platformResourcesDir); + }).toThrow( + new CordovaError('One of the following attributes are set but missing the other for the density type: size=192. Please ensure that all require attributes are defined.') + ); + }); + + it('Test#008 : Should display incorrect configuration with size in message from width.', function () { + const updateIcons = prepare.__get__('updateIcons'); + + // mock data. + cordovaProject.projectConfig.getIcons = function () { + return [mockGetIconItem({width: '192'})]; + }; + + expect(function () { + updateIcons(cordovaProject, platformResourcesDir); + }).toThrow( + new CordovaError('One of the following attributes are set but missing the other for the density type: size=192. Please ensure that all require attributes are defined.') + ); + }); + + it('Test#009 : Should detech incorrect configrations (missing background) for adaptive icon and throw an error.', function () { + const updateIcons = prepare.__get__('updateIcons'); + + // mock data. + cordovaProject.projectConfig.getIcons = function () { + return [mockGetIconItem({ + density: 'mdpi', + foreground: 'res/icon/android/mdpi-foreground.png' + })]; + }; + + expect(function () { + updateIcons(cordovaProject, platformResourcesDir); + }).toThrow( + new CordovaError('One of the following attributes are set but missing the other for the density type: mdpi. Please ensure that all require attributes are defined.') + ); + }); + + it('Test#010 : Should detech adaptive icon with vector foreground and throws error for missing backwards compatability settings.', function () { + const updateIcons = prepare.__get__('updateIcons'); + + // mock data. + cordovaProject.projectConfig.getIcons = function () { + return [mockGetIconItem({ + density: 'mdpi', + background: 'res/icon/android/mdpi-background.png', + foreground: 'res/icon/android/mdpi-foreground.xml' + })]; + }; + + expect(function () { + updateIcons(cordovaProject, platformResourcesDir); + }).toThrow( + new CordovaError('For the following icons with the density of: mdpi, 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.') + ); + }); + + it('Test#011 : Should detech adaptive icon with color foreground and throws error for missing backwards compatability settings.', function () { + const updateIcons = prepare.__get__('updateIcons'); + + // mock data. + cordovaProject.projectConfig.getIcons = function () { + return [mockGetIconItem({ + density: 'mdpi', + background: 'res/icon/android/mdpi-background.png', + foreground: '@color/background' + })]; + }; + + expect(function () { + updateIcons(cordovaProject, platformResourcesDir); + }).toThrow( + new CordovaError('For the following icons with the density of: mdpi, 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.') + ); + }); + + it('Test#012 : Should update paths with adaptive and standard icons. Standard icon comes from adaptive foreground', function () { + const updateIcons = prepare.__get__('updateIcons'); + + // mock data. + cordovaProject.projectConfig.getIcons = function () { + return [mockGetIconItem({ + density: 'mdpi', + background: 'res/icon/android/mdpi-background.png', + foreground: 'res/icon/android/mdpi-foreground.png' + })]; + }; + + // Creating Spies + let resourceMap = createResourceMap(); + let phaseOneModification = {}; + phaseOneModification[path.join(PATH_RESOURCE, 'mipmap-mdpi-v26', 'ic_launcher_foreground.png')] = 'res/icon/android/mdpi-foreground.png'; + phaseOneModification[path.join(PATH_RESOURCE, 'mipmap-mdpi-v26', 'ic_launcher_background.png')] = 'res/icon/android/mdpi-background.png'; + let phaseOneUpdatedIconsForAdaptive = Object.assign({}, resourceMap, phaseOneModification); + + updateIconResourceForAdaptiveSpy = jasmine.createSpy('updateIconResourceForAdaptiveSpy'); + prepare.__set__('updateIconResourceForAdaptive', function (preparedIcons, resourceMap, platformResourcesDir) { + updateIconResourceForAdaptiveSpy(); + return phaseOneUpdatedIconsForAdaptive; + }); + + let phaseTwoModification = {}; + phaseTwoModification[path.join(PATH_RESOURCE, 'mipmap-mdpi', 'ic_launcher.png')] = 'res/icon/android/mdpi-foreground.png'; + phaseTwoModification[path.join(PATH_RESOURCE, 'mipmap-mdpi-v26', 'ic_launcher_background.png')] = 'res/icon/android/mdpi-background.png'; + let phaseTwoUpdatedIconsForLegacy = Object.assign({}, phaseOneUpdatedIconsForAdaptive, phaseTwoModification); + + updateIconResourceForLegacySpy = jasmine.createSpy('updateIconResourceForLegacySpy'); + prepare.__set__('updateIconResourceForLegacy', function (preparedIcons, resourceMap, platformResourcesDir) { + updateIconResourceForLegacySpy(); + return phaseTwoUpdatedIconsForLegacy; + }); + + updateIcons(cordovaProject, platformResourcesDir); + + // The emit was called + expect(emitSpy).toHaveBeenCalled(); + + // The emit message was. + let actual = emitSpy.calls.argsFor(0)[1]; + let expected = 'Updating icons at ' + PATH_RESOURCE; + expect(actual).toEqual(expected); + + // Expected to be called. + expect(updatePathsSpy).toHaveBeenCalled(); + expect(updateIconResourceForAdaptiveSpy).toHaveBeenCalled(); + expect(updateIconResourceForLegacySpy).toHaveBeenCalled(); + + let actualResourceMap = updatePathsSpy.calls.argsFor(0)[0]; + let expectedResourceMap = phaseTwoUpdatedIconsForLegacy; + expect(actualResourceMap).toEqual(expectedResourceMap); + }); + + it('Test#013 : Should update paths with adaptive and standard icons.', function () { + const updateIcons = prepare.__get__('updateIcons'); + + // mock data. + cordovaProject.projectConfig.getIcons = function () { + return [mockGetIconItem({ + density: 'mdpi', + src: 'res/icon/android/mdpi-icon.png', + background: 'res/icon/android/mdpi-background.png', + foreground: 'res/icon/android/mdpi-foreground.png' + })]; + }; + + // Creating Spies + let resourceMap = createResourceMap(); + let phaseOneModification = {}; + phaseOneModification[path.join(PATH_RESOURCE, 'mipmap-mdpi-v26', 'ic_launcher_foreground.png')] = 'res/icon/android/mdpi-foreground.png'; + phaseOneModification[path.join(PATH_RESOURCE, 'mipmap-mdpi-v26', 'ic_launcher_background.png')] = 'res/icon/android/mdpi-background.png'; + let phaseOneUpdatedIconsForAdaptive = Object.assign({}, resourceMap, phaseOneModification); + + updateIconResourceForAdaptiveSpy = jasmine.createSpy('updateIconResourceForAdaptiveSpy'); + prepare.__set__('updateIconResourceForAdaptive', function (preparedIcons, resourceMap, platformResourcesDir) { + updateIconResourceForAdaptiveSpy(); + return phaseOneUpdatedIconsForAdaptive; + }); + + let phaseTwoModification = {}; + phaseTwoModification[path.join(PATH_RESOURCE, 'mipmap-mdpi', 'ic_launcher.png')] = 'res/icon/android/mdpi-foreground.png'; + phaseTwoModification[path.join(PATH_RESOURCE, 'mipmap-mdpi-v26', 'ic_launcher_background.png')] = 'res/icon/android/mdpi-background.png'; + let phaseTwoUpdatedIconsForLegacy = Object.assign({}, phaseOneUpdatedIconsForAdaptive, phaseTwoModification); + + updateIconResourceForLegacySpy = jasmine.createSpy('updateIconResourceForLegacySpy'); + prepare.__set__('updateIconResourceForLegacy', function (preparedIcons, resourceMap, platformResourcesDir) { + updateIconResourceForLegacySpy(); + return phaseTwoUpdatedIconsForLegacy; + }); + + updateIcons(cordovaProject, platformResourcesDir); + + // The emit was called + expect(emitSpy).toHaveBeenCalled(); + + // The emit message was. + let actual = emitSpy.calls.argsFor(0)[1]; + let expected = 'Updating icons at ' + PATH_RESOURCE; + expect(actual).toEqual(expected); + + // Expected to be called. + expect(updatePathsSpy).toHaveBeenCalled(); + expect(updateIconResourceForAdaptiveSpy).toHaveBeenCalled(); + expect(updateIconResourceForLegacySpy).toHaveBeenCalled(); + + let actualResourceMap = updatePathsSpy.calls.argsFor(0)[0]; + let expectedResourceMap = phaseTwoUpdatedIconsForLegacy; + expect(actualResourceMap).toEqual(expectedResourceMap); + }); + + it('Test#014 : Should update paths with standard icons.', function () { + const updateIcons = prepare.__get__('updateIcons'); + + // mock data. + cordovaProject.projectConfig.getIcons = function () { + return [mockGetIconItem({ + density: 'mdpi', + src: 'res/icon/android/mdpi-icon.png' + })]; + }; + + // Creating Spies + let phaseOneUpdatedIconsForAdaptive = createResourceMap(); + + updateIconResourceForAdaptiveSpy = jasmine.createSpy('updateIconResourceForAdaptiveSpy'); + prepare.__set__('updateIconResourceForAdaptive', function (preparedIcons, resourceMap, platformResourcesDir) { + updateIconResourceForAdaptiveSpy(); + return phaseOneUpdatedIconsForAdaptive; + }); + + let phaseTwoModification = {}; + phaseTwoModification[path.join(PATH_RESOURCE, 'mipmap-mdpi', 'ic_launcher.png')] = 'res/icon/android/mdpi-icon.png'; + let phaseTwoUpdatedIconsForLegacy = Object.assign({}, phaseOneUpdatedIconsForAdaptive, phaseTwoModification); + + updateIconResourceForLegacySpy = jasmine.createSpy('updateIconResourceForLegacySpy'); + prepare.__set__('updateIconResourceForLegacy', function (preparedIcons, resourceMap, platformResourcesDir) { + updateIconResourceForLegacySpy(); + return phaseTwoUpdatedIconsForLegacy; + }); + + updateIcons(cordovaProject, platformResourcesDir); + + // The emit was called + expect(emitSpy).toHaveBeenCalled(); + + // The emit message was. + let actual = emitSpy.calls.argsFor(0)[1]; + let expected = 'Updating icons at ' + PATH_RESOURCE; + expect(actual).toEqual(expected); + + // Expected to be called. + expect(updatePathsSpy).toHaveBeenCalled(); + expect(updateIconResourceForAdaptiveSpy).not.toHaveBeenCalled(); + expect(updateIconResourceForLegacySpy).toHaveBeenCalled(); + + let actualResourceMap = updatePathsSpy.calls.argsFor(0)[0]; + let expectedResourceMap = phaseTwoUpdatedIconsForLegacy; + expect(actualResourceMap).toEqual(expectedResourceMap); + }); +}); + +describe('prepareIcons method', function () { + let prepare; + let emitSpy; + let prepareIcons; + + beforeEach(function () { + prepare = rewire('../../bin/templates/cordova/lib/prepare'); + + prepareIcons = prepare.__get__('prepareIcons'); + + // Creating Spies + emitSpy = jasmine.createSpy('emit'); + prepare.__set__('events', { + emit: emitSpy + }); + }); + + it('Test#001 : should emit extra default icon found for adaptive use case.', function () { + // mock data. + let ldpi = mockGetIconItem({ + density: 'ldpi', + background: 'res/icon/android/ldpi-background.png', + foreground: 'res/icon/android/ldpi-foreground.png' + }); + + let mdpi = mockGetIconItem({ + density: 'mdpi', + background: 'res/icon/android/mdpi-background.png', + foreground: 'res/icon/android/mdpi-foreground.png' + }); + + let icons = [ldpi, mdpi]; + let actual = prepareIcons(icons); + let expected = { + android_icons: {ldpi, mdpi}, + default_icon: undefined + }; + + expect(expected).toEqual(actual); + + }); + + it('Test#002 : should emit extra default icon found for legacy use case.', function () { + // mock data. + let ldpi = mockGetIconItem({ + src: 'res/icon/android/ldpi-icon.png', + density: 'ldpi' + }); + + let mdpi = mockGetIconItem({ + src: 'res/icon/android/mdpi-icon.png', + density: 'mdpi' + }); + + let icons = [ldpi, mdpi]; + let actual = prepareIcons(icons); + let expected = { + android_icons: {ldpi, mdpi}, + default_icon: undefined + }; + + expect(expected).toEqual(actual); + + }); +}); + +describe('updateIconResourceForLegacy method', function () { + let prepare; + + // Spies + let fsWriteFileSyncSpy; + + // Mock Data + let platformResourcesDir; + let preparedIcons; + let resourceMap; + + beforeEach(function () { + prepare = rewire('../../bin/templates/cordova/lib/prepare'); + + // Mocked Data + platformResourcesDir = PATH_RESOURCE; + preparedIcons = { + android_icons: { + mdpi: mockGetIconItem({ + src: 'res/icon/android/mdpi-icon.png', + density: 'mdpi' + }) + }, + default_icon: undefined + }; + + resourceMap = createResourceMap(); + + fsWriteFileSyncSpy = jasmine.createSpy('writeFileSync'); + prepare.__set__('fs', { + writeFileSync: fsWriteFileSyncSpy + }); + }); + + it('Test#001 : Should update resource map with prepared icons.', function () { + // Get method for testing + const updateIconResourceForLegacy = prepare.__get__('updateIconResourceForLegacy'); + + // Run Test + let expectedModification = {}; + expectedModification[path.join(PATH_RESOURCE, 'mipmap-mdpi', 'ic_launcher.png')] = 'res/icon/android/mdpi-icon.png'; + let expected = Object.assign({}, resourceMap, expectedModification); + let actual = updateIconResourceForLegacy(preparedIcons, resourceMap, platformResourcesDir); + + expect(actual).toEqual(expected); + + }); +}); + +describe('updateIconResourceForAdaptive method', function () { + let prepare; + + // Spies + let fsWriteFileSyncSpy; + + // Mock Data + let platformResourcesDir; + let preparedIcons; + let resourceMap; + + beforeEach(function () { + prepare = rewire('../../bin/templates/cordova/lib/prepare'); + + // Mocked Data + platformResourcesDir = PATH_RESOURCE; + preparedIcons = { + android_icons: { + mdpi: mockGetIconItem({ + density: 'mdpi', + background: 'res/icon/android/mdpi-background.png', + foreground: 'res/icon/android/mdpi-foreground.png' + }) + }, + default_icon: undefined + }; + + resourceMap = createResourceMap(); + + fsWriteFileSyncSpy = jasmine.createSpy('writeFileSync'); + prepare.__set__('fs', { + writeFileSync: fsWriteFileSyncSpy + }); + }); + + it('Test#001 : Should update resource map with prepared icons.', function () { + // Get method for testing + const updateIconResourceForAdaptive = prepare.__get__('updateIconResourceForAdaptive'); + + // Run Test + let expectedModification = {}; + expectedModification[path.join(PATH_RESOURCE, 'mipmap-mdpi-v26', 'ic_launcher_background.png')] = 'res/icon/android/mdpi-background.png'; + expectedModification[path.join(PATH_RESOURCE, 'mipmap-mdpi-v26', 'ic_launcher_foreground.png')] = 'res/icon/android/mdpi-foreground.png'; + + let expected = Object.assign({}, resourceMap, expectedModification); + let actual = updateIconResourceForAdaptive(preparedIcons, resourceMap, platformResourcesDir); + + expect(actual).toEqual(expected); + + }); +}); + +describe('cleanIcons method', function () { + let prepare; + let emitSpy; + let updatePathsSpy; + + beforeEach(function () { + prepare = rewire('../../bin/templates/cordova/lib/prepare'); + + emitSpy = jasmine.createSpy('emit'); + prepare.__set__('events', { + emit: emitSpy + }); + + updatePathsSpy = jasmine.createSpy('updatePaths'); + prepare.__set__('FileUpdater', { + updatePaths: updatePathsSpy + }); + }); + + it('Test#001 : should detect that the app does not have defined icons.', function () { + // Mock + let icons = []; + let projectRoot = '/mock'; + let projectConfig = { + getIcons: function () { return icons; }, + path: '/mock/config.xml', + cdvNamespacePrefix: 'cdv' + }; + let platformResourcesDir = PATH_RESOURCE; + + const cleanIcons = prepare.__get__('cleanIcons'); + cleanIcons(projectRoot, projectConfig, platformResourcesDir); + + let actualEmitMessage = emitSpy.calls.argsFor(0)[1]; + expect(actualEmitMessage).toContain('This app does not have launcher icons defined'); + }); + + it('Test#002 : Should clean paths for adaptive icons.', function () { + // Mock + let icons = [mockGetIconItem({ + density: 'mdpi', + background: 'res/icon/android/mdpi-background.png', + foreground: 'res/icon/android/mdpi-foreground.png' + })]; + let projectRoot = '/mock'; + let projectConfig = { + getIcons: function () { return icons; }, + path: '/mock/config.xml', + cdvNamespacePrefix: 'cdv' + }; + let platformResourcesDir = PATH_RESOURCE; + + var expectedResourceMapBackground = createResourceMap('ic_launcher_background.png'); + + // mocking initial responses for mapImageResources + prepare.__set__('mapImageResources', function (rootDir, subDir, type, resourceName) { + if (resourceName.includes('ic_launcher_background.png')) { + return expectedResourceMapBackground; + } + }); + + const cleanIcons = prepare.__get__('cleanIcons'); + cleanIcons(projectRoot, projectConfig, platformResourcesDir); + + let actualResourceMapBackground = updatePathsSpy.calls.argsFor(0)[0]; + expect(actualResourceMapBackground).toEqual(expectedResourceMapBackground); + }); + + it('Test#003 : Should clean paths for legacy icons.', function () { + // Mock + let icons = [mockGetIconItem({ + src: 'res/icon/android/mdpi.png', + density: 'mdpi' + })]; + + let projectRoot = '/mock'; + let projectConfig = { + getIcons: function () { return icons; }, + path: '/mock/config.xml', + cdvNamespacePrefix: 'cdv' + }; + let platformResourcesDir = PATH_RESOURCE; + + var expectedResourceMap = createResourceMap(); + + // mocking initial responses for mapImageResources + prepare.__set__('mapImageResources', function (rootDir, subDir, type, resourceName) { + return expectedResourceMap; + }); + + const cleanIcons = prepare.__get__('cleanIcons'); + cleanIcons(projectRoot, projectConfig, platformResourcesDir); + + let actualResourceMap = updatePathsSpy.calls.argsFor(0)[0]; + expect(actualResourceMap).toEqual(expectedResourceMap); + }); +}); ---------------------------------------------------------------- This is an automated message from the Apache Git Service. To respond to the message, please log on GitHub and use the URL above to go to the specific comment. For queries about this service, please contact Infrastructure at: us...@infra.apache.org > Android Adaptive Icons > ---------------------- > > Key: CB-13685 > URL: https://issues.apache.org/jira/browse/CB-13685 > Project: Apache Cordova > Issue Type: Improvement > Components: cordova-android > Environment: All > Reporter: Josef Brandl > Assignee: Joe Bowser > Priority: Minor > > Starting with Android 8 Oreo (API level 26) Android allows developers to > create app icons using a background and a foreground image file. This feature > is called "adaptive icons". One major change that goes with this feature is > that icons get now clipped into a shape by the system. This leads to a very > uniform and clean design like on iOS where all icons are a rounded rectangle. > The other advantage is that visual effects can be applied to the icon by the > system due to the separation between foreground an background. > Android Studio greatly assists the developer at the creation of the app icon > resources because it creates backwards compatible icons for older devices > that don't support the adaptive icons feature. > https://developer.android.com/studio/write/image-asset-studio.html > The following resources are created. > {code} > res > ├── drawable > │ ├── ic_launcher_background.xml > │ └── ic_launcher_foreground.xml > ├── mipmap-anydpi-v26 > │ ├── ic_launcher.xml > │ └── ic_launcher_round.xml > ├── mipmap-hdpi > │ ├── ic_launcher.png > │ └── ic_launcher_round.png > ├── mipmap-mdpi > │ ├── ic_launcher.png > │ └── ic_launcher_round.png > ├── mipmap-xhdpi > │ ├── ic_launcher.png > │ └── ic_launcher_round.png > ├── mipmap-xxhdpi > │ ├── ic_launcher.png > │ └── ic_launcher_round.png > └── mipmap-xxxhdpi > ├── ic_launcher.png > └── ic_launcher_round.png > {code} > It is currently not clear how these files can be used inside a cordova > project. > - res/mipmap-anydpi-v26/ic_launcher.xml points to other image resources > (foreground, background) > - The foreground and background can be vector graphics (-> xml files in > res/drawable) > - The documentation needs to be updated > (I've never reported an issue using JIRA before - I'm only used to github. > So, please guide me if I'm doing something incorrect) -- This message was sent by Atlassian JIRA (v7.6.3#76005) --------------------------------------------------------------------- To unsubscribe, e-mail: issues-unsubscr...@cordova.apache.org For additional commands, e-mail: issues-h...@cordova.apache.org