Loading services/core/java/com/android/server/pm/dex/DexManager.java +82 −35 Original line number Diff line number Diff line Loading @@ -16,6 +16,11 @@ package com.android.server.pm.dex; import static com.android.server.pm.InstructionSets.getAppDexInstructionSets; import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo; import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo; import static com.android.server.pm.dex.PackageDynamicCodeLoading.PackageDynamicCode; import android.content.ContentResolver; import android.content.Context; import android.content.pm.ApplicationInfo; Loading @@ -26,9 +31,9 @@ import android.database.ContentObserver; import android.os.Build; import android.os.FileUtils; import android.os.RemoteException; import android.os.storage.StorageManager; import android.os.SystemProperties; import android.os.UserHandle; import android.os.storage.StorageManager; import android.provider.Settings.Global; import android.util.Log; import android.util.Slog; Loading @@ -48,18 +53,14 @@ import java.io.IOException; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.zip.ZipEntry; import static com.android.server.pm.InstructionSets.getAppDexInstructionSets; import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo; import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo; /** * This class keeps track of how dex files are used. * Every time it gets a notification about a dex file being loaded it tracks Loading Loading @@ -89,6 +90,12 @@ public class DexManager { // encode and save the dex usage data. private final PackageDexUsage mPackageDexUsage; // PackageDynamicCodeLoading handles recording of dynamic code loading - // which is similar to PackageDexUsage but records a different aspect of the data. // (It additionally includes DEX files loaded with unsupported class loaders, and doesn't // record class loaders or ISAs.) private final PackageDynamicCodeLoading mPackageDynamicCodeLoading; private final IPackageManager mPackageManager; private final PackageDexOptimizer mPackageDexOptimizer; private final Object mInstallLock; Loading Loading @@ -129,6 +136,7 @@ public class DexManager { mContext = context; mPackageCodeLocationsCache = new HashMap<>(); mPackageDexUsage = new PackageDexUsage(); mPackageDynamicCodeLoading = new PackageDynamicCodeLoading(); mPackageManager = pms; mPackageDexOptimizer = pdo; mInstaller = installer; Loading Loading @@ -207,7 +215,6 @@ public class DexManager { Slog.i(TAG, loadingAppInfo.packageName + " uses unsupported class loader in " + classLoaderNames); } return; } int dexPathIndex = 0; Loading Loading @@ -236,9 +243,17 @@ public class DexManager { continue; } // Record dex file usage. If the current usage is a new pattern (e.g. new secondary, // or UsedByOtherApps), record will return true and we trigger an async write // to disk to make sure we don't loose the data in case of a reboot. if (mPackageDynamicCodeLoading.record(searchResult.mOwningPackageName, dexPath, PackageDynamicCodeLoading.FILE_TYPE_DEX, loaderUserId, loadingAppInfo.packageName)) { mPackageDynamicCodeLoading.maybeWriteAsync(); } if (classLoaderContexts != null) { // Record dex file usage. If the current usage is a new pattern (e.g. new // secondary, or UsedByOtherApps), record will return true and we trigger an // async write to disk to make sure we don't loose the data in case of a reboot. String classLoaderContext = classLoaderContexts[dexPathIndex]; if (mPackageDexUsage.record(searchResult.mOwningPackageName, Loading @@ -246,6 +261,7 @@ public class DexManager { loadingAppInfo.packageName, classLoaderContext)) { mPackageDexUsage.maybeWriteAsync(); } } } else { // If we can't find the owner of the dex we simply do not track it. The impact is // that the dex file will not be considered for offline optimizations. Loading @@ -268,8 +284,8 @@ public class DexManager { loadInternal(existingPackages); } catch (Exception e) { mPackageDexUsage.clear(); Slog.w(TAG, "Exception while loading package dex usage. " + "Starting with a fresh state.", e); mPackageDynamicCodeLoading.clear(); Slog.w(TAG, "Exception while loading. Starting with a fresh state.", e); } } Loading Loading @@ -311,16 +327,25 @@ public class DexManager { * 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 // Note that we do the writing here and not in the lower level classes in order to be // consistent with other methods in DexManager (e.g. reconcileSecondaryDexFiles performs // multiple updates in PackageDexUsage before writing it). if (updated) { if (userId == UserHandle.USER_ALL) { if (mPackageDexUsage.removePackage(packageName)) { mPackageDexUsage.maybeWriteAsync(); } if (mPackageDynamicCodeLoading.removePackage(packageName)) { mPackageDynamicCodeLoading.maybeWriteAsync(); } } else { if (mPackageDexUsage.removeUserPackage(packageName, userId)) { mPackageDexUsage.maybeWriteAsync(); } if (mPackageDynamicCodeLoading.removeUserPackage(packageName, userId)) { mPackageDynamicCodeLoading.maybeWriteAsync(); } } } /** Loading Loading @@ -388,8 +413,23 @@ public class DexManager { } } try { mPackageDexUsage.read(); mPackageDexUsage.syncData(packageToUsersMap, packageToCodePaths); } catch (Exception e) { mPackageDexUsage.clear(); Slog.w(TAG, "Exception while loading package dex usage. " + "Starting with a fresh state.", e); } try { mPackageDynamicCodeLoading.read(); mPackageDynamicCodeLoading.syncData(packageToUsersMap); } catch (Exception e) { mPackageDynamicCodeLoading.clear(); Slog.w(TAG, "Exception while loading package dynamic code usage. " + "Starting with a fresh state.", e); } } /** Loading @@ -415,10 +455,16 @@ public class DexManager { * TODO(calin): maybe we should not (prune) so we can have an accurate view when we try * to access the package use. */ @VisibleForTesting /*package*/ boolean hasInfoOnPackage(String packageName) { return mPackageDexUsage.getPackageUseInfo(packageName) != null; } @VisibleForTesting /*package*/ PackageDynamicCode getPackageDynamicCodeInfo(String packageName) { return mPackageDynamicCodeLoading.getPackageDynamicCodeInfo(packageName); } /** * Perform dexopt on with the given {@code options} on the secondary dex files. * @return true if all secondary dex files were processed successfully (compiled or skipped Loading Loading @@ -652,7 +698,7 @@ public class DexManager { // to load dex files through it. try { String dexPathReal = PackageManagerServiceUtils.realpath(new File(dexPath)); if (dexPathReal != dexPath) { if (!dexPath.equals(dexPathReal)) { Slog.d(TAG, "Dex loaded with symlink. dexPath=" + dexPath + " dexPathReal=" + dexPathReal); } Loading @@ -675,6 +721,7 @@ public class DexManager { */ public void writePackageDexUsageNow() { mPackageDexUsage.writeNow(); mPackageDynamicCodeLoading.writeNow(); } private void registerSettingObserver() { Loading services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java +1 −1 Original line number Diff line number Diff line Loading @@ -96,7 +96,7 @@ class PackageDynamicCodeLoading extends AbstractStatsBase<Void> { * @param ownerUserId the user id which runs the code loading the file * @param loadingPackageName the package performing the load * @return whether new information has been recorded * @throw IllegalArgumentException if clearly invalid information is detected * @throws IllegalArgumentException if clearly invalid information is detected */ boolean record(String owningPackageName, String filePath, int fileType, int ownerUserId, String loadingPackageName) { Loading services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java +99 −12 Original line number Diff line number Diff line Loading @@ -18,10 +18,14 @@ package com.android.server.pm.dex; import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo; import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo; import static com.android.server.pm.dex.PackageDynamicCodeLoading.DynamicCodeFile; import static com.android.server.pm.dex.PackageDynamicCodeLoading.PackageDynamicCode; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; Loading @@ -41,6 +45,10 @@ import androidx.test.runner.AndroidJUnit4; import com.android.server.pm.Installer; import dalvik.system.DelegateLastClassLoader; import dalvik.system.PathClassLoader; import dalvik.system.VMRuntime; import org.junit.Before; import org.junit.Rule; import org.junit.Test; Loading @@ -50,10 +58,6 @@ import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.mockito.quality.Strictness; import dalvik.system.DelegateLastClassLoader; import dalvik.system.PathClassLoader; import dalvik.system.VMRuntime; import java.io.File; import java.util.ArrayList; import java.util.Arrays; Loading Loading @@ -129,6 +133,9 @@ public class DexManagerTests { // Package is not used by others, so we should get nothing back. assertNoUseInfo(mFooUser0); // A package loading its own code is not stored as DCL. assertNoDclInfo(mFooUser0); } @Test Loading @@ -140,6 +147,8 @@ public class DexManagerTests { PackageUseInfo pui = getPackageUseInfo(mBarUser0); assertIsUsedByOtherApps(mBarUser0, pui, true); assertTrue(pui.getDexUseInfoMap().isEmpty()); assertHasDclInfo(mBarUser0, mFooUser0, mBarUser0.getBaseAndSplitDexPaths()); } @Test Loading @@ -152,6 +161,8 @@ public class DexManagerTests { assertIsUsedByOtherApps(mFooUser0, pui, false); assertEquals(fooSecondaries.size(), pui.getDexUseInfoMap().size()); assertSecondaryUse(mFooUser0, pui, fooSecondaries, /*isUsedByOtherApps*/false, mUser0); assertHasDclInfo(mFooUser0, mFooUser0, fooSecondaries); } @Test Loading @@ -164,6 +175,8 @@ public class DexManagerTests { assertIsUsedByOtherApps(mBarUser0, pui, false); assertEquals(barSecondaries.size(), pui.getDexUseInfoMap().size()); assertSecondaryUse(mFooUser0, pui, barSecondaries, /*isUsedByOtherApps*/true, mUser0); assertHasDclInfo(mBarUser0, mFooUser0, barSecondaries); } @Test Loading Loading @@ -200,9 +213,10 @@ public class DexManagerTests { } @Test public void testPackageUseInfoNotFound() { public void testNoNotify() { // Assert we don't get back data we did not previously record. assertNoUseInfo(mFooUser0); assertNoDclInfo(mFooUser0); } @Test Loading @@ -210,6 +224,7 @@ public class DexManagerTests { // Notifying with an invalid ISA should be ignored. notifyDexLoad(mInvalidIsa, mInvalidIsa.getSecondaryDexPaths(), mUser0); assertNoUseInfo(mInvalidIsa); assertNoDclInfo(mInvalidIsa); } @Test Loading @@ -218,6 +233,7 @@ public class DexManagerTests { // register in DexManager#load should be ignored. notifyDexLoad(mDoesNotExist, mDoesNotExist.getBaseAndSplitDexPaths(), mUser0); assertNoUseInfo(mDoesNotExist); assertNoDclInfo(mDoesNotExist); } @Test Loading @@ -226,6 +242,8 @@ public class DexManagerTests { // Request should be ignored. notifyDexLoad(mBarUser1, mBarUser0.getSecondaryDexPaths(), mUser1); assertNoUseInfo(mBarUser1); assertNoDclInfo(mBarUser1); } @Test Loading @@ -235,6 +253,10 @@ public class DexManagerTests { // still check that nothing goes unexpected in DexManager. notifyDexLoad(mBarUser0, mFooUser0.getBaseAndSplitDexPaths(), mUser1); assertNoUseInfo(mBarUser1); assertNoUseInfo(mFooUser0); assertNoDclInfo(mBarUser1); assertNoDclInfo(mFooUser0); } @Test Loading @@ -247,6 +269,7 @@ public class DexManagerTests { // is trying to load something from it we should not find it. notifyDexLoad(mFooUser0, newSecondaries, mUser0); assertNoUseInfo(newPackage); assertNoDclInfo(newPackage); // Notify about newPackage install and let mFoo load its dexes. mDexManager.notifyPackageInstalled(newPackage.mPackageInfo, mUser0); Loading @@ -257,6 +280,7 @@ public class DexManagerTests { assertIsUsedByOtherApps(newPackage, pui, false); assertEquals(newSecondaries.size(), pui.getDexUseInfoMap().size()); assertSecondaryUse(newPackage, pui, newSecondaries, /*isUsedByOtherApps*/true, mUser0); assertHasDclInfo(newPackage, mFooUser0, newSecondaries); } @Test Loading @@ -273,6 +297,7 @@ public class DexManagerTests { assertIsUsedByOtherApps(newPackage, pui, false); assertEquals(newSecondaries.size(), pui.getDexUseInfoMap().size()); assertSecondaryUse(newPackage, pui, newSecondaries, /*isUsedByOtherApps*/false, mUser0); assertHasDclInfo(newPackage, newPackage, newSecondaries); } @Test Loading Loading @@ -305,6 +330,7 @@ public class DexManagerTests { // We shouldn't find yet the new split as we didn't notify the package update. notifyDexLoad(mFooUser0, newSplits, mUser0); assertNoUseInfo(mBarUser0); assertNoDclInfo(mBarUser0); // Notify that bar is updated. splitSourceDirs will contain the updated path. mDexManager.notifyPackageUpdated(mBarUser0.getPackageName(), Loading @@ -314,8 +340,8 @@ public class DexManagerTests { // Now, when the split is loaded we will find it and we should mark Bar as usedByOthers. notifyDexLoad(mFooUser0, newSplits, mUser0); PackageUseInfo pui = getPackageUseInfo(mBarUser0); assertNotNull(pui); assertIsUsedByOtherApps(newSplits, pui, true); assertHasDclInfo(mBarUser0, mFooUser0, newSplits); } @Test Loading @@ -326,11 +352,15 @@ public class DexManagerTests { mDexManager.notifyPackageDataDestroyed(mBarUser0.getPackageName(), mUser0); // Bar should not be around since it was removed for all users. // Data for user 1 should still be present PackageUseInfo pui = getPackageUseInfo(mBarUser1); assertNotNull(pui); assertSecondaryUse(mBarUser1, pui, mBarUser1.getSecondaryDexPaths(), /*isUsedByOtherApps*/false, mUser1); assertHasDclInfo(mBarUser1, mBarUser1, mBarUser1.getSecondaryDexPaths()); // But not user 0 assertNoUseInfo(mBarUser0, mUser0); assertNoDclInfo(mBarUser0, mUser0); } @Test Loading @@ -349,6 +379,8 @@ public class DexManagerTests { PackageUseInfo pui = getPackageUseInfo(mFooUser0); assertIsUsedByOtherApps(mFooUser0, pui, true); assertTrue(pui.getDexUseInfoMap().isEmpty()); assertNoDclInfo(mFooUser0); } @Test Loading @@ -362,6 +394,7 @@ public class DexManagerTests { // Foo should not be around since all its secondary dex info were deleted // and it is not used by other apps. assertNoUseInfo(mFooUser0); assertNoDclInfo(mFooUser0); } @Test Loading @@ -374,6 +407,7 @@ public class DexManagerTests { // Bar should not be around since it was removed for all users. assertNoUseInfo(mBarUser0); assertNoDclInfo(mBarUser0); } @Test Loading @@ -383,6 +417,7 @@ public class DexManagerTests { notifyDexLoad(mFooUser0, Arrays.asList(frameworkDex), mUser0); // The dex file should not be recognized as a package. assertFalse(mDexManager.hasInfoOnPackage(frameworkDex)); assertNull(mDexManager.getPackageDynamicCodeInfo(frameworkDex)); } @Test Loading @@ -395,6 +430,8 @@ public class DexManagerTests { assertIsUsedByOtherApps(mFooUser0, pui, false); assertEquals(fooSecondaries.size(), pui.getDexUseInfoMap().size()); assertSecondaryUse(mFooUser0, pui, fooSecondaries, /*isUsedByOtherApps*/false, mUser0); assertHasDclInfo(mFooUser0, mFooUser0, fooSecondaries); } @Test Loading @@ -402,7 +439,12 @@ public class DexManagerTests { List<String> secondaries = mBarUser0UnsupportedClassLoader.getSecondaryDexPaths(); notifyDexLoad(mBarUser0UnsupportedClassLoader, secondaries, mUser0); // We don't record the dex usage assertNoUseInfo(mBarUser0UnsupportedClassLoader); // But we do record this as an intance of dynamic code loading assertHasDclInfo( mBarUser0UnsupportedClassLoader, mBarUser0UnsupportedClassLoader, secondaries); } @Test Loading @@ -414,6 +456,8 @@ public class DexManagerTests { notifyDexLoad(mBarUser0, classLoaders, classPaths, mUser0); assertNoUseInfo(mBarUser0); assertHasDclInfo(mBarUser0, mBarUser0, mBarUser0.getSecondaryDexPaths()); } @Test Loading @@ -421,6 +465,7 @@ public class DexManagerTests { notifyDexLoad(mBarUser0, null, mUser0); assertNoUseInfo(mBarUser0); assertNoDclInfo(mBarUser0); } @Test Loading Loading @@ -455,12 +500,14 @@ public class DexManagerTests { notifyDexLoad(mBarUser0, secondaries, mUser0); PackageUseInfo pui = getPackageUseInfo(mBarUser0); assertSecondaryUse(mBarUser0, pui, secondaries, /*isUsedByOtherApps*/false, mUser0); assertHasDclInfo(mBarUser0, mBarUser0, secondaries); // Record bar secondaries again with an unsupported class loader. This should not change the // context. notifyDexLoad(mBarUser0UnsupportedClassLoader, secondaries, mUser0); pui = getPackageUseInfo(mBarUser0); assertSecondaryUse(mBarUser0, pui, secondaries, /*isUsedByOtherApps*/false, mUser0); assertHasDclInfo(mBarUser0, mBarUser0, secondaries); } @Test Loading Loading @@ -533,13 +580,53 @@ public class DexManagerTests { private PackageUseInfo getPackageUseInfo(TestData testData) { assertTrue(mDexManager.hasInfoOnPackage(testData.getPackageName())); return mDexManager.getPackageUseInfoOrDefault(testData.getPackageName()); PackageUseInfo pui = mDexManager.getPackageUseInfoOrDefault(testData.getPackageName()); assertNotNull(pui); return pui; } private void assertNoUseInfo(TestData testData) { assertFalse(mDexManager.hasInfoOnPackage(testData.getPackageName())); } private void assertNoUseInfo(TestData testData, int userId) { if (!mDexManager.hasInfoOnPackage(testData.getPackageName())) { return; } PackageUseInfo pui = getPackageUseInfo(testData); for (DexUseInfo dexUseInfo : pui.getDexUseInfoMap().values()) { assertNotEquals(userId, dexUseInfo.getOwnerUserId()); } } private void assertNoDclInfo(TestData testData) { assertNull(mDexManager.getPackageDynamicCodeInfo(testData.getPackageName())); } private void assertNoDclInfo(TestData testData, int userId) { PackageDynamicCode info = mDexManager.getPackageDynamicCodeInfo(testData.getPackageName()); if (info == null) { return; } for (DynamicCodeFile fileInfo : info.mFileUsageMap.values()) { assertNotEquals(userId, fileInfo.mUserId); } } private void assertHasDclInfo(TestData owner, TestData loader, List<String> paths) { PackageDynamicCode info = mDexManager.getPackageDynamicCodeInfo(owner.getPackageName()); assertNotNull("No DCL data for owner " + owner.getPackageName(), info); for (String path : paths) { DynamicCodeFile fileInfo = info.mFileUsageMap.get(path); assertNotNull("No DCL data for path " + path, fileInfo); assertEquals(PackageDynamicCodeLoading.FILE_TYPE_DEX, fileInfo.mFileType); assertEquals(owner.mUserId, fileInfo.mUserId); assertTrue("No DCL data for loader " + loader.getPackageName(), fileInfo.mLoadingPackages.contains(loader.getPackageName())); } } private static PackageInfo getMockPackageInfo(String packageName, int userId) { PackageInfo pi = new PackageInfo(); pi.packageName = packageName; Loading @@ -563,11 +650,13 @@ public class DexManagerTests { private final PackageInfo mPackageInfo; private final String mLoaderIsa; private final String mClassLoader; private final int mUserId; private TestData(String packageName, String loaderIsa, int userId, String classLoader) { mPackageInfo = getMockPackageInfo(packageName, userId); mLoaderIsa = loaderIsa; mClassLoader = classLoader; mUserId = userId; } private TestData(String packageName, String loaderIsa, int userId) { Loading Loading @@ -603,9 +692,7 @@ public class DexManagerTests { List<String> getBaseAndSplitDexPaths() { List<String> paths = new ArrayList<>(); paths.add(mPackageInfo.applicationInfo.sourceDir); for (String split : mPackageInfo.applicationInfo.splitSourceDirs) { paths.add(split); } Collections.addAll(paths, mPackageInfo.applicationInfo.splitSourceDirs); return paths; } Loading Loading
services/core/java/com/android/server/pm/dex/DexManager.java +82 −35 Original line number Diff line number Diff line Loading @@ -16,6 +16,11 @@ package com.android.server.pm.dex; import static com.android.server.pm.InstructionSets.getAppDexInstructionSets; import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo; import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo; import static com.android.server.pm.dex.PackageDynamicCodeLoading.PackageDynamicCode; import android.content.ContentResolver; import android.content.Context; import android.content.pm.ApplicationInfo; Loading @@ -26,9 +31,9 @@ import android.database.ContentObserver; import android.os.Build; import android.os.FileUtils; import android.os.RemoteException; import android.os.storage.StorageManager; import android.os.SystemProperties; import android.os.UserHandle; import android.os.storage.StorageManager; import android.provider.Settings.Global; import android.util.Log; import android.util.Slog; Loading @@ -48,18 +53,14 @@ import java.io.IOException; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.zip.ZipEntry; import static com.android.server.pm.InstructionSets.getAppDexInstructionSets; import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo; import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo; /** * This class keeps track of how dex files are used. * Every time it gets a notification about a dex file being loaded it tracks Loading Loading @@ -89,6 +90,12 @@ public class DexManager { // encode and save the dex usage data. private final PackageDexUsage mPackageDexUsage; // PackageDynamicCodeLoading handles recording of dynamic code loading - // which is similar to PackageDexUsage but records a different aspect of the data. // (It additionally includes DEX files loaded with unsupported class loaders, and doesn't // record class loaders or ISAs.) private final PackageDynamicCodeLoading mPackageDynamicCodeLoading; private final IPackageManager mPackageManager; private final PackageDexOptimizer mPackageDexOptimizer; private final Object mInstallLock; Loading Loading @@ -129,6 +136,7 @@ public class DexManager { mContext = context; mPackageCodeLocationsCache = new HashMap<>(); mPackageDexUsage = new PackageDexUsage(); mPackageDynamicCodeLoading = new PackageDynamicCodeLoading(); mPackageManager = pms; mPackageDexOptimizer = pdo; mInstaller = installer; Loading Loading @@ -207,7 +215,6 @@ public class DexManager { Slog.i(TAG, loadingAppInfo.packageName + " uses unsupported class loader in " + classLoaderNames); } return; } int dexPathIndex = 0; Loading Loading @@ -236,9 +243,17 @@ public class DexManager { continue; } // Record dex file usage. If the current usage is a new pattern (e.g. new secondary, // or UsedByOtherApps), record will return true and we trigger an async write // to disk to make sure we don't loose the data in case of a reboot. if (mPackageDynamicCodeLoading.record(searchResult.mOwningPackageName, dexPath, PackageDynamicCodeLoading.FILE_TYPE_DEX, loaderUserId, loadingAppInfo.packageName)) { mPackageDynamicCodeLoading.maybeWriteAsync(); } if (classLoaderContexts != null) { // Record dex file usage. If the current usage is a new pattern (e.g. new // secondary, or UsedByOtherApps), record will return true and we trigger an // async write to disk to make sure we don't loose the data in case of a reboot. String classLoaderContext = classLoaderContexts[dexPathIndex]; if (mPackageDexUsage.record(searchResult.mOwningPackageName, Loading @@ -246,6 +261,7 @@ public class DexManager { loadingAppInfo.packageName, classLoaderContext)) { mPackageDexUsage.maybeWriteAsync(); } } } else { // If we can't find the owner of the dex we simply do not track it. The impact is // that the dex file will not be considered for offline optimizations. Loading @@ -268,8 +284,8 @@ public class DexManager { loadInternal(existingPackages); } catch (Exception e) { mPackageDexUsage.clear(); Slog.w(TAG, "Exception while loading package dex usage. " + "Starting with a fresh state.", e); mPackageDynamicCodeLoading.clear(); Slog.w(TAG, "Exception while loading. Starting with a fresh state.", e); } } Loading Loading @@ -311,16 +327,25 @@ public class DexManager { * 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 // Note that we do the writing here and not in the lower level classes in order to be // consistent with other methods in DexManager (e.g. reconcileSecondaryDexFiles performs // multiple updates in PackageDexUsage before writing it). if (updated) { if (userId == UserHandle.USER_ALL) { if (mPackageDexUsage.removePackage(packageName)) { mPackageDexUsage.maybeWriteAsync(); } if (mPackageDynamicCodeLoading.removePackage(packageName)) { mPackageDynamicCodeLoading.maybeWriteAsync(); } } else { if (mPackageDexUsage.removeUserPackage(packageName, userId)) { mPackageDexUsage.maybeWriteAsync(); } if (mPackageDynamicCodeLoading.removeUserPackage(packageName, userId)) { mPackageDynamicCodeLoading.maybeWriteAsync(); } } } /** Loading Loading @@ -388,8 +413,23 @@ public class DexManager { } } try { mPackageDexUsage.read(); mPackageDexUsage.syncData(packageToUsersMap, packageToCodePaths); } catch (Exception e) { mPackageDexUsage.clear(); Slog.w(TAG, "Exception while loading package dex usage. " + "Starting with a fresh state.", e); } try { mPackageDynamicCodeLoading.read(); mPackageDynamicCodeLoading.syncData(packageToUsersMap); } catch (Exception e) { mPackageDynamicCodeLoading.clear(); Slog.w(TAG, "Exception while loading package dynamic code usage. " + "Starting with a fresh state.", e); } } /** Loading @@ -415,10 +455,16 @@ public class DexManager { * TODO(calin): maybe we should not (prune) so we can have an accurate view when we try * to access the package use. */ @VisibleForTesting /*package*/ boolean hasInfoOnPackage(String packageName) { return mPackageDexUsage.getPackageUseInfo(packageName) != null; } @VisibleForTesting /*package*/ PackageDynamicCode getPackageDynamicCodeInfo(String packageName) { return mPackageDynamicCodeLoading.getPackageDynamicCodeInfo(packageName); } /** * Perform dexopt on with the given {@code options} on the secondary dex files. * @return true if all secondary dex files were processed successfully (compiled or skipped Loading Loading @@ -652,7 +698,7 @@ public class DexManager { // to load dex files through it. try { String dexPathReal = PackageManagerServiceUtils.realpath(new File(dexPath)); if (dexPathReal != dexPath) { if (!dexPath.equals(dexPathReal)) { Slog.d(TAG, "Dex loaded with symlink. dexPath=" + dexPath + " dexPathReal=" + dexPathReal); } Loading @@ -675,6 +721,7 @@ public class DexManager { */ public void writePackageDexUsageNow() { mPackageDexUsage.writeNow(); mPackageDynamicCodeLoading.writeNow(); } private void registerSettingObserver() { Loading
services/core/java/com/android/server/pm/dex/PackageDynamicCodeLoading.java +1 −1 Original line number Diff line number Diff line Loading @@ -96,7 +96,7 @@ class PackageDynamicCodeLoading extends AbstractStatsBase<Void> { * @param ownerUserId the user id which runs the code loading the file * @param loadingPackageName the package performing the load * @return whether new information has been recorded * @throw IllegalArgumentException if clearly invalid information is detected * @throws IllegalArgumentException if clearly invalid information is detected */ boolean record(String owningPackageName, String filePath, int fileType, int ownerUserId, String loadingPackageName) { Loading
services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java +99 −12 Original line number Diff line number Diff line Loading @@ -18,10 +18,14 @@ package com.android.server.pm.dex; import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo; import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo; import static com.android.server.pm.dex.PackageDynamicCodeLoading.DynamicCodeFile; import static com.android.server.pm.dex.PackageDynamicCodeLoading.PackageDynamicCode; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; Loading @@ -41,6 +45,10 @@ import androidx.test.runner.AndroidJUnit4; import com.android.server.pm.Installer; import dalvik.system.DelegateLastClassLoader; import dalvik.system.PathClassLoader; import dalvik.system.VMRuntime; import org.junit.Before; import org.junit.Rule; import org.junit.Test; Loading @@ -50,10 +58,6 @@ import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.mockito.quality.Strictness; import dalvik.system.DelegateLastClassLoader; import dalvik.system.PathClassLoader; import dalvik.system.VMRuntime; import java.io.File; import java.util.ArrayList; import java.util.Arrays; Loading Loading @@ -129,6 +133,9 @@ public class DexManagerTests { // Package is not used by others, so we should get nothing back. assertNoUseInfo(mFooUser0); // A package loading its own code is not stored as DCL. assertNoDclInfo(mFooUser0); } @Test Loading @@ -140,6 +147,8 @@ public class DexManagerTests { PackageUseInfo pui = getPackageUseInfo(mBarUser0); assertIsUsedByOtherApps(mBarUser0, pui, true); assertTrue(pui.getDexUseInfoMap().isEmpty()); assertHasDclInfo(mBarUser0, mFooUser0, mBarUser0.getBaseAndSplitDexPaths()); } @Test Loading @@ -152,6 +161,8 @@ public class DexManagerTests { assertIsUsedByOtherApps(mFooUser0, pui, false); assertEquals(fooSecondaries.size(), pui.getDexUseInfoMap().size()); assertSecondaryUse(mFooUser0, pui, fooSecondaries, /*isUsedByOtherApps*/false, mUser0); assertHasDclInfo(mFooUser0, mFooUser0, fooSecondaries); } @Test Loading @@ -164,6 +175,8 @@ public class DexManagerTests { assertIsUsedByOtherApps(mBarUser0, pui, false); assertEquals(barSecondaries.size(), pui.getDexUseInfoMap().size()); assertSecondaryUse(mFooUser0, pui, barSecondaries, /*isUsedByOtherApps*/true, mUser0); assertHasDclInfo(mBarUser0, mFooUser0, barSecondaries); } @Test Loading Loading @@ -200,9 +213,10 @@ public class DexManagerTests { } @Test public void testPackageUseInfoNotFound() { public void testNoNotify() { // Assert we don't get back data we did not previously record. assertNoUseInfo(mFooUser0); assertNoDclInfo(mFooUser0); } @Test Loading @@ -210,6 +224,7 @@ public class DexManagerTests { // Notifying with an invalid ISA should be ignored. notifyDexLoad(mInvalidIsa, mInvalidIsa.getSecondaryDexPaths(), mUser0); assertNoUseInfo(mInvalidIsa); assertNoDclInfo(mInvalidIsa); } @Test Loading @@ -218,6 +233,7 @@ public class DexManagerTests { // register in DexManager#load should be ignored. notifyDexLoad(mDoesNotExist, mDoesNotExist.getBaseAndSplitDexPaths(), mUser0); assertNoUseInfo(mDoesNotExist); assertNoDclInfo(mDoesNotExist); } @Test Loading @@ -226,6 +242,8 @@ public class DexManagerTests { // Request should be ignored. notifyDexLoad(mBarUser1, mBarUser0.getSecondaryDexPaths(), mUser1); assertNoUseInfo(mBarUser1); assertNoDclInfo(mBarUser1); } @Test Loading @@ -235,6 +253,10 @@ public class DexManagerTests { // still check that nothing goes unexpected in DexManager. notifyDexLoad(mBarUser0, mFooUser0.getBaseAndSplitDexPaths(), mUser1); assertNoUseInfo(mBarUser1); assertNoUseInfo(mFooUser0); assertNoDclInfo(mBarUser1); assertNoDclInfo(mFooUser0); } @Test Loading @@ -247,6 +269,7 @@ public class DexManagerTests { // is trying to load something from it we should not find it. notifyDexLoad(mFooUser0, newSecondaries, mUser0); assertNoUseInfo(newPackage); assertNoDclInfo(newPackage); // Notify about newPackage install and let mFoo load its dexes. mDexManager.notifyPackageInstalled(newPackage.mPackageInfo, mUser0); Loading @@ -257,6 +280,7 @@ public class DexManagerTests { assertIsUsedByOtherApps(newPackage, pui, false); assertEquals(newSecondaries.size(), pui.getDexUseInfoMap().size()); assertSecondaryUse(newPackage, pui, newSecondaries, /*isUsedByOtherApps*/true, mUser0); assertHasDclInfo(newPackage, mFooUser0, newSecondaries); } @Test Loading @@ -273,6 +297,7 @@ public class DexManagerTests { assertIsUsedByOtherApps(newPackage, pui, false); assertEquals(newSecondaries.size(), pui.getDexUseInfoMap().size()); assertSecondaryUse(newPackage, pui, newSecondaries, /*isUsedByOtherApps*/false, mUser0); assertHasDclInfo(newPackage, newPackage, newSecondaries); } @Test Loading Loading @@ -305,6 +330,7 @@ public class DexManagerTests { // We shouldn't find yet the new split as we didn't notify the package update. notifyDexLoad(mFooUser0, newSplits, mUser0); assertNoUseInfo(mBarUser0); assertNoDclInfo(mBarUser0); // Notify that bar is updated. splitSourceDirs will contain the updated path. mDexManager.notifyPackageUpdated(mBarUser0.getPackageName(), Loading @@ -314,8 +340,8 @@ public class DexManagerTests { // Now, when the split is loaded we will find it and we should mark Bar as usedByOthers. notifyDexLoad(mFooUser0, newSplits, mUser0); PackageUseInfo pui = getPackageUseInfo(mBarUser0); assertNotNull(pui); assertIsUsedByOtherApps(newSplits, pui, true); assertHasDclInfo(mBarUser0, mFooUser0, newSplits); } @Test Loading @@ -326,11 +352,15 @@ public class DexManagerTests { mDexManager.notifyPackageDataDestroyed(mBarUser0.getPackageName(), mUser0); // Bar should not be around since it was removed for all users. // Data for user 1 should still be present PackageUseInfo pui = getPackageUseInfo(mBarUser1); assertNotNull(pui); assertSecondaryUse(mBarUser1, pui, mBarUser1.getSecondaryDexPaths(), /*isUsedByOtherApps*/false, mUser1); assertHasDclInfo(mBarUser1, mBarUser1, mBarUser1.getSecondaryDexPaths()); // But not user 0 assertNoUseInfo(mBarUser0, mUser0); assertNoDclInfo(mBarUser0, mUser0); } @Test Loading @@ -349,6 +379,8 @@ public class DexManagerTests { PackageUseInfo pui = getPackageUseInfo(mFooUser0); assertIsUsedByOtherApps(mFooUser0, pui, true); assertTrue(pui.getDexUseInfoMap().isEmpty()); assertNoDclInfo(mFooUser0); } @Test Loading @@ -362,6 +394,7 @@ public class DexManagerTests { // Foo should not be around since all its secondary dex info were deleted // and it is not used by other apps. assertNoUseInfo(mFooUser0); assertNoDclInfo(mFooUser0); } @Test Loading @@ -374,6 +407,7 @@ public class DexManagerTests { // Bar should not be around since it was removed for all users. assertNoUseInfo(mBarUser0); assertNoDclInfo(mBarUser0); } @Test Loading @@ -383,6 +417,7 @@ public class DexManagerTests { notifyDexLoad(mFooUser0, Arrays.asList(frameworkDex), mUser0); // The dex file should not be recognized as a package. assertFalse(mDexManager.hasInfoOnPackage(frameworkDex)); assertNull(mDexManager.getPackageDynamicCodeInfo(frameworkDex)); } @Test Loading @@ -395,6 +430,8 @@ public class DexManagerTests { assertIsUsedByOtherApps(mFooUser0, pui, false); assertEquals(fooSecondaries.size(), pui.getDexUseInfoMap().size()); assertSecondaryUse(mFooUser0, pui, fooSecondaries, /*isUsedByOtherApps*/false, mUser0); assertHasDclInfo(mFooUser0, mFooUser0, fooSecondaries); } @Test Loading @@ -402,7 +439,12 @@ public class DexManagerTests { List<String> secondaries = mBarUser0UnsupportedClassLoader.getSecondaryDexPaths(); notifyDexLoad(mBarUser0UnsupportedClassLoader, secondaries, mUser0); // We don't record the dex usage assertNoUseInfo(mBarUser0UnsupportedClassLoader); // But we do record this as an intance of dynamic code loading assertHasDclInfo( mBarUser0UnsupportedClassLoader, mBarUser0UnsupportedClassLoader, secondaries); } @Test Loading @@ -414,6 +456,8 @@ public class DexManagerTests { notifyDexLoad(mBarUser0, classLoaders, classPaths, mUser0); assertNoUseInfo(mBarUser0); assertHasDclInfo(mBarUser0, mBarUser0, mBarUser0.getSecondaryDexPaths()); } @Test Loading @@ -421,6 +465,7 @@ public class DexManagerTests { notifyDexLoad(mBarUser0, null, mUser0); assertNoUseInfo(mBarUser0); assertNoDclInfo(mBarUser0); } @Test Loading Loading @@ -455,12 +500,14 @@ public class DexManagerTests { notifyDexLoad(mBarUser0, secondaries, mUser0); PackageUseInfo pui = getPackageUseInfo(mBarUser0); assertSecondaryUse(mBarUser0, pui, secondaries, /*isUsedByOtherApps*/false, mUser0); assertHasDclInfo(mBarUser0, mBarUser0, secondaries); // Record bar secondaries again with an unsupported class loader. This should not change the // context. notifyDexLoad(mBarUser0UnsupportedClassLoader, secondaries, mUser0); pui = getPackageUseInfo(mBarUser0); assertSecondaryUse(mBarUser0, pui, secondaries, /*isUsedByOtherApps*/false, mUser0); assertHasDclInfo(mBarUser0, mBarUser0, secondaries); } @Test Loading Loading @@ -533,13 +580,53 @@ public class DexManagerTests { private PackageUseInfo getPackageUseInfo(TestData testData) { assertTrue(mDexManager.hasInfoOnPackage(testData.getPackageName())); return mDexManager.getPackageUseInfoOrDefault(testData.getPackageName()); PackageUseInfo pui = mDexManager.getPackageUseInfoOrDefault(testData.getPackageName()); assertNotNull(pui); return pui; } private void assertNoUseInfo(TestData testData) { assertFalse(mDexManager.hasInfoOnPackage(testData.getPackageName())); } private void assertNoUseInfo(TestData testData, int userId) { if (!mDexManager.hasInfoOnPackage(testData.getPackageName())) { return; } PackageUseInfo pui = getPackageUseInfo(testData); for (DexUseInfo dexUseInfo : pui.getDexUseInfoMap().values()) { assertNotEquals(userId, dexUseInfo.getOwnerUserId()); } } private void assertNoDclInfo(TestData testData) { assertNull(mDexManager.getPackageDynamicCodeInfo(testData.getPackageName())); } private void assertNoDclInfo(TestData testData, int userId) { PackageDynamicCode info = mDexManager.getPackageDynamicCodeInfo(testData.getPackageName()); if (info == null) { return; } for (DynamicCodeFile fileInfo : info.mFileUsageMap.values()) { assertNotEquals(userId, fileInfo.mUserId); } } private void assertHasDclInfo(TestData owner, TestData loader, List<String> paths) { PackageDynamicCode info = mDexManager.getPackageDynamicCodeInfo(owner.getPackageName()); assertNotNull("No DCL data for owner " + owner.getPackageName(), info); for (String path : paths) { DynamicCodeFile fileInfo = info.mFileUsageMap.get(path); assertNotNull("No DCL data for path " + path, fileInfo); assertEquals(PackageDynamicCodeLoading.FILE_TYPE_DEX, fileInfo.mFileType); assertEquals(owner.mUserId, fileInfo.mUserId); assertTrue("No DCL data for loader " + loader.getPackageName(), fileInfo.mLoadingPackages.contains(loader.getPackageName())); } } private static PackageInfo getMockPackageInfo(String packageName, int userId) { PackageInfo pi = new PackageInfo(); pi.packageName = packageName; Loading @@ -563,11 +650,13 @@ public class DexManagerTests { private final PackageInfo mPackageInfo; private final String mLoaderIsa; private final String mClassLoader; private final int mUserId; private TestData(String packageName, String loaderIsa, int userId, String classLoader) { mPackageInfo = getMockPackageInfo(packageName, userId); mLoaderIsa = loaderIsa; mClassLoader = classLoader; mUserId = userId; } private TestData(String packageName, String loaderIsa, int userId) { Loading Loading @@ -603,9 +692,7 @@ public class DexManagerTests { List<String> getBaseAndSplitDexPaths() { List<String> paths = new ArrayList<>(); paths.add(mPackageInfo.applicationInfo.sourceDir); for (String split : mPackageInfo.applicationInfo.splitSourceDirs) { paths.add(split); } Collections.addAll(paths, mPackageInfo.applicationInfo.splitSourceDirs); return paths; } Loading