summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/content/pm/PackageInstaller.java3
-rw-r--r--services/core/java/com/android/server/pm/AppStateHelper.java42
-rw-r--r--services/core/java/com/android/server/pm/GentleUpdateHelper.java78
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