[Multi-user] Create setting for multi-user backup service support
Whether the backup service supports multi-user is now configured in a
Global setting: backup_multi_user_enabled
This allows us to develop multi-user support hidden behind a flag. In a
future CL, we'll also gate the types of users we support.
Also create basic infrastructure for starting the service for a newly
unlocked user (currently a no-op).
Bug: 120212806
Test: 1) atest TrampolineTest
2) adb shell settings put global backup_multi_user_enabled 0;
unlock system user -> verify service started;
unlock user 10 -> verify service not started;
3) adb shell settings put global backup_multi_user_enabled 1;
unlock system user -> verify service started;
unlock user 10 -> verify service started;
Change-Id: I048e017cfa6148097cebe2eb2916d1b53c53d3b0
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 3437e1d..de6afcb 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -13795,6 +13795,14 @@
"backup_agent_timeout_parameters";
/**
+ * Whether the backup system service supports multiple users (0 = disabled, 1 = enabled). If
+ * disabled, the service will only be active for the system user.
+ *
+ * @hide
+ */
+ public static final String BACKUP_MULTI_USER_ENABLED = "backup_multi_user_enabled";
+
+ /**
* Blacklist of GNSS satellites.
*
* This is a list of integers separated by commas to represent pairs of (constellation,
diff --git a/core/proto/android/providers/settings/global.proto b/core/proto/android/providers/settings/global.proto
index da6e208..11bd43b 100644
--- a/core/proto/android/providers/settings/global.proto
+++ b/core/proto/android/providers/settings/global.proto
@@ -109,7 +109,15 @@
}
optional Autofill autofill = 140;
- optional SettingProto backup_agent_timeout_parameters = 18;
+ reserved 18; // Used to be backup_agent_timeout_parameters
+
+ message Backup {
+ option (android.msg_privacy).dest = DEST_EXPLICIT;
+
+ optional SettingProto backup_agent_timeout_parameters = 1;
+ optional SettingProto backup_multi_user_enabled = 2;
+ }
+ optional Backup backup = 146;
message Battery {
option (android.msg_privacy).dest = DEST_EXPLICIT;
@@ -1007,5 +1015,5 @@
// Please insert fields in alphabetical order and group them into messages
// if possible (to avoid reaching the method limit).
- // Next tag = 146;
+ // Next tag = 147;
}
diff --git a/core/tests/coretests/src/android/provider/SettingsBackupTest.java b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
index 79eaab8..49a7555 100644
--- a/core/tests/coretests/src/android/provider/SettingsBackupTest.java
+++ b/core/tests/coretests/src/android/provider/SettingsBackupTest.java
@@ -541,7 +541,8 @@
Settings.Global.OVERRIDE_SETTINGS_PROVIDER_RESTORE_ANY_VERSION,
Settings.Global.CHAINED_BATTERY_ATTRIBUTION_ENABLED,
Settings.Global.HIDDEN_API_BLACKLIST_EXEMPTIONS,
- Settings.Global.BACKUP_AGENT_TIMEOUT_PARAMETERS);
+ Settings.Global.BACKUP_AGENT_TIMEOUT_PARAMETERS,
+ Settings.Global.BACKUP_MULTI_USER_ENABLED);
private static final Set<String> BACKUP_BLACKLISTED_SECURE_SETTINGS =
newHashSet(
Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 533956f..e3d3d81 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -208,9 +208,14 @@
GlobalSettingsProto.Autofill.MAX_VISIBLE_DATASETS);
p.end(autofillToken);
+ final long backupToken = p.start(GlobalSettingsProto.BACKUP);
dumpSetting(s, p,
Settings.Global.BACKUP_AGENT_TIMEOUT_PARAMETERS,
- GlobalSettingsProto.BACKUP_AGENT_TIMEOUT_PARAMETERS);
+ GlobalSettingsProto.Backup.BACKUP_AGENT_TIMEOUT_PARAMETERS);
+ dumpSetting(s, p,
+ Settings.Global.BACKUP_MULTI_USER_ENABLED,
+ GlobalSettingsProto.Backup.BACKUP_MULTI_USER_ENABLED);
+ p.end(backupToken);
final long batteryToken = p.start(GlobalSettingsProto.BATTERY);
dumpSetting(s, p,
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 0b06f28..e62ec7a 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -184,6 +184,15 @@
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
+ /**
+ * Starts the backup service for user {@code userId} by creating a new instance of {@link
+ * UserBackupManagerService} and registering it with this service.
+ */
+ // TODO(b/120212806): Add UserBackupManagerService initialization logic.
+ void startServiceForUser(int userId) {
+ // Intentionally empty.
+ }
+
/*
* The following methods are implementations of IBackupManager methods called from Trampoline.
* They delegate to the appropriate per-user instance of UserBackupManagerService to perform the
@@ -626,7 +635,9 @@
@Override
public void onUnlockUser(int userId) {
if (userId == UserHandle.USER_SYSTEM) {
- sInstance.unlockSystemUser();
+ sInstance.initializeServiceAndUnlockSystemUser();
+ } else {
+ sInstance.startServiceForUser(userId);
}
}
}
diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java
index 59629aa..9ca2070 100644
--- a/services/backup/java/com/android/server/backup/Trampoline.java
+++ b/services/backup/java/com/android/server/backup/Trampoline.java
@@ -41,6 +41,7 @@
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
+import android.provider.Settings;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
@@ -81,6 +82,12 @@
// Product-level suppression of backup/restore.
private static final String BACKUP_DISABLE_PROPERTY = "ro.backup.disable";
+ private static final String BACKUP_THREAD = "backup";
+
+ /** Values for setting {@link Settings.Global#BACKUP_MULTI_USER_ENABLED} */
+ private static final int MULTI_USER_DISABLED = 0;
+ private static final int MULTI_USER_ENABLED = 1;
+
private final Context mContext;
@GuardedBy("mStateLock")
@@ -91,18 +98,31 @@
private volatile BackupManagerService mService;
private HandlerThread mHandlerThread;
+ private Handler mHandler;
public Trampoline(Context context) {
mContext = context;
mGlobalDisable = isBackupDisabled();
mSuppressFile = getSuppressFile();
mSuppressFile.getParentFile().mkdirs();
+
+ mHandlerThread = new HandlerThread(BACKUP_THREAD, Process.THREAD_PRIORITY_BACKGROUND);
+ mHandlerThread.start();
+ mHandler = new Handler(mHandlerThread.getLooper());
}
protected boolean isBackupDisabled() {
return SystemProperties.getBoolean(BACKUP_DISABLE_PROPERTY, false);
}
+ protected boolean isMultiUserEnabled() {
+ return Settings.Global.getInt(
+ mContext.getContentResolver(),
+ Settings.Global.BACKUP_MULTI_USER_ENABLED,
+ MULTI_USER_DISABLED)
+ == MULTI_USER_ENABLED;
+ }
+
protected int binderGetCallingUid() {
return Binder.getCallingUid();
}
@@ -147,15 +167,9 @@
/**
* Called from {@link BackupManagerService.Lifecycle} when the system user is unlocked. Attempts
* to initialize {@link BackupManagerService} and set backup state for the system user.
- *
- * @see BackupManagerService#unlockSystemUser()
*/
- void unlockSystemUser() {
- mHandlerThread = new HandlerThread("backup", Process.THREAD_PRIORITY_BACKGROUND);
- mHandlerThread.start();
-
- Handler h = new Handler(mHandlerThread.getLooper());
- h.post(
+ void initializeServiceAndUnlockSystemUser() {
+ mHandler.post(
() -> {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup init");
initializeService(UserHandle.USER_SYSTEM);
@@ -170,6 +184,29 @@
}
/**
+ * Called from {@link BackupManagerService.Lifecycle} when a non-system user {@code userId} is
+ * unlocked. Starts the backup service for this user if the service supports multi-user.
+ * Offloads work onto the handler thread {@link #mHandlerThread} to keep unlock time low.
+ */
+ // TODO(b/120212806): Consolidate service start for system and non-system users when system
+ // user-only logic is removed.
+ void startServiceForUser(int userId) {
+ if (!isMultiUserEnabled()) {
+ Slog.i(TAG, "Multi-user disabled, cannot start service for user: " + userId);
+ return;
+ }
+
+ mHandler.post(
+ () -> {
+ BackupManagerService service = mService;
+ if (service != null) {
+ Slog.i(TAG, "Starting service for user: " + userId);
+ service.startServiceForUser(userId);
+ }
+ });
+ }
+
+ /**
* Only privileged callers should be changing the backup state. This method only acts on {@link
* UserHandle#USER_SYSTEM} and is a no-op if passed non-system users. Deactivating backup in the
* system user also deactivates backup in all users.
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 7c00299..fd010f1 100644
--- a/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java
@@ -23,6 +23,7 @@
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -43,10 +44,14 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
+import android.provider.Settings;
+import android.test.mock.MockContentResolver;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.util.test.FakeSettingsProvider;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -97,6 +102,7 @@
private FileDescriptor mFileDescriptorStub = new FileDescriptor();
private TrampolineTestable mTrampoline;
+ private MockContentResolver mContentResolver;
@Before
public void setUp() {
@@ -110,6 +116,10 @@
when(mSuppressFileMock.getParentFile()).thenReturn(mSuppressFileParentMock);
mTrampoline = new TrampolineTestable(mContextMock);
+
+ mContentResolver = new MockContentResolver();
+ mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+ when(mContextMock.getContentResolver()).thenReturn(mContentResolver);
}
@Test
@@ -118,6 +128,24 @@
}
@Test
+ public void startServiceForUser_whenMultiUserSettingDisabled_isIgnored() {
+ Settings.Global.putInt(mContentResolver, Settings.Global.BACKUP_MULTI_USER_ENABLED, 0);
+
+ mTrampoline.startServiceForUser(10);
+
+ verify(mBackupManagerServiceMock, never()).startServiceForUser(10);
+ }
+
+ @Test
+ public void startServiceForUser_whenMultiUserSettingEnabled_callsBackupManagerService() {
+ Settings.Global.putInt(mContentResolver, Settings.Global.BACKUP_MULTI_USER_ENABLED, 1);
+
+ mTrampoline.startServiceForUser(10);
+
+ verify(mBackupManagerServiceMock).startServiceForUser(10);
+ }
+
+ @Test
public void initializeService_forUserSystem_successfullyInitialized() {
mTrampoline.initializeService(UserHandle.USER_SYSTEM);