diff options
| author | 2019-01-14 20:07:14 +0000 | |
|---|---|---|
| committer | 2019-01-14 20:07:14 +0000 | |
| commit | 1cf011cd7658f7f10ae5c802799a79ef91e3ec2b (patch) | |
| tree | 9d94bce56c09b154fafabd878b017a4dfd47d83f | |
| parent | 9e60699ea0323b8a12095e4a032dccde1de5942a (diff) | |
| parent | 851bdd45711400f83e8c1d8be421735f183d7c1d (diff) | |
Merge "[Multi-User] Make JobScheduler logic multi-user aware"
18 files changed, 407 insertions, 82 deletions
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index 12db4f37da58..ff378b373775 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerService.java +++ b/services/backup/java/com/android/server/backup/BackupManagerService.java @@ -149,6 +149,9 @@ public class BackupManagerService { if (userBackupManagerService != null) { userBackupManagerService.tearDownService(); + + KeyValueBackupJob.cancel(userId, mContext); + FullBackupJob.cancel(userId, mContext); } } @@ -577,9 +580,9 @@ public class BackupManagerService { * @return Whether ongoing work will continue. The return value here will be passed along as the * return value to the callback {@link JobService#onStartJob(JobParameters)}. */ - public boolean beginFullBackup(FullBackupJob scheduledJob) { + public boolean beginFullBackup(@UserIdInt int userId, FullBackupJob scheduledJob) { UserBackupManagerService userBackupManagerService = - getServiceForUserIfCallerHasPermission(UserHandle.USER_SYSTEM, "beginFullBackup()"); + getServiceForUserIfCallerHasPermission(userId, "beginFullBackup()"); return userBackupManagerService != null && userBackupManagerService.beginFullBackup(scheduledJob); @@ -589,9 +592,9 @@ public class BackupManagerService { * Used by the {@link JobScheduler} to end the current full backup task when conditions are no * longer met for running the full backup job. */ - public void endFullBackup() { + public void endFullBackup(@UserIdInt int userId) { UserBackupManagerService userBackupManagerService = - getServiceForUserIfCallerHasPermission(UserHandle.USER_SYSTEM, "endFullBackup()"); + getServiceForUserIfCallerHasPermission(userId, "endFullBackup()"); if (userBackupManagerService != null) { userBackupManagerService.endFullBackup(); diff --git a/services/backup/java/com/android/server/backup/FullBackupJob.java b/services/backup/java/com/android/server/backup/FullBackupJob.java index 5708b1c36822..33d21cb0bf91 100644 --- a/services/backup/java/com/android/server/backup/FullBackupJob.java +++ b/services/backup/java/com/android/server/backup/FullBackupJob.java @@ -22,18 +22,30 @@ import android.app.job.JobScheduler; import android.app.job.JobService; import android.content.ComponentName; import android.content.Context; +import android.os.Bundle; +import android.util.SparseArray; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; public class FullBackupJob extends JobService { + private static final String USER_ID_EXTRA_KEY = "userId"; + + @VisibleForTesting + static final int MIN_JOB_ID = 52418896; + @VisibleForTesting + static final int MAX_JOB_ID = 52419896; + private static ComponentName sIdleService = new ComponentName("android", FullBackupJob.class.getName()); - private static final int JOB_ID = 0x5038; - - private JobParameters mParams; + @GuardedBy("mParamsForUser") + private final SparseArray<JobParameters> mParamsForUser = new SparseArray<>(); - public static void schedule(Context ctx, long minDelay, BackupManagerConstants constants) { + public static void schedule(int userId, Context ctx, long minDelay, + BackupManagerConstants constants) { JobScheduler js = (JobScheduler) ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE); - JobInfo.Builder builder = new JobInfo.Builder(JOB_ID, sIdleService); + JobInfo.Builder builder = new JobInfo.Builder(getJobIdForUserId(userId), sIdleService); synchronized (constants) { builder.setRequiresDeviceIdle(true) .setRequiredNetworkType(constants.getFullBackupRequiredNetworkType()) @@ -42,14 +54,28 @@ public class FullBackupJob extends JobService { if (minDelay > 0) { builder.setMinimumLatency(minDelay); } + + Bundle extraInfo = new Bundle(); + extraInfo.putInt(USER_ID_EXTRA_KEY, userId); + builder.setTransientExtras(extraInfo); + js.schedule(builder.build()); } + public static void cancel(int userId, Context ctx) { + JobScheduler js = (JobScheduler) ctx.getSystemService( + Context.JOB_SCHEDULER_SERVICE); + js.cancel(getJobIdForUserId(userId)); + } + // callback from the Backup Manager Service: it's finished its work for this pass - public void finishBackupPass() { - if (mParams != null) { - jobFinished(mParams, false); - mParams = null; + public void finishBackupPass(int userId) { + synchronized (mParamsForUser) { + JobParameters jobParameters = mParamsForUser.get(userId); + if (jobParameters != null) { + jobFinished(jobParameters, false); + mParamsForUser.remove(userId); + } } } @@ -57,19 +83,33 @@ public class FullBackupJob extends JobService { @Override public boolean onStartJob(JobParameters params) { - mParams = params; + int userId = params.getTransientExtras().getInt(USER_ID_EXTRA_KEY); + + synchronized (mParamsForUser) { + mParamsForUser.put(userId, params); + } + Trampoline service = BackupManagerService.getInstance(); - return service.beginFullBackup(this); + return service.beginFullBackup(userId, this); } @Override public boolean onStopJob(JobParameters params) { - if (mParams != null) { - mParams = null; - Trampoline service = BackupManagerService.getInstance(); - service.endFullBackup(); + int userId = params.getTransientExtras().getInt(USER_ID_EXTRA_KEY); + + synchronized (mParamsForUser) { + if (mParamsForUser.removeReturnOld(userId) == null) { + return false; + } } + + Trampoline service = BackupManagerService.getInstance(); + service.endFullBackup(userId); + return false; } + private static int getJobIdForUserId(int userId) { + return JobIdManager.getJobIdForUserId(MIN_JOB_ID, MAX_JOB_ID, userId); + } } diff --git a/services/backup/java/com/android/server/backup/JobIdManager.java b/services/backup/java/com/android/server/backup/JobIdManager.java new file mode 100644 index 000000000000..2e834dbfe245 --- /dev/null +++ b/services/backup/java/com/android/server/backup/JobIdManager.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2019 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.backup; + +/** + * Allocates job IDs for {@link FullBackupJob} and {@link KeyValueBackupJob} + */ +public class JobIdManager { + public static int getJobIdForUserId(int minJobId, int maxJobId, int userId) { + if (minJobId + userId > maxJobId) { + throw new RuntimeException("No job IDs available in the given range"); + } + + return minJobId + userId; + } +} diff --git a/services/backup/java/com/android/server/backup/KeyValueBackupJob.java b/services/backup/java/com/android/server/backup/KeyValueBackupJob.java index f2e74352b004..d43859ebeeda 100644 --- a/services/backup/java/com/android/server/backup/KeyValueBackupJob.java +++ b/services/backup/java/com/android/server/backup/KeyValueBackupJob.java @@ -25,8 +25,14 @@ import android.app.job.JobScheduler; import android.app.job.JobService; import android.content.ComponentName; import android.content.Context; +import android.os.Bundle; import android.os.RemoteException; import android.util.Slog; +import android.util.SparseBooleanArray; +import android.util.SparseLongArray; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import java.util.Random; @@ -38,7 +44,8 @@ public class KeyValueBackupJob extends JobService { private static final String TAG = "KeyValueBackupJob"; private static ComponentName sKeyValueJobService = new ComponentName("android", KeyValueBackupJob.class.getName()); - private static final int JOB_ID = 0x5039; + + private static final String USER_ID_EXTRA_KEY = "userId"; // Once someone asks for a backup, this is how long we hold off until we find // an on-charging opportunity. If we hit this max latency we will run the operation @@ -46,16 +53,22 @@ public class KeyValueBackupJob extends JobService { // BackupManager.backupNow(). private static final long MAX_DEFERRAL = AlarmManager.INTERVAL_DAY; - private static boolean sScheduled = false; - private static long sNextScheduled = 0; + @GuardedBy("KeyValueBackupJob.class") + private static final SparseBooleanArray sScheduledForUserId = new SparseBooleanArray(); + @GuardedBy("KeyValueBackupJob.class") + private static final SparseLongArray sNextScheduledForUserId = new SparseLongArray(); + + private static final int MIN_JOB_ID = 52417896; + private static final int MAX_JOB_ID = 52418896; - public static void schedule(Context ctx, BackupManagerConstants constants) { - schedule(ctx, 0, constants); + public static void schedule(int userId, Context ctx, BackupManagerConstants constants) { + schedule(userId, ctx, 0, constants); } - public static void schedule(Context ctx, long delay, BackupManagerConstants constants) { + public static void schedule(int userId, Context ctx, long delay, + BackupManagerConstants constants) { synchronized (KeyValueBackupJob.class) { - if (sScheduled) { + if (sScheduledForUserId.get(userId)) { return; } @@ -76,51 +89,61 @@ public class KeyValueBackupJob extends JobService { if (DEBUG_SCHEDULING) { Slog.v(TAG, "Scheduling k/v pass in " + (delay / 1000 / 60) + " minutes"); } - JobInfo.Builder builder = new JobInfo.Builder(JOB_ID, sKeyValueJobService) + + JobInfo.Builder builder = new JobInfo.Builder(getJobIdForUserId(userId), + sKeyValueJobService) .setMinimumLatency(delay) .setRequiredNetworkType(networkType) .setRequiresCharging(needsCharging) .setOverrideDeadline(MAX_DEFERRAL); + + Bundle extraInfo = new Bundle(); + extraInfo.putInt(USER_ID_EXTRA_KEY, userId); + builder.setTransientExtras(extraInfo); + JobScheduler js = (JobScheduler) ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE); js.schedule(builder.build()); - sNextScheduled = System.currentTimeMillis() + delay; - sScheduled = true; + sScheduledForUserId.put(userId, true); + sNextScheduledForUserId.put(userId, System.currentTimeMillis() + delay); } } - public static void cancel(Context ctx) { + public static void cancel(int userId, Context ctx) { synchronized (KeyValueBackupJob.class) { - JobScheduler js = (JobScheduler) ctx.getSystemService(Context.JOB_SCHEDULER_SERVICE); - js.cancel(JOB_ID); - sNextScheduled = 0; - sScheduled = false; + JobScheduler js = (JobScheduler) ctx.getSystemService( + Context.JOB_SCHEDULER_SERVICE); + js.cancel(getJobIdForUserId(userId)); + + clearScheduledForUserId(userId); } } - public static long nextScheduled() { + public static long nextScheduled(int userId) { synchronized (KeyValueBackupJob.class) { - return sNextScheduled; + return sNextScheduledForUserId.get(userId); } } - public static boolean isScheduled() { + @VisibleForTesting + public static boolean isScheduled(int userId) { synchronized (KeyValueBackupJob.class) { - return sScheduled; + return sScheduledForUserId.get(userId); } } @Override public boolean onStartJob(JobParameters params) { + int userId = params.getTransientExtras().getInt(USER_ID_EXTRA_KEY); + synchronized (KeyValueBackupJob.class) { - sNextScheduled = 0; - sScheduled = false; + clearScheduledForUserId(userId); } // Time to run a key/value backup! Trampoline service = BackupManagerService.getInstance(); try { - service.backupNow(); + service.backupNowForUser(userId); } catch (RemoteException e) {} // This was just a trigger; ongoing wakelock management is done by the @@ -134,4 +157,13 @@ public class KeyValueBackupJob extends JobService { return false; } + @GuardedBy("KeyValueBackupJob.class") + private static void clearScheduledForUserId(int userId) { + sScheduledForUserId.delete(userId); + sNextScheduledForUserId.delete(userId); + } + + private static int getJobIdForUserId(int userId) { + return JobIdManager.getJobIdForUserId(MIN_JOB_ID, MAX_JOB_ID, userId); + } } diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java index 79d4a2cb33de..4ca2545802c7 100644 --- a/services/backup/java/com/android/server/backup/Trampoline.java +++ b/services/backup/java/com/android/server/backup/Trampoline.java @@ -698,15 +698,15 @@ public class Trampoline extends IBackupManager.Stub { // Full backup/restore entry points - non-Binder; called directly // by the full-backup scheduled job - /* package */ boolean beginFullBackup(FullBackupJob scheduledJob) { + /* package */ boolean beginFullBackup(@UserIdInt int userId, FullBackupJob scheduledJob) { BackupManagerService svc = mService; - return (svc != null) ? svc.beginFullBackup(scheduledJob) : false; + return (svc != null) ? svc.beginFullBackup(userId, scheduledJob) : false; } - /* package */ void endFullBackup() { + /* package */ void endFullBackup(@UserIdInt int userId) { BackupManagerService svc = mService; if (svc != null) { - svc.endFullBackup(); + svc.endFullBackup(userId); } } } diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index e07578570b79..ed6a46cc58aa 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -1674,8 +1674,8 @@ public class UserBackupManagerService { } // We don't want the backup jobs to kick in any time soon. // Reschedules them to run in the distant future. - KeyValueBackupJob.schedule(mContext, BUSY_BACKOFF_MIN_MILLIS, mConstants); - FullBackupJob.schedule(mContext, 2 * BUSY_BACKOFF_MIN_MILLIS, mConstants); + KeyValueBackupJob.schedule(mUserId, mContext, BUSY_BACKOFF_MIN_MILLIS, mConstants); + FullBackupJob.schedule(mUserId, mContext, 2 * BUSY_BACKOFF_MIN_MILLIS, mConstants); } finally { Binder.restoreCallingIdentity(oldToken); } @@ -1910,7 +1910,7 @@ public class UserBackupManagerService { Runnable r = new Runnable() { @Override public void run() { - FullBackupJob.schedule(mContext, latency, mConstants); + FullBackupJob.schedule(mUserId, mContext, latency, mConstants); } }; mBackupHandler.postDelayed(r, 2500); @@ -2033,7 +2033,7 @@ public class UserBackupManagerService { mPowerManager.getPowerSaveState(ServiceType.FULL_BACKUP); if (result.batterySaverEnabled) { if (DEBUG) Slog.i(TAG, "Deferring scheduled full backups in battery saver mode"); - FullBackupJob.schedule(mContext, keyValueBackupInterval, mConstants); + FullBackupJob.schedule(mUserId, mContext, keyValueBackupInterval, mConstants); return false; } @@ -2147,7 +2147,7 @@ public class UserBackupManagerService { mBackupHandler.post(new Runnable() { @Override public void run() { - FullBackupJob.schedule(mContext, deferTime, mConstants); + FullBackupJob.schedule(mUserId, mContext, deferTime, mConstants); } }); return false; @@ -2251,7 +2251,7 @@ public class UserBackupManagerService { } // ...and schedule a backup pass if necessary - KeyValueBackupJob.schedule(mContext, mConstants); + KeyValueBackupJob.schedule(mUserId, mContext, mConstants); } // Note: packageName is currently unused, but may be in the future @@ -2401,7 +2401,8 @@ public class UserBackupManagerService { mPowerManager.getPowerSaveState(ServiceType.KEYVALUE_BACKUP); if (result.batterySaverEnabled) { if (DEBUG) Slog.v(TAG, "Not running backup while in battery save mode"); - KeyValueBackupJob.schedule(mContext, mConstants); // try again in several hours + // Try again in several hours. + KeyValueBackupJob.schedule(mUserId, mContext, mConstants); } else { if (DEBUG) Slog.v(TAG, "Scheduling immediate backup pass"); synchronized (mQueueLock) { @@ -2414,7 +2415,7 @@ public class UserBackupManagerService { } // ...and cancel any pending scheduled job, because we've just superseded it - KeyValueBackupJob.cancel(mContext); + KeyValueBackupJob.cancel(mUserId, mContext); } } } finally { @@ -2737,13 +2738,13 @@ public class UserBackupManagerService { synchronized (mQueueLock) { if (enable && !wasEnabled && mSetupComplete) { // if we've just been enabled, start scheduling backup passes - KeyValueBackupJob.schedule(mContext, mConstants); + KeyValueBackupJob.schedule(mUserId, mContext, mConstants); scheduleNextFullBackupJob(0); } else if (!enable) { // No longer enabled, so stop running backups if (MORE_DEBUG) Slog.i(TAG, "Opting out of backup"); - KeyValueBackupJob.cancel(mContext); + KeyValueBackupJob.cancel(mUserId, mContext); // This also constitutes an opt-out, so we wipe any data for // this device from the backend. We start that process with @@ -3451,7 +3452,7 @@ public class UserBackupManagerService { pw.println(isBackupOperationInProgress() ? "Backup in progress" : "No backups running"); pw.println("Last backup pass started: " + mLastBackupPass + " (now = " + System.currentTimeMillis() + ')'); - pw.println(" next scheduled: " + KeyValueBackupJob.nextScheduled()); + pw.println(" next scheduled: " + KeyValueBackupJob.nextScheduled(mUserId)); pw.println("Transport whitelist:"); for (ComponentName transport : mTransportManager.getTransportWhitelist()) { diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java index 2ee96d190f90..0fb4f93e542b 100644 --- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java +++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java @@ -633,7 +633,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba unregisterTask(); if (mJob != null) { - mJob.finishBackupPass(); + mJob.finishBackupPass(backupManagerService.getUserId()); } synchronized (backupManagerService.getQueueLock()) { diff --git a/services/backup/java/com/android/server/backup/internal/SetupObserver.java b/services/backup/java/com/android/server/backup/internal/SetupObserver.java index 62ae3eb8e192..c5e912e6d18b 100644 --- a/services/backup/java/com/android/server/backup/internal/SetupObserver.java +++ b/services/backup/java/com/android/server/backup/internal/SetupObserver.java @@ -77,7 +77,8 @@ public class SetupObserver extends ContentObserver { if (MORE_DEBUG) { Slog.d(TAG, "Setup complete so starting backups"); } - KeyValueBackupJob.schedule(mContext, mUserBackupManagerService.getConstants()); + KeyValueBackupJob.schedule(mUserBackupManagerService.getUserId(), mContext, + mUserBackupManagerService.getConstants()); mUserBackupManagerService.scheduleNextFullBackupJob(0); } } diff --git a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java index f39d795735f3..ef7ff9270a66 100644 --- a/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java +++ b/services/backup/java/com/android/server/backup/keyvalue/KeyValueBackupTask.java @@ -1003,7 +1003,7 @@ public class KeyValueBackupTask implements BackupRestoreTask, Runnable { // Use the scheduler's default. delay = 0; } - KeyValueBackupJob.schedule( + KeyValueBackupJob.schedule(mBackupManagerService.getUserId(), mBackupManagerService.getContext(), delay, mBackupManagerService.getConstants()); for (String packageName : mOriginalQueue) { diff --git a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceTest.java b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceTest.java index 769a9d4a2bd4..0d3b6b2afd2c 100644 --- a/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceTest.java +++ b/services/robotests/backup/src/com/android/server/backup/BackupManagerServiceTest.java @@ -1075,7 +1075,7 @@ public class BackupManagerServiceTest { createServiceAndRegisterUser(UserHandle.USER_SYSTEM, mUserOneService); FullBackupJob job = new FullBackupJob(); - backupManagerService.beginFullBackup(job); + backupManagerService.beginFullBackup(UserHandle.USER_SYSTEM, job); verify(mUserOneService).beginFullBackup(job); } @@ -1086,7 +1086,7 @@ public class BackupManagerServiceTest { BackupManagerService backupManagerService = createService(); FullBackupJob job = new FullBackupJob(); - backupManagerService.beginFullBackup(job); + backupManagerService.beginFullBackup(UserHandle.USER_SYSTEM, job); verify(mUserOneService, never()).beginFullBackup(job); } @@ -1097,7 +1097,7 @@ public class BackupManagerServiceTest { BackupManagerService backupManagerService = createServiceAndRegisterUser(UserHandle.USER_SYSTEM, mUserOneService); - backupManagerService.endFullBackup(); + backupManagerService.endFullBackup(UserHandle.USER_SYSTEM); verify(mUserOneService).endFullBackup(); } @@ -1107,7 +1107,7 @@ public class BackupManagerServiceTest { public void testEndFullBackup_onUnknownUser_doesNotPropagateCall() throws Exception { BackupManagerService backupManagerService = createService(); - backupManagerService.endFullBackup(); + backupManagerService.endFullBackup(UserHandle.USER_SYSTEM); verify(mUserOneService, never()).endFullBackup(); } diff --git a/services/robotests/backup/src/com/android/server/backup/FullBackupJobTest.java b/services/robotests/backup/src/com/android/server/backup/FullBackupJobTest.java new file mode 100644 index 000000000000..9a78d0b3f456 --- /dev/null +++ b/services/robotests/backup/src/com/android/server/backup/FullBackupJobTest.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2019 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.backup; + +import static com.google.common.truth.Truth.assertThat; + +import android.annotation.UserIdInt; +import android.app.job.JobScheduler; +import android.content.Context; +import android.os.Handler; +import android.os.UserHandle; +import android.platform.test.annotations.Presubmit; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.Shadows; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowJobScheduler; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowJobScheduler.class}) +@Presubmit +public class FullBackupJobTest { + private Context mContext; + private BackupManagerConstants mConstants; + private ShadowJobScheduler mShadowJobScheduler; + + @UserIdInt private int mUserOneId; + @UserIdInt private int mUserTwoId; + + @Before + public void setUp() throws Exception { + mContext = RuntimeEnvironment.application; + mConstants = new BackupManagerConstants(Handler.getMain(), mContext.getContentResolver()); + mConstants.start(); + + mShadowJobScheduler = Shadows.shadowOf(mContext.getSystemService(JobScheduler.class)); + + mUserOneId = UserHandle.USER_SYSTEM; + mUserTwoId = mUserOneId + 1; + } + + @After + public void tearDown() throws Exception { + mConstants.stop(); + FullBackupJob.cancel(mUserOneId, mContext); + FullBackupJob.cancel(mUserTwoId, mContext); + } + + @Test + public void testSchedule_afterScheduling_jobExists() { + FullBackupJob.schedule(mUserOneId, mContext, 0, mConstants); + FullBackupJob.schedule(mUserTwoId, mContext, 0, mConstants); + + assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserOneId))).isNotNull(); + assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserTwoId))).isNotNull(); + } + + @Test + public void testCancel_afterCancelling_jobDoesntExist() { + FullBackupJob.schedule(mUserOneId, mContext, 0, mConstants); + FullBackupJob.schedule(mUserTwoId, mContext, 0, mConstants); + FullBackupJob.cancel(mUserOneId, mContext); + FullBackupJob.cancel(mUserTwoId, mContext); + + assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserOneId))).isNull(); + assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserTwoId))).isNull(); + } +// + @Test + public void testSchedule_onlySchedulesForRequestedUser() { + FullBackupJob.schedule(mUserOneId, mContext, 0, mConstants); + + assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserOneId))).isNotNull(); + assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserTwoId))).isNull(); + } +// + @Test + public void testCancel_onlyCancelsForRequestedUser() { + FullBackupJob.schedule(mUserOneId, mContext, 0, mConstants); + FullBackupJob.schedule(mUserTwoId, mContext, 0, mConstants); + FullBackupJob.cancel(mUserOneId, mContext); + + assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserOneId))).isNull(); + assertThat(mShadowJobScheduler.getPendingJob(getJobIdForUserId(mUserTwoId))).isNotNull(); + } + + private static int getJobIdForUserId(int userId) { + return JobIdManager.getJobIdForUserId(FullBackupJob.MIN_JOB_ID, FullBackupJob.MAX_JOB_ID, + userId); + } +} diff --git a/services/robotests/backup/src/com/android/server/backup/JobIdManagerTest.java b/services/robotests/backup/src/com/android/server/backup/JobIdManagerTest.java new file mode 100644 index 000000000000..f8bb1ee60b80 --- /dev/null +++ b/services/robotests/backup/src/com/android/server/backup/JobIdManagerTest.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2019 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.backup; + +import static com.google.common.truth.Truth.assertThat; + +import android.annotation.UserIdInt; +import android.os.UserHandle; +import android.platform.test.annotations.Presubmit; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import static org.testng.Assert.expectThrows; + +@RunWith(RobolectricTestRunner.class) +@Presubmit +public class JobIdManagerTest { + private static final int MIN_JOB_ID = 10; + private static final int MAX_JOB_ID = 20; + + @UserIdInt private int mUserOneId; + @UserIdInt private int mUserTwoId; + + @Before + public void setUp() { + mUserOneId = UserHandle.USER_SYSTEM; + mUserTwoId = mUserOneId + 1; + } + + @Test + public void testGetJobIdForUserId_returnsDifferentJobIdsForDifferentUsers() { + int jobIdOne = JobIdManager.getJobIdForUserId(MIN_JOB_ID, MAX_JOB_ID, mUserOneId); + int jobIdTwo = JobIdManager.getJobIdForUserId(MIN_JOB_ID, MAX_JOB_ID, mUserTwoId); + + assertThat(jobIdOne).isNotEqualTo(jobIdTwo); + } + + @Test + public void testGetJobIdForUserId_returnsSameJobIdForSameUser() { + int jobIdOne = JobIdManager.getJobIdForUserId(MIN_JOB_ID, MAX_JOB_ID, mUserOneId); + int jobIdTwo = JobIdManager.getJobIdForUserId(MIN_JOB_ID, MAX_JOB_ID, mUserOneId); + + assertThat(jobIdOne).isEqualTo(jobIdTwo); + } + + @Test + public void testGetJobIdForUserId_throwsExceptionIfRangeIsExceeded() { + expectThrows( + RuntimeException.class, + () -> JobIdManager.getJobIdForUserId(MIN_JOB_ID, MAX_JOB_ID, + MAX_JOB_ID + 1)); + } +} diff --git a/services/robotests/backup/src/com/android/server/backup/KeyValueBackupJobTest.java b/services/robotests/backup/src/com/android/server/backup/KeyValueBackupJobTest.java index 8e17209e1f50..8d9e44fbf1ad 100644 --- a/services/robotests/backup/src/com/android/server/backup/KeyValueBackupJobTest.java +++ b/services/robotests/backup/src/com/android/server/backup/KeyValueBackupJobTest.java @@ -18,8 +18,10 @@ package com.android.server.backup; import static com.google.common.truth.Truth.assertThat; +import android.annotation.UserIdInt; import android.content.Context; import android.os.Handler; +import android.os.UserHandle; import android.platform.test.annotations.Presubmit; import org.junit.After; @@ -35,32 +37,67 @@ public class KeyValueBackupJobTest { private Context mContext; private BackupManagerConstants mConstants; + @UserIdInt private int mUserOneId; + @UserIdInt private int mUserTwoId; + @Before public void setUp() throws Exception { mContext = RuntimeEnvironment.application; mConstants = new BackupManagerConstants(Handler.getMain(), mContext.getContentResolver()); mConstants.start(); + + mUserOneId = UserHandle.USER_SYSTEM; + mUserTwoId = mUserOneId + 1; } @After public void tearDown() throws Exception { mConstants.stop(); - KeyValueBackupJob.cancel(mContext); + KeyValueBackupJob.cancel(mUserOneId, mContext); + KeyValueBackupJob.cancel(mUserTwoId, mContext); } @Test public void testIsScheduled_beforeScheduling_returnsFalse() { - boolean isScheduled = KeyValueBackupJob.isScheduled(); - - assertThat(isScheduled).isFalse(); + assertThat(KeyValueBackupJob.isScheduled(mUserOneId)).isFalse(); + assertThat(KeyValueBackupJob.isScheduled(mUserTwoId)).isFalse(); } @Test public void testIsScheduled_afterScheduling_returnsTrue() { - KeyValueBackupJob.schedule(mContext, mConstants); + KeyValueBackupJob.schedule(mUserOneId, mContext, mConstants); + KeyValueBackupJob.schedule(mUserTwoId, mContext, mConstants); + + assertThat(KeyValueBackupJob.isScheduled(mUserOneId)).isTrue(); + assertThat(KeyValueBackupJob.isScheduled(mUserTwoId)).isTrue(); + } + + @Test + public void testIsScheduled_afterCancelling_returnsFalse() { + KeyValueBackupJob.schedule(mUserOneId, mContext, mConstants); + KeyValueBackupJob.schedule(mUserTwoId, mContext, mConstants); + KeyValueBackupJob.cancel(mUserOneId, mContext); + KeyValueBackupJob.cancel(mUserTwoId, mContext); - boolean isScheduled = KeyValueBackupJob.isScheduled(); + assertThat(KeyValueBackupJob.isScheduled(mUserOneId)).isFalse(); + assertThat(KeyValueBackupJob.isScheduled(mUserTwoId)).isFalse(); + } + + @Test + public void testIsScheduled_afterScheduling_returnsTrueOnlyForScheduledUser() { + KeyValueBackupJob.schedule(mUserOneId, mContext, mConstants); + + assertThat(KeyValueBackupJob.isScheduled(mUserOneId)).isTrue(); + assertThat(KeyValueBackupJob.isScheduled(mUserTwoId)).isFalse(); + } + + @Test + public void testIsScheduled_afterCancelling_returnsFalseOnlyForCancelledUser() { + KeyValueBackupJob.schedule(mUserOneId, mContext, mConstants); + KeyValueBackupJob.schedule(mUserTwoId, mContext, mConstants); + KeyValueBackupJob.cancel(mUserOneId, mContext); - assertThat(isScheduled).isTrue(); + assertThat(KeyValueBackupJob.isScheduled(mUserOneId)).isFalse(); + assertThat(KeyValueBackupJob.isScheduled(mUserTwoId)).isTrue(); } } diff --git a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java index 8d5c301d93e7..496817613216 100644 --- a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java +++ b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java @@ -1137,8 +1137,9 @@ public class UserBackupManagerServiceTest { * Implementation of {@link ShadowKeyValueBackupJob#schedule(Context, long, * BackupManagerConstants)} that throws an {@link IllegalArgumentException}. */ - public static void schedule(Context ctx, long delay, BackupManagerConstants constants) { - ShadowKeyValueBackupJob.schedule(ctx, delay, constants); + public static void schedule(int userId, Context ctx, long delay, + BackupManagerConstants constants) { + ShadowKeyValueBackupJob.schedule(userId, ctx, delay, constants); throw new IllegalArgumentException(); } } diff --git a/services/robotests/backup/src/com/android/server/backup/internal/SetupObserverTest.java b/services/robotests/backup/src/com/android/server/backup/internal/SetupObserverTest.java index b754356ca0cb..b08ae6d5756e 100644 --- a/services/robotests/backup/src/com/android/server/backup/internal/SetupObserverTest.java +++ b/services/robotests/backup/src/com/android/server/backup/internal/SetupObserverTest.java @@ -125,7 +125,7 @@ public class SetupObserverTest { setupObserver.onChange(true); - assertThat(KeyValueBackupJob.isScheduled()).isTrue(); + assertThat(KeyValueBackupJob.isScheduled(mUserBackupManagerService.getUserId())).isTrue(); // Verifies that the full backup job is scheduled. The job is scheduled via a posted message // on the backup handler so we verify that a message exists. assertThat(mUserBackupManagerService.getBackupHandler().hasMessagesOrCallbacks()).isTrue(); diff --git a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java index 7dac79599ac6..b923bb09a423 100644 --- a/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java +++ b/services/robotests/backup/src/com/android/server/backup/keyvalue/KeyValueBackupTaskTest.java @@ -2727,7 +2727,7 @@ public class KeyValueBackupTaskTest { throws RemoteException, IOException { verify(transportMock.transport).requestBackupTime(); assertBackupPendingFor(packages); - assertThat(KeyValueBackupJob.isScheduled()).isTrue(); + assertThat(KeyValueBackupJob.isScheduled(mBackupManagerService.getUserId())).isTrue(); } private void assertBackupPendingFor(PackageData... packages) throws IOException { diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowKeyValueBackupJob.java b/services/robotests/src/com/android/server/testing/shadows/ShadowKeyValueBackupJob.java index 23c44b0e8c83..f90ea6aff41b 100644 --- a/services/robotests/src/com/android/server/testing/shadows/ShadowKeyValueBackupJob.java +++ b/services/robotests/src/com/android/server/testing/shadows/ShadowKeyValueBackupJob.java @@ -34,7 +34,8 @@ public class ShadowKeyValueBackupJob { } @Implementation - protected static void schedule(Context ctx, long delay, BackupManagerConstants constants) { + protected static void schedule(int userId, Context ctx, long delay, + BackupManagerConstants constants) { callingUid = Binder.getCallingUid(); } } diff --git a/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java b/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java index db83505900c5..6a9a12133e70 100644 --- a/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java +++ b/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java @@ -1149,31 +1149,31 @@ public class TrampolineTest { @Test public void beginFullBackup_calledBeforeInitialize_ignored() throws RemoteException { - mTrampoline.beginFullBackup(new FullBackupJob()); + mTrampoline.beginFullBackup(mUserId, new FullBackupJob()); verifyNoMoreInteractions(mBackupManagerServiceMock); } @Test public void beginFullBackup_forwarded() throws RemoteException { FullBackupJob fullBackupJob = new FullBackupJob(); - when(mBackupManagerServiceMock.beginFullBackup(fullBackupJob)).thenReturn(true); + when(mBackupManagerServiceMock.beginFullBackup(mUserId, fullBackupJob)).thenReturn(true); mTrampoline.initializeService(); - assertTrue(mTrampoline.beginFullBackup(fullBackupJob)); - verify(mBackupManagerServiceMock).beginFullBackup(fullBackupJob); + assertTrue(mTrampoline.beginFullBackup(mUserId, fullBackupJob)); + verify(mBackupManagerServiceMock).beginFullBackup(mUserId, fullBackupJob); } @Test public void endFullBackup_calledBeforeInitialize_ignored() throws RemoteException { - mTrampoline.endFullBackup(); + mTrampoline.endFullBackup(mUserId); verifyNoMoreInteractions(mBackupManagerServiceMock); } @Test public void endFullBackup_forwarded() throws RemoteException { mTrampoline.initializeService(); - mTrampoline.endFullBackup(); - verify(mBackupManagerServiceMock).endFullBackup(); + mTrampoline.endFullBackup(mUserId); + verify(mBackupManagerServiceMock).endFullBackup(mUserId); } @Test |