Loading services/core/java/com/android/server/job/JobSchedulerService.java +10 −0 Original line number Diff line number Diff line Loading @@ -861,6 +861,11 @@ public class JobSchedulerService extends com.android.server.SystemService Slog.d(TAG, "Removing jobs for uid: " + uidRemoved); } cancelJobsForPackageAndUid(pkgName, uidRemoved, "app uninstalled"); synchronized (mLock) { for (int c = 0; c < mControllers.size(); ++c) { mControllers.get(c).onAppRemovedLocked(pkgName, pkgUid); } } } } else if (Intent.ACTION_USER_REMOVED.equals(action)) { final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0); Loading @@ -868,6 +873,11 @@ public class JobSchedulerService extends com.android.server.SystemService Slog.d(TAG, "Removing jobs for user: " + userId); } cancelJobsForUser(userId); synchronized (mLock) { for (int c = 0; c < mControllers.size(); ++c) { mControllers.get(c).onUserRemovedLocked(userId); } } } else if (Intent.ACTION_QUERY_PACKAGE_RESTART.equals(action)) { // Has this package scheduled any jobs, such that we will take action // if it were to be force-stopped? Loading services/core/java/com/android/server/job/controllers/ConnectivityController.java +6 −0 Original line number Diff line number Diff line Loading @@ -296,6 +296,12 @@ public final class ConnectivityController extends StateController implements mRequestedWhitelistJobs.remove(uid); } @GuardedBy("mLock") @Override public void onAppRemovedLocked(String pkgName, int uid) { mTrackedJobs.delete(uid); } /** * Test to see if running the given job on the given network is insane. * <p> Loading services/core/java/com/android/server/job/controllers/QuotaController.java +54 −0 Original line number Diff line number Diff line Loading @@ -98,6 +98,19 @@ public final class QuotaController extends StateController { data.put(packageName, obj); } /** Removes all the data for the user, if there was any. */ public void delete(int userId) { mData.delete(userId); } /** Removes the data for the user and package, if there was any. */ public void delete(int userId, @NonNull String packageName) { ArrayMap<String, T> data = mData.get(userId); if (data != null) { data.remove(packageName); } } @Nullable public T get(int userId, @NonNull String packageName) { ArrayMap<String, T> data = mData.get(userId); Loading Loading @@ -371,6 +384,38 @@ public final class QuotaController extends StateController { } } @Override public void onAppRemovedLocked(String packageName, int uid) { if (packageName == null) { Slog.wtf(TAG, "Told app removed but given null package name."); return; } final int userId = UserHandle.getUserId(uid); mTrackedJobs.delete(userId, packageName); Timer timer = mPkgTimers.get(userId, packageName); if (timer != null) { if (timer.isActive()) { Slog.wtf(TAG, "onAppRemovedLocked called before Timer turned off."); timer.dropEverything(); } mPkgTimers.delete(userId, packageName); } mTimingSessions.delete(userId, packageName); QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName); if (alarmListener != null) { mAlarmManager.cancel(alarmListener); mInQuotaAlarmListeners.delete(userId, packageName); } } @Override public void onUserRemovedLocked(int userId) { mTrackedJobs.delete(userId); mPkgTimers.delete(userId); mTimingSessions.delete(userId); mInQuotaAlarmListeners.delete(userId); } /** * Returns an appropriate standby bucket for the job, taking into account any standby * exemptions. Loading Loading @@ -882,6 +927,15 @@ public final class QuotaController extends StateController { } } /** * Stops tracking all jobs and cancels any pending alarms. This should only be called if * the Timer is not going to be used anymore. */ void dropEverything() { mRunningBgJobs.clear(); cancelCutoff(); } private void emitSessionLocked(long nowElapsed) { if (mBgJobCount <= 0) { // Nothing to emit. Loading services/core/java/com/android/server/job/controllers/StateController.java +22 −14 Original line number Diff line number Diff line Loading @@ -83,21 +83,12 @@ public abstract class StateController { public void onConstantsUpdatedLocked() { } protected boolean wouldBeReadyWithConstraintLocked(JobStatus jobStatus, int constraint) { // This is very cheap to check (just a few conditions on data in JobStatus). final boolean jobWouldBeReady = jobStatus.wouldBeReadyWithConstraint(constraint); if (DEBUG) { Slog.v(TAG, "wouldBeReadyWithConstraintLocked: " + jobStatus.toShortString() + " readyWithConstraint=" + jobWouldBeReady); } if (!jobWouldBeReady) { // If the job wouldn't be ready, nothing to do here. return false; /** Called when a package is uninstalled from the device (not for an update). */ public void onAppRemovedLocked(String packageName, int uid) { } // This is potentially more expensive since JSS may have to query component // presence. return mService.areComponentsInPlaceLocked(jobStatus); /** Called when a user is removed from the device. */ public void onUserRemovedLocked(int userId) { } /** Loading @@ -114,6 +105,23 @@ public abstract class StateController { public void reevaluateStateLocked(int uid) { } protected boolean wouldBeReadyWithConstraintLocked(JobStatus jobStatus, int constraint) { // This is very cheap to check (just a few conditions on data in JobStatus). final boolean jobWouldBeReady = jobStatus.wouldBeReadyWithConstraint(constraint); if (DEBUG) { Slog.v(TAG, "wouldBeReadyWithConstraintLocked: " + jobStatus.toShortString() + " readyWithConstraint=" + jobWouldBeReady); } if (!jobWouldBeReady) { // If the job wouldn't be ready, nothing to do here. return false; } // This is potentially more expensive since JSS may have to query component // presence. return mService.areComponentsInPlaceLocked(jobStatus); } public abstract void dumpControllerStateLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate); public abstract void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId, Loading services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java +54 −0 Original line number Diff line number Diff line Loading @@ -269,6 +269,60 @@ public class QuotaControllerTest { assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test")); } @Test public void testOnAppRemovedLocked() { final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); mQuotaController.saveTimingSession(0, "com.android.test.remove", createTimingSession(now - (6 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5)); mQuotaController.saveTimingSession(0, "com.android.test.remove", createTimingSession( now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 6 * MINUTE_IN_MILLIS, 5)); mQuotaController.saveTimingSession(0, "com.android.test.remove", createTimingSession(now - (HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 1)); // Test that another app isn't affected. TimingSession one = createTimingSession( now - 10 * MINUTE_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3); TimingSession two = createTimingSession( now - (70 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1); List<TimingSession> expected = new ArrayList<>(); // Added in correct (chronological) order. expected.add(two); expected.add(one); mQuotaController.saveTimingSession(0, "com.android.test.stay", two); mQuotaController.saveTimingSession(0, "com.android.test.stay", one); mQuotaController.onAppRemovedLocked("com.android.test.remove", 10001); assertNull(mQuotaController.getTimingSessions(0, "com.android.test.remove")); assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test.stay")); } @Test public void testOnUserRemovedLocked() { final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); mQuotaController.saveTimingSession(0, "com.android.test", createTimingSession(now - (6 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5)); mQuotaController.saveTimingSession(0, "com.android.test", createTimingSession( now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 6 * MINUTE_IN_MILLIS, 5)); mQuotaController.saveTimingSession(0, "com.android.test", createTimingSession(now - (HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 1)); // Test that another user isn't affected. TimingSession one = createTimingSession( now - 10 * MINUTE_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3); TimingSession two = createTimingSession( now - (70 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1); List<TimingSession> expected = new ArrayList<>(); // Added in correct (chronological) order. expected.add(two); expected.add(one); mQuotaController.saveTimingSession(10, "com.android.test", two); mQuotaController.saveTimingSession(10, "com.android.test", one); mQuotaController.onUserRemovedLocked(0); assertNull(mQuotaController.getTimingSessions(0, "com.android.test")); assertEquals(expected, mQuotaController.getTimingSessions(10, "com.android.test")); } @Test public void testGetTrailingExecutionTimeLocked_NoTimer() { final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); Loading Loading
services/core/java/com/android/server/job/JobSchedulerService.java +10 −0 Original line number Diff line number Diff line Loading @@ -861,6 +861,11 @@ public class JobSchedulerService extends com.android.server.SystemService Slog.d(TAG, "Removing jobs for uid: " + uidRemoved); } cancelJobsForPackageAndUid(pkgName, uidRemoved, "app uninstalled"); synchronized (mLock) { for (int c = 0; c < mControllers.size(); ++c) { mControllers.get(c).onAppRemovedLocked(pkgName, pkgUid); } } } } else if (Intent.ACTION_USER_REMOVED.equals(action)) { final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0); Loading @@ -868,6 +873,11 @@ public class JobSchedulerService extends com.android.server.SystemService Slog.d(TAG, "Removing jobs for user: " + userId); } cancelJobsForUser(userId); synchronized (mLock) { for (int c = 0; c < mControllers.size(); ++c) { mControllers.get(c).onUserRemovedLocked(userId); } } } else if (Intent.ACTION_QUERY_PACKAGE_RESTART.equals(action)) { // Has this package scheduled any jobs, such that we will take action // if it were to be force-stopped? Loading
services/core/java/com/android/server/job/controllers/ConnectivityController.java +6 −0 Original line number Diff line number Diff line Loading @@ -296,6 +296,12 @@ public final class ConnectivityController extends StateController implements mRequestedWhitelistJobs.remove(uid); } @GuardedBy("mLock") @Override public void onAppRemovedLocked(String pkgName, int uid) { mTrackedJobs.delete(uid); } /** * Test to see if running the given job on the given network is insane. * <p> Loading
services/core/java/com/android/server/job/controllers/QuotaController.java +54 −0 Original line number Diff line number Diff line Loading @@ -98,6 +98,19 @@ public final class QuotaController extends StateController { data.put(packageName, obj); } /** Removes all the data for the user, if there was any. */ public void delete(int userId) { mData.delete(userId); } /** Removes the data for the user and package, if there was any. */ public void delete(int userId, @NonNull String packageName) { ArrayMap<String, T> data = mData.get(userId); if (data != null) { data.remove(packageName); } } @Nullable public T get(int userId, @NonNull String packageName) { ArrayMap<String, T> data = mData.get(userId); Loading Loading @@ -371,6 +384,38 @@ public final class QuotaController extends StateController { } } @Override public void onAppRemovedLocked(String packageName, int uid) { if (packageName == null) { Slog.wtf(TAG, "Told app removed but given null package name."); return; } final int userId = UserHandle.getUserId(uid); mTrackedJobs.delete(userId, packageName); Timer timer = mPkgTimers.get(userId, packageName); if (timer != null) { if (timer.isActive()) { Slog.wtf(TAG, "onAppRemovedLocked called before Timer turned off."); timer.dropEverything(); } mPkgTimers.delete(userId, packageName); } mTimingSessions.delete(userId, packageName); QcAlarmListener alarmListener = mInQuotaAlarmListeners.get(userId, packageName); if (alarmListener != null) { mAlarmManager.cancel(alarmListener); mInQuotaAlarmListeners.delete(userId, packageName); } } @Override public void onUserRemovedLocked(int userId) { mTrackedJobs.delete(userId); mPkgTimers.delete(userId); mTimingSessions.delete(userId); mInQuotaAlarmListeners.delete(userId); } /** * Returns an appropriate standby bucket for the job, taking into account any standby * exemptions. Loading Loading @@ -882,6 +927,15 @@ public final class QuotaController extends StateController { } } /** * Stops tracking all jobs and cancels any pending alarms. This should only be called if * the Timer is not going to be used anymore. */ void dropEverything() { mRunningBgJobs.clear(); cancelCutoff(); } private void emitSessionLocked(long nowElapsed) { if (mBgJobCount <= 0) { // Nothing to emit. Loading
services/core/java/com/android/server/job/controllers/StateController.java +22 −14 Original line number Diff line number Diff line Loading @@ -83,21 +83,12 @@ public abstract class StateController { public void onConstantsUpdatedLocked() { } protected boolean wouldBeReadyWithConstraintLocked(JobStatus jobStatus, int constraint) { // This is very cheap to check (just a few conditions on data in JobStatus). final boolean jobWouldBeReady = jobStatus.wouldBeReadyWithConstraint(constraint); if (DEBUG) { Slog.v(TAG, "wouldBeReadyWithConstraintLocked: " + jobStatus.toShortString() + " readyWithConstraint=" + jobWouldBeReady); } if (!jobWouldBeReady) { // If the job wouldn't be ready, nothing to do here. return false; /** Called when a package is uninstalled from the device (not for an update). */ public void onAppRemovedLocked(String packageName, int uid) { } // This is potentially more expensive since JSS may have to query component // presence. return mService.areComponentsInPlaceLocked(jobStatus); /** Called when a user is removed from the device. */ public void onUserRemovedLocked(int userId) { } /** Loading @@ -114,6 +105,23 @@ public abstract class StateController { public void reevaluateStateLocked(int uid) { } protected boolean wouldBeReadyWithConstraintLocked(JobStatus jobStatus, int constraint) { // This is very cheap to check (just a few conditions on data in JobStatus). final boolean jobWouldBeReady = jobStatus.wouldBeReadyWithConstraint(constraint); if (DEBUG) { Slog.v(TAG, "wouldBeReadyWithConstraintLocked: " + jobStatus.toShortString() + " readyWithConstraint=" + jobWouldBeReady); } if (!jobWouldBeReady) { // If the job wouldn't be ready, nothing to do here. return false; } // This is potentially more expensive since JSS may have to query component // presence. return mService.areComponentsInPlaceLocked(jobStatus); } public abstract void dumpControllerStateLocked(IndentingPrintWriter pw, Predicate<JobStatus> predicate); public abstract void dumpControllerStateLocked(ProtoOutputStream proto, long fieldId, Loading
services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java +54 −0 Original line number Diff line number Diff line Loading @@ -269,6 +269,60 @@ public class QuotaControllerTest { assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test")); } @Test public void testOnAppRemovedLocked() { final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); mQuotaController.saveTimingSession(0, "com.android.test.remove", createTimingSession(now - (6 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5)); mQuotaController.saveTimingSession(0, "com.android.test.remove", createTimingSession( now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 6 * MINUTE_IN_MILLIS, 5)); mQuotaController.saveTimingSession(0, "com.android.test.remove", createTimingSession(now - (HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 1)); // Test that another app isn't affected. TimingSession one = createTimingSession( now - 10 * MINUTE_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3); TimingSession two = createTimingSession( now - (70 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1); List<TimingSession> expected = new ArrayList<>(); // Added in correct (chronological) order. expected.add(two); expected.add(one); mQuotaController.saveTimingSession(0, "com.android.test.stay", two); mQuotaController.saveTimingSession(0, "com.android.test.stay", one); mQuotaController.onAppRemovedLocked("com.android.test.remove", 10001); assertNull(mQuotaController.getTimingSessions(0, "com.android.test.remove")); assertEquals(expected, mQuotaController.getTimingSessions(0, "com.android.test.stay")); } @Test public void testOnUserRemovedLocked() { final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); mQuotaController.saveTimingSession(0, "com.android.test", createTimingSession(now - (6 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5)); mQuotaController.saveTimingSession(0, "com.android.test", createTimingSession( now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), 6 * MINUTE_IN_MILLIS, 5)); mQuotaController.saveTimingSession(0, "com.android.test", createTimingSession(now - (HOUR_IN_MILLIS), MINUTE_IN_MILLIS, 1)); // Test that another user isn't affected. TimingSession one = createTimingSession( now - 10 * MINUTE_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3); TimingSession two = createTimingSession( now - (70 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1); List<TimingSession> expected = new ArrayList<>(); // Added in correct (chronological) order. expected.add(two); expected.add(one); mQuotaController.saveTimingSession(10, "com.android.test", two); mQuotaController.saveTimingSession(10, "com.android.test", one); mQuotaController.onUserRemovedLocked(0); assertNull(mQuotaController.getTimingSessions(0, "com.android.test")); assertEquals(expected, mQuotaController.getTimingSessions(10, "com.android.test")); } @Test public void testGetTrailingExecutionTimeLocked_NoTimer() { final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); Loading