Icon class should support Maskable bitmap type
Test: Unit test on IconTest $ runtest --path=frameworks/base/graphics/tests/graphicstests/src/android/graphics/drawable/IconTest.java b/34196580 Change-Id: I321c4b02f17ad9426c053216c4c88616a605aacf
This commit is contained in:
@@ -13766,6 +13766,7 @@ package android.graphics.drawable {
|
||||
method public static android.graphics.drawable.Icon createWithContentUri(android.net.Uri);
|
||||
method public static android.graphics.drawable.Icon createWithData(byte[], int, int);
|
||||
method public static android.graphics.drawable.Icon createWithFilePath(java.lang.String);
|
||||
method public static android.graphics.drawable.Icon createWithMaskableBitmap(android.graphics.Bitmap);
|
||||
method public static android.graphics.drawable.Icon createWithResource(android.content.Context, int);
|
||||
method public static android.graphics.drawable.Icon createWithResource(java.lang.String, int);
|
||||
method public int describeContents();
|
||||
@@ -13849,9 +13850,9 @@ package android.graphics.drawable {
|
||||
}
|
||||
|
||||
public class MaskableIconDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback {
|
||||
ctor public MaskableIconDrawable(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable);
|
||||
method public void draw(android.graphics.Canvas);
|
||||
method public android.graphics.drawable.Drawable getBackground();
|
||||
method public static float getExtraInsetPercentage();
|
||||
method public android.graphics.drawable.Drawable getForeground();
|
||||
method public android.graphics.Path getIconMask();
|
||||
method public int getOpacity();
|
||||
@@ -13861,8 +13862,6 @@ package android.graphics.drawable {
|
||||
method public void setColorFilter(android.graphics.ColorFilter);
|
||||
method public void setOpacity(int);
|
||||
method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable);
|
||||
field public static final float DEFAULT_VIEW_PORT_SCALE = 0.6666667f;
|
||||
field public static final float MASK_SIZE = 100.0f;
|
||||
}
|
||||
|
||||
public class NinePatchDrawable extends android.graphics.drawable.Drawable {
|
||||
|
||||
@@ -14334,6 +14334,7 @@ package android.graphics.drawable {
|
||||
method public static android.graphics.drawable.Icon createWithContentUri(android.net.Uri);
|
||||
method public static android.graphics.drawable.Icon createWithData(byte[], int, int);
|
||||
method public static android.graphics.drawable.Icon createWithFilePath(java.lang.String);
|
||||
method public static android.graphics.drawable.Icon createWithMaskableBitmap(android.graphics.Bitmap);
|
||||
method public static android.graphics.drawable.Icon createWithResource(android.content.Context, int);
|
||||
method public static android.graphics.drawable.Icon createWithResource(java.lang.String, int);
|
||||
method public int describeContents();
|
||||
@@ -14417,9 +14418,9 @@ package android.graphics.drawable {
|
||||
}
|
||||
|
||||
public class MaskableIconDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback {
|
||||
ctor public MaskableIconDrawable(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable);
|
||||
method public void draw(android.graphics.Canvas);
|
||||
method public android.graphics.drawable.Drawable getBackground();
|
||||
method public static float getExtraInsetPercentage();
|
||||
method public android.graphics.drawable.Drawable getForeground();
|
||||
method public android.graphics.Path getIconMask();
|
||||
method public int getOpacity();
|
||||
@@ -14429,8 +14430,6 @@ package android.graphics.drawable {
|
||||
method public void setColorFilter(android.graphics.ColorFilter);
|
||||
method public void setOpacity(int);
|
||||
method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable);
|
||||
field public static final float DEFAULT_VIEW_PORT_SCALE = 0.6666667f;
|
||||
field public static final float MASK_SIZE = 100.0f;
|
||||
}
|
||||
|
||||
public class NinePatchDrawable extends android.graphics.drawable.Drawable {
|
||||
|
||||
@@ -13800,6 +13800,7 @@ package android.graphics.drawable {
|
||||
method public static android.graphics.drawable.Icon createWithContentUri(android.net.Uri);
|
||||
method public static android.graphics.drawable.Icon createWithData(byte[], int, int);
|
||||
method public static android.graphics.drawable.Icon createWithFilePath(java.lang.String);
|
||||
method public static android.graphics.drawable.Icon createWithMaskableBitmap(android.graphics.Bitmap);
|
||||
method public static android.graphics.drawable.Icon createWithResource(android.content.Context, int);
|
||||
method public static android.graphics.drawable.Icon createWithResource(java.lang.String, int);
|
||||
method public int describeContents();
|
||||
@@ -13883,20 +13884,19 @@ package android.graphics.drawable {
|
||||
}
|
||||
|
||||
public class MaskableIconDrawable extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback {
|
||||
ctor public MaskableIconDrawable(android.graphics.drawable.Drawable, android.graphics.drawable.Drawable);
|
||||
method public void draw(android.graphics.Canvas);
|
||||
method public android.graphics.drawable.Drawable getBackground();
|
||||
method public static float getExtraInsetPercentage();
|
||||
method public android.graphics.drawable.Drawable getForeground();
|
||||
method public android.graphics.Path getIconMask();
|
||||
method public int getOpacity();
|
||||
method public android.graphics.Region getSafeZone();
|
||||
method public void invalidateDrawable(android.graphics.drawable.Drawable);
|
||||
method public void scheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable, long);
|
||||
method public void setAlpha(int);
|
||||
method public void setColorFilter(android.graphics.ColorFilter);
|
||||
method public void setOpacity(int);
|
||||
method public void unscheduleDrawable(android.graphics.drawable.Drawable, java.lang.Runnable);
|
||||
field public static final float DEFAULT_VIEW_PORT_SCALE = 0.6666667f;
|
||||
field public static final float MASK_SIZE = 100.0f;
|
||||
}
|
||||
|
||||
public class NinePatchDrawable extends android.graphics.drawable.Drawable {
|
||||
|
||||
@@ -67,6 +67,8 @@ public final class Icon implements Parcelable {
|
||||
public static final int TYPE_DATA = 3;
|
||||
/** @hide */
|
||||
public static final int TYPE_URI = 4;
|
||||
/** @hide */
|
||||
public static final int TYPE_BITMAP_MASKABLE = 5;
|
||||
|
||||
private static final int VERSION_STREAM_SERIALIZER = 1;
|
||||
|
||||
@@ -101,6 +103,7 @@ public final class Icon implements Parcelable {
|
||||
* {@link #TYPE_RESOURCE},
|
||||
* {@link #TYPE_DATA}, or
|
||||
* {@link #TYPE_URI}.
|
||||
* {@link #TYPE_BITMAP_MASKABLE}
|
||||
* @hide
|
||||
*/
|
||||
public int getType() {
|
||||
@@ -112,7 +115,7 @@ public final class Icon implements Parcelable {
|
||||
* @hide
|
||||
*/
|
||||
public Bitmap getBitmap() {
|
||||
if (mType != TYPE_BITMAP) {
|
||||
if (mType != TYPE_BITMAP && mType != TYPE_BITMAP_MASKABLE) {
|
||||
throw new IllegalStateException("called getBitmap() on " + this);
|
||||
}
|
||||
return (Bitmap) mObj1;
|
||||
@@ -218,6 +221,7 @@ public final class Icon implements Parcelable {
|
||||
private static final String typeToString(int x) {
|
||||
switch (x) {
|
||||
case TYPE_BITMAP: return "BITMAP";
|
||||
case TYPE_BITMAP_MASKABLE: return "BITMAP_MASKABLE";
|
||||
case TYPE_DATA: return "DATA";
|
||||
case TYPE_RESOURCE: return "RESOURCE";
|
||||
case TYPE_URI: return "URI";
|
||||
@@ -285,6 +289,9 @@ public final class Icon implements Parcelable {
|
||||
switch (mType) {
|
||||
case TYPE_BITMAP:
|
||||
return new BitmapDrawable(context.getResources(), getBitmap());
|
||||
case TYPE_BITMAP_MASKABLE:
|
||||
return new MaskableIconDrawable(null,
|
||||
new BitmapDrawable(context.getResources(), getBitmap()));
|
||||
case TYPE_RESOURCE:
|
||||
if (getResources() == null) {
|
||||
// figure out where to load resources from
|
||||
@@ -388,7 +395,7 @@ public final class Icon implements Parcelable {
|
||||
* @hide
|
||||
*/
|
||||
public void convertToAshmem() {
|
||||
if (mType == TYPE_BITMAP &&
|
||||
if ((mType == TYPE_BITMAP || mType == TYPE_BITMAP_MASKABLE) &&
|
||||
getBitmap().isMutable() &&
|
||||
getBitmap().getAllocationByteCount() >= MIN_ASHMEM_ICON_SIZE) {
|
||||
setBitmap(getBitmap().createAshmemBitmap());
|
||||
@@ -409,6 +416,7 @@ public final class Icon implements Parcelable {
|
||||
|
||||
switch (mType) {
|
||||
case TYPE_BITMAP:
|
||||
case TYPE_BITMAP_MASKABLE:
|
||||
getBitmap().compress(Bitmap.CompressFormat.PNG, 100, dataStream);
|
||||
break;
|
||||
case TYPE_DATA:
|
||||
@@ -444,6 +452,8 @@ public final class Icon implements Parcelable {
|
||||
switch (type) {
|
||||
case TYPE_BITMAP:
|
||||
return createWithBitmap(BitmapFactory.decodeStream(inputStream));
|
||||
case TYPE_BITMAP_MASKABLE:
|
||||
return createWithMaskableBitmap(BitmapFactory.decodeStream(inputStream));
|
||||
case TYPE_DATA:
|
||||
final int length = inputStream.readInt();
|
||||
final byte[] data = new byte[length];
|
||||
@@ -478,6 +488,7 @@ public final class Icon implements Parcelable {
|
||||
}
|
||||
switch (mType) {
|
||||
case TYPE_BITMAP:
|
||||
case TYPE_BITMAP_MASKABLE:
|
||||
return getBitmap() == otherIcon.getBitmap();
|
||||
case TYPE_DATA:
|
||||
return getDataLength() == otherIcon.getDataLength()
|
||||
@@ -550,6 +561,20 @@ public final class Icon implements Parcelable {
|
||||
return rep;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an Icon pointing to a bitmap in memory that follows the icon design guideline defined
|
||||
* by {@link MaskableIconDrawable}.
|
||||
* @param bits A valid {@link android.graphics.Bitmap} object
|
||||
*/
|
||||
public static Icon createWithMaskableBitmap(Bitmap bits) {
|
||||
if (bits == null) {
|
||||
throw new IllegalArgumentException("Bitmap must not be null.");
|
||||
}
|
||||
final Icon rep = new Icon(TYPE_BITMAP_MASKABLE);
|
||||
rep.setBitmap(bits);
|
||||
return rep;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an Icon pointing to a compressed bitmap stored in a byte array.
|
||||
* @param data Byte array storing compressed bitmap data of a type that
|
||||
@@ -654,6 +679,7 @@ public final class Icon implements Parcelable {
|
||||
final StringBuilder sb = new StringBuilder("Icon(typ=").append(typeToString(mType));
|
||||
switch (mType) {
|
||||
case TYPE_BITMAP:
|
||||
case TYPE_BITMAP_MASKABLE:
|
||||
sb.append(" size=")
|
||||
.append(getBitmap().getWidth())
|
||||
.append("x")
|
||||
@@ -692,7 +718,7 @@ public final class Icon implements Parcelable {
|
||||
* Parcelable interface
|
||||
*/
|
||||
public int describeContents() {
|
||||
return (mType == TYPE_BITMAP || mType == TYPE_DATA)
|
||||
return (mType == TYPE_BITMAP || mType == TYPE_BITMAP_MASKABLE || mType == TYPE_DATA)
|
||||
? Parcelable.CONTENTS_FILE_DESCRIPTOR : 0;
|
||||
}
|
||||
|
||||
@@ -702,6 +728,7 @@ public final class Icon implements Parcelable {
|
||||
this(in.readInt());
|
||||
switch (mType) {
|
||||
case TYPE_BITMAP:
|
||||
case TYPE_BITMAP_MASKABLE:
|
||||
final Bitmap bits = Bitmap.CREATOR.createFromParcel(in);
|
||||
mObj1 = bits;
|
||||
break;
|
||||
@@ -740,6 +767,7 @@ public final class Icon implements Parcelable {
|
||||
dest.writeInt(mType);
|
||||
switch (mType) {
|
||||
case TYPE_BITMAP:
|
||||
case TYPE_BITMAP_MASKABLE:
|
||||
final Bitmap bits = getBitmap();
|
||||
getBitmap().writeToParcel(dest, flags);
|
||||
break;
|
||||
|
||||
@@ -20,6 +20,7 @@ import static android.graphics.drawable.Drawable.obtainAttributes;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.TestApi;
|
||||
import android.content.pm.ActivityInfo.Config;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.content.res.Resources;
|
||||
@@ -62,17 +63,22 @@ public class MaskableIconDrawable extends Drawable implements Drawable.Callback
|
||||
|
||||
/**
|
||||
* Mask path is defined inside device configuration in following dimension: [100 x 100]
|
||||
* @hide
|
||||
*/
|
||||
public static final float MASK_SIZE = 100f;
|
||||
private static final float SAFEZONE_SCALE = .9f;
|
||||
|
||||
/**
|
||||
* The view port of the layers is smaller than their intrinsic width and height by this factor.
|
||||
*
|
||||
* It is part of the API contract that all four sides of the layers are padded so as to provide
|
||||
* 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.
|
||||
*
|
||||
* Each layers will reserve 25% of it's width and height.
|
||||
*
|
||||
* As a result, the view port of the layers is smaller than their intrinsic width and height.
|
||||
*/
|
||||
public static final float DEFAULT_VIEW_PORT_SCALE = 2f / 3f;
|
||||
private static final float EXTRA_INSET_PERCENTAGE = 1 / 4f;
|
||||
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}.
|
||||
@@ -155,12 +161,17 @@ public class MaskableIconDrawable extends Drawable implements Drawable.Callback
|
||||
*
|
||||
* @param backgroundDrawable drawable that should be rendered in the background
|
||||
* @param foregroundDrawable drawable that should be rendered in the foreground
|
||||
* @hide
|
||||
*/
|
||||
public MaskableIconDrawable(Drawable backgroundDrawable,
|
||||
Drawable foregroundDrawable) {
|
||||
this((LayerState)null, null);
|
||||
addLayer(BACKGROUND_ID, createChildDrawable(backgroundDrawable));
|
||||
addLayer(FOREGROUND_ID, createChildDrawable(foregroundDrawable));
|
||||
if (backgroundDrawable != null) {
|
||||
addLayer(BACKGROUND_ID, createChildDrawable(backgroundDrawable));
|
||||
}
|
||||
if (foregroundDrawable != null) {
|
||||
addLayer(FOREGROUND_ID, createChildDrawable(foregroundDrawable));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -198,6 +209,15 @@ public class MaskableIconDrawable extends Drawable implements Drawable.Callback
|
||||
inflateLayers(r, parser, attrs, theme);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public static float getExtraInsetPercentage() {
|
||||
return EXTRA_INSET_PERCENTAGE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the mask path object used to clip the drawable
|
||||
*/
|
||||
@@ -242,13 +262,20 @@ public class MaskableIconDrawable extends Drawable implements Drawable.Callback
|
||||
int cY = bounds.centerY();
|
||||
|
||||
for (int i = 0, count = mLayerState.N_CHILDREN; i < count; i++) {
|
||||
final ChildDrawable r = mLayerState.mChildren[i];
|
||||
if (r == null) {
|
||||
continue;
|
||||
}
|
||||
final Drawable d = r.mDrawable;
|
||||
if (d == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int insetWidth = (int) (bounds.width() / (DEFAULT_VIEW_PORT_SCALE * 2));
|
||||
int insetHeight = (int) (bounds.height() / (DEFAULT_VIEW_PORT_SCALE * 2));
|
||||
final Rect outRect = mTmpOutRect;
|
||||
outRect.set(cX - insetWidth, cY - insetHeight, cX + insetWidth, cY + insetHeight);
|
||||
|
||||
final ChildDrawable r = mLayerState.mChildren[i];
|
||||
final Drawable d = r.mDrawable;
|
||||
d.setBounds(outRect);
|
||||
}
|
||||
}
|
||||
@@ -273,6 +300,9 @@ public class MaskableIconDrawable extends Drawable implements Drawable.Callback
|
||||
if (mLayersShader == null) {
|
||||
mCanvas.setBitmap(mLayersBitmap);
|
||||
for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
|
||||
if (mLayerState.mChildren[i] == null) {
|
||||
continue;
|
||||
}
|
||||
final Drawable dr = mLayerState.mChildren[i].mDrawable;
|
||||
if (dr != null) {
|
||||
dr.draw(mCanvas);
|
||||
@@ -295,6 +325,18 @@ public class MaskableIconDrawable extends Drawable implements Drawable.Callback
|
||||
outline.setConvexPath(mMask);
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@TestApi
|
||||
public Region getSafeZone() {
|
||||
mMaskMatrix.reset();
|
||||
mMaskMatrix.setScale(SAFEZONE_SCALE, SAFEZONE_SCALE, getBounds().centerX(), getBounds().centerY());
|
||||
Path p = new Path();
|
||||
mMask.transform(mMaskMatrix, p);
|
||||
Region safezoneRegion = new Region(getBounds());
|
||||
safezoneRegion.setPath(p, safezoneRegion);
|
||||
return safezoneRegion;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable Region getTransparentRegion() {
|
||||
if (mTransparentRegion.isEmpty()) {
|
||||
|
||||
@@ -18,6 +18,7 @@ package android.graphics.drawable;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Region;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.Parcel;
|
||||
@@ -107,6 +108,40 @@ public class IconTest extends AndroidTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
public void testWithMaskableBitmap() throws Exception {
|
||||
final Bitmap bm1 = Bitmap.createBitmap(150, 150, Bitmap.Config.ARGB_8888);
|
||||
|
||||
final Canvas can1 = new Canvas(bm1);
|
||||
can1.drawColor(0xFFFF0000);
|
||||
|
||||
final Icon im1 = Icon.createWithMaskableBitmap(bm1);
|
||||
|
||||
final MaskableIconDrawable draw1 = (MaskableIconDrawable) im1.loadDrawable(mContext);
|
||||
|
||||
final Bitmap test1 = Bitmap.createBitmap(
|
||||
(int)(draw1.getIntrinsicWidth() * (1 + 2 * MaskableIconDrawable.getExtraInsetPercentage())),
|
||||
(int)(draw1.getIntrinsicHeight() * (1 + 2 * MaskableIconDrawable.getExtraInsetPercentage())),
|
||||
Bitmap.Config.ARGB_8888);
|
||||
|
||||
draw1.setBounds(0, 0,
|
||||
(int) (draw1.getIntrinsicWidth() * (1 + 2 * MaskableIconDrawable.getExtraInsetPercentage())),
|
||||
(int) (draw1.getIntrinsicHeight() * (1 + 2 * MaskableIconDrawable.getExtraInsetPercentage())));
|
||||
draw1.draw(new Canvas(test1));
|
||||
|
||||
final File dir = getContext().getExternalFilesDir(null);
|
||||
L("writing temp bitmaps to %s...", dir);
|
||||
|
||||
bm1.compress(Bitmap.CompressFormat.PNG, 100,
|
||||
new FileOutputStream(new File(dir, "maskable-bitmap1-original.png")));
|
||||
test1.compress(Bitmap.CompressFormat.PNG, 100,
|
||||
new FileOutputStream(new File(dir, "maskable-bitmap1-test.png")));
|
||||
if (!equalBitmaps(bm1, test1, draw1.getSafeZone())) {
|
||||
findBitmapDifferences(bm1, test1);
|
||||
fail("maskable bitmap1 differs, check " + dir);
|
||||
}
|
||||
}
|
||||
|
||||
@SmallTest
|
||||
public void testWithBitmapResource() throws Exception {
|
||||
final Bitmap res1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape))
|
||||
@@ -294,17 +329,31 @@ public class IconTest extends AndroidTestCase {
|
||||
printBits(aPix, w, h);
|
||||
}
|
||||
boolean equalBitmaps(Bitmap a, Bitmap b) {
|
||||
return equalBitmaps(a, b, null);
|
||||
}
|
||||
|
||||
boolean equalBitmaps(Bitmap a, Bitmap b, Region region) {
|
||||
if (a.getWidth() != b.getWidth() || a.getHeight() != b.getHeight()) return false;
|
||||
|
||||
|
||||
final int w = a.getWidth();
|
||||
final int h = a.getHeight();
|
||||
int[] aPix = new int[w * h];
|
||||
int[] bPix = new int[w * h];
|
||||
|
||||
a.getPixels(aPix, 0, w, 0, 0, w, h);
|
||||
b.getPixels(bPix, 0, w, 0, 0, w, h);
|
||||
|
||||
return Arrays.equals(aPix, bPix);
|
||||
if (region != null) {
|
||||
for (int i = 0; i < w; i++) {
|
||||
for (int j = 0; j < h; j++) {
|
||||
if (region.contains(i, j) && a.getPixel(i, j) != b.getPixel(i, j)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
a.getPixels(aPix, 0, w, 0, 0, w, h);
|
||||
b.getPixels(bPix, 0, w, 0, 0, w, h);
|
||||
return Arrays.equals(aPix, bPix);
|
||||
}
|
||||
}
|
||||
|
||||
void findBitmapDifferences(Bitmap a, Bitmap b) {
|
||||
|
||||
Reference in New Issue
Block a user