Update API, add more documentation on AdaptiveIconDrawable
and fix multiple miscellaneous bugs.
Bug: 37079814
Bug: 37100106
Bug: 34829216
Test: $ runtest --path=frameworks/base/core/tests/coretests/src/android/graphics/drawable/IconTest.java
Test: $ runtest --path=frameworks/base/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
Test: $ runtest --path=frameworks/base/core/tests/coretests/src/android/grpahics/drawable/AdaptiveIconDrawableTest.java
Change-Id: I0f5b2232853031bf3860ebea2736e894d17e4d2e
diff --git a/api/current.txt b/api/current.txt
index 7c532d4..d8b0177 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -13832,7 +13832,7 @@
public class AdaptiveIconDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback {
method public void draw(android.graphics.Canvas);
method public android.graphics.drawable.Drawable getBackground();
- method public static float getExtraInsetPercentage();
+ method public static float getExtraInsetFraction();
method public android.graphics.drawable.Drawable getForeground();
method public android.graphics.Path getIconMask();
method public int getOpacity();
diff --git a/api/system-current.txt b/api/system-current.txt
index 99a5793..09819522 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -14595,7 +14595,7 @@
public class AdaptiveIconDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback {
method public void draw(android.graphics.Canvas);
method public android.graphics.drawable.Drawable getBackground();
- method public static float getExtraInsetPercentage();
+ method public static float getExtraInsetFraction();
method public android.graphics.drawable.Drawable getForeground();
method public android.graphics.Path getIconMask();
method public int getOpacity();
diff --git a/api/test-current.txt b/api/test-current.txt
index 570855f..047f9cd 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -13882,7 +13882,7 @@
public class AdaptiveIconDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback {
method public void draw(android.graphics.Canvas);
method public android.graphics.drawable.Drawable getBackground();
- method public static float getExtraInsetPercentage();
+ method public static float getExtraInsetFraction();
method public android.graphics.drawable.Drawable getForeground();
method public android.graphics.Path getIconMask();
method public int getOpacity();
diff --git a/core/java/android/util/LauncherIcons.java b/core/java/android/util/LauncherIcons.java
index e5aa2b5..89b9646 100644
--- a/core/java/android/util/LauncherIcons.java
+++ b/core/java/android/util/LauncherIcons.java
@@ -20,11 +20,8 @@
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
-import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
-import android.graphics.Path;
-import android.graphics.RectF;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
@@ -49,7 +46,7 @@
public LauncherIcons(Context context) {
mRes = context.getResources();
DisplayMetrics metrics = mRes.getDisplayMetrics();
- mShadowInset = (int) metrics.density / DisplayMetrics.DENSITY_DEFAULT;
+ mShadowInset = (int)(2 * metrics.density);
mCanvas.setDrawFilter(new PaintFlagsDrawFilter(Paint.DITHER_FLAG,
Paint.FILTER_BITMAP_FLAG));
mIconSize = (int) mRes.getDimensionPixelSize(android.R.dimen.app_icon_size);
@@ -91,7 +88,6 @@
return mShadowBitmap;
}
- int shadowSize = mIconSize - mShadowInset;
mShadowBitmap = Bitmap.createBitmap(mIconSize, mIconSize, Bitmap.Config.ALPHA_8);
mCanvas.setBitmap(mShadowBitmap);
diff --git a/core/tests/coretests/src/android/graphics/drawable/AdaptiveIconDrawableTest.java b/core/tests/coretests/src/android/graphics/drawable/AdaptiveIconDrawableTest.java
index 5ade66e..7550cb5 100644
--- a/core/tests/coretests/src/android/graphics/drawable/AdaptiveIconDrawableTest.java
+++ b/core/tests/coretests/src/android/graphics/drawable/AdaptiveIconDrawableTest.java
@@ -2,18 +2,22 @@
import static org.junit.Assert.assertTrue;
+import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Path.Direction;
import android.graphics.Rect;
+import android.graphics.RectF;
import android.graphics.Region;
import android.test.AndroidTestCase;
import android.util.Log;
+import android.util.PathParser;
import java.io.File;
import java.io.FileOutputStream;
import java.util.Arrays;
@@ -35,7 +39,7 @@
* Nothing is drawn.
*/
@Test
- public void testDrawWithoutSetBounds() throws Exception {
+ public void testDraw_withoutBounds() throws Exception {
mBackgroundDrawable = new ColorDrawable(Color.BLUE);
mForegroundDrawable = new ColorDrawable(Color.RED);
mIconDrawable = new AdaptiveIconDrawable(mBackgroundDrawable, mForegroundDrawable);
@@ -59,7 +63,7 @@
* When setBound is called, translate accordingly.
*/
@Test
- public void testDrawSetBounds() throws Exception {
+ public void testDraw_withBounds() throws Exception {
int dpi = 4 ;
int top = 18 * dpi;
int left = 18 * dpi;
@@ -102,6 +106,71 @@
}
}
+ /**
+ * When setBound isn't called before getIconMask method is called.
+ * default device config mask is returned.
+ */
+ @Test
+ public void testGetIconMask_withoutBounds() throws Exception {
+ mIconDrawable = new AdaptiveIconDrawable(mBackgroundDrawable, mForegroundDrawable);
+ Path pathFromDrawable = mIconDrawable.getIconMask();
+ Path pathFromDeviceConfig = PathParser.createPathFromPathData(
+ Resources.getSystem().getString(com.android.internal.R.string.config_icon_mask));
+
+ RectF boundFromDrawable = new RectF();
+ pathFromDrawable.computeBounds(boundFromDrawable, true);
+
+ RectF boundFromDeviceConfig = new RectF();
+ pathFromDeviceConfig.computeBounds(boundFromDeviceConfig, true);
+
+ double delta = 0.01;
+ assertEquals("left", boundFromDrawable.left, boundFromDeviceConfig.left, delta);
+ assertEquals("top", boundFromDrawable.top, boundFromDeviceConfig.top, delta);
+ assertEquals("right", boundFromDrawable.right, boundFromDeviceConfig.right, delta);
+ assertEquals("bottom", boundFromDrawable.bottom, boundFromDeviceConfig.bottom, delta);
+
+ assertTrue("path from device config is convex.", pathFromDeviceConfig.isConvex());
+ assertTrue("path from drawable is convex.", pathFromDrawable.isConvex());
+ }
+
+ @Test
+ public void testGetIconMaskAfterSetBounds() throws Exception {
+ int dpi = 4;
+ int top = 18 * dpi;
+ int left = 18 * dpi;
+ int right = 90 * dpi;
+ int bottom = 90 * dpi;
+
+ mIconDrawable = new AdaptiveIconDrawable(mBackgroundDrawable, mForegroundDrawable);
+ mIconDrawable.setBounds(left, top, right, bottom);
+ RectF maskBounds = new RectF();
+
+ mIconDrawable.getIconMask().computeBounds(maskBounds, true);
+
+ double delta = 0.01;
+ assertEquals("left", left, maskBounds.left, delta);
+ assertEquals("top", top, maskBounds.top, delta);
+ assertEquals("right", right, maskBounds.right, delta);
+ assertEquals("bottom", bottom, maskBounds.bottom, delta);
+
+ assertTrue(mIconDrawable.getIconMask().isConvex());
+ }
+
+ @Test
+ public void testGetOutline_withBounds() throws Exception {
+ int dpi = 4;
+ int top = 18 * dpi;
+ int left = 18 * dpi;
+ int right = 90 * dpi;
+ int bottom = 90 * dpi;
+
+ mIconDrawable = new AdaptiveIconDrawable(mBackgroundDrawable, mForegroundDrawable);
+ mIconDrawable.setBounds(left, top, right, bottom);
+ Outline outline = new Outline();
+ mIconDrawable.getOutline(outline);
+ assertTrue("outline path should be convex", outline.mPath.isConvex());
+ }
+
//
// Utils
//
diff --git a/core/tests/coretests/src/android/graphics/drawable/IconTest.java b/core/tests/coretests/src/android/graphics/drawable/IconTest.java
index b50955b..b7a48c7 100644
--- a/core/tests/coretests/src/android/graphics/drawable/IconTest.java
+++ b/core/tests/coretests/src/android/graphics/drawable/IconTest.java
@@ -119,13 +119,13 @@
final AdaptiveIconDrawable draw1 = (AdaptiveIconDrawable) im1.loadDrawable(mContext);
final Bitmap test1 = Bitmap.createBitmap(
- (int)(draw1.getIntrinsicWidth() * (1 + 2 * AdaptiveIconDrawable.getExtraInsetPercentage())),
- (int)(draw1.getIntrinsicHeight() * (1 + 2 * AdaptiveIconDrawable.getExtraInsetPercentage())),
+ (int)(draw1.getIntrinsicWidth() * (1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction())),
+ (int)(draw1.getIntrinsicHeight() * (1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction())),
Bitmap.Config.ARGB_8888);
draw1.setBounds(0, 0,
- (int) (draw1.getIntrinsicWidth() * (1 + 2 * AdaptiveIconDrawable.getExtraInsetPercentage())),
- (int) (draw1.getIntrinsicHeight() * (1 + 2 * AdaptiveIconDrawable.getExtraInsetPercentage())));
+ (int) (draw1.getIntrinsicWidth() * (1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction())),
+ (int) (draw1.getIntrinsicHeight() * (1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction())));
draw1.draw(new Canvas(test1));
final File dir = getContext().getExternalFilesDir(null);
diff --git a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java
index 643c0da..2ab6667 100644
--- a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java
+++ b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java
@@ -29,6 +29,7 @@
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
+import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Matrix;
import android.graphics.Outline;
@@ -51,12 +52,27 @@
import java.io.IOException;
/**
- * This drawable supports two layers: foreground and background.
- *
- * <p>The layers are clipped when rendering using the mask path defined in the device configuration.
- *
* <p>This class can also be created via XML inflation using <code><adaptive-icon></code> tag
* in addition to dynamic creation.
+ *
+ * <p>This drawable supports two drawable layers: foreground and background. The layers are clipped
+ * when rendering using the mask defined in the device configuration.
+ *
+ * <ul>
+ * <li>Both foreground and background layers should be sized at 108 x 108 dp.</li>
+ * <li>The inner 72 x 72 dp of the icon appears within the masked viewport.</li>
+ * <li>The outer 18 dp on each of the 4 sides of the layers is reserved for use by the system UI
+ * surfaces to create interesting visual effects, such as parallax or pulsing.</li>
+ * </ul>
+ *
+ * Such motion effect is achieved by internally setting the bounds of the foreground and
+ * background layer as following:
+ * <pre>
+ * Rect(getBounds().left - getBounds().getWidth() * #getExtraInsetFraction(),
+ * getBounds().top - getBounds().getHeight() * #getExtraInsetFraction(),
+ * getBounds().right + getBounds().getWidth() * #getExtraInsetFraction(),
+ * getBounds().bottom + getBounds().getHeight() * #getExtraInsetFraction())
+ * </pre>
*/
public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback {
@@ -65,7 +81,11 @@
* @hide
*/
public static final float MASK_SIZE = 100f;
- private static final float SAFEZONE_SCALE = .9f;
+
+ /**
+ * Launcher icons design guideline
+ */
+ private static final float SAFEZONE_SCALE = 72f/66f;
/**
* All four sides of the layers are padded with extra inset so as to provide
@@ -80,7 +100,7 @@
private static final float DEFAULT_VIEW_PORT_SCALE = 1f / (1 + 2 * EXTRA_INSET_PERCENTAGE);
/**
- * Clip path defined in {@link com.android.internal.R.string.config_icon_mask}.
+ * Clip path defined in R.string.config_icon_mask.
*/
private static Path sMask;
@@ -134,9 +154,10 @@
if (sMask == null) {
sMask = PathParser.createPathFromPathData(
- Resources.getSystem().getString(com.android.internal.R.string.config_icon_mask));
+ Resources.getSystem().getString(R.string.config_icon_mask));
}
- mMask = new Path();
+ mMask = PathParser.createPathFromPathData(
+ Resources.getSystem().getString(R.string.config_icon_mask));
mMaskMatrix = new Matrix();
mCanvas = new Canvas();
mTransparentRegion = new Region();
@@ -212,13 +233,24 @@
* All four sides of the layers are padded with extra inset so as to provide
* extra content to reveal within the clip path when performing affine transformations on the
* layers.
+ *
+ * @see #getForeground() and #getBackground() for more info on how this value is used
+ */
+ public static float getExtraInsetFraction() {
+ return EXTRA_INSET_PERCENTAGE;
+ }
+
+ /**
+ * @hide
*/
public static float getExtraInsetPercentage() {
return EXTRA_INSET_PERCENTAGE;
}
/**
- * Only call this method after bound is set on this drawable.
+ * When called before the bound is set, the returned path is identical to
+ * R.string.config_icon_mask. After the bound is set, the
+ * returned path's computed bound is same as the #getBounds().
*
* @return the mask path object used to clip the drawable
*/
@@ -227,6 +259,10 @@
}
/**
+ * Returns the foreground drawable managed by this class. The bound of this drawable is
+ * extended by {@link #getExtraInsetFraction()} * getBounds().width on left/right sides and by
+ * {@link #getExtraInsetFraction()} * getBounds().height on top/bottom sides.
+ *
* @return the foreground drawable managed by this drawable
*/
public Drawable getForeground() {
@@ -234,6 +270,10 @@
}
/**
+ * Returns the foreground drawable managed by this class. The bound of this drawable is
+ * extended by {@link #getExtraInsetFraction()} * getBounds().width on left/right sides and by
+ * {@link #getExtraInsetFraction()} * getBounds().height on top/bottom sides.
+ *
* @return the background drawable managed by this drawable
*/
public Drawable getBackground() {
@@ -293,10 +333,15 @@
mMaskBitmap = Bitmap.createBitmap(b.width(), b.height(), Bitmap.Config.ALPHA_8);
mLayersBitmap = Bitmap.createBitmap(b.width(), b.height(), Bitmap.Config.ARGB_8888);
}
+ // mMaskBitmap bound [0, w] x [0, h]
mCanvas.setBitmap(mMaskBitmap);
mPaint.setShader(null);
mCanvas.drawPath(mMask, mPaint);
+ // mMask bound [left, top, right, bottom]
+ mMaskMatrix.postTranslate(b.left, b.top);
+ mMask.reset();
+ sMask.transform(mMaskMatrix, mMask);
// reset everything that depends on the view bounds
mTransparentRegion.setEmpty();
mLayersShader = null;
@@ -309,6 +354,7 @@
}
if (mLayersShader == null) {
mCanvas.setBitmap(mLayersBitmap);
+ mCanvas.drawColor(Color.BLACK);
for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
if (mLayerState.mChildren[i] == null) {
continue;
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index 9861aa1..cd39d88 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -708,7 +708,7 @@
Drawable dr = mLauncherApps.getShortcutIconDrawable(
makeShortcutWithIcon("bmp64x64", bmp64x64_maskable), 0);
assertTrue(dr instanceof AdaptiveIconDrawable);
- float viewportPercentage = 1 / (1 + 2 * AdaptiveIconDrawable.getExtraInsetPercentage());
+ float viewportPercentage = 1 / (1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction());
assertEquals((int) (bmp64x64_maskable.getBitmap().getWidth() * viewportPercentage),
dr.getIntrinsicWidth());
assertEquals((int) (bmp64x64_maskable.getBitmap().getHeight() * viewportPercentage),