diff options
3 files changed, 87 insertions, 36 deletions
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 8deecd7f0b1b..febdaed276c6 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -907,6 +907,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: diff --git a/services/core/java/com/android/server/pm/AppStateHelper.java b/services/core/java/com/android/server/pm/AppStateHelper.java index bb0c41ea5591..2ef193cacbae 100644 --- a/services/core/java/com/android/server/pm/AppStateHelper.java +++ b/services/core/java/com/android/server/pm/AppStateHelper.java @@ -19,9 +19,13 @@ package com.android.server.pm; import android.app.ActivityManager; import android.app.ActivityManager.RunningAppProcessInfo; import android.app.ActivityManagerInternal; +import android.app.ActivityThread; +import android.app.usage.NetworkStats; +import android.app.usage.NetworkStatsManager; import android.content.Context; import android.media.AudioManager; import android.media.IAudioService; +import android.net.ConnectivityManager; import android.os.ServiceManager; import android.os.SystemProperties; import android.telecom.TelecomManager; @@ -33,11 +37,15 @@ import com.android.server.LocalServices; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.TimeUnit; /** * A helper class to provide queries for app states concerning gentle-update. */ public class AppStateHelper { + // The duration to monitor network usage to determine if network is active or not + private static final long ACTIVE_NETWORK_DURATION_MILLIS = TimeUnit.MINUTES.toMillis(10); + private final Context mContext; public AppStateHelper(Context context) { @@ -127,12 +135,35 @@ public class AppStateHelper { return hasAudioFocus(packageName) || isRecordingAudio(packageName); } + private boolean hasActiveNetwork(List<String> packageNames, int networkType) { + var pm = ActivityThread.getPackageManager(); + var nsm = mContext.getSystemService(NetworkStatsManager.class); + var endTime = System.currentTimeMillis(); + var startTime = endTime - ACTIVE_NETWORK_DURATION_MILLIS; + try (var stats = nsm.querySummary(networkType, null, startTime, endTime)) { + var bucket = new NetworkStats.Bucket(); + while (stats.hasNextBucket()) { + stats.getNextBucket(bucket); + var packageName = pm.getNameForUid(bucket.getUid()); + if (!packageNames.contains(packageName)) { + continue; + } + if (bucket.getRxPackets() > 0 || bucket.getTxPackets() > 0) { + return true; + } + } + } catch (Exception ignore) { + } + return false; + } + /** - * True if the app is sending or receiving network data. + * True if any app has sent or received network data over the past + * {@link #ACTIVE_NETWORK_DURATION_MILLIS} milliseconds. */ - private boolean hasActiveNetwork(String packageName) { - // To be implemented - return false; + private boolean hasActiveNetwork(List<String> packageNames) { + return hasActiveNetwork(packageNames, ConnectivityManager.TYPE_WIFI) + || hasActiveNetwork(packageNames, ConnectivityManager.TYPE_MOBILE); } /** @@ -141,12 +172,11 @@ public class AppStateHelper { public boolean hasInteractingApp(List<String> packageNames) { for (var packageName : packageNames) { if (hasActiveAudio(packageName) - || hasActiveNetwork(packageName) || isAppTopVisible(packageName)) { return true; } } - return false; + return hasActiveNetwork(packageNames); } /** diff --git a/services/core/java/com/android/server/pm/GentleUpdateHelper.java b/services/core/java/com/android/server/pm/GentleUpdateHelper.java index 9ad847d5d88c..ae80babac0c6 100644 --- a/services/core/java/com/android/server/pm/GentleUpdateHelper.java +++ b/services/core/java/com/android/server/pm/GentleUpdateHelper.java @@ -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 |