diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index b4cd714f12056..6faded53f55eb 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -144,6 +144,7 @@ import libcore.io.DropBox; import libcore.io.EventLogger; import libcore.io.IoUtils; import libcore.net.event.NetworkEventDispatcher; +import dalvik.system.BaseDexClassLoader; import dalvik.system.CloseGuard; import dalvik.system.VMDebug; import dalvik.system.VMRuntime; @@ -5348,6 +5349,16 @@ public final class ActivityThread { } } + // If we use profiles, setup the dex reporter to notify package manager + // of any relevant dex loads. The idle maintenance job will use the information + // reported to optimize the loaded dex files. + // Note that we only need one global reporter per app. + // Make sure we do this before calling onCreate so that we can capture the + // complete application startup. + if (SystemProperties.getBoolean("dalvik.vm.usejitprofiles", false)) { + BaseDexClassLoader.setReporter(DexLoadReporter.getInstance()); + } + // Install the Network Security Config Provider. This must happen before the application // code is loaded to prevent issues with instances of TLS objects being created before // the provider is installed. diff --git a/core/java/android/app/DexLoadReporter.java b/core/java/android/app/DexLoadReporter.java new file mode 100644 index 0000000000000..13f288ab74549 --- /dev/null +++ b/core/java/android/app/DexLoadReporter.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2017 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 android.app; + +import android.os.FileUtils; +import android.os.RemoteException; +import android.os.SystemProperties; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; + +import dalvik.system.BaseDexClassLoader; +import dalvik.system.VMRuntime; + +import java.io.File; +import java.io.IOException; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * A dex load reporter which will notify package manager of any dex file loaded + * with {@code BaseDexClassLoader}. + * The goals are: + * 1) discover secondary dex files so that they can be optimized during the + * idle maintenance job. + * 2) determine whether or not a dex file is used by an app which does not + * own it (in order to select the optimal compilation method). + * @hide + */ +/*package*/ class DexLoadReporter implements BaseDexClassLoader.Reporter { + private static final String TAG = "DexLoadReporter"; + + private static final DexLoadReporter INSTANCE = new DexLoadReporter(); + + private static final boolean DEBUG = false; + + // We must guard the access to the list of data directories because + // we might have concurrent accesses. Apps might load dex files while + // new data dirs are registered (due to creation of LoadedApks via + // create createApplicationContext). + @GuardedBy("mDataDirs") + private final Set mDataDirs; + + private DexLoadReporter() { + mDataDirs = new HashSet<>(); + } + + /*package*/ static DexLoadReporter getInstance() { + return INSTANCE; + } + + /** + * Register an application data directory with the reporter. + * The data directories are used to determine if a dex file is secondary dex or not. + * Note that this method may be called multiple times for the same app, registering + * different data directories. This may happen when apps share the same user id + * ({@code android:sharedUserId}). For example, if app1 and app2 share the same user + * id, and app1 loads app2 apk, then both data directories will be registered. + */ + /*package*/ void registerAppDataDir(String packageName, String dataDir) { + if (DEBUG) { + Slog.i(TAG, "Package " + packageName + " registering data dir: " + dataDir); + } + // TODO(calin): A few code paths imply that the data dir + // might be null. Investigate when that can happen. + if (dataDir != null) { + synchronized (mDataDirs) { + mDataDirs.add(dataDir); + } + } + } + + @Override + public void report(List dexPaths) { + if (dexPaths.isEmpty()) { + return; + } + // Notify the package manager about the dex loads unconditionally. + // The load might be for either a primary or secondary dex file. + notifyPackageManager(dexPaths); + // Check for secondary dex files and register them for profiling if + // possible. + registerSecondaryDexForProfiling(dexPaths); + } + + private void notifyPackageManager(List dexPaths) { + String packageName = ActivityThread.currentPackageName(); + try { + ActivityThread.getPackageManager().notifyDexLoad( + packageName, dexPaths, VMRuntime.getRuntime().vmInstructionSet()); + } catch (RemoteException re) { + Slog.e(TAG, "Failed to notify PM about dex load for package " + packageName, re); + } + } + + private void registerSecondaryDexForProfiling(List dexPaths) { + if (!SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false)) { + return; + } + // Make a copy of the current data directories so that we don't keep the lock + // while registering for profiling. The registration will perform I/O to + // check for or create the profile. + String[] dataDirs; + synchronized (mDataDirs) { + dataDirs = mDataDirs.toArray(new String[0]); + } + for (String dexPath : dexPaths) { + registerSecondaryDexForProfiling(dexPath, dataDirs); + } + } + + private void registerSecondaryDexForProfiling(String dexPath, String[] dataDirs) { + if (!isSecondaryDexFile(dexPath, dataDirs)) { + // The dex path is not a secondary dex file. Nothing to do. + return; + } + File secondaryProfile = getSecondaryProfileFile(dexPath); + try { + // Create the profile if not already there. + // Returns true if the file was created, false if the file already exists. + // or throws exceptions in case of errors. + boolean created = secondaryProfile.createNewFile(); + if (DEBUG && created) { + Slog.i(TAG, "Created profile for secondary dex: " + secondaryProfile); + } + } catch (IOException ex) { + Slog.e(TAG, "Failed to create profile for secondary dex " + secondaryProfile + + ":" + ex.getMessage()); + // Don't move forward with the registration if we failed to create the profile. + return; + } + + VMRuntime.registerAppInfo(secondaryProfile.getPath(), new String[] { dexPath }); + } + + // A dex file is a secondary dex file if it is in any of the registered app + // data directories. + private boolean isSecondaryDexFile(String dexPath, String[] dataDirs) { + for (String dataDir : dataDirs) { + if (FileUtils.contains(dataDir, dexPath)) { + return true; + } + } + return false; + } + + // Secondary dex profiles are stored next to the dex file and have the same + // name with '.prof' appended. + // NOTE: Keep in sync with installd. + private File getSecondaryProfileFile(String dexPath) { + return new File(dexPath + ".prof"); + } +} diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index c625756cac1cf..404b7db8fe327 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -51,7 +51,6 @@ import android.util.SparseArray; import android.view.Display; import android.view.DisplayAdjustments; -import dalvik.system.BaseDexClassLoader; import dalvik.system.VMRuntime; import java.io.File; @@ -610,39 +609,10 @@ public final class LoadedApk { VMRuntime.registerAppInfo(profileFile.getPath(), codePaths.toArray(new String[codePaths.size()])); - // Setup the reporter to notify package manager of any relevant dex loads. - // At this point the primary apk is loaded and will not be reported. - // Anything loaded from now on will be tracked as a potential secondary - // or foreign dex file. The goal is to enable: - // 1) monitoring and compilation of secondary dex file - // 2) track whether or not a dex file is used by other apps (used to - // determined the compilation filter of apks). - if (BaseDexClassLoader.getReporter() != DexLoadReporter.INSTANCE) { - // Set the dex load reporter if not already set. - // Note that during the app's life cycle different LoadedApks may be - // created and loaded (e.g. if two different apps share the same runtime). - BaseDexClassLoader.setReporter(DexLoadReporter.INSTANCE); - } - } - - private static class DexLoadReporter implements BaseDexClassLoader.Reporter { - private static final DexLoadReporter INSTANCE = new DexLoadReporter(); - - private DexLoadReporter() {} - - @Override - public void report(List dexPaths) { - if (dexPaths.isEmpty()) { - return; - } - String packageName = ActivityThread.currentPackageName(); - try { - ActivityThread.getPackageManager().notifyDexLoad( - packageName, dexPaths, VMRuntime.getRuntime().vmInstructionSet()); - } catch (RemoteException re) { - Slog.e(TAG, "Failed to notify PM about dex load for package " + packageName, re); - } - } + // Register the app data directory with the reporter. It will + // help deciding whether or not a dex file is the primary apk or a + // secondary dex. + DexLoadReporter.getInstance().registerAppDataDir(mPackageName, mDataDir); } /** diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java index 8e24caf478393..bd8af4deec9a6 100644 --- a/core/java/android/os/FileUtils.java +++ b/core/java/android/os/FileUtils.java @@ -428,14 +428,13 @@ public class FileUtils { */ public static boolean contains(File dir, File file) { if (dir == null || file == null) return false; + return contains(dir.getAbsolutePath(), file.getAbsolutePath()); + } - String dirPath = dir.getAbsolutePath(); - String filePath = file.getAbsolutePath(); - + public static boolean contains(String dirPath, String filePath) { if (dirPath.equals(filePath)) { return true; } - if (!dirPath.endsWith("/")) { dirPath += "/"; } diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java index 7aa96cfddc626..d364d17fa764d 100644 --- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java +++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java @@ -49,8 +49,6 @@ public class BackgroundDexOptService extends JobService { private static final boolean DEBUG = false; - private static final long RETRY_LATENCY = 4 * AlarmManager.INTERVAL_HOUR; - private static final int JOB_IDLE_OPTIMIZE = 800; private static final int JOB_POST_BOOT_UPDATE = 801; @@ -292,8 +290,8 @@ public class BackgroundDexOptService extends JobService { PackageManagerService.REASON_BACKGROUND_DEXOPT, /* force */ false) : pm.performDexOptSecondary(pkg, - PackageManagerServiceCompilerMapping.getFullCompilerFilter(), - /* force */ true); + PackageManagerService.REASON_BACKGROUND_DEXOPT, + /* force */ false); if (success) { // Dexopt succeeded, remove package from the list of failing ones. synchronized (failedPackageNames) { diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 4b064b8533404..b207b818f1c4c 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -7548,6 +7548,11 @@ public class PackageManagerService extends IPackageManager.Stub { return mDexManager.dexoptSecondaryDex(packageName, compilerFilter, force); } + public boolean performDexOptSecondary(String packageName, int compileReason, + boolean force) { + return mDexManager.dexoptSecondaryDex(packageName, compileReason, force); + } + /** * Reconcile the information we have about the secondary dex files belonging to * {@code packagName} and the actual dex files. For all dex files that were @@ -7770,6 +7775,7 @@ public class PackageManagerService extends IPackageManager.Stub { } catch (InstallerException e) { Slog.w(TAG, String.valueOf(e)); } + mDexManager.notifyPackageDataDestroyed(pkg.packageName, userId); } } @@ -14499,6 +14505,8 @@ public class PackageManagerService extends IPackageManager.Stub { } prepareAppDataAfterInstallLIF(newPackage); addedPkg = true; + mDexManager.notifyPackageUpdated(newPackage.packageName, + newPackage.baseCodePath, newPackage.splitCodePaths); } catch (PackageManagerException e) { res.setError("Package couldn't be installed in " + pkg.codePath, e); } @@ -14646,6 +14654,9 @@ public class PackageManagerService extends IPackageManager.Stub { updateSettingsLI(newPackage, installerPackageName, allUsers, res, user); prepareAppDataAfterInstallLIF(newPackage); + + mDexManager.notifyPackageUpdated(newPackage.packageName, + newPackage.baseCodePath, newPackage.splitCodePaths); } } catch (PackageManagerException e) { res.setReturnCode(INSTALL_FAILED_INTERNAL_ERROR); diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java index 01124e2ee8358..a904d178b03d6 100644 --- a/services/core/java/com/android/server/pm/dex/DexManager.java +++ b/services/core/java/com/android/server/pm/dex/DexManager.java @@ -22,6 +22,7 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageParser; import android.os.RemoteException; import android.os.storage.StorageManager; +import android.os.UserHandle; import android.util.Slog; @@ -179,17 +180,64 @@ public class DexManager { } } - public void notifyPackageInstalled(PackageInfo info, int userId) { - cachePackageCodeLocation(info, userId); + /** + * Notifies that a new package was installed for {@code userId}. + * {@code userId} must not be {@code UserHandle.USER_ALL}. + * + * @throws IllegalArgumentException if {@code userId} is {@code UserHandle.USER_ALL}. + */ + public void notifyPackageInstalled(PackageInfo pi, int userId) { + if (userId == UserHandle.USER_ALL) { + throw new IllegalArgumentException( + "notifyPackageInstalled called with USER_ALL"); + } + cachePackageCodeLocation(pi.packageName, pi.applicationInfo.sourceDir, + pi.applicationInfo.splitSourceDirs, pi.applicationInfo.dataDir, userId); } - private void cachePackageCodeLocation(PackageInfo info, int userId) { - PackageCodeLocations pcl = mPackageCodeLocationsCache.get(info.packageName); - if (pcl != null) { - pcl.mergeAppDataDirs(info.applicationInfo, userId); - } else { - mPackageCodeLocationsCache.put(info.packageName, - new PackageCodeLocations(info.applicationInfo, userId)); + /** + * Notifies that package {@code packageName} was updated. + * This will clear the UsedByOtherApps mark if it exists. + */ + public void notifyPackageUpdated(String packageName, String baseCodePath, + String[] splitCodePaths) { + cachePackageCodeLocation(packageName, baseCodePath, splitCodePaths, null, /*userId*/ -1); + // In case there was an update, write the package use info to disk async. + // Note that we do the writing here and not in PackageDexUsage in order to be + // consistent with other methods in DexManager (e.g. reconcileSecondaryDexFiles performs + // multiple updates in PackaeDexUsage before writing it). + if (mPackageDexUsage.clearUsedByOtherApps(packageName)) { + mPackageDexUsage.maybeWriteAsync(); + } + } + + /** + * Notifies that the user {@code userId} data for package {@code packageName} + * was destroyed. This will remove all usage info associated with the package + * for the given user. + * {@code userId} is allowed to be {@code UserHandle.USER_ALL} in which case + * all usage information for the package will be removed. + */ + public void notifyPackageDataDestroyed(String packageName, int userId) { + boolean updated = userId == UserHandle.USER_ALL + ? mPackageDexUsage.removePackage(packageName) + : mPackageDexUsage.removeUserPackage(packageName, userId); + // In case there was an update, write the package use info to disk async. + // Note that we do the writing here and not in PackageDexUsage in order to be + // consistent with other methods in DexManager (e.g. reconcileSecondaryDexFiles performs + // multiple updates in PackaeDexUsage before writing it). + if (updated) { + mPackageDexUsage.maybeWriteAsync(); + } + } + + public void cachePackageCodeLocation(String packageName, String baseCodePath, + String[] splitCodePaths, String dataDir, int userId) { + PackageCodeLocations pcl = putIfAbsent(mPackageCodeLocationsCache, packageName, + new PackageCodeLocations(packageName, baseCodePath, splitCodePaths)); + pcl.updateCodeLocation(baseCodePath, splitCodePaths); + if (dataDir != null) { + pcl.mergeAppDataDirs(dataDir, userId); } } @@ -202,7 +250,8 @@ public class DexManager { int userId = entry.getKey(); for (PackageInfo pi : packageInfoList) { // Cache the code locations. - cachePackageCodeLocation(pi, userId); + cachePackageCodeLocation(pi.packageName, pi.applicationInfo.sourceDir, + pi.applicationInfo.splitSourceDirs, pi.applicationInfo.dataDir, userId); // Cache a map from package name to the set of user ids who installed the package. // We will use it to sync the data and remove obsolete entries from @@ -225,6 +274,17 @@ public class DexManager { return mPackageDexUsage.getPackageUseInfo(packageName); } + /** + * Perform dexopt on the package {@code packageName} secondary dex files. + * @return true if all secondary dex files were processed successfully (compiled or skipped + * because they don't need to be compiled).. + */ + public boolean dexoptSecondaryDex(String packageName, int compilerReason, boolean force) { + return dexoptSecondaryDex(packageName, + PackageManagerServiceCompilerMapping.getCompilerFilterForReason(compilerReason), + force); + } + /** * Perform dexopt on the package {@code packageName} secondary dex files. * @return true if all secondary dex files were processed successfully (compiled or skipped @@ -383,7 +443,7 @@ public class DexManager { // Ignore framework code. // TODO(calin): is there a better way to detect it? if (dexPath.startsWith("/system/framework/")) { - new DexSearchResult("framework", DEX_SEARCH_NOT_FOUND); + return new DexSearchResult("framework", DEX_SEARCH_NOT_FOUND); } // First, check if the package which loads the dex file actually owns it. @@ -425,27 +485,36 @@ public class DexManager { */ private static class PackageCodeLocations { private final String mPackageName; - private final String mBaseCodePath; + private String mBaseCodePath; private final Set mSplitCodePaths; // Maps user id to the application private directory. private final Map> mAppDataDirs; public PackageCodeLocations(ApplicationInfo ai, int userId) { - mPackageName = ai.packageName; - mBaseCodePath = ai.sourceDir; + this(ai.packageName, ai.sourceDir, ai.splitSourceDirs); + mergeAppDataDirs(ai.dataDir, userId); + } + public PackageCodeLocations(String packageName, String baseCodePath, + String[] splitCodePaths) { + mPackageName = packageName; mSplitCodePaths = new HashSet<>(); - if (ai.splitSourceDirs != null) { - for (String split : ai.splitSourceDirs) { + mAppDataDirs = new HashMap<>(); + updateCodeLocation(baseCodePath, splitCodePaths); + } + + public void updateCodeLocation(String baseCodePath, String[] splitCodePaths) { + mBaseCodePath = baseCodePath; + mSplitCodePaths.clear(); + if (splitCodePaths != null) { + for (String split : splitCodePaths) { mSplitCodePaths.add(split); } } - mAppDataDirs = new HashMap<>(); - mergeAppDataDirs(ai, userId); } - public void mergeAppDataDirs(ApplicationInfo ai, int userId) { + public void mergeAppDataDirs(String dataDir, int userId) { Set dataDirs = putIfAbsent(mAppDataDirs, userId, new HashSet<>()); - dataDirs.add(ai.dataDir); + dataDirs.add(dataDir); } public int searchDex(String dexPath, int userId) { diff --git a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java index 3693bce04eb10..8a66f12cb6d9d 100644 --- a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java +++ b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java @@ -376,8 +376,35 @@ public class PackageDexUsage extends AbstractStatsBase { } } + /** + * Clears the {@code usesByOtherApps} marker for the package {@code packageName}. + * @return true if the package usage info was updated. + */ + public boolean clearUsedByOtherApps(String packageName) { + synchronized (mPackageUseInfoMap) { + PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(packageName); + if (packageUseInfo == null || !packageUseInfo.mIsUsedByOtherApps) { + return false; + } + packageUseInfo.mIsUsedByOtherApps = false; + return true; + } + } + + /** + * Remove the usage data associated with package {@code packageName}. + * @return true if the package usage was found and removed successfully. + */ + public boolean removePackage(String packageName) { + synchronized (mPackageUseInfoMap) { + return mPackageUseInfoMap.remove(packageName) != null; + } + } + /** * Remove all the records about package {@code packageName} belonging to user {@code userId}. + * If the package is left with no records of secondary dex usage and is not used by other + * apps it will be removed as well. * @return true if the record was found and actually deleted, * false if the record doesn't exist */ @@ -397,6 +424,12 @@ public class PackageDexUsage extends AbstractStatsBase { updated = true; } } + // If no secondary dex info is left and the package is not used by other apps + // remove the data since it is now useless. + if (packageUseInfo.mDexUseInfoMap.isEmpty() && !packageUseInfo.mIsUsedByOtherApps) { + mPackageUseInfoMap.remove(packageName); + updated = true; + } return updated; } } diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java index 90a2ec0c76289..72fb78e89ea21 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java @@ -16,9 +16,10 @@ package com.android.server.pm.dex; -import android.os.Build; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; +import android.os.Build; +import android.os.UserHandle; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; @@ -57,9 +58,9 @@ public class DexManagerTests { private int mUser0; private int mUser1; + @Before public void setup() { - mUser0 = 0; mUser1 = 1; @@ -243,6 +244,122 @@ public class DexManagerTests { assertSecondaryUse(newPackage, pui, newSecondaries, /*isUsedByOtherApps*/false, mUser0); } + @Test + public void testNotifyPackageUpdated() { + // Foo loads Bar main apks. + notifyDexLoad(mFooUser0, mBarUser0.getBaseAndSplitDexPaths(), mUser0); + + // Bar is used by others now and should be in our records. + PackageUseInfo pui = getPackageUseInfo(mBarUser0); + assertNotNull(pui); + assertTrue(pui.isUsedByOtherApps()); + assertTrue(pui.getDexUseInfoMap().isEmpty()); + + // Notify that bar is updated. + mDexManager.notifyPackageUpdated(mBarUser0.getPackageName(), + mBarUser0.mPackageInfo.applicationInfo.sourceDir, + mBarUser0.mPackageInfo.applicationInfo.splitSourceDirs); + + // The usedByOtherApps flag should be clear now. + pui = getPackageUseInfo(mBarUser0); + assertNotNull(pui); + assertFalse(pui.isUsedByOtherApps()); + } + + @Test + public void testNotifyPackageUpdatedCodeLocations() { + // Simulate a split update. + String newSplit = mBarUser0.replaceLastSplit(); + List newSplits = new ArrayList<>(); + newSplits.add(newSplit); + + // We shouldn't find yet the new split as we didn't notify the package update. + notifyDexLoad(mFooUser0, newSplits, mUser0); + PackageUseInfo pui = getPackageUseInfo(mBarUser0); + assertNull(pui); + + // Notify that bar is updated. splitSourceDirs will contain the updated path. + mDexManager.notifyPackageUpdated(mBarUser0.getPackageName(), + mBarUser0.mPackageInfo.applicationInfo.sourceDir, + mBarUser0.mPackageInfo.applicationInfo.splitSourceDirs); + + // Now, when the split is loaded we will find it and we should mark Bar as usedByOthers. + notifyDexLoad(mFooUser0, newSplits, mUser0); + pui = getPackageUseInfo(mBarUser0); + assertNotNull(pui); + assertTrue(pui.isUsedByOtherApps()); + } + + @Test + public void testNotifyPackageDataDestroyForOne() { + // Bar loads its own secondary files. + notifyDexLoad(mBarUser0, mBarUser0.getSecondaryDexPaths(), mUser0); + notifyDexLoad(mBarUser1, mBarUser1.getSecondaryDexPaths(), mUser1); + + mDexManager.notifyPackageDataDestroyed(mBarUser0.getPackageName(), mUser0); + + // Bar should not be around since it was removed for all users. + PackageUseInfo pui = getPackageUseInfo(mBarUser1); + assertNotNull(pui); + assertSecondaryUse(mBarUser1, pui, mBarUser1.getSecondaryDexPaths(), + /*isUsedByOtherApps*/false, mUser1); + } + + @Test + public void testNotifyPackageDataDestroyForeignUse() { + // Foo loads its own secondary files. + List fooSecondaries = mFooUser0.getSecondaryDexPaths(); + notifyDexLoad(mFooUser0, fooSecondaries, mUser0); + + // Bar loads Foo main apks. + notifyDexLoad(mBarUser0, mFooUser0.getBaseAndSplitDexPaths(), mUser0); + + mDexManager.notifyPackageDataDestroyed(mFooUser0.getPackageName(), mUser0); + + // Foo should still be around since it's used by other apps but with no + // secondary dex info. + PackageUseInfo pui = getPackageUseInfo(mFooUser0); + assertNotNull(pui); + assertTrue(pui.isUsedByOtherApps()); + assertTrue(pui.getDexUseInfoMap().isEmpty()); + } + + @Test + public void testNotifyPackageDataDestroyComplete() { + // Foo loads its own secondary files. + List fooSecondaries = mFooUser0.getSecondaryDexPaths(); + notifyDexLoad(mFooUser0, fooSecondaries, mUser0); + + mDexManager.notifyPackageDataDestroyed(mFooUser0.getPackageName(), mUser0); + + // Foo should not be around since all its secondary dex info were deleted + // and it is not used by other apps. + PackageUseInfo pui = getPackageUseInfo(mFooUser0); + assertNull(pui); + } + + @Test + public void testNotifyPackageDataDestroyForAll() { + // Foo loads its own secondary files. + notifyDexLoad(mBarUser0, mBarUser0.getSecondaryDexPaths(), mUser0); + notifyDexLoad(mBarUser1, mBarUser1.getSecondaryDexPaths(), mUser1); + + mDexManager.notifyPackageDataDestroyed(mBarUser0.getPackageName(), UserHandle.USER_ALL); + + // Bar should not be around since it was removed for all users. + PackageUseInfo pui = getPackageUseInfo(mBarUser0); + assertNull(pui); + } + + @Test + public void testNotifyFrameworkLoad() { + String frameworkDex = "/system/framework/com.android.location.provider.jar"; + // Load a dex file from framework. + notifyDexLoad(mFooUser0, Arrays.asList(frameworkDex), mUser0); + // The dex file should not be recognized as a package. + assertNull(mDexManager.getPackageUseInfo(frameworkDex)); + } + private void assertSecondaryUse(TestData testData, PackageUseInfo pui, List secondaries, boolean isUsedByOtherApps, int ownerUserId) { for (String dex : secondaries) { @@ -317,5 +434,12 @@ public class DexManagerTests { } return paths; } + + String replaceLastSplit() { + int length = mPackageInfo.applicationInfo.splitSourceDirs.length; + // Add an extra bogus dex extension to simulate a new split name. + mPackageInfo.applicationInfo.splitSourceDirs[length - 1] += ".dex"; + return mPackageInfo.applicationInfo.splitSourceDirs[length - 1]; + } } } diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java index 19e0bcf6ba56d..2e99433149eae 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/PackageDexUsageTests.java @@ -256,6 +256,30 @@ public class PackageDexUsageTests { assertNull(mPackageDexUsage.getPackageUseInfo(mFooBaseUser0.mPackageName)); } + @Test + public void testRemovePackage() { + // Record Bar secondaries for two different users. + assertTrue(record(mBarSecondary1User0)); + assertTrue(record(mBarSecondary2User1)); + + // Remove the package. + assertTrue(mPackageDexUsage.removePackage(mBarSecondary1User0.mPackageName)); + // Assert that we can't find the package anymore. + assertNull(mPackageDexUsage.getPackageUseInfo(mBarSecondary1User0.mPackageName)); + } + + @Test + public void testRemoveNonexistentPackage() { + // Record Bar secondaries for two different users. + assertTrue(record(mBarSecondary1User0)); + + // Remove the package. + assertTrue(mPackageDexUsage.removePackage(mBarSecondary1User0.mPackageName)); + // Remove the package again. It should return false because the package no longer + // has a record in the use info. + assertFalse(mPackageDexUsage.removePackage(mBarSecondary1User0.mPackageName)); + } + @Test public void testRemoveUserPackage() { // Record Bar secondaries for two different users. @@ -282,6 +306,32 @@ public class PackageDexUsageTests { assertPackageDexUsage(null, mBarSecondary2User1); } + @Test + public void testClearUsedByOtherApps() { + // Write a package which is used by other apps. + assertTrue(record(mFooSplit2UsedByOtherApps0)); + assertTrue(mPackageDexUsage.clearUsedByOtherApps(mFooSplit2UsedByOtherApps0.mPackageName)); + + // Check that the package is no longer used by other apps. + TestData noLongerUsedByOtherApps = new TestData( + mFooSplit2UsedByOtherApps0.mPackageName, + mFooSplit2UsedByOtherApps0.mDexFile, + mFooSplit2UsedByOtherApps0.mOwnerUserId, + mFooSplit2UsedByOtherApps0.mLoaderIsa, + /*mIsUsedByOtherApps*/false, + mFooSplit2UsedByOtherApps0.mPrimaryOrSplit); + assertPackageDexUsage(noLongerUsedByOtherApps); + } + + @Test + public void testClearUsedByOtherAppsNonexistent() { + // Write a package which is used by other apps. + assertTrue(record(mFooSplit2UsedByOtherApps0)); + assertTrue(mPackageDexUsage.clearUsedByOtherApps(mFooSplit2UsedByOtherApps0.mPackageName)); + // Clearing again should return false as there should be no update on the use info. + assertFalse(mPackageDexUsage.clearUsedByOtherApps(mFooSplit2UsedByOtherApps0.mPackageName)); + } + private void assertPackageDexUsage(TestData primary, TestData... secondaries) { String packageName = primary == null ? secondaries[0].mPackageName : primary.mPackageName; boolean primaryUsedByOtherApps = primary == null ? false : primary.mUsedByOtherApps;