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

Commit c7d16d6f authored by Kweku Adams's avatar Kweku Adams
Browse files

Defer low priority jobs in poor network conditions.

A poor network connection typically means it takes more power to
transmit data and the device will heat up quickly trying to transmit
data. In a poor network scenario, we should try to only use the network
for important work, so defer low priority jobs on poorer network
connections.

Bug: 210077394
Test: atest frameworks/base/services/tests/servicestests/src/com/android/server/job
Test: atest frameworks/base/services/tests/mockingservicestests/src/com/android/server/job
Test: atest CtsJobSchedulerTestCases
Change-Id: I33f2daac404ef2c399c58364e4c214152cc47de0
parent e7e3dd67
Loading
Loading
Loading
Loading
+46 −0
Original line number Diff line number Diff line
@@ -417,6 +417,9 @@ public class JobSchedulerService extends com.android.server.SystemService
                            break;
                        case Constants.KEY_CONN_CONGESTION_DELAY_FRAC:
                        case Constants.KEY_CONN_PREFETCH_RELAX_FRAC:
                        case Constants.KEY_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC:
                        case Constants.KEY_CONN_USE_CELL_SIGNAL_STRENGTH:
                        case Constants.KEY_CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS:
                            mConstants.updateConnectivityConstantsLocked();
                            break;
                        case Constants.KEY_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS:
@@ -489,6 +492,12 @@ public class JobSchedulerService extends com.android.server.SystemService
        private static final String KEY_MIN_EXP_BACKOFF_TIME_MS = "min_exp_backoff_time_ms";
        private static final String KEY_CONN_CONGESTION_DELAY_FRAC = "conn_congestion_delay_frac";
        private static final String KEY_CONN_PREFETCH_RELAX_FRAC = "conn_prefetch_relax_frac";
        private static final String KEY_CONN_USE_CELL_SIGNAL_STRENGTH =
                "conn_use_cell_signal_strength";
        private static final String KEY_CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS =
                "conn_update_all_jobs_min_interval_ms";
        private static final String KEY_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC =
                "conn_low_signal_strength_relax_frac";
        private static final String KEY_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS =
                "prefetch_force_batch_relax_threshold_ms";
        private static final String KEY_ENABLE_API_QUOTAS = "enable_api_quotas";
@@ -514,6 +523,9 @@ public class JobSchedulerService extends com.android.server.SystemService
        private static final long DEFAULT_MIN_EXP_BACKOFF_TIME_MS = JobInfo.MIN_BACKOFF_MILLIS;
        private static final float DEFAULT_CONN_CONGESTION_DELAY_FRAC = 0.5f;
        private static final float DEFAULT_CONN_PREFETCH_RELAX_FRAC = 0.5f;
        private static final boolean DEFAULT_CONN_USE_CELL_SIGNAL_STRENGTH = true;
        private static final long DEFAULT_CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS = MINUTE_IN_MILLIS;
        private static final float DEFAULT_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC = 0.5f;
        private static final long DEFAULT_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS = HOUR_IN_MILLIS;
        private static final boolean DEFAULT_ENABLE_API_QUOTAS = true;
        private static final int DEFAULT_API_QUOTA_SCHEDULE_COUNT = 250;
