diff options
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; |