Merge "ShortcutManager: Support manifest shortcuts" into nyc-mr1-dev
This commit is contained in:
@@ -1106,6 +1106,15 @@ package android {
|
||||
field public static final int shareInterpolator = 16843195; // 0x10101bb
|
||||
field public static final int sharedUserId = 16842763; // 0x101000b
|
||||
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 showAsAction = 16843481; // 0x10102d9
|
||||
field public static final int showDefault = 16843258; // 0x10101fa
|
||||
@@ -9534,13 +9543,14 @@ package android.content.pm {
|
||||
|
||||
public static class LauncherApps.ShortcutQuery {
|
||||
ctor public LauncherApps.ShortcutQuery();
|
||||
method public void setActivity(android.content.ComponentName);
|
||||
method public void setChangedSince(long);
|
||||
method public void setPackage(java.lang.String);
|
||||
method public void setQueryFlags(int);
|
||||
method public void setShortcutIds(java.util.List<java.lang.String>);
|
||||
method public android.content.pm.LauncherApps.ShortcutQuery setActivity(android.content.ComponentName);
|
||||
method public android.content.pm.LauncherApps.ShortcutQuery setChangedSince(long);
|
||||
method public android.content.pm.LauncherApps.ShortcutQuery setPackage(java.lang.String);
|
||||
method public android.content.pm.LauncherApps.ShortcutQuery setQueryFlags(int);
|
||||
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_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
|
||||
}
|
||||
|
||||
@@ -10044,29 +10054,30 @@ package android.content.pm {
|
||||
|
||||
public final class ShortcutInfo implements android.os.Parcelable {
|
||||
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.lang.String getDisabledMessage();
|
||||
method public java.lang.CharSequence getDisabledMessage();
|
||||
method public int getDisabledMessageResId();
|
||||
method public android.os.PersistableBundle getExtras();
|
||||
method public int getIconResourceId();
|
||||
method public java.lang.String getId();
|
||||
method public android.content.Intent getIntent();
|
||||
method public long getLastChangedTimestamp();
|
||||
method public java.lang.String getPackageName();
|
||||
method public java.lang.String getPackage();
|
||||
method public int getRank();
|
||||
method public java.lang.String getText();
|
||||
method public java.lang.CharSequence getText();
|
||||
method public int getTextResId();
|
||||
method public java.lang.String getTitle();
|
||||
method public java.lang.CharSequence getTitle();
|
||||
method public int getTitleResId();
|
||||
method public android.os.UserHandle getUserHandle();
|
||||
method public boolean hasIconFile();
|
||||
method public boolean hasIconResource();
|
||||
method public boolean hasKeyFieldsOnly();
|
||||
method public boolean hasStringResourcesResolved();
|
||||
method public boolean isDisabled();
|
||||
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 void writeToParcel(android.os.Parcel, int);
|
||||
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 int FLAG_DISABLED = 64; // 0x40
|
||||
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_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_MANIFEST = 32; // 0x20
|
||||
field public static final int FLAG_PINNED = 2; // 0x2
|
||||
field public static final int FLAG_STRINGS_RESOLVED = 128; // 0x80
|
||||
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 {
|
||||
ctor public ShortcutInfo.Builder(android.content.Context);
|
||||
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 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 setIcon(android.graphics.drawable.Icon);
|
||||
method public android.content.pm.ShortcutInfo.Builder setId(java.lang.String);
|
||||
method public android.content.pm.ShortcutInfo.Builder setIntent(android.content.Intent);
|
||||
method public android.content.pm.ShortcutInfo.Builder setRank(int);
|
||||
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 setTitleResId(int);
|
||||
}
|
||||
|
||||
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>, int);
|
||||
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 int getIconMaxDimensions();
|
||||
method public int getMaxDynamicShortcutCount();
|
||||
method public int getIconMaxHeight();
|
||||
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 long getRateLimitResetTime();
|
||||
method public int getRemainingCallCount();
|
||||
|
||||
@@ -1216,6 +1216,15 @@ package android {
|
||||
field public static final int shareInterpolator = 16843195; // 0x10101bb
|
||||
field public static final int sharedUserId = 16842763; // 0x101000b
|
||||
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 showAsAction = 16843481; // 0x10102d9
|
||||
field public static final int showDefault = 16843258; // 0x10101fa
|
||||
@@ -9887,13 +9896,14 @@ package android.content.pm {
|
||||
|
||||
public static class LauncherApps.ShortcutQuery {
|
||||
ctor public LauncherApps.ShortcutQuery();
|
||||
method public void setActivity(android.content.ComponentName);
|
||||
method public void setChangedSince(long);
|
||||
method public void setPackage(java.lang.String);
|
||||
method public void setQueryFlags(int);
|
||||
method public void setShortcutIds(java.util.List<java.lang.String>);
|
||||
method public android.content.pm.LauncherApps.ShortcutQuery setActivity(android.content.ComponentName);
|
||||
method public android.content.pm.LauncherApps.ShortcutQuery setChangedSince(long);
|
||||
method public android.content.pm.LauncherApps.ShortcutQuery setPackage(java.lang.String);
|
||||
method public android.content.pm.LauncherApps.ShortcutQuery setQueryFlags(int);
|
||||
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_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
|
||||
}
|
||||
|
||||
@@ -10467,29 +10477,30 @@ package android.content.pm {
|
||||
|
||||
public final class ShortcutInfo implements android.os.Parcelable {
|
||||
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.lang.String getDisabledMessage();
|
||||
method public java.lang.CharSequence getDisabledMessage();
|
||||
method public int getDisabledMessageResId();
|
||||
method public android.os.PersistableBundle getExtras();
|
||||
method public int getIconResourceId();
|
||||
method public java.lang.String getId();
|
||||
method public android.content.Intent getIntent();
|
||||
method public long getLastChangedTimestamp();
|
||||
method public java.lang.String getPackageName();
|
||||
method public java.lang.String getPackage();
|
||||
method public int getRank();
|
||||
method public java.lang.String getText();
|
||||
method public java.lang.CharSequence getText();
|
||||
method public int getTextResId();
|
||||
method public java.lang.String getTitle();
|
||||
method public java.lang.CharSequence getTitle();
|
||||
method public int getTitleResId();
|
||||
method public android.os.UserHandle getUserHandle();
|
||||
method public boolean hasIconFile();
|
||||
method public boolean hasIconResource();
|
||||
method public boolean hasKeyFieldsOnly();
|
||||
method public boolean hasStringResourcesResolved();
|
||||
method public boolean isDisabled();
|
||||
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 void writeToParcel(android.os.Parcel, int);
|
||||
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 int FLAG_DISABLED = 64; // 0x40
|
||||
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_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_MANIFEST = 32; // 0x20
|
||||
field public static final int FLAG_PINNED = 2; // 0x2
|
||||
field public static final int FLAG_STRINGS_RESOLVED = 128; // 0x80
|
||||
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 {
|
||||
ctor public ShortcutInfo.Builder(android.content.Context);
|
||||
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 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 setIcon(android.graphics.drawable.Icon);
|
||||
method public android.content.pm.ShortcutInfo.Builder setId(java.lang.String);
|
||||
method public android.content.pm.ShortcutInfo.Builder setIntent(android.content.Intent);
|
||||
method public android.content.pm.ShortcutInfo.Builder setRank(int);
|
||||
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 setTitleResId(int);
|
||||
}
|
||||
|
||||
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>, int);
|
||||
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 int getIconMaxDimensions();
|
||||
method public int getMaxDynamicShortcutCount();
|
||||
method public int getIconMaxHeight();
|
||||
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 long getRateLimitResetTime();
|
||||
method public int getRemainingCallCount();
|
||||
|
||||
@@ -1106,6 +1106,15 @@ package android {
|
||||
field public static final int shareInterpolator = 16843195; // 0x10101bb
|
||||
field public static final int sharedUserId = 16842763; // 0x101000b
|
||||
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 showAsAction = 16843481; // 0x10102d9
|
||||
field public static final int showDefault = 16843258; // 0x10101fa
|
||||
@@ -9546,13 +9555,14 @@ package android.content.pm {
|
||||
|
||||
public static class LauncherApps.ShortcutQuery {
|
||||
ctor public LauncherApps.ShortcutQuery();
|
||||
method public void setActivity(android.content.ComponentName);
|
||||
method public void setChangedSince(long);
|
||||
method public void setPackage(java.lang.String);
|
||||
method public void setQueryFlags(int);
|
||||
method public void setShortcutIds(java.util.List<java.lang.String>);
|
||||
method public android.content.pm.LauncherApps.ShortcutQuery setActivity(android.content.ComponentName);
|
||||
method public android.content.pm.LauncherApps.ShortcutQuery setChangedSince(long);
|
||||
method public android.content.pm.LauncherApps.ShortcutQuery setPackage(java.lang.String);
|
||||
method public android.content.pm.LauncherApps.ShortcutQuery setQueryFlags(int);
|
||||
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_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
|
||||
}
|
||||
|
||||
@@ -10057,29 +10067,30 @@ package android.content.pm {
|
||||
|
||||
public final class ShortcutInfo implements android.os.Parcelable {
|
||||
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.lang.String getDisabledMessage();
|
||||
method public java.lang.CharSequence getDisabledMessage();
|
||||
method public int getDisabledMessageResId();
|
||||
method public android.os.PersistableBundle getExtras();
|
||||
method public int getIconResourceId();
|
||||
method public java.lang.String getId();
|
||||
method public android.content.Intent getIntent();
|
||||
method public long getLastChangedTimestamp();
|
||||
method public java.lang.String getPackageName();
|
||||
method public java.lang.String getPackage();
|
||||
method public int getRank();
|
||||
method public java.lang.String getText();
|
||||
method public java.lang.CharSequence getText();
|
||||
method public int getTextResId();
|
||||
method public java.lang.String getTitle();
|
||||
method public java.lang.CharSequence getTitle();
|
||||
method public int getTitleResId();
|
||||
method public android.os.UserHandle getUserHandle();
|
||||
method public boolean hasIconFile();
|
||||
method public boolean hasIconResource();
|
||||
method public boolean hasKeyFieldsOnly();
|
||||
method public boolean hasStringResourcesResolved();
|
||||
method public boolean isDisabled();
|
||||
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 void writeToParcel(android.os.Parcel, int);
|
||||
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 int FLAG_DISABLED = 64; // 0x40
|
||||
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_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_MANIFEST = 32; // 0x20
|
||||
field public static final int FLAG_PINNED = 2; // 0x2
|
||||
field public static final int FLAG_STRINGS_RESOLVED = 128; // 0x80
|
||||
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 {
|
||||
ctor public ShortcutInfo.Builder(android.content.Context);
|
||||
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 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 setIcon(android.graphics.drawable.Icon);
|
||||
method public android.content.pm.ShortcutInfo.Builder setId(java.lang.String);
|
||||
method public android.content.pm.ShortcutInfo.Builder setIntent(android.content.Intent);
|
||||
method public android.content.pm.ShortcutInfo.Builder setRank(int);
|
||||
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 setTitleResId(int);
|
||||
}
|
||||
|
||||
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>, int);
|
||||
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 int getIconMaxDimensions();
|
||||
method public int getMaxDynamicShortcutCount();
|
||||
method public int getIconMaxHeight();
|
||||
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 long getRateLimitResetTime();
|
||||
method public int getRemainingCallCount();
|
||||
|
||||
@@ -28,6 +28,8 @@ interface IShortcutService {
|
||||
|
||||
ParceledListSlice getDynamicShortcuts(String packageName, int userId);
|
||||
|
||||
ParceledListSlice getManifestShortcuts(String packageName, int userId);
|
||||
|
||||
boolean addDynamicShortcuts(String packageName, in ParceledListSlice shortcutInfoList,
|
||||
int userId);
|
||||
|
||||
@@ -42,6 +44,8 @@ interface IShortcutService {
|
||||
void disableShortcuts(String packageName, in List shortcutIds, String disabledMessage,
|
||||
int disabledMessageResId, int userId);
|
||||
|
||||
void enableShortcuts(String packageName, in List shortcutIds, int userId);
|
||||
|
||||
int getMaxDynamicShortcutCount(String packageName, int userId);
|
||||
|
||||
int getRemainingCallCount(String packageName, int userId);
|
||||
|
||||
@@ -187,6 +187,11 @@ public class LauncherApps {
|
||||
*/
|
||||
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
|
||||
* fields are available.
|
||||
@@ -198,6 +203,7 @@ public class LauncherApps {
|
||||
value = {
|
||||
FLAG_GET_DYNAMIC,
|
||||
FLAG_GET_PINNED,
|
||||
FLAG_GET_MANIFEST,
|
||||
FLAG_GET_KEY_FIELDS_ONLY,
|
||||
})
|
||||
@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,
|
||||
* which is a milliseconds since the Epoch.
|
||||
*/
|
||||
public void setChangedSince(long changedSince) {
|
||||
public ShortcutQuery setChangedSince(long changedSince) {
|
||||
mChangedSince = changedSince;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* If non-null, returns only shortcuts from the package.
|
||||
*/
|
||||
public void setPackage(@Nullable String packageName) {
|
||||
public ShortcutQuery setPackage(@Nullable String packageName) {
|
||||
mPackage = packageName;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* If non-null, return only the specified shortcuts by ID. When setting this field,
|
||||
* 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;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* If non-null, returns only shortcuts associated with the activity, which are
|
||||
* {@link ShortcutInfo}s that have null {@link ShortcutInfo#getActivityComponent()}, or
|
||||
* {@link ShortcutInfo#getActivityComponent()} equals to {@code activity}.
|
||||
* If non-null, returns only shortcuts associated with the activity; i.e.
|
||||
* {@link ShortcutInfo}s whose {@link ShortcutInfo#getActivity()} are equal
|
||||
* to {@code activity}.
|
||||
*/
|
||||
public void setActivity(@Nullable ComponentName activity) {
|
||||
public ShortcutQuery setActivity(@Nullable ComponentName activity) {
|
||||
mActivity = activity;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set query options.
|
||||
*/
|
||||
public void setQueryFlags(@QueryFlags int queryFlags) {
|
||||
public ShortcutQuery setQueryFlags(@QueryFlags int queryFlags) {
|
||||
mQueryFlags = queryFlags;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -525,7 +536,7 @@ public class LauncherApps {
|
||||
*/
|
||||
public ParcelFileDescriptor getShortcutIconFd(
|
||||
@NonNull ShortcutInfo shortcut) {
|
||||
return getShortcutIconFd(shortcut.getPackageName(), shortcut.getId(),
|
||||
return getShortcutIconFd(shortcut.getPackage(), shortcut.getId(),
|
||||
shortcut.getUserId());
|
||||
}
|
||||
|
||||
@@ -555,14 +566,14 @@ public class LauncherApps {
|
||||
}
|
||||
}
|
||||
|
||||
/** TODO Javadoc */
|
||||
/** TODO Javadoc (not implemented yet) */
|
||||
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) {
|
||||
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,
|
||||
@Nullable Rect sourceBounds, @Nullable Bundle startActivityOptions) {
|
||||
return startShortcut(shortcut.getPackageName(), shortcut.getId(),
|
||||
return startShortcut(shortcut.getPackage(), shortcut.getId(),
|
||||
sourceBounds, startActivityOptions,
|
||||
shortcut.getUserId());
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ public final class ShortcutInfo implements Parcelable {
|
||||
public static final int FLAG_KEY_FIELDS_ONLY = 1 << 4;
|
||||
|
||||
/* @hide */
|
||||
public static final int FLAG_FROM_MANIFEST = 1 << 5;
|
||||
public static final int FLAG_MANIFEST = 1 << 5;
|
||||
|
||||
/* @hide */
|
||||
public static final int FLAG_DISABLED = 1 << 6;
|
||||
@@ -78,6 +78,9 @@ public final class ShortcutInfo implements Parcelable {
|
||||
/* @hide */
|
||||
public static final int FLAG_STRINGS_RESOLVED = 1 << 7;
|
||||
|
||||
/* @hide */
|
||||
public static final int FLAG_IMMUTABLE = 1 << 8;
|
||||
|
||||
/** @hide */
|
||||
@IntDef(flag = true,
|
||||
value = {
|
||||
@@ -86,9 +89,10 @@ public final class ShortcutInfo implements Parcelable {
|
||||
FLAG_HAS_ICON_RES,
|
||||
FLAG_HAS_ICON_FILE,
|
||||
FLAG_KEY_FIELDS_ONLY,
|
||||
FLAG_FROM_MANIFEST,
|
||||
FLAG_MANIFEST,
|
||||
FLAG_DISABLED,
|
||||
FLAG_STRINGS_RESOLVED,
|
||||
FLAG_IMMUTABLE,
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface ShortcutFlags {}
|
||||
@@ -133,7 +137,7 @@ public final class ShortcutInfo implements Parcelable {
|
||||
private final String mPackageName;
|
||||
|
||||
@Nullable
|
||||
private ComponentName mActivityComponent;
|
||||
private ComponentName mActivity;
|
||||
|
||||
@Nullable
|
||||
private Icon mIcon;
|
||||
@@ -141,17 +145,17 @@ public final class ShortcutInfo implements Parcelable {
|
||||
private int mTitleResId;
|
||||
|
||||
@Nullable
|
||||
private String mTitle;
|
||||
private CharSequence mTitle;
|
||||
|
||||
private int mTextResId;
|
||||
|
||||
@Nullable
|
||||
private String mText;
|
||||
private CharSequence mText;
|
||||
|
||||
private int mDisabledMessageResId;
|
||||
|
||||
@Nullable
|
||||
private String mDisabledMessage;
|
||||
private CharSequence mDisabledMessage;
|
||||
|
||||
@Nullable
|
||||
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
|
||||
// information.
|
||||
mPackageName = b.mContext.getPackageName();
|
||||
mActivityComponent = b.mActivityComponent;
|
||||
mActivity = b.mActivity;
|
||||
mIcon = b.mIcon;
|
||||
mTitle = b.mTitle;
|
||||
mTitleResId = b.mTitleResId;
|
||||
@@ -229,7 +233,7 @@ public final class ShortcutInfo implements Parcelable {
|
||||
*/
|
||||
public void enforceMandatoryFields() {
|
||||
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) {
|
||||
throw new IllegalArgumentException("Shortcut title must be provided");
|
||||
}
|
||||
@@ -250,7 +254,7 @@ public final class ShortcutInfo implements Parcelable {
|
||||
mIconResourceId = source.mIconResourceId;
|
||||
|
||||
if ((cloneFlags & CLONE_REMOVE_NON_KEY_INFO) == 0) {
|
||||
mActivityComponent = source.mActivityComponent;
|
||||
mActivity = source.mActivity;
|
||||
|
||||
if ((cloneFlags & CLONE_REMOVE_ICON) == 0) {
|
||||
mIcon = source.mIcon;
|
||||
@@ -308,6 +312,17 @@ public final class ShortcutInfo implements Parcelable {
|
||||
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
|
||||
* will be overwritten. The timestamp will be updated.
|
||||
@@ -316,16 +331,15 @@ public final class ShortcutInfo implements Parcelable {
|
||||
* - mBitmapPath will not change
|
||||
* - Current time will be set to timestamp
|
||||
*
|
||||
* @throws IllegalStateException if source is not compatible.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public void copyNonNullFieldsFrom(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");
|
||||
ensureUpdatableWith(source);
|
||||
|
||||
if (source.mActivityComponent != null) {
|
||||
mActivityComponent = source.mActivityComponent;
|
||||
if (source.mActivity != null) {
|
||||
mActivity = source.mActivity;
|
||||
}
|
||||
|
||||
if (source.mIcon != null) {
|
||||
@@ -401,21 +415,21 @@ public final class ShortcutInfo implements Parcelable {
|
||||
|
||||
private String mId;
|
||||
|
||||
private ComponentName mActivityComponent;
|
||||
private ComponentName mActivity;
|
||||
|
||||
private Icon mIcon;
|
||||
|
||||
private int mTitleResId;
|
||||
|
||||
private String mTitle;
|
||||
private CharSequence mTitle;
|
||||
|
||||
private int mTextResId;
|
||||
|
||||
private String mText;
|
||||
private CharSequence mText;
|
||||
|
||||
private int mDisabledMessageResId;
|
||||
|
||||
private String mDisabledMessage;
|
||||
private CharSequence mDisabledMessage;
|
||||
|
||||
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.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder setActivityComponent(@NonNull ComponentName activityComponent) {
|
||||
mActivityComponent = Preconditions.checkNotNull(activityComponent, "activityComponent");
|
||||
public Builder setActivity(@NonNull ComponentName activity) {
|
||||
mActivity = Preconditions.checkNotNull(activity, "activity");
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -476,7 +490,7 @@ public final class ShortcutInfo implements Parcelable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** TODO Javadoc */
|
||||
/** @hide */
|
||||
public Builder setTitleResId(int titleResId) {
|
||||
Preconditions.checkState(mTitle == null, "title already set");
|
||||
mTitleResId = titleResId;
|
||||
@@ -496,7 +510,7 @@ public final class ShortcutInfo implements Parcelable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** TODO Javadoc */
|
||||
/** @hide */
|
||||
public Builder setTextResId(int textResId) {
|
||||
Preconditions.checkState(mText == null, "text already set");
|
||||
mTextResId = textResId;
|
||||
@@ -516,7 +530,7 @@ public final class ShortcutInfo implements Parcelable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** TODO Javadoc */
|
||||
/** @hide */
|
||||
public Builder setDisabledMessageResId(int disabledMessageResId) {
|
||||
Preconditions.checkState(mDisabledMessage == null, "disabledMessage already set");
|
||||
mDisabledMessageResId = disabledMessageResId;
|
||||
@@ -595,7 +609,7 @@ public final class ShortcutInfo implements Parcelable {
|
||||
* Return the package name of the creator application.
|
||||
*/
|
||||
@NonNull
|
||||
public String getPackageName() {
|
||||
public String getPackage() {
|
||||
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
|
||||
* a hint to the launcher app that on which launcher icon this shortcut should be shown.
|
||||
*
|
||||
* @see Builder#setActivityComponent
|
||||
* @see Builder#setActivity
|
||||
*/
|
||||
@Nullable
|
||||
public ComponentName getActivityComponent() {
|
||||
return mActivityComponent;
|
||||
public ComponentName getActivity() {
|
||||
return mActivity;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -634,7 +648,7 @@ public final class ShortcutInfo implements Parcelable {
|
||||
* {@link #hasKeyFieldsOnly()} is true.
|
||||
*/
|
||||
@Nullable
|
||||
public String getTitle() {
|
||||
public CharSequence getTitle() {
|
||||
return mTitle;
|
||||
}
|
||||
|
||||
@@ -647,7 +661,7 @@ public final class ShortcutInfo implements Parcelable {
|
||||
* Return the shortcut text.
|
||||
*/
|
||||
@Nullable
|
||||
public String getText() {
|
||||
public CharSequence getText() {
|
||||
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.
|
||||
*/
|
||||
@Nullable
|
||||
public String getDisabledMessage() {
|
||||
public CharSequence getDisabledMessage() {
|
||||
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
|
||||
* immutable.
|
||||
* Return whether a shortcut is published via AndroidManifest.xml or not. If {@code true},
|
||||
* 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() {
|
||||
return hasFlags(FLAG_FROM_MANIFEST);
|
||||
public boolean isManifestShortcut() {
|
||||
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);
|
||||
}
|
||||
|
||||
/** @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.
|
||||
*
|
||||
@@ -833,7 +896,7 @@ public final class ShortcutInfo implements Parcelable {
|
||||
* following fields are available.
|
||||
* <ul>
|
||||
* <li>{@link #getId()}
|
||||
* <li>{@link #getPackageName()}
|
||||
* <li>{@link #getPackage()}
|
||||
* <li>{@link #getLastChangedTimestamp()}
|
||||
* <li>{@link #isDynamic()}
|
||||
* <li>{@link #isPinned()}
|
||||
@@ -888,19 +951,31 @@ public final class ShortcutInfo implements Parcelable {
|
||||
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) {
|
||||
final ClassLoader cl = getClass().getClassLoader();
|
||||
|
||||
mUserId = source.readInt();
|
||||
mId = source.readString();
|
||||
mPackageName = source.readString();
|
||||
mActivityComponent = source.readParcelable(cl);
|
||||
mActivity = source.readParcelable(cl);
|
||||
mIcon = source.readParcelable(cl);
|
||||
mTitle = source.readString();
|
||||
mTitle = source.readCharSequence();
|
||||
mTitleResId = source.readInt();
|
||||
mText = source.readString();
|
||||
mText = source.readCharSequence();
|
||||
mTextResId = source.readInt();
|
||||
mDisabledMessage = source.readString();
|
||||
mDisabledMessage = source.readCharSequence();
|
||||
mDisabledMessageResId = source.readInt();
|
||||
mIntent = source.readParcelable(cl);
|
||||
mIntentPersistableExtras = source.readParcelable(cl);
|
||||
@@ -927,13 +1002,13 @@ public final class ShortcutInfo implements Parcelable {
|
||||
dest.writeInt(mUserId);
|
||||
dest.writeString(mId);
|
||||
dest.writeString(mPackageName);
|
||||
dest.writeParcelable(mActivityComponent, flags);
|
||||
dest.writeParcelable(mActivity, flags);
|
||||
dest.writeParcelable(mIcon, flags);
|
||||
dest.writeString(mTitle);
|
||||
dest.writeCharSequence(mTitle);
|
||||
dest.writeInt(mTitleResId);
|
||||
dest.writeString(mText);
|
||||
dest.writeCharSequence(mText);
|
||||
dest.writeInt(mTextResId);
|
||||
dest.writeString(mDisabledMessage);
|
||||
dest.writeCharSequence(mDisabledMessage);
|
||||
dest.writeInt(mDisabledMessageResId);
|
||||
|
||||
dest.writeParcelable(mIntent, flags);
|
||||
@@ -991,18 +1066,43 @@ public final class ShortcutInfo implements Parcelable {
|
||||
sb.append("id=");
|
||||
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(mPackageName);
|
||||
|
||||
if (isDynamic()) {
|
||||
sb.append(", dynamic");
|
||||
}
|
||||
if (isPinned()) {
|
||||
sb.append(", pinned");
|
||||
}
|
||||
|
||||
sb.append(", activity=");
|
||||
sb.append(mActivityComponent);
|
||||
sb.append(mActivity);
|
||||
|
||||
sb.append(", title=");
|
||||
sb.append(secure ? "***" : mTitle);
|
||||
@@ -1040,35 +1140,6 @@ public final class ShortcutInfo implements Parcelable {
|
||||
sb.append(", extras=");
|
||||
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) {
|
||||
|
||||
sb.append(", iconRes=");
|
||||
@@ -1084,16 +1155,16 @@ public final class ShortcutInfo implements Parcelable {
|
||||
|
||||
/** @hide */
|
||||
public ShortcutInfo(
|
||||
@UserIdInt int userId, String id, String packageName, ComponentName activityComponent,
|
||||
Icon icon, String title, int titleResId, String text, int textResId,
|
||||
String disabledMessage, int disabledMessageResId, Set<String> categories, Intent intent,
|
||||
PersistableBundle intentPersistableExtras,
|
||||
@UserIdInt int userId, String id, String packageName, ComponentName activity,
|
||||
Icon icon, CharSequence title, int titleResId, CharSequence text, int textResId,
|
||||
CharSequence disabledMessage, int disabledMessageResId, Set<String> categories,
|
||||
Intent intent, PersistableBundle intentPersistableExtras,
|
||||
int rank, PersistableBundle extras, long lastChangedTimestamp,
|
||||
int flags, int iconResId, String bitmapPath) {
|
||||
mUserId = userId;
|
||||
mId = id;
|
||||
mPackageName = packageName;
|
||||
mActivityComponent = activityComponent;
|
||||
mActivity = activity;
|
||||
mIcon = icon;
|
||||
mTitle = title;
|
||||
mTitleResId = titleResId;
|
||||
|
||||
@@ -28,7 +28,7 @@ import java.util.List;
|
||||
|
||||
// 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.
|
||||
*
|
||||
@@ -36,8 +36,8 @@ import java.util.List;
|
||||
*
|
||||
* An application can publish shortcuts with {@link #setDynamicShortcuts(List)} and
|
||||
* {@link #addDynamicShortcuts(List)}. There can be at most
|
||||
* {@link #getMaxDynamicShortcutCount()} number of dynamic shortcuts at a time from the same
|
||||
* application.
|
||||
* {@link #getMaxShortcutCountForActivity()} number of dynamic shortcuts at a time from the
|
||||
* same application.
|
||||
* A dynamic shortcut can be deleted with {@link #removeDynamicShortcuts(List)}, and apps
|
||||
* 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
|
||||
* published by an application at a time.
|
||||
* 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.
|
||||
*
|
||||
* <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.
|
||||
*
|
||||
* @throws IllegalArgumentException if {@code shortcutInfoList} contains more than
|
||||
* {@link #getMaxDynamicShortcutCount()} shortcuts.
|
||||
* {@link #getMaxShortcutCountForActivity()} shortcuts.
|
||||
*/
|
||||
public boolean setDynamicShortcuts(@NonNull List<ShortcutInfo> shortcutInfoList) {
|
||||
try {
|
||||
@@ -145,7 +146,7 @@ public class ShortcutManager {
|
||||
|
||||
/**
|
||||
* 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
|
||||
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
|
||||
* 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 {
|
||||
return mService.getMaxDynamicShortcutCount(mContext.getPackageName(), injectMyUserId());
|
||||
} 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 {
|
||||
// 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());
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
|
||||
@@ -8237,4 +8237,17 @@ i
|
||||
color. -->
|
||||
<attr name="colorBackground" />
|
||||
</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>
|
||||
|
||||
@@ -2728,4 +2728,18 @@
|
||||
<public type="id" name="icon_frame" id="0x0102003e" />
|
||||
<public type="id" name="list_container" id="0x0102003f" />
|
||||
<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>
|
||||
|
||||
@@ -36,6 +36,8 @@ import java.util.List;
|
||||
|
||||
/**
|
||||
* Launcher information used by {@link ShortcutService}.
|
||||
*
|
||||
* All methods should be guarded by {@code #mShortcutUser.mService.mLock}.
|
||||
*/
|
||||
class ShortcutLauncher extends ShortcutPackageItem {
|
||||
private static final String TAG = ShortcutService.TAG;
|
||||
@@ -125,7 +127,8 @@ class ShortcutLauncher extends ShortcutPackageItem {
|
||||
if (si == null) {
|
||||
continue;
|
||||
}
|
||||
if (si.isDynamic() || (prevSet != null && prevSet.contains(id))) {
|
||||
if (si.isDynamic() || si.isManifestShortcut()
|
||||
|| (prevSet != null && prevSet.contains(id))) {
|
||||
newSet.add(id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import android.annotation.Nullable;
|
||||
import android.annotation.UserIdInt;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.os.PersistableBundle;
|
||||
import android.text.format.Formatter;
|
||||
@@ -28,6 +29,7 @@ import android.util.ArraySet;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.util.Preconditions;
|
||||
import com.android.internal.util.XmlUtils;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
@@ -44,6 +46,11 @@ import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
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 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_LAST_RESET = "last-reset";
|
||||
private static final String ATTR_ID = "id";
|
||||
@@ -83,11 +89,6 @@ class ShortcutPackage extends ShortcutPackageItem {
|
||||
*/
|
||||
final private ArrayMap<String, ShortcutInfo> mShortcuts = new ArrayMap<>();
|
||||
|
||||
/**
|
||||
* # of dynamic shortcuts.
|
||||
*/
|
||||
private int mDynamicShortcutCount = 0;
|
||||
|
||||
/**
|
||||
* # 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
|
||||
* 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
|
||||
// handlePackageUpdated().
|
||||
if (getPackageInfo().getVersionCode() < 0) {
|
||||
final ShortcutService s = mShortcutUser.mService;
|
||||
|
||||
final int versionCode = s.getApplicationVersionCode(getPackageName(), getOwnerUserId());
|
||||
if (ShortcutService.DEBUG) {
|
||||
Slog.d(TAG, String.format("Package %s version = %d", getPackageName(),
|
||||
versionCode));
|
||||
}
|
||||
if (versionCode >= 0) {
|
||||
getPackageInfo().setVersionCode(versionCode);
|
||||
final PackageInfo pi = s.getPackageInfo(getPackageName(), getOwnerUserId());
|
||||
if (pi != null) {
|
||||
if (ShortcutService.DEBUG) {
|
||||
Slog.d(TAG, String.format("Package %s version = %d", getPackageName(),
|
||||
pi.versionCode));
|
||||
}
|
||||
getPackageInfo().updateVersionInfo(pi);
|
||||
s.scheduleSaveUser(getOwnerUserId());
|
||||
}
|
||||
}
|
||||
@@ -168,17 +169,42 @@ class ShortcutPackage extends ShortcutPackageItem {
|
||||
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);
|
||||
if (shortcut != null) {
|
||||
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;
|
||||
}
|
||||
|
||||
void addShortcut(@NonNull ShortcutInfo newShortcut) {
|
||||
deleteShortcut(newShortcut.getId());
|
||||
private void addShortcutInner(@NonNull ShortcutInfo newShortcut) {
|
||||
deleteShortcutInner(newShortcut.getId());
|
||||
mShortcutUser.mService.saveIconAndFixUpShortcut(getPackageUserId(), newShortcut);
|
||||
mShortcuts.put(newShortcut.getId(), newShortcut);
|
||||
}
|
||||
@@ -188,39 +214,41 @@ class ShortcutPackage extends ShortcutPackageItem {
|
||||
*
|
||||
* 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);
|
||||
|
||||
final ShortcutInfo oldShortcut = mShortcuts.get(newShortcut.getId());
|
||||
|
||||
final boolean wasPinned;
|
||||
final int newDynamicCount;
|
||||
|
||||
if (oldShortcut == null) {
|
||||
wasPinned = false;
|
||||
newDynamicCount = mDynamicShortcutCount + 1; // adding a dynamic shortcut.
|
||||
} else {
|
||||
// It's an update case.
|
||||
// Make sure the target is updatable. (i.e. should be mutable.)
|
||||
oldShortcut.ensureUpdatableWith(newShortcut);
|
||||
|
||||
wasPinned = oldShortcut.isPinned();
|
||||
if (oldShortcut.isDynamic()) {
|
||||
newDynamicCount = mDynamicShortcutCount; // not adding a dynamic shortcut.
|
||||
} else {
|
||||
newDynamicCount = mDynamicShortcutCount + 1; // adding a dynamic shortcut.
|
||||
if (!oldShortcut.isEnabled()) {
|
||||
newShortcut.addFlags(ShortcutInfo.FLAG_DISABLED);
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure there's still room.
|
||||
mShortcutUser.mService.enforceMaxDynamicShortcuts(newDynamicCount);
|
||||
// TODO Check max dynamic count.
|
||||
// mShortcutUser.mService.enforceMaxDynamicShortcuts(newDynamicCount);
|
||||
|
||||
// Okay, make it dynamic and add.
|
||||
if (wasPinned) {
|
||||
newShortcut.addFlags(ShortcutInfo.FLAG_PINNED);
|
||||
}
|
||||
|
||||
addShortcut(newShortcut);
|
||||
mDynamicShortcutCount = newDynamicCount;
|
||||
addShortcutInner(newShortcut);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -232,7 +260,7 @@ class ShortcutPackage extends ShortcutPackageItem {
|
||||
for (int i = mShortcuts.size() - 1; i >= 0; i--) {
|
||||
final ShortcutInfo si = mShortcuts.valueAt(i);
|
||||
|
||||
if (si.isPinned() || si.isDynamic()) continue;
|
||||
if (si.isAlive()) continue;
|
||||
|
||||
if (removeList == null) {
|
||||
removeList = new ArrayList<>();
|
||||
@@ -241,7 +269,7 @@ class ShortcutPackage extends ShortcutPackageItem {
|
||||
}
|
||||
if (removeList != null) {
|
||||
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.
|
||||
*/
|
||||
public void deleteAllDynamicShortcuts() {
|
||||
boolean changed = false;
|
||||
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.
|
||||
*/
|
||||
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);
|
||||
|
||||
if (oldShortcut == null) {
|
||||
return;
|
||||
if (oldShortcut == null || !oldShortcut.isEnabled()) {
|
||||
return null; // Doesn't exist or already disabled.
|
||||
}
|
||||
if (oldShortcut.isDynamic()) {
|
||||
mDynamicShortcutCount--;
|
||||
if (!overrideImmutable) {
|
||||
ensureNotImmutable(oldShortcut);
|
||||
}
|
||||
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 {
|
||||
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++) {
|
||||
final ShortcutInfo si = mShortcuts.valueAt(i);
|
||||
|
||||
// If it's called by non-launcher (i.e. publisher, always include -> true.
|
||||
// Otherwise, only include non-dynamic pinned one, if the calling launcher has pinned
|
||||
// it.
|
||||
// Need to adjust PINNED flag depending on the caller.
|
||||
// Basically if the caller is a launcher (callingLauncher != null) and the launcher
|
||||
// isn't pinning it, then we need to clear PINNED for this caller.
|
||||
final boolean isPinnedByCaller = (callingLauncher == null)
|
||||
|| ((pinnedByCallerSet != null) && pinnedByCallerSet.contains(si.getId()));
|
||||
if (!si.isDynamic()) {
|
||||
if (!si.isPinned()) {
|
||||
s.wtf("Shortcut not pinned: package " + getPackageName()
|
||||
+ ", user=" + getPackageUserId() + ", id=" + si.getId());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (si.isFloating()) {
|
||||
if (!isPinnedByCaller) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
final ShortcutInfo clone = si.clone(cloneFlag);
|
||||
|
||||
// Fix up isPinned for the caller. Note we need to do it before the "test" callback,
|
||||
// since it may check isPinned.
|
||||
if (!isPinnedByCaller) {
|
||||
@@ -486,32 +550,81 @@ class ShortcutPackage extends ShortcutPackageItem {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the package is updated. If there are shortcuts with resource icons, update
|
||||
* their timestamps.
|
||||
* Called when the package is updated or added.
|
||||
*
|
||||
* 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) {
|
||||
if (getPackageInfo().getVersionCode() >= newVersionCode) {
|
||||
// Version hasn't changed; nothing to do.
|
||||
return;
|
||||
}
|
||||
if (ShortcutService.DEBUG) {
|
||||
Slog.d(TAG, String.format("Package %s updated, version %d -> %d", getPackageName(),
|
||||
getPackageInfo().getVersionCode(), newVersionCode));
|
||||
public boolean handlePackageAddedOrUpdated(boolean isNewApp) {
|
||||
final PackageInfo pi = mShortcutUser.mService.getPackageInfo(
|
||||
getPackageName(), getPackageUserId());
|
||||
if (pi == null) {
|
||||
return false; // Shouldn't happen.
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
boolean changed = false;
|
||||
for (int i = mShortcuts.size() - 1; i >= 0; i--) {
|
||||
final ShortcutInfo si = mShortcuts.valueAt(i);
|
||||
|
||||
if (si.hasAnyResources()) {
|
||||
changed = true;
|
||||
si.setTimestamp(s.injectCurrentTimeMillis());
|
||||
// For existing shortcuts, update timestamps if they have any resources.
|
||||
if (!isNewApp) {
|
||||
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) {
|
||||
// This will send a notification to the launcher, and also save .
|
||||
s.packageShortcutsChanged(getPackageName(), getPackageUserId());
|
||||
@@ -519,6 +632,97 @@ class ShortcutPackage extends ShortcutPackageItem {
|
||||
// Still save the version code.
|
||||
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) {
|
||||
@@ -597,7 +801,6 @@ class ShortcutPackage extends ShortcutPackageItem {
|
||||
out.startTag(null, TAG_ROOT);
|
||||
|
||||
ShortcutService.writeAttr(out, ATTR_NAME, getPackageName());
|
||||
ShortcutService.writeAttr(out, ATTR_DYNAMIC_COUNT, mDynamicShortcutCount);
|
||||
ShortcutService.writeAttr(out, ATTR_CALL_COUNT, mApiCallCount);
|
||||
ShortcutService.writeAttr(out, ATTR_LAST_RESET, mLastResetTime);
|
||||
getPackageInfo().saveToXml(out);
|
||||
@@ -619,7 +822,7 @@ class ShortcutPackage extends ShortcutPackageItem {
|
||||
out.startTag(null, TAG_SHORTCUT);
|
||||
ShortcutService.writeAttr(out, ATTR_ID, si.getId());
|
||||
// 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.
|
||||
ShortcutService.writeAttr(out, ATTR_TITLE, si.getTitle());
|
||||
ShortcutService.writeAttr(out, ATTR_TITLE_RES_ID, si.getTitleResId());
|
||||
@@ -670,8 +873,6 @@ class ShortcutPackage extends ShortcutPackageItem {
|
||||
final ShortcutPackage ret = new ShortcutPackage(shortcutUser,
|
||||
shortcutUser.getUserId(), packageName);
|
||||
|
||||
ret.mDynamicShortcutCount =
|
||||
ShortcutService.parseIntAttribute(parser, ATTR_DYNAMIC_COUNT);
|
||||
ret.mApiCallCount =
|
||||
ShortcutService.parseIntAttribute(parser, ATTR_CALL_COUNT);
|
||||
ret.mLastResetTime =
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.android.server.pm;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.UserIdInt;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.util.Slog;
|
||||
@@ -34,12 +35,15 @@ import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Package information used by {@link android.content.pm.ShortcutManager} for backup / restore.
|
||||
*
|
||||
* All methods should be guarded by {@code ShortcutService.mLock}.
|
||||
*/
|
||||
class ShortcutPackageInfo {
|
||||
private static final String TAG = ShortcutService.TAG;
|
||||
|
||||
static final String TAG_ROOT = "package-info";
|
||||
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 TAG_SIGNATURE = "signature";
|
||||
@@ -53,16 +57,20 @@ class ShortcutPackageInfo {
|
||||
*/
|
||||
private boolean mIsShadow;
|
||||
private int mVersionCode = VERSION_UNKNOWN;
|
||||
private long mLastUpdateTime;
|
||||
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;
|
||||
mLastUpdateTime = lastUpdateTime;
|
||||
mIsShadow = isShadow;
|
||||
mSigHashes = sigHashes;
|
||||
}
|
||||
|
||||
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() {
|
||||
@@ -77,8 +85,15 @@ class ShortcutPackageInfo {
|
||||
return mVersionCode;
|
||||
}
|
||||
|
||||
public void setVersionCode(int versionCode) {
|
||||
mVersionCode = versionCode;
|
||||
public long getLastUpdateTime() {
|
||||
return mLastUpdateTime;
|
||||
}
|
||||
|
||||
public void updateVersionInfo(@NonNull PackageInfo pi) {
|
||||
if (pi != null) {
|
||||
mVersionCode = pi.versionCode;
|
||||
mLastUpdateTime = pi.lastUpdateTime;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasSignatures() {
|
||||
@@ -111,7 +126,7 @@ class ShortcutPackageInfo {
|
||||
Slog.e(TAG, "Can't get signatures: package=" + packageName);
|
||||
return null;
|
||||
}
|
||||
final ShortcutPackageInfo ret = new ShortcutPackageInfo(pi.versionCode,
|
||||
final ShortcutPackageInfo ret = new ShortcutPackageInfo(pi.versionCode, pi.lastUpdateTime,
|
||||
BackupUtils.hashSignatureArray(pi.signatures), /* shadow=*/ false);
|
||||
|
||||
return ret;
|
||||
@@ -131,6 +146,7 @@ class ShortcutPackageInfo {
|
||||
return;
|
||||
}
|
||||
mVersionCode = pi.versionCode;
|
||||
mLastUpdateTime = pi.lastUpdateTime;
|
||||
mSigHashes = BackupUtils.hashSignatureArray(pi.signatures);
|
||||
}
|
||||
|
||||
@@ -139,6 +155,7 @@ class ShortcutPackageInfo {
|
||||
out.startTag(null, TAG_ROOT);
|
||||
|
||||
ShortcutService.writeAttr(out, ATTR_VERSION, mVersionCode);
|
||||
ShortcutService.writeAttr(out, ATTR_LAST_UPDATE_TIME, mLastUpdateTime);
|
||||
ShortcutService.writeAttr(out, ATTR_SHADOW, mIsShadow);
|
||||
|
||||
for (int i = 0; i < mSigHashes.size(); i++) {
|
||||
@@ -154,6 +171,9 @@ class ShortcutPackageInfo {
|
||||
|
||||
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.
|
||||
final boolean shadow =
|
||||
fromBackup || ShortcutService.parseBooleanAttribute(parser, ATTR_SHADOW);
|
||||
@@ -185,6 +205,7 @@ class ShortcutPackageInfo {
|
||||
|
||||
// Successfully loaded; replace the feilds.
|
||||
mVersionCode = versionCode;
|
||||
mLastUpdateTime = lastUpdateTime;
|
||||
mIsShadow = shadow;
|
||||
mSigHashes = hashes;
|
||||
}
|
||||
@@ -205,6 +226,11 @@ class ShortcutPackageInfo {
|
||||
pw.print(mVersionCode);
|
||||
pw.println();
|
||||
|
||||
pw.print(prefix);
|
||||
pw.print(" Last package update time: ");
|
||||
pw.print(mLastUpdateTime);
|
||||
pw.println();
|
||||
|
||||
for (int i = 0; i < mSigHashes.size(); i++) {
|
||||
pw.print(prefix);
|
||||
pw.print(" ");
|
||||
|
||||
@@ -26,6 +26,9 @@ import org.xmlpull.v1.XmlSerializer;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* All methods should be guarded by {@code #mShortcutUser.mService.mLock}.
|
||||
*/
|
||||
abstract class ShortcutPackageItem {
|
||||
private static final String TAG = ShortcutService.TAG;
|
||||
|
||||
|
||||
236
services/core/java/com/android/server/pm/ShortcutParser.java
Normal file
236
services/core/java/com/android/server/pm/ShortcutParser.java
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ import android.app.IUidObserver;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.IPackageManager;
|
||||
import android.content.pm.IShortcutService;
|
||||
@@ -39,6 +40,7 @@ import android.content.pm.ResolveInfo;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.content.pm.ShortcutServiceInternal;
|
||||
import android.content.pm.ShortcutServiceInternal.ShortcutChangeListener;
|
||||
import android.content.res.XmlResourceParser;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Bitmap.CompressFormat;
|
||||
import android.graphics.Canvas;
|
||||
@@ -103,6 +105,7 @@ import java.io.PrintWriter;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
@@ -111,16 +114,21 @@ import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* TODO:
|
||||
* - Manifest shortcuts.
|
||||
* - Implement # of dynamic shortcuts cap.
|
||||
*
|
||||
* - Implement disableShortcuts().
|
||||
* - HandleUnlockUser needs to be async. Wait on it in onCleanupUser.
|
||||
*
|
||||
* - Implement reportShortcutUsed().
|
||||
*
|
||||
* - validateForXml() should be removed.
|
||||
*
|
||||
* - Ranks should be recalculated after each update.
|
||||
*
|
||||
* - 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.
|
||||
*
|
||||
@@ -128,11 +136,13 @@ import java.util.function.Predicate;
|
||||
* internal bitmap handling.
|
||||
*
|
||||
* - Add more call stats.
|
||||
*
|
||||
* - Rename getMaxDynamicShortcutCount and mMaxDynamicShortcuts
|
||||
*/
|
||||
public class ShortcutService extends IShortcutService.Stub {
|
||||
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_PROCSTATE = false; // STOPSHIP if true
|
||||
|
||||
@@ -301,8 +311,11 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
int GET_APPLICATION_INFO = 3;
|
||||
int LAUNCHER_PERMISSION_CHECK = 4;
|
||||
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();
|
||||
@@ -444,6 +457,9 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
|
||||
/** lifecycle event */
|
||||
void handleUnlockUser(int userId) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "handleUnlockUser: user=" + userId);
|
||||
}
|
||||
synchronized (mLock) {
|
||||
// Preload
|
||||
getUserShortcutsLocked(userId);
|
||||
@@ -642,10 +658,10 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
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;
|
||||
|
||||
out.attribute(null, name, value);
|
||||
out.attribute(null, name, value.toString());
|
||||
}
|
||||
|
||||
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)
|
||||
throws IOException {
|
||||
final File packagePath = new File(getUserBitmapFilePath(userId),
|
||||
shortcut.getPackageName());
|
||||
shortcut.getPackage());
|
||||
if (!packagePath.isDirectory()) {
|
||||
packagePath.mkdirs();
|
||||
if (!packagePath.isDirectory()) {
|
||||
@@ -1129,7 +1145,6 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
}
|
||||
|
||||
Bitmap bitmap;
|
||||
Bitmap bitmapToRecycle = null;
|
||||
try {
|
||||
switch (icon.getType()) {
|
||||
case Icon.TYPE_RESOURCE: {
|
||||
@@ -1181,9 +1196,6 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
if (bitmapToRecycle != null) {
|
||||
bitmapToRecycle.recycle();
|
||||
}
|
||||
// Once saved, we won't use the original icon information, so null it out.
|
||||
shortcut.clearIcon();
|
||||
}
|
||||
@@ -1196,7 +1208,7 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
// so override in unit tests.
|
||||
// TODO CTS this case.
|
||||
void injectValidateIconResPackage(ShortcutInfo shortcut, Icon icon) {
|
||||
if (!shortcut.getPackageName().equals(icon.getResPackage())) {
|
||||
if (!shortcut.getPackage().equals(icon.getResPackage())) {
|
||||
throw new IllegalArgumentException(
|
||||
"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) {
|
||||
Preconditions.checkNotNull(shortcut, "Null shortcut detected");
|
||||
if (shortcut.getActivityComponent() != null) {
|
||||
if (shortcut.getActivity() != null) {
|
||||
Preconditions.checkState(
|
||||
shortcut.getPackageName().equals(
|
||||
shortcut.getActivityComponent().getPackageName()),
|
||||
shortcut.getPackage().equals(shortcut.getActivity().getPackageName()),
|
||||
"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)) {
|
||||
return;
|
||||
}
|
||||
@@ -1427,6 +1438,8 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
synchronized (mLock) {
|
||||
final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
|
||||
|
||||
ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
|
||||
|
||||
// Throttling.
|
||||
if (!ps.tryApiCall()) {
|
||||
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.
|
||||
for (int i = 0; i < size; i++) {
|
||||
final ShortcutInfo newShortcut = newShortcuts.get(i);
|
||||
ps.addDynamicShortcut(newShortcut);
|
||||
ps.addOrUpdateDynamicShortcut(newShortcut);
|
||||
}
|
||||
}
|
||||
packageShortcutsChanged(packageName, userId);
|
||||
@@ -1462,6 +1475,8 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
synchronized (mLock) {
|
||||
final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
|
||||
|
||||
ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
|
||||
|
||||
// Throttling.
|
||||
if (!ps.tryApiCall()) {
|
||||
return false;
|
||||
@@ -1473,11 +1488,22 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
|
||||
final ShortcutInfo target = ps.findShortcutById(source.getId());
|
||||
if (target != null) {
|
||||
if (target.isEnabled() != source.isEnabled()) {
|
||||
Slog.w(TAG,
|
||||
"ShortcutInfo.enabled cannot be changed with updateShortcuts()");
|
||||
}
|
||||
|
||||
final boolean replacingIcon = (source.getIcon() != null);
|
||||
if (replacingIcon) {
|
||||
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);
|
||||
|
||||
if (replacingIcon) {
|
||||
@@ -1502,6 +1528,8 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
synchronized (mLock) {
|
||||
final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
|
||||
|
||||
ps.ensureImmutableShortcutsNotIncluded(newShortcuts);
|
||||
|
||||
// Throttling.
|
||||
if (!ps.tryApiCall()) {
|
||||
return false;
|
||||
@@ -1513,7 +1541,7 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
fixUpIncomingShortcutInfo(newShortcut, /* forUpdate= */ false);
|
||||
|
||||
// Add it.
|
||||
ps.addDynamicShortcut(newShortcut);
|
||||
ps.addOrUpdateDynamicShortcut(newShortcut);
|
||||
}
|
||||
}
|
||||
packageShortcutsChanged(packageName, userId);
|
||||
@@ -1528,7 +1556,32 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
Preconditions.checkNotNull(shortcutIds, "shortcutIds must be provided");
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -1540,8 +1593,12 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
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--) {
|
||||
getPackageShortcutsLocked(packageName, userId).deleteDynamicWithId(
|
||||
ps.deleteDynamicWithId(
|
||||
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
|
||||
public ParceledListSlice<ShortcutInfo> getPinnedShortcuts(String packageName,
|
||||
@UserIdInt int userId) {
|
||||
@@ -1883,18 +1951,24 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
return false;
|
||||
}
|
||||
if (componentName != null) {
|
||||
if (si.getActivityComponent() != null
|
||||
&& !si.getActivityComponent().equals(componentName)) {
|
||||
if (si.getActivity() != null
|
||||
&& !si.getActivity().equals(componentName)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
final boolean matchDynamic =
|
||||
((queryFlags & ShortcutQuery.FLAG_GET_DYNAMIC) != 0)
|
||||
&& si.isDynamic();
|
||||
final boolean matchPinned =
|
||||
((queryFlags & ShortcutQuery.FLAG_GET_PINNED) != 0)
|
||||
&& si.isPinned();
|
||||
return matchDynamic || matchPinned;
|
||||
if (((queryFlags & ShortcutQuery.FLAG_GET_DYNAMIC) != 0)
|
||||
&& si.isDynamic()) {
|
||||
return true;
|
||||
}
|
||||
if (((queryFlags & ShortcutQuery.FLAG_GET_PINNED) != 0)
|
||||
&& si.isPinned()) {
|
||||
return true;
|
||||
}
|
||||
if (((queryFlags & ShortcutQuery.FLAG_GET_MANIFEST) != 0)
|
||||
&& si.isManifestShortcut()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}, cloneFlag, callingPackage, launcherUserId);
|
||||
}
|
||||
|
||||
@@ -1967,7 +2041,7 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
final ShortcutInfo si = getShortcutInfoLocked(
|
||||
launcherUserId, callingPackage, packageName, shortcutId, userId);
|
||||
// "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 si.getIntent();
|
||||
@@ -2114,31 +2188,37 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "checkPackageChanges() ownerUserId=" + ownerUserId);
|
||||
}
|
||||
final ArrayList<PackageWithUser> gonePackages = new ArrayList<>();
|
||||
|
||||
synchronized (mLock) {
|
||||
final ShortcutUser user = getUserShortcutsLocked(ownerUserId);
|
||||
final long start = injectElapsedRealtime();
|
||||
try {
|
||||
final ArrayList<PackageWithUser> gonePackages = new ArrayList<>();
|
||||
|
||||
user.forAllPackageItems(spi -> {
|
||||
if (spi.getPackageInfo().isShadow()) {
|
||||
return; // Don't delete shadow information.
|
||||
}
|
||||
final int versionCode = getApplicationVersionCode(
|
||||
spi.getPackageName(), spi.getPackageUserId());
|
||||
if (versionCode >= 0) {
|
||||
// Package still installed, see if it's updated.
|
||||
getUserShortcutsLocked(ownerUserId).handlePackageUpdated(
|
||||
spi.getPackageName(), versionCode);
|
||||
} else {
|
||||
gonePackages.add(PackageWithUser.of(spi));
|
||||
}
|
||||
});
|
||||
if (gonePackages.size() > 0) {
|
||||
for (int i = gonePackages.size() - 1; i >= 0; i--) {
|
||||
final PackageWithUser pu = gonePackages.get(i);
|
||||
cleanUpPackageLocked(pu.packageName, ownerUserId, pu.userId);
|
||||
synchronized (mLock) {
|
||||
final ShortcutUser user = getUserShortcutsLocked(ownerUserId);
|
||||
|
||||
// Find packages that have been uninstalled.
|
||||
user.forAllPackageItems(spi -> {
|
||||
if (spi.getPackageInfo().isShadow()) {
|
||||
return; // Don't delete shadow information.
|
||||
}
|
||||
if (!isPackageInstalled(spi.getPackageName(), spi.getPackageUserId())) {
|
||||
gonePackages.add(PackageWithUser.of(spi));
|
||||
}
|
||||
});
|
||||
if (gonePackages.size() > 0) {
|
||||
for (int i = gonePackages.size() - 1; i >= 0; i--) {
|
||||
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));
|
||||
}
|
||||
synchronized (mLock) {
|
||||
forEachLoadedUserLocked(user ->
|
||||
user.attemptToRestoreIfNeededAndSave(this, packageName, userId));
|
||||
final ShortcutUser user = getUserShortcutsLocked(userId);
|
||||
user.attemptToRestoreIfNeededAndSave(this, packageName, userId);
|
||||
user.handlePackageAddedOrUpdated(packageName);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2158,14 +2239,12 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
packageName, userId));
|
||||
}
|
||||
synchronized (mLock) {
|
||||
forEachLoadedUserLocked(user ->
|
||||
user.attemptToRestoreIfNeededAndSave(this, packageName, userId));
|
||||
final ShortcutUser user = getUserShortcutsLocked(userId);
|
||||
user.attemptToRestoreIfNeededAndSave(this, packageName, userId);
|
||||
|
||||
final int versionCode = getApplicationVersionCode(packageName, userId);
|
||||
if (versionCode < 0) {
|
||||
return; // shouldn't happen
|
||||
if (isPackageInstalled(packageName, userId)) {
|
||||
user.handlePackageAddedOrUpdated(packageName);
|
||||
}
|
||||
getUserShortcutsLocked(userId).handlePackageUpdated(packageName, versionCode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2187,10 +2266,16 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
|
||||
// === PackageManager interaction ===
|
||||
|
||||
@Nullable
|
||||
PackageInfo getPackageInfoWithSignatures(String packageName, @UserIdInt int userId) {
|
||||
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) {
|
||||
final long token = injectClearCallingIdentity();
|
||||
try {
|
||||
@@ -2205,6 +2290,7 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@VisibleForTesting
|
||||
PackageInfo injectPackageInfo(String packageName, @UserIdInt int userId,
|
||||
boolean getSignatures) {
|
||||
@@ -2227,6 +2313,7 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@VisibleForTesting
|
||||
ApplicationInfo injectApplicationInfo(String packageName, @UserIdInt int userId) {
|
||||
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) {
|
||||
final ApplicationInfo ai = injectApplicationInfo(packageName, userId);
|
||||
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 the version code of the package, or -1 if the app is not installed.
|
||||
*/
|
||||
int getApplicationVersionCode(String packageName, int userId) {
|
||||
final ApplicationInfo ai = injectApplicationInfo(packageName, userId);
|
||||
if ((ai == null) || ((ai.flags & ApplicationInfo.FLAG_INSTALLED) == 0)) {
|
||||
return -1;
|
||||
}
|
||||
return ai.versionCode;
|
||||
@Nullable
|
||||
XmlResourceParser injectXmlMetaData(ActivityInfo activityInfo, String key) {
|
||||
// TODO Doesn't seem like ACROSS_USER is needed, but double check.
|
||||
return activityInfo.loadXmlMetaData(mContext.getPackageManager(), key);
|
||||
}
|
||||
|
||||
// === 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_WITH_SIG, "getPackageInfo(SIG)");
|
||||
dumpStatLS(pw, p, Stats.GET_APPLICATION_INFO, "getApplicationInfo");
|
||||
|
||||
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++) {
|
||||
@@ -2783,12 +2922,19 @@ public class ShortcutService extends IShortcutService.Stub {
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
ShortcutInfo getPackageShortcutForTest(String packageName, String shortcutId, int userId) {
|
||||
ShortcutPackage getPackageShortcutForTest(String packageName, int userId) {
|
||||
synchronized (mLock) {
|
||||
final ShortcutUser user = mUsers.get(userId);
|
||||
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;
|
||||
|
||||
return pkg.findShortcutById(shortcutId);
|
||||
|
||||
@@ -24,6 +24,7 @@ import android.util.ArrayMap;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.util.Preconditions;
|
||||
|
||||
@@ -40,6 +41,8 @@ import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* User information used by {@link ShortcutService}.
|
||||
*
|
||||
* All methods should be guarded by {@code #mService.mLock}.
|
||||
*/
|
||||
class ShortcutUser {
|
||||
private static final String TAG = ShortcutService.TAG;
|
||||
@@ -230,19 +233,16 @@ class ShortcutUser {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a package is updated.
|
||||
*/
|
||||
public void handlePackageUpdated(@NonNull String packageName,
|
||||
int newVersionCode) {
|
||||
if (!mPackages.containsKey(packageName)) {
|
||||
return;
|
||||
public void handlePackageAddedOrUpdated(@NonNull String packageName) {
|
||||
final boolean isNewApp = !mPackages.containsKey(packageName);
|
||||
|
||||
final ShortcutPackage shortcutPackage = getPackageShortcuts(packageName);
|
||||
|
||||
if (!shortcutPackage.handlePackageAddedOrUpdated(isNewApp)) {
|
||||
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,
|
||||
|
||||
24
services/tests/servicestests/res/values/strings.xml
Normal file
24
services/tests/servicestests/res/values/strings.xml
Normal 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>
|
||||
2
services/tests/servicestests/res/xml/shortcut_0.xml
Normal file
2
services/tests/servicestests/res/xml/shortcut_0.xml
Normal file
@@ -0,0 +1,2 @@
|
||||
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android" >
|
||||
</shortcuts>
|
||||
14
services/tests/servicestests/res/xml/shortcut_1.xml
Normal file
14
services/tests/servicestests/res/xml/shortcut_1.xml
Normal 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>
|
||||
11
services/tests/servicestests/res/xml/shortcut_1_disable.xml
Normal file
11
services/tests/servicestests/res/xml/shortcut_1_disable.xml
Normal 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>
|
||||
26
services/tests/servicestests/res/xml/shortcut_2.xml
Normal file
26
services/tests/servicestests/res/xml/shortcut_2.xml
Normal 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>
|
||||
@@ -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>
|
||||
40
services/tests/servicestests/res/xml/shortcut_5.xml
Normal file
40
services/tests/servicestests/res/xml/shortcut_5.xml
Normal 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>
|
||||
11
services/tests/servicestests/res/xml/shortcut_error_1.xml
Normal file
11
services/tests/servicestests/res/xml/shortcut_error_1.xml
Normal 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>
|
||||
11
services/tests/servicestests/res/xml/shortcut_error_2.xml
Normal file
11
services/tests/servicestests/res/xml/shortcut_error_2.xml
Normal 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>
|
||||
11
services/tests/servicestests/res/xml/shortcut_error_3.xml
Normal file
11
services/tests/servicestests/res/xml/shortcut_error_3.xml
Normal 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>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -246,11 +246,22 @@ public class ShortcutManagerTestUtils {
|
||||
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,
|
||||
String expectedExceptionMessageRegex, Runnable 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) {
|
||||
assertExpectException(IllegalArgumentException.class,
|
||||
"Max number of dynamic shortcuts exceeded", r);
|
||||
@@ -383,6 +394,46 @@ public class ShortcutManagerTestUtils {
|
||||
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(
|
||||
List<ShortcutInfo> actualShortcuts) {
|
||||
for (ShortcutInfo s : actualShortcuts) {
|
||||
@@ -398,6 +449,7 @@ public class ShortcutManagerTestUtils {
|
||||
|
||||
public static void assertPinnedOnly(ShortcutInfo si) {
|
||||
assertFalse(si.isDynamic());
|
||||
assertFalse(si.isManifestShortcut());
|
||||
assertTrue(si.isPinned());
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user