diff options
6 files changed, 133 insertions, 11 deletions
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java index c8f64061e6fc..40deeae28335 100644 --- a/core/java/android/content/pm/LauncherApps.java +++ b/core/java/android/content/pm/LauncherApps.java @@ -37,6 +37,7 @@ import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; +import android.graphics.drawable.MaskableIconDrawable; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -803,7 +804,15 @@ public class LauncherApps { } try { final Bitmap bmp = BitmapFactory.decodeFileDescriptor(pfd.getFileDescriptor()); - return (bmp == null) ? null : new BitmapDrawable(mContext.getResources(), bmp); + if (bmp != null) { + BitmapDrawable dr = new BitmapDrawable(mContext.getResources(), bmp); + if (shortcut.hasMaskableBitmap()) { + return new MaskableIconDrawable(null, dr); + } else { + return dr; + } + } + return null; } finally { try { pfd.close(); @@ -821,7 +830,8 @@ public class LauncherApps { return loadDrawableResourceFromPackage(shortcut.getPackage(), icon.getResId(), shortcut.getUserHandle(), density); } - case Icon.TYPE_BITMAP: { + case Icon.TYPE_BITMAP: + case Icon.TYPE_BITMAP_MASKABLE: { return icon.loadDrawable(mContext); } default: diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java index b4dcdf7e4e51..f1f268306bb2 100644 --- a/core/java/android/content/pm/ShortcutInfo.java +++ b/core/java/android/content/pm/ShortcutInfo.java @@ -92,6 +92,9 @@ public final class ShortcutInfo implements Parcelable { public static final int FLAG_IMMUTABLE = 1 << 8; /** @hide */ + public static final int FLAG_MASKABLE_BITMAP = 1 << 9; + + /** @hide */ @IntDef(flag = true, value = { FLAG_DYNAMIC, @@ -103,6 +106,7 @@ public final class ShortcutInfo implements Parcelable { FLAG_DISABLED, FLAG_STRINGS_RESOLVED, FLAG_IMMUTABLE, + FLAG_MASKABLE_BITMAP, }) @Retention(RetentionPolicy.SOURCE) public @interface ShortcutFlags {} @@ -690,6 +694,7 @@ public final class ShortcutInfo implements Parcelable { switch (icon.getType()) { case Icon.TYPE_RESOURCE: case Icon.TYPE_BITMAP: + case Icon.TYPE_BITMAP_MASKABLE: break; // OK default: throw getInvalidIconException(); @@ -815,8 +820,9 @@ public final class ShortcutInfo implements Parcelable { * <p>Tints set with {@link Icon#setTint} or {@link Icon#setTintList} are not supported * and will be ignored. * - * <p>Only icons created with {@link Icon#createWithBitmap(Bitmap)} and - * {@link Icon#createWithResource} are supported. + * <p>Only icons created with {@link Icon#createWithBitmap(Bitmap)}, + * {@link Icon#createWithMaskableBitmap(Bitmap)} + * and {@link Icon#createWithResource} are supported. * Other types, such as URI-based icons, are not supported. * * @see LauncherApps#getShortcutIconDrawable(ShortcutInfo, int) @@ -1442,6 +1448,15 @@ public final class ShortcutInfo implements Parcelable { } /** + * Return whether a shortcut's icon is maskable. + * + * @hide internal/unit tests only + */ + public boolean hasMaskableBitmap() { + return hasFlags(FLAG_MASKABLE_BITMAP); + } + + /** * Return whether a shortcut only contains "key" information only or not. If true, only the * following fields are available. * <ul> diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index 3085c9cc9d29..570259ba5615 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -1581,6 +1581,11 @@ class ShortcutPackage extends ShortcutPackageItem { Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId() + " still has an icon"); } + if (si.hasMaskableBitmap() && !si.hasIconFile()) { + failed = true; + Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId() + + " has maskable bitmap but was not saved to a file."); + } if (si.hasIconFile() && si.hasIconResource()) { failed = true; Log.e(TAG_VERIFY, "Package " + getPackageName() + ": shortcut " + si.getId() diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index d8857b77d961..057e781e76dd 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -1216,7 +1216,8 @@ public class ShortcutService extends IShortcutService.Stub { // he XML we'd lose the icon. We just remove all dangling files after saving the XML. shortcut.setIconResourceId(0); shortcut.setIconResName(null); - shortcut.clearFlags(ShortcutInfo.FLAG_HAS_ICON_FILE | ShortcutInfo.FLAG_HAS_ICON_RES); + shortcut.clearFlags(ShortcutInfo.FLAG_HAS_ICON_FILE | + ShortcutInfo.FLAG_MASKABLE_BITMAP | ShortcutInfo.FLAG_HAS_ICON_RES); } public void cleanupBitmapsForPackage(@UserIdInt int userId, String packageName) { @@ -1351,7 +1352,8 @@ public class ShortcutService extends IShortcutService.Stub { shortcut.addFlags(ShortcutInfo.FLAG_HAS_ICON_RES); return; } - case Icon.TYPE_BITMAP: { + case Icon.TYPE_BITMAP: + case Icon.TYPE_BITMAP_MASKABLE: { bitmap = icon.getBitmap(); // Don't recycle in this case. break; } @@ -1382,6 +1384,9 @@ public class ShortcutService extends IShortcutService.Stub { shortcut.setBitmapPath(out.getFile().getAbsolutePath()); shortcut.addFlags(ShortcutInfo.FLAG_HAS_ICON_FILE); + if (icon.getType() == Icon.TYPE_BITMAP_MASKABLE) { + shortcut.addFlags(ShortcutInfo.FLAG_MASKABLE_BITMAP); + } } finally { IoUtils.closeQuietly(out); } 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 980aa2d34c79..0c5316743e6b 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java @@ -75,7 +75,9 @@ import android.content.pm.ShortcutInfo; import android.graphics.Bitmap; import android.graphics.Bitmap.CompressFormat; import android.graphics.BitmapFactory; +import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; +import android.graphics.drawable.MaskableIconDrawable; import android.net.Uri; import android.os.Bundle; import android.os.Handler; @@ -244,6 +246,8 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { final Icon icon1 = Icon.createWithResource(getTestContext(), R.drawable.icon1); final Icon icon2 = Icon.createWithBitmap(BitmapFactory.decodeResource( getTestContext().getResources(), R.drawable.icon2)); + final Icon icon3 = Icon.createWithMaskableBitmap(BitmapFactory.decodeResource( + getTestContext().getResources(), R.drawable.icon2)); final ShortcutInfo si1 = makeShortcut( "shortcut1", @@ -261,12 +265,18 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { icon2, makeIntent(Intent.ACTION_ASSIST, ShortcutActivity3.class), /* weight */ 12); - final ShortcutInfo si3 = makeShortcut("shortcut3"); + final ShortcutInfo si3 = makeShortcut( + "shortcut3", + "Title 3", + /* activity */ null, + icon3, + makeIntent(Intent.ACTION_ASSIST, ShortcutActivity3.class), + /* weight */ 13); - assertTrue(mManager.setDynamicShortcuts(list(si1, si2))); + assertTrue(mManager.setDynamicShortcuts(list(si1, si2, si3))); assertShortcutIds(assertAllNotKeyFieldsOnly( mManager.getDynamicShortcuts()), - "shortcut1", "shortcut2"); + "shortcut1", "shortcut2", "shortcut3"); assertEquals(2, mManager.getRemainingCallCount()); // TODO: Check fields @@ -550,7 +560,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { final Icon bmp32x32 = Icon.createWithBitmap(BitmapFactory.decodeResource( getTestContext().getResources(), R.drawable.black_32x32)); - final Icon bmp64x64 = Icon.createWithBitmap(BitmapFactory.decodeResource( + final Icon bmp64x64_maskable = Icon.createWithMaskableBitmap(BitmapFactory.decodeResource( getTestContext().getResources(), R.drawable.black_64x64)); final Icon bmp512x512 = Icon.createWithBitmap(BitmapFactory.decodeResource( getTestContext().getResources(), R.drawable.black_512x512)); @@ -561,7 +571,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { makeShortcutWithIcon("res32x32", res32x32), makeShortcutWithIcon("res64x64", res64x64), makeShortcutWithIcon("bmp32x32", bmp32x32), - makeShortcutWithIcon("bmp64x64", bmp64x64), + makeShortcutWithIcon("bmp64x64", bmp64x64_maskable), makeShortcutWithIcon("bmp512x512", bmp512x512), makeShortcut("none") ))); @@ -691,6 +701,15 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { bmp = pfdToBitmap( mLauncherApps.getShortcutIconFd(CALLING_PACKAGE_1, "bmp32x32", HANDLE_USER_P0)); assertBitmapSize(128, 128, bmp); + + Drawable dr = mLauncherApps.getShortcutIconDrawable( + makeShortcutWithIcon("bmp64x64", bmp64x64_maskable), 0); + assertTrue(dr instanceof MaskableIconDrawable); + float viewportPercentage = 1 / (1 + 2 * MaskableIconDrawable.getExtraInsetPercentage()); + assertEquals((int) (bmp64x64_maskable.getBitmap().getWidth() * viewportPercentage), + dr.getIntrinsicWidth()); + assertEquals((int) (bmp64x64_maskable.getBitmap().getHeight() * viewportPercentage), + dr.getIntrinsicHeight()); } public void testCleanupDanglingBitmaps() throws Exception { diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java index 562de4148bb1..28ec4fd27ccc 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java @@ -932,6 +932,74 @@ public class ShortcutManagerTest2 extends BaseShortcutManagerTest { dumpUserFile(USER_10); } + public void testShortcutInfoSaveAndLoad_maskableBitmap() throws InterruptedException { + mRunningUsers.put(USER_10, true); + + setCaller(CALLING_PACKAGE_1, USER_10); + + final Icon bmp32x32 = Icon.createWithMaskableBitmap(BitmapFactory.decodeResource( + getTestContext().getResources(), R.drawable.black_32x32)); + + PersistableBundle pb = new PersistableBundle(); + pb.putInt("k", 1); + ShortcutInfo sorig = new ShortcutInfo.Builder(mClientContext) + .setId("id") + .setActivity(new ComponentName(mClientContext, ShortcutActivity2.class)) + .setIcon(bmp32x32) + .setTitle("title") + .setText("text") + .setDisabledMessage("dismes") + .setCategories(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz")) + .setIntent(makeIntent("action", ShortcutActivity.class, "key", "val")) + .setRank(123) + .setExtras(pb) + .build(); + sorig.setTimestamp(mInjectedCurrentTimeMillis); + + mManager.addDynamicShortcuts(list(sorig)); + + mInjectedCurrentTimeMillis += 1; + final long now = mInjectedCurrentTimeMillis; + mInjectedCurrentTimeMillis += 1; + + dumpsysOnLogcat("before save"); + + // Save and load. + mService.saveDirtyInfo(); + initService(); + mService.handleUnlockUser(USER_10); + + dumpUserFile(USER_10); + dumpsysOnLogcat("after load"); + + ShortcutInfo si; + si = mService.getPackageShortcutForTest(CALLING_PACKAGE_1, "id", USER_10); + + assertEquals(USER_10, si.getUserId()); + assertEquals(HANDLE_USER_10, si.getUserHandle()); + assertEquals(CALLING_PACKAGE_1, si.getPackage()); + assertEquals("id", si.getId()); + assertEquals(ShortcutActivity2.class.getName(), si.getActivity().getClassName()); + assertEquals(null, si.getIcon()); + assertEquals("title", si.getTitle()); + assertEquals("text", si.getText()); + assertEquals("dismes", si.getDisabledMessage()); + assertEquals(set(ShortcutInfo.SHORTCUT_CATEGORY_CONVERSATION, "xyz"), si.getCategories()); + assertEquals("action", si.getIntent().getAction()); + assertEquals("val", si.getIntent().getStringExtra("key")); + assertEquals(0, si.getRank()); + assertEquals(1, si.getExtras().getInt("k")); + + assertEquals(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_HAS_ICON_FILE + | ShortcutInfo.FLAG_STRINGS_RESOLVED | ShortcutInfo.FLAG_MASKABLE_BITMAP, + si.getFlags()); + assertNotNull(si.getBitmapPath()); // Something should be set. + assertEquals(0, si.getIconResourceId()); + assertTrue(si.getLastChangedTimestamp() < now); + + dumpUserFile(USER_10); + } + public void testShortcutInfoSaveAndLoad_resId() throws InterruptedException { mRunningUsers.put(USER_10, true); |