@@ -569,6 +581,23 @@ public class JobSchedulerService extends com.android.server.SystemService
         * we consider matching it against a metered network.
         */
        public float CONN_PREFETCH_RELAX_FRAC = DEFAULT_CONN_PREFETCH_RELAX_FRAC;
        /**
         * Whether to use the cell signal strength to determine if a particular job is eligible to
         * run.
         */
        public boolean CONN_USE_CELL_SIGNAL_STRENGTH = DEFAULT_CONN_USE_CELL_SIGNAL_STRENGTH;
        /**
         * When throttling updating all tracked jobs, make sure not to update them more frequently
         * than this value.
         */
        public long CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS =
                DEFAULT_CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS;
        /**
         * The fraction of a job's running window that must pass before we consider running it on
         * low signal strength networks.
         */
        public float CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC =
                DEFAULT_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC;

        /**
         * The amount of time within which we would consider the app to be launching relatively soon
@@ -661,6 +690,18 @@ public class JobSchedulerService extends com.android.server.SystemService
            CONN_PREFETCH_RELAX_FRAC = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
                    KEY_CONN_PREFETCH_RELAX_FRAC,
                    DEFAULT_CONN_PREFETCH_RELAX_FRAC);
            CONN_USE_CELL_SIGNAL_STRENGTH = DeviceConfig.getBoolean(
                    DeviceConfig.NAMESPACE_JOB_SCHEDULER,
                    KEY_CONN_USE_CELL_SIGNAL_STRENGTH,
                    DEFAULT_CONN_USE_CELL_SIGNAL_STRENGTH);
            CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS = DeviceConfig.getLong(
                    DeviceConfig.NAMESPACE_JOB_SCHEDULER,
                    KEY_CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS,
                    DEFAULT_CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS);
            CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC = DeviceConfig.getFloat(
                    DeviceConfig.NAMESPACE_JOB_SCHEDULER,
                    KEY_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC,
                    DEFAULT_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC);
        }

        private void updatePrefetchConstantsLocked() {
@@ -739,6 +780,11 @@ public class JobSchedulerService extends com.android.server.SystemService
            pw.print(KEY_MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME_MS).println();
            pw.print(KEY_CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC).println();
            pw.print(KEY_CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC).println();
            pw.print(KEY_CONN_USE_CELL_SIGNAL_STRENGTH, CONN_USE_CELL_SIGNAL_STRENGTH).println();
            pw.print(KEY_CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS, CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS)
                    .println();
            pw.print(KEY_CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC, CONN_LOW_SIGNAL_STRENGTH_RELAX_FRAC)
                    .println();
            pw.print(KEY_PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS,
                    PREFETCH_FORCE_BATCH_RELAX_THRESHOLD_MS).println();

+225 −9
Original line number Diff line number Diff line
@@ -35,6 +35,10 @@ import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.UserHandle;
import android.telephony.CellSignalStrength;
import android.telephony.SignalStrength;
import android.telephony.TelephonyCallback;
import android.telephony.TelephonyManager;
import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -60,6 +64,7 @@ import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.function.Predicate;

/**
@@ -213,9 +218,16 @@ public final class ConnectivityController extends RestrictingController implemen
     * is only done in {@link #maybeAdjustRegisteredCallbacksLocked()} and may sometimes be stale.
     */
    private final List<UidStats> mSortedStats = new ArrayList<>();
    @GuardedBy("mLock")
    private long mLastCallbackAdjustmentTimeElapsed;
    @GuardedBy("mLock")
    private final SparseArray<CellSignalStrengthCallback> mSignalStrengths = new SparseArray<>();

    @GuardedBy("mLock")
    private long mLastAllJobUpdateTimeElapsed;

    private static final int MSG_ADJUST_CALLBACKS = 0;
    private static final int MSG_UPDATE_ALL_TRACKED_JOBS = 1;

    private final Handler mHandler;

