Merge "ShortcutManager: Support manifest shortcuts" into nyc-mr1-dev

am: 05e0a9f3cf

* commit '05e0a9f3cf632e237654c13f6d223eb22ea3be89':
  ShortcutManager: Support manifest shortcuts

Change-Id: Ib0a2620e92249452130a681208424fbddfa1935b
This commit is contained in:
Makoto Onuki
2016-05-27 17:34:04 +00:00
committed by android-build-merger
28 changed files with 2464 additions and 394 deletions

View File

@@ -1106,6 +1106,15 @@ package android {
field public static final int shareInterpolator = 16843195; // 0x10101bb field public static final int shareInterpolator = 16843195; // 0x10101bb
field public static final int sharedUserId = 16842763; // 0x101000b field public static final int sharedUserId = 16842763; // 0x101000b
field public static final int sharedUserLabel = 16843361; // 0x1010261 field public static final int sharedUserLabel = 16843361; // 0x1010261
field public static final int shortcutCategories = 16844078; // 0x101052e
field public static final int shortcutDisabledMessage = 16844077; // 0x101052d
field public static final int shortcutIcon = 16844074; // 0x101052a
field public static final int shortcutId = 16844072; // 0x1010528
field public static final int shortcutIntentAction = 16844079; // 0x101052f
field public static final int shortcutIntentData = 16844080; // 0x1010530
field public static final int shortcutRank = 16844073; // 0x1010529
field public static final int shortcutText = 16844076; // 0x101052c
field public static final int shortcutTitle = 16844075; // 0x101052b
field public static final int shouldDisableView = 16843246; // 0x10101ee field public static final int shouldDisableView = 16843246; // 0x10101ee
field public static final int showAsAction = 16843481; // 0x10102d9 field public static final int showAsAction = 16843481; // 0x10102d9
field public static final int showDefault = 16843258; // 0x10101fa field public static final int showDefault = 16843258; // 0x10101fa
@@ -9534,13 +9543,14 @@ package android.content.pm {
public static class LauncherApps.ShortcutQuery { public static class LauncherApps.ShortcutQuery {
ctor public LauncherApps.ShortcutQuery(); ctor public LauncherApps.ShortcutQuery();
method public void setActivity(android.content.ComponentName); method public android.content.pm.LauncherApps.ShortcutQuery setActivity(android.content.ComponentName);
method public void setChangedSince(long); method public android.content.pm.LauncherApps.ShortcutQuery setChangedSince(long);
method public void setPackage(java.lang.String); method public android.content.pm.LauncherApps.ShortcutQuery setPackage(java.lang.String);
method public void setQueryFlags(int); method public android.content.pm.LauncherApps.ShortcutQuery setQueryFlags(int);
method public void setShortcutIds(java.util.List<java.lang.String>); method public android.content.pm.LauncherApps.ShortcutQuery setShortcutIds(java.util.List<java.lang.String>);
field public static final int FLAG_GET_DYNAMIC = 1; // 0x1 field public static final int FLAG_GET_DYNAMIC = 1; // 0x1
field public static final int FLAG_GET_KEY_FIELDS_ONLY = 4; // 0x4 field public static final int FLAG_GET_KEY_FIELDS_ONLY = 4; // 0x4
field public static final int FLAG_GET_MANIFEST = 8; // 0x8
field public static final int FLAG_GET_PINNED = 2; // 0x2 field public static final int FLAG_GET_PINNED = 2; // 0x2
} }
@@ -10044,29 +10054,30 @@ package android.content.pm {
public final class ShortcutInfo implements android.os.Parcelable { public final class ShortcutInfo implements android.os.Parcelable {
method public int describeContents(); method public int describeContents();
method public android.content.ComponentName getActivityComponent(); method public android.content.ComponentName getActivity();
method public java.util.Set<java.lang.String> getCategories(); method public java.util.Set<java.lang.String> getCategories();
method public java.lang.String getDisabledMessage(); method public java.lang.CharSequence getDisabledMessage();
method public int getDisabledMessageResId(); method public int getDisabledMessageResId();
method public android.os.PersistableBundle getExtras(); method public android.os.PersistableBundle getExtras();
method public int getIconResourceId(); method public int getIconResourceId();
method public java.lang.String getId(); method public java.lang.String getId();
method public android.content.Intent getIntent(); method public android.content.Intent getIntent();
method public long getLastChangedTimestamp(); method public long getLastChangedTimestamp();
method public java.lang.String getPackageName(); method public java.lang.String getPackage();
method public int getRank(); method public int getRank();
method public java.lang.String getText(); method public java.lang.CharSequence getText();
method public int getTextResId(); method public int getTextResId();
method public java.lang.String getTitle(); method public java.lang.CharSequence getTitle();
method public int getTitleResId(); method public int getTitleResId();
method public android.os.UserHandle getUserHandle(); method public android.os.UserHandle getUserHandle();
method public boolean hasIconFile(); method public boolean hasIconFile();
method public boolean hasIconResource(); method public boolean hasIconResource();
method public boolean hasKeyFieldsOnly(); method public boolean hasKeyFieldsOnly();
method public boolean hasStringResourcesResolved(); method public boolean hasStringResourcesResolved();
method public boolean isDisabled();
method public boolean isDynamic(); method public boolean isDynamic();
method public boolean isFromManifest(); method public boolean isEnabled();
method public boolean isImmutable();
method public boolean isManifestShortcut();
method public boolean isPinned(); method public boolean isPinned();
method public void writeToParcel(android.os.Parcel, int); method public void writeToParcel(android.os.Parcel, int);
field public static final int CLONE_REMOVE_FOR_CREATOR = 1; // 0x1 field public static final int CLONE_REMOVE_FOR_CREATOR = 1; // 0x1
@@ -10075,10 +10086,11 @@ package android.content.pm {
field public static final android.os.Parcelable.Creator<android.content.pm.ShortcutInfo> CREATOR; field public static final android.os.Parcelable.Creator<android.content.pm.ShortcutInfo> CREATOR;
field public static final int FLAG_DISABLED = 64; // 0x40 field public static final int FLAG_DISABLED = 64; // 0x40
field public static final int FLAG_DYNAMIC = 1; // 0x1 field public static final int FLAG_DYNAMIC = 1; // 0x1
field public static final int FLAG_FROM_MANIFEST = 32; // 0x20
field public static final int FLAG_HAS_ICON_FILE = 8; // 0x8 field public static final int FLAG_HAS_ICON_FILE = 8; // 0x8
field public static final int FLAG_HAS_ICON_RES = 4; // 0x4 field public static final int FLAG_HAS_ICON_RES = 4; // 0x4
field public static final int FLAG_IMMUTABLE = 256; // 0x100
field public static final int FLAG_KEY_FIELDS_ONLY = 16; // 0x10 field public static final int FLAG_KEY_FIELDS_ONLY = 16; // 0x10
field public static final int FLAG_MANIFEST = 32; // 0x20
field public static final int FLAG_PINNED = 2; // 0x2 field public static final int FLAG_PINNED = 2; // 0x2
field public static final int FLAG_STRINGS_RESOLVED = 128; // 0x80 field public static final int FLAG_STRINGS_RESOLVED = 128; // 0x80
field public static final java.lang.String SHORTCUT_CATEGORY_CONVERSATION = "android.shortcut.conversation"; field public static final java.lang.String SHORTCUT_CATEGORY_CONVERSATION = "android.shortcut.conversation";
@@ -10087,19 +10099,16 @@ package android.content.pm {
public static class ShortcutInfo.Builder { public static class ShortcutInfo.Builder {
ctor public ShortcutInfo.Builder(android.content.Context); ctor public ShortcutInfo.Builder(android.content.Context);
method public android.content.pm.ShortcutInfo build(); method public android.content.pm.ShortcutInfo build();
method public android.content.pm.ShortcutInfo.Builder setActivityComponent(android.content.ComponentName); method public android.content.pm.ShortcutInfo.Builder setActivity(android.content.ComponentName);
method public android.content.pm.ShortcutInfo.Builder setCategories(java.util.Set<java.lang.String>); method public android.content.pm.ShortcutInfo.Builder setCategories(java.util.Set<java.lang.String>);
method public android.content.pm.ShortcutInfo.Builder setDisabledMessage(java.lang.String); method public android.content.pm.ShortcutInfo.Builder setDisabledMessage(java.lang.String);
method public android.content.pm.ShortcutInfo.Builder setDisabledMessageResId(int);
method public android.content.pm.ShortcutInfo.Builder setExtras(android.os.PersistableBundle); method public android.content.pm.ShortcutInfo.Builder setExtras(android.os.PersistableBundle);
method public android.content.pm.ShortcutInfo.Builder setIcon(android.graphics.drawable.Icon); 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 setId(java.lang.String);
method public android.content.pm.ShortcutInfo.Builder setIntent(android.content.Intent); method public android.content.pm.ShortcutInfo.Builder setIntent(android.content.Intent);
method public android.content.pm.ShortcutInfo.Builder setRank(int); method public android.content.pm.ShortcutInfo.Builder setRank(int);
method public android.content.pm.ShortcutInfo.Builder setText(java.lang.String); method public android.content.pm.ShortcutInfo.Builder setText(java.lang.String);
method public android.content.pm.ShortcutInfo.Builder setTextResId(int);
method public android.content.pm.ShortcutInfo.Builder setTitle(java.lang.String); method public android.content.pm.ShortcutInfo.Builder setTitle(java.lang.String);
method public android.content.pm.ShortcutInfo.Builder setTitleResId(int);
} }
public class ShortcutManager { public class ShortcutManager {
@@ -10107,9 +10116,12 @@ package android.content.pm {
method public void disableShortcuts(java.util.List<java.lang.String>); method public void disableShortcuts(java.util.List<java.lang.String>);
method public void disableShortcuts(java.util.List<java.lang.String>, int); method public void disableShortcuts(java.util.List<java.lang.String>, int);
method public void disableShortcuts(java.util.List<java.lang.String>, java.lang.String); method public void disableShortcuts(java.util.List<java.lang.String>, java.lang.String);
method public void enableShortcuts(java.util.List<java.lang.String>);
method public java.util.List<android.content.pm.ShortcutInfo> getDynamicShortcuts(); method public java.util.List<android.content.pm.ShortcutInfo> getDynamicShortcuts();
method public int getIconMaxDimensions(); method public int getIconMaxHeight();
method public int getMaxDynamicShortcutCount(); method public int getIconMaxWidth();
method public java.util.List<android.content.pm.ShortcutInfo> getManifestShortcuts();
method public int getMaxShortcutCountForActivity();
method public java.util.List<android.content.pm.ShortcutInfo> getPinnedShortcuts(); method public java.util.List<android.content.pm.ShortcutInfo> getPinnedShortcuts();
method public long getRateLimitResetTime(); method public long getRateLimitResetTime();
method public int getRemainingCallCount(); method public int getRemainingCallCount();

View File

@@ -1216,6 +1216,15 @@ package android {
field public static final int shareInterpolator = 16843195; // 0x10101bb field public static final int shareInterpolator = 16843195; // 0x10101bb
field public static final int sharedUserId = 16842763; // 0x101000b field public static final int sharedUserId = 16842763; // 0x101000b
field public static final int sharedUserLabel = 16843361; // 0x1010261 field public static final int sharedUserLabel = 16843361; // 0x1010261
field public static final int shortcutCategories = 16844078; // 0x101052e
field public static final int shortcutDisabledMessage = 16844077; // 0x101052d
field public static final int shortcutIcon = 16844074; // 0x101052a
field public static final int shortcutId = 16844072; // 0x1010528
field public static final int shortcutIntentAction = 16844079; // 0x101052f
field public static final int shortcutIntentData = 16844080; // 0x1010530
field public static final int shortcutRank = 16844073; // 0x1010529
field public static final int shortcutText = 16844076; // 0x101052c
field public static final int shortcutTitle = 16844075; // 0x101052b
field public static final int shouldDisableView = 16843246; // 0x10101ee field public static final int shouldDisableView = 16843246; // 0x10101ee
field public static final int showAsAction = 16843481; // 0x10102d9 field public static final int showAsAction = 16843481; // 0x10102d9
field public static final int showDefault = 16843258; // 0x10101fa field public static final int showDefault = 16843258; // 0x10101fa
@@ -9887,13 +9896,14 @@ package android.content.pm {
public static class LauncherApps.ShortcutQuery { public static class LauncherApps.ShortcutQuery {
ctor public LauncherApps.ShortcutQuery(); ctor public LauncherApps.ShortcutQuery();
method public void setActivity(android.content.ComponentName); method public android.content.pm.LauncherApps.ShortcutQuery setActivity(android.content.ComponentName);
method public void setChangedSince(long); method public android.content.pm.LauncherApps.ShortcutQuery setChangedSince(long);
method public void setPackage(java.lang.String); method public android.content.pm.LauncherApps.ShortcutQuery setPackage(java.lang.String);
method public void setQueryFlags(int); method public android.content.pm.LauncherApps.ShortcutQuery setQueryFlags(int);
method public void setShortcutIds(java.util.List<java.lang.String>); method public android.content.pm.LauncherApps.ShortcutQuery setShortcutIds(java.util.List<java.lang.String>);
field public static final int FLAG_GET_DYNAMIC = 1; // 0x1 field public static final int FLAG_GET_DYNAMIC = 1; // 0x1
field public static final int FLAG_GET_KEY_FIELDS_ONLY = 4; // 0x4 field public static final int FLAG_GET_KEY_FIELDS_ONLY = 4; // 0x4
field public static final int FLAG_GET_MANIFEST = 8; // 0x8
field public static final int FLAG_GET_PINNED = 2; // 0x2 field public static final int FLAG_GET_PINNED = 2; // 0x2
} }
@@ -10467,29 +10477,30 @@ package android.content.pm {
public final class ShortcutInfo implements android.os.Parcelable { public final class ShortcutInfo implements android.os.Parcelable {
method public int describeContents(); method public int describeContents();
method public android.content.ComponentName getActivityComponent(); method public android.content.ComponentName getActivity();
method public java.util.Set<java.lang.String> getCategories(); method public java.util.Set<java.lang.String> getCategories();
method public java.lang.String getDisabledMessage(); method public java.lang.CharSequence getDisabledMessage();
method public int getDisabledMessageResId(); method public int getDisabledMessageResId();
method public android.os.PersistableBundle getExtras(); method public android.os.PersistableBundle getExtras();
method public int getIconResourceId(); method public int getIconResourceId();
method public java.lang.String getId(); method public java.lang.String getId();
method public android.content.Intent getIntent(); method public android.content.Intent getIntent();
method public long getLastChangedTimestamp(); method public long getLastChangedTimestamp();
method public java.lang.String getPackageName(); method public java.lang.String getPackage();
method public int getRank(); method public int getRank();
method public java.lang.String getText(); method public java.lang.CharSequence getText();
method public int getTextResId(); method public int getTextResId();
method public java.lang.String getTitle(); method public java.lang.CharSequence getTitle();
method public int getTitleResId(); method public int getTitleResId();
method public android.os.UserHandle getUserHandle(); method public android.os.UserHandle getUserHandle();
method public boolean hasIconFile(); method public boolean hasIconFile();
method public boolean hasIconResource(); method public boolean hasIconResource();
method public boolean hasKeyFieldsOnly(); method public boolean hasKeyFieldsOnly();
method public boolean hasStringResourcesResolved(); method public boolean hasStringResourcesResolved();
method public boolean isDisabled();
method public boolean isDynamic(); method public boolean isDynamic();
method public boolean isFromManifest(); method public boolean isEnabled();
method public boolean isImmutable();
method public boolean isManifestShortcut();
method public boolean isPinned(); method public boolean isPinned();
method public void writeToParcel(android.os.Parcel, int); method public void writeToParcel(android.os.Parcel, int);
field public static final int CLONE_REMOVE_FOR_CREATOR = 1; // 0x1 field public static final int CLONE_REMOVE_FOR_CREATOR = 1; // 0x1
@@ -10498,10 +10509,11 @@ package android.content.pm {
field public static final android.os.Parcelable.Creator<android.content.pm.ShortcutInfo> CREATOR; field public static final android.os.Parcelable.Creator<android.content.pm.ShortcutInfo> CREATOR;
field public static final int FLAG_DISABLED = 64; // 0x40 field public static final int FLAG_DISABLED = 64; // 0x40
field public static final int FLAG_DYNAMIC = 1; // 0x1 field public static final int FLAG_DYNAMIC = 1; // 0x1
field public static final int FLAG_FROM_MANIFEST = 32; // 0x20
field public static final int FLAG_HAS_ICON_FILE = 8; // 0x8 field public static final int FLAG_HAS_ICON_FILE = 8; // 0x8
field public static final int FLAG_HAS_ICON_RES = 4; // 0x4 field public static final int FLAG_HAS_ICON_RES = 4; // 0x4
field public static final int FLAG_IMMUTABLE = 256; // 0x100
field public static final int FLAG_KEY_FIELDS_ONLY = 16; // 0x10 field public static final int FLAG_KEY_FIELDS_ONLY = 16; // 0x10
field public static final int FLAG_MANIFEST = 32; // 0x20
field public static final int FLAG_PINNED = 2; // 0x2 field public static final int FLAG_PINNED = 2; // 0x2
field public static final int FLAG_STRINGS_RESOLVED = 128; // 0x80 field public static final int FLAG_STRINGS_RESOLVED = 128; // 0x80
field public static final java.lang.String SHORTCUT_CATEGORY_CONVERSATION = "android.shortcut.conversation"; field public static final java.lang.String SHORTCUT_CATEGORY_CONVERSATION = "android.shortcut.conversation";
@@ -10510,19 +10522,16 @@ package android.content.pm {
public static class ShortcutInfo.Builder { public static class ShortcutInfo.Builder {
ctor public ShortcutInfo.Builder(android.content.Context); ctor public ShortcutInfo.Builder(android.content.Context);
method public android.content.pm.ShortcutInfo build(); method public android.content.pm.ShortcutInfo build();
method public android.content.pm.ShortcutInfo.Builder setActivityComponent(android.content.ComponentName); method public android.content.pm.ShortcutInfo.Builder setActivity(android.content.ComponentName);
method public android.content.pm.ShortcutInfo.Builder setCategories(java.util.Set<java.lang.String>); method public android.content.pm.ShortcutInfo.Builder setCategories(java.util.Set<java.lang.String>);
method public android.content.pm.ShortcutInfo.Builder setDisabledMessage(java.lang.String); method public android.content.pm.ShortcutInfo.Builder setDisabledMessage(java.lang.String);
method public android.content.pm.ShortcutInfo.Builder setDisabledMessageResId(int);
method public android.content.pm.ShortcutInfo.Builder setExtras(android.os.PersistableBundle); method public android.content.pm.ShortcutInfo.Builder setExtras(android.os.PersistableBundle);
method public android.content.pm.ShortcutInfo.Builder setIcon(android.graphics.drawable.Icon); 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 setId(java.lang.String);
method public android.content.pm.ShortcutInfo.Builder setIntent(android.content.Intent); method public android.content.pm.ShortcutInfo.Builder setIntent(android.content.Intent);
method public android.content.pm.ShortcutInfo.Builder setRank(int); method public android.content.pm.ShortcutInfo.Builder setRank(int);
method public android.content.pm.ShortcutInfo.Builder setText(java.lang.String); method public android.content.pm.ShortcutInfo.Builder setText(java.lang.String);
method public android.content.pm.ShortcutInfo.Builder setTextResId(int);
method public android.content.pm.ShortcutInfo.Builder setTitle(java.lang.String); method public android.content.pm.ShortcutInfo.Builder setTitle(java.lang.String);
method public android.content.pm.ShortcutInfo.Builder setTitleResId(int);
} }
public class ShortcutManager { public class ShortcutManager {
@@ -10530,9 +10539,12 @@ package android.content.pm {
method public void disableShortcuts(java.util.List<java.lang.String>); method public void disableShortcuts(java.util.List<java.lang.String>);
method public void disableShortcuts(java.util.List<java.lang.String>, int); method public void disableShortcuts(java.util.List<java.lang.String>, int);
method public void disableShortcuts(java.util.List<java.lang.String>, java.lang.String); method public void disableShortcuts(java.util.List<java.lang.String>, java.lang.String);
method public void enableShortcuts(java.util.List<java.lang.String>);
method public java.util.List<android.content.pm.ShortcutInfo> getDynamicShortcuts(); method public java.util.List<android.content.pm.ShortcutInfo> getDynamicShortcuts();
method public int getIconMaxDimensions(); method public int getIconMaxHeight();
method public int getMaxDynamicShortcutCount(); method public int getIconMaxWidth();
method public java.util.List<android.content.pm.ShortcutInfo> getManifestShortcuts();
method public int getMaxShortcutCountForActivity();
method public java.util.List<android.content.pm.ShortcutInfo> getPinnedShortcuts(); method public java.util.List<android.content.pm.ShortcutInfo> getPinnedShortcuts();
method public long getRateLimitResetTime(); method public long getRateLimitResetTime();
method public int getRemainingCallCount(); method public int getRemainingCallCount();

View File

@@ -1106,6 +1106,15 @@ package android {
field public static final int shareInterpolator = 16843195; // 0x10101bb field public static final int shareInterpolator = 16843195; // 0x10101bb
field public static final int sharedUserId = 16842763; // 0x101000b field public static final int sharedUserId = 16842763; // 0x101000b
field public static final int sharedUserLabel = 16843361; // 0x1010261 field public static final int sharedUserLabel = 16843361; // 0x1010261
field public static final int shortcutCategories = 16844078; // 0x101052e
field public static final int shortcutDisabledMessage = 16844077; // 0x101052d
field public static final int shortcutIcon = 16844074; // 0x101052a
field public static final int shortcutId = 16844072; // 0x1010528
field public static final int shortcutIntentAction = 16844079; // 0x101052f
field public static final int shortcutIntentData = 16844080; // 0x1010530
field public static final int shortcutRank = 16844073; // 0x1010529
field public static final int shortcutText = 16844076; // 0x101052c
field public static final int shortcutTitle = 16844075; // 0x101052b
field public static final int shouldDisableView = 16843246; // 0x10101ee field public static final int shouldDisableView = 16843246; // 0x10101ee
field public static final int showAsAction = 16843481; // 0x10102d9 field public static final int showAsAction = 16843481; // 0x10102d9
field public static final int showDefault = 16843258; // 0x10101fa field public static final int showDefault = 16843258; // 0x10101fa
@@ -9546,13 +9555,14 @@ package android.content.pm {
public static class LauncherApps.ShortcutQuery { public static class LauncherApps.ShortcutQuery {
ctor public LauncherApps.ShortcutQuery(); ctor public LauncherApps.ShortcutQuery();
method public void setActivity(android.content.ComponentName); method public android.content.pm.LauncherApps.ShortcutQuery setActivity(android.content.ComponentName);
method public void setChangedSince(long); method public android.content.pm.LauncherApps.ShortcutQuery setChangedSince(long);
method public void setPackage(java.lang.String); method public android.content.pm.LauncherApps.ShortcutQuery setPackage(java.lang.String);
method public void setQueryFlags(int); method public android.content.pm.LauncherApps.ShortcutQuery setQueryFlags(int);
method public void setShortcutIds(java.util.List<java.lang.String>); method public android.content.pm.LauncherApps.ShortcutQuery setShortcutIds(java.util.List<java.lang.String>);
field public static final int FLAG_GET_DYNAMIC = 1; // 0x1 field public static final int FLAG_GET_DYNAMIC = 1; // 0x1
field public static final int FLAG_GET_KEY_FIELDS_ONLY = 4; // 0x4 field public static final int FLAG_GET_KEY_FIELDS_ONLY = 4; // 0x4
field public static final int FLAG_GET_MANIFEST = 8; // 0x8
field public static final int FLAG_GET_PINNED = 2; // 0x2 field public static final int FLAG_GET_PINNED = 2; // 0x2
} }
@@ -10057,29 +10067,30 @@ package android.content.pm {
public final class ShortcutInfo implements android.os.Parcelable { public final class ShortcutInfo implements android.os.Parcelable {
method public int describeContents(); method public int describeContents();
method public android.content.ComponentName getActivityComponent(); method public android.content.ComponentName getActivity();
method public java.util.Set<java.lang.String> getCategories(); method public java.util.Set<java.lang.String> getCategories();
method public java.lang.String getDisabledMessage(); method public java.lang.CharSequence getDisabledMessage();
method public int getDisabledMessageResId(); method public int getDisabledMessageResId();
method public android.os.PersistableBundle getExtras(); method public android.os.PersistableBundle getExtras();
method public int getIconResourceId(); method public int getIconResourceId();
method public java.lang.String getId(); method public java.lang.String getId();
method public android.content.Intent getIntent(); method public android.content.Intent getIntent();
method public long getLastChangedTimestamp(); method public long getLastChangedTimestamp();
method public java.lang.String getPackageName(); method public java.lang.String getPackage();
method public int getRank(); method public int getRank();
method public java.lang.String getText(); method public java.lang.CharSequence getText();
method public int getTextResId(); method public int getTextResId();
method public java.lang.String getTitle(); method public java.lang.CharSequence getTitle();
method public int getTitleResId(); method public int getTitleResId();
method public android.os.UserHandle getUserHandle(); method public android.os.UserHandle getUserHandle();
method public boolean hasIconFile(); method public boolean hasIconFile();
method public boolean hasIconResource(); method public boolean hasIconResource();
method public boolean hasKeyFieldsOnly(); method public boolean hasKeyFieldsOnly();
method public boolean hasStringResourcesResolved(); method public boolean hasStringResourcesResolved();
method public boolean isDisabled();
method public boolean isDynamic(); method public boolean isDynamic();
method public boolean isFromManifest(); method public boolean isEnabled();
method public boolean isImmutable();
method public boolean isManifestShortcut();
method public boolean isPinned(); method public boolean isPinned();
method public void writeToParcel(android.os.Parcel, int); method public void writeToParcel(android.os.Parcel, int);
field public static final int CLONE_REMOVE_FOR_CREATOR = 1; // 0x1 field public static final int CLONE_REMOVE_FOR_CREATOR = 1; // 0x1
@@ -10088,10 +10099,11 @@ package android.content.pm {
field public static final android.os.Parcelable.Creator<android.content.pm.ShortcutInfo> CREATOR; field public static final android.os.Parcelable.Creator<android.content.pm.ShortcutInfo> CREATOR;
field public static final int FLAG_DISABLED = 64; // 0x40 field public static final int FLAG_DISABLED = 64; // 0x40
field public static final int FLAG_DYNAMIC = 1; // 0x1 field public static final int FLAG_DYNAMIC = 1; // 0x1
field public static final int FLAG_FROM_MANIFEST = 32; // 0x20
field public static final int FLAG_HAS_ICON_FILE = 8; // 0x8 field public static final int FLAG_HAS_ICON_FILE = 8; // 0x8
field public static final int FLAG_HAS_ICON_RES = 4; // 0x4 field public static final int FLAG_HAS_ICON_RES = 4; // 0x4
field public static final int FLAG_IMMUTABLE = 256; // 0x100
field public static final int FLAG_KEY_FIELDS_ONLY = 16; // 0x10 field public static final int FLAG_KEY_FIELDS_ONLY = 16; // 0x10
field public static final int FLAG_MANIFEST = 32; // 0x20
field public static final int FLAG_PINNED = 2; // 0x2 field public static final int FLAG_PINNED = 2; // 0x2
field public static final int FLAG_STRINGS_RESOLVED = 128; // 0x80 field public static final int FLAG_STRINGS_RESOLVED = 128; // 0x80
field public static final java.lang.String SHORTCUT_CATEGORY_CONVERSATION = "android.shortcut.conversation"; field public static final java.lang.String SHORTCUT_CATEGORY_CONVERSATION = "android.shortcut.conversation";
@@ -10100,19 +10112,16 @@ package android.content.pm {
public static class ShortcutInfo.Builder { public static class ShortcutInfo.Builder {
ctor public ShortcutInfo.Builder(android.content.Context); ctor public ShortcutInfo.Builder(android.content.Context);
method public android.content.pm.ShortcutInfo build(); method public android.content.pm.ShortcutInfo build();
method public android.content.pm.ShortcutInfo.Builder setActivityComponent(android.content.ComponentName); method public android.content.pm.ShortcutInfo.Builder setActivity(android.content.ComponentName);
method public android.content.pm.ShortcutInfo.Builder setCategories(java.util.Set<java.lang.String>); method public android.content.pm.ShortcutInfo.Builder setCategories(java.util.Set<java.lang.String>);
method public android.content.pm.ShortcutInfo.Builder setDisabledMessage(java.lang.String); method public android.content.pm.ShortcutInfo.Builder setDisabledMessage(java.lang.String);
method public android.content.pm.ShortcutInfo.Builder setDisabledMessageResId(int);
method public android.content.pm.ShortcutInfo.Builder setExtras(android.os.PersistableBundle); method public android.content.pm.ShortcutInfo.Builder setExtras(android.os.PersistableBundle);
method public android.content.pm.ShortcutInfo.Builder setIcon(android.graphics.drawable.Icon); 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 setId(java.lang.String);
method public android.content.pm.ShortcutInfo.Builder setIntent(android.content.Intent); method public android.content.pm.ShortcutInfo.Builder setIntent(android.content.Intent);
method public android.content.pm.ShortcutInfo.Builder setRank(int); method public android.content.pm.ShortcutInfo.Builder setRank(int);
method public android.content.pm.ShortcutInfo.Builder setText(java.lang.String); method public android.content.pm.ShortcutInfo.Builder setText(java.lang.String);
method public android.content.pm.ShortcutInfo.Builder setTextResId(int);
method public android.content.pm.ShortcutInfo.Builder setTitle(java.lang.String); method public android.content.pm.ShortcutInfo.Builder setTitle(java.lang.String);
method public android.content.pm.ShortcutInfo.Builder setTitleResId(int);
} }
public class ShortcutManager { public class ShortcutManager {
@@ -10121,9 +10130,12 @@ package android.content.pm {
method public void disableShortcuts(java.util.List<java.lang.String>); method public void disableShortcuts(java.util.List<java.lang.String>);
method public void disableShortcuts(java.util.List<java.lang.String>, int); method public void disableShortcuts(java.util.List<java.lang.String>, int);
method public void disableShortcuts(java.util.List<java.lang.String>, java.lang.String); method public void disableShortcuts(java.util.List<java.lang.String>, java.lang.String);
method public void enableShortcuts(java.util.List<java.lang.String>);
method public java.util.List<android.content.pm.ShortcutInfo> getDynamicShortcuts(); method public java.util.List<android.content.pm.ShortcutInfo> getDynamicShortcuts();
method public int getIconMaxDimensions(); method public int getIconMaxHeight();
method public int getMaxDynamicShortcutCount(); method public int getIconMaxWidth();
method public java.util.List<android.content.pm.ShortcutInfo> getManifestShortcuts();
method public int getMaxShortcutCountForActivity();
method public java.util.List<android.content.pm.ShortcutInfo> getPinnedShortcuts(); method public java.util.List<android.content.pm.ShortcutInfo> getPinnedShortcuts();
method public long getRateLimitResetTime(); method public long getRateLimitResetTime();
method public int getRemainingCallCount(); method public int getRemainingCallCount();

View File

@@ -28,6 +28,8 @@ interface IShortcutService {
ParceledListSlice getDynamicShortcuts(String packageName, int userId); ParceledListSlice getDynamicShortcuts(String packageName, int userId);
ParceledListSlice getManifestShortcuts(String packageName, int userId);
boolean addDynamicShortcuts(String packageName, in ParceledListSlice shortcutInfoList, boolean addDynamicShortcuts(String packageName, in ParceledListSlice shortcutInfoList,
int userId); int userId);
@@ -42,6 +44,8 @@ interface IShortcutService {
void disableShortcuts(String packageName, in List shortcutIds, String disabledMessage, void disableShortcuts(String packageName, in List shortcutIds, String disabledMessage,
int disabledMessageResId, int userId); int disabledMessageResId, int userId);
void enableShortcuts(String packageName, in List shortcutIds, int userId);
int getMaxDynamicShortcutCount(String packageName, int userId); int getMaxDynamicShortcutCount(String packageName, int userId);
int getRemainingCallCount(String packageName, int userId); int getRemainingCallCount(String packageName, int userId);

View File

@@ -187,6 +187,11 @@ public class LauncherApps {
*/ */
public static final int FLAG_GET_PINNED = 1 << 1; public static final int FLAG_GET_PINNED = 1 << 1;
/**
* Include manifest shortcuts in the result.
*/
public static final int FLAG_GET_MANIFEST = 1 << 3;
/** /**
* Requests "key" fields only. See {@link ShortcutInfo#hasKeyFieldsOnly()} for which * Requests "key" fields only. See {@link ShortcutInfo#hasKeyFieldsOnly()} for which
* fields are available. * fields are available.
@@ -198,6 +203,7 @@ public class LauncherApps {
value = { value = {
FLAG_GET_DYNAMIC, FLAG_GET_DYNAMIC,
FLAG_GET_PINNED, FLAG_GET_PINNED,
FLAG_GET_MANIFEST,
FLAG_GET_KEY_FIELDS_ONLY, FLAG_GET_KEY_FIELDS_ONLY,
}) })
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@@ -224,39 +230,44 @@ public class LauncherApps {
* If non-zero, returns only shortcuts that have been added or updated since the timestamp, * If non-zero, returns only shortcuts that have been added or updated since the timestamp,
* which is a milliseconds since the Epoch. * which is a milliseconds since the Epoch.
*/ */
public void setChangedSince(long changedSince) { public ShortcutQuery setChangedSince(long changedSince) {
mChangedSince = changedSince; mChangedSince = changedSince;
return this;
} }
/** /**
* If non-null, returns only shortcuts from the package. * If non-null, returns only shortcuts from the package.
*/ */
public void setPackage(@Nullable String packageName) { public ShortcutQuery setPackage(@Nullable String packageName) {
mPackage = packageName; mPackage = packageName;
return this;
} }
/** /**
* If non-null, return only the specified shortcuts by ID. When setting this field, * If non-null, return only the specified shortcuts by ID. When setting this field,
* a packange name must also be set with {@link #setPackage}. * a packange name must also be set with {@link #setPackage}.
*/ */
public void setShortcutIds(@Nullable List<String> shortcutIds) { public ShortcutQuery setShortcutIds(@Nullable List<String> shortcutIds) {
mShortcutIds = shortcutIds; mShortcutIds = shortcutIds;
return this;
} }
/** /**
* If non-null, returns only shortcuts associated with the activity, which are * If non-null, returns only shortcuts associated with the activity; i.e.
* {@link ShortcutInfo}s that have null {@link ShortcutInfo#getActivityComponent()}, or * {@link ShortcutInfo}s whose {@link ShortcutInfo#getActivity()} are equal
* {@link ShortcutInfo#getActivityComponent()} equals to {@code activity}. * to {@code activity}.
*/ */
public void setActivity(@Nullable ComponentName activity) { public ShortcutQuery setActivity(@Nullable ComponentName activity) {
mActivity = activity; mActivity = activity;
return this;
} }
/** /**
* Set query options. * Set query options.
*/ */
public void setQueryFlags(@QueryFlags int queryFlags) { public ShortcutQuery setQueryFlags(@QueryFlags int queryFlags) {
mQueryFlags = queryFlags; mQueryFlags = queryFlags;
return this;
} }
} }
@@ -525,7 +536,7 @@ public class LauncherApps {
*/ */
public ParcelFileDescriptor getShortcutIconFd( public ParcelFileDescriptor getShortcutIconFd(
@NonNull ShortcutInfo shortcut) { @NonNull ShortcutInfo shortcut) {
return getShortcutIconFd(shortcut.getPackageName(), shortcut.getId(), return getShortcutIconFd(shortcut.getPackage(), shortcut.getId(),
shortcut.getUserId()); shortcut.getUserId());
} }
@@ -555,14 +566,14 @@ public class LauncherApps {
} }
} }
/** TODO Javadoc */ /** TODO Javadoc (not implemented yet) */
public Drawable getShortcutIconDrawable(int density) { public Drawable getShortcutIconDrawable(int density) {
throw new RuntimeException("TODO implement it"); throw new RuntimeException("TODO not implemented yet");
} }
/** TODO Javadoc */ /** TODO Javadoc (not implemented yet) */
public Drawable getShortcutBadgedIconDrawable(int density) { public Drawable getShortcutBadgedIconDrawable(int density) {
throw new RuntimeException("TODO implement it"); throw new RuntimeException("TODO not implemented yet");
} }
/** /**
@@ -600,7 +611,7 @@ public class LauncherApps {
*/ */
public boolean startShortcut(@NonNull ShortcutInfo shortcut, public boolean startShortcut(@NonNull ShortcutInfo shortcut,
@Nullable Rect sourceBounds, @Nullable Bundle startActivityOptions) { @Nullable Rect sourceBounds, @Nullable Bundle startActivityOptions) {
return startShortcut(shortcut.getPackageName(), shortcut.getId(), return startShortcut(shortcut.getPackage(), shortcut.getId(),
sourceBounds, startActivityOptions, sourceBounds, startActivityOptions,
shortcut.getUserId()); shortcut.getUserId());
} }

View File

@@ -70,7 +70,7 @@ public final class ShortcutInfo implements Parcelable {
public static final int FLAG_KEY_FIELDS_ONLY = 1 << 4; public static final int FLAG_KEY_FIELDS_ONLY = 1 << 4;
/* @hide */ /* @hide */
public static final int FLAG_FROM_MANIFEST = 1 << 5; public static final int FLAG_MANIFEST = 1 << 5;
/* @hide */ /* @hide */
public static final int FLAG_DISABLED = 1 << 6; public static final int FLAG_DISABLED = 1 << 6;
@@ -78,6 +78,9 @@ public final class ShortcutInfo implements Parcelable {
/* @hide */ /* @hide */
public static final int FLAG_STRINGS_RESOLVED = 1 << 7; public static final int FLAG_STRINGS_RESOLVED = 1 << 7;
/* @hide */
public static final int FLAG_IMMUTABLE = 1 << 8;
/** @hide */ /** @hide */
@IntDef(flag = true, @IntDef(flag = true,
value = { value = {
@@ -86,9 +89,10 @@ public final class ShortcutInfo implements Parcelable {
FLAG_HAS_ICON_RES, FLAG_HAS_ICON_RES,
FLAG_HAS_ICON_FILE, FLAG_HAS_ICON_FILE,
FLAG_KEY_FIELDS_ONLY, FLAG_KEY_FIELDS_ONLY,
FLAG_FROM_MANIFEST, FLAG_MANIFEST,
FLAG_DISABLED, FLAG_DISABLED,
FLAG_STRINGS_RESOLVED, FLAG_STRINGS_RESOLVED,
FLAG_IMMUTABLE,
}) })
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
public @interface ShortcutFlags {} public @interface ShortcutFlags {}
@@ -133,7 +137,7 @@ public final class ShortcutInfo implements Parcelable {
private final String mPackageName; private final String mPackageName;
@Nullable @Nullable
private ComponentName mActivityComponent; private ComponentName mActivity;
@Nullable @Nullable
private Icon mIcon; private Icon mIcon;
@@ -141,17 +145,17 @@ public final class ShortcutInfo implements Parcelable {
private int mTitleResId; private int mTitleResId;
@Nullable @Nullable
private String mTitle; private CharSequence mTitle;
private int mTextResId; private int mTextResId;
@Nullable @Nullable
private String mText; private CharSequence mText;
private int mDisabledMessageResId; private int mDisabledMessageResId;
@Nullable @Nullable
private String mDisabledMessage; private CharSequence mDisabledMessage;
@Nullable @Nullable
private ArraySet<String> mCategories; private ArraySet<String> mCategories;
@@ -196,7 +200,7 @@ public final class ShortcutInfo implements Parcelable {
// Note we can't do other null checks here because SM.updateShortcuts() takes partial // Note we can't do other null checks here because SM.updateShortcuts() takes partial
// information. // information.
mPackageName = b.mContext.getPackageName(); mPackageName = b.mContext.getPackageName();
mActivityComponent = b.mActivityComponent; mActivity = b.mActivity;
mIcon = b.mIcon; mIcon = b.mIcon;
mTitle = b.mTitle; mTitle = b.mTitle;
mTitleResId = b.mTitleResId; mTitleResId = b.mTitleResId;
@@ -229,7 +233,7 @@ public final class ShortcutInfo implements Parcelable {
*/ */
public void enforceMandatoryFields() { public void enforceMandatoryFields() {
Preconditions.checkStringNotEmpty(mId, "Shortcut ID must be provided"); Preconditions.checkStringNotEmpty(mId, "Shortcut ID must be provided");
Preconditions.checkNotNull(mActivityComponent, "activityComponent must be provided"); Preconditions.checkNotNull(mActivity, "activity must be provided");
if (mTitle == null && mTitleResId == 0) { if (mTitle == null && mTitleResId == 0) {
throw new IllegalArgumentException("Shortcut title must be provided"); throw new IllegalArgumentException("Shortcut title must be provided");
} }
@@ -250,7 +254,7 @@ public final class ShortcutInfo implements Parcelable {
mIconResourceId = source.mIconResourceId; mIconResourceId = source.mIconResourceId;
if ((cloneFlags & CLONE_REMOVE_NON_KEY_INFO) == 0) { if ((cloneFlags & CLONE_REMOVE_NON_KEY_INFO) == 0) {
mActivityComponent = source.mActivityComponent; mActivity = source.mActivity;
if ((cloneFlags & CLONE_REMOVE_ICON) == 0) { if ((cloneFlags & CLONE_REMOVE_ICON) == 0) {
mIcon = source.mIcon; mIcon = source.mIcon;
@@ -308,6 +312,17 @@ public final class ShortcutInfo implements Parcelable {
return new ShortcutInfo(this, cloneFlags); return new ShortcutInfo(this, cloneFlags);
} }
/**
* @hide
*/
public void ensureUpdatableWith(ShortcutInfo source) {
Preconditions.checkState(mUserId == source.mUserId, "Owner User ID must match");
Preconditions.checkState(mId.equals(source.mId), "ID must match");
Preconditions.checkState(mPackageName.equals(source.mPackageName),
"Package name must match");
Preconditions.checkState(!isImmutable(), "Target ShortcutInfo is immutable");
}
/** /**
* Copy non-null/zero fields from another {@link ShortcutInfo}. Only "public" information * Copy non-null/zero fields from another {@link ShortcutInfo}. Only "public" information
* will be overwritten. The timestamp will be updated. * will be overwritten. The timestamp will be updated.
@@ -316,16 +331,15 @@ public final class ShortcutInfo implements Parcelable {
* - mBitmapPath will not change * - mBitmapPath will not change
* - Current time will be set to timestamp * - Current time will be set to timestamp
* *
* @throws IllegalStateException if source is not compatible.
*
* @hide * @hide
*/ */
public void copyNonNullFieldsFrom(ShortcutInfo source) { public void copyNonNullFieldsFrom(ShortcutInfo source) {
Preconditions.checkState(mUserId == source.mUserId, "Owner User ID must match"); ensureUpdatableWith(source);
Preconditions.checkState(mId.equals(source.mId), "ID must match");
Preconditions.checkState(mPackageName.equals(source.mPackageName),
"Package name must match");
if (source.mActivityComponent != null) { if (source.mActivity != null) {
mActivityComponent = source.mActivityComponent; mActivity = source.mActivity;
} }
if (source.mIcon != null) { if (source.mIcon != null) {
@@ -401,21 +415,21 @@ public final class ShortcutInfo implements Parcelable {
private String mId; private String mId;
private ComponentName mActivityComponent; private ComponentName mActivity;
private Icon mIcon; private Icon mIcon;
private int mTitleResId; private int mTitleResId;
private String mTitle; private CharSequence mTitle;
private int mTextResId; private int mTextResId;
private String mText; private CharSequence mText;
private int mDisabledMessageResId; private int mDisabledMessageResId;
private String mDisabledMessage; private CharSequence mDisabledMessage;
private Set<String> mCategories; private Set<String> mCategories;
@@ -451,8 +465,8 @@ public final class ShortcutInfo implements Parcelable {
* a hint to the launcher app about which launcher icon to associate this shortcut with. * a hint to the launcher app about which launcher icon to associate this shortcut with.
*/ */
@NonNull @NonNull
public Builder setActivityComponent(@NonNull ComponentName activityComponent) { public Builder setActivity(@NonNull ComponentName activity) {
mActivityComponent = Preconditions.checkNotNull(activityComponent, "activityComponent"); mActivity = Preconditions.checkNotNull(activity, "activity");
return this; return this;
} }
@@ -476,7 +490,7 @@ public final class ShortcutInfo implements Parcelable {
return this; return this;
} }
/** TODO Javadoc */ /** @hide */
public Builder setTitleResId(int titleResId) { public Builder setTitleResId(int titleResId) {
Preconditions.checkState(mTitle == null, "title already set"); Preconditions.checkState(mTitle == null, "title already set");
mTitleResId = titleResId; mTitleResId = titleResId;
@@ -496,7 +510,7 @@ public final class ShortcutInfo implements Parcelable {
return this; return this;
} }
/** TODO Javadoc */ /** @hide */
public Builder setTextResId(int textResId) { public Builder setTextResId(int textResId) {
Preconditions.checkState(mText == null, "text already set"); Preconditions.checkState(mText == null, "text already set");
mTextResId = textResId; mTextResId = textResId;
@@ -516,7 +530,7 @@ public final class ShortcutInfo implements Parcelable {
return this; return this;
} }
/** TODO Javadoc */ /** @hide */
public Builder setDisabledMessageResId(int disabledMessageResId) { public Builder setDisabledMessageResId(int disabledMessageResId) {
Preconditions.checkState(mDisabledMessage == null, "disabledMessage already set"); Preconditions.checkState(mDisabledMessage == null, "disabledMessage already set");
mDisabledMessageResId = disabledMessageResId; mDisabledMessageResId = disabledMessageResId;
@@ -595,7 +609,7 @@ public final class ShortcutInfo implements Parcelable {
* Return the package name of the creator application. * Return the package name of the creator application.
*/ */
@NonNull @NonNull
public String getPackageName() { public String getPackage() {
return mPackageName; return mPackageName;
} }
@@ -606,11 +620,11 @@ public final class ShortcutInfo implements Parcelable {
* <p>This has nothing to do with the activity that this shortcut will launch. This is * <p>This has nothing to do with the activity that this shortcut will launch. This is
* a hint to the launcher app that on which launcher icon this shortcut should be shown. * a hint to the launcher app that on which launcher icon this shortcut should be shown.
* *
* @see Builder#setActivityComponent * @see Builder#setActivity
*/ */
@Nullable @Nullable
public ComponentName getActivityComponent() { public ComponentName getActivity() {
return mActivityComponent; return mActivity;
} }
/** /**
@@ -634,7 +648,7 @@ public final class ShortcutInfo implements Parcelable {
* {@link #hasKeyFieldsOnly()} is true. * {@link #hasKeyFieldsOnly()} is true.
*/ */
@Nullable @Nullable
public String getTitle() { public CharSequence getTitle() {
return mTitle; return mTitle;
} }
@@ -647,7 +661,7 @@ public final class ShortcutInfo implements Parcelable {
* Return the shortcut text. * Return the shortcut text.
*/ */
@Nullable @Nullable
public String getText() { public CharSequence getText() {
return mText; return mText;
} }
@@ -660,7 +674,7 @@ public final class ShortcutInfo implements Parcelable {
* Return the message that should be shown when a shortcut in disabled state is launched. * Return the message that should be shown when a shortcut in disabled state is launched.
*/ */
@Nullable @Nullable
public String getDisabledMessage() { public CharSequence getDisabledMessage() {
return mDisabledMessage; return mDisabledMessage;
} }
@@ -788,18 +802,67 @@ public final class ShortcutInfo implements Parcelable {
} }
/** /**
* Return whether a shortcut is published via manifest or not. If true, the shortcut is * Return whether a shortcut is published via AndroidManifest.xml or not. If {@code true},
* immutable. * it's also {@link #isImmutable()}.
*
* <p>When an app is upgraded and a shortcut is no longer published from AndroidManifest.xml,
* this will be set to {@code false}. If the shortcut is not pinned, then it'll just disappear.
* However, if it's pinned, it will still be alive, and {@link #isEnabled()} will be
* {@code false} and {@link #isImmutable()} will be {@code true}.
*
* <p>NOTE this is whether a shortcut is published from the <b>current version's</b>
* AndroidManifest.xml.
*/ */
public boolean isFromManifest() { public boolean isManifestShortcut() {
return hasFlags(FLAG_FROM_MANIFEST); return hasFlags(FLAG_MANIFEST);
} }
/** Return whether a shortcut is disabled by publisher or not. */ /**
public boolean isDisabled() { * @return true if pinned but neither dynamic nor manifest.
* @hide
*/
public boolean isFloating() {
return isPinned() && !(isDynamic() || isManifestShortcut());
}
/** @hide */
public boolean isOriginallyFromManifest() {
return hasFlags(FLAG_IMMUTABLE);
}
/**
* Return if a shortcut is immutable, in which case it cannot be modified with any of
* {@link ShortcutManager} APIs.
*
* <p>All manifest shortcuts are immutable. When a manifest shortcut is pinned and then
* disabled because the app is upgraded and its AndroidManifest.xml no longer publishes it,
* {@link #isManifestShortcut} returns {@code false}, but it is still immutable.
*
* <p>All shortcuts originally published via the {@link ShortcutManager} APIs
* are all mutable.
*/
public boolean isImmutable() {
return hasFlags(FLAG_IMMUTABLE);
}
/**
* Returns {@code false} if a shortcut is disabled with
* {@link ShortcutManager#disableShortcuts}.
*/
public boolean isEnabled() {
return !hasFlags(FLAG_DISABLED); return !hasFlags(FLAG_DISABLED);
} }
/** @hide */
public boolean isAlive() {
return hasFlags(FLAG_PINNED) || hasFlags(FLAG_DYNAMIC) || hasFlags(FLAG_MANIFEST);
}
/** @hide */
public boolean usesQuota() {
return hasFlags(FLAG_DYNAMIC) || hasFlags(FLAG_MANIFEST);
}
/** /**
* Return whether a shortcut's icon is a resource in the owning package. * Return whether a shortcut's icon is a resource in the owning package.
* *
@@ -833,7 +896,7 @@ public final class ShortcutInfo implements Parcelable {
* following fields are available. * following fields are available.
* <ul> * <ul>
* <li>{@link #getId()} * <li>{@link #getId()}
* <li>{@link #getPackageName()} * <li>{@link #getPackage()}
* <li>{@link #getLastChangedTimestamp()} * <li>{@link #getLastChangedTimestamp()}
* <li>{@link #isDynamic()} * <li>{@link #isDynamic()}
* <li>{@link #isPinned()} * <li>{@link #isPinned()}
@@ -888,19 +951,31 @@ public final class ShortcutInfo implements Parcelable {
mBitmapPath = bitmapPath; mBitmapPath = bitmapPath;
} }
/** @hide */
public void setDisabledMessageResId(int disabledMessageResId) {
mDisabledMessageResId = disabledMessageResId;
mDisabledMessage = null;
}
/** @hide */
public void setDisabledMessage(String disabledMessage) {
mDisabledMessage = disabledMessage;
mDisabledMessageResId = 0;
}
private ShortcutInfo(Parcel source) { private ShortcutInfo(Parcel source) {
final ClassLoader cl = getClass().getClassLoader(); final ClassLoader cl = getClass().getClassLoader();
mUserId = source.readInt(); mUserId = source.readInt();
mId = source.readString(); mId = source.readString();
mPackageName = source.readString(); mPackageName = source.readString();
mActivityComponent = source.readParcelable(cl); mActivity = source.readParcelable(cl);
mIcon = source.readParcelable(cl); mIcon = source.readParcelable(cl);
mTitle = source.readString(); mTitle = source.readCharSequence();
mTitleResId = source.readInt(); mTitleResId = source.readInt();
mText = source.readString(); mText = source.readCharSequence();
mTextResId = source.readInt(); mTextResId = source.readInt();
mDisabledMessage = source.readString(); mDisabledMessage = source.readCharSequence();
mDisabledMessageResId = source.readInt(); mDisabledMessageResId = source.readInt();
mIntent = source.readParcelable(cl); mIntent = source.readParcelable(cl);
mIntentPersistableExtras = source.readParcelable(cl); mIntentPersistableExtras = source.readParcelable(cl);
@@ -927,13 +1002,13 @@ public final class ShortcutInfo implements Parcelable {
dest.writeInt(mUserId); dest.writeInt(mUserId);
dest.writeString(mId); dest.writeString(mId);
dest.writeString(mPackageName); dest.writeString(mPackageName);
dest.writeParcelable(mActivityComponent, flags); dest.writeParcelable(mActivity, flags);
dest.writeParcelable(mIcon, flags); dest.writeParcelable(mIcon, flags);
dest.writeString(mTitle); dest.writeCharSequence(mTitle);
dest.writeInt(mTitleResId); dest.writeInt(mTitleResId);
dest.writeString(mText); dest.writeCharSequence(mText);
dest.writeInt(mTextResId); dest.writeInt(mTextResId);
dest.writeString(mDisabledMessage); dest.writeCharSequence(mDisabledMessage);
dest.writeInt(mDisabledMessageResId); dest.writeInt(mDisabledMessageResId);
dest.writeParcelable(mIntent, flags); dest.writeParcelable(mIntent, flags);
@@ -991,18 +1066,43 @@ public final class ShortcutInfo implements Parcelable {
sb.append("id="); sb.append("id=");
sb.append(secure ? "***" : mId); sb.append(secure ? "***" : mId);
sb.append(", flags=0x");
sb.append(Integer.toHexString(mFlags));
sb.append(" [");
if (!isEnabled()) {
sb.append("X");
}
if (isImmutable()) {
sb.append("Im");
}
if (isManifestShortcut()) {
sb.append("M");
}
if (isDynamic()) {
sb.append("D");
}
if (isPinned()) {
sb.append("P");
}
if (hasIconFile()) {
sb.append("If");
}
if (hasIconResource()) {
sb.append("Ir");
}
if (hasKeyFieldsOnly()) {
sb.append("K");
}
if (hasStringResourcesResolved()) {
sb.append("Sr");
}
sb.append("]");
sb.append(", packageName="); sb.append(", packageName=");
sb.append(mPackageName); sb.append(mPackageName);
if (isDynamic()) {
sb.append(", dynamic");
}
if (isPinned()) {
sb.append(", pinned");
}
sb.append(", activity="); sb.append(", activity=");
sb.append(mActivityComponent); sb.append(mActivity);
sb.append(", title="); sb.append(", title=");
sb.append(secure ? "***" : mTitle); sb.append(secure ? "***" : mTitle);
@@ -1040,35 +1140,6 @@ public final class ShortcutInfo implements Parcelable {
sb.append(", extras="); sb.append(", extras=");
sb.append(mExtras); sb.append(mExtras);
sb.append(", flags=");
sb.append(mFlags);
sb.append(" [");
if (hasFlags(FLAG_DISABLED)) {
sb.append("X");
}
if (hasFlags(FLAG_FROM_MANIFEST)) {
sb.append("M");
}
if (hasFlags(FLAG_DYNAMIC)) {
sb.append("D");
}
if (hasFlags(FLAG_PINNED)) {
sb.append("P");
}
if (hasFlags(FLAG_HAS_ICON_FILE)) {
sb.append("If");
}
if (hasFlags(FLAG_HAS_ICON_RES)) {
sb.append("Ir");
}
if (hasFlags(FLAG_KEY_FIELDS_ONLY)) {
sb.append("K");
}
if (hasFlags(FLAG_STRINGS_RESOLVED)) {
sb.append("S");
}
sb.append("]");
if (includeInternalData) { if (includeInternalData) {
sb.append(", iconRes="); sb.append(", iconRes=");
@@ -1084,16 +1155,16 @@ public final class ShortcutInfo implements Parcelable {
/** @hide */ /** @hide */
public ShortcutInfo( public ShortcutInfo(
@UserIdInt int userId, String id, String packageName, ComponentName activityComponent, @UserIdInt int userId, String id, String packageName, ComponentName activity,
Icon icon, String title, int titleResId, String text, int textResId, Icon icon, CharSequence title, int titleResId, CharSequence text, int textResId,
String disabledMessage, int disabledMessageResId, Set<String> categories, Intent intent, CharSequence disabledMessage, int disabledMessageResId, Set<String> categories,
PersistableBundle intentPersistableExtras, Intent intent, PersistableBundle intentPersistableExtras,
int rank, PersistableBundle extras, long lastChangedTimestamp, int rank, PersistableBundle extras, long lastChangedTimestamp,
int flags, int iconResId, String bitmapPath) { int flags, int iconResId, String bitmapPath) {
mUserId = userId; mUserId = userId;
mId = id; mId = id;
mPackageName = packageName; mPackageName = packageName;
mActivityComponent = activityComponent; mActivity = activity;
mIcon = icon; mIcon = icon;
mTitle = title; mTitle = title;
mTitleResId = titleResId; mTitleResId = titleResId;

View File

@@ -28,7 +28,7 @@ import java.util.List;
// TODO Enhance javadoc // TODO Enhance javadoc
/** /**
* <b>TODO: Update to reflect DR changes.</b><br> * <b>TODO: Update to reflect DR changes, such as manifest shortcuts.</b><br>
* *
* {@link ShortcutManager} manages shortcuts created by applications. * {@link ShortcutManager} manages shortcuts created by applications.
* *
@@ -36,8 +36,8 @@ import java.util.List;
* *
* An application can publish shortcuts with {@link #setDynamicShortcuts(List)} and * An application can publish shortcuts with {@link #setDynamicShortcuts(List)} and
* {@link #addDynamicShortcuts(List)}. There can be at most * {@link #addDynamicShortcuts(List)}. There can be at most
* {@link #getMaxDynamicShortcutCount()} number of dynamic shortcuts at a time from the same * {@link #getMaxShortcutCountForActivity()} number of dynamic shortcuts at a time from the
* application. * same application.
* A dynamic shortcut can be deleted with {@link #removeDynamicShortcuts(List)}, and apps * A dynamic shortcut can be deleted with {@link #removeDynamicShortcuts(List)}, and apps
* can also use {@link #removeAllDynamicShortcuts()} to delete all dynamic shortcuts. * can also use {@link #removeAllDynamicShortcuts()} to delete all dynamic shortcuts.
* *
@@ -51,7 +51,8 @@ import java.util.List;
* <p>The number of pinned shortcuts does not affect the number of dynamic shortcuts that can be * <p>The number of pinned shortcuts does not affect the number of dynamic shortcuts that can be
* published by an application at a time. * published by an application at a time.
* No matter how many pinned shortcuts that Launcher has for an application, the * No matter how many pinned shortcuts that Launcher has for an application, the
* application can still always publish {@link #getMaxDynamicShortcutCount()} number of dynamic * application can still always publish {@link #getMaxShortcutCountForActivity()} number of
* dynamic
* shortcuts. * shortcuts.
* *
* <h3>Shortcut IDs</h3> * <h3>Shortcut IDs</h3>
@@ -132,7 +133,7 @@ public class ShortcutManager {
* @return {@code true} if the call has succeeded. {@code false} if the call is rate-limited. * @return {@code true} if the call has succeeded. {@code false} if the call is rate-limited.
* *
* @throws IllegalArgumentException if {@code shortcutInfoList} contains more than * @throws IllegalArgumentException if {@code shortcutInfoList} contains more than
* {@link #getMaxDynamicShortcutCount()} shortcuts. * {@link #getMaxShortcutCountForActivity()} shortcuts.
*/ */
public boolean setDynamicShortcuts(@NonNull List<ShortcutInfo> shortcutInfoList) { public boolean setDynamicShortcuts(@NonNull List<ShortcutInfo> shortcutInfoList) {
try { try {
@@ -145,7 +146,7 @@ public class ShortcutManager {
/** /**
* Return all dynamic shortcuts from the caller application. The number of result items * Return all dynamic shortcuts from the caller application. The number of result items
* will not exceed the value returned by {@link #getMaxDynamicShortcutCount()}. * will not exceed the value returned by {@link #getMaxShortcutCountForActivity()}.
*/ */
@NonNull @NonNull
public List<ShortcutInfo> getDynamicShortcuts() { public List<ShortcutInfo> getDynamicShortcuts() {
@@ -157,6 +158,19 @@ public class ShortcutManager {
} }
} }
/**
* TODO Javadoc
*/
@NonNull
public List<ShortcutInfo> getManifestShortcuts() {
try {
return mService.getManifestShortcuts(mContext.getPackageName(), injectMyUserId())
.getList();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/** /**
* Publish list of dynamic shortcuts. If there's already dynamic or pinned shortcuts with * Publish list of dynamic shortcuts. If there's already dynamic or pinned shortcuts with
* the same IDs, they will all be updated. * the same IDs, they will all be updated.
@@ -269,9 +283,21 @@ public class ShortcutManager {
} }
/** /**
* Return the max number of dynamic shortcuts that each application can have at a time. * TODO Javadoc
*/ */
public int getMaxDynamicShortcutCount() { public void enableShortcuts(@NonNull List<String> shortcutIds) {
try {
mService.enableShortcuts(mContext.getPackageName(), shortcutIds, injectMyUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Return the max number of dynamic shortcuts + manifest shortcuts that each launcehr icon
* can have at a time.
*/
public int getMaxShortcutCountForActivity() {
try { try {
return mService.getMaxDynamicShortcutCount(mContext.getPackageName(), injectMyUserId()); return mService.getMaxDynamicShortcutCount(mContext.getPackageName(), injectMyUserId());
} catch (RemoteException e) { } catch (RemoteException e) {
@@ -308,10 +334,23 @@ public class ShortcutManager {
} }
/** /**
* Return the max width and height for icons, in pixels. * Return the max width for icons, in pixels.
*/ */
public int getIconMaxDimensions() { public int getIconMaxWidth() {
try { try {
// TODO Implement it properly using xdpi.
return mService.getIconMaxDimensions(mContext.getPackageName(), injectMyUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Return the max height for icons, in pixels.
*/
public int getIconMaxHeight() {
try {
// TODO Implement it properly using ydpi.
return mService.getIconMaxDimensions(mContext.getPackageName(), injectMyUserId()); return mService.getIconMaxDimensions(mContext.getPackageName(), injectMyUserId());
} catch (RemoteException e) { } catch (RemoteException e) {
throw e.rethrowFromSystemServer(); throw e.rethrowFromSystemServer();

View File

@@ -8237,4 +8237,17 @@ i
color. --> color. -->
<attr name="colorBackground" /> <attr name="colorBackground" />
</declare-styleable> </declare-styleable>
<declare-styleable name="Shortcut">
<attr name="shortcutId" format="string" />
<attr name="enabled" format="boolean" />
<attr name="shortcutRank" format="integer" />
<attr name="shortcutIcon" format="reference" />
<attr name="shortcutTitle" format="reference" />
<attr name="shortcutText" format="reference" />
<attr name="shortcutDisabledMessage" format="reference" />
<attr name="shortcutCategories" format="string" />
<attr name="shortcutIntentAction" format="string" />
<attr name="shortcutIntentData" format="string" />
</declare-styleable>
</resources> </resources>

View File

@@ -2728,4 +2728,18 @@
<public type="id" name="icon_frame" id="0x0102003e" /> <public type="id" name="icon_frame" id="0x0102003e" />
<public type="id" name="list_container" id="0x0102003f" /> <public type="id" name="list_container" id="0x0102003f" />
<public type="id" name="switch_widget" id="0x01020040" /> <public type="id" name="switch_widget" id="0x01020040" />
<!-- ===============================================================
Resources added in version N MR1 of the platform
=============================================================== -->
<eat-comment />
<public type="attr" name="shortcutId" />
<public type="attr" name="shortcutRank" />
<public type="attr" name="shortcutIcon" />
<public type="attr" name="shortcutTitle" />
<public type="attr" name="shortcutText" />
<public type="attr" name="shortcutDisabledMessage" />
<public type="attr" name="shortcutCategories" />
<public type="attr" name="shortcutIntentAction" />
<public type="attr" name="shortcutIntentData" />
</resources> </resources>

View File

@@ -36,6 +36,8 @@ import java.util.List;
/** /**
* Launcher information used by {@link ShortcutService}. * Launcher information used by {@link ShortcutService}.
*
* All methods should be guarded by {@code #mShortcutUser.mService.mLock}.
*/ */
class ShortcutLauncher extends ShortcutPackageItem { class ShortcutLauncher extends ShortcutPackageItem {
private static final String TAG = ShortcutService.TAG; private static final String TAG = ShortcutService.TAG;
@@ -125,7 +127,8 @@ class ShortcutLauncher extends ShortcutPackageItem {
if (si == null) { if (si == null) {
continue; continue;
} }
if (si.isDynamic() || (prevSet != null && prevSet.contains(id))) { if (si.isDynamic() || si.isManifestShortcut()
|| (prevSet != null && prevSet.contains(id))) {
newSet.add(id); newSet.add(id);
} }
} }

View File

@@ -20,6 +20,7 @@ import android.annotation.Nullable;
import android.annotation.UserIdInt; import android.annotation.UserIdInt;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutInfo;
import android.os.PersistableBundle; import android.os.PersistableBundle;
import android.text.format.Formatter; import android.text.format.Formatter;
@@ -28,6 +29,7 @@ import android.util.ArraySet;
import android.util.Slog; import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils; import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParser;
@@ -44,6 +46,11 @@ import java.util.function.Predicate;
/** /**
* Package information used by {@link ShortcutService}. * Package information used by {@link ShortcutService}.
* User information used by {@link ShortcutService}.
*
* All methods should be guarded by {@code #mShortcutUser.mService.mLock}.
*
* TODO Max dynamic shortcuts cap should be per activity.
*/ */
class ShortcutPackage extends ShortcutPackageItem { class ShortcutPackage extends ShortcutPackageItem {
private static final String TAG = ShortcutService.TAG; private static final String TAG = ShortcutService.TAG;
@@ -55,7 +62,6 @@ class ShortcutPackage extends ShortcutPackageItem {
private static final String TAG_CATEGORIES = "categories"; private static final String TAG_CATEGORIES = "categories";
private static final String ATTR_NAME = "name"; private static final String ATTR_NAME = "name";
private static final String ATTR_DYNAMIC_COUNT = "dynamic-count";
private static final String ATTR_CALL_COUNT = "call-count"; private static final String ATTR_CALL_COUNT = "call-count";
private static final String ATTR_LAST_RESET = "last-reset"; private static final String ATTR_LAST_RESET = "last-reset";
private static final String ATTR_ID = "id"; private static final String ATTR_ID = "id";
@@ -83,11 +89,6 @@ class ShortcutPackage extends ShortcutPackageItem {
*/ */
final private ArrayMap<String, ShortcutInfo> mShortcuts = new ArrayMap<>(); final private ArrayMap<String, ShortcutInfo> mShortcuts = new ArrayMap<>();
/**
* # of dynamic shortcuts.
*/
private int mDynamicShortcutCount = 0;
/** /**
* # of times the package has called rate-limited APIs. * # of times the package has called rate-limited APIs.
*/ */
@@ -129,19 +130,19 @@ class ShortcutPackage extends ShortcutPackageItem {
* exists (as opposed to Launcher trying to fetch shortcuts from a non-existent package), so * exists (as opposed to Launcher trying to fetch shortcuts from a non-existent package), so
* we do some initialization for the package. * we do some initialization for the package.
*/ */
private void onShortcutPublish() { private void ensurePackageVersionInfo() {
// Make sure we have the version code for the app. We need the version code in // Make sure we have the version code for the app. We need the version code in
// handlePackageUpdated(). // handlePackageUpdated().
if (getPackageInfo().getVersionCode() < 0) { if (getPackageInfo().getVersionCode() < 0) {
final ShortcutService s = mShortcutUser.mService; final ShortcutService s = mShortcutUser.mService;
final int versionCode = s.getApplicationVersionCode(getPackageName(), getOwnerUserId()); final PackageInfo pi = s.getPackageInfo(getPackageName(), getOwnerUserId());
if (ShortcutService.DEBUG) { if (pi != null) {
Slog.d(TAG, String.format("Package %s version = %d", getPackageName(), if (ShortcutService.DEBUG) {
versionCode)); Slog.d(TAG, String.format("Package %s version = %d", getPackageName(),
} pi.versionCode));
if (versionCode >= 0) { }
getPackageInfo().setVersionCode(versionCode); getPackageInfo().updateVersionInfo(pi);
s.scheduleSaveUser(getOwnerUserId()); s.scheduleSaveUser(getOwnerUserId());
} }
} }
@@ -168,17 +169,42 @@ class ShortcutPackage extends ShortcutPackageItem {
return mShortcuts.get(id); return mShortcuts.get(id);
} }
private ShortcutInfo deleteShortcut(@NonNull String id) { private void ensureNotImmutable(@Nullable ShortcutInfo shortcut) {
if (shortcut != null && shortcut.isImmutable()) {
throw new IllegalArgumentException(
"Manifest shortcut ID=" + shortcut.getId()
+ " may not be manipulated via APIs");
}
}
private void ensureNotImmutable(@NonNull String id) {
ensureNotImmutable(mShortcuts.get(id));
}
public void ensureImmutableShortcutsNotIncludedWithIds(@NonNull List<String> shortcutIds) {
for (int i = shortcutIds.size() - 1; i >= 0; i--) {
ensureNotImmutable(shortcutIds.get(i));
}
}
public void ensureImmutableShortcutsNotIncluded(@NonNull List<ShortcutInfo> shortcuts) {
for (int i = shortcuts.size() - 1; i >= 0; i--) {
ensureNotImmutable(shortcuts.get(i).getId());
}
}
private ShortcutInfo deleteShortcutInner(@NonNull String id) {
final ShortcutInfo shortcut = mShortcuts.remove(id); final ShortcutInfo shortcut = mShortcuts.remove(id);
if (shortcut != null) { if (shortcut != null) {
mShortcutUser.mService.removeIcon(getPackageUserId(), shortcut); mShortcutUser.mService.removeIcon(getPackageUserId(), shortcut);
shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_PINNED); shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_PINNED
| ShortcutInfo.FLAG_MANIFEST);
} }
return shortcut; return shortcut;
} }
void addShortcut(@NonNull ShortcutInfo newShortcut) { private void addShortcutInner(@NonNull ShortcutInfo newShortcut) {
deleteShortcut(newShortcut.getId()); deleteShortcutInner(newShortcut.getId());
mShortcutUser.mService.saveIconAndFixUpShortcut(getPackageUserId(), newShortcut); mShortcutUser.mService.saveIconAndFixUpShortcut(getPackageUserId(), newShortcut);
mShortcuts.put(newShortcut.getId(), newShortcut); mShortcuts.put(newShortcut.getId(), newShortcut);
} }
@@ -188,39 +214,41 @@ class ShortcutPackage extends ShortcutPackageItem {
* *
* It checks the max number of dynamic shortcuts. * It checks the max number of dynamic shortcuts.
*/ */
public void addDynamicShortcut(@NonNull ShortcutInfo newShortcut) { public void addOrUpdateDynamicShortcut(@NonNull ShortcutInfo newShortcut) {
onShortcutPublish(); Preconditions.checkArgument(newShortcut.isEnabled(),
"add/setDynamicShortcuts() cannot publish disabled shortcuts");
ensurePackageVersionInfo();
newShortcut.addFlags(ShortcutInfo.FLAG_DYNAMIC); newShortcut.addFlags(ShortcutInfo.FLAG_DYNAMIC);
final ShortcutInfo oldShortcut = mShortcuts.get(newShortcut.getId()); final ShortcutInfo oldShortcut = mShortcuts.get(newShortcut.getId());
final boolean wasPinned; final boolean wasPinned;
final int newDynamicCount;
if (oldShortcut == null) { if (oldShortcut == null) {
wasPinned = false; wasPinned = false;
newDynamicCount = mDynamicShortcutCount + 1; // adding a dynamic shortcut.
} else { } else {
// It's an update case.
// Make sure the target is updatable. (i.e. should be mutable.)
oldShortcut.ensureUpdatableWith(newShortcut);
wasPinned = oldShortcut.isPinned(); wasPinned = oldShortcut.isPinned();
if (oldShortcut.isDynamic()) { if (!oldShortcut.isEnabled()) {
newDynamicCount = mDynamicShortcutCount; // not adding a dynamic shortcut. newShortcut.addFlags(ShortcutInfo.FLAG_DISABLED);
} else {
newDynamicCount = mDynamicShortcutCount + 1; // adding a dynamic shortcut.
} }
} }
// Make sure there's still room. // TODO Check max dynamic count.
mShortcutUser.mService.enforceMaxDynamicShortcuts(newDynamicCount); // mShortcutUser.mService.enforceMaxDynamicShortcuts(newDynamicCount);
// Okay, make it dynamic and add. // Okay, make it dynamic and add.
if (wasPinned) { if (wasPinned) {
newShortcut.addFlags(ShortcutInfo.FLAG_PINNED); newShortcut.addFlags(ShortcutInfo.FLAG_PINNED);
} }
addShortcut(newShortcut); addShortcutInner(newShortcut);
mDynamicShortcutCount = newDynamicCount;
} }
/** /**
@@ -232,7 +260,7 @@ class ShortcutPackage extends ShortcutPackageItem {
for (int i = mShortcuts.size() - 1; i >= 0; i--) { for (int i = mShortcuts.size() - 1; i >= 0; i--) {
final ShortcutInfo si = mShortcuts.valueAt(i); final ShortcutInfo si = mShortcuts.valueAt(i);
if (si.isPinned() || si.isDynamic()) continue; if (si.isAlive()) continue;
if (removeList == null) { if (removeList == null) {
removeList = new ArrayList<>(); removeList = new ArrayList<>();
@@ -241,7 +269,7 @@ class ShortcutPackage extends ShortcutPackageItem {
} }
if (removeList != null) { if (removeList != null) {
for (int i = removeList.size() - 1; i >= 0; i--) { for (int i = removeList.size() - 1; i >= 0; i--) {
deleteShortcut(removeList.get(i)); deleteShortcutInner(removeList.get(i));
} }
} }
} }
@@ -250,29 +278,68 @@ class ShortcutPackage extends ShortcutPackageItem {
* Remove all dynamic shortcuts. * Remove all dynamic shortcuts.
*/ */
public void deleteAllDynamicShortcuts() { public void deleteAllDynamicShortcuts() {
boolean changed = false;
for (int i = mShortcuts.size() - 1; i >= 0; i--) { for (int i = mShortcuts.size() - 1; i >= 0; i--) {
mShortcuts.valueAt(i).clearFlags(ShortcutInfo.FLAG_DYNAMIC); final ShortcutInfo si = mShortcuts.valueAt(i);
if (si.isDynamic()) {
changed = true;
si.clearFlags(ShortcutInfo.FLAG_DYNAMIC);
}
}
if (changed) {
removeOrphans();
} }
removeOrphans();
mDynamicShortcutCount = 0;
} }
/** /**
* Remove a dynamic shortcut by ID. * Remove a dynamic shortcut by ID.
*/ */
public void deleteDynamicWithId(@NonNull String shortcutId) { public void deleteDynamicWithId(@NonNull String shortcutId) {
deleteOrDisableWithId(shortcutId, /* disable =*/ false, /* overrideImmutable=*/ false);
}
public void disableWithId(@NonNull String shortcutId, String disabledMessage,
int disabledMessageResId, boolean overrideImmutable) {
final ShortcutInfo disabled = deleteOrDisableWithId(shortcutId, /* disable =*/ true,
overrideImmutable);
if (disabled != null) {
if (disabledMessage != null) {
disabled.setDisabledMessage(disabledMessage);
} else if (disabledMessageResId != 0) {
disabled.setDisabledMessageResId(disabledMessageResId);
}
}
}
@Nullable
private ShortcutInfo deleteOrDisableWithId(@NonNull String shortcutId, boolean disable,
boolean overrideImmutable) {
final ShortcutInfo oldShortcut = mShortcuts.get(shortcutId); final ShortcutInfo oldShortcut = mShortcuts.get(shortcutId);
if (oldShortcut == null) { if (oldShortcut == null || !oldShortcut.isEnabled()) {
return; return null; // Doesn't exist or already disabled.
} }
if (oldShortcut.isDynamic()) { if (!overrideImmutable) {
mDynamicShortcutCount--; ensureNotImmutable(oldShortcut);
} }
if (oldShortcut.isPinned()) { if (oldShortcut.isPinned()) {
oldShortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC); oldShortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_MANIFEST);
if (disable) {
oldShortcut.addFlags(ShortcutInfo.FLAG_DISABLED);
}
return oldShortcut;
} else { } else {
deleteShortcut(shortcutId); deleteShortcutInner(shortcutId);
return null;
}
}
public void enableWithId(@NonNull String shortcutId) {
final ShortcutInfo shortcut = mShortcuts.get(shortcutId);
if (shortcut != null) {
ensureNotImmutable(shortcut);
shortcut.clearFlags(ShortcutInfo.FLAG_DISABLED);
} }
} }
@@ -430,22 +497,19 @@ class ShortcutPackage extends ShortcutPackageItem {
for (int i = 0; i < mShortcuts.size(); i++) { for (int i = 0; i < mShortcuts.size(); i++) {
final ShortcutInfo si = mShortcuts.valueAt(i); final ShortcutInfo si = mShortcuts.valueAt(i);
// If it's called by non-launcher (i.e. publisher, always include -> true. // Need to adjust PINNED flag depending on the caller.
// Otherwise, only include non-dynamic pinned one, if the calling launcher has pinned // Basically if the caller is a launcher (callingLauncher != null) and the launcher
// it. // isn't pinning it, then we need to clear PINNED for this caller.
final boolean isPinnedByCaller = (callingLauncher == null) final boolean isPinnedByCaller = (callingLauncher == null)
|| ((pinnedByCallerSet != null) && pinnedByCallerSet.contains(si.getId())); || ((pinnedByCallerSet != null) && pinnedByCallerSet.contains(si.getId()));
if (!si.isDynamic()) {
if (!si.isPinned()) { if (si.isFloating()) {
s.wtf("Shortcut not pinned: package " + getPackageName()
+ ", user=" + getPackageUserId() + ", id=" + si.getId());
continue;
}
if (!isPinnedByCaller) { if (!isPinnedByCaller) {
continue; continue;
} }
} }
final ShortcutInfo clone = si.clone(cloneFlag); final ShortcutInfo clone = si.clone(cloneFlag);
// Fix up isPinned for the caller. Note we need to do it before the "test" callback, // Fix up isPinned for the caller. Note we need to do it before the "test" callback,
// since it may check isPinned. // since it may check isPinned.
if (!isPinnedByCaller) { if (!isPinnedByCaller) {
@@ -486,32 +550,81 @@ class ShortcutPackage extends ShortcutPackageItem {
} }
/** /**
* Called when the package is updated. If there are shortcuts with resource icons, update * Called when the package is updated or added.
* their timestamps. *
* Add case:
* - Publish manifest shortcuts.
*
* Update case:
* - Re-publish manifest shortcuts.
* - If there are shortcuts with resources (icons or strings), update their timestamps.
*
* @return TRUE if any shortcuts have been changed.
*/ */
public void handlePackageUpdated(int newVersionCode) { public boolean handlePackageAddedOrUpdated(boolean isNewApp) {
if (getPackageInfo().getVersionCode() >= newVersionCode) { final PackageInfo pi = mShortcutUser.mService.getPackageInfo(
// Version hasn't changed; nothing to do. getPackageName(), getPackageUserId());
return; if (pi == null) {
} return false; // Shouldn't happen.
if (ShortcutService.DEBUG) {
Slog.d(TAG, String.format("Package %s updated, version %d -> %d", getPackageName(),
getPackageInfo().getVersionCode(), newVersionCode));
} }
getPackageInfo().setVersionCode(newVersionCode); if (!isNewApp) {
// Make sure the version code or last update time has changed.
// Otherwise, nothing to do.
if (getPackageInfo().getVersionCode() >= pi.versionCode
&& getPackageInfo().getLastUpdateTime() >= pi.lastUpdateTime) {
return false;
}
}
// Now prepare to publish manifest shortcuts.
List<ShortcutInfo> newManifestShortcutList = null;
try {
newManifestShortcutList = ShortcutParser.parseShortcuts(mShortcutUser.mService,
getPackageName(), getPackageUserId());
} catch (IOException|XmlPullParserException e) {
Slog.e(TAG, "Failed to load shortcuts from AndroidManifest.xml.", e);
}
final int manifestShortcutSize = newManifestShortcutList == null ? 0
: newManifestShortcutList.size();
if (ShortcutService.DEBUG) {
Slog.d(TAG, String.format("Package %s has %d manifest shortcut(s)",
getPackageName(), manifestShortcutSize));
}
if (isNewApp && (manifestShortcutSize == 0)) {
// If it's a new app, and it doesn't have manifest shortcuts, then nothing to do.
// If it's an update, then it may already have manifest shortcuts, which need to be
// disabled.
return false;
}
if (ShortcutService.DEBUG) {
Slog.d(TAG, String.format("Package %s %s, version %d -> %d", getPackageName(),
(isNewApp ? "added" : "updated"),
getPackageInfo().getVersionCode(), pi.versionCode));
}
getPackageInfo().updateVersionInfo(pi);
final ShortcutService s = mShortcutUser.mService; final ShortcutService s = mShortcutUser.mService;
boolean changed = false; boolean changed = false;
for (int i = mShortcuts.size() - 1; i >= 0; i--) {
final ShortcutInfo si = mShortcuts.valueAt(i);
if (si.hasAnyResources()) { // For existing shortcuts, update timestamps if they have any resources.
changed = true; if (!isNewApp) {
si.setTimestamp(s.injectCurrentTimeMillis()); for (int i = mShortcuts.size() - 1; i >= 0; i--) {
final ShortcutInfo si = mShortcuts.valueAt(i);
if (si.hasAnyResources()) {
changed = true;
si.setTimestamp(s.injectCurrentTimeMillis());
}
} }
} }
// (Re-)publish manifest shortcut.
changed |= publishManifestShortcuts(newManifestShortcutList);
if (changed) { if (changed) {
// This will send a notification to the launcher, and also save . // This will send a notification to the launcher, and also save .
s.packageShortcutsChanged(getPackageName(), getPackageUserId()); s.packageShortcutsChanged(getPackageName(), getPackageUserId());
@@ -519,6 +632,97 @@ class ShortcutPackage extends ShortcutPackageItem {
// Still save the version code. // Still save the version code.
s.scheduleSaveUser(getPackageUserId()); s.scheduleSaveUser(getPackageUserId());
} }
return changed;
}
private boolean publishManifestShortcuts(List<ShortcutInfo> newManifestShortcutList) {
if (ShortcutService.DEBUG) {
Slog.d(TAG, String.format(
"Package %s: publishing manifest shortcuts", getPackageName()));
}
boolean changed = false;
// TODO: Check dynamic count
// TODO: Kick out dynamic if too many
// Keep the previous IDs.
ArraySet<String> toDisableList = null;
for (int i = mShortcuts.size() - 1; i >= 0; i--) {
final ShortcutInfo si = mShortcuts.valueAt(i);
if (si.isManifestShortcut()) {
if (toDisableList == null) {
toDisableList = new ArraySet<>();
}
toDisableList.add(si.getId());
}
}
// Publish new ones.
if (newManifestShortcutList != null) {
final int newListSize = newManifestShortcutList.size();
for (int i = 0; i < newListSize; i++) {
changed = true;
final ShortcutInfo newShortcut = newManifestShortcutList.get(i);
final boolean newDisabled = !newShortcut.isEnabled();
final String id = newShortcut.getId();
final ShortcutInfo oldShortcut = mShortcuts.get(id);
boolean wasPinned = false;
if (oldShortcut != null) {
if (!oldShortcut.isOriginallyFromManifest()) {
Slog.e(TAG, "Shortcut with ID=" + newShortcut.getId()
+ " exists but is not from AndroidManifest.xml, not updating.");
continue;
}
// Take over the pinned flag.
if (oldShortcut.isPinned()) {
wasPinned = true;
newShortcut.addFlags(ShortcutInfo.FLAG_PINNED);
}
}
if (newDisabled && !wasPinned) {
// If the shortcut is disabled, and it was *not* pinned, then this
// just doesn't have to be published.
// Just keep it in toDisableList, so the previous one would be removed.
continue;
}
// TODO: Check dynamic count
// Note even if enabled=false, we still need to update all fields, so do it
// regardless.
addShortcutInner(newShortcut); // This will clean up the old one too.
if (!newDisabled && toDisableList != null) {
// Still alive, don't remove.
toDisableList.remove(id);
}
}
}
// Disable the previous manifest shortcuts that are no longer in the manifest.
if (toDisableList != null) {
if (ShortcutService.DEBUG) {
Slog.d(TAG, String.format(
"Package %s: disabling %d stale shortcuts", getPackageName(),
toDisableList.size()));
}
for (int i = toDisableList.size() - 1; i >= 0; i--) {
changed = true;
final String id = toDisableList.valueAt(i);
disableWithId(id, /* disable message =*/ null, /* disable message resid */ 0,
/* overrideImmutable=*/ true);
}
removeOrphans();
}
return changed;
} }
public void dump(@NonNull PrintWriter pw, @NonNull String prefix) { public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
@@ -597,7 +801,6 @@ class ShortcutPackage extends ShortcutPackageItem {
out.startTag(null, TAG_ROOT); out.startTag(null, TAG_ROOT);
ShortcutService.writeAttr(out, ATTR_NAME, getPackageName()); ShortcutService.writeAttr(out, ATTR_NAME, getPackageName());
ShortcutService.writeAttr(out, ATTR_DYNAMIC_COUNT, mDynamicShortcutCount);
ShortcutService.writeAttr(out, ATTR_CALL_COUNT, mApiCallCount); ShortcutService.writeAttr(out, ATTR_CALL_COUNT, mApiCallCount);
ShortcutService.writeAttr(out, ATTR_LAST_RESET, mLastResetTime); ShortcutService.writeAttr(out, ATTR_LAST_RESET, mLastResetTime);
getPackageInfo().saveToXml(out); getPackageInfo().saveToXml(out);
@@ -619,7 +822,7 @@ class ShortcutPackage extends ShortcutPackageItem {
out.startTag(null, TAG_SHORTCUT); out.startTag(null, TAG_SHORTCUT);
ShortcutService.writeAttr(out, ATTR_ID, si.getId()); ShortcutService.writeAttr(out, ATTR_ID, si.getId());
// writeAttr(out, "package", si.getPackageName()); // not needed // writeAttr(out, "package", si.getPackageName()); // not needed
ShortcutService.writeAttr(out, ATTR_ACTIVITY, si.getActivityComponent()); ShortcutService.writeAttr(out, ATTR_ACTIVITY, si.getActivity());
// writeAttr(out, "icon", si.getIcon()); // We don't save it. // writeAttr(out, "icon", si.getIcon()); // We don't save it.
ShortcutService.writeAttr(out, ATTR_TITLE, si.getTitle()); ShortcutService.writeAttr(out, ATTR_TITLE, si.getTitle());
ShortcutService.writeAttr(out, ATTR_TITLE_RES_ID, si.getTitleResId()); ShortcutService.writeAttr(out, ATTR_TITLE_RES_ID, si.getTitleResId());
@@ -670,8 +873,6 @@ class ShortcutPackage extends ShortcutPackageItem {
final ShortcutPackage ret = new ShortcutPackage(shortcutUser, final ShortcutPackage ret = new ShortcutPackage(shortcutUser,
shortcutUser.getUserId(), packageName); shortcutUser.getUserId(), packageName);
ret.mDynamicShortcutCount =
ShortcutService.parseIntAttribute(parser, ATTR_DYNAMIC_COUNT);
ret.mApiCallCount = ret.mApiCallCount =
ShortcutService.parseIntAttribute(parser, ATTR_CALL_COUNT); ShortcutService.parseIntAttribute(parser, ATTR_CALL_COUNT);
ret.mLastResetTime = ret.mLastResetTime =

View File

@@ -15,6 +15,7 @@
*/ */
package com.android.server.pm; package com.android.server.pm;
import android.annotation.NonNull;
import android.annotation.UserIdInt; import android.annotation.UserIdInt;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.util.Slog; import android.util.Slog;
@@ -34,12 +35,15 @@ import java.util.ArrayList;
/** /**
* Package information used by {@link android.content.pm.ShortcutManager} for backup / restore. * Package information used by {@link android.content.pm.ShortcutManager} for backup / restore.
*
* All methods should be guarded by {@code ShortcutService.mLock}.
*/ */
class ShortcutPackageInfo { class ShortcutPackageInfo {
private static final String TAG = ShortcutService.TAG; private static final String TAG = ShortcutService.TAG;
static final String TAG_ROOT = "package-info"; static final String TAG_ROOT = "package-info";
private static final String ATTR_VERSION = "version"; private static final String ATTR_VERSION = "version";
private static final String ATTR_LAST_UPDATE_TIME = "last_udpate_time";
private static final String ATTR_SHADOW = "shadow"; private static final String ATTR_SHADOW = "shadow";
private static final String TAG_SIGNATURE = "signature"; private static final String TAG_SIGNATURE = "signature";
@@ -53,16 +57,20 @@ class ShortcutPackageInfo {
*/ */
private boolean mIsShadow; private boolean mIsShadow;
private int mVersionCode = VERSION_UNKNOWN; private int mVersionCode = VERSION_UNKNOWN;
private long mLastUpdateTime;
private ArrayList<byte[]> mSigHashes; private ArrayList<byte[]> mSigHashes;
private ShortcutPackageInfo(int versionCode, ArrayList<byte[]> sigHashes, boolean isShadow) { private ShortcutPackageInfo(int versionCode, long lastUpdateTime,
ArrayList<byte[]> sigHashes, boolean isShadow) {
mVersionCode = versionCode; mVersionCode = versionCode;
mLastUpdateTime = lastUpdateTime;
mIsShadow = isShadow; mIsShadow = isShadow;
mSigHashes = sigHashes; mSigHashes = sigHashes;
} }
public static ShortcutPackageInfo newEmpty() { public static ShortcutPackageInfo newEmpty() {
return new ShortcutPackageInfo(VERSION_UNKNOWN, new ArrayList<>(0), /* isShadow */ false); return new ShortcutPackageInfo(VERSION_UNKNOWN, /* last update time =*/ 0,
new ArrayList<>(0), /* isShadow */ false);
} }
public boolean isShadow() { public boolean isShadow() {
@@ -77,8 +85,15 @@ class ShortcutPackageInfo {
return mVersionCode; return mVersionCode;
} }
public void setVersionCode(int versionCode) { public long getLastUpdateTime() {
mVersionCode = versionCode; return mLastUpdateTime;
}
public void updateVersionInfo(@NonNull PackageInfo pi) {
if (pi != null) {
mVersionCode = pi.versionCode;
mLastUpdateTime = pi.lastUpdateTime;
}
} }
public boolean hasSignatures() { public boolean hasSignatures() {
@@ -111,7 +126,7 @@ class ShortcutPackageInfo {
Slog.e(TAG, "Can't get signatures: package=" + packageName); Slog.e(TAG, "Can't get signatures: package=" + packageName);
return null; return null;
} }
final ShortcutPackageInfo ret = new ShortcutPackageInfo(pi.versionCode, final ShortcutPackageInfo ret = new ShortcutPackageInfo(pi.versionCode, pi.lastUpdateTime,
BackupUtils.hashSignatureArray(pi.signatures), /* shadow=*/ false); BackupUtils.hashSignatureArray(pi.signatures), /* shadow=*/ false);
return ret; return ret;
@@ -131,6 +146,7 @@ class ShortcutPackageInfo {
return; return;
} }
mVersionCode = pi.versionCode; mVersionCode = pi.versionCode;
mLastUpdateTime = pi.lastUpdateTime;
mSigHashes = BackupUtils.hashSignatureArray(pi.signatures); mSigHashes = BackupUtils.hashSignatureArray(pi.signatures);
} }
@@ -139,6 +155,7 @@ class ShortcutPackageInfo {
out.startTag(null, TAG_ROOT); out.startTag(null, TAG_ROOT);
ShortcutService.writeAttr(out, ATTR_VERSION, mVersionCode); ShortcutService.writeAttr(out, ATTR_VERSION, mVersionCode);
ShortcutService.writeAttr(out, ATTR_LAST_UPDATE_TIME, mLastUpdateTime);
ShortcutService.writeAttr(out, ATTR_SHADOW, mIsShadow); ShortcutService.writeAttr(out, ATTR_SHADOW, mIsShadow);
for (int i = 0; i < mSigHashes.size(); i++) { for (int i = 0; i < mSigHashes.size(); i++) {
@@ -154,6 +171,9 @@ class ShortcutPackageInfo {
final int versionCode = ShortcutService.parseIntAttribute(parser, ATTR_VERSION); final int versionCode = ShortcutService.parseIntAttribute(parser, ATTR_VERSION);
final long lastUpdateTime = ShortcutService.parseIntAttribute(
parser, ATTR_LAST_UPDATE_TIME);
// When restoring from backup, it's always shadow. // When restoring from backup, it's always shadow.
final boolean shadow = final boolean shadow =
fromBackup || ShortcutService.parseBooleanAttribute(parser, ATTR_SHADOW); fromBackup || ShortcutService.parseBooleanAttribute(parser, ATTR_SHADOW);
@@ -185,6 +205,7 @@ class ShortcutPackageInfo {
// Successfully loaded; replace the feilds. // Successfully loaded; replace the feilds.
mVersionCode = versionCode; mVersionCode = versionCode;
mLastUpdateTime = lastUpdateTime;
mIsShadow = shadow; mIsShadow = shadow;
mSigHashes = hashes; mSigHashes = hashes;
} }
@@ -205,6 +226,11 @@ class ShortcutPackageInfo {
pw.print(mVersionCode); pw.print(mVersionCode);
pw.println(); pw.println();
pw.print(prefix);
pw.print(" Last package update time: ");
pw.print(mLastUpdateTime);
pw.println();
for (int i = 0; i < mSigHashes.size(); i++) { for (int i = 0; i < mSigHashes.size(); i++) {
pw.print(prefix); pw.print(prefix);
pw.print(" "); pw.print(" ");

View File

@@ -26,6 +26,9 @@ import org.xmlpull.v1.XmlSerializer;
import java.io.IOException; import java.io.IOException;
/**
* All methods should be guarded by {@code #mShortcutUser.mService.mLock}.
*/
abstract class ShortcutPackageItem { abstract class ShortcutPackageItem {
private static final String TAG = ShortcutService.TAG; private static final String TAG = ShortcutService.TAG;

View File

@@ -0,0 +1,236 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed 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.
*/
package com.android.server.pm;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
import android.content.pm.ShortcutInfo;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.net.Uri;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.Slog;
import android.util.Xml;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
public class ShortcutParser {
private static final String TAG = ShortcutService.TAG;
private static final boolean DEBUG = ShortcutService.DEBUG || false; // DO NOT SUBMIT WITH TRUE
@VisibleForTesting
static final String METADATA_KEY = "android.pm.Shortcuts";
private static final String TAG_SHORTCUTS = "shortcuts";
private static final String TAG_SHORTCUT = "shortcut";
@Nullable
public static List<ShortcutInfo> parseShortcuts(ShortcutService service,
String packageName, @UserIdInt int userId) throws IOException, XmlPullParserException {
final PackageInfo pi = service.injectGetActivitiesWithMetadata(packageName, userId);
List<ShortcutInfo> result = null;
if (pi != null && pi.activities != null) {
for (ActivityInfo activityInfo : pi.activities) {
result = parseShortcutsOneFile(service, activityInfo, packageName, userId, result);
}
}
return result;
}
private static List<ShortcutInfo> parseShortcutsOneFile(
ShortcutService service,
ActivityInfo activityInfo, String packageName, @UserIdInt int userId,
List<ShortcutInfo> result) throws IOException, XmlPullParserException {
XmlResourceParser parser = null;
try {
parser = service.injectXmlMetaData(activityInfo, METADATA_KEY);
if (parser == null) {
return result;
}
final ComponentName activity = new ComponentName(packageName, activityInfo.name);
final AttributeSet attrs = Xml.asAttributeSet(parser);
int type;
outer:
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > 0)) {
if (type != XmlPullParser.START_TAG) {
continue;
}
final int depth = parser.getDepth();
final String tag = parser.getName();
if (depth == 1 && TAG_SHORTCUTS.equals(tag)) {
continue; // Root tag.
}
if (depth == 2 && TAG_SHORTCUT.equals(tag)) {
final ShortcutInfo si = parseShortcutAttributes(
service, attrs, packageName, activity, userId);
if (ShortcutService.DEBUG) {
Slog.d(TAG, "Shortcut=" + si);
}
if (result != null) {
for (int i = result.size() - 1; i >= 0; i--) {
if (si.getId().equals(result.get(i).getId())) {
Slog.w(TAG, "Duplicate shortcut ID detected, skipping.");
continue outer;
}
}
}
if (si != null) {
if (result == null) {
result = new ArrayList<>();
}
result.add(si);
}
continue;
}
Slog.w(TAG, "Unknown tag " + tag);
}
} finally {
if (parser != null) {
parser.close();
}
}
return result;
}
private static ShortcutInfo parseShortcutAttributes(ShortcutService service,
AttributeSet attrs, String packageName, ComponentName activity,
@UserIdInt int userId) {
final TypedArray sa = service.mContext.getResources().obtainAttributes(attrs,
R.styleable.Shortcut);
try {
final String id = sa.getString(R.styleable.Shortcut_shortcutId);
final boolean enabled = sa.getBoolean(R.styleable.Shortcut_enabled, true);
final int rank = sa.getInt(R.styleable.Shortcut_shortcutRank, 0);
final int iconResId = sa.getResourceId(R.styleable.Shortcut_shortcutIcon, 0);
final int titleResId = sa.getResourceId(R.styleable.Shortcut_shortcutTitle, 0);
final int textResId = sa.getResourceId(R.styleable.Shortcut_shortcutText, 0);
final int disabledMessageResId = sa.getResourceId(
R.styleable.Shortcut_shortcutDisabledMessage, 0);
final String categories = sa.getString(R.styleable.Shortcut_shortcutCategories);
String intentAction = sa.getString(R.styleable.Shortcut_shortcutIntentAction);
final String intentData = sa.getString(R.styleable.Shortcut_shortcutIntentData);
if (TextUtils.isEmpty(id)) {
Slog.w(TAG, "Shortcut ID must be provided. activity=" + activity);
return null;
}
if (titleResId == 0) {
Slog.w(TAG, "Shortcut title must be provided. activity=" + activity);
return null;
}
if (TextUtils.isEmpty(intentAction)) {
if (enabled) {
Slog.w(TAG, "Shortcut intent action must be provided. activity=" + activity);
return null;
} else {
// Disabled shortcut doesn't have to have an action, but just set VIEW as the
// default.
intentAction = Intent.ACTION_VIEW;
}
}
final ArraySet<String> categoriesSet;
if (categories == null) {
categoriesSet = null;
} else {
final String[] arr = categories.split(":");
categoriesSet = new ArraySet<>(arr.length);
for (String v : arr) {
categoriesSet.add(v);
}
}
final Intent intent = new Intent(intentAction);
if (!TextUtils.isEmpty(intentData)) {
intent.setData(Uri.parse(intentData));
}
return createShortcutFromManifest(
service,
userId,
id,
packageName,
activity,
titleResId,
textResId,
disabledMessageResId,
categoriesSet,
intent,
rank,
iconResId,
enabled);
} finally {
sa.recycle();
}
}
private static ShortcutInfo createShortcutFromManifest(ShortcutService service,
@UserIdInt int userId, String id, String packageName, ComponentName activityComponent,
int titleResId, int textResId, int disabledMessageResId, Set<String> categories,
Intent intent, int rank, int iconResId, boolean enabled) {
final int flags =
(enabled ? ShortcutInfo.FLAG_MANIFEST : ShortcutInfo.FLAG_DISABLED)
| ShortcutInfo.FLAG_IMMUTABLE
| ((iconResId != 0) ? ShortcutInfo.FLAG_HAS_ICON_RES : 0);
return new ShortcutInfo(
userId,
id,
packageName,
activityComponent,
null, // icon
null, // title string
titleResId,
null, // text string
textResId,
null, // disabled message string
disabledMessageResId,
categories,
intent,
null, // intent extras
rank,
null, // extras
service.injectCurrentTimeMillis(),
flags,
iconResId,
null); // bitmap path
}
}

View File

@@ -25,6 +25,7 @@ import android.app.IUidObserver;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager; import android.content.pm.IPackageManager;
import android.content.pm.IShortcutService; import android.content.pm.IShortcutService;
@@ -39,6 +40,7 @@ import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo; import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutServiceInternal; import android.content.pm.ShortcutServiceInternal;
import android.content.pm.ShortcutServiceInternal.ShortcutChangeListener; import android.content.pm.ShortcutServiceInternal.ShortcutChangeListener;
import android.content.res.XmlResourceParser;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat; import android.graphics.Bitmap.CompressFormat;
import android.graphics.Canvas; import android.graphics.Canvas;
@@ -103,6 +105,7 @@ import java.io.PrintWriter;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
@@ -111,16 +114,21 @@ import java.util.function.Predicate;
/** /**
* TODO: * TODO:
* - Manifest shortcuts. * - Implement # of dynamic shortcuts cap.
* *
* - Implement disableShortcuts(). * - HandleUnlockUser needs to be async. Wait on it in onCleanupUser.
* *
* - Implement reportShortcutUsed(). * - Implement reportShortcutUsed().
* *
* - validateForXml() should be removed.
*
* - Ranks should be recalculated after each update. * - Ranks should be recalculated after each update.
* *
* - When the system locale changes, update timestamps for shortcuts with string resources, * - When the system locale changes, update timestamps for shortcuts with string resources,
* and notify the launcher. * and notify the launcher. Right now, it resets the throttling, but timestamps are not changed
* and there's no notification either.
*
* - getIconMaxWidth()/getIconMaxHeight() should use xdpi and ydpi.
* *
* - Default launcher check does take a few ms. Worth caching. * - Default launcher check does take a few ms. Worth caching.
* *
@@ -128,11 +136,13 @@ import java.util.function.Predicate;
* internal bitmap handling. * internal bitmap handling.
* *
* - Add more call stats. * - Add more call stats.
*
* - Rename getMaxDynamicShortcutCount and mMaxDynamicShortcuts
*/ */
public class ShortcutService extends IShortcutService.Stub { public class ShortcutService extends IShortcutService.Stub {
static final String TAG = "ShortcutService"; static final String TAG = "ShortcutService";
static final boolean DEBUG = false; // STOPSHIP if true static final boolean DEBUG = true; // STOPSHIP if true
static final boolean DEBUG_LOAD = false; // STOPSHIP if true static final boolean DEBUG_LOAD = false; // STOPSHIP if true
static final boolean DEBUG_PROCSTATE = false; // STOPSHIP if true static final boolean DEBUG_PROCSTATE = false; // STOPSHIP if true
@@ -301,8 +311,11 @@ public class ShortcutService extends IShortcutService.Stub {
int GET_APPLICATION_INFO = 3; int GET_APPLICATION_INFO = 3;
int LAUNCHER_PERMISSION_CHECK = 4; int LAUNCHER_PERMISSION_CHECK = 4;
int CLEANUP_DANGLING_BITMAPS = 5; int CLEANUP_DANGLING_BITMAPS = 5;
int GET_ACTIVITIES_WITH_METADATA = 6;
int GET_INSTALLED_APPLICATIONS = 7;
int CHECK_PACKAGE_CHANGES = 8;
int COUNT = CLEANUP_DANGLING_BITMAPS + 1; int COUNT = CHECK_PACKAGE_CHANGES + 1;
} }
final Object mStatLock = new Object(); final Object mStatLock = new Object();
@@ -444,6 +457,9 @@ public class ShortcutService extends IShortcutService.Stub {
/** lifecycle event */ /** lifecycle event */
void handleUnlockUser(int userId) { void handleUnlockUser(int userId) {
if (DEBUG) {
Slog.d(TAG, "handleUnlockUser: user=" + userId);
}
synchronized (mLock) { synchronized (mLock) {
// Preload // Preload
getUserShortcutsLocked(userId); getUserShortcutsLocked(userId);
@@ -642,10 +658,10 @@ public class ShortcutService extends IShortcutService.Stub {
out.endTag(null, tag); out.endTag(null, tag);
} }
static void writeAttr(XmlSerializer out, String name, String value) throws IOException { static void writeAttr(XmlSerializer out, String name, CharSequence value) throws IOException {
if (TextUtils.isEmpty(value)) return; if (TextUtils.isEmpty(value)) return;
out.attribute(null, name, value); out.attribute(null, name, value.toString());
} }
static void writeAttr(XmlSerializer out, String name, long value) throws IOException { static void writeAttr(XmlSerializer out, String name, long value) throws IOException {
@@ -1090,7 +1106,7 @@ public class ShortcutService extends IShortcutService.Stub {
FileOutputStreamWithPath openIconFileForWrite(@UserIdInt int userId, ShortcutInfo shortcut) FileOutputStreamWithPath openIconFileForWrite(@UserIdInt int userId, ShortcutInfo shortcut)
throws IOException { throws IOException {
final File packagePath = new File(getUserBitmapFilePath(userId), final File packagePath = new File(getUserBitmapFilePath(userId),
shortcut.getPackageName()); shortcut.getPackage());
if (!packagePath.isDirectory()) { if (!packagePath.isDirectory()) {
packagePath.mkdirs(); packagePath.mkdirs();
if (!packagePath.isDirectory()) { if (!packagePath.isDirectory()) {
@@ -1129,7 +1145,6 @@ public class ShortcutService extends IShortcutService.Stub {
} }
Bitmap bitmap; Bitmap bitmap;
Bitmap bitmapToRecycle = null;
try { try {
switch (icon.getType()) { switch (icon.getType()) {
case Icon.TYPE_RESOURCE: { case Icon.TYPE_RESOURCE: {
@@ -1181,9 +1196,6 @@ public class ShortcutService extends IShortcutService.Stub {
} }
} }
} finally { } finally {
if (bitmapToRecycle != null) {
bitmapToRecycle.recycle();
}
// Once saved, we won't use the original icon information, so null it out. // Once saved, we won't use the original icon information, so null it out.
shortcut.clearIcon(); shortcut.clearIcon();
} }
@@ -1196,7 +1208,7 @@ public class ShortcutService extends IShortcutService.Stub {
// so override in unit tests. // so override in unit tests.
// TODO CTS this case. // TODO CTS this case.
void injectValidateIconResPackage(ShortcutInfo shortcut, Icon icon) { void injectValidateIconResPackage(ShortcutInfo shortcut, Icon icon) {
if (!shortcut.getPackageName().equals(icon.getResPackage())) { if (!shortcut.getPackage().equals(icon.getResPackage())) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
"Icon resource must reside in shortcut owner package"); "Icon resource must reside in shortcut owner package");
} }
@@ -1353,10 +1365,9 @@ public class ShortcutService extends IShortcutService.Stub {
*/ */
private void fixUpIncomingShortcutInfo(@NonNull ShortcutInfo shortcut, boolean forUpdate) { private void fixUpIncomingShortcutInfo(@NonNull ShortcutInfo shortcut, boolean forUpdate) {
Preconditions.checkNotNull(shortcut, "Null shortcut detected"); Preconditions.checkNotNull(shortcut, "Null shortcut detected");
if (shortcut.getActivityComponent() != null) { if (shortcut.getActivity() != null) {
Preconditions.checkState( Preconditions.checkState(
shortcut.getPackageName().equals( shortcut.getPackage().equals(shortcut.getActivity().getPackageName()),
shortcut.getActivityComponent().getPackageName()),
"Activity package name mismatch"); "Activity package name mismatch");
} }
@@ -1399,7 +1410,7 @@ public class ShortcutService extends IShortcutService.Stub {
} }
} }
private static void validateForXml(String s) { private static void validateForXml(CharSequence s) {
if (TextUtils.isEmpty(s)) { if (TextUtils.isEmpty(s)) {
return; return;
} }
@@ -1427,6 +1438,8 @@ public class ShortcutService extends IShortcutService.Stub {
synchronized (mLock) { synchronized (mLock) {
final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId); final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
// Throttling. // Throttling.
if (!ps.tryApiCall()) { if (!ps.tryApiCall()) {
return false; return false;
@@ -1444,7 +1457,7 @@ public class ShortcutService extends IShortcutService.Stub {
// Then, add/update all. We need to make sure to take over "pinned" flag. // Then, add/update all. We need to make sure to take over "pinned" flag.
for (int i = 0; i < size; i++) { for (int i = 0; i < size; i++) {
final ShortcutInfo newShortcut = newShortcuts.get(i); final ShortcutInfo newShortcut = newShortcuts.get(i);
ps.addDynamicShortcut(newShortcut); ps.addOrUpdateDynamicShortcut(newShortcut);
} }
} }
packageShortcutsChanged(packageName, userId); packageShortcutsChanged(packageName, userId);
@@ -1462,6 +1475,8 @@ public class ShortcutService extends IShortcutService.Stub {
synchronized (mLock) { synchronized (mLock) {
final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId); final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
// Throttling. // Throttling.
if (!ps.tryApiCall()) { if (!ps.tryApiCall()) {
return false; return false;
@@ -1473,11 +1488,22 @@ public class ShortcutService extends IShortcutService.Stub {
final ShortcutInfo target = ps.findShortcutById(source.getId()); final ShortcutInfo target = ps.findShortcutById(source.getId());
if (target != null) { if (target != null) {
if (target.isEnabled() != source.isEnabled()) {
Slog.w(TAG,
"ShortcutInfo.enabled cannot be changed with updateShortcuts()");
}
final boolean replacingIcon = (source.getIcon() != null); final boolean replacingIcon = (source.getIcon() != null);
if (replacingIcon) { if (replacingIcon) {
removeIcon(userId, target); removeIcon(userId, target);
} }
if (source.getActivity() != null &&
!source.getActivity().equals(target.getActivity())) {
// TODO When activity is changing, check the dynamic count.
}
// Note copyNonNullFieldsFrom() does the "udpatable with?" check too.
target.copyNonNullFieldsFrom(source); target.copyNonNullFieldsFrom(source);
if (replacingIcon) { if (replacingIcon) {
@@ -1502,6 +1528,8 @@ public class ShortcutService extends IShortcutService.Stub {
synchronized (mLock) { synchronized (mLock) {
final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId); final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
// Throttling. // Throttling.
if (!ps.tryApiCall()) { if (!ps.tryApiCall()) {
return false; return false;
@@ -1513,7 +1541,7 @@ public class ShortcutService extends IShortcutService.Stub {
fixUpIncomingShortcutInfo(newShortcut, /* forUpdate= */ false); fixUpIncomingShortcutInfo(newShortcut, /* forUpdate= */ false);
// Add it. // Add it.
ps.addDynamicShortcut(newShortcut); ps.addOrUpdateDynamicShortcut(newShortcut);
} }
} }
packageShortcutsChanged(packageName, userId); packageShortcutsChanged(packageName, userId);
@@ -1528,7 +1556,32 @@ public class ShortcutService extends IShortcutService.Stub {
Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided"); Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided");
synchronized (mLock) { synchronized (mLock) {
// TODO implement it. final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds);
for (int i = shortcutIds.size() - 1; i >= 0; i--) {
ps.disableWithId(Preconditions.checkStringNotEmpty((String) shortcutIds.get(i)),
disabledMessage, disabledMessageResId,
/* overrideImmutable=*/ false);
}
}
packageShortcutsChanged(packageName, userId);
}
@Override
public void enableShortcuts(String packageName, List shortcutIds, @UserIdInt int userId) {
verifyCaller(packageName, userId);
Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided");
synchronized (mLock) {
final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds);
for (int i = shortcutIds.size() - 1; i >= 0; i--) {
ps.enableWithId((String) shortcutIds.get(i));
}
} }
packageShortcutsChanged(packageName, userId); packageShortcutsChanged(packageName, userId);
} }
@@ -1540,8 +1593,12 @@ public class ShortcutService extends IShortcutService.Stub {
Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided"); Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided");
synchronized (mLock) { synchronized (mLock) {
final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
ps.ensureImmutableShortcutsNotIncludedWithIds((List<String>) shortcutIds);
for (int i = shortcutIds.size() - 1; i >= 0; i--) { for (int i = shortcutIds.size() - 1; i >= 0; i--) {
getPackageShortcutsLocked(packageName, userId).deleteDynamicWithId( ps.deleteDynamicWithId(
Preconditions.checkStringNotEmpty((String) shortcutIds.get(i))); Preconditions.checkStringNotEmpty((String) shortcutIds.get(i)));
} }
} }
@@ -1569,6 +1626,17 @@ public class ShortcutService extends IShortcutService.Stub {
} }
} }
@Override
public ParceledListSlice<ShortcutInfo> getManifestShortcuts(String packageName,
@UserIdInt int userId) {
verifyCaller(packageName, userId);
synchronized (mLock) {
return getShortcutsWithQueryLocked(
packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
ShortcutInfo::isManifestShortcut);
}
}
@Override @Override
public ParceledListSlice<ShortcutInfo> getPinnedShortcuts(String packageName, public ParceledListSlice<ShortcutInfo> getPinnedShortcuts(String packageName,
@UserIdInt int userId) { @UserIdInt int userId) {
@@ -1883,18 +1951,24 @@ public class ShortcutService extends IShortcutService.Stub {
return false; return false;
} }
if (componentName != null) { if (componentName != null) {
if (si.getActivityComponent() != null if (si.getActivity() != null
&& !si.getActivityComponent().equals(componentName)) { && !si.getActivity().equals(componentName)) {
return false; return false;
} }
} }
final boolean matchDynamic = if (((queryFlags & ShortcutQuery.FLAG_GET_DYNAMIC) != 0)
((queryFlags & ShortcutQuery.FLAG_GET_DYNAMIC) != 0) && si.isDynamic()) {
&& si.isDynamic(); return true;
final boolean matchPinned = }
((queryFlags & ShortcutQuery.FLAG_GET_PINNED) != 0) if (((queryFlags & ShortcutQuery.FLAG_GET_PINNED) != 0)
&& si.isPinned(); && si.isPinned()) {
return matchDynamic || matchPinned; return true;
}
if (((queryFlags & ShortcutQuery.FLAG_GET_MANIFEST) != 0)
&& si.isManifestShortcut()) {
return true;
}
return false;
}, cloneFlag, callingPackage, launcherUserId); }, cloneFlag, callingPackage, launcherUserId);
} }
@@ -1967,7 +2041,7 @@ public class ShortcutService extends IShortcutService.Stub {
final ShortcutInfo si = getShortcutInfoLocked( final ShortcutInfo si = getShortcutInfoLocked(
launcherUserId, callingPackage, packageName, shortcutId, userId); launcherUserId, callingPackage, packageName, shortcutId, userId);
// "si == null" should suffice here, but check the flags too just to make sure. // "si == null" should suffice here, but check the flags too just to make sure.
if (si == null || !(si.isDynamic() || si.isPinned())) { if (si == null || !si.isEnabled() || !si.isAlive()) {
return null; return null;
} }
return si.getIntent(); return si.getIntent();
@@ -2114,31 +2188,37 @@ public class ShortcutService extends IShortcutService.Stub {
if (DEBUG) { if (DEBUG) {
Slog.d(TAG, "checkPackageChanges() ownerUserId=" + ownerUserId); Slog.d(TAG, "checkPackageChanges() ownerUserId=" + ownerUserId);
} }
final ArrayList<PackageWithUser> gonePackages = new ArrayList<>();
synchronized (mLock) { final long start = injectElapsedRealtime();
final ShortcutUser user = getUserShortcutsLocked(ownerUserId); try {
final ArrayList<PackageWithUser> gonePackages = new ArrayList<>();
user.forAllPackageItems(spi -> { synchronized (mLock) {
if (spi.getPackageInfo().isShadow()) { final ShortcutUser user = getUserShortcutsLocked(ownerUserId);
return; // Don't delete shadow information.
} // Find packages that have been uninstalled.
final int versionCode = getApplicationVersionCode( user.forAllPackageItems(spi -> {
spi.getPackageName(), spi.getPackageUserId()); if (spi.getPackageInfo().isShadow()) {
if (versionCode >= 0) { return; // Don't delete shadow information.
// Package still installed, see if it's updated. }
getUserShortcutsLocked(ownerUserId).handlePackageUpdated( if (!isPackageInstalled(spi.getPackageName(), spi.getPackageUserId())) {
spi.getPackageName(), versionCode); gonePackages.add(PackageWithUser.of(spi));
} else { }
gonePackages.add(PackageWithUser.of(spi)); });
} if (gonePackages.size() > 0) {
}); for (int i = gonePackages.size() - 1; i >= 0; i--) {
if (gonePackages.size() > 0) { final PackageWithUser pu = gonePackages.get(i);
for (int i = gonePackages.size() - 1; i >= 0; i--) { cleanUpPackageLocked(pu.packageName, ownerUserId, pu.userId);
final PackageWithUser pu = gonePackages.get(i); }
cleanUpPackageLocked(pu.packageName, ownerUserId, pu.userId);
} }
// Then for each installed app, publish manifest shortcuts when needed.
forInstalledApplications(ownerUserId, ai -> {
user.handlePackageAddedOrUpdated(ai.packageName);
});
} }
} finally {
logDurationStat(Stats.CHECK_PACKAGE_CHANGES, start);
} }
} }
@@ -2147,8 +2227,9 @@ public class ShortcutService extends IShortcutService.Stub {
Slog.d(TAG, String.format("handlePackageAdded: %s user=%d", packageName, userId)); Slog.d(TAG, String.format("handlePackageAdded: %s user=%d", packageName, userId));
} }
synchronized (mLock) { synchronized (mLock) {
forEachLoadedUserLocked(user -> final ShortcutUser user = getUserShortcutsLocked(userId);
user.attemptToRestoreIfNeededAndSave(this, packageName, userId)); user.attemptToRestoreIfNeededAndSave(this, packageName, userId);
user.handlePackageAddedOrUpdated(packageName);
} }
} }
@@ -2158,14 +2239,12 @@ public class ShortcutService extends IShortcutService.Stub {
packageName, userId)); packageName, userId));
} }
synchronized (mLock) { synchronized (mLock) {
forEachLoadedUserLocked(user -> final ShortcutUser user = getUserShortcutsLocked(userId);
user.attemptToRestoreIfNeededAndSave(this, packageName, userId)); user.attemptToRestoreIfNeededAndSave(this, packageName, userId);
final int versionCode = getApplicationVersionCode(packageName, userId); if (isPackageInstalled(packageName, userId)) {
if (versionCode < 0) { user.handlePackageAddedOrUpdated(packageName);
return; // shouldn't happen
} }
getUserShortcutsLocked(userId).handlePackageUpdated(packageName, versionCode);
} }
} }
@@ -2187,10 +2266,16 @@ public class ShortcutService extends IShortcutService.Stub {
// === PackageManager interaction === // === PackageManager interaction ===
@Nullable
PackageInfo getPackageInfoWithSignatures(String packageName, @UserIdInt int userId) { PackageInfo getPackageInfoWithSignatures(String packageName, @UserIdInt int userId) {
return injectPackageInfo(packageName, userId, true); return injectPackageInfo(packageName, userId, true);
} }
@Nullable
PackageInfo getPackageInfo(String packageName, @UserIdInt int userId) {
return injectPackageInfo(packageName, userId, false);
}
int injectGetPackageUid(@NonNull String packageName, @UserIdInt int userId) { int injectGetPackageUid(@NonNull String packageName, @UserIdInt int userId) {
final long token = injectClearCallingIdentity(); final long token = injectClearCallingIdentity();
try { try {
@@ -2205,6 +2290,7 @@ public class ShortcutService extends IShortcutService.Stub {
} }
} }
@Nullable
@VisibleForTesting @VisibleForTesting
PackageInfo injectPackageInfo(String packageName, @UserIdInt int userId, PackageInfo injectPackageInfo(String packageName, @UserIdInt int userId,
boolean getSignatures) { boolean getSignatures) {
@@ -2227,6 +2313,7 @@ public class ShortcutService extends IShortcutService.Stub {
} }
} }
@Nullable
@VisibleForTesting @VisibleForTesting
ApplicationInfo injectApplicationInfo(String packageName, @UserIdInt int userId) { ApplicationInfo injectApplicationInfo(String packageName, @UserIdInt int userId) {
final long start = injectElapsedRealtime(); final long start = injectElapsedRealtime();
@@ -2244,6 +2331,61 @@ public class ShortcutService extends IShortcutService.Stub {
} }
} }
@Nullable
@VisibleForTesting
PackageInfo injectGetActivitiesWithMetadata(String packageName, @UserIdInt int userId) {
final long start = injectElapsedRealtime();
final long token = injectClearCallingIdentity();
try {
return mIPackageManager.getPackageInfo(packageName,
PACKAGE_MATCH_FLAGS | PackageManager.GET_ACTIVITIES
| PackageManager.GET_META_DATA, userId);
} catch (RemoteException e) {
// Shouldn't happen.
Slog.wtf(TAG, "RemoteException", e);
return null;
} finally {
injectRestoreCallingIdentity(token);
logDurationStat(Stats.GET_ACTIVITIES_WITH_METADATA, start);
}
}
@Nullable
@VisibleForTesting
List<ApplicationInfo> injectInstalledApplications(@UserIdInt int userId) {
final long start = injectElapsedRealtime();
final long token = injectClearCallingIdentity();
try {
final ParceledListSlice<ApplicationInfo> parceledList =
mIPackageManager.getInstalledApplications(PACKAGE_MATCH_FLAGS, userId);
if (parceledList == null) {
return Collections.emptyList();
}
return parceledList.getList();
} catch (RemoteException e) {
// Shouldn't happen.
Slog.wtf(TAG, "RemoteException", e);
return null;
} finally {
injectRestoreCallingIdentity(token);
logDurationStat(Stats.GET_INSTALLED_APPLICATIONS, start);
}
}
private void forInstalledApplications(@UserIdInt int userId,
Consumer<ApplicationInfo> callback) {
final List<ApplicationInfo> list = injectInstalledApplications(userId);
for (int i = list.size() - 1; i >= 0; i--) {
final ApplicationInfo ai = list.get(i);
if ((ai.flags & ApplicationInfo.FLAG_INSTALLED) != 0) {
callback.accept(ai);
}
}
}
private boolean isApplicationFlagSet(String packageName, int userId, int flags) { private boolean isApplicationFlagSet(String packageName, int userId, int flags) {
final ApplicationInfo ai = injectApplicationInfo(packageName, userId); final ApplicationInfo ai = injectApplicationInfo(packageName, userId);
return (ai != null) && ((ai.flags & flags) == flags); return (ai != null) && ((ai.flags & flags) == flags);
@@ -2253,15 +2395,10 @@ public class ShortcutService extends IShortcutService.Stub {
return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_INSTALLED); return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_INSTALLED);
} }
/** @Nullable
* @return the version code of the package, or -1 if the app is not installed. XmlResourceParser injectXmlMetaData(ActivityInfo activityInfo, String key) {
*/ // TODO Doesn't seem like ACROSS_USER is needed, but double check.
int getApplicationVersionCode(String packageName, int userId) { return activityInfo.loadXmlMetaData(mContext.getPackageManager(), key);
final ApplicationInfo ai = injectApplicationInfo(packageName, userId);
if ((ai == null) || ((ai.flags & ApplicationInfo.FLAG_INSTALLED) == 0)) {
return -1;
}
return ai.versionCode;
} }
// === Backup & restore === // === Backup & restore ===
@@ -2402,8 +2539,10 @@ public class ShortcutService extends IShortcutService.Stub {
dumpStatLS(pw, p, Stats.GET_PACKAGE_INFO, "getPackageInfo()"); dumpStatLS(pw, p, Stats.GET_PACKAGE_INFO, "getPackageInfo()");
dumpStatLS(pw, p, Stats.GET_PACKAGE_INFO_WITH_SIG, "getPackageInfo(SIG)"); dumpStatLS(pw, p, Stats.GET_PACKAGE_INFO_WITH_SIG, "getPackageInfo(SIG)");
dumpStatLS(pw, p, Stats.GET_APPLICATION_INFO, "getApplicationInfo"); dumpStatLS(pw, p, Stats.GET_APPLICATION_INFO, "getApplicationInfo");
dumpStatLS(pw, p, Stats.CLEANUP_DANGLING_BITMAPS, "cleanupDanglingBitmaps"); dumpStatLS(pw, p, Stats.CLEANUP_DANGLING_BITMAPS, "cleanupDanglingBitmaps");
dumpStatLS(pw, p, Stats.GET_ACTIVITIES_WITH_METADATA, "getActivities+metadata");
dumpStatLS(pw, p, Stats.GET_INSTALLED_APPLICATIONS, "getInstalledApplications");
dumpStatLS(pw, p, Stats.CHECK_PACKAGE_CHANGES, "checkPackageChanges");
} }
for (int i = 0; i < mUsers.size(); i++) { for (int i = 0; i < mUsers.size(); i++) {
@@ -2783,12 +2922,19 @@ public class ShortcutService extends IShortcutService.Stub {
} }
@VisibleForTesting @VisibleForTesting
ShortcutInfo getPackageShortcutForTest(String packageName, String shortcutId, int userId) { ShortcutPackage getPackageShortcutForTest(String packageName, int userId) {
synchronized (mLock) { synchronized (mLock) {
final ShortcutUser user = mUsers.get(userId); final ShortcutUser user = mUsers.get(userId);
if (user == null) return null; if (user == null) return null;
final ShortcutPackage pkg = user.getAllPackagesForTest().get(packageName); return user.getAllPackagesForTest().get(packageName);
}
}
@VisibleForTesting
ShortcutInfo getPackageShortcutForTest(String packageName, String shortcutId, int userId) {
synchronized (mLock) {
final ShortcutPackage pkg = getPackageShortcutForTest(packageName, userId);
if (pkg == null) return null; if (pkg == null) return null;
return pkg.findShortcutById(shortcutId); return pkg.findShortcutById(shortcutId);

View File

@@ -24,6 +24,7 @@ import android.util.ArrayMap;
import android.util.Slog; import android.util.Slog;
import android.util.SparseArray; import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions; import com.android.internal.util.Preconditions;
@@ -40,6 +41,8 @@ import java.util.function.Consumer;
/** /**
* User information used by {@link ShortcutService}. * User information used by {@link ShortcutService}.
*
* All methods should be guarded by {@code #mService.mLock}.
*/ */
class ShortcutUser { class ShortcutUser {
private static final String TAG = ShortcutService.TAG; private static final String TAG = ShortcutService.TAG;
@@ -230,19 +233,16 @@ class ShortcutUser {
} }
} }
/** public void handlePackageAddedOrUpdated(@NonNull String packageName) {
* Called when a package is updated. final boolean isNewApp = !mPackages.containsKey(packageName);
*/
public void handlePackageUpdated(@NonNull String packageName, final ShortcutPackage shortcutPackage = getPackageShortcuts(packageName);
int newVersionCode) {
if (!mPackages.containsKey(packageName)) { if (!shortcutPackage.handlePackageAddedOrUpdated(isNewApp)) {
return; if (isNewApp) {
mPackages.remove(packageName);
}
} }
final ShortcutPackage p = getPackageShortcutsIfExists(packageName);
if (p == null) {
return; // No need to instantiate ShortcutPackage.
}
p.handlePackageUpdated(newVersionCode);
} }
public void attemptToRestoreIfNeededAndSave(ShortcutService s, @NonNull String packageName, public void attemptToRestoreIfNeededAndSave(ShortcutService s, @NonNull String packageName,

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2016 The Android Open Source Project
Licensed 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.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="shortcut_title1"></string>
<string name="shortcut_text1"></string>
<string name="shortcut_disabled_message1"></string>
<string name="shortcut_title2"></string>
<string name="shortcut_text2"></string>
<string name="shortcut_disabled_message2"></string>
</resources>

View File

@@ -0,0 +1,2 @@
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
</shortcuts>

View File

@@ -0,0 +1,14 @@
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
<shortcut
android:shortcutId="ms1"
android:enabled="true"
android:shortcutRank="1"
android:shortcutIcon="@drawable/icon1"
android:shortcutTitle="@string/shortcut_title1"
android:shortcutText="@string/shortcut_text1"
android:shortcutDisabledMessage="@string/shortcut_disabled_message1"
android:shortcutCategories="android.shortcut.conversation:android.shortcut.media"
android:shortcutIntentAction="action1"
android:shortcutIntentData="data1"
/>
</shortcuts>

View File

@@ -0,0 +1,11 @@
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
<shortcut
android:shortcutId="ms1"
android:enabled="false"
android:shortcutRank="1"
android:shortcutIcon="@drawable/icon2"
android:shortcutTitle="@string/shortcut_title2"
android:shortcutText="@string/shortcut_text2"
android:shortcutDisabledMessage="@string/shortcut_disabled_message2"
/>
</shortcuts>

View File

@@ -0,0 +1,26 @@
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
<shortcut
android:shortcutId="ms1"
android:enabled="true"
android:shortcutRank="1"
android:shortcutIcon="@drawable/icon1"
android:shortcutTitle="@string/shortcut_title1"
android:shortcutText="@string/shortcut_text1"
android:shortcutDisabledMessage="@string/shortcut_disabled_message1"
android:shortcutCategories="android.shortcut.conversation:android.shortcut.media"
android:shortcutIntentAction="action1"
android:shortcutIntentData="http://a.b.c/"
/>
<shortcut
android:shortcutId="ms2"
android:enabled="true"
android:shortcutRank="2"
android:shortcutIcon="@drawable/icon2"
android:shortcutTitle="@string/shortcut_title2"
android:shortcutText="@string/shortcut_text2"
android:shortcutDisabledMessage="@string/shortcut_disabled_message2"
android:shortcutCategories="android.shortcut.conversation"
android:shortcutIntentAction="action2"
android:shortcutIntentData="http://a.b.c/2"
/>
</shortcuts>

View File

@@ -0,0 +1,12 @@
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
<shortcut
android:shortcutId="ms1"
android:shortcutTitle="@string/shortcut_title1"
android:shortcutIntentAction="action1"
/>
<shortcut
android:shortcutId="ms1"
android:shortcutTitle="@string/shortcut_title2"
android:shortcutIntentAction="action2"
/>
</shortcuts>

View File

@@ -0,0 +1,40 @@
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
<shortcut
android:shortcutId="ms1"
android:enabled="true"
android:shortcutRank="1"
android:shortcutIcon="@drawable/icon1"
android:shortcutTitle="@string/shortcut_title1"
android:shortcutText="@string/shortcut_text1"
android:shortcutDisabledMessage="@string/shortcut_disabled_message1"
android:shortcutCategories="android.shortcut.conversation:android.shortcut.media"
android:shortcutIntentAction="action1"
android:shortcutIntentData="http://a.b.c/1"
/>
<shortcut
android:shortcutId="ms2"
android:enabled="true"
android:shortcutRank="2"
android:shortcutIcon="@drawable/icon2"
android:shortcutTitle="@string/shortcut_title2"
android:shortcutText="@string/shortcut_text2"
android:shortcutDisabledMessage="@string/shortcut_disabled_message2"
android:shortcutCategories="android.shortcut.conversation"
android:shortcutIntentAction="action2"
/>
<shortcut
android:shortcutId="ms3"
android:shortcutTitle="@string/shortcut_title1"
android:shortcutIntentAction="android.intent.action.VIEW"
/>
<shortcut
android:shortcutId="ms4"
android:shortcutTitle="@string/shortcut_title1"
android:shortcutIntentAction="android.intent.action.VIEW"
/>
<shortcut
android:shortcutId="ms5"
android:shortcutTitle="@string/shortcut_title1"
android:shortcutIntentAction="android.intent.action.VIEW"
/>
</shortcuts>

View File

@@ -0,0 +1,11 @@
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
<shortcut
android:shortcutTitle="@string/shortcut_title1"
android:shortcutIntentAction="android.intent.action.VIEW"
/>
<shortcut
android:shortcutId="x1"
android:shortcutTitle="@string/shortcut_title1"
android:shortcutIntentAction="android.intent.action.VIEW"
/>
</shortcuts>

View File

@@ -0,0 +1,11 @@
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
<shortcut
android:shortcutId="manifest-shortcut-3"
android:shortcutIntentAction="android.intent.action.VIEW"
/>
<shortcut
android:shortcutId="x2"
android:shortcutTitle="@string/shortcut_title1"
android:shortcutIntentAction="android.intent.action.VIEW"
/>
</shortcuts>

View File

@@ -0,0 +1,11 @@
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
<shortcut
android:shortcutId="manifest-shortcut-3"
android:shortcutTitle="@string/shortcut_title1"
/>
<shortcut
android:shortcutId="x3"
android:shortcutTitle="@string/shortcut_title1"
android:shortcutIntentAction="android.intent.action.VIEW"
/>
</shortcuts>

View File

@@ -246,11 +246,22 @@ public class ShortcutManagerTestUtils {
reset(o); reset(o);
} }
} }
public static <T> List<T> assertEmpty(List<T> list) {
assertEquals(0, list.size());
return list;
}
public static void assertExpectException(Class<? extends Throwable> expectedExceptionType, public static void assertExpectException(Class<? extends Throwable> expectedExceptionType,
String expectedExceptionMessageRegex, Runnable r) { String expectedExceptionMessageRegex, Runnable r) {
assertExpectException("", expectedExceptionType, expectedExceptionMessageRegex, r); assertExpectException("", expectedExceptionType, expectedExceptionMessageRegex, r);
} }
public static void assertCannotUpdateImmutable(Runnable r) {
assertExpectException(
IllegalArgumentException.class, "may not be manipulated via APIs", r);
}
public static void assertDynamicShortcutCountExceeded(Runnable r) { public static void assertDynamicShortcutCountExceeded(Runnable r) {
assertExpectException(IllegalArgumentException.class, assertExpectException(IllegalArgumentException.class,
"Max number of dynamic shortcuts exceeded", r); "Max number of dynamic shortcuts exceeded", r);
@@ -383,6 +394,46 @@ public class ShortcutManagerTestUtils {
return actualShortcuts; return actualShortcuts;
} }
public static List<ShortcutInfo> assertAllManifest(
List<ShortcutInfo> actualShortcuts) {
for (ShortcutInfo s : actualShortcuts) {
assertTrue("ID " + s.getId(), s.isManifestShortcut());
}
return actualShortcuts;
}
public static List<ShortcutInfo> assertAllNotManifest(
List<ShortcutInfo> actualShortcuts) {
for (ShortcutInfo s : actualShortcuts) {
assertFalse("ID " + s.getId(), s.isManifestShortcut());
}
return actualShortcuts;
}
public static List<ShortcutInfo> assertAllDisabled(
List<ShortcutInfo> actualShortcuts) {
for (ShortcutInfo s : actualShortcuts) {
assertTrue("ID " + s.getId(), !s.isEnabled());
}
return actualShortcuts;
}
public static List<ShortcutInfo> assertAllEnabled(
List<ShortcutInfo> actualShortcuts) {
for (ShortcutInfo s : actualShortcuts) {
assertTrue("ID " + s.getId(), s.isEnabled());
}
return actualShortcuts;
}
public static List<ShortcutInfo> assertAllImmutable(
List<ShortcutInfo> actualShortcuts) {
for (ShortcutInfo s : actualShortcuts) {
assertTrue("ID " + s.getId(), s.isImmutable());
}
return actualShortcuts;
}
public static List<ShortcutInfo> assertAllStringsResolved( public static List<ShortcutInfo> assertAllStringsResolved(
List<ShortcutInfo> actualShortcuts) { List<ShortcutInfo> actualShortcuts) {
for (ShortcutInfo s : actualShortcuts) { for (ShortcutInfo s : actualShortcuts) {
@@ -398,6 +449,7 @@ public class ShortcutManagerTestUtils {
public static void assertPinnedOnly(ShortcutInfo si) { public static void assertPinnedOnly(ShortcutInfo si) {
assertFalse(si.isDynamic()); assertFalse(si.isDynamic());
assertFalse(si.isManifestShortcut());
assertTrue(si.isPinned()); assertTrue(si.isPinned());
} }