Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f8d1c26e authored by JW Wang's avatar JW Wang
Browse files

Improve the logic of checking device idle

* #checkDeviceIdle encapsulates the logic of checking device idle,
  making the life of the caller easier.
* Now timeout-check always happens after device-idle-check.
  No clamping is needed w.r.t PENDING_CHECK_MILLIS.

Bug: 235306967
Test: atest CtsStagedInstallHostTestCases
Change-Id: Ibbdd34518ce9f53b5053eff18826e2dd72edac6b
parent 377a1c36
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -906,6 +906,9 @@ public class PackageInstaller {
    /**
     * Similar to {@link #checkInstallConstraints(List, InstallConstraints, Executor, Consumer)},
     * but the callback is invoked only when the constraints are satisfied or after timeout.
     * <p>
     * Note: the device idle constraint might take a long time to evaluate. The system will
     * ensure the constraint is evaluated completely before handling timeout.
     *
     * @param callback Called when the constraints are satisfied or after timeout.
     *                 Intents sent to this callback contain:
+48 −30
Original line number Diff line number Diff line
@@ -39,7 +39,10 @@ import android.os.SystemProperties;
import android.text.format.DateUtils;
import android.util.Slog;

import com.android.internal.util.Preconditions;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
@@ -101,6 +104,13 @@ public class GentleUpdateHelper {
        public boolean isTimedOut() {
            return SystemClock.elapsedRealtime() >= mFinishTime;
        }
        /**
         * The remaining time before this pending check is timed out.
         */
        public long getRemainingTimeMillis() {
            long timeout = mFinishTime - SystemClock.elapsedRealtime();
            return Math.max(timeout, 0);
        }
    }

    private final Context mContext;
@@ -108,6 +118,7 @@ public class GentleUpdateHelper {
    private final AppStateHelper mAppStateHelper;
    // Worker thread only
    private final ArrayDeque<PendingInstallConstraintsCheck> mPendingChecks = new ArrayDeque<>();
    private final ArrayList<CompletableFuture<Boolean>> mPendingIdleFutures = new ArrayList<>();
    private boolean mHasPendingIdleJob;

    GentleUpdateHelper(Context context, Looper looper, AppStateHelper appStateHelper) {
@@ -130,39 +141,41 @@ public class GentleUpdateHelper {
    CompletableFuture<InstallConstraintsResult> checkInstallConstraints(
            List<String> packageNames, InstallConstraints constraints,
            long timeoutMillis) {
        var future = new CompletableFuture<InstallConstraintsResult>();
        var resultFuture = new CompletableFuture<InstallConstraintsResult>();
        mHandler.post(() -> {
            long clampedTimeoutMillis = timeoutMillis;
            if (constraints.isRequireDeviceIdle()) {
                // Device-idle-constraint is required. Clamp the timeout to ensure
                // timeout-check happens after device-idle-check.
                clampedTimeoutMillis = Math.max(timeoutMillis, PENDING_CHECK_MILLIS);
            }

            var pendingCheck = new PendingInstallConstraintsCheck(
                    packageNames, constraints, future, clampedTimeoutMillis);
            if (constraints.isRequireDeviceIdle()) {
                mPendingChecks.add(pendingCheck);
                // JobScheduler doesn't provide queries about whether the device is idle.
                // We schedule 2 tasks to determine device idle. If the idle job is executed
                // before the delayed runnable, we know the device is idle.
                // Note #processPendingCheck will be no-op for the task executed later.
                scheduleIdleJob();
                mHandler.postDelayed(() -> processPendingCheck(pendingCheck, false),
                        PENDING_CHECK_MILLIS);
            } else if (!processPendingCheck(pendingCheck, false)) {
                    packageNames, constraints, resultFuture, timeoutMillis);
            var deviceIdleFuture = constraints.isRequireDeviceIdle()
                    ? checkDeviceIdle() : CompletableFuture.completedFuture(false);
            deviceIdleFuture.thenAccept(isIdle -> {
                Preconditions.checkState(mHandler.getLooper().isCurrentThread());
                if (!processPendingCheck(pendingCheck, isIdle)) {
                    // Not resolved. Schedule a job for re-check
                    mPendingChecks.add(pendingCheck);
                    scheduleIdleJob();
            }

            if (!future.isDone()) {
                    // Ensure the pending check is resolved after timeout, no matter constraints
                    // satisfied or not.
                mHandler.postDelayed(() -> processPendingCheck(pendingCheck, false),
                        clampedTimeoutMillis);
                    mHandler.postDelayed(() -> processPendingCheck(
                            pendingCheck, false), pendingCheck.getRemainingTimeMillis());
                }
            });
        });
        return resultFuture;
    }

    /**
     * Checks if the device is idle or not.
     * @return A future resolved to {@code true} if the device is idle, or {@code false} if not.
     */
    @WorkerThread
    private CompletableFuture<Boolean> checkDeviceIdle() {
        // JobScheduler doesn't provide queries about whether the device is idle.
        // We schedule 2 tasks here and the task which resolves
        // the future first will determine whether the device is idle or not.
        var future = new CompletableFuture<Boolean>();
        mPendingIdleFutures.add(future);
        scheduleIdleJob();
        mHandler.postDelayed(() -> future.complete(false), PENDING_CHECK_MILLIS);
        return future;
    }

@@ -194,6 +207,11 @@ public class GentleUpdateHelper {
    private void runIdleJob() {
        mHasPendingIdleJob = false;
        processPendingChecksInIdle();

        for (var f : mPendingIdleFutures) {
            f.complete(true);
        }
        mPendingIdleFutures.clear();
    }

    @WorkerThread