Clean up dangling bitmaps.

Bug 28471799

Change-Id: I48785ccc533a7499d46e35264c6a0cbf0c5398d5
This commit is contained in:
Makoto Onuki
2016-05-02 15:19:32 -07:00
parent 553aae2501
commit 6c1dbd577b
4 changed files with 302 additions and 8 deletions

View File

@@ -451,6 +451,30 @@ class ShortcutPackage extends ShortcutPackageItem {
mApiCallCount = 0;
}
/**
* Return the filenames (excluding path names) of icon bitmap files from this package.
*/
public ArraySet<String> getUsedBitmapFiles() {
final ArraySet<String> usedFiles = new ArraySet<>(mShortcuts.size());
for (int i = mShortcuts.size() - 1; i >= 0; i--) {
final ShortcutInfo si = mShortcuts.valueAt(i);
if (si.getBitmapPath() != null) {
usedFiles.add(getFileName(si.getBitmapPath()));
}
}
return usedFiles;
}
private static String getFileName(@NonNull String path) {
final int sep = path.lastIndexOf(File.separatorChar);
if (sep == -1) {
return path;
} else {
return path.substring(sep + 1);
}
}
/**
* Called when the package is updated. If there are shortcuts with resource icons, update
* their timestamps.

View File

@@ -291,8 +291,9 @@ public class ShortcutService extends IShortcutService.Stub {
int GET_PACKAGE_INFO_WITH_SIG = 2;
int GET_APPLICATION_INFO = 3;
int LAUNCHER_PERMISSION_CHECK = 4;
int CLEANUP_DANGLING_BITMAPS = 5;
int COUNT = LAUNCHER_PERMISSION_CHECK + 1;
int COUNT = CLEANUP_DANGLING_BITMAPS + 1;
}
final Object mStatLock = new Object();
@@ -328,7 +329,7 @@ public class ShortcutService extends IShortcutService.Stub {
void logDurationStat(int statId, long start) {
synchronized (mStatLock) {
mCountStats[statId]++;
mDurationStats[statId] += (System.currentTimeMillis() - start);
mDurationStats[statId] += (injectElapsedRealtime() - start);
}
}
@@ -804,7 +805,9 @@ public class ShortcutService extends IShortcutService.Stub {
return null;
}
try {
return loadUserInternal(userId, in, /* forBackup= */ false);
final ShortcutUser ret = loadUserInternal(userId, in, /* forBackup= */ false);
cleanupDanglingBitmapDirectoriesLocked(userId, ret);
return ret;
} catch (IOException|XmlPullParserException e) {
Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
return null;
@@ -1001,6 +1004,57 @@ public class ShortcutService extends IShortcutService.Stub {
}
}
private void cleanupDanglingBitmapDirectoriesLocked(
@UserIdInt int userId, @NonNull ShortcutUser user) {
if (DEBUG) {
Slog.d(TAG, "cleanupDanglingBitmaps: userId=" + userId);
}
final long start = injectElapsedRealtime();
final File bitmapDir = getUserBitmapFilePath(userId);
final File[] children = bitmapDir.listFiles();
if (children == null) {
return;
}
for (File child : children) {
if (!child.isDirectory()) {
continue;
}
final String packageName = child.getName();
if (DEBUG) {
Slog.d(TAG, "cleanupDanglingBitmaps: Found directory=" + packageName);
}
if (!user.hasPackage(packageName)) {
if (DEBUG) {
Slog.d(TAG, "Removing dangling bitmap directory: " + packageName);
}
cleanupBitmapsForPackage(userId, packageName);
} else {
cleanupDanglingBitmapFilesLocked(userId, user, packageName, child);
}
}
logDurationStat(Stats.CLEANUP_DANGLING_BITMAPS, start);
}
private void cleanupDanglingBitmapFilesLocked(@UserIdInt int userId, @NonNull ShortcutUser user,
@NonNull String packageName, @NonNull File path) {
final ArraySet<String> usedFiles =
user.getPackageShortcuts(this, packageName).getUsedBitmapFiles();
for (File child : path.listFiles()) {
if (!child.isFile()) {
continue;
}
final String name = child.getName();
if (!usedFiles.contains(name)) {
if (DEBUG) {
Slog.d(TAG, "Removing dangling bitmap file: " + child.getAbsolutePath());
}
child.delete();
}
}
}
@VisibleForTesting
static class FileOutputStreamWithPath extends FileOutputStream {
private final File mFile;
@@ -1596,14 +1650,14 @@ public class ShortcutService extends IShortcutService.Stub {
@VisibleForTesting
boolean hasShortcutHostPermissionInner(@NonNull String callingPackage, int userId) {
synchronized (mLock) {
final long start = System.currentTimeMillis();
final long start = injectElapsedRealtime();
final ShortcutUser user = getUserShortcutsLocked(userId);
final List<ResolveInfo> allHomeCandidates = new ArrayList<>();
// Default launcher from package manager.
final long startGetHomeActivitiesAsUser = System.currentTimeMillis();
final long startGetHomeActivitiesAsUser = injectElapsedRealtime();
final ComponentName defaultLauncher = injectPackageManagerInternal()
.getHomeActivitiesAsUser(allHomeCandidates, userId);
logDurationStat(Stats.GET_DEFAULT_HOME, startGetHomeActivitiesAsUser);
@@ -2075,7 +2129,7 @@ public class ShortcutService extends IShortcutService.Stub {
@VisibleForTesting
PackageInfo injectPackageInfo(String packageName, @UserIdInt int userId,
boolean getSignatures) {
final long start = System.currentTimeMillis();
final long start = injectElapsedRealtime();
final long token = injectClearCallingIdentity();
try {
return mIPackageManager.getPackageInfo(packageName, PACKAGE_MATCH_FLAGS
@@ -2096,7 +2150,7 @@ public class ShortcutService extends IShortcutService.Stub {
@VisibleForTesting
ApplicationInfo injectApplicationInfo(String packageName, @UserIdInt int userId) {
final long start = System.currentTimeMillis();
final long start = injectElapsedRealtime();
final long token = injectClearCallingIdentity();
try {
return mIPackageManager.getApplicationInfo(packageName, PACKAGE_MATCH_FLAGS, userId);
@@ -2269,6 +2323,8 @@ public class ShortcutService extends IShortcutService.Stub {
dumpStatLS(pw, p, Stats.GET_PACKAGE_INFO, "getPackageInfo()");
dumpStatLS(pw, p, Stats.GET_PACKAGE_INFO_WITH_SIG, "getPackageInfo(SIG)");
dumpStatLS(pw, p, Stats.GET_APPLICATION_INFO, "getApplicationInfo");
dumpStatLS(pw, p, Stats.CLEANUP_DANGLING_BITMAPS, "cleanupDanglingBitmaps");
}
for (int i = 0; i < mUsers.size(); i++) {

View File

@@ -116,6 +116,10 @@ class ShortcutUser {
return mPackages;
}
public boolean hasPackage(@NonNull String packageName) {
return mPackages.containsKey(packageName);
}
public ShortcutPackage removePackage(@NonNull ShortcutService s, @NonNull String packageName) {
final ShortcutPackage removed = mPackages.remove(packageName);

View File

@@ -156,6 +156,8 @@ public class ShortcutManagerTest extends InstrumentationTestCase {
private static final boolean DUMP_IN_TEARDOWN = false; // DO NOT SUBMIT WITH true
private static final String[] EMPTY_STRINGS = new String[0]; // Just for readability.
// public for mockito
public class BaseContext extends MockContext {
@Override
@@ -1047,6 +1049,49 @@ public class ShortcutManagerTest extends InstrumentationTestCase {
}
}
private void assertBitmapDirectories(int userId, String... expectedDirectories) {
final Set<String> expected = hashSet(set(expectedDirectories));
final Set<String> actual = new HashSet<>();
final File[] files = mService.getUserBitmapFilePath(userId).listFiles();
if (files != null) {
for (File child : files) {
if (child.isDirectory()) {
actual.add(child.getName());
}
}
}
assertEquals(expected, actual);
}
private void assertBitmapFiles(int userId, String packageName, String... expectedFiles) {
final Set<String> expected = hashSet(set(expectedFiles));
final Set<String> actual = new HashSet<>();
final File[] files = new File(mService.getUserBitmapFilePath(userId), packageName)
.listFiles();
if (files != null) {
for (File child : files) {
if (child.isFile()) {
actual.add(child.getName());
}
}
}
assertEquals(expected, actual);
}
private String getBitmapFilename(int userId, String packageName, String shortcutId) {
final ShortcutInfo si = mService.getPackageShortcutForTest(packageName, shortcutId, userId);
if (si == null) {
return null;
}
return new File(si.getBitmapPath()).getName();
}
private ShortcutInfo getPackageShortcut(String packageName, String shortcutId) {
return getPackageShortcut(packageName, shortcutId, getCallingUserId());
}
@@ -1732,8 +1777,173 @@ public class ShortcutManagerTest extends InstrumentationTestCase {
bmp = pfdToBitmap(
mLauncherApps.getShortcutIconFd(CALLING_PACKAGE_1, "bmp32x32", HANDLE_USER_P0));
assertBitmapSize(128, 128, bmp);
}
// TODO Test the content URI case too.
private File makeFile(File baseDirectory, String... paths) {
File ret = baseDirectory;
for (String path : paths) {
ret = new File(ret, path);
}
return ret;
}
public void testCleanupDanglingBitmaps() throws Exception {
assertBitmapDirectories(USER_0, EMPTY_STRINGS);
assertBitmapDirectories(USER_10, EMPTY_STRINGS);
// Make some shortcuts with bitmap icons.
final Icon bmp32x32 = Icon.createWithBitmap(BitmapFactory.decodeResource(
getTestContext().getResources(), R.drawable.black_32x32));
runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
mManager.setDynamicShortcuts(list(
makeShortcutWithIcon("s1", bmp32x32),
makeShortcutWithIcon("s2", bmp32x32),
makeShortcutWithIcon("s3", bmp32x32)
));
});
// Increment the time (which actually we don't have to), which is used for filenames.
mInjectedCurrentTimeLillis++;
runWithCaller(CALLING_PACKAGE_2, USER_0, () -> {
mManager.setDynamicShortcuts(list(
makeShortcutWithIcon("s4", bmp32x32),
makeShortcutWithIcon("s5", bmp32x32),
makeShortcutWithIcon("s6", bmp32x32)
));
});
// Increment the time, which is used for filenames.
mInjectedCurrentTimeLillis++;
runWithCaller(CALLING_PACKAGE_3, USER_0, () -> {
mManager.setDynamicShortcuts(list(
));
});
// For USER-10, let's try without updating the times.
runWithCaller(CALLING_PACKAGE_1, USER_10, () -> {
mManager.setDynamicShortcuts(list(
makeShortcutWithIcon("10s1", bmp32x32),
makeShortcutWithIcon("10s2", bmp32x32),
makeShortcutWithIcon("10s3", bmp32x32)
));
});
runWithCaller(CALLING_PACKAGE_2, USER_10, () -> {
mManager.setDynamicShortcuts(list(
makeShortcutWithIcon("10s4", bmp32x32),
makeShortcutWithIcon("10s5", bmp32x32),
makeShortcutWithIcon("10s6", bmp32x32)
));
});
runWithCaller(CALLING_PACKAGE_3, USER_10, () -> {
mManager.setDynamicShortcuts(list(
));
});
dumpsysOnLogcat();
// Check files and directories.
// Package 3 has no bitmaps, so we don't create a directory.
assertBitmapDirectories(USER_0, CALLING_PACKAGE_1, CALLING_PACKAGE_2);
assertBitmapDirectories(USER_10, CALLING_PACKAGE_1, CALLING_PACKAGE_2);
assertBitmapFiles(USER_0, CALLING_PACKAGE_1,
getBitmapFilename(USER_0, CALLING_PACKAGE_1, "s1"),
getBitmapFilename(USER_0, CALLING_PACKAGE_1, "s2"),
getBitmapFilename(USER_0, CALLING_PACKAGE_1, "s3")
);
assertBitmapFiles(USER_0, CALLING_PACKAGE_2,
getBitmapFilename(USER_0, CALLING_PACKAGE_2, "s4"),
getBitmapFilename(USER_0, CALLING_PACKAGE_2, "s5"),
getBitmapFilename(USER_0, CALLING_PACKAGE_2, "s6")
);
assertBitmapFiles(USER_0, CALLING_PACKAGE_3,
EMPTY_STRINGS
);
assertBitmapFiles(USER_10, CALLING_PACKAGE_1,
getBitmapFilename(USER_10, CALLING_PACKAGE_1, "10s1"),
getBitmapFilename(USER_10, CALLING_PACKAGE_1, "10s2"),
getBitmapFilename(USER_10, CALLING_PACKAGE_1, "10s3")
);
assertBitmapFiles(USER_10, CALLING_PACKAGE_2,
getBitmapFilename(USER_10, CALLING_PACKAGE_2, "10s4"),
getBitmapFilename(USER_10, CALLING_PACKAGE_2, "10s5"),
getBitmapFilename(USER_10, CALLING_PACKAGE_2, "10s6")
);
assertBitmapFiles(USER_10, CALLING_PACKAGE_3,
EMPTY_STRINGS
);
// Then create random directories and files.
makeFile(mService.getUserBitmapFilePath(USER_0), "a.b.c").mkdir();
makeFile(mService.getUserBitmapFilePath(USER_0), "d.e.f").mkdir();
makeFile(mService.getUserBitmapFilePath(USER_0), "d.e.f", "123").createNewFile();
makeFile(mService.getUserBitmapFilePath(USER_0), "d.e.f", "456").createNewFile();
makeFile(mService.getUserBitmapFilePath(USER_0), CALLING_PACKAGE_3).mkdir();
makeFile(mService.getUserBitmapFilePath(USER_0), CALLING_PACKAGE_1, "1").createNewFile();
makeFile(mService.getUserBitmapFilePath(USER_0), CALLING_PACKAGE_1, "2").createNewFile();
makeFile(mService.getUserBitmapFilePath(USER_0), CALLING_PACKAGE_1, "3").createNewFile();
makeFile(mService.getUserBitmapFilePath(USER_0), CALLING_PACKAGE_1, "4").createNewFile();
makeFile(mService.getUserBitmapFilePath(USER_10), "10a.b.c").mkdir();
makeFile(mService.getUserBitmapFilePath(USER_10), "10d.e.f").mkdir();
makeFile(mService.getUserBitmapFilePath(USER_10), "10d.e.f", "123").createNewFile();
makeFile(mService.getUserBitmapFilePath(USER_10), "10d.e.f", "456").createNewFile();
makeFile(mService.getUserBitmapFilePath(USER_10), CALLING_PACKAGE_2, "1").createNewFile();
makeFile(mService.getUserBitmapFilePath(USER_10), CALLING_PACKAGE_2, "2").createNewFile();
makeFile(mService.getUserBitmapFilePath(USER_10), CALLING_PACKAGE_2, "3").createNewFile();
makeFile(mService.getUserBitmapFilePath(USER_10), CALLING_PACKAGE_2, "4").createNewFile();
assertBitmapDirectories(USER_0, CALLING_PACKAGE_1, CALLING_PACKAGE_2, CALLING_PACKAGE_3,
"a.b.c", "d.e.f");
// Save and load. When a user is loaded, we do the cleanup.
mService.saveDirtyInfo();
initService();
mService.handleUnlockUser(USER_0);
mService.handleUnlockUser(USER_10);
mService.handleUnlockUser(20); // Make sure the logic will still work for nonexistent user.
// The below check is the same as above, except this time USER_0 use the CALLING_PACKAGE_3
// directory.
assertBitmapDirectories(USER_0, CALLING_PACKAGE_1, CALLING_PACKAGE_2, CALLING_PACKAGE_3);
assertBitmapDirectories(USER_10, CALLING_PACKAGE_1, CALLING_PACKAGE_2);
assertBitmapFiles(USER_0, CALLING_PACKAGE_1,
getBitmapFilename(USER_0, CALLING_PACKAGE_1, "s1"),
getBitmapFilename(USER_0, CALLING_PACKAGE_1, "s2"),
getBitmapFilename(USER_0, CALLING_PACKAGE_1, "s3")
);
assertBitmapFiles(USER_0, CALLING_PACKAGE_2,
getBitmapFilename(USER_0, CALLING_PACKAGE_2, "s4"),
getBitmapFilename(USER_0, CALLING_PACKAGE_2, "s5"),
getBitmapFilename(USER_0, CALLING_PACKAGE_2, "s6")
);
assertBitmapFiles(USER_0, CALLING_PACKAGE_3,
EMPTY_STRINGS
);
assertBitmapFiles(USER_10, CALLING_PACKAGE_1,
getBitmapFilename(USER_10, CALLING_PACKAGE_1, "10s1"),
getBitmapFilename(USER_10, CALLING_PACKAGE_1, "10s2"),
getBitmapFilename(USER_10, CALLING_PACKAGE_1, "10s3")
);
assertBitmapFiles(USER_10, CALLING_PACKAGE_2,
getBitmapFilename(USER_10, CALLING_PACKAGE_2, "10s4"),
getBitmapFilename(USER_10, CALLING_PACKAGE_2, "10s5"),
getBitmapFilename(USER_10, CALLING_PACKAGE_2, "10s6")
);
assertBitmapFiles(USER_10, CALLING_PACKAGE_3,
EMPTY_STRINGS
);
}
private void checkShrinkBitmap(int expectedWidth, int expectedHeight, int resId, int maxSize) {