[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);