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

am: 05e0a9f3cf

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

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

View File

@@ -1106,6 +1106,15 @@ package android {
field public static final int shareInterpolator = 16843195; // 0x10101bb
field public static final int 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();

View File

@@ -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();

View File

@@ -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();

View File

@@ -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);

View File

@@ -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());
}

View File

@@ -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;

View File

@@ -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();

View File

@@ -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>

View File

@@ -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>

View File

@@ -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);
}
}

View File

@@ -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 =

View File

@@ -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(" ");

View File

@@ -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;

View File

@@ -0,0 +1,236 @@
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.pm;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
import android.content.pm.ShortcutInfo;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
import android.net.Uri;
import android.text.TextUtils;
import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.Slog;
import android.util.Xml;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
public class ShortcutParser {
private static final String TAG = ShortcutService.TAG;
private static final boolean DEBUG = ShortcutService.DEBUG || false; // DO NOT SUBMIT WITH TRUE
@VisibleForTesting
static final String METADATA_KEY = "android.pm.Shortcuts";
private static final String TAG_SHORTCUTS = "shortcuts";
private static final String TAG_SHORTCUT = "shortcut";
@Nullable
public static List<ShortcutInfo> parseShortcuts(ShortcutService service,
String packageName, @UserIdInt int userId) throws IOException, XmlPullParserException {
final PackageInfo pi = service.injectGetActivitiesWithMetadata(packageName, userId);
List<ShortcutInfo> result = null;
if (pi != null && pi.activities != null) {
for (ActivityInfo activityInfo : pi.activities) {
result = parseShortcutsOneFile(service, activityInfo, packageName, userId, result);
}
}
return result;
}
private static List<ShortcutInfo> parseShortcutsOneFile(
ShortcutService service,
ActivityInfo activityInfo, String packageName, @UserIdInt int userId,
List<ShortcutInfo> result) throws IOException, XmlPullParserException {
XmlResourceParser parser = null;
try {
parser = service.injectXmlMetaData(activityInfo, METADATA_KEY);
if (parser == null) {
return result;
}
final ComponentName activity = new ComponentName(packageName, activityInfo.name);
final AttributeSet attrs = Xml.asAttributeSet(parser);
int type;
outer:
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG || parser.getDepth() > 0)) {
if (type != XmlPullParser.START_TAG) {
continue;
}
final int depth = parser.getDepth();
final String tag = parser.getName();
if (depth == 1 && TAG_SHORTCUTS.equals(tag)) {
continue; // Root tag.
}
if (depth == 2 && TAG_SHORTCUT.equals(tag)) {
final ShortcutInfo si = parseShortcutAttributes(
service, attrs, packageName, activity, userId);
if (ShortcutService.DEBUG) {
Slog.d(TAG, "Shortcut=" + si);
}
if (result != null) {
for (int i = result.size() - 1; i >= 0; i--) {
if (si.getId().equals(result.get(i).getId())) {
Slog.w(TAG, "Duplicate shortcut ID detected, skipping.");
continue outer;
}
}
}
if (si != null) {
if (result == null) {
result = new ArrayList<>();
}
result.add(si);
}
continue;
}
Slog.w(TAG, "Unknown tag " + tag);
}
} finally {
if (parser != null) {
parser.close();
}
}
return result;
}
private static ShortcutInfo parseShortcutAttributes(ShortcutService service,
AttributeSet attrs, String packageName, ComponentName activity,
@UserIdInt int userId) {
final TypedArray sa = service.mContext.getResources().obtainAttributes(attrs,
R.styleable.Shortcut);
try {
final String id = sa.getString(R.styleable.Shortcut_shortcutId);
final boolean enabled = sa.getBoolean(R.styleable.Shortcut_enabled, true);
final int rank = sa.getInt(R.styleable.Shortcut_shortcutRank, 0);
final int iconResId = sa.getResourceId(R.styleable.Shortcut_shortcutIcon, 0);
final int titleResId = sa.getResourceId(R.styleable.Shortcut_shortcutTitle, 0);
final int textResId = sa.getResourceId(R.styleable.Shortcut_shortcutText, 0);
final int disabledMessageResId = sa.getResourceId(
R.styleable.Shortcut_shortcutDisabledMessage, 0);
final String categories = sa.getString(R.styleable.Shortcut_shortcutCategories);
String intentAction = sa.getString(R.styleable.Shortcut_shortcutIntentAction);
final String intentData = sa.getString(R.styleable.Shortcut_shortcutIntentData);
if (TextUtils.isEmpty(id)) {
Slog.w(TAG, "Shortcut ID must be provided. activity=" + activity);
return null;
}
if (titleResId == 0) {
Slog.w(TAG, "Shortcut title must be provided. activity=" + activity);
return null;
}
if (TextUtils.isEmpty(intentAction)) {
if (enabled) {
Slog.w(TAG, "Shortcut intent action must be provided. activity=" + activity);
return null;
} else {
// Disabled shortcut doesn't have to have an action, but just set VIEW as the
// default.
intentAction = Intent.ACTION_VIEW;
}
}
final ArraySet<String> categoriesSet;
if (categories == null) {
categoriesSet = null;
} else {
final String[] arr = categories.split(":");
categoriesSet = new ArraySet<>(arr.length);
for (String v : arr) {
categoriesSet.add(v);
}
}
final Intent intent = new Intent(intentAction);
if (!TextUtils.isEmpty(intentData)) {
intent.setData(Uri.parse(intentData));
}
return createShortcutFromManifest(
service,
userId,
id,
packageName,
activity,
titleResId,
textResId,
disabledMessageResId,
categoriesSet,
intent,
rank,
iconResId,
enabled);
} finally {
sa.recycle();
}
}
private static ShortcutInfo createShortcutFromManifest(ShortcutService service,
@UserIdInt int userId, String id, String packageName, ComponentName activityComponent,
int titleResId, int textResId, int disabledMessageResId, Set<String> categories,
Intent intent, int rank, int iconResId, boolean enabled) {
final int flags =
(enabled ? ShortcutInfo.FLAG_MANIFEST : ShortcutInfo.FLAG_DISABLED)
| ShortcutInfo.FLAG_IMMUTABLE
| ((iconResId != 0) ? ShortcutInfo.FLAG_HAS_ICON_RES : 0);
return new ShortcutInfo(
userId,
id,
packageName,
activityComponent,
null, // icon
null, // title string
titleResId,
null, // text string
textResId,
null, // disabled message string
disabledMessageResId,
categories,
intent,
null, // intent extras
rank,
null, // extras
service.injectCurrentTimeMillis(),
flags,
iconResId,
null); // bitmap path
}
}

View File

@@ -25,6 +25,7 @@ import android.app.IUidObserver;
import android.content.ComponentName;
import android.content.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);

View File

@@ -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,

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="shortcut_title1"></string>
<string name="shortcut_text1"></string>
<string name="shortcut_disabled_message1"></string>
<string name="shortcut_title2"></string>
<string name="shortcut_text2"></string>
<string name="shortcut_disabled_message2"></string>
</resources>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -246,11 +246,22 @@ public class ShortcutManagerTestUtils {
reset(o);
}
}
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());
}