@@ -529,11 +541,7 @@ public final class ConnectivityController extends RestrictingController implemen
    @GuardedBy("mLock")
    public void onBatteryStateChangedLocked() {
        // Update job bookkeeping out of band to avoid blocking broadcast progress.
        JobSchedulerBackgroundThread.getHandler().post(() -> {
            synchronized (mLock) {
                updateTrackedJobsLocked(-1, null);
            }
        });
        mHandler.sendEmptyMessage(MSG_UPDATE_ALL_TRACKED_JOBS);
    }

    private boolean isUsable(NetworkCapabilities capabilities) {
@@ -650,6 +658,82 @@ public final class ConnectivityController extends RestrictingController implemen
        }
    }

    @GuardedBy("mLock")
    private boolean isStrongEnough(JobStatus jobStatus, NetworkCapabilities capabilities,
            Constants constants) {
        final int priority = jobStatus.getEffectivePriority();
        if (priority >= JobInfo.PRIORITY_HIGH) {
            return true;
        }
        if (!constants.CONN_USE_CELL_SIGNAL_STRENGTH) {
            return true;
        }
        if (!capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
            return true;
        }
        if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_VPN)) {
            // Exclude VPNs because it's currently not possible to determine the VPN's underlying
            // network, and thus the correct signal strength of the VPN's network.
            // Transmitting data over a VPN is generally more battery-expensive than on the
            // underlying network, so:
            // TODO: find a good way to reduce job use of VPN when it'll be very expensive
            // For now, we just pretend VPNs are always strong enough
            return true;
        }

        // VCNs running over WiFi will declare TRANSPORT_CELLULAR. When connected, a VCN will
        // most likely be the default network. We ideally don't want this to restrict jobs when the
        // VCN incorrectly declares the CELLULAR transport, but there's currently no way to
        // determine if a network is a VCN. When there is:
        // TODO(216127782): exclude VCN running over WiFi from this check

        int signalStrength = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN;
        // Use the best strength found.
        final Set<Integer> subscriptionIds = capabilities.getSubscriptionIds();
        for (int subId : subscriptionIds) {
            CellSignalStrengthCallback callback = mSignalStrengths.get(subId);
            if (callback != null) {
                signalStrength = Math.max(signalStrength, callback.signalStrength);
            } else {
                Slog.wtf(TAG,
                        "Subscription ID " + subId + " doesn't have a registered callback");
            }
        }
        if (DEBUG) {
            Slog.d(TAG, "Cell signal strength for job=" + signalStrength);
        }
        // Treat "NONE_OR_UNKNOWN" as "NONE".
        if (signalStrength <= CellSignalStrength.SIGNAL_STRENGTH_POOR) {
            // If signal strength is poor, don't run MIN or LOW priority jobs, and only
            // run DEFAULT priority jobs if the device is charging or the job has been waiting
            // long enough.
            if (priority > JobInfo.PRIORITY_DEFAULT) {
                return true;
            }
            if (priority < JobInfo.PRIORITY_DEFAULT) {
                return false;
            }
            // DEFAULT job.
            return (mService.isBatteryCharging() && mService.isBatteryNotLow())
                    || jobStatus.getFractionRunTime() > constants.CONN_PREFETCH_RELAX_FRAC;
        }
        if (signalStrength <= CellSignalStrength.SIGNAL_STRENGTH_MODERATE) {
            // If signal strength is moderate, only run MIN priority jobs when the device
            // is charging, or the job is already running.
            if (priority >= JobInfo.PRIORITY_LOW) {
                return true;
            }
            // MIN job.
            if (mService.isBatteryCharging() && mService.isBatteryNotLow()) {
                return true;
            }
            final UidStats uidStats = getUidStats(
                    jobStatus.getSourceUid(), jobStatus.getSourcePackageName(), true);
            return uidStats.runningJobs.contains(jobStatus);
        }
        return true;
    }

    private static NetworkCapabilities.Builder copyCapabilities(
            @NonNull final NetworkRequest request) {
        final NetworkCapabilities.Builder builder = new NetworkCapabilities.Builder();
@@ -717,10 +801,12 @@ public final class ConnectivityController extends RestrictingController implemen
        // Second, is the network congested?
        if (isCongestionDelayed(jobStatus, network, capabilities, constants)) return false;

        // Third, is the network a strict match?
        if (!isStrongEnough(jobStatus, capabilities, constants)) return false;

        // Is the network a strict match?
        if (isStrictSatisfied(jobStatus, network, capabilities, constants)) return true;

        // Third, is the network a relaxed match?
        // Is the network a relaxed match?
        if (isRelaxedSatisfied(jobStatus, network, capabilities, constants)) return true;

        return false;
@@ -985,6 +1071,24 @@ public final class ConnectivityController extends RestrictingController implemen
        return changed;
    }

    @GuardedBy("mLock")
    private void updateAllTrackedJobsLocked(boolean allowThrottle) {
        if (allowThrottle) {
            final long throttleTimeLeftMs =
                    (mLastAllJobUpdateTimeElapsed + mConstants.CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS)
                            - sElapsedRealtimeClock.millis();
            if (throttleTimeLeftMs > 0) {
                Message msg = mHandler.obtainMessage(MSG_UPDATE_ALL_TRACKED_JOBS, 1, 0);
                mHandler.sendMessageDelayed(msg, throttleTimeLeftMs);
                return;
            }
        }

        mHandler.removeMessages(MSG_UPDATE_ALL_TRACKED_JOBS);
        updateTrackedJobsLocked(-1, null);
        mLastAllJobUpdateTimeElapsed = sElapsedRealtimeClock.millis();
    }

    /**
     * Update any jobs tracked by this controller that match given filters.
     *
@@ -1088,7 +1192,11 @@ public final class ConnectivityController extends RestrictingController implemen
                Slog.v(TAG, "onCapabilitiesChanged: " + network);
            }
            synchronized (mLock) {
                mAvailableNetworks.put(network, capabilities);
                final NetworkCapabilities oldCaps = mAvailableNetworks.put(network, capabilities);
                if (oldCaps != null) {
                    maybeUnregisterSignalStrengthCallbackLocked(oldCaps);
                }
                maybeRegisterSignalStrengthCallbackLocked(capabilities);
                updateTrackedJobsLocked(-1, network);
                postAdjustCallbacks();
            }
@@ -1100,7 +1208,10 @@ public final class ConnectivityController extends RestrictingController implemen
                Slog.v(TAG, "onLost: " + network);
            }
            synchronized (mLock) {
                mAvailableNetworks.remove(network);
                final NetworkCapabilities capabilities = mAvailableNetworks.remove(network);
                if (capabilities != null) {
                    maybeUnregisterSignalStrengthCallbackLocked(capabilities);
                }
                for (int u = 0; u < mCurrentDefaultNetworkCallbacks.size(); ++u) {
                    UidDefaultNetworkCallback callback = mCurrentDefaultNetworkCallbacks.valueAt(u);
                    if (Objects.equals(callback.mDefaultNetwork, network)) {
@@ -1111,6 +1222,63 @@ public final class ConnectivityController extends RestrictingController implemen
                postAdjustCallbacks();
            }
        }

        @GuardedBy("mLock")
        private void maybeRegisterSignalStrengthCallbackLocked(
                @NonNull NetworkCapabilities capabilities) {
            if (!capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
                return;
            }
            TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
            final Set<Integer> subscriptionIds = capabilities.getSubscriptionIds();
            for (int subId : subscriptionIds) {
                if (mSignalStrengths.indexOfKey(subId) >= 0) {
                    continue;
                }
                TelephonyManager idTm = telephonyManager.createForSubscriptionId(subId);
                CellSignalStrengthCallback callback = new CellSignalStrengthCallback();
                idTm.registerTelephonyCallback(
                        JobSchedulerBackgroundThread.getExecutor(), callback);
                mSignalStrengths.put(subId, callback);

                final SignalStrength signalStrength = idTm.getSignalStrength();
                if (signalStrength != null) {
                    callback.signalStrength = signalStrength.getLevel();
                }
            }
        }

        @GuardedBy("mLock")
        private void maybeUnregisterSignalStrengthCallbackLocked(
                @NonNull NetworkCapabilities capabilities) {
            if (!capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
                return;
            }
            ArraySet<Integer> activeIds = new ArraySet<>();
            for (int i = 0, size = mAvailableNetworks.size(); i < size; ++i) {
                NetworkCapabilities nc = mAvailableNetworks.valueAt(i);
                if (nc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
                    activeIds.addAll(nc.getSubscriptionIds());
                }
            }
            if (DEBUG) {
                Slog.d(TAG, "Active subscription IDs: " + activeIds);
            }
            TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
            Set<Integer> subscriptionIds = capabilities.getSubscriptionIds();
            for (int subId : subscriptionIds) {
                if (activeIds.contains(subId)) {
                    continue;
                }
                TelephonyManager idTm = telephonyManager.createForSubscriptionId(subId);
                CellSignalStrengthCallback callback = mSignalStrengths.removeReturnOld(subId);
                if (callback != null) {
                    idTm.unregisterTelephonyCallback(callback);
                } else {
                    Slog.wtf(TAG, "Callback for sub " + subId + " didn't exist?!?!");
                }
            }
        }
    };

    private class CcHandler extends Handler {
@@ -1127,6 +1295,13 @@ public final class ConnectivityController extends RestrictingController implemen
                            maybeAdjustRegisteredCallbacksLocked();
                        }
                        break;

                    case MSG_UPDATE_ALL_TRACKED_JOBS:
                        synchronized (mLock) {
                            final boolean allowThrottle = msg.arg1 == 1;
                            updateAllTrackedJobsLocked(allowThrottle);
                        }
                        break;
                }
            }
        }
@@ -1268,6 +1443,33 @@ public final class ConnectivityController extends RestrictingController implemen
        }
    }

    private class CellSignalStrengthCallback extends TelephonyCallback
            implements TelephonyCallback.SignalStrengthsListener {
        @GuardedBy("mLock")
        public int signalStrength = CellSignalStrength.SIGNAL_STRENGTH_GREAT;

        @Override
        public void onSignalStrengthsChanged(@NonNull SignalStrength signalStrength) {
            synchronized (mLock) {
                final int newSignalStrength = signalStrength.getLevel();
                if (DEBUG) {
                    Slog.d(TAG, "Signal strength changing from "
                            + this.signalStrength + " to " + newSignalStrength);
                    for (CellSignalStrength css : signalStrength.getCellSignalStrengths()) {
                        Slog.d(TAG, "CSS: " + css.getLevel() + " " + css);
                    }
                }
                if (this.signalStrength == newSignalStrength) {
                    // This happens a lot.
                    return;
                }
                this.signalStrength = newSignalStrength;
                // Update job bookkeeping out of band to avoid blocking callback progress.
                mHandler.obtainMessage(MSG_UPDATE_ALL_TRACKED_JOBS, 1, 0).sendToTarget();
            }
        }
    }

    @GuardedBy("mLock")
    @Override
    public void dumpControllerStateLocked(IndentingPrintWriter pw,
@@ -1299,6 +1501,20 @@ public final class ConnectivityController extends RestrictingController implemen
        }
        pw.println();

        if (mSignalStrengths.size() > 0) {
            pw.println("Subscription ID signal strengths:");
            pw.increaseIndent();
            for (int i = 0; i < mSignalStrengths.size(); ++i) {
                pw.print(mSignalStrengths.keyAt(i));
                pw.print(": ");
                pw.println(mSignalStrengths.valueAt(i).signalStrength);
            }
            pw.decreaseIndent();
        } else {
            pw.println("No cached signal strengths");
        }
        pw.println();

        pw.println("Current default network callbacks:");
        pw.increaseIndent();
        for (int i = 0; i < mCurrentDefaultNetworkCallbacks.size(); i++) {
+4 −3
Original line number Diff line number Diff line
@@ -1139,11 +1139,12 @@ public final class JobStatus {
     */
    public float getFractionRunTime() {
        final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
        if (earliestRunTimeElapsedMillis == 0 && latestRunTimeElapsedMillis == Long.MAX_VALUE) {
        if (earliestRunTimeElapsedMillis == NO_EARLIEST_RUNTIME
                && latestRunTimeElapsedMillis == NO_LATEST_RUNTIME) {
            return 1;
        } else if (earliestRunTimeElapsedMillis == 0) {
        } else if (earliestRunTimeElapsedMillis == NO_EARLIEST_RUNTIME) {
            return now >= latestRunTimeElapsedMillis ? 1 : 0;
        } else if (latestRunTimeElapsedMillis == Long.MAX_VALUE) {
        } else if (latestRunTimeElapsedMillis == NO_LATEST_RUNTIME) {
            return now >= earliestRunTimeElapsedMillis ? 1 : 0;
        } else {
            if (now <= earliestRunTimeElapsedMillis) {
+427 −0

File changed.

Preview size limit exceeded, changes collapsed.