diff options
8 files changed, 280 insertions, 13 deletions
diff --git a/api/current.txt b/api/current.txt index dd70a3298aeb..be07ab622650 100644 --- a/api/current.txt +++ b/api/current.txt @@ -10001,6 +10001,7 @@ package android.content.pm { method public android.content.Intent getIntent(); method public long getLastChangedTimestamp(); method public java.lang.String getPackageName(); + method public java.lang.String getText(); method public java.lang.String getTitle(); method public int getWeight(); method public boolean hasIconFile(); @@ -10028,6 +10029,7 @@ package android.content.pm { method public android.content.pm.ShortcutInfo.Builder setIcon(android.graphics.drawable.Icon); method public android.content.pm.ShortcutInfo.Builder setId(java.lang.String); method public android.content.pm.ShortcutInfo.Builder setIntent(android.content.Intent); + method public android.content.pm.ShortcutInfo.Builder setText(java.lang.String); method public android.content.pm.ShortcutInfo.Builder setTitle(java.lang.String); method public android.content.pm.ShortcutInfo.Builder setWeight(int); } diff --git a/api/system-current.txt b/api/system-current.txt index 0fe632d27d88..3eda66dd82fa 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -10399,6 +10399,7 @@ package android.content.pm { method public android.content.Intent getIntent(); method public long getLastChangedTimestamp(); method public java.lang.String getPackageName(); + method public java.lang.String getText(); method public java.lang.String getTitle(); method public int getWeight(); method public boolean hasIconFile(); @@ -10426,6 +10427,7 @@ package android.content.pm { method public android.content.pm.ShortcutInfo.Builder setIcon(android.graphics.drawable.Icon); method public android.content.pm.ShortcutInfo.Builder setId(java.lang.String); method public android.content.pm.ShortcutInfo.Builder setIntent(android.content.Intent); + method public android.content.pm.ShortcutInfo.Builder setText(java.lang.String); method public android.content.pm.ShortcutInfo.Builder setTitle(java.lang.String); method public android.content.pm.ShortcutInfo.Builder setWeight(int); } diff --git a/api/test-current.txt b/api/test-current.txt index d59fa272088d..a17929878af1 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -10011,6 +10011,7 @@ package android.content.pm { method public android.content.Intent getIntent(); method public long getLastChangedTimestamp(); method public java.lang.String getPackageName(); + method public java.lang.String getText(); method public java.lang.String getTitle(); method public int getWeight(); method public boolean hasIconFile(); @@ -10038,6 +10039,7 @@ package android.content.pm { method public android.content.pm.ShortcutInfo.Builder setIcon(android.graphics.drawable.Icon); method public android.content.pm.ShortcutInfo.Builder setId(java.lang.String); method public android.content.pm.ShortcutInfo.Builder setIntent(android.content.Intent); + method public android.content.pm.ShortcutInfo.Builder setText(java.lang.String); method public android.content.pm.ShortcutInfo.Builder setTitle(java.lang.String); method public android.content.pm.ShortcutInfo.Builder setWeight(int); } diff --git a/core/java/android/content/pm/ShortcutInfo.java b/core/java/android/content/pm/ShortcutInfo.java index ae75e3f9a156..7408c34538ab 100644 --- a/core/java/android/content/pm/ShortcutInfo.java +++ b/core/java/android/content/pm/ShortcutInfo.java @@ -118,6 +118,9 @@ public class ShortcutInfo implements Parcelable { @NonNull private String mTitle; + @Nullable + private String mText; + /** * Intent *with extras removed*. */ @@ -157,6 +160,7 @@ public class ShortcutInfo implements Parcelable { mActivityComponent = b.mActivityComponent; mIcon = b.mIcon; mTitle = b.mTitle; + mText = b.mText; mIntent = b.mIntent; if (mIntent != null) { final Bundle intentExtras = mIntent.getExtras(); @@ -176,6 +180,7 @@ public class ShortcutInfo implements Parcelable { * @hide */ public void enforceMandatoryFields() { + Preconditions.checkStringNotEmpty(mId, "Shortcut ID must be provided"); Preconditions.checkStringNotEmpty(mTitle, "Shortcut title must be provided"); Preconditions.checkNotNull(mIntent, "Shortcut Intent must be provided"); } @@ -195,16 +200,17 @@ public class ShortcutInfo implements Parcelable { if ((cloneFlags & CLONE_REMOVE_ICON) == 0) { mIcon = source.mIcon; mBitmapPath = source.mBitmapPath; + mIconResourceId = source.mIconResourceId; } mTitle = source.mTitle; + mText = source.mText; if ((cloneFlags & CLONE_REMOVE_INTENT) == 0) { mIntent = source.mIntent; mIntentPersistableExtras = source.mIntentPersistableExtras; } mWeight = source.mWeight; mExtras = source.mExtras; - mIconResourceId = source.mIconResourceId; } else { // Set this bit. mFlags |= FLAG_KEY_FIELDS_ONLY; @@ -244,6 +250,9 @@ public class ShortcutInfo implements Parcelable { if (source.mTitle != null) { mTitle = source.mTitle; } + if (source.mText != null) { + mText = source.mText; + } if (source.mIntent != null) { mIntent = source.mIntent; mIntentPersistableExtras = source.mIntentPersistableExtras; @@ -305,6 +314,8 @@ public class ShortcutInfo implements Parcelable { private String mTitle; + private String mText; + private Intent mIntent; private int mWeight; @@ -368,6 +379,15 @@ public class ShortcutInfo implements Parcelable { } /** + * Sets the text of a shortcut. This is an optional field. + */ + @NonNull + public Builder setText(@NonNull String text) { + mText = Preconditions.checkStringNotEmpty(text, "text"); + return this; + } + + /** * Sets the intent of a shortcut. This is a mandatory field. The extras must only contain * persistable information. (See {@link PersistableBundle}). */ @@ -457,6 +477,14 @@ public class ShortcutInfo implements Parcelable { } /** + * Return the shortcut text. + */ + @Nullable + public String getText() { + return mText; + } + + /** * Return the intent. * * <p>All shortcuts must have an intent, but this method will return null when @@ -630,6 +658,7 @@ public class ShortcutInfo implements Parcelable { mActivityComponent = source.readParcelable(cl); mIcon = source.readParcelable(cl); mTitle = source.readString(); + mText = source.readString(); mIntent = source.readParcelable(cl); mIntentPersistableExtras = source.readParcelable(cl); mWeight = source.readInt(); @@ -647,6 +676,7 @@ public class ShortcutInfo implements Parcelable { dest.writeParcelable(mActivityComponent, flags); dest.writeParcelable(mIcon, flags); dest.writeString(mTitle); + dest.writeString(mText); dest.writeParcelable(mIntent, flags); dest.writeParcelable(mIntentPersistableExtras, flags); dest.writeInt(mWeight); @@ -708,6 +738,9 @@ public class ShortcutInfo implements Parcelable { sb.append(", title="); sb.append(secure ? "***" : mTitle); + sb.append(", text="); + sb.append(secure ? "***" : mText); + sb.append(", icon="); sb.append(mIcon); @@ -744,7 +777,8 @@ public class ShortcutInfo implements Parcelable { /** @hide */ public ShortcutInfo(String id, String packageName, ComponentName activityComponent, - Icon icon, String title, Intent intent, PersistableBundle intentPersistableExtras, + Icon icon, String title, String text, Intent intent, + PersistableBundle intentPersistableExtras, int weight, PersistableBundle extras, long lastChangedTimestamp, int flags, int iconResId, String bitmapPath) { mId = id; @@ -752,6 +786,7 @@ public class ShortcutInfo implements Parcelable { mActivityComponent = activityComponent; mIcon = icon; mTitle = title; + mText = text; mIntent = intent; mIntentPersistableExtras = intentPersistableExtras; mWeight = weight; diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index f9414328885c..5916202a0bdb 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -55,6 +55,7 @@ class ShortcutPackage extends ShortcutPackageItem { private static final String ATTR_ID = "id"; private static final String ATTR_ACTIVITY = "activity"; private static final String ATTR_TITLE = "title"; + private static final String ATTR_TEXT = "text"; private static final String ATTR_INTENT = "intent"; private static final String ATTR_WEIGHT = "weight"; private static final String ATTR_TIMESTAMP = "timestamp"; @@ -439,6 +440,7 @@ class ShortcutPackage extends ShortcutPackageItem { ShortcutService.writeAttr(out, ATTR_ACTIVITY, si.getActivityComponent()); // writeAttr(out, "icon", si.getIcon()); // We don't save it. ShortcutService.writeAttr(out, ATTR_TITLE, si.getTitle()); + ShortcutService.writeAttr(out, ATTR_TEXT, si.getText()); ShortcutService.writeAttr(out, ATTR_INTENT, si.getIntentNoExtras()); ShortcutService.writeAttr(out, ATTR_WEIGHT, si.getWeight()); ShortcutService.writeAttr(out, ATTR_TIMESTAMP, @@ -515,6 +517,7 @@ class ShortcutPackage extends ShortcutPackageItem { ComponentName activityComponent; // Icon icon; String title; + String text; Intent intent; PersistableBundle intentPersistableExtras = null; int weight; @@ -528,6 +531,7 @@ class ShortcutPackage extends ShortcutPackageItem { activityComponent = ShortcutService.parseComponentNameAttribute(parser, ATTR_ACTIVITY); title = ShortcutService.parseStringAttribute(parser, ATTR_TITLE); + text = ShortcutService.parseStringAttribute(parser, ATTR_TEXT); intent = ShortcutService.parseIntentAttribute(parser, ATTR_INTENT); weight = (int) ShortcutService.parseLongAttribute(parser, ATTR_WEIGHT); lastChangedTimestamp = ShortcutService.parseLongAttribute(parser, ATTR_TIMESTAMP); @@ -559,7 +563,7 @@ class ShortcutPackage extends ShortcutPackageItem { throw ShortcutService.throwForInvalidTag(depth, tag); } return new ShortcutInfo( - id, packageName, activityComponent, /* icon =*/ null, title, intent, + id, packageName, activityComponent, /* icon =*/ null, title, text, intent, intentPersistableExtras, weight, extras, lastChangedTimestamp, flags, iconRes, bitmapPath); } diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutInfoTest.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutInfoTest.java index eb16a1db5876..c44ffa481f8b 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutInfoTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutInfoTest.java @@ -16,9 +16,16 @@ */ package com.android.server.pm; +import android.content.ComponentName; +import android.content.Intent; import android.content.pm.ShortcutInfo; +import android.graphics.drawable.Icon; +import android.os.Bundle; +import android.os.Parcel; +import android.os.PersistableBundle; import android.test.AndroidTestCase; +import com.android.internal.util.Preconditions; import com.android.server.testutis.TestUtils; /** @@ -33,12 +40,232 @@ import com.android.server.testutis.TestUtils; */ public class ShortcutInfoTest extends AndroidTestCase { - public void testNoId() { + public void testMissingMandatoryFields() { TestUtils.assertExpectException( IllegalArgumentException.class, "ID must be provided", () -> new ShortcutInfo.Builder(mContext).build()); + TestUtils.assertExpectException( + IllegalArgumentException.class, + "title must be provided", + () -> new ShortcutInfo.Builder(mContext).setId("id").build() + .enforceMandatoryFields()); + TestUtils.assertExpectException( + NullPointerException.class, + "Intent must be provided", + () -> new ShortcutInfo.Builder(mContext).setId("id").setTitle("x").build() + .enforceMandatoryFields()); } - // TODO Add more tests. + private ShortcutInfo parceled(ShortcutInfo si) { + Parcel p = Parcel.obtain(); + p.writeParcelable(si, 0); + p.setDataPosition(0); + ShortcutInfo si2 = p.readParcelable(getClass().getClassLoader()); + p.recycle(); + return si2; + } + + private Intent makeIntent(String action, Object... bundleKeysAndValues) { + final Intent intent = new Intent(action); + intent.replaceExtras(ShortcutManagerTest.makeBundle(bundleKeysAndValues)); + return intent; + } + + public void testParcel() { + ShortcutInfo si = parceled(new ShortcutInfo.Builder(getContext()) + .setId("id") + .setTitle("title") + .setIntent(makeIntent("action")) + .build()); + assertEquals(getContext().getPackageName(), si.getPackageName()); + assertEquals("id", si.getId()); + assertEquals("title", si.getTitle()); + assertEquals("action", si.getIntent().getAction()); + + PersistableBundle pb = new PersistableBundle(); + pb.putInt("k", 1); + + si = new ShortcutInfo.Builder(getContext()) + .setId("id") + .setActivityComponent(new ComponentName("a", "b")) + .setIcon(Icon.createWithContentUri("content://a.b.c/")) + .setTitle("title") + .setText("text") + .setIntent(makeIntent("action", "key", "val")) + .setWeight(123) + .setExtras(pb) + .build(); + si.addFlags(ShortcutInfo.FLAG_PINNED); + si.setBitmapPath("abc"); + si.setIconResourceId(456); + + si = parceled(si); + + assertEquals(getContext().getPackageName(), si.getPackageName()); + assertEquals("id", si.getId()); + assertEquals(new ComponentName("a", "b"), si.getActivityComponent()); + assertEquals("content://a.b.c/", si.getIcon().getUriString()); + assertEquals("title", si.getTitle()); + assertEquals("text", si.getText()); + assertEquals("action", si.getIntent().getAction()); + assertEquals("val", si.getIntent().getStringExtra("key")); + assertEquals(123, si.getWeight()); + assertEquals(1, si.getExtras().getInt("k")); + + assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags()); + assertEquals("abc", si.getBitmapPath()); + assertEquals(456, si.getIconResourceId()); + } + + public void testClone() { + PersistableBundle pb = new PersistableBundle(); + pb.putInt("k", 1); + ShortcutInfo sorig = new ShortcutInfo.Builder(getContext()) + .setId("id") + .setActivityComponent(new ComponentName("a", "b")) + .setIcon(Icon.createWithContentUri("content://a.b.c/")) + .setTitle("title") + .setText("text") + .setIntent(makeIntent("action", "key", "val")) + .setWeight(123) + .setExtras(pb) + .build(); + sorig.addFlags(ShortcutInfo.FLAG_PINNED); + sorig.setBitmapPath("abc"); + sorig.setIconResourceId(456); + + ShortcutInfo si = sorig.clone(/* clone flags*/ 0); + + assertEquals(getContext().getPackageName(), si.getPackageName()); + assertEquals("id", si.getId()); + assertEquals(new ComponentName("a", "b"), si.getActivityComponent()); + assertEquals("content://a.b.c/", si.getIcon().getUriString()); + assertEquals("title", si.getTitle()); + assertEquals("text", si.getText()); + assertEquals("action", si.getIntent().getAction()); + assertEquals("val", si.getIntent().getStringExtra("key")); + assertEquals(123, si.getWeight()); + assertEquals(1, si.getExtras().getInt("k")); + + assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags()); + assertEquals("abc", si.getBitmapPath()); + assertEquals(456, si.getIconResourceId()); + + si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_CREATOR); + + assertEquals(getContext().getPackageName(), si.getPackageName()); + assertEquals("id", si.getId()); + assertEquals(new ComponentName("a", "b"), si.getActivityComponent()); + assertEquals(null, si.getIcon()); + assertEquals("title", si.getTitle()); + assertEquals("text", si.getText()); + assertEquals("action", si.getIntent().getAction()); + assertEquals("val", si.getIntent().getStringExtra("key")); + assertEquals(123, si.getWeight()); + assertEquals(1, si.getExtras().getInt("k")); + + assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags()); + assertEquals(null, si.getBitmapPath()); + assertEquals(0, si.getIconResourceId()); + + si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER); + + assertEquals(getContext().getPackageName(), si.getPackageName()); + assertEquals("id", si.getId()); + assertEquals(new ComponentName("a", "b"), si.getActivityComponent()); + assertEquals(null, si.getIcon()); + assertEquals("title", si.getTitle()); + assertEquals("text", si.getText()); + assertEquals(null, si.getIntent()); + assertEquals(123, si.getWeight()); + assertEquals(1, si.getExtras().getInt("k")); + + assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags()); + assertEquals(null, si.getBitmapPath()); + assertEquals(0, si.getIconResourceId()); + + si = sorig.clone(ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO); + + assertEquals(getContext().getPackageName(), si.getPackageName()); + assertEquals("id", si.getId()); + assertEquals(null, si.getActivityComponent()); + assertEquals(null, si.getIcon()); + assertEquals(null, si.getTitle()); + assertEquals(null, si.getText()); + assertEquals(null, si.getIntent()); + assertEquals(0, si.getWeight()); + assertEquals(null, si.getExtras()); + + assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_KEY_FIELDS_ONLY, si.getFlags()); + assertEquals(null, si.getBitmapPath()); + assertEquals(0, si.getIconResourceId()); + } + + + public void testCopyNonNullFieldsFrom() { + PersistableBundle pb = new PersistableBundle(); + pb.putInt("k", 1); + ShortcutInfo sorig = new ShortcutInfo.Builder(getContext()) + .setId("id") + .setActivityComponent(new ComponentName("a", "b")) + .setIcon(Icon.createWithContentUri("content://a.b.c/")) + .setTitle("title") + .setText("text") + .setIntent(makeIntent("action", "key", "val")) + .setWeight(123) + .setExtras(pb) + .build(); + sorig.addFlags(ShortcutInfo.FLAG_PINNED); + sorig.setBitmapPath("abc"); + sorig.setIconResourceId(456); + + ShortcutInfo si; + + si = sorig.clone(/* flags=*/ 0); + si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id") + .setActivityComponent(new ComponentName("x", "y")).build()); + assertEquals(new ComponentName("x", "y"), si.getActivityComponent()); + + si = sorig.clone(/* flags=*/ 0); + si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id") + .setIcon(Icon.createWithContentUri("content://x.y.z/")).build()); + assertEquals("content://x.y.z/", si.getIcon().getUriString()); + + si = sorig.clone(/* flags=*/ 0); + si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id") + .setTitle("xyz").build()); + assertEquals("xyz", si.getTitle()); + + si = sorig.clone(/* flags=*/ 0); + si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id") + .setText("xxx").build()); + assertEquals("xxx", si.getText()); + + si = sorig.clone(/* flags=*/ 0); + si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id") + .setIntent(makeIntent("action2")).build()); + assertEquals("action2", si.getIntent().getAction()); + assertEquals(null, si.getIntent().getStringExtra("key")); + + si = sorig.clone(/* flags=*/ 0); + si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id") + .setIntent(makeIntent("action3", "key", "x")).build()); + assertEquals("action3", si.getIntent().getAction()); + assertEquals("x", si.getIntent().getStringExtra("key")); + + si = sorig.clone(/* flags=*/ 0); + si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id") + .setWeight(999).build()); + assertEquals(999, si.getWeight()); + + + PersistableBundle pb2 = new PersistableBundle(); + pb2.putInt("x", 99); + + si = sorig.clone(/* flags=*/ 0); + si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id") + .setExtras(pb2).build()); + assertEquals(99, si.getExtras().getInt("x")); + } } diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java index f034d55d9736..5d2924283ef7 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest.java @@ -646,7 +646,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase { runTestOnUiThread(() -> {}); } - private static Bundle makeBundle(Object... keysAndValues) { + public static Bundle makeBundle(Object... keysAndValues) { Preconditions.checkState((keysAndValues.length % 2) == 0); if (keysAndValues.length == 0) { diff --git a/services/tests/servicestests/src/com/android/server/testutis/TestUtils.java b/services/tests/servicestests/src/com/android/server/testutis/TestUtils.java index 52e8f37f0b6c..d2a44841ee0d 100644 --- a/services/tests/servicestests/src/com/android/server/testutis/TestUtils.java +++ b/services/tests/servicestests/src/com/android/server/testutis/TestUtils.java @@ -24,19 +24,14 @@ public class TestUtils { } public static void assertExpectException(Class<? extends Throwable> expectedExceptionType, - Runnable r) { - assertExpectException(expectedExceptionType, null, r); - } - - public static void assertExpectException(Class<? extends Throwable> expectedExceptionType, String expectedExceptionMessageRegex, Runnable r) { try { r.run(); - Assert.fail("Expected exception type " + expectedExceptionType.getClass().getName() + Assert.fail("Expected exception type " + expectedExceptionType.getName() + " was not thrown"); } catch (Throwable e) { Assert.assertTrue( - "Expected exception type was " + expectedExceptionType.getClass().getName() + "Expected exception type was " + expectedExceptionType.getName() + " but caught " + e.getClass().getName(), expectedExceptionType.isAssignableFrom(e.getClass())); if (expectedExceptionMessageRegex != null) { |