Merge "When app is updated, save the new version code, and update shortcuts with resource based icons." into nyc-dev

This commit is contained in:
Makoto Onuki
2016-04-26 00:07:57 +00:00
committed by Android (Google) Code Review
6 changed files with 337 additions and 25 deletions

View File

@@ -109,6 +109,27 @@ class ShortcutPackage extends ShortcutPackageItem {
return getPackageUserId();
}
/**
* Called when a shortcut is about to be published. At this point we know the publisher package
* exists (as opposed to Launcher trying to fetch shortcuts from a non-existent package), so
* we do some initialization for the package.
*/
private void onShortcutPublish(ShortcutService s) {
// Make sure we have the version code for the app. We need the version code in
// handlePackageUpdated().
if (getPackageInfo().getVersionCode() < 0) {
final int versionCode = s.getApplicationVersionCode(getPackageName(), getOwnerUserId());
if (ShortcutService.DEBUG) {
Slog.d(TAG, String.format("Package %s version = %d", getPackageName(),
versionCode));
}
if (versionCode >= 0) {
getPackageInfo().setVersionCode(versionCode);
s.scheduleSaveUser(getOwnerUserId());
}
}
}
@Override
protected void onRestoreBlocked(ShortcutService s) {
// Can't restore due to version/signature mismatch. Remove all shortcuts.
@@ -153,6 +174,9 @@ class ShortcutPackage extends ShortcutPackageItem {
*/
public void addDynamicShortcut(@NonNull ShortcutService s,
@NonNull ShortcutInfo newShortcut) {
onShortcutPublish(s);
newShortcut.addFlags(ShortcutInfo.FLAG_DYNAMIC);
final ShortcutInfo oldShortcut = mShortcuts.get(newShortcut.getId());
@@ -387,6 +411,40 @@ class ShortcutPackage extends ShortcutPackageItem {
mApiCallCount = 0;
}
/**
* Called when the package is updated. If there are shortcuts with resource icons, update
* their timestamps.
*/
public void handlePackageUpdated(ShortcutService s, int newVersionCode) {
if (getPackageInfo().getVersionCode() >= newVersionCode) {
// Version hasn't changed; nothing to do.
return;
}
if (ShortcutService.DEBUG) {
Slog.d(TAG, String.format("Package %s updated, version %d -> %d", getPackageName(),
getPackageInfo().getVersionCode(), newVersionCode));
}
getPackageInfo().setVersionCode(newVersionCode);
boolean changed = false;
for (int i = mShortcuts.size() - 1; i >= 0; i--) {
final ShortcutInfo si = mShortcuts.valueAt(i);
if (si.hasIconResource()) {
changed = true;
si.setTimestamp(s.injectCurrentTimeMillis());
}
}
if (changed) {
// This will send a notification to the launcher, and also save .
s.packageShortcutsChanged(getPackageName(), getPackageUserId());
} else {
// Still save the version code.
s.scheduleSaveUser(getPackageUserId());
}
}
public void dump(@NonNull ShortcutService s, @NonNull PrintWriter pw, @NonNull String prefix) {
pw.println();
@@ -413,17 +471,20 @@ class ShortcutPackage extends ShortcutPackageItem {
getPackageInfo().dump(s, pw, prefix + " ");
pw.println();
pw.println(" Shortcuts:");
pw.print(prefix);
pw.println(" Shortcuts:");
long totalBitmapSize = 0;
final ArrayMap<String, ShortcutInfo> shortcuts = mShortcuts;
final int size = shortcuts.size();
for (int i = 0; i < size; i++) {
final ShortcutInfo si = shortcuts.valueAt(i);
pw.print(" ");
pw.print(prefix);
pw.print(" ");
pw.println(si.toInsecureString());
if (si.getBitmapPath() != null) {
final long len = new File(si.getBitmapPath()).length();
pw.print(" ");
pw.print(prefix);
pw.print(" ");
pw.print("bitmap size=");
pw.println(len);

View File

@@ -45,12 +45,14 @@ class ShortcutPackageInfo {
private static final String TAG_SIGNATURE = "signature";
private static final String ATTR_SIGNATURE_HASH = "hash";
private static final int VERSION_UNKNOWN = -1;
/**
* When true, this package information was restored from the previous device, and the app hasn't
* been installed yet.
*/
private boolean mIsShadow;
private int mVersionCode;
private int mVersionCode = VERSION_UNKNOWN;
private ArrayList<byte[]> mSigHashes;
private ShortcutPackageInfo(int versionCode, ArrayList<byte[]> sigHashes, boolean isShadow) {
@@ -60,7 +62,7 @@ class ShortcutPackageInfo {
}
public static ShortcutPackageInfo newEmpty() {
return new ShortcutPackageInfo(0, new ArrayList<>(0), /* isShadow */ false);
return new ShortcutPackageInfo(VERSION_UNKNOWN, new ArrayList<>(0), /* isShadow */ false);
}
public boolean isShadow() {
@@ -75,6 +77,10 @@ class ShortcutPackageInfo {
return mVersionCode;
}
public void setVersionCode(int versionCode) {
mVersionCode = versionCode;
}
public boolean hasSignatures() {
return mSigHashes.size() > 0;
}

View File

@@ -356,7 +356,7 @@ public class ShortcutService extends IShortcutService.Stub {
// Preload
getUserShortcutsLocked(userId);
cleanupGonePackages(userId);
checkPackageChanges(userId);
}
}
@@ -1158,7 +1158,11 @@ public class ShortcutService extends IShortcutService.Stub {
* - Sends a notification to LauncherApps
* - Write to file
*/
private void userPackageChanged(@NonNull String packageName, @UserIdInt int userId) {
void packageShortcutsChanged(@NonNull String packageName, @UserIdInt int userId) {
if (DEBUG) {
Slog.d(TAG, String.format(
"Shortcut changes: package=%s, user=%d", packageName, userId));
}
notifyListeners(packageName, userId);
scheduleSaveUser(userId);
}
@@ -1284,7 +1288,7 @@ public class ShortcutService extends IShortcutService.Stub {
ps.addDynamicShortcut(this, newShortcut);
}
}
userPackageChanged(packageName, userId);
packageShortcutsChanged(packageName, userId);
return true;
}
@@ -1323,7 +1327,7 @@ public class ShortcutService extends IShortcutService.Stub {
}
}
}
userPackageChanged(packageName, userId);
packageShortcutsChanged(packageName, userId);
return true;
}
@@ -1353,7 +1357,7 @@ public class ShortcutService extends IShortcutService.Stub {
ps.addDynamicShortcut(this, newShortcut);
}
}
userPackageChanged(packageName, userId);
packageShortcutsChanged(packageName, userId);
return true;
}
@@ -1370,7 +1374,7 @@ public class ShortcutService extends IShortcutService.Stub {
Preconditions.checkStringNotEmpty((String) shortcutIds.get(i)));
}
}
userPackageChanged(packageName, userId);
packageShortcutsChanged(packageName, userId);
}
@Override
@@ -1380,7 +1384,7 @@ public class ShortcutService extends IShortcutService.Stub {
synchronized (mLock) {
getPackageShortcutsLocked(packageName, userId).deleteAllDynamicShortcuts(this);
}
userPackageChanged(packageName, userId);
packageShortcutsChanged(packageName, userId);
}
@Override
@@ -1729,7 +1733,7 @@ public class ShortcutService extends IShortcutService.Stub {
launcher.pinShortcuts(
ShortcutService.this, userId, packageName, shortcutIds);
}
userPackageChanged(packageName, userId);
packageShortcutsChanged(packageName, userId);
}
@Override
@@ -1841,13 +1845,15 @@ public class ShortcutService extends IShortcutService.Stub {
};
/**
* Called when a user is unlocked. Check all known packages still exist, and otherwise
* perform cleanup.
* Called when a user is unlocked.
* - Check all known packages still exist, and otherwise perform cleanup.
* - If a package still exists, check the version code. If it's been updated, may need to
* update timestamps of its shortcuts.
*/
@VisibleForTesting
void cleanupGonePackages(@UserIdInt int ownerUserId) {
void checkPackageChanges(@UserIdInt int ownerUserId) {
if (DEBUG) {
Slog.d(TAG, "cleanupGonePackages() ownerUserId=" + ownerUserId);
Slog.d(TAG, "checkPackageChanges() ownerUserId=" + ownerUserId);
}
final ArrayList<PackageWithUser> gonePackages = new ArrayList<>();
@@ -1858,10 +1864,15 @@ public class ShortcutService extends IShortcutService.Stub {
if (spi.getPackageInfo().isShadow()) {
return; // Don't delete shadow information.
}
if (isPackageInstalled(spi.getPackageName(), spi.getPackageUserId())) {
return; // Package not gone.
final int versionCode = getApplicationVersionCode(
spi.getPackageName(), spi.getPackageUserId());
if (versionCode >= 0) {
// Package still installed, see if it's updated.
getUserShortcutsLocked(ownerUserId).handlePackageUpdated(
this, spi.getPackageName(), versionCode);
} else {
gonePackages.add(PackageWithUser.of(spi));
}
gonePackages.add(PackageWithUser.of(spi));
});
if (gonePackages.size() > 0) {
for (int i = gonePackages.size() - 1; i >= 0; i--) {
@@ -1890,6 +1901,12 @@ public class ShortcutService extends IShortcutService.Stub {
synchronized (mLock) {
forEachLoadedUserLocked(user ->
user.attemptToRestoreIfNeededAndSave(this, packageName, userId));
final int versionCode = getApplicationVersionCode(packageName, userId);
if (versionCode < 0) {
return; // shouldn't happen
}
getUserShortcutsLocked(userId).handlePackageUpdated(this, packageName, versionCode);
}
}
@@ -1978,6 +1995,17 @@ public class ShortcutService extends IShortcutService.Stub {
return isApplicationFlagSet(packageName, userId, ApplicationInfo.FLAG_INSTALLED);
}
/**
* @return the version code of the package, or -1 if the app is not installed.
*/
int getApplicationVersionCode(String packageName, int userId) {
final ApplicationInfo ai = injectApplicationInfo(packageName, userId);
if ((ai == null) || ((ai.flags & ApplicationInfo.FLAG_INSTALLED) == 0)) {
return -1;
}
return ai.versionCode;
}
// === Backup & restore ===
boolean shouldBackupApp(String packageName, int userId) {
@@ -2100,7 +2128,7 @@ public class ShortcutService extends IShortcutService.Stub {
pw.println(mResetInterval);
pw.print(" maxUpdatesPerInterval: ");
pw.println(mMaxUpdatesPerInterval);
pw.print(" maxDynamicShortcuts:");
pw.print(" maxDynamicShortcuts: ");
pw.println(mMaxDynamicShortcuts);
pw.println();

View File

@@ -176,6 +176,17 @@ class ShortcutUser {
});
}
/**
* Called when a package is updated.
*/
public void handlePackageUpdated(ShortcutService s, @NonNull String packageName,
int newVersionCode) {
if (!mPackages.containsKey(packageName)) {
return;
}
getPackageShortcuts(s, packageName).handlePackageUpdated(s, newVersionCode);
}
public void attemptToRestoreIfNeededAndSave(ShortcutService s, @NonNull String packageName,
@UserIdInt int packageUserId) {
forPackageItem(packageName, packageUserId, spi -> {

View File

@@ -36,6 +36,7 @@ import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertDynamicOnly;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertExpectException;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.assertShortcutIds;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.findShortcut;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.hashSet;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.list;
import static com.android.server.pm.shortcutmanagertest.ShortcutManagerTestUtils.makeBundle;
@@ -644,6 +645,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase {
pi.applicationInfo.flags = ApplicationInfo.FLAG_INSTALLED
| ApplicationInfo.FLAG_ALLOW_BACKUP;
pi.versionCode = version;
pi.applicationInfo.versionCode = version;
pi.signatures = genSignatures(signatures);
return pi;
@@ -657,6 +659,13 @@ public class ShortcutManagerTest extends InstrumentationTestCase {
c.accept(mInjectedPackages.get(packageName));
}
private void updatePackageVersion(String packageName, int increment) {
updatePackageInfo(packageName, pi -> {
pi.versionCode += increment;
pi.applicationInfo.versionCode += increment;
});
}
private void uninstallPackage(int userId, String packageName) {
if (ENABLE_DUMP) {
Log.i(TAG, "Unnstall package " + packageName + " / " + userId);
@@ -3839,7 +3848,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase {
// Start uninstalling.
uninstallPackage(USER_10, LAUNCHER_1);
mService.cleanupGonePackages(USER_10);
mService.checkPackageChanges(USER_10);
assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0));
assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0));
@@ -3859,7 +3868,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase {
// Uninstall.
uninstallPackage(USER_10, CALLING_PACKAGE_1);
mService.cleanupGonePackages(USER_10);
mService.checkPackageChanges(USER_10);
assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0));
assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0));
@@ -3878,7 +3887,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase {
assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
uninstallPackage(USER_P0, LAUNCHER_1);
mService.cleanupGonePackages(USER_0);
mService.checkPackageChanges(USER_0);
assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0));
assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0));
@@ -3896,7 +3905,7 @@ public class ShortcutManagerTest extends InstrumentationTestCase {
assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_10));
assertNull(getPackageShortcut(CALLING_PACKAGE_1, "s3", USER_10));
mService.cleanupGonePackages(USER_P0);
mService.checkPackageChanges(USER_P0);
assertDynamicAndPinned(getPackageShortcut(CALLING_PACKAGE_1, "s1", USER_0));
assertDynamicOnly(getPackageShortcut(CALLING_PACKAGE_1, "s2", USER_0));
@@ -4150,6 +4159,193 @@ public class ShortcutManagerTest extends InstrumentationTestCase {
assertFalse(bitmapDirectoryExists(CALLING_PACKAGE_3, USER_10));
}
public void testHandlePackageUpdate() throws Throwable {
// Set up shortcuts and launchers.
final Icon res32x32 = Icon.createWithResource(getTestContext(), R.drawable.black_32x32);
final Icon bmp32x32 = Icon.createWithBitmap(BitmapFactory.decodeResource(
getTestContext().getResources(), R.drawable.black_32x32));
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
assertTrue(mManager.setDynamicShortcuts(list(
makeShortcut("s1"),
makeShortcutWithIcon("s2", res32x32),
makeShortcutWithIcon("s3", res32x32),
makeShortcutWithIcon("s4", bmp32x32))));
});
runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
assertTrue(mManager.setDynamicShortcuts(list(
makeShortcut("s1"),
makeShortcutWithIcon("s2", bmp32x32))));
});
runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
assertTrue(mManager.setDynamicShortcuts(list(
makeShortcutWithIcon("s1", res32x32))));
});
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
assertTrue(mManager.setDynamicShortcuts(list(
makeShortcutWithIcon("s1", res32x32),
makeShortcutWithIcon("s2", res32x32))));
});
runWithCaller(CALLING_PACKAGE_2, USER_10, () -> {
assertTrue(mManager.setDynamicShortcuts(list(
makeShortcutWithIcon("s1", bmp32x32),
makeShortcutWithIcon("s2", bmp32x32))));
});
LauncherApps.Callback c0 = mock(LauncherApps.Callback.class);
LauncherApps.Callback c10 = mock(LauncherApps.Callback.class);
runWithCaller(LAUNCHER_1, USER_0, () -> {
mLauncherApps.registerCallback(c0, new Handler(Looper.getMainLooper()));
});
runWithCaller(LAUNCHER_1, USER_10, () -> {
mLauncherApps.registerCallback(c10, new Handler(Looper.getMainLooper()));
});
mInjectedCurrentTimeLillis = START_TIME + 100;
ArgumentCaptor<List> shortcuts;
// First, call the event without updating the versions.
reset(c0);
reset(c10);
mService.mPackageMonitor.onReceive(getTestContext(),
genPackageUpdateIntent(CALLING_PACKAGE_1, USER_0));
mService.mPackageMonitor.onReceive(getTestContext(),
genPackageUpdateIntent(CALLING_PACKAGE_1, USER_10));
waitOnMainThread();
// Version not changed, so no callback.
verify(c0, times(0)).onShortcutsChanged(
eq(CALLING_PACKAGE_1),
any(List.class),
any(UserHandle.class));
verify(c10, times(0)).onShortcutsChanged(
eq(CALLING_PACKAGE_1),
any(List.class),
any(UserHandle.class));
// Next, update the version info for package 1.
reset(c0);
reset(c10);
updatePackageVersion(CALLING_PACKAGE_1, 1);
// Then send the broadcast, to only user-0.
mService.mPackageMonitor.onReceive(getTestContext(),
genPackageUpdateIntent(CALLING_PACKAGE_1, USER_0));
waitOnMainThread();
// User-0 should get the notification.
shortcuts = ArgumentCaptor.forClass(List.class);
verify(c0).onShortcutsChanged(
eq(CALLING_PACKAGE_1),
shortcuts.capture(),
eq(HANDLE_USER_0));
// User-10 shouldn't yet get the notification.
verify(c10, times(0)).onShortcutsChanged(
eq(CALLING_PACKAGE_1),
any(List.class),
any(UserHandle.class));
assertShortcutIds(shortcuts.getValue(), "s1", "s2", "s3", "s4");
assertEquals(START_TIME,
findShortcut(shortcuts.getValue(), "s1").getLastChangedTimestamp());
assertEquals(START_TIME + 100,
findShortcut(shortcuts.getValue(), "s2").getLastChangedTimestamp());
assertEquals(START_TIME + 100,
findShortcut(shortcuts.getValue(), "s3").getLastChangedTimestamp());
assertEquals(START_TIME,
findShortcut(shortcuts.getValue(), "s4").getLastChangedTimestamp());
// Next, send unlock even on user-10. Now we scan packages on this user and send a
// notification to the launcher.
mInjectedCurrentTimeLillis = START_TIME + 200;
when(mMockUserManager.isUserRunning(eq(USER_10))).thenReturn(true);
reset(c0);
reset(c10);
mService.handleUnlockUser(USER_10);
shortcuts = ArgumentCaptor.forClass(List.class);
verify(c0, times(0)).onShortcutsChanged(
eq(CALLING_PACKAGE_1),
any(List.class),
any(UserHandle.class));
verify(c10).onShortcutsChanged(
eq(CALLING_PACKAGE_1),
shortcuts.capture(),
eq(HANDLE_USER_10));
assertShortcutIds(shortcuts.getValue(), "s1", "s2");
assertEquals(START_TIME + 200,
findShortcut(shortcuts.getValue(), "s1").getLastChangedTimestamp());
assertEquals(START_TIME + 200,
findShortcut(shortcuts.getValue(), "s2").getLastChangedTimestamp());
// Do the same thing for package 2, which doesn't have resource icons.
mInjectedCurrentTimeLillis = START_TIME + 300;
reset(c0);
reset(c10);
updatePackageVersion(CALLING_PACKAGE_2, 10);
// Then send the broadcast, to only user-0.
mService.mPackageMonitor.onReceive(getTestContext(),
genPackageUpdateIntent(CALLING_PACKAGE_2, USER_0));
mService.handleUnlockUser(USER_10);
waitOnMainThread();
verify(c0, times(0)).onShortcutsChanged(
eq(CALLING_PACKAGE_1),
any(List.class),
any(UserHandle.class));
verify(c10, times(0)).onShortcutsChanged(
eq(CALLING_PACKAGE_1),
any(List.class),
any(UserHandle.class));
// Do the same thing for package 3
mInjectedCurrentTimeLillis = START_TIME + 400;
reset(c0);
reset(c10);
updatePackageVersion(CALLING_PACKAGE_3, 100);
// Then send the broadcast, to only user-0.
mService.mPackageMonitor.onReceive(getTestContext(),
genPackageUpdateIntent(CALLING_PACKAGE_3, USER_0));
mService.handleUnlockUser(USER_10);
waitOnMainThread();
shortcuts = ArgumentCaptor.forClass(List.class);
verify(c0).onShortcutsChanged(
eq(CALLING_PACKAGE_3),
shortcuts.capture(),
eq(HANDLE_USER_0));
// User 10 doesn't have package 3, so no callback.
verify(c10, times(0)).onShortcutsChanged(
eq(CALLING_PACKAGE_3),
any(List.class),
any(UserHandle.class));
assertShortcutIds(shortcuts.getValue(), "s1");
assertEquals(START_TIME + 400,
findShortcut(shortcuts.getValue(), "s1").getLastChangedTimestamp());
}
private void backupAndRestore() {
int prevUid = mInjectedCallingUid;

View File

@@ -413,6 +413,16 @@ public class ShortcutManagerTestUtils {
}
}
public static ShortcutInfo findShortcut(List<ShortcutInfo> list, String id) {
for (ShortcutInfo si : list) {
if (si.getId().equals(id)) {
return si;
}
}
fail("Shortcut " + id + " not found in the list");
return null;
}
public static Bitmap pfdToBitmap(ParcelFileDescriptor pfd) {
assertNotNull(pfd);
try {