Exempt hibernating apps from dex optimization
We previously only checked whether an app could be optimized in PackageManagerService#getOptimizablePackages. This missed edge cases where the app would still generate vdex/odex such as when the app is updated. We fix this by considering any hibernating app as not optimizable in all cases. To support unit testing, we also change canOptimizePackage to a non-static method and enumerate PackageDexOptimizer's dependencies clearly with an injector. Bug: 203693378 Test: Reproduce steps in bug, confirm app does not have vdex/odex Test: atest PackageManagerServiceHibernationTests Test: forrest run of asit/dexoptota/self_full Change-Id: I6b88713529a570f285422e6716ad797e2148ce2c
This commit is contained in:
@@ -43,4 +43,9 @@ public abstract class AppHibernationManagerInternal {
|
|||||||
* @see AppHibernationService#setHibernatingGlobally
|
* @see AppHibernationService#setHibernatingGlobally
|
||||||
*/
|
*/
|
||||||
public abstract void setHibernatingGlobally(String packageName, boolean isHibernating);
|
public abstract void setHibernatingGlobally(String packageName, boolean isHibernating);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see AppHibernationService#isOatArtifactDeletionEnabled
|
||||||
|
*/
|
||||||
|
public abstract boolean isOatArtifactDeletionEnabled();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -199,6 +199,14 @@ public final class AppHibernationService extends SystemService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether global hibernation should delete ART ahead-of-time compilation artifacts and prevent
|
||||||
|
* package manager from re-optimizing the APK.
|
||||||
|
*/
|
||||||
|
private boolean isOatArtifactDeletionEnabled() {
|
||||||
|
return mOatArtifactDeletionEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether a package is hibernating for a given user.
|
* Whether a package is hibernating for a given user.
|
||||||
*
|
*
|
||||||
@@ -730,6 +738,11 @@ public final class AppHibernationService extends SystemService {
|
|||||||
public boolean isHibernatingGlobally(String packageName) {
|
public boolean isHibernatingGlobally(String packageName) {
|
||||||
return mService.isHibernatingGlobally(packageName);
|
return mService.isHibernatingGlobally(packageName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOatArtifactDeletionEnabled() {
|
||||||
|
return mService.isOatArtifactDeletionEnabled();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final AppHibernationServiceStub mServiceStub = new AppHibernationServiceStub(this);
|
private final AppHibernationServiceStub mServiceStub = new AppHibernationServiceStub(this);
|
||||||
|
|||||||
@@ -49,8 +49,6 @@ import android.util.Slog;
|
|||||||
import com.android.internal.R;
|
import com.android.internal.R;
|
||||||
import com.android.internal.annotations.GuardedBy;
|
import com.android.internal.annotations.GuardedBy;
|
||||||
import com.android.internal.logging.MetricsLogger;
|
import com.android.internal.logging.MetricsLogger;
|
||||||
import com.android.server.apphibernation.AppHibernationManagerInternal;
|
|
||||||
import com.android.server.apphibernation.AppHibernationService;
|
|
||||||
import com.android.server.pm.dex.DexManager;
|
import com.android.server.pm.dex.DexManager;
|
||||||
import com.android.server.pm.dex.DexoptOptions;
|
import com.android.server.pm.dex.DexoptOptions;
|
||||||
import com.android.server.pm.parsing.pkg.AndroidPackage;
|
import com.android.server.pm.parsing.pkg.AndroidPackage;
|
||||||
@@ -171,7 +169,7 @@ final class DexOptHelper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!PackageDexOptimizer.canOptimizePackage(pkg)) {
|
if (!mPm.mPackageDexOptimizer.canOptimizePackage(pkg)) {
|
||||||
if (DEBUG_DEXOPT) {
|
if (DEBUG_DEXOPT) {
|
||||||
Log.i(TAG, "Skipping update of non-optimizable app " + pkg.getPackageName());
|
Log.i(TAG, "Skipping update of non-optimizable app " + pkg.getPackageName());
|
||||||
}
|
}
|
||||||
@@ -291,16 +289,11 @@ final class DexOptHelper {
|
|||||||
ArraySet<String> pkgs = new ArraySet<>();
|
ArraySet<String> pkgs = new ArraySet<>();
|
||||||
synchronized (mPm.mLock) {
|
synchronized (mPm.mLock) {
|
||||||
for (AndroidPackage p : mPm.mPackages.values()) {
|
for (AndroidPackage p : mPm.mPackages.values()) {
|
||||||
if (PackageDexOptimizer.canOptimizePackage(p)) {
|
if (mPm.mPackageDexOptimizer.canOptimizePackage(p)) {
|
||||||
pkgs.add(p.getPackageName());
|
pkgs.add(p.getPackageName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (AppHibernationService.isAppHibernationEnabled()) {
|
|
||||||
AppHibernationManagerInternal appHibernationManager =
|
|
||||||
mPm.mInjector.getLocalService(AppHibernationManagerInternal.class);
|
|
||||||
pkgs.removeIf(pkgName -> appHibernationManager.isHibernatingGlobally(pkgName));
|
|
||||||
}
|
|
||||||
return pkgs;
|
return pkgs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -387,7 +387,7 @@ public class OtaDexoptService extends IOtaDexopt.Stub {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Does the package have code? If not, there won't be any artifacts.
|
// Does the package have code? If not, there won't be any artifacts.
|
||||||
if (!PackageDexOptimizer.canOptimizePackage(pkg)) {
|
if (!mPackageManagerService.mPackageDexOptimizer.canOptimizePackage(pkg)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (pkg.getPath() == null) {
|
if (pkg.getPath() == null) {
|
||||||
|
|||||||
@@ -64,7 +64,10 @@ import android.util.Slog;
|
|||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
|
|
||||||
import com.android.internal.annotations.GuardedBy;
|
import com.android.internal.annotations.GuardedBy;
|
||||||
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
import com.android.internal.util.IndentingPrintWriter;
|
import com.android.internal.util.IndentingPrintWriter;
|
||||||
|
import com.android.server.LocalServices;
|
||||||
|
import com.android.server.apphibernation.AppHibernationManagerInternal;
|
||||||
import com.android.server.pm.Installer.InstallerException;
|
import com.android.server.pm.Installer.InstallerException;
|
||||||
import com.android.server.pm.dex.ArtManagerService;
|
import com.android.server.pm.dex.ArtManagerService;
|
||||||
import com.android.server.pm.dex.ArtStatsLogUtils;
|
import com.android.server.pm.dex.ArtStatsLogUtils;
|
||||||
@@ -134,16 +137,24 @@ public class PackageDexOptimizer {
|
|||||||
private volatile boolean mSystemReady;
|
private volatile boolean mSystemReady;
|
||||||
|
|
||||||
private final ArtStatsLogger mArtStatsLogger = new ArtStatsLogger();
|
private final ArtStatsLogger mArtStatsLogger = new ArtStatsLogger();
|
||||||
|
private final Injector mInjector;
|
||||||
|
|
||||||
|
|
||||||
private static final Random sRandom = new Random();
|
private static final Random sRandom = new Random();
|
||||||
|
|
||||||
PackageDexOptimizer(Installer installer, Object installLock, Context context,
|
PackageDexOptimizer(Installer installer, Object installLock, Context context,
|
||||||
String wakeLockTag) {
|
String wakeLockTag) {
|
||||||
this.mInstaller = installer;
|
this(new Injector() {
|
||||||
this.mInstallLock = installLock;
|
@Override
|
||||||
|
public AppHibernationManagerInternal getAppHibernationManagerInternal() {
|
||||||
|
return LocalServices.getService(AppHibernationManagerInternal.class);
|
||||||
|
}
|
||||||
|
|
||||||
PowerManager powerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
|
@Override
|
||||||
mDexoptWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, wakeLockTag);
|
public PowerManager getPowerManager(Context context) {
|
||||||
|
return context.getSystemService(PowerManager.class);
|
||||||
|
}
|
||||||
|
}, installer, installLock, context, wakeLockTag);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected PackageDexOptimizer(PackageDexOptimizer from) {
|
protected PackageDexOptimizer(PackageDexOptimizer from) {
|
||||||
@@ -151,9 +162,21 @@ public class PackageDexOptimizer {
|
|||||||
this.mInstallLock = from.mInstallLock;
|
this.mInstallLock = from.mInstallLock;
|
||||||
this.mDexoptWakeLock = from.mDexoptWakeLock;
|
this.mDexoptWakeLock = from.mDexoptWakeLock;
|
||||||
this.mSystemReady = from.mSystemReady;
|
this.mSystemReady = from.mSystemReady;
|
||||||
|
this.mInjector = from.mInjector;
|
||||||
}
|
}
|
||||||
|
|
||||||
static boolean canOptimizePackage(AndroidPackage pkg) {
|
@VisibleForTesting
|
||||||
|
PackageDexOptimizer(@NonNull Injector injector, Installer installer, Object installLock,
|
||||||
|
Context context, String wakeLockTag) {
|
||||||
|
this.mInstaller = installer;
|
||||||
|
this.mInstallLock = installLock;
|
||||||
|
|
||||||
|
PowerManager powerManager = injector.getPowerManager(context);
|
||||||
|
mDexoptWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, wakeLockTag);
|
||||||
|
mInjector = injector;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean canOptimizePackage(AndroidPackage pkg) {
|
||||||
// We do not dexopt a package with no code.
|
// We do not dexopt a package with no code.
|
||||||
// Note that the system package is marked as having no code, however we can
|
// Note that the system package is marked as having no code, however we can
|
||||||
// still optimize it via dexoptSystemServerPath.
|
// still optimize it via dexoptSystemServerPath.
|
||||||
@@ -161,6 +184,17 @@ public class PackageDexOptimizer {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We do not dexopt unused packages.
|
||||||
|
// It's possible for this to be called before app hibernation service is ready due to
|
||||||
|
// an OTA dexopt. In this case, we ignore the hibernation check here. This is fine since
|
||||||
|
// a hibernating app should have no artifacts to copy in the first place.
|
||||||
|
AppHibernationManagerInternal ahm = mInjector.getAppHibernationManagerInternal();
|
||||||
|
if (ahm != null
|
||||||
|
&& ahm.isHibernatingGlobally(pkg.getPackageName())
|
||||||
|
&& ahm.isOatArtifactDeletionEnabled()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1000,4 +1034,13 @@ public class PackageDexOptimizer {
|
|||||||
private Installer getInstallerWithoutLock() {
|
private Installer getInstallerWithoutLock() {
|
||||||
return mInstaller;
|
return mInstaller;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injector for {@link PackageDexOptimizer} dependencies
|
||||||
|
*/
|
||||||
|
interface Injector {
|
||||||
|
AppHibernationManagerInternal getAppHibernationManagerInternal();
|
||||||
|
|
||||||
|
PowerManager getPowerManager(Context context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,8 +16,10 @@
|
|||||||
|
|
||||||
package com.android.server.pm
|
package com.android.server.pm
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
|
import android.os.PowerManager
|
||||||
import android.provider.DeviceConfig
|
import android.provider.DeviceConfig
|
||||||
import android.provider.DeviceConfig.NAMESPACE_APP_HIBERNATION
|
import android.provider.DeviceConfig.NAMESPACE_APP_HIBERNATION
|
||||||
import android.testing.AndroidTestingRunner
|
import android.testing.AndroidTestingRunner
|
||||||
@@ -55,6 +57,8 @@ class PackageManagerServiceHibernationTests {
|
|||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
lateinit var appHibernationManager: AppHibernationManagerInternal
|
lateinit var appHibernationManager: AppHibernationManagerInternal
|
||||||
|
@Mock
|
||||||
|
lateinit var powerManager: PowerManager
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
@Throws(Exception::class)
|
@Throws(Exception::class)
|
||||||
@@ -68,6 +72,24 @@ class PackageManagerServiceHibernationTests {
|
|||||||
.thenReturn(appHibernationManager)
|
.thenReturn(appHibernationManager)
|
||||||
whenever(rule.mocks().injector.handler)
|
whenever(rule.mocks().injector.handler)
|
||||||
.thenReturn(Handler(TestableLooper.get(this).looper))
|
.thenReturn(Handler(TestableLooper.get(this).looper))
|
||||||
|
val injector = object : PackageDexOptimizer.Injector {
|
||||||
|
override fun getAppHibernationManagerInternal(): AppHibernationManagerInternal {
|
||||||
|
return appHibernationManager
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getPowerManager(context: Context?): PowerManager {
|
||||||
|
return powerManager
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val packageDexOptimizer = PackageDexOptimizer(
|
||||||
|
injector,
|
||||||
|
rule.mocks().installer,
|
||||||
|
rule.mocks().installLock,
|
||||||
|
rule.mocks().context,
|
||||||
|
"*dexopt*")
|
||||||
|
whenever(rule.mocks().injector.packageDexOptimizer)
|
||||||
|
.thenReturn(packageDexOptimizer)
|
||||||
|
whenever(appHibernationManager.isOatArtifactDeletionEnabled).thenReturn(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
Reference in New Issue
Block a user