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

Commit f6f0b58c authored by Chun-Wei Wang's avatar Chun-Wei Wang Committed by Android (Google) Code Review
Browse files

Merge changes from topic "install_constraints"

* changes:
  Implement AppStateHelper#hasActiveNetwork
  Improve the logic of checking device idle
parents 71d7b807 d2f1260c
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -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:
+36 −6
Original line number Diff line number Diff line
@@ -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);
    }

    /**
+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