summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Keun-young Park <keunyoung@google.com> 2021-10-18 18:51:00 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2021-10-18 18:51:00 +0000
commit0cc800c66ea45f98a0bc4246c26d3ff0859d6464 (patch)
tree09e986232cf1e56cea9c03613d42b4d8764ec190
parent073f23ece58db2a95c40c22fd5dfda4574904758 (diff)
parentf992cb310195fe4b9cc942daca964ea229df9cb1 (diff)
Merge "add cancellation to background dexopt"
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl6
-rw-r--r--core/res/AndroidManifest.xml2
-rw-r--r--services/core/java/com/android/server/pm/BackgroundDexOptJobService.java37
-rw-r--r--services/core/java/com/android/server/pm/BackgroundDexOptService.java974
-rw-r--r--services/core/java/com/android/server/pm/ComputerEngine.java6
-rw-r--r--services/core/java/com/android/server/pm/InstallParams.java2
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java29
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerServiceInjector.java9
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java1
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerShellCommand.java17
-rw-r--r--services/java/com/android/server/SystemServer.java10
-rw-r--r--startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java17
12 files changed, 703 insertions, 407 deletions
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 7ac385beffc3..46f179797e4a 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -599,12 +599,6 @@ interface IPackageManager {
void forceDexOpt(String packageName);
/**
- * Execute the background dexopt job immediately on packages in packageNames.
- * If null, then execute on all packages.
- */
- boolean runBackgroundDexoptJob(in List<String> packageNames);
-
- /**
* 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
* deleted, update the internal records and delete the generated oat files.
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index d0910fcfc836..5a5337449b79 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -6311,7 +6311,7 @@
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
- <service android:name="com.android.server.pm.BackgroundDexOptService"
+ <service android:name="com.android.server.pm.BackgroundDexOptJobService"
android:exported="true"
android:permission="android.permission.BIND_JOB_SERVICE">
</service>
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptJobService.java b/services/core/java/com/android/server/pm/BackgroundDexOptJobService.java
new file mode 100644
index 000000000000..d9452742f99c
--- /dev/null
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptJobService.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 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 com.android.server.pm;
+
+import android.app.job.JobParameters;
+import android.app.job.JobService;
+
+/**
+ * JobService to run background dex optimization. This is a thin wrapper and most logic exits in
+ * {@link BackgroundDexOptService}.
+ */
+public final class BackgroundDexOptJobService extends JobService {
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ return BackgroundDexOptService.getService().onStartJob(this, params);
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ return BackgroundDexOptService.getService().onStopJob(this, params);
+ }
+}
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index 49a0a8827699..44e8e6646179 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -18,11 +18,11 @@ package com.android.server.pm;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
+import android.annotation.IntDef;
import android.annotation.Nullable;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobScheduler;
-import android.app.job.JobService;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -30,63 +30,88 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageInfo;
import android.os.BatteryManagerInternal;
+import android.os.Binder;
import android.os.Environment;
import android.os.IThermalService;
import android.os.PowerManager;
+import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
+import android.os.Trace;
import android.os.UserHandle;
import android.os.storage.StorageManager;
import android.util.ArraySet;
import android.util.Log;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.util.IndentingPrintWriter;
import com.android.server.LocalServices;
import com.android.server.PinnerService;
+import com.android.server.pm.PackageDexOptimizer.DexOptResult;
import com.android.server.pm.dex.DexManager;
import com.android.server.pm.dex.DexoptOptions;
+import com.android.server.utils.TimingsTraceAndSlog;
import java.io.File;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
/**
- * {@hide}
+ * Controls background dex optimization run as idle job or command line.
*/
-public class BackgroundDexOptService extends JobService {
+public final class BackgroundDexOptService {
private static final String TAG = "BackgroundDexOptService";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- private static final int JOB_IDLE_OPTIMIZE = 800;
- private static final int JOB_POST_BOOT_UPDATE = 801;
+ @VisibleForTesting
+ static final int JOB_IDLE_OPTIMIZE = 800;
+ @VisibleForTesting
+ static final int JOB_POST_BOOT_UPDATE = 801;
private static final long IDLE_OPTIMIZATION_PERIOD = TimeUnit.DAYS.toMillis(1);
- private static ComponentName sDexoptServiceName = new ComponentName(
- "android",
- BackgroundDexOptService.class.getName());
+ private static final long CANCELLATION_WAIT_CHECK_INTERVAL_MS = 200;
- // Possible return codes of individual optimization steps.
+ private static ComponentName sDexoptServiceName = new ComponentName("android",
+ BackgroundDexOptJobService.class.getName());
- // Optimizations finished. All packages were processed.
- private static final int OPTIMIZE_PROCESSED = 0;
- // Optimizations should continue. Issued after checking the scheduler, disk space or battery.
- private static final int OPTIMIZE_CONTINUE = 1;
- // Optimizations should be aborted. Job scheduler requested it.
- private static final int OPTIMIZE_ABORT_BY_JOB_SCHEDULER = 2;
- // Optimizations should be aborted. No space left on device.
- private static final int OPTIMIZE_ABORT_NO_SPACE_LEFT = 3;
- // Optimizations should be aborted. Thermal throttling level too high.
- private static final int OPTIMIZE_ABORT_THERMAL = 4;
+ // Possible return codes of individual optimization steps.
+ /** Ok status: Optimizations finished, All packages were processed, can continue */
+ private static final int STATUS_OK = 0;
+ /** Optimizations should be aborted. Job scheduler requested it. */
+ private static final int STATUS_ABORT_BY_CANCELLATION = 1;
+ /** Optimizations should be aborted. No space left on device. */
+ private static final int STATUS_ABORT_NO_SPACE_LEFT = 2;
+ /** Optimizations should be aborted. Thermal throttling level too high. */
+ private static final int STATUS_ABORT_THERMAL = 3;
+ /** Battery level too low */
+ private static final int STATUS_ABORT_BATTERY = 4;
+ /** {@link PackageDexOptimizer#DEX_OPT_FAILED} case */
+ private static final int STATUS_DEX_OPT_FAILED = 5;
+
+ @IntDef(prefix = {"STATUS_"}, value = {
+ STATUS_OK,
+ STATUS_ABORT_BY_CANCELLATION,
+ STATUS_ABORT_NO_SPACE_LEFT,
+ STATUS_ABORT_THERMAL,
+ STATUS_ABORT_BATTERY,
+ STATUS_DEX_OPT_FAILED,
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ private @interface Status {
+ }
// Used for calculating space threshold for downgrading unused apps.
private static final int LOW_THRESHOLD_MULTIPLIER_FOR_DOWNGRADE = 2;
@@ -94,213 +119,407 @@ public class BackgroundDexOptService extends JobService {
// Thermal cutoff value used if one isn't defined by a system property.
private static final int THERMAL_CUTOFF_DEFAULT = PowerManager.THERMAL_STATUS_MODERATE;
+ private final Injector mInjector;
+
+ private final Object mLock = new Object();
+
+ // Thread currently running dexopt. This will be null if dexopt is not running.
+ // The thread running dexopt make sure to set this into null when the pending dexopt is
+ // completed.
+ @GuardedBy("mLock")
+ @Nullable
+ private Thread mDexOptThread;
+
+ // Thread currently cancelling dexopt. This thread is in blocked wait state until
+ // cancellation is done. Only this thread can change states for control. The other threads, if
+ // need to wait for cancellation, should just wait without doing any control.
+ @GuardedBy("mLock")
+ @Nullable
+ private Thread mDexOptCancellingThread;
+
+ // Tells whether post boot update is completed or not.
+ @GuardedBy("mLock")
+ private boolean mFinishedPostBootUpdate;
+
+ @GuardedBy("mLock")
+ @Status private int mLastExecutionStatus = STATUS_OK;
+
+ // Keeps packages cancelled from PDO for last session. This is for debugging.
+ @GuardedBy("mLock")
+ private final ArraySet<String> mLastCancelledPackages = new ArraySet<String>();
+
/**
* Set of failed packages remembered across job runs.
*/
- static final ArraySet<String> sFailedPackageNamesPrimary = new ArraySet<String>();
- static final ArraySet<String> sFailedPackageNamesSecondary = new ArraySet<String>();
+ @GuardedBy("mLock")
+ private final ArraySet<String> mFailedPackageNamesPrimary = new ArraySet<String>();
+ @GuardedBy("mLock")
+ private final ArraySet<String> mFailedPackageNamesSecondary = new ArraySet<String>();
- /**
- * Atomics set to true if the JobScheduler requests an abort.
- */
- private final AtomicBoolean mAbortPostBootUpdate = new AtomicBoolean(false);
- private final AtomicBoolean mAbortIdleOptimization = new AtomicBoolean(false);
+ private final long mDowngradeUnusedAppsThresholdInMillis;
- /**
- * Atomic set to true if one job should exit early because another job was started.
- */
- private final AtomicBoolean mExitPostBootUpdate = new AtomicBoolean(false);
+ private List<PackagesUpdatedListener> mPackagesUpdatedListeners = new ArrayList<>();
- private final File mDataDir = Environment.getDataDirectory();
- private static final long mDowngradeUnusedAppsThresholdInMillis =
- getDowngradeUnusedAppsThresholdInMillis();
+ private int mThermalStatusCutoff = THERMAL_CUTOFF_DEFAULT;
- private final IThermalService mThermalService =
- IThermalService.Stub.asInterface(
- ServiceManager.getService(Context.THERMAL_SERVICE));
+ /** Listener for monitoring package change due to dexopt. */
+ public interface PackagesUpdatedListener {
+ /** Called when the packages are updated through dexopt */
+ void onPackagesUpdated(ArraySet<String> updatedPackages);
+ }
- private static List<PackagesUpdatedListener> sPackagesUpdatedListeners = new ArrayList<>();
+ public BackgroundDexOptService(Context context, DexManager dexManager) {
+ this(new Injector(context, dexManager));
+ }
- private int mThermalStatusCutoff = THERMAL_CUTOFF_DEFAULT;
+ @VisibleForTesting
+ public BackgroundDexOptService(Injector injector) {
+ mInjector = injector;
+ LocalServices.addService(BackgroundDexOptService.class, this);
+ mDowngradeUnusedAppsThresholdInMillis = mInjector.getDowngradeUnusedAppsThresholdInMillis();
+ }
- public static void schedule(Context context) {
- if (isBackgroundDexoptDisabled()) {
+ /** Start scheduling job after boot completion */
+ public void systemReady() {
+ if (mInjector.isBackgroundDexOptDisabled()) {
return;
}
- final JobScheduler js = context.getSystemService(JobScheduler.class);
-
- // Schedule a one-off job which scans installed packages and updates
- // out-of-date oat files. Schedule it 10 minutes after the boot complete event,
- // so that we don't overload the boot with additional dex2oat compilations.
- context.registerReceiver(new BroadcastReceiver() {
+ mInjector.getContext().registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- js.schedule(new JobInfo.Builder(JOB_POST_BOOT_UPDATE, sDexoptServiceName)
- .setMinimumLatency(TimeUnit.MINUTES.toMillis(10))
- .setOverrideDeadline(TimeUnit.MINUTES.toMillis(60))
- .build());
- context.unregisterReceiver(this);
+ mInjector.getContext().unregisterReceiver(this);
+ // queue both job. JOB_IDLE_OPTIMIZE will not start until JOB_POST_BOOT_UPDATE is
+ // completed.
+ scheduleAJob(JOB_POST_BOOT_UPDATE);
+ scheduleAJob(JOB_IDLE_OPTIMIZE);
if (DEBUG) {
- Slog.i(TAG, "BootBgDexopt scheduled");
+ Slog.d(TAG, "BootBgDexopt scheduled");
}
}
}, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
+ }
- // Schedule a daily job which scans installed packages and compiles
- // those with fresh profiling data.
- js.schedule(new JobInfo.Builder(JOB_IDLE_OPTIMIZE, sDexoptServiceName)
- .setRequiresDeviceIdle(true)
- .setRequiresCharging(true)
- .setPeriodic(IDLE_OPTIMIZATION_PERIOD)
- .build());
-
- if (DEBUG) {
- Slog.d(TAG, "BgDexopt scheduled");
+ /** Dump the current state */
+ public void dump(IndentingPrintWriter writer) {
+ boolean disabled = mInjector.isBackgroundDexOptDisabled();
+ writer.print("enabled:");
+ writer.println(!disabled);
+ if (disabled) {
+ return;
+ }
+ synchronized (mLock) {
+ writer.print("mDexOptThread:");
+ writer.println(mDexOptThread);
+ writer.print("mDexOptCancellingThread:");
+ writer.println(mDexOptCancellingThread);
+ writer.print("mFinishedPostBootUpdate:");
+ writer.print(mFinishedPostBootUpdate);
+ writer.print(",mLastExecutionStatus:");
+ writer.println(mLastExecutionStatus);
+ writer.print("mLastCancelledPackages:");
+ writer.println(String.join(",", mLastCancelledPackages));
+ writer.print("mFailedPackageNamesPrimary:");
+ writer.println(String.join(",", mFailedPackageNamesPrimary));
+ writer.print("mFailedPackageNamesSecondary:");
+ writer.println(String.join(",", mFailedPackageNamesSecondary));
}
}
- public static void notifyPackageChanged(String packageName) {
- // The idle maintanance job skips packages which previously failed to
- // compile. The given package has changed and may successfully compile
- // now. Remove it from the list of known failing packages.
- synchronized (sFailedPackageNamesPrimary) {
- sFailedPackageNamesPrimary.remove(packageName);
+ /** Gets the instance of the service */
+ public static BackgroundDexOptService getService() {
+ return LocalServices.getService(BackgroundDexOptService.class);
+ }
+
+ /**
+ * Executes the background dexopt job immediately for selected packages or all packages.
+ *
+ * <p>This is only for shell command and only root or shell user can use this.
+ *
+ * @param packageNames dex optimize the passed packages or all packages if null
+ *
+ * @return true if dex optimization is complete. false if the task is cancelled or if there was
+ * an error.
+ */
+ public boolean runBackgroundDexoptJob(@Nullable List<String> packageNames) {
+ enforceRootOrShell();
+ long identity = Binder.clearCallingIdentity();
+ try {
+ synchronized (mLock) {
+ // Do not cancel and wait for completion if there is pending task.
+ waitForDexOptThreadToFinishLocked();
+ resetStatesForNewDexOptRunLocked(Thread.currentThread());
+ }
+ PackageManagerService pm = mInjector.getPackageManagerService();
+ ArraySet<String> packagesToOptimize;
+ if (packageNames == null) {
+ packagesToOptimize = pm.getOptimizablePackages();
+ } else {
+ packagesToOptimize = new ArraySet<>(packageNames);
+ }
+ return runIdleOptimization(pm, packagesToOptimize, /* isPostBootUpdate= */ false);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ markDexOptCompleted();
}
- synchronized (sFailedPackageNamesSecondary) {
- sFailedPackageNamesSecondary.remove(packageName);
+ }
+
+ /**
+ * Cancels currently running any idle optimization tasks started from JobScheduler
+ * or runIdleOptimizationsNow call.
+ *
+ * <p>This is only for shell command and only root or shell user can use this.
+ */
+ public void cancelBackgroundDexoptJob() {
+ enforceRootOrShell();
+ Binder.withCleanCallingIdentity(() -> cancelDexOptAndWaitForCompletion());
+ }
+
+ /** Adds listener for package update */
+ public void addPackagesUpdatedListener(PackagesUpdatedListener listener) {
+ synchronized (mLock) {
+ mPackagesUpdatedListeners.add(listener);
}
}
- private long getLowStorageThreshold(Context context) {
- @SuppressWarnings("deprecation")
- final long lowThreshold = StorageManager.from(context).getStorageLowBytes(mDataDir);
- if (lowThreshold == 0) {
- Slog.e(TAG, "Invalid low storage threshold");
+ /** Removes package update listener */
+ public void removePackagesUpdatedListener(PackagesUpdatedListener listener) {
+ synchronized (mLock) {
+ mPackagesUpdatedListeners.remove(listener);
}
+ }
- return lowThreshold;
+ /**
+ * Notifies package change and removes the package from the failed package list so that
+ * the package can run dexopt again.
+ */
+ public void notifyPackageChanged(String packageName) {
+ // The idle maintenance job skips packages which previously failed to
+ // compile. The given package has changed and may successfully compile
+ // now. Remove it from the list of known failing packages.
+ synchronized (mLock) {
+ mFailedPackageNamesPrimary.remove(packageName);
+ mFailedPackageNamesSecondary.remove(packageName);
+ }
}
- private boolean runPostBootUpdate(final JobParameters jobParams,
- final PackageManagerService pm, final ArraySet<String> pkgs) {
- if (mExitPostBootUpdate.get()) {
- // This job has already been superseded. Do not start it.
+ /** For BackgroundDexOptJobService to dispatch onStartJob event */
+ /* package */ boolean onStartJob(BackgroundDexOptJobService job, JobParameters params) {
+ Slog.i(TAG, "onStartJob:" + params.getJobId());
+
+ boolean isPostBootUpdateJob = params.getJobId() == JOB_POST_BOOT_UPDATE;
+ // NOTE: PackageManagerService.isStorageLow uses a different set of criteria from
+ // the checks above. This check is not "live" - the value is determined by a background
+ // restart with a period of ~1 minute.
+ PackageManagerService pm = mInjector.getPackageManagerService();
+ if (pm.isStorageLow()) {
+ Slog.w(TAG, "Low storage, skipping this run");
+ markPostBootUpdateCompleted(params);
+ return false;
+ }
+
+ ArraySet<String> pkgs = pm.getOptimizablePackages();
+ if (pkgs.isEmpty()) {
+ Slog.i(TAG, "No packages to optimize");
+ markPostBootUpdateCompleted(params);
return false;
}
- new Thread("BackgroundDexOptService_PostBootUpdate") {
- @Override
- public void run() {
- postBootUpdate(jobParams, pm, pkgs);
- }
- }.start();
+ mThermalStatusCutoff = mInjector.getDexOptThermalCutoff();
+
+ synchronized (mLock) {
+ if (mDexOptThread != null && mDexOptThread.isAlive()) {
+ // Other task is already running.
+ return false;
+ }
+ if (!isPostBootUpdateJob && !mFinishedPostBootUpdate) {
+ // Post boot job not finished yet. Run post boot job first.
+ return false;
+ }
+ resetStatesForNewDexOptRunLocked(mInjector.createAndStartThread(
+ "BackgroundDexOptService_" + (isPostBootUpdateJob ? "PostBoot" : "Idle"),
+ () -> {
+ TimingsTraceAndSlog tr = new TimingsTraceAndSlog(TAG,
+ Trace.TRACE_TAG_PACKAGE_MANAGER);
+ tr.traceBegin("jobExecution");
+ boolean completed = false;
+ try {
+ completed = runIdleOptimization(pm, pkgs,
+ params.getJobId() == JOB_POST_BOOT_UPDATE);
+ } finally { // Those cleanup should be done always.
+ tr.traceEnd();
+ Slog.i(TAG, "dexopt finishing. jobid:" + params.getJobId()
+ + " completed:" + completed);
+
+ if (params.getJobId() == JOB_POST_BOOT_UPDATE) {
+ if (completed) {
+ markPostBootUpdateCompleted(params);
+ }
+ // Reschedule when cancelled
+ job.jobFinished(params, !completed);
+ } else {
+ // Periodic job
+ job.jobFinished(params, true);
+ }
+ markDexOptCompleted();
+ }
+ }));
+ }
return true;
}
- private void postBootUpdate(JobParameters jobParams, PackageManagerService pm,
- ArraySet<String> pkgs) {
- final BatteryManagerInternal batteryManagerInternal =
- LocalServices.getService(BatteryManagerInternal.class);
- final long lowThreshold = getLowStorageThreshold(this);
-
- mAbortPostBootUpdate.set(false);
+ /** For BackgroundDexOptJobService to dispatch onStopJob event */
+ /* package */ boolean onStopJob(BackgroundDexOptJobService job, JobParameters params) {
+ Slog.i(TAG, "onStopJob:" + params.getJobId());
+ // This cannot block as it is in main thread, thus dispatch to a newly created thread and
+ // cancel it from there.
+ // As this event does not happen often, creating a new thread is justified rather than
+ // having one thread kept permanently.
+ mInjector.createAndStartThread("DexOptCancel", this::cancelDexOptAndWaitForCompletion);
+ // Always reschedule for cancellation.
+ return true;
+ }
- ArraySet<String> updatedPackages = new ArraySet<>();
- for (String pkg : pkgs) {
- if (mAbortPostBootUpdate.get()) {
- // JobScheduler requested an early abort.
+ /**
+ * Cancels pending dexopt and wait for completion of the cancellation. This can block the caller
+ * until cancellation is done.
+ */
+ private void cancelDexOptAndWaitForCompletion() {
+ synchronized (mLock) {
+ if (mDexOptThread == null) {
return;
}
- if (mExitPostBootUpdate.get()) {
- // Different job, which supersedes this one, is running.
- break;
- }
- if (batteryManagerInternal.getBatteryLevelLow()) {
- // Rather bail than completely drain the battery.
- break;
+ if (mDexOptCancellingThread != null && mDexOptCancellingThread.isAlive()) {
+ // No control, just wait
+ waitForDexOptThreadToFinishLocked();
+ // Do not wait for other cancellation's complete. That will be handled by the next
+ // start flow.
+ return;
}
- long usableSpace = mDataDir.getUsableSpace();
- if (usableSpace < lowThreshold) {
- // Rather bail than completely fill up the disk.
- Slog.w(TAG, "Aborting background dex opt job due to low storage: " +
- usableSpace);
- break;
+ mDexOptCancellingThread = Thread.currentThread();
+ // Take additional caution to make sure that we do not leave this call
+ // with controlDexOptBlockingLocked(true) state.
+ try {
+ controlDexOptBlockingLocked(true);
+ waitForDexOptThreadToFinishLocked();
+ } finally {
+ // Reset to default states regardless of previous states
+ mDexOptCancellingThread = null;
+ mDexOptThread = null;
+ controlDexOptBlockingLocked(false);
+ mLock.notifyAll();
}
- if (DEBUG) {
- Slog.i(TAG, "Updating package " + pkg);
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void waitForDexOptThreadToFinishLocked() {
+ TimingsTraceAndSlog tr = new TimingsTraceAndSlog(TAG, Trace.TRACE_TAG_PACKAGE_MANAGER);
+ tr.traceBegin("waitForDexOptThreadToFinishLocked");
+ try {
+ // Wait but check in regular internal to see if the thread is still alive.
+ while (mDexOptThread != null && mDexOptThread.isAlive()) {
+ mLock.wait(CANCELLATION_WAIT_CHECK_INTERVAL_MS);
}
+ } catch (InterruptedException e) {
+ Slog.w(TAG, "Interrupted while waiting for dexopt thread");
+ Thread.currentThread().interrupt();
+ }
+ tr.traceEnd();
+ }
- // Update package if needed. Note that there can be no race between concurrent
- // jobs because PackageDexOptimizer.performDexOpt is synchronized.
-
- // checkProfiles is false to avoid merging profiles during boot which
- // might interfere with background compilation (b/28612421).
- // Unfortunately this will also means that "pm.dexopt.boot=speed-profile" will
- // behave differently than "pm.dexopt.bg-dexopt=speed-profile" but that's a
- // trade-off worth doing to save boot time work.
- int result = pm.performDexOptWithStatus(new DexoptOptions(
- pkg,
- PackageManagerService.REASON_POST_BOOT,
- DexoptOptions.DEXOPT_BOOT_COMPLETE));
- if (result == PackageDexOptimizer.DEX_OPT_PERFORMED) {
- updatedPackages.add(pkg);
+ private void markDexOptCompleted() {
+ synchronized (mLock) {
+ if (mDexOptThread != Thread.currentThread()) {
+ throw new IllegalStateException(
+ "Only mDexOptThread can mark completion, mDexOptThread:" + mDexOptThread
+ + " current:" + Thread.currentThread());
}
+ mDexOptThread = null;
+ // Other threads may be waiting for completion.
+ mLock.notifyAll();
}
- notifyPinService(updatedPackages);
- notifyPackagesUpdated(updatedPackages);
- // Ran to completion, so we abandon our timeslice and do not reschedule.
- jobFinished(jobParams, /* reschedule */ false);
}
- private boolean runIdleOptimization(final JobParameters jobParams,
- final PackageManagerService pm, final ArraySet<String> pkgs) {
- new Thread("BackgroundDexOptService_IdleOptimization") {
- @Override
- public void run() {
- int result = idleOptimization(pm, pkgs, BackgroundDexOptService.this);
- if (result == OPTIMIZE_PROCESSED) {
- Slog.i(TAG, "Idle optimizations completed.");
- } else if (result == OPTIMIZE_ABORT_NO_SPACE_LEFT) {
- Slog.w(TAG, "Idle optimizations aborted because of space constraints.");
- } else if (result == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
- Slog.w(TAG, "Idle optimizations aborted by job scheduler.");
- } else if (result == OPTIMIZE_ABORT_THERMAL) {
- Slog.w(TAG, "Idle optimizations aborted by thermal throttling.");
- } else {
- Slog.w(TAG, "Idle optimizations ended with unexpected code: " + result);
- }
+ @GuardedBy("mLock")
+ private void resetStatesForNewDexOptRunLocked(Thread thread) {
+ mDexOptThread = thread;
+ mLastCancelledPackages.clear();
+ controlDexOptBlockingLocked(false);
+ }
- if (result == OPTIMIZE_ABORT_THERMAL) {
- // Abandon our timeslice and reschedule
- jobFinished(jobParams, /* wantsReschedule */ true);
- } else if (result != OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
- // Abandon our timeslice and do not reschedule.
- jobFinished(jobParams, /* wantsReschedule */ false);
- }
- }
- }.start();
- return true;
+ private void enforceRootOrShell() {
+ int uid = Binder.getCallingUid();
+ if (uid != Process.ROOT_UID && uid != Process.SHELL_UID) {
+ throw new SecurityException("Should be shell or root user");
+ }
}
- // Optimize the given packages and return the optimization result (one of the OPTIMIZE_* codes).
- private int idleOptimization(PackageManagerService pm, ArraySet<String> pkgs,
- Context context) {
- Slog.i(TAG, "Performing idle optimizations");
- // If post-boot update is still running, request that it exits early.
- mExitPostBootUpdate.set(true);
- mAbortIdleOptimization.set(false);
+ @GuardedBy("mLock")
+ private void controlDexOptBlockingLocked(boolean block) {
+ mInjector.getPackageManagerService().controlDexOptBlocking(block);
+ }
- long lowStorageThreshold = getLowStorageThreshold(context);
- int result = idleOptimizePackages(pm, pkgs, lowStorageThreshold);
- return result;
+ private void scheduleAJob(int jobId) {
+ JobScheduler js = mInjector.getJobScheduler();
+ JobInfo.Builder builder = new JobInfo.Builder(jobId, sDexoptServiceName)
+ .setRequiresDeviceIdle(true);
+ if (jobId == JOB_IDLE_OPTIMIZE) {
+ builder.setRequiresCharging(true)
+ .setPeriodic(IDLE_OPTIMIZATION_PERIOD);
+ }
+ js.schedule(builder.build());
}
- /**
- * Get the size of the directory. It uses recursion to go over all files.
- * @param f
- * @return
- */
+ private long getLowStorageThreshold() {
+ long lowThreshold = mInjector.getDataDirStorageLowBytes();
+ if (lowThreshold == 0) {
+ Slog.e(TAG, "Invalid low storage threshold");
+ }
+
+ return lowThreshold;
+ }
+
+ private void logStatus(int status) {
+ switch (status) {
+ case STATUS_OK:
+ Slog.i(TAG, "Idle optimizations completed.");
+ break;
+ case STATUS_ABORT_NO_SPACE_LEFT:
+ Slog.w(TAG, "Idle optimizations aborted because of space constraints.");
+ break;
+ case STATUS_ABORT_BY_CANCELLATION:
+ Slog.w(TAG, "Idle optimizations aborted by cancellation.");
+ break;
+ case STATUS_ABORT_THERMAL:
+ Slog.w(TAG, "Idle optimizations aborted by thermal throttling.");
+ break;
+ case STATUS_ABORT_BATTERY:
+ Slog.w(TAG, "Idle optimizations aborted by low battery.");
+ break;
+ case STATUS_DEX_OPT_FAILED:
+ Slog.w(TAG, "Idle optimizations failed from dexopt.");
+ break;
+ default:
+ Slog.w(TAG, "Idle optimizations ended with unexpected code: " + status);
+ break;
+ }
+ }
+
+ /** Returns true if completed */
+ private boolean runIdleOptimization(PackageManagerService pm, ArraySet<String> pkgs,
+ boolean isPostBootUpdate) {
+ long lowStorageThreshold = getLowStorageThreshold();
+ int status = idleOptimizePackages(pm, pkgs, lowStorageThreshold, isPostBootUpdate);
+ logStatus(status);
+ synchronized (mLock) {
+ mLastExecutionStatus = status;
+ }
+
+ return status == STATUS_OK;
+ }
+
+ /** Gets the size of the directory. It uses recursion to go over all files. */
private long getDirectorySize(File f) {
long size = 0;
if (f.isDirectory()) {
@@ -313,10 +532,7 @@ public class BackgroundDexOptService extends JobService {
return size;
}
- /**
- * Get the size of a package.
- * @param pkg
- */
+ /** Gets the size of a package. */
private long getPackageSize(PackageManagerService pm, String pkg) {
PackageInfo info = pm.getPackageInfo(pkg, 0, UserHandle.USER_SYSTEM);
long size = 0;
@@ -340,17 +556,18 @@ public class BackgroundDexOptService extends JobService {
return 0;
}
+ @Status
private int idleOptimizePackages(PackageManagerService pm, ArraySet<String> pkgs,
- long lowStorageThreshold) {
+ long lowStorageThreshold, boolean isPostBootUpdate) {
ArraySet<String> updatedPackages = new ArraySet<>();
ArraySet<String> updatedPackagesDueToSecondaryDex = new ArraySet<>();
try {
- final boolean supportSecondaryDex = supportSecondaryDex();
+ boolean supportSecondaryDex = mInjector.supportSecondaryDex();
if (supportSecondaryDex) {
- int result = reconcileSecondaryDexFiles(pm.getDexManager());
- if (result == OPTIMIZE_ABORT_BY_JOB_SCHEDULER) {
+ @Status int result = reconcileSecondaryDexFiles();
+ if (result != STATUS_OK) {
return result;
}
}
@@ -358,7 +575,7 @@ public class BackgroundDexOptService extends JobService {
// Only downgrade apps when space is low on device.
// Threshold is selected above the lowStorageThreshold so that we can pro-actively clean
// up disk before user hits the actual lowStorageThreshold.
- final long lowStorageThresholdForDowngrade = LOW_THRESHOLD_MULTIPLIER_FOR_DOWNGRADE
+ long lowStorageThresholdForDowngrade = LOW_THRESHOLD_MULTIPLIER_FOR_DOWNGRADE
* lowStorageThreshold;
boolean shouldDowngrade = shouldDowngrade(lowStorageThresholdForDowngrade);
if (DEBUG) {
@@ -368,21 +585,33 @@ public class BackgroundDexOptService extends JobService {
Set<String> unusedPackages =
pm.getUnusedPackages(mDowngradeUnusedAppsThresholdInMillis);
if (DEBUG) {
- Slog.d(TAG, "Unsused Packages " + String.join(",", unusedPackages));
+ Slog.d(TAG, "Unsused Packages " + String.join(",", unusedPackages));
}
if (!unusedPackages.isEmpty()) {
for (String pkg : unusedPackages) {
- int abortCode = abortIdleOptimizations(/*lowStorageThreshold*/ -1);
- if (abortCode != OPTIMIZE_CONTINUE) {
+ @Status int abortCode = abortIdleOptimizations(/*lowStorageThreshold*/ -1);
+ if (abortCode != STATUS_OK) {
// Should be aborted by the scheduler.
return abortCode;
}
- if (downgradePackage(pm, pkg, /*isForPrimaryDex*/ true)) {
+ @DexOptResult int downgradeResult = downgradePackage(pm, pkg,
+ /* isForPrimaryDex= */ true, isPostBootUpdate);
+ if (downgradeResult == PackageDexOptimizer.DEX_OPT_PERFORMED) {
updatedPackages.add(pkg);
}
+ @Status int status = convertPackageDexOptimizerStatusToInternal(
+ downgradeResult);
+ if (status != STATUS_OK) {
+ return status;
+ }
if (supportSecondaryDex) {
- downgradePackage(pm, pkg, /*isForPrimaryDex*/ false);
+ downgradeResult = downgradePackage(pm, pkg,
+ /* isForPrimaryDex= */false, isPostBootUpdate);
+ status = convertPackageDexOptimizerStatusToInternal(downgradeResult);
+ if (status != STATUS_OK) {
+ return status;
+ }
}
}
@@ -391,18 +620,19 @@ public class BackgroundDexOptService extends JobService {
}
}
- int primaryResult = optimizePackages(pm, pkgs, lowStorageThreshold,
- /*isForPrimaryDex*/ true, updatedPackages);
- if (primaryResult != OPTIMIZE_PROCESSED) {
+ @Status int primaryResult = optimizePackages(pm, pkgs, lowStorageThreshold,
+ /*isForPrimaryDex=*/ true, updatedPackages, isPostBootUpdate);
+ if (primaryResult != STATUS_OK) {
return primaryResult;
}
if (!supportSecondaryDex) {
- return OPTIMIZE_PROCESSED;
+ return STATUS_OK;
}
- int secondaryResult = optimizePackages(pm, pkgs, lowStorageThreshold,
- /*isForPrimaryDex*/ false, updatedPackagesDueToSecondaryDex);
+ @Status int secondaryResult = optimizePackages(pm, pkgs, lowStorageThreshold,
+ /*isForPrimaryDex*/ false, updatedPackagesDueToSecondaryDex,
+ isPostBootUpdate);
return secondaryResult;
} finally {
// Always let the pinner service know about changes.
@@ -414,21 +644,25 @@ public class BackgroundDexOptService extends JobService {
}
}
+ @Status
private int optimizePackages(PackageManagerService pm, ArraySet<String> pkgs,
- long lowStorageThreshold, boolean isForPrimaryDex, ArraySet<String> updatedPackages) {
+ long lowStorageThreshold, boolean isForPrimaryDex, ArraySet<String> updatedPackages,
+ boolean isPostBootUpdate) {
for (String pkg : pkgs) {
int abortCode = abortIdleOptimizations(lowStorageThreshold);
- if (abortCode != OPTIMIZE_CONTINUE) {
+ if (abortCode != STATUS_OK) {
// Either aborted by the scheduler or no space left.
return abortCode;
}
- boolean dexOptPerformed = optimizePackage(pm, pkg, isForPrimaryDex);
- if (dexOptPerformed) {
+ @DexOptResult int result = optimizePackage(pm, pkg, isForPrimaryDex, isPostBootUpdate);
+ if (result == PackageDexOptimizer.DEX_OPT_PERFORMED) {
updatedPackages.add(pkg);
+ } else if (result != PackageDexOptimizer.DEX_OPT_SKIPPED) {
+ return convertPackageDexOptimizerStatusToInternal(result);
}
}
- return OPTIMIZE_PROCESSED;
+ return STATUS_OK;
}
/**
@@ -436,21 +670,25 @@ public class BackgroundDexOptService extends JobService {
* eg. if the package is in speed-profile the package will be downgraded to verify.
* @param pm PackageManagerService
* @param pkg The package to be downgraded.
- * @param isForPrimaryDex. Apps can have several dex file, primary and secondary.
- * @return true if the package was downgraded.
+ * @param isForPrimaryDex Apps can have several dex file, primary and secondary.
+ * @return PackageDexOptimizer.DEX_*
*/
- private boolean downgradePackage(PackageManagerService pm, String pkg,
- boolean isForPrimaryDex) {
+ @DexOptResult
+ private int downgradePackage(PackageManagerService pm, String pkg,
+ boolean isForPrimaryDex, boolean isPostBootUpdate) {
if (DEBUG) {
Slog.d(TAG, "Downgrading " + pkg);
}
- boolean dex_opt_performed = false;
+ if (isCancelling()) {
+ return PackageDexOptimizer.DEX_OPT_CANCELLED;
+ }
int reason = PackageManagerService.REASON_INACTIVE_PACKAGE_DOWNGRADE;
- int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE
- | DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB
- | DexoptOptions.DEXOPT_DOWNGRADE;
+ int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE | DexoptOptions.DEXOPT_DOWNGRADE;
+ if (!isPostBootUpdate) {
+ dexoptFlags |= DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB;
+ }
long package_size_before = getPackageSize(pm, pkg);
-
+ int result = PackageDexOptimizer.DEX_OPT_SKIPPED;
if (isForPrimaryDex || PLATFORM_PACKAGE_NAME.equals(pkg)) {
// This applies for system apps or if packages location is not a directory, i.e.
// monolithic install.
@@ -459,32 +697,29 @@ public class BackgroundDexOptService extends JobService {
// remove their compiler artifacts from dalvik cache.
pm.deleteOatArtifactsOfPackage(pkg);
} else {
- dex_opt_performed = performDexOptPrimary(pm, pkg, reason, dexoptFlags);
+ result = performDexOptPrimary(pm, pkg, reason, dexoptFlags);
}
} else {
- dex_opt_performed = performDexOptSecondary(pm, pkg, reason, dexoptFlags);
+ result = performDexOptSecondary(pm, pkg, reason, dexoptFlags);
}
- if (dex_opt_performed) {
+ if (result == PackageDexOptimizer.DEX_OPT_PERFORMED) {
FrameworkStatsLog.write(FrameworkStatsLog.APP_DOWNGRADED, pkg, package_size_before,
getPackageSize(pm, pkg), /*aggressive=*/ false);
}
- return dex_opt_performed;
- }
-
- private boolean supportSecondaryDex() {
- return (SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false));
+ return result;
}
- private int reconcileSecondaryDexFiles(DexManager dm) {
+ @Status
+ private int reconcileSecondaryDexFiles() {
// TODO(calin): should we denylist packages for which we fail to reconcile?
- for (String p : dm.getAllPackagesWithSecondaryDexFiles()) {
- if (mAbortIdleOptimization.get()) {
- return OPTIMIZE_ABORT_BY_JOB_SCHEDULER;
+ for (String p : mInjector.getDexManager().getAllPackagesWithSecondaryDexFiles()) {
+ if (isCancelling()) {
+ return STATUS_ABORT_BY_CANCELLATION;
}
- dm.reconcileSecondaryDexFiles(p);
+ mInjector.getDexManager().reconcileSecondaryDexFiles(p);
}
- return OPTIMIZE_PROCESSED;
+ return STATUS_OK;
}
/**
@@ -493,39 +728,46 @@ public class BackgroundDexOptService extends JobService {
* concurrent jobs because PackageDexOptimizer.performDexOpt is synchronized.
* @param pm An instance of PackageManagerService
* @param pkg The package to be downgraded.
- * @param isForPrimaryDex. Apps can have several dex file, primary and secondary.
- * @return true if the package was downgraded.
+ * @param isForPrimaryDex Apps can have several dex file, primary and secondary.
+ * @param isPostBootUpdate is post boot update or not.
+ * @return PackageDexOptimizer#DEX_OPT_*
*/
- private boolean optimizePackage(PackageManagerService pm, String pkg,
- boolean isForPrimaryDex) {
- int reason = PackageManagerService.REASON_BACKGROUND_DEXOPT;
- int dexoptFlags = DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES
- | DexoptOptions.DEXOPT_BOOT_COMPLETE
- | DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB;
+ @DexOptResult
+ private int optimizePackage(PackageManagerService pm, String pkg,
+ boolean isForPrimaryDex, boolean isPostBootUpdate) {
+ int reason = isPostBootUpdate ? PackageManagerService.REASON_POST_BOOT
+ : PackageManagerService.REASON_BACKGROUND_DEXOPT;
+ int dexoptFlags = DexoptOptions.DEXOPT_BOOT_COMPLETE;
+ if (!isPostBootUpdate) {
+ dexoptFlags |= DexoptOptions.DEXOPT_CHECK_FOR_PROFILES_UPDATES
+ | DexoptOptions.DEXOPT_IDLE_BACKGROUND_JOB;
+ }
// System server share the same code path as primary dex files.
// PackageManagerService will select the right optimization path for it.
- return (isForPrimaryDex || PLATFORM_PACKAGE_NAME.equals(pkg))
- ? performDexOptPrimary(pm, pkg, reason, dexoptFlags)
- : performDexOptSecondary(pm, pkg, reason, dexoptFlags);
+ if (isForPrimaryDex || PLATFORM_PACKAGE_NAME.equals(pkg)) {
+ return performDexOptPrimary(pm, pkg, reason, dexoptFlags);
+ } else {
+ return performDexOptSecondary(pm, pkg, reason, dexoptFlags);
+ }
}
- private boolean performDexOptPrimary(PackageManagerService pm, String pkg, int reason,
+ @DexOptResult
+ private int performDexOptPrimary(PackageManagerService pm, String pkg, int reason,
int dexoptFlags) {
- int result = trackPerformDexOpt(pkg, /*isForPrimaryDex=*/ false,
+ return trackPerformDexOpt(pkg, /*isForPrimaryDex=*/ true,
() -> pm.performDexOptWithStatus(new DexoptOptions(pkg, reason, dexoptFlags)));
- return result == PackageDexOptimizer.DEX_OPT_PERFORMED;
}
- private boolean performDexOptSecondary(PackageManagerService pm, String pkg, int reason,
+ @DexOptResult
+ private int performDexOptSecondary(PackageManagerService pm, String pkg, int reason,
int dexoptFlags) {
DexoptOptions dexoptOptions = new DexoptOptions(pkg, reason,
dexoptFlags | DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX);
- int result = trackPerformDexOpt(pkg, /*isForPrimaryDex=*/ true,
+ return trackPerformDexOpt(pkg, /*isForPrimaryDex=*/ false,
() -> pm.performDexOpt(dexoptOptions)
? PackageDexOptimizer.DEX_OPT_PERFORMED : PackageDexOptimizer.DEX_OPT_FAILED
);
- return result == PackageDexOptimizer.DEX_OPT_PERFORMED;
}
/**
@@ -533,194 +775,212 @@ public class BackgroundDexOptService extends JobService {
* the package is added to the list of failed packages.
* Return one of following result:
* {@link PackageDexOptimizer#DEX_OPT_SKIPPED}
+ * {@link PackageDexOptimizer#DEX_OPT_CANCELLED}
* {@link PackageDexOptimizer#DEX_OPT_PERFORMED}
* {@link PackageDexOptimizer#DEX_OPT_FAILED}
*/
+ @DexOptResult
private int trackPerformDexOpt(String pkg, boolean isForPrimaryDex,
Supplier<Integer> performDexOptWrapper) {
- ArraySet<String> sFailedPackageNames =
- isForPrimaryDex ? sFailedPackageNamesPrimary : sFailedPackageNamesSecondary;
- synchronized (sFailedPackageNames) {
- if (sFailedPackageNames.contains(pkg)) {
+ ArraySet<String> failedPackageNames;
+ synchronized (mLock) {
+ failedPackageNames =
+ isForPrimaryDex ? mFailedPackageNamesPrimary : mFailedPackageNamesSecondary;
+ if (failedPackageNames.contains(pkg)) {
// Skip previously failing package
return PackageDexOptimizer.DEX_OPT_SKIPPED;
}
- sFailedPackageNames.add(pkg);
}
int result = performDexOptWrapper.get();
- if (result != PackageDexOptimizer.DEX_OPT_FAILED) {
- synchronized (sFailedPackageNames) {
- sFailedPackageNames.remove(pkg);
+ if (result == PackageDexOptimizer.DEX_OPT_FAILED) {
+ synchronized (mLock) {
+ failedPackageNames.add(pkg);
+ }
+ } else if (result == PackageDexOptimizer.DEX_OPT_CANCELLED) {
+ synchronized (mLock) {
+ mLastCancelledPackages.add(pkg);
}
}
return result;
}
- // Evaluate whether or not idle optimizations should continue.
+ @Status
+ private int convertPackageDexOptimizerStatusToInternal(@DexOptResult int pdoStatus) {
+ switch (pdoStatus) {
+ case PackageDexOptimizer.DEX_OPT_CANCELLED:
+ return STATUS_ABORT_BY_CANCELLATION;
+ case PackageDexOptimizer.DEX_OPT_FAILED:
+ return STATUS_DEX_OPT_FAILED;
+ case PackageDexOptimizer.DEX_OPT_PERFORMED:
+ case PackageDexOptimizer.DEX_OPT_SKIPPED:
+ return STATUS_OK;
+ default:
+ Slog.e(TAG, "Unkknown error code from PackageDexOptimizer:" + pdoStatus,
+ new RuntimeException());
+ return STATUS_DEX_OPT_FAILED;
+ }
+ }
+
+ /** Evaluate whether or not idle optimizations should continue. */
+ @Status
private int abortIdleOptimizations(long lowStorageThreshold) {
- if (mAbortIdleOptimization.get()) {
+ if (isCancelling()) {
// JobScheduler requested an early abort.
- return OPTIMIZE_ABORT_BY_JOB_SCHEDULER;
+ return STATUS_ABORT_BY_CANCELLATION;
}
// Abort background dexopt if the device is in a moderate or stronger thermal throttling
// state.
- try {
- final int thermalStatus = mThermalService.getCurrentThermalStatus();
-
- if (DEBUG) {
- Log.i(TAG, "Thermal throttling status during bgdexopt: " + thermalStatus);
- }
+ int thermalStatus = mInjector.getCurrentThermalStatus();
+ if (DEBUG) {
+ Log.d(TAG, "Thermal throttling status during bgdexopt: " + thermalStatus);
+ }
+ if (thermalStatus >= mThermalStatusCutoff) {
+ return STATUS_ABORT_THERMAL;
+ }
- if (thermalStatus >= mThermalStatusCutoff) {
- return OPTIMIZE_ABORT_THERMAL;
- }
- } catch (RemoteException ex) {
- // Because this is a intra-process Binder call it is impossible for a RemoteException
- // to be raised.
+ if (mInjector.isBatteryLevelLow()) {
+ return STATUS_ABORT_BATTERY;
}
- long usableSpace = mDataDir.getUsableSpace();
+ long usableSpace = mInjector.getDataDirUsableSpace();
if (usableSpace < lowStorageThreshold) {
// Rather bail than completely fill up the disk.
Slog.w(TAG, "Aborting background dex opt job due to low storage: " + usableSpace);
- return OPTIMIZE_ABORT_NO_SPACE_LEFT;
+ return STATUS_ABORT_NO_SPACE_LEFT;
}
- return OPTIMIZE_CONTINUE;
+ return STATUS_OK;
}
// Evaluate whether apps should be downgraded.
private boolean shouldDowngrade(long lowStorageThresholdForDowngrade) {
- long usableSpace = mDataDir.getUsableSpace();
- if (usableSpace < lowStorageThresholdForDowngrade) {
+ if (mInjector.getDataDirUsableSpace() < lowStorageThresholdForDowngrade) {
return true;
}
return false;
}
- /**
- * Execute idle optimizations immediately on packages in packageNames. If packageNames is null,
- * then execute on all packages.
- */
- public static boolean runIdleOptimizationsNow(PackageManagerService pm, Context context,
- @Nullable List<String> packageNames) {
- // Create a new object to make sure we don't interfere with the scheduled jobs.
- // Note that this may still run at the same time with the job scheduled by the
- // JobScheduler but the scheduler will not be able to cancel it.
- BackgroundDexOptService bdos = new BackgroundDexOptService();
- ArraySet<String> packagesToOptimize;
- if (packageNames == null) {
- packagesToOptimize = pm.getOptimizablePackages();
- } else {
- packagesToOptimize = new ArraySet<>(packageNames);
+ private boolean isCancelling() {
+ synchronized (mLock) {
+ return mDexOptCancellingThread != null;
}
- int result = bdos.idleOptimization(pm, packagesToOptimize, context);
- return result == OPTIMIZE_PROCESSED;
}
- @Override
- public boolean onStartJob(JobParameters params) {
- if (DEBUG) {
- Slog.i(TAG, "onStartJob");
+ private void markPostBootUpdateCompleted(JobParameters params) {
+ if (params.getJobId() != JOB_POST_BOOT_UPDATE) {
+ return;
}
+ synchronized (mLock) {
+ if (!mFinishedPostBootUpdate) {
+ mFinishedPostBootUpdate = true;
+ JobScheduler js = mInjector.getJobScheduler();
+ js.cancel(JOB_POST_BOOT_UPDATE);
+ }
+ }
+ }
- // NOTE: PackageManagerService.isStorageLow uses a different set of criteria from
- // the checks above. This check is not "live" - the value is determined by a background
- // restart with a period of ~1 minute.
- PackageManagerService pm = (PackageManagerService)ServiceManager.getService("package");
- if (pm.isStorageLow()) {
- Slog.i(TAG, "Low storage, skipping this run");
- return false;
+ private void notifyPinService(ArraySet<String> updatedPackages) {
+ PinnerService pinnerService = mInjector.getPinnerService();
+ if (pinnerService != null) {
+ Slog.i(TAG, "Pinning optimized code " + updatedPackages);
+ pinnerService.update(updatedPackages, false /* force */);
}
+ }
- final ArraySet<String> pkgs = pm.getOptimizablePackages();
- if (pkgs.isEmpty()) {
- Slog.i(TAG, "No packages to optimize");
- return false;
+ /** Notify all listeners (#addPackagesUpdatedListener) that packages have been updated. */
+ private void notifyPackagesUpdated(ArraySet<String> updatedPackages) {
+ synchronized (mLock) {
+ for (PackagesUpdatedListener listener : mPackagesUpdatedListeners) {
+ listener.onPackagesUpdated(updatedPackages);
+ }
}
+ }
- mThermalStatusCutoff =
- SystemProperties.getInt("dalvik.vm.dexopt.thermal-cutoff", THERMAL_CUTOFF_DEFAULT);
+ /** Injector pattern for testing purpose */
+ @VisibleForTesting
+ static final class Injector {
+ private final Context mContext;
+ private final DexManager mDexManager;
+ private final File mDataDir = Environment.getDataDirectory();
- boolean result;
- if (params.getJobId() == JOB_POST_BOOT_UPDATE) {
- result = runPostBootUpdate(params, pm, pkgs);
- } else {
- result = runIdleOptimization(params, pm, pkgs);
+ Injector(Context context, DexManager dexManager) {
+ mContext = context;
+ mDexManager = dexManager;
}
- return result;
- }
+ Context getContext() {
+ return mContext;
+ }
- @Override
- public boolean onStopJob(JobParameters params) {
- if (DEBUG) {
- Slog.d(TAG, "onStopJob");
+ PackageManagerService getPackageManagerService() {
+ return (PackageManagerService) ServiceManager.getService("package");
}
- if (params.getJobId() == JOB_POST_BOOT_UPDATE) {
- mAbortPostBootUpdate.set(true);
+ JobScheduler getJobScheduler() {
+ return mContext.getSystemService(JobScheduler.class);
+ }
- // Do not reschedule.
- // TODO: We should reschedule if we didn't process all apps, yet.
- return false;
- } else {
- mAbortIdleOptimization.set(true);
+ DexManager getDexManager() {
+ return mDexManager;
+ }
- // Reschedule the run.
- // TODO: Should this be dependent on the stop reason?
- return true;
+ PinnerService getPinnerService() {
+ return LocalServices.getService(PinnerService.class);
}
- }
- private void notifyPinService(ArraySet<String> updatedPackages) {
- PinnerService pinnerService = LocalServices.getService(PinnerService.class);
- if (pinnerService != null) {
- Slog.i(TAG, "Pinning optimized code " + updatedPackages);
- pinnerService.update(updatedPackages, false /* force */);
+ boolean isBackgroundDexOptDisabled() {
+ return SystemProperties.getBoolean("pm.dexopt.disable_bg_dexopt" /* key */,
+ false /* default */);
}
- }
- public static interface PackagesUpdatedListener {
- /** Callback when packages have been updated by the bg-dexopt service. */
- public void onPackagesUpdated(ArraySet<String> updatedPackages);
- }
+ boolean isBatteryLevelLow() {
+ return LocalServices.getService(BatteryManagerInternal.class).getBatteryLevelLow();
+ }
- public static void addPackagesUpdatedListener(PackagesUpdatedListener listener) {
- synchronized (sPackagesUpdatedListeners) {
- sPackagesUpdatedListeners.add(listener);
+ long getDowngradeUnusedAppsThresholdInMillis() {
+ String sysPropKey = "pm.dexopt.downgrade_after_inactive_days";
+ String sysPropValue = SystemProperties.get(sysPropKey);
+ if (sysPropValue == null || sysPropValue.isEmpty()) {
+ Slog.w(TAG, "SysProp " + sysPropKey + " not set");
+ return Long.MAX_VALUE;
+ }
+ return TimeUnit.DAYS.toMillis(Long.parseLong(sysPropValue));
}
- }
- public static void removePackagesUpdatedListener(PackagesUpdatedListener listener) {
- synchronized (sPackagesUpdatedListeners) {
- sPackagesUpdatedListeners.remove(listener);
+ boolean supportSecondaryDex() {
+ return (SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false));
}
- }
- /** Notify all listeners (#addPackagesUpdatedListener) that packages have been updated. */
- private void notifyPackagesUpdated(ArraySet<String> updatedPackages) {
- synchronized (sPackagesUpdatedListeners) {
- for (PackagesUpdatedListener listener : sPackagesUpdatedListeners) {
- listener.onPackagesUpdated(updatedPackages);
+ long getDataDirUsableSpace() {
+ return mDataDir.getUsableSpace();
+ }
+
+ long getDataDirStorageLowBytes() {
+ return mContext.getSystemService(StorageManager.class).getStorageLowBytes(mDataDir);
+ }
+
+ int getCurrentThermalStatus() {
+ IThermalService thermalService = IThermalService.Stub
+ .asInterface(ServiceManager.getService(Context.THERMAL_SERVICE));
+ try {
+ return thermalService.getCurrentThermalStatus();
+ } catch (RemoteException e) {
+ return STATUS_ABORT_THERMAL;
}
}
- }
- private static long getDowngradeUnusedAppsThresholdInMillis() {
- final String sysPropKey = "pm.dexopt.downgrade_after_inactive_days";
- String sysPropValue = SystemProperties.get(sysPropKey);
- if (sysPropValue == null || sysPropValue.isEmpty()) {
- Slog.w(TAG, "SysProp " + sysPropKey + " not set");
- return Long.MAX_VALUE;
+ int getDexOptThermalCutoff() {
+ return SystemProperties.getInt("dalvik.vm.dexopt.thermal-cutoff",
+ THERMAL_CUTOFF_DEFAULT);
}
- return TimeUnit.DAYS.toMillis(Long.parseLong(sysPropValue));
- }
- private static boolean isBackgroundDexoptDisabled() {
- return SystemProperties.getBoolean("pm.dexopt.disable_bg_dexopt" /* key */,
- false /* default */);
+ Thread createAndStartThread(String name, Runnable target) {
+ Thread thread = new Thread(target, name);
+ Slog.i(TAG, "Starting thread:" + name);
+ thread.start();
+ return thread;
+ }
}
}
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 34e942885ba4..9c6f3ebaeabd 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -179,6 +179,7 @@ public class ComputerEngine implements Computer {
private final PackageDexOptimizer mPackageDexOptimizer;
private final DexManager mDexManager;
private final CompilerStats mCompilerStats;
+ private final BackgroundDexOptService mBackgroundDexOptService;
// PackageManagerService attributes that are primitives are referenced through the
// pms object directly. Primitives are the only attributes so referenced.
@@ -228,6 +229,7 @@ public class ComputerEngine implements Computer {
mPackageDexOptimizer = args.service.mPackageDexOptimizer;
mDexManager = args.service.getDexManager();
mCompilerStats = args.service.mCompilerStats;
+ mBackgroundDexOptService = args.service.mBackgroundDexOptService;
// Used to reference PMS attributes that are primitives and which are not
// updated under control of the PMS lock.
@@ -2929,6 +2931,10 @@ public class ComputerEngine implements Computer {
mDexManager.getPackageUseInfoOrDefault(pkgName));
ipw.decreaseIndent();
}
+ ipw.println("BgDexopt state:");
+ ipw.increaseIndent();
+ mBackgroundDexOptService.dump(ipw);
+ ipw.decreaseIndent();
break;
}
diff --git a/services/core/java/com/android/server/pm/InstallParams.java b/services/core/java/com/android/server/pm/InstallParams.java
index 5a18d4026607..676d12ec993f 100644
--- a/services/core/java/com/android/server/pm/InstallParams.java
+++ b/services/core/java/com/android/server/pm/InstallParams.java
@@ -1973,7 +1973,7 @@ final class InstallParams extends HandlerParams {
// If this is an update of a package which used to fail to compile,
// BackgroundDexOptService will remove it from its denylist.
// TODO: Layering violation
- BackgroundDexOptService.notifyPackageChanged(packageName);
+ BackgroundDexOptService.getService().notifyPackageChanged(packageName);
notifyPackageChangeObserversOnUpdate(reconciledPkg);
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 2bec8be395fb..7607fa44627e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -899,6 +899,7 @@ public class PackageManagerService extends IPackageManager.Stub
final ArtManagerService mArtManagerService;
final PackageDexOptimizer mPackageDexOptimizer;
+ final BackgroundDexOptService mBackgroundDexOptService;
// DexManager handles the usage of dex files (e.g. secondary files, whether or not a package
// is used by other apps).
private final DexManager mDexManager;
@@ -1531,7 +1532,8 @@ public class PackageManagerService extends IPackageManager.Stub
},
new DefaultSystemWrapper(),
LocalServices::getService,
- context::getSystemService);
+ context::getSystemService,
+ (i, pm) -> new BackgroundDexOptService(i.getContext(), i.getDexManager()));
if (Build.VERSION.SDK_INT <= 0) {
Slog.w(TAG, "**** ro.build.version.sdk not set!");
@@ -1676,6 +1678,7 @@ public class PackageManagerService extends IPackageManager.Stub
mApexManager = testParams.apexManager;
mArtManagerService = testParams.artManagerService;
mAvailableFeatures = testParams.availableFeatures;
+ mBackgroundDexOptService = testParams.backgroundDexOptService;
mDefParseFlags = testParams.defParseFlags;
mDefaultAppProvider = testParams.defaultAppProvider;
mLegacyPermissionManager = testParams.legacyPermissionManagerInternal;
@@ -1852,6 +1855,7 @@ public class PackageManagerService extends IPackageManager.Stub
mPackageDexOptimizer = injector.getPackageDexOptimizer();
mDexManager = injector.getDexManager();
+ mBackgroundDexOptService = injector.getBackgroundDexOptService();
mArtManagerService = injector.getArtManagerService();
mMoveCallbacks = new MovePackageHelper.MoveCallbacks(FgThread.get().getLooper());
mViewCompiler = injector.getViewCompiler();
@@ -6140,6 +6144,10 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
+ /*package*/ void controlDexOptBlocking(boolean block) {
+ mPackageDexOptimizer.controlDexOptBlocking(block);
+ }
+
/**
* Perform dexopt on the given package and return one of following result:
* {@link PackageDexOptimizer#DEX_OPT_SKIPPED}
@@ -6276,23 +6284,6 @@ public class PackageManagerService extends IPackageManager.Stub
return mDexManager;
}
- /**
- * Execute the background dexopt job immediately.
- */
- @Override
- public boolean runBackgroundDexoptJob(@Nullable List<String> packageNames) {
- if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
- return false;
- }
- enforceSystemOrRootOrShell("runBackgroundDexoptJob");
- final long identity = Binder.clearCallingIdentity();
- try {
- return BackgroundDexOptService.runIdleOptimizationsNow(this, mContext, packageNames);
- } finally {
- Binder.restoreCallingIdentity(identity);
- }
- }
-
private static List<SharedLibraryInfo> findSharedLibraries(PackageSetting pkgSetting) {
if (!pkgSetting.getPkgState().getUsesLibraryInfos().isEmpty()) {
ArrayList<SharedLibraryInfo> retValue = new ArrayList<>();
@@ -11468,6 +11459,8 @@ public class PackageManagerService extends IPackageManager.Stub
mPerUidReadTimeoutsCache = null;
}
});
+
+ mBackgroundDexOptService.systemReady();
}
public void waitForAppDataPrepared() {
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
index a63cc362dc0c..97a09ffc810f 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
@@ -134,6 +134,7 @@ public class PackageManagerServiceInjector {
private final Singleton<DomainVerificationManagerInternal>
mDomainVerificationManagerInternalProducer;
private final Singleton<Handler> mHandlerProducer;
+ private final Singleton<BackgroundDexOptService> mBackgroundDexOptService;
PackageManagerServiceInjector(Context context, PackageManagerTracedLock lock,
Installer installer, Object installLock, PackageAbiHelper abiHelper,
@@ -168,7 +169,8 @@ public class PackageManagerServiceInjector {
Producer<Handler> handlerProducer,
SystemWrapper systemWrapper,
ServiceProducer getLocalServiceProducer,
- ServiceProducer getSystemServiceProducer) {
+ ServiceProducer getSystemServiceProducer,
+ Producer<BackgroundDexOptService> backgroundDexOptService) {
mContext = context;
mLock = lock;
mInstaller = installer;
@@ -217,6 +219,7 @@ public class PackageManagerServiceInjector {
new Singleton<>(
domainVerificationManagerInternalProducer);
mHandlerProducer = new Singleton<>(handlerProducer);
+ mBackgroundDexOptService = new Singleton<>(backgroundDexOptService);
}
/**
@@ -377,6 +380,10 @@ public class PackageManagerServiceInjector {
return getLocalService(ActivityManagerInternal.class);
}
+ public BackgroundDexOptService getBackgroundDexOptService() {
+ return mBackgroundDexOptService.get(this, mPackageManager);
+ }
+
/** Provides an abstraction to static access to system state. */
public interface SystemWrapper {
void disablePackageCaches();
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
index 2870c4526678..c1c32dd84c9e 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
@@ -100,5 +100,6 @@ public final class PackageManagerServiceTestParams {
public boolean isEngBuild;
public boolean isUserDebugBuild;
public int sdkInt = Build.VERSION.SDK_INT;
+ public BackgroundDexOptService backgroundDexOptService;
public final String incrementalVersion = Build.VERSION.INCREMENTAL;
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index e4f63987956b..3554cc02b9fc 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -226,6 +226,8 @@ class PackageManagerShellCommand extends ShellCommand {
return runForceDexOpt();
case "bg-dexopt-job":
return runDexoptJob();
+ case "cancel-bg-dexopt-job":
+ return cancelBgDexOptJob();
case "dump-profiles":
return runDumpProfiles();
case "snapshot-profile":
@@ -1863,12 +1865,18 @@ class PackageManagerShellCommand extends ShellCommand {
while ((arg = getNextArg()) != null) {
packageNames.add(arg);
}
- boolean result = mInterface.runBackgroundDexoptJob(packageNames.isEmpty() ? null :
- packageNames);
+ boolean result = BackgroundDexOptService.getService().runBackgroundDexoptJob(
+ packageNames.isEmpty() ? null : packageNames);
getOutPrintWriter().println(result ? "Success" : "Failure");
return result ? 0 : -1;
}
+ private int cancelBgDexOptJob() throws RemoteException {
+ BackgroundDexOptService.getService().cancelBackgroundDexoptJob();
+ getOutPrintWriter().println("Success");
+ return 0;
+ }
+
private int runDumpProfiles() throws RemoteException {
String packageName = getNextArg();
mInterface.dumpProfiles(packageName);
@@ -3940,6 +3948,11 @@ class PackageManagerShellCommand extends ShellCommand {
pw.println(" overlap with the actual job but the job scheduler will not be able to");
pw.println(" cancel it. It will also run even if the device is not in the idle");
pw.println(" maintenance mode.");
+ pw.println(" cancel-bg-dexopt-job");
+ pw.println(" Cancels currently running background optimizations immediately.");
+ pw.println(" This cancels optimizations run from bg-dexopt-job or from JobScjeduler.");
+ pw.println(" Note that cancelling currently running bg-dexopt-job command requires");
+ pw.println(" running this command from separate adb shell.");
pw.println("");
pw.println(" reconcile-secondary-dex-files TARGET-PACKAGE");
pw.println(" Reconciles the package secondary dex files with the generated oat files.");
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 4a2b51ddcf58..f351a8c8922f 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -150,7 +150,6 @@ import com.android.server.os.DeviceIdentifiersPolicyService;
import com.android.server.os.NativeTombstoneManagerService;
import com.android.server.os.SchedulingPolicyService;
import com.android.server.people.PeopleService;
-import com.android.server.pm.BackgroundDexOptService;
import com.android.server.pm.CrossProfileAppsService;
import com.android.server.pm.DataLoaderManagerService;
import com.android.server.pm.DynamicCodeLoggingService;
@@ -2409,15 +2408,6 @@ public final class SystemServer implements Dumpable {
mSystemServiceManager.startService(AuthService.class);
t.traceEnd();
-
- t.traceBegin("StartBackgroundDexOptService");
- try {
- BackgroundDexOptService.schedule(context);
- } catch (Throwable e) {
- reportWtf("starting StartBackgroundDexOptService", e);
- }
- t.traceEnd();
-
if (!isWatch) {
// We don't run this on watches as there are no plans to use the data logged
// on watch devices.
diff --git a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
index 1a0e5269d51a..bd8f5a4d225b 100644
--- a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
+++ b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
@@ -21,16 +21,13 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.job.JobInfo;
import android.app.job.JobParameters;
-import android.app.job.JobService;
import android.app.job.JobScheduler;
+import android.app.job.JobService;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.os.IBinder;
-import android.os.IBinder.DeathRecipient;
import android.os.Handler;
-import android.os.Parcel;
+import android.os.IBinder.DeathRecipient;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
@@ -45,18 +42,15 @@ import com.android.server.SystemService;
import com.android.server.pm.BackgroundDexOptService;
import com.android.server.pm.PackageManagerService;
import com.android.server.wm.ActivityMetricsLaunchObserver;
-import com.android.server.wm.ActivityMetricsLaunchObserver.ActivityRecordProto;
-import com.android.server.wm.ActivityMetricsLaunchObserver.Temperature;
import com.android.server.wm.ActivityMetricsLaunchObserverRegistry;
import com.android.server.wm.ActivityTaskManagerInternal;
import java.time.Duration;
-import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BooleanSupplier;
-import java.util.HashMap;
-import java.util.List;
/**
* System-server-local proxy into the {@code IIorap} native service.
@@ -347,7 +341,8 @@ public class IorapForwardingService extends SystemService {
launchObserverRegistry.registerLaunchObserver(mAppLaunchObserver);
launchObserverRegistry.registerLaunchObserver(mEventSequenceValidator);
- BackgroundDexOptService.addPackagesUpdatedListener(mDexOptPackagesUpdated);
+ BackgroundDexOptService.getService().addPackagesUpdatedListener(
+ mDexOptPackagesUpdated);
mRegisteredListeners = true;