Merge "ShortcutManager: implement backup & restore" into nyc-dev

This commit is contained in:
Makoto Onuki
2016-04-01 23:18:41 +00:00
committed by Android (Google) Code Review
11 changed files with 1725 additions and 566 deletions

View File

@@ -0,0 +1,70 @@
/*
* 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.backup;
import android.app.backup.BlobBackupHelper;
import android.content.Context;
import android.content.pm.IShortcutService;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.util.Slog;
public class ShortcutBackupHelper extends BlobBackupHelper {
private static final String TAG = "ShortcutBackupAgent";
private static final int BLOB_VERSION = 1;
private static final String KEY_USER_FILE = "shortcutuser.xml";
public ShortcutBackupHelper() {
super(BLOB_VERSION, KEY_USER_FILE);
}
private IShortcutService getShortcutService() {
return IShortcutService.Stub.asInterface(
ServiceManager.getService(Context.SHORTCUT_SERVICE));
}
@Override
protected byte[] getBackupPayload(String key) {
switch (key) {
case KEY_USER_FILE:
try {
return getShortcutService().getBackupPayload(UserHandle.USER_SYSTEM);
} catch (Exception e) {
Slog.wtf(TAG, "Backup failed", e);
}
break;
default:
Slog.w(TAG, "Unknown key: " + key);
}
return null;
}
@Override
protected void applyRestoredPayload(String key, byte[] payload) {
switch (key) {
case KEY_USER_FILE:
try {
getShortcutService().applyRestore(payload, UserHandle.USER_SYSTEM);
} catch (Exception e) {
Slog.wtf(TAG, "Restore failed", e);
}
break;
default:
Slog.w(TAG, "Unknown key: " + key);
}
}
}

View File

@@ -17,9 +17,9 @@
package com.android.server.backup;
import android.app.IWallpaperManager;
import android.app.backup.BackupAgentHelper;
import android.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
import android.app.backup.BackupAgentHelper;
import android.app.backup.FullBackup;
import android.app.backup.FullBackupDataOutput;
import android.app.backup.WallpaperBackupHelper;
@@ -48,6 +48,7 @@ public class SystemBackupAgent extends BackupAgentHelper {
private static final String NOTIFICATION_HELPER = "notifications";
private static final String PERMISSION_HELPER = "permissions";
private static final String USAGE_STATS_HELPER = "usage_stats";
private static final String SHORTCUT_MANAGER_HELPER = "shortcut_manager";
// These paths must match what the WallpaperManagerService uses. The leaf *_FILENAME
// are also used in the full-backup file format, so must not change unless steps are
@@ -100,6 +101,7 @@ public class SystemBackupAgent extends BackupAgentHelper {
addHelper(NOTIFICATION_HELPER, new NotificationBackupHelper(this));
addHelper(PERMISSION_HELPER, new PermissionBackupHelper());
addHelper(USAGE_STATS_HELPER, new UsageStatsBackupHelper(this));
addHelper(SHORTCUT_MANAGER_HELPER, new ShortcutBackupHelper());
super.onBackup(oldState, data, newState);
}
@@ -138,6 +140,7 @@ public class SystemBackupAgent extends BackupAgentHelper {
addHelper(NOTIFICATION_HELPER, new NotificationBackupHelper(this));
addHelper(PERMISSION_HELPER, new PermissionBackupHelper());
addHelper(USAGE_STATS_HELPER, new UsageStatsBackupHelper(this));
addHelper(SHORTCUT_MANAGER_HELPER, new ShortcutBackupHelper());
try {
super.onRestore(data, appVersionCode, newState);

View File

@@ -1,37 +0,0 @@
/*
* 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.app.backup.BlobBackupHelper;
public class ShortcutBackupAgent extends BlobBackupHelper {
private static final String TAG = "ShortcutBackupAgent";
private static final int BLOB_VERSION = 1;
public ShortcutBackupAgent(int currentBlobVersion, String... keys) {
super(currentBlobVersion, keys);
}
@Override
protected byte[] getBackupPayload(String key) {
throw new RuntimeException("not implemented yet"); // todo
}
@Override
protected void applyRestoredPayload(String key, byte[] payload) {
throw new RuntimeException("not implemented yet"); // todo
}
}

View File

@@ -20,6 +20,10 @@ import android.annotation.UserIdInt;
import android.content.pm.ShortcutInfo;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.pm.ShortcutUser.PackageWithUser;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -27,6 +31,7 @@ import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
/**
@@ -43,15 +48,16 @@ class ShortcutLauncher extends ShortcutPackageItem {
private static final String ATTR_LAUNCHER_USER_ID = "launcher-user";
private static final String ATTR_VALUE = "value";
private static final String ATTR_PACKAGE_NAME = "package-name";
private static final String ATTR_PACKAGE_USER_ID = "package-user";
private final int mOwnerUserId;
/**
* Package name -> IDs.
*/
final private ArrayMap<String, ArraySet<String>> mPinnedShortcuts = new ArrayMap<>();
final private ArrayMap<PackageWithUser, ArraySet<String>> mPinnedShortcuts = new ArrayMap<>();
public ShortcutLauncher(@UserIdInt int ownerUserId, @NonNull String packageName,
private ShortcutLauncher(@UserIdInt int ownerUserId, @NonNull String packageName,
@UserIdInt int launcherUserId, ShortcutPackageInfo spi) {
super(launcherUserId, packageName, spi != null ? spi : ShortcutPackageInfo.newEmpty());
mOwnerUserId = ownerUserId;
@@ -59,7 +65,7 @@ class ShortcutLauncher extends ShortcutPackageItem {
public ShortcutLauncher(@UserIdInt int ownerUserId, @NonNull String packageName,
@UserIdInt int launcherUserId) {
this(launcherUserId, packageName, launcherUserId, null);
this(ownerUserId, packageName, launcherUserId, null);
}
@Override
@@ -67,16 +73,38 @@ class ShortcutLauncher extends ShortcutPackageItem {
return mOwnerUserId;
}
/**
* Called when the new package can't receive the backup, due to signature or version mismatch.
*/
@Override
protected void onRestoreBlocked(ShortcutService s) {
final ArrayList<PackageWithUser> pinnedPackages =
new ArrayList<>(mPinnedShortcuts.keySet());
mPinnedShortcuts.clear();
for (int i = pinnedPackages.size() - 1; i >= 0; i--) {
final PackageWithUser pu = pinnedPackages.get(i);
s.getPackageShortcutsLocked(pu.packageName, pu.userId)
.refreshPinnedFlags(s);
}
}
@Override
protected void onRestored(ShortcutService s) {
// Nothing to do.
}
public void pinShortcuts(@NonNull ShortcutService s, @UserIdInt int packageUserId,
@NonNull String packageName, @NonNull List<String> ids) {
final ShortcutPackage packageShortcuts =
s.getPackageShortcutsLocked(packageName, packageUserId);
final PackageWithUser pu = PackageWithUser.of(packageUserId, packageName);
final int idSize = ids.size();
if (idSize == 0) {
mPinnedShortcuts.remove(packageName);
mPinnedShortcuts.remove(pu);
} else {
final ArraySet<String> prevSet = mPinnedShortcuts.get(packageName);
final ArraySet<String> prevSet = mPinnedShortcuts.get(pu);
// Pin shortcuts. Make sure only pin the ones that were visible to the caller.
// i.e. a non-dynamic, pinned shortcut by *other launchers* shouldn't be pinned here.
@@ -93,7 +121,7 @@ class ShortcutLauncher extends ShortcutPackageItem {
newSet.add(id);
}
}
mPinnedShortcuts.put(packageName, newSet);
mPinnedShortcuts.put(pu, newSet);
}
packageShortcuts.refreshPinnedFlags(s);
}
@@ -101,12 +129,13 @@ class ShortcutLauncher extends ShortcutPackageItem {
/**
* Return the pinned shortcut IDs for the publisher package.
*/
public ArraySet<String> getPinnedShortcutIds(@NonNull String packageName) {
return mPinnedShortcuts.get(packageName);
public ArraySet<String> getPinnedShortcutIds(@NonNull String packageName,
@UserIdInt int packageUserId) {
return mPinnedShortcuts.get(PackageWithUser.of(packageUserId, packageName));
}
boolean cleanUpPackage(String packageName) {
return mPinnedShortcuts.remove(packageName) != null;
boolean cleanUpPackage(String packageName, @UserIdInt int packageUserId) {
return mPinnedShortcuts.remove(PackageWithUser.of(packageUserId, packageName)) != null;
}
/**
@@ -126,9 +155,15 @@ class ShortcutLauncher extends ShortcutPackageItem {
getPackageInfo().saveToXml(out);
for (int i = 0; i < size; i++) {
final PackageWithUser pu = mPinnedShortcuts.keyAt(i);
if (forBackup && (pu.userId != getOwnerUserId())) {
continue; // Target package on a different user, skip. (i.e. work profile)
}
out.startTag(null, TAG_PACKAGE);
ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME,
mPinnedShortcuts.keyAt(i));
ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME, pu.packageName);
ShortcutService.writeAttr(out, ATTR_PACKAGE_USER_ID, pu.userId);
final ArraySet<String> ids = mPinnedShortcuts.valueAt(i);
final int idSize = ids.size();
@@ -157,8 +192,6 @@ class ShortcutLauncher extends ShortcutPackageItem {
final ShortcutLauncher ret = new ShortcutLauncher(launcherUserId, launcherPackageName,
launcherUserId);
ShortcutPackageInfo spi = null;
ArraySet<String> ids = null;
final int outerDepth = parser.getDepth();
int type;
@@ -172,13 +205,17 @@ class ShortcutLauncher extends ShortcutPackageItem {
if (depth == outerDepth + 1) {
switch (tag) {
case ShortcutPackageInfo.TAG_ROOT:
spi = ShortcutPackageInfo.loadFromXml(parser);
ret.getPackageInfo().loadFromXml(parser, fromBackup);
continue;
case TAG_PACKAGE: {
final String packageName = ShortcutService.parseStringAttribute(parser,
ATTR_PACKAGE_NAME);
final int packageUserId = fromBackup ? ownerUserId
: ShortcutService.parseIntAttribute(parser,
ATTR_PACKAGE_USER_ID, ownerUserId);
ids = new ArraySet<>();
ret.mPinnedShortcuts.put(packageName, ids);
ret.mPinnedShortcuts.put(
PackageWithUser.of(packageUserId, packageName), ids);
continue;
}
}
@@ -186,17 +223,17 @@ class ShortcutLauncher extends ShortcutPackageItem {
if (depth == outerDepth + 2) {
switch (tag) {
case TAG_PIN: {
ids.add(ShortcutService.parseStringAttribute(parser,
ATTR_VALUE));
if (ids == null) {
Slog.w(TAG, TAG_PIN + " in invalid place");
} else {
ids.add(ShortcutService.parseStringAttribute(parser, ATTR_VALUE));
}
continue;
}
}
}
ShortcutService.warnForInvalidTag(depth, tag);
}
if (spi != null) {
ret.replacePackageInfo(spi);
}
return ret;
}
@@ -208,6 +245,8 @@ class ShortcutLauncher extends ShortcutPackageItem {
pw.print(getPackageName());
pw.print(" Package user: ");
pw.print(getPackageUserId());
pw.print(" Owner user: ");
pw.print(getOwnerUserId());
pw.println();
getPackageInfo().dump(s, pw, prefix + " ");
@@ -217,10 +256,14 @@ class ShortcutLauncher extends ShortcutPackageItem {
for (int i = 0; i < size; i++) {
pw.println();
final PackageWithUser pu = mPinnedShortcuts.keyAt(i);
pw.print(prefix);
pw.print(" ");
pw.print("Package: ");
pw.println(mPinnedShortcuts.keyAt(i));
pw.print(pu.packageName);
pw.print(" User: ");
pw.println(pu.userId);
final ArraySet<String> ids = mPinnedShortcuts.valueAt(i);
final int idSize = ids.size();
@@ -233,4 +276,9 @@ class ShortcutLauncher extends ShortcutPackageItem {
}
}
}
@VisibleForTesting
ArraySet<String> getAllPinnedShortcutsForTest(String packageName, int packageUserId) {
return new ArraySet<>(mPinnedShortcuts.get(PackageWithUser.of(packageUserId, packageName)));
}
}

View File

@@ -26,6 +26,8 @@ import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
@@ -34,6 +36,7 @@ import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.Predicate;
@@ -83,7 +86,7 @@ class ShortcutPackage extends ShortcutPackageItem {
*/
private long mLastResetTime;
public ShortcutPackage(int packageUserId, String packageName, ShortcutPackageInfo spi) {
private ShortcutPackage(int packageUserId, String packageName, ShortcutPackageInfo spi) {
super(packageUserId, packageName, spi != null ? spi : ShortcutPackageInfo.newEmpty());
}
@@ -97,6 +100,19 @@ class ShortcutPackage extends ShortcutPackageItem {
return getPackageUserId();
}
@Override
protected void onRestoreBlocked(ShortcutService s) {
// Can't restore due to version/signature mismatch. Remove all shortcuts.
mShortcuts.clear();
}
@Override
protected void onRestored(ShortcutService s) {
// Because some launchers may not have been restored (e.g. allowBackup=false),
// we need to re-calculate the pinned shortcuts.
refreshPinnedFlags(s);
}
/**
* Note this does *not* provide a correct view to the calling launcher.
*/
@@ -229,20 +245,26 @@ class ShortcutPackage extends ShortcutPackageItem {
s.getUserShortcutsLocked(getPackageUserId()).getAllLaunchers();
for (int l = launchers.size() - 1; l >= 0; l--) {
// Note even if a launcher that hasn't been installed can still pin shortcuts.
final ShortcutLauncher launcherShortcuts = launchers.valueAt(l);
final ArraySet<String> pinned = launcherShortcuts.getPinnedShortcutIds(
getPackageName());
getPackageName(), getPackageUserId());
if (pinned == null || pinned.size() == 0) {
continue;
}
for (int i = pinned.size() - 1; i >= 0; i--) {
final ShortcutInfo si = mShortcuts.get(pinned.valueAt(i));
final String id = pinned.valueAt(i);
final ShortcutInfo si = mShortcuts.get(id);
if (si == null) {
s.wtf("Shortcut not found");
} else {
si.addFlags(ShortcutInfo.FLAG_PINNED);
// This happens if a launcher pinned shortcuts from this package, then backup&
// restored, but this package doesn't allow backing up.
// In that case the launcher ends up having a dangling pinned shortcuts.
// That's fine, when the launcher is restored, we'll fix it.
continue;
}
si.addFlags(ShortcutInfo.FLAG_PINNED);
}
}
@@ -312,11 +334,15 @@ class ShortcutPackage extends ShortcutPackageItem {
public void findAll(@NonNull ShortcutService s, @NonNull List<ShortcutInfo> result,
@Nullable Predicate<ShortcutInfo> query, int cloneFlag,
@Nullable String callingLauncher, int launcherUserId) {
if (getPackageInfo().isShadow()) {
// Restored and the app not installed yet, so don't return any.
return;
}
// Set of pinned shortcuts by the calling launcher.
final ArraySet<String> pinnedByCallerSet = (callingLauncher == null) ? null
: s.getLauncherShortcuts(callingLauncher, getPackageUserId(), launcherUserId)
.getPinnedShortcutIds(getPackageName());
: s.getLauncherShortcutsLocked(callingLauncher, getPackageUserId(), launcherUserId)
.getPinnedShortcutIds(getPackageName(), getPackageUserId());
for (int i = 0; i < mShortcuts.size(); i++) {
final ShortcutInfo si = mShortcuts.valueAt(i);
@@ -328,7 +354,8 @@ class ShortcutPackage extends ShortcutPackageItem {
|| ((pinnedByCallerSet != null) && pinnedByCallerSet.contains(si.getId()));
if (!si.isDynamic()) {
if (!si.isPinned()) {
s.wtf("Shortcut not pinned here");
s.wtf("Shortcut not pinned: package " + getPackageName()
+ ", user=" + getPackageUserId() + ", id=" + si.getId());
continue;
}
if (!isPinnedByCaller) {
@@ -479,7 +506,6 @@ class ShortcutPackage extends ShortcutPackageItem {
ShortcutService.parseIntAttribute(parser, ATTR_CALL_COUNT);
ret.mLastResetTime =
ShortcutService.parseLongAttribute(parser, ATTR_LAST_RESET);
ShortcutPackageInfo spi = null;
final int outerDepth = parser.getDepth();
int type;
@@ -493,7 +519,7 @@ class ShortcutPackage extends ShortcutPackageItem {
if (depth == outerDepth + 1) {
switch (tag) {
case ShortcutPackageInfo.TAG_ROOT:
spi = ShortcutPackageInfo.loadFromXml(parser);
ret.getPackageInfo().loadFromXml(parser, fromBackup);
continue;
case TAG_SHORTCUT:
final ShortcutInfo si = parseShortcut(parser, packageName);
@@ -505,9 +531,6 @@ class ShortcutPackage extends ShortcutPackageItem {
}
ShortcutService.warnForInvalidTag(depth, tag);
}
if (spi != null) {
ret.replacePackageInfo(spi);
}
return ret;
}
@@ -567,4 +590,9 @@ class ShortcutPackage extends ShortcutPackageItem {
intentPersistableExtras, weight, extras, lastChangedTimestamp, flags,
iconRes, bitmapPath);
}
@VisibleForTesting
List<ShortcutInfo> getAllShortcutsForTest() {
return new ArrayList<>(mShortcuts.values());
}
}

View File

@@ -67,10 +67,6 @@ class ShortcutPackageInfo {
return mIsShadow;
}
public boolean isInstalled() {
return !mIsShadow;
}
public void setShadow(boolean shadow) {
mIsShadow = shadow;
}
@@ -79,14 +75,24 @@ class ShortcutPackageInfo {
return mVersionCode;
}
public boolean canRestoreTo(PackageInfo target) {
public boolean hasSignatures() {
return mSigHashes.size() > 0;
}
public boolean canRestoreTo(ShortcutService s, PackageInfo target) {
if (!s.shouldBackupApp(target)) {
// "allowBackup" was true when backed up, but now false.
Slog.w(TAG, "Can't restore: package no longer allows backup");
return false;
}
if (target.versionCode < mVersionCode) {
Slog.w(TAG, String.format("Package current version %d < backed up version %d",
Slog.w(TAG, String.format(
"Can't restore: package current version %d < backed up version %d",
target.versionCode, mVersionCode));
return false;
}
if (!BackupUtils.signaturesMatch(mSigHashes, target)) {
Slog.w(TAG, "Package signature mismtach");
Slog.w(TAG, "Can't restore: Package signature mismatch");
return false;
}
return true;
@@ -106,6 +112,11 @@ class ShortcutPackageInfo {
}
public void refresh(ShortcutService s, ShortcutPackageItem pkg) {
if (mIsShadow) {
s.wtf("Attempted to refresh package info for shadow package " + pkg.getPackageName()
+ ", user=" + pkg.getOwnerUserId());
return;
}
// Note use mUserId here, rather than userId.
final PackageInfo pi = s.getPackageInfoWithSignatures(
pkg.getPackageName(), pkg.getPackageUserId());
@@ -132,15 +143,17 @@ class ShortcutPackageInfo {
out.endTag(null, TAG_ROOT);
}
public static ShortcutPackageInfo loadFromXml(XmlPullParser parser)
public void loadFromXml(XmlPullParser parser, boolean fromBackup)
throws IOException, XmlPullParserException {
final int versionCode = ShortcutService.parseIntAttribute(parser, ATTR_VERSION);
final boolean shadow = ShortcutService.parseBooleanAttribute(parser, ATTR_SHADOW);
// When restoring from backup, it's always shadow.
final boolean shadow =
fromBackup || ShortcutService.parseBooleanAttribute(parser, ATTR_SHADOW);
final ArrayList<byte[]> hashes = new ArrayList<>();
final int outerDepth = parser.getDepth();
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
@@ -163,7 +176,11 @@ class ShortcutPackageInfo {
}
ShortcutService.warnForInvalidTag(depth, tag);
}
return new ShortcutPackageInfo(versionCode, hashes, shadow);
// Successfully loaded; replace the feilds.
mVersionCode = versionCode;
mIsShadow = shadow;
mSigHashes = hashes;
}
public void dump(ShortcutService s, PrintWriter pw, String prefix) {

View File

@@ -16,6 +16,8 @@
package com.android.server.pm;
import android.annotation.NonNull;
import android.content.pm.PackageInfo;
import android.util.Slog;
import com.android.internal.util.Preconditions;
@@ -25,10 +27,12 @@ import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
abstract class ShortcutPackageItem {
private static final String TAG = ShortcutService.TAG;
private final int mPackageUserId;
private final String mPackageName;
private ShortcutPackageInfo mPackageInfo;
private final ShortcutPackageInfo mPackageInfo;
protected ShortcutPackageItem(int packageUserId, @NonNull String packageName,
@NonNull ShortcutPackageInfo packageInfo) {
@@ -61,25 +65,62 @@ abstract class ShortcutPackageItem {
return mPackageInfo;
}
/**
* Should be only used when loading from a file.o
*/
protected void replacePackageInfo(@NonNull ShortcutPackageInfo packageInfo) {
mPackageInfo = Preconditions.checkNotNull(packageInfo);
}
public void refreshPackageInfoAndSave(ShortcutService s) {
if (mPackageInfo.isShadow()) {
return; // Don't refresh for shadow user.
}
mPackageInfo.refresh(s, this);
s.scheduleSaveUser(getOwnerUserId());
}
public void ensureNotShadowAndSave(ShortcutService s) {
if (mPackageInfo.isShadow()) {
mPackageInfo.setShadow(false);
s.scheduleSaveUser(getOwnerUserId());
public void attemptToRestoreIfNeededAndSave(ShortcutService s) {
if (!mPackageInfo.isShadow()) {
return; // Already installed, nothing to do.
}
if (!s.isPackageInstalled(mPackageName, mPackageUserId)) {
if (ShortcutService.DEBUG) {
Slog.d(TAG, String.format("Package still not installed: %s user=%d",
mPackageName, mPackageUserId));
}
return; // Not installed, no need to restore yet.
}
if (!mPackageInfo.hasSignatures()) {
s.wtf("Attempted to restore package " + mPackageName + ", user=" + mPackageUserId
+ " but signatures not found in the restore data.");
onRestoreBlocked(s);
return;
}
final PackageInfo pi = s.getPackageInfoWithSignatures(mPackageName, mPackageUserId);
if (!mPackageInfo.canRestoreTo(s, pi)) {
// Package is now installed, but can't restore. Let the subclass do the cleanup.
onRestoreBlocked(s);
return;
}
if (ShortcutService.DEBUG) {
Slog.d(TAG, String.format("Restored package: %s/%d on user %d", mPackageName,
mPackageUserId, getOwnerUserId()));
}
onRestored(s);
// Now the package is not shadow.
mPackageInfo.setShadow(false);
s.scheduleSaveUser(mPackageUserId);
}
/**
* Called when the new package can't be restored because it has a lower version number
* or different signatures.
*/
protected abstract void onRestoreBlocked(ShortcutService s);
/**
* Called when the new package is successfully restored.
*/
protected abstract void onRestored(ShortcutService s);
public abstract void saveToXml(@NonNull XmlSerializer out, boolean forBackup)
throws IOException, XmlPullParserException;
}

View File

@@ -46,6 +46,7 @@ import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Binder;
import android.os.Environment;
import android.os.FileUtils;
import android.os.Handler;
import android.os.Looper;
import android.os.ParcelFileDescriptor;
@@ -101,6 +102,7 @@ import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
@@ -112,17 +114,16 @@ import java.util.function.Predicate;
*
* - Scan and remove orphan bitmaps (just in case).
*
* - Backup & restore
*
* - Detect when already registered instances are passed to APIs again, which might break
* internal bitmap handling.
*
* - Add more call stats.
*/
public class ShortcutService extends IShortcutService.Stub {
static final String TAG = "ShortcutService";
static final boolean DEBUG = false; // STOPSHIP if true
static final boolean DEBUG_LOAD = false; // STOPSHIP if true
static final boolean ENABLE_DEBUG_COMMAND = true; // STOPSHIP if true
@VisibleForTesting
static final long DEFAULT_RESET_INTERVAL_SEC = 24 * 60 * 60; // 1 day
@@ -262,6 +263,26 @@ public class ShortcutService extends IShortcutService.Stub {
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE
| PackageManager.MATCH_UNINSTALLED_PACKAGES;
// Stats
@VisibleForTesting
interface Stats {
int GET_DEFAULT_HOME = 0;
int GET_PACKAGE_INFO = 1;
int GET_PACKAGE_INFO_WITH_SIG = 2;
int GET_APPLICATION_INFO = 3;
int LAUNCHER_PERMISSION_CHECK = 4;
int COUNT = LAUNCHER_PERMISSION_CHECK + 1;
}
final Object mStatLock = new Object();
@GuardedBy("mStatLock")
private final int[] mCountStats = new int[Stats.COUNT];
@GuardedBy("mStatLock")
private final long[] mDurationStats = new long[Stats.COUNT];
public ShortcutService(Context context) {
this(context, BackgroundThread.get().getLooper());
}
@@ -278,6 +299,13 @@ public class ShortcutService extends IShortcutService.Stub {
mPackageMonitor.register(context, looper, UserHandle.ALL, /* externalStorage= */ false);
}
void logDurationStat(int statId, long start) {
synchronized (mStatLock) {
mCountStats[statId]++;
mDurationStats[statId] += (System.currentTimeMillis() - start);
}
}
/**
* System service lifecycle.
*/
@@ -822,7 +850,7 @@ public class ShortcutService extends IShortcutService.Stub {
@GuardedBy("mLock")
@NonNull
boolean isUserLoadedLocked(@UserIdInt int userId) {
private boolean isUserLoadedLocked(@UserIdInt int userId) {
return mUsers.get(userId) != null;
}
@@ -841,19 +869,27 @@ public class ShortcutService extends IShortcutService.Stub {
return userPackages;
}
void forEachLoadedUserLocked(@NonNull Consumer<ShortcutUser> c) {
for (int i = mUsers.size() - 1; i >= 0; i--) {
c.accept(mUsers.valueAt(i));
}
}
/** Return the per-user per-package state. */
@GuardedBy("mLock")
@NonNull
ShortcutPackage getPackageShortcutsLocked(
@NonNull String packageName, @UserIdInt int userId) {
return getUserShortcutsLocked(userId).getPackageShortcuts(packageName);
return getUserShortcutsLocked(userId).getPackageShortcuts(this, packageName);
}
@GuardedBy("mLock")
@NonNull
ShortcutLauncher getLauncherShortcuts(
@NonNull String packageName, @UserIdInt int userId, @UserIdInt int launcherUserId) {
return getUserShortcutsLocked(userId).getLauncherShortcuts(packageName, launcherUserId);
ShortcutLauncher getLauncherShortcutsLocked(
@NonNull String packageName, @UserIdInt int ownerUserId,
@UserIdInt int launcherUserId) {
return getUserShortcutsLocked(ownerUserId)
.getLauncherShortcuts(this, packageName, launcherUserId);
}
// === Caller validation ===
@@ -1212,8 +1248,6 @@ public class ShortcutService extends IShortcutService.Stub {
synchronized (mLock) {
final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
ps.ensureNotShadowAndSave(this);
// Throttling.
if (!ps.tryApiCall(this)) {
return false;
@@ -1249,8 +1283,6 @@ public class ShortcutService extends IShortcutService.Stub {
synchronized (mLock) {
final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
ps.ensureNotShadowAndSave(this);
// Throttling.
if (!ps.tryApiCall(this)) {
return false;
@@ -1288,8 +1320,6 @@ public class ShortcutService extends IShortcutService.Stub {
synchronized (mLock) {
final ShortcutPackage ps = getPackageShortcutsLocked(packageName, userId);
ps.ensureNotShadowAndSave(this);
// Throttling.
if (!ps.tryApiCall(this)) {
return false;
@@ -1422,15 +1452,17 @@ public class ShortcutService extends IShortcutService.Stub {
@VisibleForTesting
boolean hasShortcutHostPermissionInner(@NonNull String callingPackage, int userId) {
synchronized (mLock) {
long start = System.currentTimeMillis();
final long start = System.currentTimeMillis();
final ShortcutUser user = getUserShortcutsLocked(userId);
final List<ResolveInfo> allHomeCandidates = new ArrayList<>();
// Default launcher from package manager.
final long startGetHomeActivitiesAsUser = System.currentTimeMillis();
final ComponentName defaultLauncher = injectPackageManagerInternal()
.getHomeActivitiesAsUser(allHomeCandidates, userId);
logDurationStat(Stats.GET_DEFAULT_HOME, startGetHomeActivitiesAsUser);
ComponentName detected;
if (defaultLauncher != null) {
@@ -1473,10 +1505,8 @@ public class ShortcutService extends IShortcutService.Stub {
lastPriority = ri.priority;
}
}
final long end = System.currentTimeMillis();
if (DEBUG) {
Slog.v(TAG, String.format("hasShortcutPermission took %d ms", end - start));
}
logDurationStat(Stats.LAUNCHER_PERMISSION_CHECK, start);
if (detected != null) {
if (DEBUG) {
Slog.v(TAG, "Detected launcher: " + detected);
@@ -1492,10 +1522,17 @@ public class ShortcutService extends IShortcutService.Stub {
// === House keeping ===
/**
* Remove all the information associated with a package. This will really remove all the
* information, including the restore information (i.e. it'll remove packages even if they're
* shadow).
*/
@VisibleForTesting
void cleanUpPackageLocked(String packageName, int owningUserId, int packageUserId) {
// TODO Don't remove shadow packages' information.
if (isPackageInstalled(packageName, packageUserId)) {
wtf("Package " + packageName + " is still installed for user " + packageUserId);
return;
}
final boolean wasUserLoaded = isUserLoadedLocked(owningUserId);
@@ -1504,7 +1541,7 @@ public class ShortcutService extends IShortcutService.Stub {
// First, remove the package from the package list (if the package is a publisher).
if (packageUserId == owningUserId) {
if (mUser.getPackages().remove(packageName) != null) {
if (mUser.removePackage(packageName) != null) {
doNotify = true;
}
}
@@ -1515,12 +1552,12 @@ public class ShortcutService extends IShortcutService.Stub {
// Then remove pinned shortcuts from all launchers.
final ArrayMap<PackageWithUser, ShortcutLauncher> launchers = mUser.getAllLaunchers();
for (int i = launchers.size() - 1; i >= 0; i--) {
launchers.valueAt(i).cleanUpPackage(packageName);
launchers.valueAt(i).cleanUpPackage(packageName, packageUserId);
}
// Now there may be orphan shortcuts because we removed pinned shortucts at the previous
// step. Remove them too.
for (int i = mUser.getPackages().size() - 1; i >= 0; i--) {
mUser.getPackages().valueAt(i).refreshPinnedFlags(this);
for (int i = mUser.getAllPackages().size() - 1; i >= 0; i--) {
mUser.getAllPackages().valueAt(i).refreshPinnedFlags(this);
}
scheduleSaveUser(owningUserId);
@@ -1539,6 +1576,7 @@ public class ShortcutService extends IShortcutService.Stub {
* Entry point from {@link LauncherApps}.
*/
private class LocalService extends ShortcutServiceInternal {
@Override
public List<ShortcutInfo> getShortcuts(int launcherUserId,
@NonNull String callingPackage, long changedSince,
@@ -1551,13 +1589,16 @@ public class ShortcutService extends IShortcutService.Stub {
: ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO;
synchronized (mLock) {
getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
.attemptToRestoreIfNeededAndSave(ShortcutService.this);
if (packageName != null) {
getShortcutsInnerLocked(launcherUserId,
callingPackage, packageName, changedSince,
componentName, queryFlags, userId, ret, cloneFlag);
} else {
final ArrayMap<String, ShortcutPackage> packages =
getUserShortcutsLocked(userId).getPackages();
getUserShortcutsLocked(userId).getAllPackages();
for (int i = packages.size() - 1; i >= 0; i--) {
getShortcutsInnerLocked(launcherUserId,
callingPackage, packages.keyAt(i), changedSince,
@@ -1601,6 +1642,9 @@ public class ShortcutService extends IShortcutService.Stub {
final ArrayList<ShortcutInfo> ret = new ArrayList<>(ids.size());
final ArraySet<String> idSet = new ArraySet<>(ids);
synchronized (mLock) {
getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
.attemptToRestoreIfNeededAndSave(ShortcutService.this);
getPackageShortcutsLocked(packageName, userId).findAll(
ShortcutService.this, ret,
(ShortcutInfo si) -> idSet.contains(si.getId()),
@@ -1616,13 +1660,16 @@ public class ShortcutService extends IShortcutService.Stub {
Preconditions.checkStringNotEmpty(shortcutId, "shortcutId");
synchronized (mLock) {
getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
.attemptToRestoreIfNeededAndSave(ShortcutService.this);
final ShortcutInfo si = getShortcutInfoLocked(
launcherUserId, callingPackage, packageName, shortcutId, userId);
return si != null && si.isPinned();
}
}
public ShortcutInfo getShortcutInfoLocked(
private ShortcutInfo getShortcutInfoLocked(
int launcherUserId, @NonNull String callingPackage,
@NonNull String packageName, @NonNull String shortcutId, int userId) {
Preconditions.checkStringNotEmpty(packageName, "packageName");
@@ -1646,9 +1693,8 @@ public class ShortcutService extends IShortcutService.Stub {
synchronized (mLock) {
final ShortcutLauncher launcher =
getLauncherShortcuts(callingPackage, userId, launcherUserId);
launcher.ensureNotShadowAndSave(ShortcutService.this);
getLauncherShortcutsLocked(callingPackage, userId, launcherUserId);
launcher.attemptToRestoreIfNeededAndSave(ShortcutService.this);
launcher.pinShortcuts(
ShortcutService.this, userId, packageName, shortcutIds);
@@ -1665,6 +1711,9 @@ public class ShortcutService extends IShortcutService.Stub {
Preconditions.checkStringNotEmpty(shortcutId, "shortcutId can't be empty");
synchronized (mLock) {
getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
.attemptToRestoreIfNeededAndSave(ShortcutService.this);
// Make sure the shortcut is actually visible to the launcher.
final ShortcutInfo si = getShortcutInfoLocked(
launcherUserId, callingPackage, packageName, shortcutId, userId);
@@ -1690,6 +1739,9 @@ public class ShortcutService extends IShortcutService.Stub {
Preconditions.checkNotNull(shortcut, "shortcut");
synchronized (mLock) {
getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
.attemptToRestoreIfNeededAndSave(ShortcutService.this);
final ShortcutInfo shortcutInfo = getPackageShortcutsLocked(
shortcut.getPackageName(), userId).findShortcutById(shortcut.getId());
return (shortcutInfo != null && shortcutInfo.hasIconResource())
@@ -1704,6 +1756,9 @@ public class ShortcutService extends IShortcutService.Stub {
Preconditions.checkNotNull(shortcutIn, "shortcut");
synchronized (mLock) {
getLauncherShortcutsLocked(callingPackage, userId, launcherUserId)
.attemptToRestoreIfNeededAndSave(ShortcutService.this);
final ShortcutInfo shortcutInfo = getPackageShortcutsLocked(
shortcutIn.getPackageName(), userId).findShortcutById(shortcutIn.getId());
if (shortcutInfo == null || !shortcutInfo.hasIconFile()) {
@@ -1757,28 +1812,28 @@ public class ShortcutService extends IShortcutService.Stub {
* perform cleanup.
*/
@VisibleForTesting
void cleanupGonePackages(@UserIdInt int userId) {
void cleanupGonePackages(@UserIdInt int ownerUserId) {
if (DEBUG) {
Slog.d(TAG, "cleanupGonePackages() userId=" + userId);
Slog.d(TAG, "cleanupGonePackages() ownerUserId=" + ownerUserId);
}
final ArrayList<PackageWithUser> gonePackages = new ArrayList<>();
synchronized (mLock) {
final ShortcutUser user = getUserShortcutsLocked(userId);
final ShortcutUser user = getUserShortcutsLocked(ownerUserId);
user.forAllPackageItems(spi -> {
if (spi.getPackageInfo().isShadow()) {
return; // Don't delete shadow information.
}
if (isPackageInstalled(spi.getPackageName(), spi.getPackageUserId())) {
return;
return; // Package not gone.
}
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, userId, pu.userId);
cleanUpPackageLocked(pu.packageName, ownerUserId, pu.userId);
}
}
}
@@ -1789,7 +1844,8 @@ public class ShortcutService extends IShortcutService.Stub {
Slog.d(TAG, String.format("handlePackageAdded: %s user=%d", packageName, userId));
}
synchronized (mLock) {
getUserShortcutsLocked(userId).unshadowPackage(this, packageName, userId);
forEachLoadedUserLocked(user ->
user.attemptToRestoreIfNeededAndSave(this, packageName, userId));
}
}
@@ -1798,18 +1854,20 @@ public class ShortcutService extends IShortcutService.Stub {
Slog.d(TAG, String.format("handlePackageUpdateFinished: %s user=%d",
packageName, userId));
}
synchronized (mLock) {
getUserShortcutsLocked(userId).unshadowPackage(this, packageName, userId);
forEachLoadedUserLocked(user ->
user.attemptToRestoreIfNeededAndSave(this, packageName, userId));
}
}
private void handlePackageRemoved(String packageName, @UserIdInt int userId) {
private void handlePackageRemoved(String packageName, @UserIdInt int packageUserId) {
if (DEBUG) {
Slog.d(TAG, String.format("handlePackageRemoved: %s user=%d", packageName, userId));
Slog.d(TAG, String.format("handlePackageRemoved: %s user=%d", packageName,
packageUserId));
}
synchronized (mLock) {
cleanUpPackageLocked(packageName, userId, userId);
forEachLoadedUserLocked(user ->
cleanUpPackageLocked(packageName, user.getUserId(), packageUserId));
}
}
@@ -1836,6 +1894,7 @@ public class ShortcutService extends IShortcutService.Stub {
@VisibleForTesting
PackageInfo injectPackageInfo(String packageName, @UserIdInt int userId,
boolean getSignatures) {
final long start = System.currentTimeMillis();
final long token = injectClearCallingIdentity();
try {
return mIPackageManager.getPackageInfo(packageName, PACKAGE_MATCH_FLAGS
@@ -1847,11 +1906,16 @@ public class ShortcutService extends IShortcutService.Stub {
return null;
} finally {
injectRestoreCallingIdentity(token);
logDurationStat(
(getSignatures ? Stats.GET_PACKAGE_INFO_WITH_SIG : Stats.GET_PACKAGE_INFO),
start);
}
}
@VisibleForTesting
ApplicationInfo injectApplicationInfo(String packageName, @UserIdInt int userId) {
final long start = System.currentTimeMillis();
final long token = injectClearCallingIdentity();
try {
return mIPackageManager.getApplicationInfo(packageName, PACKAGE_MATCH_FLAGS, userId);
@@ -1861,6 +1925,8 @@ public class ShortcutService extends IShortcutService.Stub {
return null;
} finally {
injectRestoreCallingIdentity(token);
logDurationStat(Stats.GET_APPLICATION_INFO, start);
}
}
@@ -1869,7 +1935,7 @@ public class ShortcutService extends IShortcutService.Stub {
return (ai != null) && ((ai.flags & flags) == flags);
}
private boolean isPackageInstalled(String packageName, int userId) {
boolean isPackageInstalled(String packageName, int userId) {
return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_INSTALLED);
}
@@ -1879,8 +1945,12 @@ public class ShortcutService extends IShortcutService.Stub {
return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_ALLOW_BACKUP);
}
boolean shouldBackupApp(PackageInfo pi) {
return (pi.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0;
}
@Override
public byte[] getBackupPayload(@UserIdInt int userId) throws RemoteException {
public byte[] getBackupPayload(@UserIdInt int userId) {
enforceSystem();
if (DEBUG) {
Slog.d(TAG, "Backing up user " + userId);
@@ -1908,7 +1978,7 @@ public class ShortcutService extends IShortcutService.Stub {
}
@Override
public void applyRestore(byte[] payload, @UserIdInt int userId) throws RemoteException {
public void applyRestore(byte[] payload, @UserIdInt int userId) {
enforceSystem();
if (DEBUG) {
Slog.d(TAG, "Restoring user " + userId);
@@ -1923,6 +1993,15 @@ public class ShortcutService extends IShortcutService.Stub {
}
synchronized (mLock) {
mUsers.put(userId, user);
// Then purge all the save images.
final File bitmapPath = getUserBitmapFilePath(userId);
final boolean success = FileUtils.deleteContents(bitmapPath);
if (!success) {
Slog.w(TAG, "Failed to delete " + bitmapPath);
}
saveUserLocked(userId);
}
}
@@ -1974,9 +2053,19 @@ public class ShortcutService extends IShortcutService.Stub {
pw.print(" Icon format: ");
pw.print(mIconPersistFormat);
pw.print(" Icon quality: ");
pw.print(mIconPersistQuality);
pw.println(mIconPersistQuality);
pw.println();
pw.println(" Stats:");
synchronized (mStatLock) {
final String p = " ";
dumpStatLS(pw, p, Stats.GET_DEFAULT_HOME, "getHomeActivities()");
dumpStatLS(pw, p, Stats.LAUNCHER_PERMISSION_CHECK, "Launcher permission check");
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");
}
for (int i = 0; i < mUsers.size(); i++) {
pw.println();
@@ -1991,6 +2080,15 @@ public class ShortcutService extends IShortcutService.Stub {
return tobj.format("%Y-%m-%d %H:%M:%S");
}
private void dumpStatLS(PrintWriter pw, String prefix, int statId, String label) {
pw.print(prefix);
final int count = mCountStats[statId];
final long dur = mDurationStats[statId];
pw.println(String.format("%s: count=%d, total=%dms, avg=%.1fms",
label, count, dur,
(count == 0 ? 0 : ((double) dur) / count)));
}
// === Shell support ===
@Override
@@ -2202,9 +2300,10 @@ public class ShortcutService extends IShortcutService.Stub {
}
final void wtf(String message) {
Slog.wtf(TAG, message, /* exception= */ null);
wtf( message, /* exception= */ null);
}
// Injection point.
void wtf(String message, Exception e) {
Slog.wtf(TAG, message, e);
}
@@ -2275,7 +2374,7 @@ public class ShortcutService extends IShortcutService.Stub {
final ShortcutUser user = mUsers.get(userId);
if (user == null) return null;
final ShortcutPackage pkg = user.getPackages().get(packageName);
final ShortcutPackage pkg = user.getAllPackages().get(packageName);
if (pkg == null) return null;
return pkg.findShortcutById(shortcutId);

View File

@@ -53,8 +53,8 @@ class ShortcutUser {
this.packageName = Preconditions.checkNotNull(packageName);
}
public static PackageWithUser of(int launcherUserId, String packageName) {
return new PackageWithUser(launcherUserId, packageName);
public static PackageWithUser of(int userId, String packageName) {
return new PackageWithUser(userId, packageName);
}
public static PackageWithUser of(ShortcutPackageItem spi) {
@@ -78,12 +78,12 @@ class ShortcutUser {
@Override
public String toString() {
return String.format("{Launcher: %d, %s}", userId, packageName);
return String.format("{Package: %d, %s}", userId, packageName);
}
}
@UserIdInt
final int mUserId;
private final int mUserId;
private final ArrayMap<String, ShortcutPackage> mPackages = new ArrayMap<>();
@@ -95,10 +95,18 @@ class ShortcutUser {
mUserId = userId;
}
public ArrayMap<String, ShortcutPackage> getPackages() {
public int getUserId() {
return mUserId;
}
public ArrayMap<String, ShortcutPackage> getAllPackages() {
return mPackages;
}
public ShortcutPackage removePackage(@NonNull String packageName) {
return mPackages.remove(packageName);
}
public ArrayMap<PackageWithUser, ShortcutLauncher> getAllLaunchers() {
return mLaunchers;
}
@@ -113,22 +121,26 @@ class ShortcutUser {
return mLaunchers.remove(PackageWithUser.of(packageUserId, packageName));
}
public ShortcutPackage getPackageShortcuts(@NonNull String packageName) {
public ShortcutPackage getPackageShortcuts(ShortcutService s, @NonNull String packageName) {
ShortcutPackage ret = mPackages.get(packageName);
if (ret == null) {
ret = new ShortcutPackage(mUserId, packageName);
mPackages.put(packageName, ret);
} else {
ret.attemptToRestoreIfNeededAndSave(s);
}
return ret;
}
public ShortcutLauncher getLauncherShortcuts(@NonNull String packageName,
public ShortcutLauncher getLauncherShortcuts(ShortcutService s, @NonNull String packageName,
@UserIdInt int launcherUserId) {
final PackageWithUser key = PackageWithUser.of(launcherUserId, packageName);
ShortcutLauncher ret = mLaunchers.get(key);
if (ret == null) {
ret = new ShortcutLauncher(mUserId, packageName, launcherUserId);
mLaunchers.put(key, ret);
} else {
ret.attemptToRestoreIfNeededAndSave(s);
}
return ret;
}
@@ -148,14 +160,6 @@ class ShortcutUser {
}
}
public void unshadowPackage(ShortcutService s, @NonNull String packageName,
@UserIdInt int packageUserId) {
forPackageItem(packageName, packageUserId, spi -> {
Slog.i(TAG, String.format("Restoring for %s, user=%d", packageName, packageUserId));
spi.ensureNotShadowAndSave(s);
});
}
public void forPackageItem(@NonNull String packageName, @UserIdInt int packageUserId,
Consumer<ShortcutPackageItem> callback) {
forAllPackageItems(spi -> {
@@ -166,6 +170,13 @@ class ShortcutUser {
});
}
public void attemptToRestoreIfNeededAndSave(ShortcutService s, @NonNull String packageName,
@UserIdInt int packageUserId) {
forPackageItem(packageName, packageUserId, spi -> {
spi.attemptToRestoreIfNeededAndSave(s);
});
}
public void saveToXml(ShortcutService s, XmlSerializer out, boolean forBackup)
throws IOException, XmlPullParserException {
out.startTag(null, TAG_ROOT);
@@ -229,7 +240,7 @@ class ShortcutUser {
s, parser, userId, fromBackup);
// Don't use addShortcut(), we don't need to save the icon.
ret.getPackages().put(shortcuts.getPackageName(), shortcuts);
ret.mPackages.put(shortcuts.getPackageName(), shortcuts);
continue;
}

View File

@@ -1,271 +0,0 @@
/*
* 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.content.ComponentName;
import android.content.Intent;
import android.content.pm.ShortcutInfo;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.os.Parcel;
import android.os.PersistableBundle;
import android.test.AndroidTestCase;
import com.android.internal.util.Preconditions;
import com.android.server.testutis.TestUtils;
/**
* Tests for {@link ShortcutInfo}.
m FrameworksServicesTests &&
adb install \
-r -g ${ANDROID_PRODUCT_OUT}/data/app/FrameworksServicesTests/FrameworksServicesTests.apk &&
adb shell am instrument -e class com.android.server.pm.ShortcutInfoTest \
-w com.android.frameworks.servicestests/android.support.test.runner.AndroidJUnitRunner
*/
public class ShortcutInfoTest extends AndroidTestCase {
public void testMissingMandatoryFields() {
TestUtils.assertExpectException(
IllegalArgumentException.class,
"ID must be provided",
() -> new ShortcutInfo.Builder(mContext).build());
TestUtils.assertExpectException(
IllegalArgumentException.class,
"title must be provided",
() -> new ShortcutInfo.Builder(mContext).setId("id").build()
.enforceMandatoryFields());
TestUtils.assertExpectException(
NullPointerException.class,
"Intent must be provided",
() -> new ShortcutInfo.Builder(mContext).setId("id").setTitle("x").build()
.enforceMandatoryFields());
}
private ShortcutInfo parceled(ShortcutInfo si) {
Parcel p = Parcel.obtain();
p.writeParcelable(si, 0);
p.setDataPosition(0);
ShortcutInfo si2 = p.readParcelable(getClass().getClassLoader());
p.recycle();
return si2;
}
private Intent makeIntent(String action, Object... bundleKeysAndValues) {
final Intent intent = new Intent(action);
intent.replaceExtras(ShortcutManagerTest.makeBundle(bundleKeysAndValues));
return intent;
}
public void testParcel() {
ShortcutInfo si = parceled(new ShortcutInfo.Builder(getContext())
.setId("id")
.setTitle("title")
.setIntent(makeIntent("action"))
.build());
assertEquals(getContext().getPackageName(), si.getPackageName());
assertEquals("id", si.getId());
assertEquals("title", si.getTitle());
assertEquals("action", si.getIntent().getAction());
PersistableBundle pb = new PersistableBundle();
pb.putInt("k", 1);
si = new ShortcutInfo.Builder(getContext())
.setId("id")
.setActivityComponent(new ComponentName("a", "b"))
.setIcon(Icon.createWithContentUri("content://a.b.c/"))
.setTitle("title")
.setText("text")
.setIntent(makeIntent("action", "key", "val"))
.setWeight(123)
.setExtras(pb)
.build();
si.addFlags(ShortcutInfo.FLAG_PINNED);
si.setBitmapPath("abc");
si.setIconResourceId(456);
si = parceled(si);
assertEquals(getContext().getPackageName(), si.getPackageName());
assertEquals("id", si.getId());
assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
assertEquals("content://a.b.c/", si.getIcon().getUriString());
assertEquals("title", si.getTitle());
assertEquals("text", si.getText());
assertEquals("action", si.getIntent().getAction());
assertEquals("val", si.getIntent().getStringExtra("key"));
assertEquals(123, si.getWeight());
assertEquals(1, si.getExtras().getInt("k"));
assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
assertEquals("abc", si.getBitmapPath());
assertEquals(456, si.getIconResourceId());
}
public void testClone() {
PersistableBundle pb = new PersistableBundle();
pb.putInt("k", 1);
ShortcutInfo sorig = new ShortcutInfo.Builder(getContext())
.setId("id")
.setActivityComponent(new ComponentName("a", "b"))
.setIcon(Icon.createWithContentUri("content://a.b.c/"))
.setTitle("title")
.setText("text")
.setIntent(makeIntent("action", "key", "val"))
.setWeight(123)
.setExtras(pb)
.build();
sorig.addFlags(ShortcutInfo.FLAG_PINNED);
sorig.setBitmapPath("abc");
sorig.setIconResourceId(456);
ShortcutInfo si = sorig.clone(/* clone flags*/ 0);
assertEquals(getContext().getPackageName(), si.getPackageName());
assertEquals("id", si.getId());
assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
assertEquals("content://a.b.c/", si.getIcon().getUriString());
assertEquals("title", si.getTitle());
assertEquals("text", si.getText());
assertEquals("action", si.getIntent().getAction());
assertEquals("val", si.getIntent().getStringExtra("key"));
assertEquals(123, si.getWeight());
assertEquals(1, si.getExtras().getInt("k"));
assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
assertEquals("abc", si.getBitmapPath());
assertEquals(456, si.getIconResourceId());
si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_CREATOR);
assertEquals(getContext().getPackageName(), si.getPackageName());
assertEquals("id", si.getId());
assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
assertEquals(null, si.getIcon());
assertEquals("title", si.getTitle());
assertEquals("text", si.getText());
assertEquals("action", si.getIntent().getAction());
assertEquals("val", si.getIntent().getStringExtra("key"));
assertEquals(123, si.getWeight());
assertEquals(1, si.getExtras().getInt("k"));
assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
assertEquals(null, si.getBitmapPath());
assertEquals(0, si.getIconResourceId());
si = sorig.clone(ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER);
assertEquals(getContext().getPackageName(), si.getPackageName());
assertEquals("id", si.getId());
assertEquals(new ComponentName("a", "b"), si.getActivityComponent());
assertEquals(null, si.getIcon());
assertEquals("title", si.getTitle());
assertEquals("text", si.getText());
assertEquals(null, si.getIntent());
assertEquals(123, si.getWeight());
assertEquals(1, si.getExtras().getInt("k"));
assertEquals(ShortcutInfo.FLAG_PINNED, si.getFlags());
assertEquals(null, si.getBitmapPath());
assertEquals(0, si.getIconResourceId());
si = sorig.clone(ShortcutInfo.CLONE_REMOVE_NON_KEY_INFO);
assertEquals(getContext().getPackageName(), si.getPackageName());
assertEquals("id", si.getId());
assertEquals(null, si.getActivityComponent());
assertEquals(null, si.getIcon());
assertEquals(null, si.getTitle());
assertEquals(null, si.getText());
assertEquals(null, si.getIntent());
assertEquals(0, si.getWeight());
assertEquals(null, si.getExtras());
assertEquals(ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_KEY_FIELDS_ONLY, si.getFlags());
assertEquals(null, si.getBitmapPath());
assertEquals(0, si.getIconResourceId());
}
public void testCopyNonNullFieldsFrom() {
PersistableBundle pb = new PersistableBundle();
pb.putInt("k", 1);
ShortcutInfo sorig = new ShortcutInfo.Builder(getContext())
.setId("id")
.setActivityComponent(new ComponentName("a", "b"))
.setIcon(Icon.createWithContentUri("content://a.b.c/"))
.setTitle("title")
.setText("text")
.setIntent(makeIntent("action", "key", "val"))
.setWeight(123)
.setExtras(pb)
.build();
sorig.addFlags(ShortcutInfo.FLAG_PINNED);
sorig.setBitmapPath("abc");
sorig.setIconResourceId(456);
ShortcutInfo si;
si = sorig.clone(/* flags=*/ 0);
si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
.setActivityComponent(new ComponentName("x", "y")).build());
assertEquals(new ComponentName("x", "y"), si.getActivityComponent());
si = sorig.clone(/* flags=*/ 0);
si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
.setIcon(Icon.createWithContentUri("content://x.y.z/")).build());
assertEquals("content://x.y.z/", si.getIcon().getUriString());
si = sorig.clone(/* flags=*/ 0);
si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
.setTitle("xyz").build());
assertEquals("xyz", si.getTitle());
si = sorig.clone(/* flags=*/ 0);
si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
.setText("xxx").build());
assertEquals("xxx", si.getText());
si = sorig.clone(/* flags=*/ 0);
si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
.setIntent(makeIntent("action2")).build());
assertEquals("action2", si.getIntent().getAction());
assertEquals(null, si.getIntent().getStringExtra("key"));
si = sorig.clone(/* flags=*/ 0);
si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
.setIntent(makeIntent("action3", "key", "x")).build());
assertEquals("action3", si.getIntent().getAction());
assertEquals("x", si.getIntent().getStringExtra("key"));
si = sorig.clone(/* flags=*/ 0);
si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
.setWeight(999).build());
assertEquals(999, si.getWeight());
PersistableBundle pb2 = new PersistableBundle();
pb2.putInt("x", 99);
si = sorig.clone(/* flags=*/ 0);
si.copyNonNullFieldsFrom(new ShortcutInfo.Builder(getContext()).setId("id")
.setExtras(pb2).build());
assertEquals(99, si.getExtras().getInt("x"));
}
}