Merge "[Multi-user] Change device provisioned to per-user setup complete"
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java
index b669019..198a258 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.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 @@
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 @@
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 @@
// 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 @@
mUserBackupThread.quit();
}
+ public int getUserId() {
+ return mUserId;
+ }
+
public BackupManagerConstants getConstants() {
return mConstants;
}
@@ -619,12 +624,12 @@
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 @@
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 @@
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 @@
}
}
- /** 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 @@
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 @@
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 @@
}
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 @@
// 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<String> transportNames = new ArrayList<>();
@@ -3455,7 +3456,7 @@
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 5b449c5..2ee96d1 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 @@
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 7e2ac79..0000000
--- 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 2a5d913..3b87724 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 @@
} 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 @@
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 0000000..41eb966
--- /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 c65830d..8d5c301 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 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 @@
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 @@
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 @@
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 0000000..b56b5e6
--- /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);
+ }
+}