[ 
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

Reply via email to