Merge changes from topic "install_constraints"
* changes: Implement AppStateHelper#hasActiveNetwork Improve the logic of checking device idle
This commit is contained in:
committed by
Android (Google) Code Review
commit
f6f0b58c20
@@ -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:
|
||||
|
||||
@@ -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,26 +135,48 @@ public class AppStateHelper {
|
||||
return hasAudioFocus(packageName) || isRecordingAudio(packageName);
|
||||
}
|
||||
|
||||
/**
|
||||
* True if the app is sending or receiving network data.
|
||||
*/
|
||||
private boolean hasActiveNetwork(String packageName) {
|
||||
// To be implemented
|
||||
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 any app has sent or received network data over the past
|
||||
* {@link #ACTIVE_NETWORK_DURATION_MILLIS} milliseconds.
|
||||
*/
|
||||
private boolean hasActiveNetwork(List<String> packageNames) {
|
||||
return hasActiveNetwork(packageNames, ConnectivityManager.TYPE_WIFI)
|
||||
|| hasActiveNetwork(packageNames, ConnectivityManager.TYPE_MOBILE);
|
||||
}
|
||||
|
||||
/**
|
||||
* True if any app is interacting with the user.
|
||||
*/
|
||||
public boolean hasInteractingApp(List<String> packageNames) {
|
||||
for (var packageName : packageNames) {
|
||||
if (hasActiveAudio(packageName)
|
||||
|| hasActiveNetwork(packageName)
|
||||
|| isAppTopVisible(packageName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return hasActiveNetwork(packageNames);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user