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
This commit is contained in:
JW Wang
2022-12-09 08:32:50 +08:00
parent 377a1c368d
commit f8d1c26e50
2 changed files with 51 additions and 30 deletions

View File

@@ -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:

View File

@@ -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)) {
// 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);
}
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();
// Ensure the pending check is resolved after timeout, no matter constraints
// satisfied or not.
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