summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java108
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java32
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java160
-rw-r--r--core/api/test-current.txt1
-rw-r--r--core/java/android/app/ActivityManager.java13
-rw-r--r--core/java/android/content/Context.java11
-rw-r--r--core/java/android/net/NetworkPolicyManager.java17
-rw-r--r--services/core/java/com/android/server/am/OomAdjuster.java10
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java6
-rw-r--r--services/core/java/com/android/server/net/NetworkPolicyManagerService.java3
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java107
11 files changed, 432 insertions, 36 deletions
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index d94993d64995..ed53827646b9 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -365,6 +365,16 @@ public class JobSchedulerService extends com.android.server.SystemService
* A mapping of which uids are currently in the foreground to their effective bias.
*/
final SparseIntArray mUidBiasOverride = new SparseIntArray();
+ /**
+ * A cached mapping of uids to their current capabilities.
+ */
+ @GuardedBy("mLock")
+ private final SparseIntArray mUidCapabilities = new SparseIntArray();
+ /**
+ * A cached mapping of uids to their proc states.
+ */
+ @GuardedBy("mLock")
+ private final SparseIntArray mUidProcStates = new SparseIntArray();
/**
* Which uids are currently performing backups, so we shouldn't allow their jobs to run.
@@ -1135,6 +1145,14 @@ public class JobSchedulerService extends com.android.server.SystemService
mDebuggableApps.remove(pkgName);
mConcurrencyManager.onAppRemovedLocked(pkgName, pkgUid);
}
+ } else if (Intent.ACTION_UID_REMOVED.equals(action)) {
+ if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+ synchronized (mLock) {
+ mUidBiasOverride.delete(pkgUid);
+ mUidCapabilities.delete(pkgUid);
+ mUidProcStates.delete(pkgUid);
+ }
+ }
} else if (Intent.ACTION_USER_ADDED.equals(action)) {
final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0);
synchronized (mLock) {
@@ -1205,7 +1223,11 @@ public class JobSchedulerService extends com.android.server.SystemService
final private IUidObserver mUidObserver = new IUidObserver.Stub() {
@Override public void onUidStateChanged(int uid, int procState, long procStateSeq,
int capability) {
- mHandler.obtainMessage(MSG_UID_STATE_CHANGED, uid, procState).sendToTarget();
+ final SomeArgs args = SomeArgs.obtain();
+ args.argi1 = uid;
+ args.argi2 = procState;
+ args.argi3 = capability;
+ mHandler.obtainMessage(MSG_UID_STATE_CHANGED, args).sendToTarget();
}
@Override public void onUidGone(int uid, boolean disabled) {
@@ -1947,8 +1969,14 @@ public class JobSchedulerService extends com.android.server.SystemService
}
}
- void updateUidState(int uid, int procState) {
+ void updateUidState(int uid, int procState, int capabilities) {
+ if (DEBUG) {
+ Slog.d(TAG, "UID " + uid + " proc state changed to "
+ + ActivityManager.procStateToString(procState)
+ + " with capabilities=" + ActivityManager.getCapabilitiesSummary(capabilities));
+ }
synchronized (mLock) {
+ mUidProcStates.put(uid, procState);
final int prevBias = mUidBiasOverride.get(uid, JobInfo.BIAS_DEFAULT);
if (procState == ActivityManager.PROCESS_STATE_TOP) {
// Only use this if we are exactly the top app. All others can live
@@ -1962,6 +1990,12 @@ public class JobSchedulerService extends com.android.server.SystemService
} else {
mUidBiasOverride.delete(uid);
}
+ if (capabilities == ActivityManager.PROCESS_CAPABILITY_NONE
+ || procState == ActivityManager.PROCESS_STATE_NONEXISTENT) {
+ mUidCapabilities.delete(uid);
+ } else {
+ mUidCapabilities.put(uid, capabilities);
+ }
final int newBias = mUidBiasOverride.get(uid, JobInfo.BIAS_DEFAULT);
if (prevBias != newBias) {
if (DEBUG) {
@@ -1982,6 +2016,23 @@ public class JobSchedulerService extends com.android.server.SystemService
}
}
+ /**
+ * Return the current {@link ActivityManager#PROCESS_CAPABILITY_ALL capabilities}
+ * of the given UID.
+ */
+ public int getUidCapabilities(int uid) {
+ synchronized (mLock) {
+ return mUidCapabilities.get(uid, ActivityManager.PROCESS_CAPABILITY_NONE);
+ }
+ }
+
+ /** Return the current proc state of the given UID. */
+ public int getUidProcState(int uid) {
+ synchronized (mLock) {
+ return mUidProcStates.get(uid, ActivityManager.PROCESS_STATE_UNKNOWN);
+ }
+ }
+
@Override
public void onDeviceIdleStateChanged(boolean deviceIdle) {
synchronized (mLock) {
@@ -2245,6 +2296,9 @@ public class JobSchedulerService extends com.android.server.SystemService
filter.addDataScheme("package");
getContext().registerReceiverAsUser(
mBroadcastReceiver, UserHandle.ALL, filter, null, null);
+ final IntentFilter uidFilter = new IntentFilter(Intent.ACTION_UID_REMOVED);
+ getContext().registerReceiverAsUser(
+ mBroadcastReceiver, UserHandle.ALL, uidFilter, null, null);
final IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_REMOVED);
userFilter.addAction(Intent.ACTION_USER_ADDED);
getContext().registerReceiverAsUser(
@@ -2776,15 +2830,19 @@ public class JobSchedulerService extends com.android.server.SystemService
break;
case MSG_UID_STATE_CHANGED: {
- final int uid = message.arg1;
- final int procState = message.arg2;
- updateUidState(uid, procState);
+ final SomeArgs args = (SomeArgs) message.obj;
+ final int uid = args.argi1;
+ final int procState = args.argi2;
+ final int capabilities = args.argi3;
+ updateUidState(uid, procState, capabilities);
+ args.recycle();
break;
}
case MSG_UID_GONE: {
final int uid = message.arg1;
final boolean disabled = message.arg2 != 0;
- updateUidState(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY);
+ updateUidState(uid, ActivityManager.PROCESS_STATE_CACHED_EMPTY,
+ ActivityManager.PROCESS_CAPABILITY_NONE);
if (disabled) {
cancelJobsForUid(uid,
/* includeSourceApp */ true,
@@ -4836,6 +4894,25 @@ public class JobSchedulerService extends com.android.server.SystemService
pw.decreaseIndent();
}
+ boolean procStatePrinted = false;
+ for (int i = 0; i < mUidProcStates.size(); i++) {
+ int uid = mUidProcStates.keyAt(i);
+ if (filterAppId == -1 || filterAppId == UserHandle.getAppId(uid)) {
+ if (!procStatePrinted) {
+ procStatePrinted = true;
+ pw.println();
+ pw.println("Uid proc states:");
+ pw.increaseIndent();
+ }
+ pw.print(UserHandle.formatUid(uid));
+ pw.print(": ");
+ pw.println(ActivityManager.procStateToString(mUidProcStates.valueAt(i)));
+ }
+ }
+ if (procStatePrinted) {
+ pw.decreaseIndent();
+ }
+
boolean overridePrinted = false;
for (int i = 0; i < mUidBiasOverride.size(); i++) {
int uid = mUidBiasOverride.keyAt(i);
@@ -4854,6 +4931,25 @@ public class JobSchedulerService extends com.android.server.SystemService
pw.decreaseIndent();
}
+ boolean capabilitiesPrinted = false;
+ for (int i = 0; i < mUidCapabilities.size(); i++) {
+ int uid = mUidCapabilities.keyAt(i);
+ if (filterAppId == -1 || filterAppId == UserHandle.getAppId(uid)) {
+ if (!capabilitiesPrinted) {
+ capabilitiesPrinted = true;
+ pw.println();
+ pw.println("Uid capabilities:");
+ pw.increaseIndent();
+ }
+ pw.print(UserHandle.formatUid(uid));
+ pw.print(": ");
+ pw.println(ActivityManager.getCapabilitiesSummary(mUidCapabilities.valueAt(i)));
+ }
+ }
+ if (capabilitiesPrinted) {
+ pw.decreaseIndent();
+ }
+
boolean uidMapPrinted = false;
for (int i = 0; i < mUidToPackageCache.size(); ++i) {
final int uid = mUidToPackageCache.keyAt(i);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index b080bf31fed4..254101e0ddc4 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -390,23 +390,27 @@ public final class JobServiceContext implements ServiceConnection {
.setFlags(Intent.FLAG_FROM_BACKGROUND);
boolean binding = false;
try {
- final int bindFlags;
+ final Context.BindServiceFlags bindFlags;
if (job.shouldTreatAsUserInitiatedJob()) {
- // TODO (191785864, 261999509): add an appropriate flag so user-initiated jobs
- // can bypass data saver
- bindFlags = Context.BIND_AUTO_CREATE
- | Context.BIND_ALMOST_PERCEPTIBLE
- | Context.BIND_BYPASS_POWER_NETWORK_RESTRICTIONS
- | Context.BIND_NOT_APP_COMPONENT_USAGE;
+ bindFlags = Context.BindServiceFlags.of(
+ Context.BIND_AUTO_CREATE
+ | Context.BIND_ALMOST_PERCEPTIBLE
+ | Context.BIND_BYPASS_POWER_NETWORK_RESTRICTIONS
+ | Context.BIND_BYPASS_USER_NETWORK_RESTRICTIONS
+ | Context.BIND_NOT_APP_COMPONENT_USAGE);
} else if (job.shouldTreatAsExpeditedJob()) {
- bindFlags = Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND
- | Context.BIND_ALMOST_PERCEPTIBLE
- | Context.BIND_BYPASS_POWER_NETWORK_RESTRICTIONS
- | Context.BIND_NOT_APP_COMPONENT_USAGE;
+ bindFlags = Context.BindServiceFlags.of(
+ Context.BIND_AUTO_CREATE
+ | Context.BIND_NOT_FOREGROUND
+ | Context.BIND_ALMOST_PERCEPTIBLE
+ | Context.BIND_BYPASS_POWER_NETWORK_RESTRICTIONS
+ | Context.BIND_NOT_APP_COMPONENT_USAGE);
} else {
- bindFlags = Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND
- | Context.BIND_NOT_PERCEPTIBLE
- | Context.BIND_NOT_APP_COMPONENT_USAGE;
+ bindFlags = Context.BindServiceFlags.of(
+ Context.BIND_AUTO_CREATE
+ | Context.BIND_NOT_FOREGROUND
+ | Context.BIND_NOT_PERCEPTIBLE
+ | Context.BIND_NOT_APP_COMPONENT_USAGE);
}
binding = mContext.bindServiceAsUser(intent, this, bindFlags,
UserHandle.of(job.getUserId()));
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
index 3859d89c22cd..f6bdb9303a04 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
@@ -18,15 +18,18 @@ package com.android.server.job.controllers;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED;
import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityManager;
import android.app.job.JobInfo;
import android.net.ConnectivityManager;
import android.net.ConnectivityManager.NetworkCallback;
+import android.net.INetworkPolicyListener;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkPolicyManager;
@@ -47,6 +50,7 @@ import android.util.Log;
import android.util.Pools;
import android.util.Slog;
import android.util.SparseArray;
+import android.util.SparseBooleanArray;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
@@ -98,13 +102,12 @@ public final class ConnectivityController extends RestrictingController implemen
~(ConnectivityManager.BLOCKED_REASON_APP_STANDBY
| ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER
| ConnectivityManager.BLOCKED_REASON_DOZE);
- // TODO(261999509): allow bypassing data saver & user-restricted. However, when we allow a UI
- // job to run while data saver restricts the app, we must ensure that we don't run regular
- // jobs when we put a hole in the data saver wall for the UI job
private static final int UNBYPASSABLE_UI_BLOCKED_REASONS =
~(ConnectivityManager.BLOCKED_REASON_APP_STANDBY
| ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER
- | ConnectivityManager.BLOCKED_REASON_DOZE);
+ | ConnectivityManager.BLOCKED_REASON_DOZE
+ | ConnectivityManager.BLOCKED_METERED_REASON_DATA_SAVER
+ | ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED);
private static final int UNBYPASSABLE_FOREGROUND_BLOCKED_REASONS =
~(ConnectivityManager.BLOCKED_REASON_APP_STANDBY
| ConnectivityManager.BLOCKED_REASON_BATTERY_SAVER
@@ -113,6 +116,7 @@ public final class ConnectivityController extends RestrictingController implemen
| ConnectivityManager.BLOCKED_METERED_REASON_USER_RESTRICTED);
private final ConnectivityManager mConnManager;
+ private final NetworkPolicyManager mNetPolicyManager;
private final NetworkPolicyManagerInternal mNetPolicyManagerInternal;
private final FlexibilityController mFlexibilityController;
@@ -241,6 +245,8 @@ public final class ConnectivityController extends RestrictingController implemen
*/
private final List<UidStats> mSortedStats = new ArrayList<>();
@GuardedBy("mLock")
+ private final SparseBooleanArray mBackgroundMeteredAllowed = new SparseBooleanArray();
+ @GuardedBy("mLock")
private long mLastCallbackAdjustmentTimeElapsed;
@GuardedBy("mLock")
private final SparseArray<CellSignalStrengthCallback> mSignalStrengths = new SparseArray<>();
@@ -250,6 +256,8 @@ public final class ConnectivityController extends RestrictingController implemen
private static final int MSG_ADJUST_CALLBACKS = 0;
private static final int MSG_UPDATE_ALL_TRACKED_JOBS = 1;
+ private static final int MSG_DATA_SAVER_TOGGLED = 2;
+ private static final int MSG_UID_POLICIES_CHANGED = 3;
private final Handler mHandler;
@@ -259,6 +267,7 @@ public final class ConnectivityController extends RestrictingController implemen
mHandler = new CcHandler(AppSchedulingModuleThread.get().getLooper());
mConnManager = mContext.getSystemService(ConnectivityManager.class);
+ mNetPolicyManager = mContext.getSystemService(NetworkPolicyManager.class);
mNetPolicyManagerInternal = LocalServices.getService(NetworkPolicyManagerInternal.class);
mFlexibilityController = flexibilityController;
@@ -266,6 +275,8 @@ public final class ConnectivityController extends RestrictingController implemen
// network changes against the active network for each UID with jobs.
final NetworkRequest request = new NetworkRequest.Builder().clearCapabilities().build();
mConnManager.registerNetworkCallback(request, mNetworkCallback);
+
+ mNetPolicyManager.registerListener(mNetPolicyListener);
}
@GuardedBy("mLock")
@@ -530,6 +541,7 @@ public final class ConnectivityController extends RestrictingController implemen
// All packages in the UID have been removed. It's safe to remove things based on
// UID alone.
mTrackedJobs.delete(uid);
+ mBackgroundMeteredAllowed.delete(uid);
UidStats uidStats = mUidStats.removeReturnOld(uid);
unregisterDefaultNetworkCallbackLocked(uid, sElapsedRealtimeClock.millis());
mSortedStats.remove(uidStats);
@@ -549,6 +561,12 @@ public final class ConnectivityController extends RestrictingController implemen
mUidStats.removeAt(u);
}
}
+ for (int u = mBackgroundMeteredAllowed.size() - 1; u >= 0; --u) {
+ final int uid = mBackgroundMeteredAllowed.keyAt(u);
+ if (UserHandle.getUserId(uid) == userId) {
+ mBackgroundMeteredAllowed.removeAt(u);
+ }
+ }
postAdjustCallbacks();
}
@@ -666,6 +684,88 @@ public final class ConnectivityController extends RestrictingController implemen
return false;
}
+ private boolean isMeteredAllowed(@NonNull JobStatus jobStatus,
+ @NonNull NetworkCapabilities networkCapabilities) {
+ // Network isn't metered. Usage is allowed. The rest of this method doesn't apply.
+ if (networkCapabilities.hasCapability(NET_CAPABILITY_NOT_METERED)
+ || networkCapabilities.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)) {
+ return true;
+ }
+
+ final int uid = jobStatus.getSourceUid();
+ final int procState = mService.getUidProcState(uid);
+ final int capabilities = mService.getUidCapabilities(uid);
+ // Jobs don't raise the proc state to anything better than IMPORTANT_FOREGROUND.
+ // If the app is in a better state, see if it has the capability to use the metered network.
+ final boolean currentStateAllows = procState != ActivityManager.PROCESS_STATE_UNKNOWN
+ && procState < ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
+ && NetworkPolicyManager.isProcStateAllowedWhileOnRestrictBackground(
+ procState, capabilities);
+ if (DEBUG) {
+ Slog.d(TAG, "UID " + uid
+ + " current state allows metered network=" + currentStateAllows
+ + " procState=" + ActivityManager.procStateToString(procState)
+ + " capabilities=" + ActivityManager.getCapabilitiesSummary(capabilities));
+ }
+ if (currentStateAllows) {
+ return true;
+ }
+
+ if ((jobStatus.getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) != 0) {
+ final int expectedProcState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
+ final int mergedCapabilities = capabilities
+ | NetworkPolicyManager.getDefaultProcessNetworkCapabilities(expectedProcState);
+ final boolean wouldBeAllowed =
+ NetworkPolicyManager.isProcStateAllowedWhileOnRestrictBackground(
+ expectedProcState, mergedCapabilities);
+ if (DEBUG) {
+ Slog.d(TAG, "UID " + uid
+ + " willBeForeground flag allows metered network=" + wouldBeAllowed
+ + " capabilities="
+ + ActivityManager.getCapabilitiesSummary(mergedCapabilities));
+ }
+ if (wouldBeAllowed) {
+ return true;
+ }
+ }
+
+ if (jobStatus.shouldTreatAsUserInitiatedJob()) {
+ // Since the job is initiated by the user and will be visible to the user, it
+ // should be able to run on metered networks, similar to FGS.
+ // With user-initiated jobs, JobScheduler will request that the process
+ // run at IMPORTANT_FOREGROUND process state
+ // and get the USER_RESTRICTED_NETWORK process capability.
+ final int expectedProcState = ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+ final int mergedCapabilities = capabilities
+ | ActivityManager.PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK
+ | NetworkPolicyManager.getDefaultProcessNetworkCapabilities(expectedProcState);
+ final boolean wouldBeAllowed =
+ NetworkPolicyManager.isProcStateAllowedWhileOnRestrictBackground(
+ expectedProcState, mergedCapabilities);
+ if (DEBUG) {
+ Slog.d(TAG, "UID " + uid
+ + " UI job state allows metered network=" + wouldBeAllowed
+ + " capabilities=" + mergedCapabilities);
+ }
+ if (wouldBeAllowed) {
+ return true;
+ }
+ }
+
+ if (mBackgroundMeteredAllowed.indexOfKey(uid) >= 0) {
+ return mBackgroundMeteredAllowed.get(uid);
+ }
+
+ final boolean allowed =
+ mNetPolicyManager.getRestrictBackgroundStatus(uid)
+ != ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
+ if (DEBUG) {
+ Slog.d(TAG, "UID " + uid + " allowed in data saver=" + allowed);
+ }
+ mBackgroundMeteredAllowed.put(uid, allowed);
+ return allowed;
+ }
+
/**
* Return the estimated amount of time this job will be transferring data,
* based on the current network speed.
@@ -859,6 +959,12 @@ public final class ConnectivityController extends RestrictingController implemen
// First, are we insane?
if (isInsane(jobStatus, network, capabilities, constants)) return false;
+ // User-initiated jobs might make NetworkPolicyManager open up network access for
+ // the whole UID. If network access is opened up just because of UI jobs, we want
+ // to make sure that non-UI jobs don't run during that time,
+ // so make sure the job can make use of the metered network at this time.
+ if (!isMeteredAllowed(jobStatus, capabilities)) return false;
+
// Second, is the network congested?
if (isCongestionDelayed(jobStatus, network, capabilities, constants)) return false;
@@ -1138,9 +1244,10 @@ public final class ConnectivityController extends RestrictingController implemen
// but it doesn't yet satisfy the requested constraints and the old network
// is still available and satisfies the constraints. Don't change the network
// given to the job for now and let it keep running. We will re-evaluate when
- // the capabilities or connection state of the either network change.
+ // the capabilities or connection state of either network change.
if (DEBUG) {
- Slog.i(TAG, "Not reassigning network for running job " + jobStatus);
+ Slog.i(TAG, "Not reassigning network from " + jobStatus.network
+ + " to " + network + " for running job " + jobStatus);
}
return false;
}
@@ -1389,6 +1496,26 @@ public final class ConnectivityController extends RestrictingController implemen
}
};
+ private final INetworkPolicyListener mNetPolicyListener = new NetworkPolicyManager.Listener() {
+ @Override
+ public void onRestrictBackgroundChanged(boolean restrictBackground) {
+ if (DEBUG) {
+ Slog.v(TAG, "onRestrictBackgroundChanged: " + restrictBackground);
+ }
+ mHandler.obtainMessage(MSG_DATA_SAVER_TOGGLED).sendToTarget();
+ }
+
+ @Override
+ public void onUidPoliciesChanged(int uid, int uidPolicies) {
+ if (DEBUG) {
+ Slog.v(TAG, "onUidPoliciesChanged: " + uid);
+ }
+ mHandler.obtainMessage(MSG_UID_POLICIES_CHANGED,
+ uid, mNetPolicyManager.getRestrictBackgroundStatus(uid))
+ .sendToTarget();
+ }
+ };
+
private class CcHandler extends Handler {
CcHandler(Looper looper) {
super(looper);
@@ -1410,6 +1537,27 @@ public final class ConnectivityController extends RestrictingController implemen
updateAllTrackedJobsLocked(allowThrottle);
}
break;
+
+ case MSG_DATA_SAVER_TOGGLED:
+ removeMessages(MSG_DATA_SAVER_TOGGLED);
+ synchronized (mLock) {
+ mBackgroundMeteredAllowed.clear();
+ updateTrackedJobsLocked(-1, null);
+ }
+ break;
+
+ case MSG_UID_POLICIES_CHANGED:
+ final int uid = msg.arg1;
+ final boolean newAllowed =
+ msg.arg2 != ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
+ synchronized (mLock) {
+ final boolean oldAllowed = mBackgroundMeteredAllowed.get(uid);
+ if (oldAllowed != newAllowed) {
+ mBackgroundMeteredAllowed.put(uid, newAllowed);
+ updateTrackedJobsLocked(uid, null);
+ }
+ }
+ break;
}
}
}
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 7107bf7419e5..46c93eb02efa 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -149,6 +149,7 @@ package android.app {
field public static final int PROCESS_CAPABILITY_ALL_IMPLICIT = 6; // 0x6
field @Deprecated public static final int PROCESS_CAPABILITY_NETWORK = 8; // 0x8
field public static final int PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK = 8; // 0x8
+ field public static final int PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK = 32; // 0x20
field public static final int PROCESS_STATE_FOREGROUND_SERVICE = 4; // 0x4
field public static final int PROCESS_STATE_TOP = 2; // 0x2
field public static final int STOP_USER_ON_SWITCH_DEFAULT = -1; // 0xffffffff
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 929c07bc1dc5..9b67e8d5e991 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -764,6 +764,7 @@ public class ActivityManager {
PROCESS_CAPABILITY_FOREGROUND_MICROPHONE,
PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK,
PROCESS_CAPABILITY_BFSL,
+ PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ProcessCapability {}
@@ -914,6 +915,13 @@ public class ActivityManager {
public static final int PROCESS_CAPABILITY_BFSL = 1 << 4;
/**
+ * @hide
+ * Process can access network at a high enough proc state despite any user restrictions.
+ */
+ @TestApi
+ public static final int PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK = 1 << 5;
+
+ /**
* @hide all capabilities, the ORing of all flags in {@link ProcessCapability}.
*
* Don't expose it as TestApi -- we may add new capabilities any time, which could
@@ -923,7 +931,8 @@ public class ActivityManager {
| PROCESS_CAPABILITY_FOREGROUND_CAMERA
| PROCESS_CAPABILITY_FOREGROUND_MICROPHONE
| PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK
- | PROCESS_CAPABILITY_BFSL;
+ | PROCESS_CAPABILITY_BFSL
+ | PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK;
/**
* All implicit capabilities. There are capabilities that process automatically have.
@@ -943,6 +952,7 @@ public class ActivityManager {
pw.print((caps & PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) != 0 ? 'M' : '-');
pw.print((caps & PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK) != 0 ? 'N' : '-');
pw.print((caps & PROCESS_CAPABILITY_BFSL) != 0 ? 'F' : '-');
+ pw.print((caps & PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK) != 0 ? 'U' : '-');
}
/** @hide */
@@ -952,6 +962,7 @@ public class ActivityManager {
sb.append((caps & PROCESS_CAPABILITY_FOREGROUND_MICROPHONE) != 0 ? 'M' : '-');
sb.append((caps & PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK) != 0 ? 'N' : '-');
sb.append((caps & PROCESS_CAPABILITY_BFSL) != 0 ? 'F' : '-');
+ sb.append((caps & PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK) != 0 ? 'U' : '-');
}
/**
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 3b2ea785988e..2b73afcd740f 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -322,6 +322,7 @@ public abstract class Context {
BIND_EXTERNAL_SERVICE_LONG,
// Make sure no flag uses the sign bit (most significant bit) of the long integer,
// to avoid future confusion.
+ BIND_BYPASS_USER_NETWORK_RESTRICTIONS,
})
@Retention(RetentionPolicy.SOURCE)
public @interface BindServiceFlagsLongBits {}
@@ -688,6 +689,16 @@ public abstract class Context {
public static final long BIND_EXTERNAL_SERVICE_LONG = 1L << 62;
/**
+ * Flag for {@link #bindService}: allow the process hosting the target service to gain
+ * {@link ActivityManager#PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK}, which allows it be able
+ * to access network regardless of any user restrictions.
+ *
+ * @hide
+ */
+ public static final long BIND_BYPASS_USER_NETWORK_RESTRICTIONS = 0x1_0000_0000L;
+
+
+ /**
* These bind flags reduce the strength of the binding such that we shouldn't
* consider it as pulling the process up to the level of the one that is bound to it.
* @hide
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index 104a8b2095d8..efed6883a114 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -780,11 +780,11 @@ public class NetworkPolicyManager {
case ActivityManager.PROCESS_STATE_PERSISTENT:
case ActivityManager.PROCESS_STATE_PERSISTENT_UI:
case ActivityManager.PROCESS_STATE_TOP:
- return ActivityManager.PROCESS_CAPABILITY_ALL;
case ActivityManager.PROCESS_STATE_BOUND_TOP:
case ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE:
case ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE:
- return ActivityManager.PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK;
+ return ActivityManager.PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK
+ | ActivityManager.PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK;
default:
return ActivityManager.PROCESS_CAPABILITY_NONE;
}
@@ -826,13 +826,18 @@ public class NetworkPolicyManager {
if (uidState == null) {
return false;
}
- return isProcStateAllowedWhileOnRestrictBackground(uidState.procState);
+ return isProcStateAllowedWhileOnRestrictBackground(uidState.procState, uidState.capability);
}
/** @hide */
- public static boolean isProcStateAllowedWhileOnRestrictBackground(int procState) {
- // Data saver and bg policy restrictions will only take procstate into account.
- return procState <= FOREGROUND_THRESHOLD_STATE;
+ public static boolean isProcStateAllowedWhileOnRestrictBackground(int procState,
+ @ProcessCapability int capabilities) {
+ return procState <= FOREGROUND_THRESHOLD_STATE
+ // This is meant to be a user-initiated job, and therefore gets similar network
+ // access to FGS.
+ || (procState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
+ && (capabilities
+ & ActivityManager.PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK) != 0);
}
/** @hide */
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 7a92434a4ba4..b9c9efee0ffd 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -24,6 +24,7 @@ import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_LOCATION
import static android.app.ActivityManager.PROCESS_CAPABILITY_FOREGROUND_MICROPHONE;
import static android.app.ActivityManager.PROCESS_CAPABILITY_NONE;
import static android.app.ActivityManager.PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK;
+import static android.app.ActivityManager.PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK;
import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP;
import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY;
@@ -2273,6 +2274,15 @@ public class OomAdjuster {
capability |= PROCESS_CAPABILITY_POWER_RESTRICTED_NETWORK;
}
}
+ if ((cstate.getCurCapability()
+ & PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK) != 0) {
+ if (clientProcState <= PROCESS_STATE_IMPORTANT_FOREGROUND) {
+ // This is used to grant network access to User Initiated Jobs.
+ if (cr.hasFlag(Context.BIND_BYPASS_USER_NETWORK_RESTRICTIONS)) {
+ capability |= PROCESS_CAPABILITY_USER_RESTRICTED_NETWORK;
+ }
+ }
+ }
if (shouldSkipDueToCycle(app, cstate, procState, adj, cycleReEval)) {
continue;
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 4e401b258550..b26a1705b309 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -4900,12 +4900,14 @@ public final class ProcessList {
final boolean isAllowed =
isProcStateAllowedWhileIdleOrPowerSaveMode(uidRec.getCurProcState(),
uidRec.getCurCapability())
- || isProcStateAllowedWhileOnRestrictBackground(uidRec.getCurProcState());
+ || isProcStateAllowedWhileOnRestrictBackground(uidRec.getCurProcState(),
+ uidRec.getCurCapability());
// Denotes whether uid's process state was previously allowed network access.
final boolean wasAllowed =
isProcStateAllowedWhileIdleOrPowerSaveMode(uidRec.getSetProcState(),
uidRec.getSetCapability())
- || isProcStateAllowedWhileOnRestrictBackground(uidRec.getSetProcState());
+ || isProcStateAllowedWhileOnRestrictBackground(uidRec.getSetProcState(),
+ uidRec.getSetCapability());
// When the uid is coming to foreground, AMS should inform the app thread that it should
// block for the network rules to get updated before launching an activity.
diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
index 92be0943c9f4..4c36b910e77b 100644
--- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
+++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java
@@ -5550,7 +5550,8 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
// Do this without the lock held. handleUidChanged() and handleUidGone() are
// called from the handler, so there's no multi-threading issue.
if (updated) {
- updateNetworkStats(uid, isProcStateAllowedWhileOnRestrictBackground(procState));
+ updateNetworkStats(uid,
+ isProcStateAllowedWhileOnRestrictBackground(procState, capability));
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
index 2d8fa1bcd8cd..2180a781e437 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java
@@ -21,11 +21,13 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED;
import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
import static android.net.NetworkCapabilities.TRANSPORT_VPN;
import static android.text.format.DateUtils.SECOND_IN_MILLIS;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.inOrder;
@@ -50,6 +52,7 @@ import static org.mockito.Mockito.verify;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityManager;
import android.app.job.JobInfo;
import android.content.ComponentName;
import android.content.Context;
@@ -81,6 +84,7 @@ import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.stubbing.Answer;
import java.time.Clock;
import java.time.ZoneOffset;
@@ -322,6 +326,109 @@ public class ConnectivityControllerTest {
}
@Test
+ public void testMeteredAllowed() throws Exception {
+ final JobInfo.Builder jobBuilder = createJob()
+ .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1),
+ DataUnit.MEBIBYTES.toBytes(1))
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY);
+ final JobStatus job = spy(createJobStatus(jobBuilder));
+
+ final ConnectivityController controller = new ConnectivityController(mService,
+ mFlexibilityController);
+
+ // Unmetered network is always "metered allowed"
+ {
+ final Network net = mock(Network.class);
+ final NetworkCapabilities caps = createCapabilitiesBuilder()
+ .addCapability(NET_CAPABILITY_NOT_CONGESTED)
+ .addCapability(NET_CAPABILITY_NOT_METERED)
+ .build();
+ assertTrue(controller.isSatisfied(job, net, caps, mConstants));
+ }
+
+ // Temporarily unmetered network is always "metered allowed"
+ {
+ final Network net = mock(Network.class);
+ final NetworkCapabilities caps = createCapabilitiesBuilder()
+ .addCapability(NET_CAPABILITY_NOT_CONGESTED)
+ .addCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED)
+ .build();
+ assertTrue(controller.isSatisfied(job, net, caps, mConstants));
+ }
+
+ // Respond with the default values in NetworkPolicyManager. If those ever change enough
+ // to cause these tests to fail, we would likely need to go and update
+ // ConnectivityController.
+ doAnswer(
+ (Answer<Integer>) invocationOnMock
+ -> NetworkPolicyManager.getDefaultProcessNetworkCapabilities(
+ invocationOnMock.getArgument(0)))
+ .when(mService).getUidCapabilities(anyInt());
+
+ // Foreground is always allowed for metered network
+ {
+ final Network net = mock(Network.class);
+ final NetworkCapabilities caps = createCapabilitiesBuilder()
+ .addCapability(NET_CAPABILITY_NOT_CONGESTED)
+ .build();
+
+ when(mService.getUidProcState(anyInt()))
+ .thenReturn(ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE);
+ assertTrue(controller.isSatisfied(job, net, caps, mConstants));
+
+ when(mService.getUidProcState(anyInt()))
+ .thenReturn(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
+ assertTrue(controller.isSatisfied(job, net, caps, mConstants));
+
+ when(mService.getUidProcState(anyInt())).thenReturn(ActivityManager.PROCESS_STATE_TOP);
+ assertTrue(controller.isSatisfied(job, net, caps, mConstants));
+
+ when(mService.getUidProcState(anyInt())).thenReturn(JobInfo.BIAS_DEFAULT);
+ when(job.getFlags()).thenReturn(JobInfo.FLAG_WILL_BE_FOREGROUND);
+ assertTrue(controller.isSatisfied(job, net, caps, mConstants));
+ }
+
+ when(mService.getUidProcState(anyInt())).thenReturn(ActivityManager.PROCESS_STATE_UNKNOWN);
+ when(job.getFlags()).thenReturn(0);
+
+ // User initiated is always allowed for metered network
+ {
+ final Network net = mock(Network.class);
+ final NetworkCapabilities caps = createCapabilitiesBuilder()
+ .addCapability(NET_CAPABILITY_NOT_CONGESTED)
+ .build();
+ when(job.shouldTreatAsUserInitiatedJob()).thenReturn(true);
+ assertTrue(controller.isSatisfied(job, net, caps, mConstants));
+ }
+
+ // Background non-user-initiated should follow the app's restricted state
+ {
+ final Network net = mock(Network.class);
+ final NetworkCapabilities caps = createCapabilitiesBuilder()
+ .addCapability(NET_CAPABILITY_NOT_CONGESTED)
+ .build();
+ when(job.shouldTreatAsUserInitiatedJob()).thenReturn(false);
+ when(mNetPolicyManager.getRestrictBackgroundStatus(anyInt()))
+ .thenReturn(ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED);
+ assertTrue(controller.isSatisfied(job, net, caps, mConstants));
+ // Test cache
+ when(mNetPolicyManager.getRestrictBackgroundStatus(anyInt()))
+ .thenReturn(ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED);
+ assertTrue(controller.isSatisfied(job, net, caps, mConstants));
+ // Clear cache
+ controller.onAppRemovedLocked(job.getSourcePackageName(), job.getSourceUid());
+ assertFalse(controller.isSatisfied(job, net, caps, mConstants));
+ // Test cache
+ when(mNetPolicyManager.getRestrictBackgroundStatus(anyInt()))
+ .thenReturn(ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED);
+ assertFalse(controller.isSatisfied(job, net, caps, mConstants));
+ // Clear cache
+ controller.onAppRemovedLocked(job.getSourcePackageName(), job.getSourceUid());
+ assertTrue(controller.isSatisfied(job, net, caps, mConstants));
+ }
+ }
+
+ @Test
public void testStrongEnough_Cellular() {
mConstants.CONN_UPDATE_ALL_JOBS_MIN_INTERVAL_MS = 0;