Loading core/java/android/app/AppOpsManager.java +12 −2 Original line number Diff line number Diff line Loading @@ -252,8 +252,10 @@ public class AppOpsManager { public static final int OP_INSTANT_APP_START_FOREGROUND = 68; /** @hide Answer incoming phone calls */ public static final int OP_ANSWER_PHONE_CALLS = 69; /** @hide Run jobs when in background */ public static final int OP_RUN_ANY_IN_BACKGROUND = 70; /** @hide */ public static final int _NUM_OP = 70; public static final int _NUM_OP = 71; /** Access to coarse location information. */ public static final String OPSTR_COARSE_LOCATION = "android:coarse_location"; Loading Loading @@ -492,7 +494,8 @@ public class AppOpsManager { OP_REQUEST_INSTALL_PACKAGES, OP_PICTURE_IN_PICTURE, OP_INSTANT_APP_START_FOREGROUND, OP_ANSWER_PHONE_CALLS OP_ANSWER_PHONE_CALLS, OP_RUN_ANY_IN_BACKGROUND, }; /** Loading Loading @@ -570,6 +573,7 @@ public class AppOpsManager { OPSTR_PICTURE_IN_PICTURE, OPSTR_INSTANT_APP_START_FOREGROUND, OPSTR_ANSWER_PHONE_CALLS, null, // OP_RUN_ANY_IN_BACKGROUND }; /** Loading Loading @@ -647,6 +651,7 @@ public class AppOpsManager { "PICTURE_IN_PICTURE", "INSTANT_APP_START_FOREGROUND", "ANSWER_PHONE_CALLS", "RUN_ANY_IN_BACKGROUND", }; /** Loading Loading @@ -724,6 +729,7 @@ public class AppOpsManager { null, // no permission for entering picture-in-picture on hide Manifest.permission.INSTANT_APP_FOREGROUND_SERVICE, Manifest.permission.ANSWER_PHONE_CALLS, null, // no permission for OP_RUN_ANY_IN_BACKGROUND }; /** Loading Loading @@ -802,6 +808,7 @@ public class AppOpsManager { null, // ENTER_PICTURE_IN_PICTURE_ON_HIDE null, // INSTANT_APP_START_FOREGROUND null, // ANSWER_PHONE_CALLS null, // OP_RUN_ANY_IN_BACKGROUND }; /** Loading Loading @@ -879,6 +886,7 @@ public class AppOpsManager { false, // ENTER_PICTURE_IN_PICTURE_ON_HIDE false, // INSTANT_APP_START_FOREGROUND false, // ANSWER_PHONE_CALLS false, // OP_RUN_ANY_IN_BACKGROUND }; /** Loading Loading @@ -955,6 +963,7 @@ public class AppOpsManager { AppOpsManager.MODE_ALLOWED, // OP_PICTURE_IN_PICTURE AppOpsManager.MODE_DEFAULT, // OP_INSTANT_APP_START_FOREGROUND AppOpsManager.MODE_ALLOWED, // ANSWER_PHONE_CALLS AppOpsManager.MODE_ALLOWED, // OP_RUN_ANY_IN_BACKGROUND }; /** Loading Loading @@ -1035,6 +1044,7 @@ public class AppOpsManager { false, // OP_PICTURE_IN_PICTURE false, false, // ANSWER_PHONE_CALLS false, // OP_RUN_ANY_IN_BACKGROUND }; /** Loading services/core/java/com/android/server/AppOpsService.java +66 −2 Original line number Diff line number Diff line Loading @@ -52,6 +52,7 @@ import android.util.SparseIntArray; import android.util.TimeUtils; import android.util.Xml; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IAppOpsCallback; import com.android.internal.app.IAppOpsService; import com.android.internal.os.Zygote; Loading Loading @@ -87,6 +88,11 @@ public class AppOpsService extends IAppOpsService.Stub { static final String TAG = "AppOps"; static final boolean DEBUG = false; private static final int NO_VERSION = -1; /** Increment by one every time and add the corresponding upgrade logic in * {@link #upgradeLocked(int)} below. The first version was 1 */ private static final int CURRENT_VERSION = 1; // Write at most every 30 minutes. static final long WRITE_DELAY = DEBUG ? 1000 : 30*60*1000; Loading @@ -112,14 +118,16 @@ public class AppOpsService extends IAppOpsService.Stub { } }; private final SparseArray<UidState> mUidStates = new SparseArray<>(); @VisibleForTesting final SparseArray<UidState> mUidStates = new SparseArray<>(); /* * These are app op restrictions imposed per user from various parties. */ private final ArrayMap<IBinder, ClientRestrictionState> mOpUserRestrictions = new ArrayMap<>(); private static final class UidState { @VisibleForTesting static final class UidState { public final int uid; public ArrayMap<String, Ops> pkgOps; public SparseIntArray opModes; Loading Loading @@ -1398,6 +1406,7 @@ public class AppOpsService extends IAppOpsService.Stub { } void readState() { int oldVersion = NO_VERSION; synchronized (mFile) { synchronized (this) { FileInputStream stream; Loading @@ -1422,6 +1431,11 @@ public class AppOpsService extends IAppOpsService.Stub { throw new IllegalStateException("no start tag found"); } final String versionString = parser.getAttributeValue(null, "v"); if (versionString != null) { oldVersion = Integer.parseInt(versionString); } int outerDepth = parser.getDepth(); while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { Loading Loading @@ -1464,6 +1478,55 @@ public class AppOpsService extends IAppOpsService.Stub { } } } synchronized (this) { upgradeLocked(oldVersion); } } private void upgradeRunAnyInBackgroundLocked() { for (int i = 0; i < mUidStates.size(); i++) { final UidState uidState = mUidStates.valueAt(i); if (uidState == null) { continue; } if (uidState.opModes != null) { final int idx = uidState.opModes.indexOfKey(AppOpsManager.OP_RUN_IN_BACKGROUND); if (idx >= 0) { uidState.opModes.put(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uidState.opModes.valueAt(idx)); } } if (uidState.pkgOps == null) { continue; } for (int j = 0; j < uidState.pkgOps.size(); j++) { Ops ops = uidState.pkgOps.valueAt(j); if (ops != null) { final Op op = ops.get(AppOpsManager.OP_RUN_IN_BACKGROUND); if (op != null && op.mode != AppOpsManager.opToDefaultMode(op.op)) { final Op copy = new Op(op.uid, op.packageName, AppOpsManager.OP_RUN_ANY_IN_BACKGROUND); copy.mode = op.mode; ops.put(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, copy); } } } } } private void upgradeLocked(int oldVersion) { if (oldVersion >= CURRENT_VERSION) { return; } Slog.d(TAG, "Upgrading app-ops xml from version " + oldVersion + " to " + CURRENT_VERSION); switch (oldVersion) { case NO_VERSION: upgradeRunAnyInBackgroundLocked(); // fall through case 1: // for future upgrades } scheduleFastWriteLocked(); } void readUidOps(XmlPullParser parser) throws NumberFormatException, Loading Loading @@ -1613,6 +1676,7 @@ public class AppOpsService extends IAppOpsService.Stub { out.setOutput(stream, StandardCharsets.UTF_8.name()); out.startDocument(null, true); out.startTag(null, "app-ops"); out.attribute(null, "v", String.valueOf(CURRENT_VERSION)); final int uidStateCount = mUidStates.size(); for (int i = 0; i < uidStateCount; i++) { Loading services/core/java/com/android/server/job/JobSchedulerService.java +32 −2 Original line number Diff line number Diff line Loading @@ -80,6 +80,7 @@ import com.android.server.FgThread; import com.android.server.LocalServices; import com.android.server.job.JobStore.JobStatusFunctor; import com.android.server.job.controllers.AppIdleController; import com.android.server.job.controllers.BackgroundJobsController; import com.android.server.job.controllers.BatteryController; import com.android.server.job.controllers.ConnectivityController; import com.android.server.job.controllers.ContentObserverController; Loading Loading @@ -140,6 +141,8 @@ public final class JobSchedulerService extends com.android.server.SystemService BatteryController mBatteryController; /** Need direct access to this for testing. */ StorageController mStorageController; /** Need directly for sending uid state changes */ private BackgroundJobsController mBackgroundJobsController; /** * Queue of pending jobs. The JobServiceContext class will receive jobs from this list * when ready to execute them. Loading Loading @@ -225,6 +228,7 @@ public final class JobSchedulerService extends com.android.server.SystemService private static final String KEY_MAX_WORK_RESCHEDULE_COUNT = "max_work_reschedule_count"; private static final String KEY_MIN_LINEAR_BACKOFF_TIME = "min_linear_backoff_time"; private static final String KEY_MIN_EXP_BACKOFF_TIME = "min_exp_backoff_time"; private static final String KEY_BG_JOBS_RESTRICTED = "bg_jobs_restricted"; private static final int DEFAULT_MIN_IDLE_COUNT = 1; private static final int DEFAULT_MIN_CHARGING_COUNT = 1; Loading @@ -240,6 +244,7 @@ public final class JobSchedulerService extends com.android.server.SystemService private static final int DEFAULT_BG_MODERATE_JOB_COUNT = 4; private static final int DEFAULT_BG_LOW_JOB_COUNT = 1; private static final int DEFAULT_BG_CRITICAL_JOB_COUNT = 1; private static final boolean DEFAULT_BG_JOBS_RESTRICTED = false; private static final int DEFAULT_MAX_STANDARD_RESCHEDULE_COUNT = Integer.MAX_VALUE; private static final int DEFAULT_MAX_WORK_RESCHEDULE_COUNT = Integer.MAX_VALUE; private static final long DEFAULT_MIN_LINEAR_BACKOFF_TIME = JobInfo.MIN_BACKOFF_MILLIS; Loading Loading @@ -333,6 +338,11 @@ public final class JobSchedulerService extends com.android.server.SystemService */ long MIN_EXP_BACKOFF_TIME = DEFAULT_MIN_EXP_BACKOFF_TIME; /** * Runtime switch for throttling background jobs */ boolean BACKGROUND_JOBS_RESTRICTED = DEFAULT_BG_JOBS_RESTRICTED; private ContentResolver mResolver; private final KeyValueListParser mParser = new KeyValueListParser(','); Loading Loading @@ -411,6 +421,12 @@ public final class JobSchedulerService extends com.android.server.SystemService DEFAULT_MIN_LINEAR_BACKOFF_TIME); MIN_EXP_BACKOFF_TIME = mParser.getLong(KEY_MIN_EXP_BACKOFF_TIME, DEFAULT_MIN_EXP_BACKOFF_TIME); final boolean bgJobsRestricted = mParser.getBoolean(KEY_BG_JOBS_RESTRICTED, DEFAULT_BG_JOBS_RESTRICTED); if (bgJobsRestricted != BACKGROUND_JOBS_RESTRICTED) { mBackgroundJobsController.enableRestrictionsLocked( BACKGROUND_JOBS_RESTRICTED = bgJobsRestricted); } } } Loading Loading @@ -470,6 +486,9 @@ public final class JobSchedulerService extends com.android.server.SystemService pw.print(" "); pw.print(KEY_MIN_EXP_BACKOFF_TIME); pw.print("="); pw.print(MIN_EXP_BACKOFF_TIME); pw.println(); pw.print(" "); pw.print(KEY_BG_JOBS_RESTRICTED); pw.print("="); pw.print(BACKGROUND_JOBS_RESTRICTED); pw.println(); } } Loading Loading @@ -613,15 +632,24 @@ public final class JobSchedulerService extends com.android.server.SystemService if (disabled) { cancelJobsForUid(uid, "uid gone"); } synchronized (mLock) { mBackgroundJobsController.setUidActiveLocked(uid, false); } } @Override public void onUidActive(int uid) throws RemoteException { synchronized (mLock) { mBackgroundJobsController.setUidActiveLocked(uid, true); } } @Override public void onUidIdle(int uid, boolean disabled) { if (disabled) { cancelJobsForUid(uid, "app uid idle"); } synchronized (mLock) { mBackgroundJobsController.setUidActiveLocked(uid, false); } } @Override public void onUidCachedChanged(int uid, boolean cached) { Loading Loading @@ -917,6 +945,8 @@ public final class JobSchedulerService extends com.android.server.SystemService mControllers.add(mBatteryController); mStorageController = StorageController.get(this); mControllers.add(mStorageController); mBackgroundJobsController = BackgroundJobsController.get(this); mControllers.add(mBackgroundJobsController); mControllers.add(AppIdleController.get(this)); mControllers.add(ContentObserverController.get(this)); mControllers.add(DeviceIdleJobsController.get(this)); Loading Loading @@ -997,8 +1027,8 @@ public final class JobSchedulerService extends com.android.server.SystemService try { ActivityManager.getService().registerUidObserver(mUidObserver, ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE | ActivityManager.UID_OBSERVER_IDLE, ActivityManager.PROCESS_STATE_UNKNOWN, null); | ActivityManager.UID_OBSERVER_IDLE | ActivityManager.UID_OBSERVER_ACTIVE, ActivityManager.PROCESS_STATE_UNKNOWN, null); } catch (RemoteException e) { // ignored; both services live in system_server } Loading services/core/java/com/android/server/job/controllers/BackgroundJobsController.java 0 → 100644 +314 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.server.job.controllers; import android.app.AppOpsManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.IDeviceIdleController; import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; import android.util.ArraySet; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; import com.android.internal.app.IAppOpsCallback; import com.android.internal.app.IAppOpsService; import com.android.internal.util.ArrayUtils; import com.android.server.job.JobSchedulerService; import com.android.server.job.JobStore; import java.io.PrintWriter; public final class BackgroundJobsController extends StateController { private static final String LOG_TAG = "BackgroundJobsController"; private static final boolean DEBUG = JobSchedulerService.DEBUG; // Singleton factory private static final Object sCreationLock = new Object(); private static volatile BackgroundJobsController sController; /* Runtime switch to keep feature under wraps */ private boolean mEnableSwitch; private final JobSchedulerService mJobSchedulerService; private final IAppOpsService mAppOpsService; private final IDeviceIdleController mDeviceIdleController; private final SparseBooleanArray mForegroundUids; private int[] mPowerWhitelistedAppIds; private int[] mTempWhitelistedAppIds; /** * Only tracks jobs for which source package app op RUN_ANY_IN_BACKGROUND is not ALLOWED. * Maps jobs to the sourceUid unlike the global {@link JobSchedulerService#mJobs JobStore} * which uses callingUid. */ private SparseArray<ArraySet<JobStatus>> mTrackedJobs; public static BackgroundJobsController get(JobSchedulerService service) { synchronized (sCreationLock) { if (sController == null) { sController = new BackgroundJobsController(service, service.getContext(), service.getLock()); } return sController; } } private BroadcastReceiver mDozeWhitelistReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { synchronized (mLock) { try { switch (intent.getAction()) { case PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED: mPowerWhitelistedAppIds = mDeviceIdleController.getAppIdWhitelist(); break; case PowerManager.ACTION_POWER_SAVE_TEMP_WHITELIST_CHANGED: mTempWhitelistedAppIds = mDeviceIdleController.getAppIdTempWhitelist(); break; } } catch (RemoteException rexc) { Slog.e(LOG_TAG, "Device idle controller not reachable"); } if (checkAllTrackedJobsLocked()) { mStateChangedListener.onControllerStateChanged(); } } } }; private BackgroundJobsController(JobSchedulerService service, Context context, Object lock) { super(service, context, lock); mJobSchedulerService = service; mAppOpsService = IAppOpsService.Stub.asInterface( ServiceManager.getService(Context.APP_OPS_SERVICE)); mDeviceIdleController = IDeviceIdleController.Stub.asInterface( ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER)); mForegroundUids = new SparseBooleanArray(); mTrackedJobs = new SparseArray<>(); try { mAppOpsService.startWatchingMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, null, new AppOpsWatcher()); mPowerWhitelistedAppIds = mDeviceIdleController.getAppIdWhitelist(); mTempWhitelistedAppIds = mDeviceIdleController.getAppIdTempWhitelist(); } catch (RemoteException rexc) { // Shouldn't happen as they are in the same process. Slog.e(LOG_TAG, "AppOps or DeviceIdle service not reachable", rexc); } IntentFilter powerWhitelistFilter = new IntentFilter(); powerWhitelistFilter.addAction(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED); powerWhitelistFilter.addAction(PowerManager.ACTION_POWER_SAVE_TEMP_WHITELIST_CHANGED); context.registerReceiverAsUser(mDozeWhitelistReceiver, UserHandle.ALL, powerWhitelistFilter, null, null); mEnableSwitch = false; } @Override public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) { final int uid = jobStatus.getSourceUid(); final String packageName = jobStatus.getSourcePackageName(); try { final int mode = mAppOpsService.checkOperation(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uid, packageName); if (mode == AppOpsManager.MODE_ALLOWED) { jobStatus.setBackgroundNotRestrictedConstraintSatisfied(true); return; } } catch (RemoteException rexc) { Slog.e(LOG_TAG, "Cannot reach app ops service", rexc); } jobStatus.setBackgroundNotRestrictedConstraintSatisfied(canRunJobLocked(uid)); startTrackingJobLocked(jobStatus); } @Override public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob, boolean forUpdate) { stopTrackingJobLocked(jobStatus); } /* Called by JobSchedulerService to report uid state changes between active and idle */ public void setUidActiveLocked(int uid, boolean active) { final boolean changed = (active != mForegroundUids.get(uid)); if (!changed) { return; } if (DEBUG) { Slog.d(LOG_TAG, "uid " + uid + " going to " + (active ? "fg" : "bg")); } if (active) { mForegroundUids.put(uid, true); } else { mForegroundUids.delete(uid); } if (checkTrackedJobsForUidLocked(uid)) { mStateChangedListener.onControllerStateChanged(); } } @Override public void dumpControllerStateLocked(final PrintWriter pw, final int filterUid) { pw.println("Background restrictions: global switch = " + mEnableSwitch); pw.print("Foreground uids: ["); for (int i = 0; i < mForegroundUids.size(); i++) { if (mForegroundUids.valueAt(i)) pw.print(mForegroundUids.keyAt(i) + " "); } pw.println("]"); mJobSchedulerService.getJobStore().forEachJob(new JobStore.JobStatusFunctor() { @Override public void process(JobStatus jobStatus) { if (!jobStatus.shouldDump(filterUid)) { return; } final int uid = jobStatus.getSourceUid(); pw.print(" #"); jobStatus.printUniqueId(pw); pw.print(" from "); UserHandle.formatUid(pw, uid); pw.print(mForegroundUids.get(uid) ? " foreground" : " background"); if (isWhitelistedLocked(uid)) { pw.print(", whitelisted"); } pw.print(": "); pw.print(jobStatus.getSourcePackageName()); pw.print(" [background restrictions"); final ArraySet<JobStatus> jobsForUid = mTrackedJobs.get(uid); pw.print(jobsForUid != null && jobsForUid.contains(jobStatus) ? " on]" : " off]"); if ((jobStatus.satisfiedConstraints & JobStatus.CONSTRAINT_BACKGROUND_NOT_RESTRICTED) != 0) { pw.println(" RUNNABLE"); } else { pw.println(" WAITING"); } } }); } public void enableRestrictionsLocked(boolean enable) { mEnableSwitch = enable; Slog.d(LOG_TAG, "Background jobs restrictions switch changed to " + mEnableSwitch); if (checkAllTrackedJobsLocked()) { mStateChangedListener.onControllerStateChanged(); } } void startTrackingJobLocked(JobStatus jobStatus) { final int uid = jobStatus.getSourceUid(); ArraySet<JobStatus> jobsForUid = mTrackedJobs.get(uid); if (jobsForUid == null) { jobsForUid = new ArraySet<>(); mTrackedJobs.put(uid, jobsForUid); } jobsForUid.add(jobStatus); } void stopTrackingJobLocked(JobStatus jobStatus) { final int uid = jobStatus.getSourceUid(); ArraySet<JobStatus> jobsForUid = mTrackedJobs.get(uid); if (jobsForUid != null) { jobsForUid.remove(jobStatus); } } boolean checkAllTrackedJobsLocked() { boolean changed = false; for (int i = 0; i < mTrackedJobs.size(); i++) { changed |= checkTrackedJobsForUidLocked(mTrackedJobs.keyAt(i)); } return changed; } private boolean checkTrackedJobsForUidLocked(int uid) { final ArraySet<JobStatus> jobsForUid = mTrackedJobs.get(uid); boolean changed = false; if (jobsForUid != null) { for (int i = 0; i < jobsForUid.size(); i++) { JobStatus jobStatus = jobsForUid.valueAt(i); changed |= jobStatus.setBackgroundNotRestrictedConstraintSatisfied( canRunJobLocked(uid)); } } return changed; } boolean isWhitelistedLocked(int uid) { return ArrayUtils.contains(mTempWhitelistedAppIds, UserHandle.getAppId(uid)) || ArrayUtils.contains(mPowerWhitelistedAppIds, UserHandle.getAppId(uid)); } boolean canRunJobLocked(int uid) { return !mEnableSwitch || mForegroundUids.get(uid) || isWhitelistedLocked(uid); } private final class AppOpsWatcher extends IAppOpsCallback.Stub { @Override public void opChanged(int op, int uid, String packageName) throws RemoteException { synchronized (mLock) { final int mode = mAppOpsService.checkOperation(op, uid, packageName); if (DEBUG) { Slog.d(LOG_TAG, "Appop changed for " + uid + ", " + packageName + " to " + mode); } final boolean shouldTrack = (mode != AppOpsManager.MODE_ALLOWED); UpdateTrackedJobsFunc updateTrackedJobs = new UpdateTrackedJobsFunc(uid, packageName, shouldTrack); mJobSchedulerService.getJobStore().forEachJob(updateTrackedJobs); if (updateTrackedJobs.mChanged) { mStateChangedListener.onControllerStateChanged(); } } } } private final class UpdateTrackedJobsFunc implements JobStore.JobStatusFunctor { private final String mPackageName; private final int mUid; private final boolean mShouldTrack; private boolean mChanged = false; UpdateTrackedJobsFunc(int uid, String packageName, boolean shouldTrack) { mUid = uid; mPackageName = packageName; mShouldTrack = shouldTrack; } @Override public void process(JobStatus jobStatus) { final String packageName = jobStatus.getSourcePackageName(); final int uid = jobStatus.getSourceUid(); if (mUid != uid || !mPackageName.equals(packageName)) { return; } if (mShouldTrack) { mChanged |= jobStatus.setBackgroundNotRestrictedConstraintSatisfied( canRunJobLocked(uid)); startTrackingJobLocked(jobStatus); } else { mChanged |= jobStatus.setBackgroundNotRestrictedConstraintSatisfied(true); stopTrackingJobLocked(jobStatus); } } } } services/core/java/com/android/server/job/controllers/JobStatus.java +10 −1 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
core/java/android/app/AppOpsManager.java +12 −2 Original line number Diff line number Diff line Loading @@ -252,8 +252,10 @@ public class AppOpsManager { public static final int OP_INSTANT_APP_START_FOREGROUND = 68; /** @hide Answer incoming phone calls */ public static final int OP_ANSWER_PHONE_CALLS = 69; /** @hide Run jobs when in background */ public static final int OP_RUN_ANY_IN_BACKGROUND = 70; /** @hide */ public static final int _NUM_OP = 70; public static final int _NUM_OP = 71; /** Access to coarse location information. */ public static final String OPSTR_COARSE_LOCATION = "android:coarse_location"; Loading Loading @@ -492,7 +494,8 @@ public class AppOpsManager { OP_REQUEST_INSTALL_PACKAGES, OP_PICTURE_IN_PICTURE, OP_INSTANT_APP_START_FOREGROUND, OP_ANSWER_PHONE_CALLS OP_ANSWER_PHONE_CALLS, OP_RUN_ANY_IN_BACKGROUND, }; /** Loading Loading @@ -570,6 +573,7 @@ public class AppOpsManager { OPSTR_PICTURE_IN_PICTURE, OPSTR_INSTANT_APP_START_FOREGROUND, OPSTR_ANSWER_PHONE_CALLS, null, // OP_RUN_ANY_IN_BACKGROUND }; /** Loading Loading @@ -647,6 +651,7 @@ public class AppOpsManager { "PICTURE_IN_PICTURE", "INSTANT_APP_START_FOREGROUND", "ANSWER_PHONE_CALLS", "RUN_ANY_IN_BACKGROUND", }; /** Loading Loading @@ -724,6 +729,7 @@ public class AppOpsManager { null, // no permission for entering picture-in-picture on hide Manifest.permission.INSTANT_APP_FOREGROUND_SERVICE, Manifest.permission.ANSWER_PHONE_CALLS, null, // no permission for OP_RUN_ANY_IN_BACKGROUND }; /** Loading Loading @@ -802,6 +808,7 @@ public class AppOpsManager { null, // ENTER_PICTURE_IN_PICTURE_ON_HIDE null, // INSTANT_APP_START_FOREGROUND null, // ANSWER_PHONE_CALLS null, // OP_RUN_ANY_IN_BACKGROUND }; /** Loading Loading @@ -879,6 +886,7 @@ public class AppOpsManager { false, // ENTER_PICTURE_IN_PICTURE_ON_HIDE false, // INSTANT_APP_START_FOREGROUND false, // ANSWER_PHONE_CALLS false, // OP_RUN_ANY_IN_BACKGROUND }; /** Loading Loading @@ -955,6 +963,7 @@ public class AppOpsManager { AppOpsManager.MODE_ALLOWED, // OP_PICTURE_IN_PICTURE AppOpsManager.MODE_DEFAULT, // OP_INSTANT_APP_START_FOREGROUND AppOpsManager.MODE_ALLOWED, // ANSWER_PHONE_CALLS AppOpsManager.MODE_ALLOWED, // OP_RUN_ANY_IN_BACKGROUND }; /** Loading Loading @@ -1035,6 +1044,7 @@ public class AppOpsManager { false, // OP_PICTURE_IN_PICTURE false, false, // ANSWER_PHONE_CALLS false, // OP_RUN_ANY_IN_BACKGROUND }; /** Loading
services/core/java/com/android/server/AppOpsService.java +66 −2 Original line number Diff line number Diff line Loading @@ -52,6 +52,7 @@ import android.util.SparseIntArray; import android.util.TimeUtils; import android.util.Xml; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.IAppOpsCallback; import com.android.internal.app.IAppOpsService; import com.android.internal.os.Zygote; Loading Loading @@ -87,6 +88,11 @@ public class AppOpsService extends IAppOpsService.Stub { static final String TAG = "AppOps"; static final boolean DEBUG = false; private static final int NO_VERSION = -1; /** Increment by one every time and add the corresponding upgrade logic in * {@link #upgradeLocked(int)} below. The first version was 1 */ private static final int CURRENT_VERSION = 1; // Write at most every 30 minutes. static final long WRITE_DELAY = DEBUG ? 1000 : 30*60*1000; Loading @@ -112,14 +118,16 @@ public class AppOpsService extends IAppOpsService.Stub { } }; private final SparseArray<UidState> mUidStates = new SparseArray<>(); @VisibleForTesting final SparseArray<UidState> mUidStates = new SparseArray<>(); /* * These are app op restrictions imposed per user from various parties. */ private final ArrayMap<IBinder, ClientRestrictionState> mOpUserRestrictions = new ArrayMap<>(); private static final class UidState { @VisibleForTesting static final class UidState { public final int uid; public ArrayMap<String, Ops> pkgOps; public SparseIntArray opModes; Loading Loading @@ -1398,6 +1406,7 @@ public class AppOpsService extends IAppOpsService.Stub { } void readState() { int oldVersion = NO_VERSION; synchronized (mFile) { synchronized (this) { FileInputStream stream; Loading @@ -1422,6 +1431,11 @@ public class AppOpsService extends IAppOpsService.Stub { throw new IllegalStateException("no start tag found"); } final String versionString = parser.getAttributeValue(null, "v"); if (versionString != null) { oldVersion = Integer.parseInt(versionString); } int outerDepth = parser.getDepth(); while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { Loading Loading @@ -1464,6 +1478,55 @@ public class AppOpsService extends IAppOpsService.Stub { } } } synchronized (this) { upgradeLocked(oldVersion); } } private void upgradeRunAnyInBackgroundLocked() { for (int i = 0; i < mUidStates.size(); i++) { final UidState uidState = mUidStates.valueAt(i); if (uidState == null) { continue; } if (uidState.opModes != null) { final int idx = uidState.opModes.indexOfKey(AppOpsManager.OP_RUN_IN_BACKGROUND); if (idx >= 0) { uidState.opModes.put(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uidState.opModes.valueAt(idx)); } } if (uidState.pkgOps == null) { continue; } for (int j = 0; j < uidState.pkgOps.size(); j++) { Ops ops = uidState.pkgOps.valueAt(j); if (ops != null) { final Op op = ops.get(AppOpsManager.OP_RUN_IN_BACKGROUND); if (op != null && op.mode != AppOpsManager.opToDefaultMode(op.op)) { final Op copy = new Op(op.uid, op.packageName, AppOpsManager.OP_RUN_ANY_IN_BACKGROUND); copy.mode = op.mode; ops.put(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, copy); } } } } } private void upgradeLocked(int oldVersion) { if (oldVersion >= CURRENT_VERSION) { return; } Slog.d(TAG, "Upgrading app-ops xml from version " + oldVersion + " to " + CURRENT_VERSION); switch (oldVersion) { case NO_VERSION: upgradeRunAnyInBackgroundLocked(); // fall through case 1: // for future upgrades } scheduleFastWriteLocked(); } void readUidOps(XmlPullParser parser) throws NumberFormatException, Loading Loading @@ -1613,6 +1676,7 @@ public class AppOpsService extends IAppOpsService.Stub { out.setOutput(stream, StandardCharsets.UTF_8.name()); out.startDocument(null, true); out.startTag(null, "app-ops"); out.attribute(null, "v", String.valueOf(CURRENT_VERSION)); final int uidStateCount = mUidStates.size(); for (int i = 0; i < uidStateCount; i++) { Loading
services/core/java/com/android/server/job/JobSchedulerService.java +32 −2 Original line number Diff line number Diff line Loading @@ -80,6 +80,7 @@ import com.android.server.FgThread; import com.android.server.LocalServices; import com.android.server.job.JobStore.JobStatusFunctor; import com.android.server.job.controllers.AppIdleController; import com.android.server.job.controllers.BackgroundJobsController; import com.android.server.job.controllers.BatteryController; import com.android.server.job.controllers.ConnectivityController; import com.android.server.job.controllers.ContentObserverController; Loading Loading @@ -140,6 +141,8 @@ public final class JobSchedulerService extends com.android.server.SystemService BatteryController mBatteryController; /** Need direct access to this for testing. */ StorageController mStorageController; /** Need directly for sending uid state changes */ private BackgroundJobsController mBackgroundJobsController; /** * Queue of pending jobs. The JobServiceContext class will receive jobs from this list * when ready to execute them. Loading Loading @@ -225,6 +228,7 @@ public final class JobSchedulerService extends com.android.server.SystemService private static final String KEY_MAX_WORK_RESCHEDULE_COUNT = "max_work_reschedule_count"; private static final String KEY_MIN_LINEAR_BACKOFF_TIME = "min_linear_backoff_time"; private static final String KEY_MIN_EXP_BACKOFF_TIME = "min_exp_backoff_time"; private static final String KEY_BG_JOBS_RESTRICTED = "bg_jobs_restricted"; private static final int DEFAULT_MIN_IDLE_COUNT = 1; private static final int DEFAULT_MIN_CHARGING_COUNT = 1; Loading @@ -240,6 +244,7 @@ public final class JobSchedulerService extends com.android.server.SystemService private static final int DEFAULT_BG_MODERATE_JOB_COUNT = 4; private static final int DEFAULT_BG_LOW_JOB_COUNT = 1; private static final int DEFAULT_BG_CRITICAL_JOB_COUNT = 1; private static final boolean DEFAULT_BG_JOBS_RESTRICTED = false; private static final int DEFAULT_MAX_STANDARD_RESCHEDULE_COUNT = Integer.MAX_VALUE; private static final int DEFAULT_MAX_WORK_RESCHEDULE_COUNT = Integer.MAX_VALUE; private static final long DEFAULT_MIN_LINEAR_BACKOFF_TIME = JobInfo.MIN_BACKOFF_MILLIS; Loading Loading @@ -333,6 +338,11 @@ public final class JobSchedulerService extends com.android.server.SystemService */ long MIN_EXP_BACKOFF_TIME = DEFAULT_MIN_EXP_BACKOFF_TIME; /** * Runtime switch for throttling background jobs */ boolean BACKGROUND_JOBS_RESTRICTED = DEFAULT_BG_JOBS_RESTRICTED; private ContentResolver mResolver; private final KeyValueListParser mParser = new KeyValueListParser(','); Loading Loading @@ -411,6 +421,12 @@ public final class JobSchedulerService extends com.android.server.SystemService DEFAULT_MIN_LINEAR_BACKOFF_TIME); MIN_EXP_BACKOFF_TIME = mParser.getLong(KEY_MIN_EXP_BACKOFF_TIME, DEFAULT_MIN_EXP_BACKOFF_TIME); final boolean bgJobsRestricted = mParser.getBoolean(KEY_BG_JOBS_RESTRICTED, DEFAULT_BG_JOBS_RESTRICTED); if (bgJobsRestricted != BACKGROUND_JOBS_RESTRICTED) { mBackgroundJobsController.enableRestrictionsLocked( BACKGROUND_JOBS_RESTRICTED = bgJobsRestricted); } } } Loading Loading @@ -470,6 +486,9 @@ public final class JobSchedulerService extends com.android.server.SystemService pw.print(" "); pw.print(KEY_MIN_EXP_BACKOFF_TIME); pw.print("="); pw.print(MIN_EXP_BACKOFF_TIME); pw.println(); pw.print(" "); pw.print(KEY_BG_JOBS_RESTRICTED); pw.print("="); pw.print(BACKGROUND_JOBS_RESTRICTED); pw.println(); } } Loading Loading @@ -613,15 +632,24 @@ public final class JobSchedulerService extends com.android.server.SystemService if (disabled) { cancelJobsForUid(uid, "uid gone"); } synchronized (mLock) { mBackgroundJobsController.setUidActiveLocked(uid, false); } } @Override public void onUidActive(int uid) throws RemoteException { synchronized (mLock) { mBackgroundJobsController.setUidActiveLocked(uid, true); } } @Override public void onUidIdle(int uid, boolean disabled) { if (disabled) { cancelJobsForUid(uid, "app uid idle"); } synchronized (mLock) { mBackgroundJobsController.setUidActiveLocked(uid, false); } } @Override public void onUidCachedChanged(int uid, boolean cached) { Loading Loading @@ -917,6 +945,8 @@ public final class JobSchedulerService extends com.android.server.SystemService mControllers.add(mBatteryController); mStorageController = StorageController.get(this); mControllers.add(mStorageController); mBackgroundJobsController = BackgroundJobsController.get(this); mControllers.add(mBackgroundJobsController); mControllers.add(AppIdleController.get(this)); mControllers.add(ContentObserverController.get(this)); mControllers.add(DeviceIdleJobsController.get(this)); Loading Loading @@ -997,8 +1027,8 @@ public final class JobSchedulerService extends com.android.server.SystemService try { ActivityManager.getService().registerUidObserver(mUidObserver, ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE | ActivityManager.UID_OBSERVER_IDLE, ActivityManager.PROCESS_STATE_UNKNOWN, null); | ActivityManager.UID_OBSERVER_IDLE | ActivityManager.UID_OBSERVER_ACTIVE, ActivityManager.PROCESS_STATE_UNKNOWN, null); } catch (RemoteException e) { // ignored; both services live in system_server } Loading
services/core/java/com/android/server/job/controllers/BackgroundJobsController.java 0 → 100644 +314 −0 Original line number Diff line number Diff line /* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License */ package com.android.server.job.controllers; import android.app.AppOpsManager; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.IDeviceIdleController; import android.os.PowerManager; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; import android.util.ArraySet; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; import com.android.internal.app.IAppOpsCallback; import com.android.internal.app.IAppOpsService; import com.android.internal.util.ArrayUtils; import com.android.server.job.JobSchedulerService; import com.android.server.job.JobStore; import java.io.PrintWriter; public final class BackgroundJobsController extends StateController { private static final String LOG_TAG = "BackgroundJobsController"; private static final boolean DEBUG = JobSchedulerService.DEBUG; // Singleton factory private static final Object sCreationLock = new Object(); private static volatile BackgroundJobsController sController; /* Runtime switch to keep feature under wraps */ private boolean mEnableSwitch; private final JobSchedulerService mJobSchedulerService; private final IAppOpsService mAppOpsService; private final IDeviceIdleController mDeviceIdleController; private final SparseBooleanArray mForegroundUids; private int[] mPowerWhitelistedAppIds; private int[] mTempWhitelistedAppIds; /** * Only tracks jobs for which source package app op RUN_ANY_IN_BACKGROUND is not ALLOWED. * Maps jobs to the sourceUid unlike the global {@link JobSchedulerService#mJobs JobStore} * which uses callingUid. */ private SparseArray<ArraySet<JobStatus>> mTrackedJobs; public static BackgroundJobsController get(JobSchedulerService service) { synchronized (sCreationLock) { if (sController == null) { sController = new BackgroundJobsController(service, service.getContext(), service.getLock()); } return sController; } } private BroadcastReceiver mDozeWhitelistReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { synchronized (mLock) { try { switch (intent.getAction()) { case PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED: mPowerWhitelistedAppIds = mDeviceIdleController.getAppIdWhitelist(); break; case PowerManager.ACTION_POWER_SAVE_TEMP_WHITELIST_CHANGED: mTempWhitelistedAppIds = mDeviceIdleController.getAppIdTempWhitelist(); break; } } catch (RemoteException rexc) { Slog.e(LOG_TAG, "Device idle controller not reachable"); } if (checkAllTrackedJobsLocked()) { mStateChangedListener.onControllerStateChanged(); } } } }; private BackgroundJobsController(JobSchedulerService service, Context context, Object lock) { super(service, context, lock); mJobSchedulerService = service; mAppOpsService = IAppOpsService.Stub.asInterface( ServiceManager.getService(Context.APP_OPS_SERVICE)); mDeviceIdleController = IDeviceIdleController.Stub.asInterface( ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER)); mForegroundUids = new SparseBooleanArray(); mTrackedJobs = new SparseArray<>(); try { mAppOpsService.startWatchingMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, null, new AppOpsWatcher()); mPowerWhitelistedAppIds = mDeviceIdleController.getAppIdWhitelist(); mTempWhitelistedAppIds = mDeviceIdleController.getAppIdTempWhitelist(); } catch (RemoteException rexc) { // Shouldn't happen as they are in the same process. Slog.e(LOG_TAG, "AppOps or DeviceIdle service not reachable", rexc); } IntentFilter powerWhitelistFilter = new IntentFilter(); powerWhitelistFilter.addAction(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED); powerWhitelistFilter.addAction(PowerManager.ACTION_POWER_SAVE_TEMP_WHITELIST_CHANGED); context.registerReceiverAsUser(mDozeWhitelistReceiver, UserHandle.ALL, powerWhitelistFilter, null, null); mEnableSwitch = false; } @Override public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) { final int uid = jobStatus.getSourceUid(); final String packageName = jobStatus.getSourcePackageName(); try { final int mode = mAppOpsService.checkOperation(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, uid, packageName); if (mode == AppOpsManager.MODE_ALLOWED) { jobStatus.setBackgroundNotRestrictedConstraintSatisfied(true); return; } } catch (RemoteException rexc) { Slog.e(LOG_TAG, "Cannot reach app ops service", rexc); } jobStatus.setBackgroundNotRestrictedConstraintSatisfied(canRunJobLocked(uid)); startTrackingJobLocked(jobStatus); } @Override public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob, boolean forUpdate) { stopTrackingJobLocked(jobStatus); } /* Called by JobSchedulerService to report uid state changes between active and idle */ public void setUidActiveLocked(int uid, boolean active) { final boolean changed = (active != mForegroundUids.get(uid)); if (!changed) { return; } if (DEBUG) { Slog.d(LOG_TAG, "uid " + uid + " going to " + (active ? "fg" : "bg")); } if (active) { mForegroundUids.put(uid, true); } else { mForegroundUids.delete(uid); } if (checkTrackedJobsForUidLocked(uid)) { mStateChangedListener.onControllerStateChanged(); } } @Override public void dumpControllerStateLocked(final PrintWriter pw, final int filterUid) { pw.println("Background restrictions: global switch = " + mEnableSwitch); pw.print("Foreground uids: ["); for (int i = 0; i < mForegroundUids.size(); i++) { if (mForegroundUids.valueAt(i)) pw.print(mForegroundUids.keyAt(i) + " "); } pw.println("]"); mJobSchedulerService.getJobStore().forEachJob(new JobStore.JobStatusFunctor() { @Override public void process(JobStatus jobStatus) { if (!jobStatus.shouldDump(filterUid)) { return; } final int uid = jobStatus.getSourceUid(); pw.print(" #"); jobStatus.printUniqueId(pw); pw.print(" from "); UserHandle.formatUid(pw, uid); pw.print(mForegroundUids.get(uid) ? " foreground" : " background"); if (isWhitelistedLocked(uid)) { pw.print(", whitelisted"); } pw.print(": "); pw.print(jobStatus.getSourcePackageName()); pw.print(" [background restrictions"); final ArraySet<JobStatus> jobsForUid = mTrackedJobs.get(uid); pw.print(jobsForUid != null && jobsForUid.contains(jobStatus) ? " on]" : " off]"); if ((jobStatus.satisfiedConstraints & JobStatus.CONSTRAINT_BACKGROUND_NOT_RESTRICTED) != 0) { pw.println(" RUNNABLE"); } else { pw.println(" WAITING"); } } }); } public void enableRestrictionsLocked(boolean enable) { mEnableSwitch = enable; Slog.d(LOG_TAG, "Background jobs restrictions switch changed to " + mEnableSwitch); if (checkAllTrackedJobsLocked()) { mStateChangedListener.onControllerStateChanged(); } } void startTrackingJobLocked(JobStatus jobStatus) { final int uid = jobStatus.getSourceUid(); ArraySet<JobStatus> jobsForUid = mTrackedJobs.get(uid); if (jobsForUid == null) { jobsForUid = new ArraySet<>(); mTrackedJobs.put(uid, jobsForUid); } jobsForUid.add(jobStatus); } void stopTrackingJobLocked(JobStatus jobStatus) { final int uid = jobStatus.getSourceUid(); ArraySet<JobStatus> jobsForUid = mTrackedJobs.get(uid); if (jobsForUid != null) { jobsForUid.remove(jobStatus); } } boolean checkAllTrackedJobsLocked() { boolean changed = false; for (int i = 0; i < mTrackedJobs.size(); i++) { changed |= checkTrackedJobsForUidLocked(mTrackedJobs.keyAt(i)); } return changed; } private boolean checkTrackedJobsForUidLocked(int uid) { final ArraySet<JobStatus> jobsForUid = mTrackedJobs.get(uid); boolean changed = false; if (jobsForUid != null) { for (int i = 0; i < jobsForUid.size(); i++) { JobStatus jobStatus = jobsForUid.valueAt(i); changed |= jobStatus.setBackgroundNotRestrictedConstraintSatisfied( canRunJobLocked(uid)); } } return changed; } boolean isWhitelistedLocked(int uid) { return ArrayUtils.contains(mTempWhitelistedAppIds, UserHandle.getAppId(uid)) || ArrayUtils.contains(mPowerWhitelistedAppIds, UserHandle.getAppId(uid)); } boolean canRunJobLocked(int uid) { return !mEnableSwitch || mForegroundUids.get(uid) || isWhitelistedLocked(uid); } private final class AppOpsWatcher extends IAppOpsCallback.Stub { @Override public void opChanged(int op, int uid, String packageName) throws RemoteException { synchronized (mLock) { final int mode = mAppOpsService.checkOperation(op, uid, packageName); if (DEBUG) { Slog.d(LOG_TAG, "Appop changed for " + uid + ", " + packageName + " to " + mode); } final boolean shouldTrack = (mode != AppOpsManager.MODE_ALLOWED); UpdateTrackedJobsFunc updateTrackedJobs = new UpdateTrackedJobsFunc(uid, packageName, shouldTrack); mJobSchedulerService.getJobStore().forEachJob(updateTrackedJobs); if (updateTrackedJobs.mChanged) { mStateChangedListener.onControllerStateChanged(); } } } } private final class UpdateTrackedJobsFunc implements JobStore.JobStatusFunctor { private final String mPackageName; private final int mUid; private final boolean mShouldTrack; private boolean mChanged = false; UpdateTrackedJobsFunc(int uid, String packageName, boolean shouldTrack) { mUid = uid; mPackageName = packageName; mShouldTrack = shouldTrack; } @Override public void process(JobStatus jobStatus) { final String packageName = jobStatus.getSourcePackageName(); final int uid = jobStatus.getSourceUid(); if (mUid != uid || !mPackageName.equals(packageName)) { return; } if (mShouldTrack) { mChanged |= jobStatus.setBackgroundNotRestrictedConstraintSatisfied( canRunJobLocked(uid)); startTrackingJobLocked(jobStatus); } else { mChanged |= jobStatus.setBackgroundNotRestrictedConstraintSatisfied(true); stopTrackingJobLocked(jobStatus); } } } }
services/core/java/com/android/server/job/controllers/JobStatus.java +10 −1 File changed.Preview size limit exceeded, changes collapsed. Show changes