diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index b669019ada76a..198a258a33b69 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -110,9 +110,9 @@ import com.android.server.backup.internal.ClearDataObserver; import com.android.server.backup.internal.OnTaskFinishedListener; import com.android.server.backup.internal.Operation; import com.android.server.backup.internal.PerformInitializeTask; -import com.android.server.backup.internal.ProvisionedObserver; import com.android.server.backup.internal.RunBackupReceiver; import com.android.server.backup.internal.RunInitializeReceiver; +import com.android.server.backup.internal.SetupObserver; import com.android.server.backup.keyvalue.BackupRequest; import com.android.server.backup.params.AdbBackupParams; import com.android.server.backup.params.AdbParams; @@ -261,7 +261,7 @@ public class UserBackupManagerService { private IBackupManager mBackupManagerBinder; private boolean mEnabled; // access to this is synchronized on 'this' - private boolean mProvisioned; + private boolean mSetupComplete; private boolean mAutoRestore; private PendingIntent mRunBackupIntent; @@ -315,9 +315,6 @@ public class UserBackupManagerService { private ActiveRestoreSession mActiveRestoreSession; - // Watch the device provisioning operation during setup - private ContentObserver mProvisionedObserver; - /** * mCurrentOperations contains the list of currently active operations. * @@ -468,15 +465,19 @@ public class UserBackupManagerService { // Set up our bookkeeping final ContentResolver resolver = context.getContentResolver(); - mProvisioned = Settings.Global.getInt(resolver, - Settings.Global.DEVICE_PROVISIONED, 0) != 0; + mSetupComplete = + Settings.Secure.getIntForUser( + resolver, Settings.Secure.USER_SETUP_COMPLETE, 0, mUserId) + != 0; mAutoRestore = Settings.Secure.getInt(resolver, Settings.Secure.BACKUP_AUTO_RESTORE, 1) != 0; - mProvisionedObserver = new ProvisionedObserver(this, mBackupHandler); + ContentObserver setupObserver = new SetupObserver(this, mBackupHandler); resolver.registerContentObserver( - Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED), - false, mProvisionedObserver); + Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE), + /* notifyForDescendents */ false, + setupObserver, + mUserId); mBaseStateDir = checkNotNull(baseStateDir, "baseStateDir cannot be null"); mBaseStateDir.mkdirs(); @@ -550,6 +551,10 @@ public class UserBackupManagerService { mUserBackupThread.quit(); } + public int getUserId() { + return mUserId; + } + public BackupManagerConstants getConstants() { return mConstants; } @@ -619,12 +624,12 @@ public class UserBackupManagerService { mEnabled = enabled; } - public boolean isProvisioned() { - return mProvisioned; + public boolean isSetupComplete() { + return mSetupComplete; } - public void setProvisioned(boolean provisioned) { - mProvisioned = provisioned; + public void setSetupComplete(boolean setupComplete) { + mSetupComplete = setupComplete; } public PowerManager.WakeLock getWakelock() { @@ -1577,11 +1582,16 @@ public class UserBackupManagerService { throw new IllegalArgumentException("No packages are provided for backup"); } - if (!mEnabled || !mProvisioned) { - Slog.i(TAG, "Backup requested but e=" + mEnabled + " p=" + mProvisioned); + if (!mEnabled || !mSetupComplete) { + Slog.i( + TAG, + "Backup requested but enabled=" + + mEnabled + + " setupComplete=" + + mSetupComplete); BackupObserverUtils.sendBackupFinished(observer, BackupManager.ERROR_BACKUP_NOT_ALLOWED); - final int logTag = mProvisioned + final int logTag = mSetupComplete ? BackupManagerMonitor.LOG_EVENT_ID_BACKUP_DISABLED : BackupManagerMonitor.LOG_EVENT_ID_DEVICE_NOT_PROVISIONED; monitor = BackupManagerMonitorUtils.monitorEvent(monitor, logTag, null, @@ -2016,13 +2026,13 @@ public class UserBackupManagerService { FullBackupEntry entry = null; long latency = fullBackupInterval; - if (!mEnabled || !mProvisioned) { + if (!mEnabled || !mSetupComplete) { // Backups are globally disabled, so don't proceed. We also don't reschedule // the job driving automatic backups; that job will be scheduled again when // the user enables backup. if (MORE_DEBUG) { - Slog.i(TAG, "beginFullBackup but e=" + mEnabled - + " p=" + mProvisioned + "; ignoring"); + Slog.i(TAG, "beginFullBackup but enabled=" + mEnabled + + " setupComplete=" + mSetupComplete + "; ignoring"); } return false; } @@ -2423,12 +2433,6 @@ public class UserBackupManagerService { } } - /** Returns {@code true} if the system user has gone through SUW. */ - public boolean deviceIsProvisioned() { - final ContentResolver resolver = mContext.getContentResolver(); - return (Settings.Global.getInt(resolver, Settings.Global.DEVICE_PROVISIONED, 0) != 0); - } - /** * Used by 'adb backup' to run a backup pass for packages supplied via the command line, writing * the resulting data stream to the supplied {@code fd}. This method is synchronous and does not @@ -2461,8 +2465,7 @@ public class UserBackupManagerService { long oldId = Binder.clearCallingIdentity(); try { - // Doesn't make sense to do a full backup prior to setup - if (!deviceIsProvisioned()) { + if (!mSetupComplete) { Slog.i(TAG, "Backup not supported before setup"); return; } @@ -2588,9 +2591,7 @@ public class UserBackupManagerService { long oldId = Binder.clearCallingIdentity(); try { - // Check whether the device has been provisioned -- we don't handle - // full restores prior to completing the setup process. - if (!deviceIsProvisioned()) { + if (!mSetupComplete) { Slog.i(TAG, "Full restore not permitted before setup"); return; } @@ -2745,7 +2746,7 @@ public class UserBackupManagerService { } synchronized (mQueueLock) { - if (enable && !wasEnabled && mProvisioned) { + if (enable && !wasEnabled && mSetupComplete) { // if we've just been enabled, start scheduling backup passes KeyValueBackupJob.schedule(mContext, mConstants); scheduleNextFullBackupJob(0); @@ -2758,7 +2759,7 @@ public class UserBackupManagerService { // This also constitutes an opt-out, so we wipe any data for // this device from the backend. We start that process with // an alarm in order to guarantee wakelock states. - if (wasEnabled && mProvisioned) { + if (wasEnabled && mSetupComplete) { // NOTE: we currently flush every registered transport, not just // the currently-active one. List transportNames = new ArrayList<>(); @@ -3455,7 +3456,7 @@ public class UserBackupManagerService { private void dumpInternal(PrintWriter pw) { synchronized (mQueueLock) { pw.println("Backup Manager is " + (mEnabled ? "enabled" : "disabled") - + " / " + (!mProvisioned ? "not " : "") + "provisioned / " + + " / " + (!mSetupComplete ? "not " : "") + "setup complete / " + (this.mPendingInits.size() == 0 ? "not " : "") + "pending init"); pw.println("Auto-restore is " + (mAutoRestore ? "enabled" : "disabled")); if (mBackupRunning) pw.println("Backup currently running"); 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 5b449c5b400ea..2ee96d190f900 100644 --- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java +++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java @@ -317,16 +317,16 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba int backupRunStatus = BackupManager.SUCCESS; try { - if (!backupManagerService.isEnabled() || !backupManagerService.isProvisioned()) { + if (!backupManagerService.isEnabled() || !backupManagerService.isSetupComplete()) { // Backups are globally disabled, so don't proceed. if (DEBUG) { Slog.i(TAG, "full backup requested but enabled=" + backupManagerService .isEnabled() - + " provisioned=" + backupManagerService.isProvisioned() + + " setupComplete=" + backupManagerService.isSetupComplete() + "; ignoring"); } int monitoringEvent; - if (backupManagerService.isProvisioned()) { + if (backupManagerService.isSetupComplete()) { monitoringEvent = BackupManagerMonitor.LOG_EVENT_ID_BACKUP_DISABLED; } else { monitoringEvent = BackupManagerMonitor.LOG_EVENT_ID_DEVICE_NOT_PROVISIONED; diff --git a/services/backup/java/com/android/server/backup/internal/ProvisionedObserver.java b/services/backup/java/com/android/server/backup/internal/ProvisionedObserver.java deleted file mode 100644 index 7e2ac796d3439..0000000000000 --- a/services/backup/java/com/android/server/backup/internal/ProvisionedObserver.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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.backup.internal; - -import static com.android.server.backup.BackupManagerService.MORE_DEBUG; -import static com.android.server.backup.BackupManagerService.TAG; - -import android.database.ContentObserver; -import android.os.Handler; -import android.util.Slog; - -import com.android.server.backup.KeyValueBackupJob; -import com.android.server.backup.UserBackupManagerService; - -public class ProvisionedObserver extends ContentObserver { - - private UserBackupManagerService backupManagerService; - - public ProvisionedObserver( - UserBackupManagerService backupManagerService, Handler handler) { - super(handler); - this.backupManagerService = backupManagerService; - } - - public void onChange(boolean selfChange) { - final boolean wasProvisioned = backupManagerService.isProvisioned(); - final boolean isProvisioned = backupManagerService.deviceIsProvisioned(); - // latch: never unprovision - backupManagerService.setProvisioned(wasProvisioned || isProvisioned); - if (MORE_DEBUG) { - Slog.d(TAG, "Provisioning change: was=" + wasProvisioned - + " is=" + isProvisioned + " now=" + backupManagerService.isProvisioned()); - } - - synchronized (backupManagerService.getQueueLock()) { - if (backupManagerService.isProvisioned() && !wasProvisioned - && backupManagerService.isEnabled()) { - // we're now good to go, so start the backup alarms - if (MORE_DEBUG) { - Slog.d(TAG, "Now provisioned, so starting backups"); - } - KeyValueBackupJob.schedule(backupManagerService.getContext(), - backupManagerService.getConstants()); - backupManagerService.scheduleNextFullBackupJob(0); - } - } - } -} diff --git a/services/backup/java/com/android/server/backup/internal/RunBackupReceiver.java b/services/backup/java/com/android/server/backup/internal/RunBackupReceiver.java index 2a5d913226b94..3b877241f7f47 100644 --- a/services/backup/java/com/android/server/backup/internal/RunBackupReceiver.java +++ b/services/backup/java/com/android/server/backup/internal/RunBackupReceiver.java @@ -59,7 +59,8 @@ public class RunBackupReceiver extends BroadcastReceiver { } else { // Don't run backups now if we're disabled or not yet // fully set up. - if (backupManagerService.isEnabled() && backupManagerService.isProvisioned()) { + if (backupManagerService.isEnabled() + && backupManagerService.isSetupComplete()) { if (!backupManagerService.isBackupRunning()) { if (DEBUG) { Slog.v(TAG, "Running a backup pass"); @@ -77,8 +78,8 @@ public class RunBackupReceiver extends BroadcastReceiver { Slog.i(TAG, "Backup time but one already running"); } } else { - Slog.w(TAG, "Backup pass but e=" + backupManagerService.isEnabled() + " p=" - + backupManagerService.isProvisioned()); + Slog.w(TAG, "Backup pass but enabled=" + backupManagerService.isEnabled() + + " setupComplete=" + backupManagerService.isSetupComplete()); } } } diff --git a/services/backup/java/com/android/server/backup/internal/SetupObserver.java b/services/backup/java/com/android/server/backup/internal/SetupObserver.java new file mode 100644 index 0000000000000..41eb9663258bc --- /dev/null +++ b/services/backup/java/com/android/server/backup/internal/SetupObserver.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2018 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.internal; + +import static com.android.server.backup.BackupManagerService.MORE_DEBUG; +import static com.android.server.backup.BackupManagerService.TAG; + +import android.content.Context; +import android.database.ContentObserver; +import android.os.Handler; +import android.provider.Settings; +import android.util.Slog; + +import com.android.server.backup.KeyValueBackupJob; +import com.android.server.backup.UserBackupManagerService; + +/** + * A {@link ContentObserver} for changes to the setting {@link Settings.Secure#USER_SETUP_COMPLETE} + * for a particular user. + */ +public class SetupObserver extends ContentObserver { + private final UserBackupManagerService mUserBackupManagerService; + private final Context mContext; + private final int mUserId; + + public SetupObserver(UserBackupManagerService userBackupManagerService, Handler handler) { + super(handler); + mUserBackupManagerService = userBackupManagerService; + mContext = userBackupManagerService.getContext(); + mUserId = userBackupManagerService.getUserId(); + } + + /** + * Callback that executes when the setting {@link Settings.Secure#USER_SETUP_COMPLETE} changes + * for the user {@link #mUserId}. If the user is newly setup and backup is enabled, then we + * schedule a key value and full backup job for the user. If the user was previously setup and + * now the setting has changed to {@code false}, we don't reset the state as having gone through + * setup is a non-reversible action. + */ + public void onChange(boolean selfChange) { + boolean previousSetupComplete = mUserBackupManagerService.isSetupComplete(); + boolean newSetupComplete = + Settings.Secure.getIntForUser( + mContext.getContentResolver(), + Settings.Secure.USER_SETUP_COMPLETE, + 0, + mUserId) + != 0; + + boolean resolvedSetupComplete = previousSetupComplete || newSetupComplete; + mUserBackupManagerService.setSetupComplete(resolvedSetupComplete); + if (MORE_DEBUG) { + Slog.d( + TAG, + "Setup complete change: was=" + + previousSetupComplete + + " new=" + + newSetupComplete + + " resolved=" + + resolvedSetupComplete); + } + + synchronized (mUserBackupManagerService.getQueueLock()) { + // Start backup if the user is newly setup and backup is enabled. + if (resolvedSetupComplete + && !previousSetupComplete + && mUserBackupManagerService.isEnabled()) { + if (MORE_DEBUG) { + Slog.d(TAG, "Setup complete so starting backups"); + } + KeyValueBackupJob.schedule(mContext, mUserBackupManagerService.getConstants()); + mUserBackupManagerService.scheduleNextFullBackupJob(0); + } + } + } +} 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 c65830d37d333..8d5c301d93e75 100644 --- a/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java +++ b/services/robotests/backup/src/com/android/server/backup/UserBackupManagerServiceTest.java @@ -829,7 +829,7 @@ public class UserBackupManagerServiceTest { public void testRequestBackup_whenNotProvisioned() throws Exception { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); - backupManagerService.setProvisioned(false); + backupManagerService.setSetupComplete(false); int result = backupManagerService.requestBackup(new String[] {PACKAGE_1}, mObserver, 0); @@ -848,7 +848,7 @@ public class UserBackupManagerServiceTest { setUpCurrentTransport(mTransportManager, mTransport.unregistered()); UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); backupManagerService.setEnabled(true); - backupManagerService.setProvisioned(true); + backupManagerService.setSetupComplete(true); int result = backupManagerService.requestBackup(new String[] {PACKAGE_1}, mObserver, 0); @@ -868,7 +868,7 @@ public class UserBackupManagerServiceTest { setUpCurrentTransport(mTransportManager, mTransport); UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); backupManagerService.setEnabled(true); - backupManagerService.setProvisioned(true); + backupManagerService.setSetupComplete(true); // Haven't set PACKAGE_1 as eligible int result = backupManagerService.requestBackup(new String[] {PACKAGE_1}, mObserver, 0); @@ -965,7 +965,7 @@ public class UserBackupManagerServiceTest { private UserBackupManagerService createBackupManagerServiceForRequestBackup() { UserBackupManagerService backupManagerService = createUserBackupManagerServiceAndRunTasks(); backupManagerService.setEnabled(true); - backupManagerService.setProvisioned(true); + backupManagerService.setSetupComplete(true); return backupManagerService; } 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 new file mode 100644 index 0000000000000..b56b5e6132d5c --- /dev/null +++ b/services/robotests/backup/src/com/android/server/backup/internal/SetupObserverTest.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2018 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.internal; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.os.Handler; +import android.os.HandlerThread; +import android.platform.test.annotations.Presubmit; +import android.provider.Settings; + +import com.android.server.backup.KeyValueBackupJob; +import com.android.server.backup.TransportManager; +import com.android.server.backup.UserBackupManagerService; +import com.android.server.backup.testing.BackupManagerServiceTestUtils; +import com.android.server.backup.testing.TestUtils; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +import java.io.File; + +/** + * Tests verifying the interaction between {@link SetupObserver} and {@link + * UserBackupManagerService}. + */ +@RunWith(RobolectricTestRunner.class) +@Presubmit +public class SetupObserverTest { + private static final String TAG = "SetupObserverTest"; + private static final int USER_ID = 10; + + @Mock private TransportManager mTransportManager; + + private Context mContext; + private UserBackupManagerService mUserBackupManagerService; + private HandlerThread mHandlerThread; + + /** Setup state. */ + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + mHandlerThread = BackupManagerServiceTestUtils.startSilentBackupThread(TAG); + mUserBackupManagerService = + BackupManagerServiceTestUtils.createUserBackupManagerServiceAndRunTasks( + USER_ID, + mContext, + mHandlerThread, + new File(mContext.getDataDir(), "test1"), + new File(mContext.getDataDir(), "test2"), + mTransportManager); + } + + /** Test observer handles changes from not setup -> setup correctly. */ + @Test + public void testOnChange_whenNewlySetup_updatesState() throws Exception { + SetupObserver setupObserver = new SetupObserver(mUserBackupManagerService, new Handler()); + mUserBackupManagerService.setSetupComplete(false); + changeSetupCompleteSettingForUser(true, USER_ID); + + setupObserver.onChange(true); + + assertThat(mUserBackupManagerService.isSetupComplete()).isTrue(); + } + + /** Test observer handles changes from setup -> not setup correctly. */ + @Test + public void testOnChange_whenPreviouslySetup_doesNotUpdateState() throws Exception { + SetupObserver setupObserver = new SetupObserver(mUserBackupManagerService, new Handler()); + mUserBackupManagerService.setSetupComplete(true); + changeSetupCompleteSettingForUser(false, USER_ID); + + setupObserver.onChange(true); + + assertThat(mUserBackupManagerService.isSetupComplete()).isTrue(); + } + + /** Test observer handles changes from not setup -> not setup correctly. */ + @Test + public void testOnChange_whenNotPreviouslySetup_doesNotUpdateStateIfNoChange() + throws Exception { + SetupObserver setupObserver = new SetupObserver(mUserBackupManagerService, new Handler()); + mUserBackupManagerService.setSetupComplete(false); + changeSetupCompleteSettingForUser(false, USER_ID); + + setupObserver.onChange(true); + + assertThat(mUserBackupManagerService.isSetupComplete()).isFalse(); + } + + /** Test observer handles changes from not setup -> setup correctly. */ + @Test + public void testOnChange_whenNewlySetup_schedulesBackup() throws Exception { + SetupObserver setupObserver = new SetupObserver(mUserBackupManagerService, new Handler()); + mUserBackupManagerService.setSetupComplete(false); + changeSetupCompleteSettingForUser(true, USER_ID); + // Setup conditions for a full backup job to be scheduled. + mUserBackupManagerService.setEnabled(true); + mUserBackupManagerService.enqueueFullBackup("testPackage", /* lastBackedUp */ 0); + // Clear the handler of all pending tasks. This is to prevent the below assertion on the + // handler from encountering false positives due to other tasks being scheduled as part of + // setup work. + TestUtils.runToEndOfTasks(mHandlerThread.getLooper()); + + setupObserver.onChange(true); + + assertThat(KeyValueBackupJob.isScheduled()).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(); + } + + private void changeSetupCompleteSettingForUser(boolean value, int userId) { + Settings.Secure.putIntForUser( + mContext.getContentResolver(), + Settings.Secure.USER_SETUP_COMPLETE, + value ? 1 : 0, + userId); + } +}