Merge "ShortcutManager: implement backup & restore" into nyc-dev
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user