diff options
author | Annie Meng <anniemeng@google.com> | 2019-01-22 15:32:25 +0000 |
---|---|---|
committer | Annie Meng <anniemeng@google.com> | 2019-01-24 15:14:09 +0000 |
commit | bdb8848abe9999fbd302d71f2c0ce62b9a09fd8a (patch) | |
tree | b2c8d96aeb9ee6e07dc9434cabe619c8857b50da | |
parent | 25b54058c0e3bb7e0630650f750092f7ccd2289f (diff) |
[Multi-user] Disable backup by default in non-system users
Key changes in this CL:
- Backup is now disabled by default in non-system users unless DPM
activates backup for this user AND the system user is activated. This
provides gating for the multi-user B&R feature.
- Activation is done via an 'activate' file that is per-user (but lives
in the system user directory to account for locked users).
- isBackupServiceActive() handles both locked and unlocked users.
- Added a bmgr command to expose isBackupServiceActive() for testing
purposes and enforce appropriate permissions.
Future CLs:
- Handle future migration to backup on by default for non-system users
- Change CTS tests to use the new bmgr command
Bug: 121306407
Test: 1) atest TrampolineTest
2) Start system user -> service started; run backup and restore
successfully
3) Start non-system user -> ignored;
4) adb shell bmgr --user 0 activate true -> security exception;
adb shell bmgr --user 10 activate true -> security exception (work
profile);
adb shell bmgr --user 11 activate true/false -> creates/deletes activate
file and starts/stops the service
Change-Id: Ic77db9b8b2e5170dcf89bef863dac4713730797a
3 files changed, 331 insertions, 200 deletions
diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java index 3defdc5467c8..062ba655640e 100644 --- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java +++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java @@ -102,7 +102,17 @@ public class Bmgr { String op = nextArg(); Slog.v(TAG, "Running " + op + " for user:" + userId); - if (!isBmgrActive(userId)) { + if (mBmgr == null) { + System.err.println(BMGR_NOT_RUNNING_ERR); + return; + } + + if ("activate".equals(op)) { + doActivateService(userId); + return; + } + + if (!isBackupActive(userId)) { return; } @@ -175,12 +185,7 @@ public class Bmgr { showUsage(); } - boolean isBmgrActive(@UserIdInt int userId) { - if (mBmgr == null) { - System.err.println(BMGR_NOT_RUNNING_ERR); - return false; - } - + boolean isBackupActive(@UserIdInt int userId) { try { if (!mBmgr.isBackupServiceActive(userId)) { System.err.println(BMGR_NOT_RUNNING_ERR); @@ -845,6 +850,27 @@ public class Bmgr { } } + private void doActivateService(int userId) { + String arg = nextArg(); + if (arg == null) { + showUsage(); + return; + } + + try { + boolean activate = Boolean.parseBoolean(arg); + mBmgr.setBackupServiceActive(userId, activate); + System.out.println( + "Backup service now " + + (activate ? "activated" : "deactivated") + + " for user " + + userId); + } catch (RemoteException e) { + System.err.println(e.toString()); + System.err.println(BMGR_NOT_RUNNING_ERR); + } + } + private String nextArg() { if (mNextArg >= mArgs.length) { return null; @@ -880,6 +906,7 @@ public class Bmgr { System.err.println(" bmgr backupnow [--monitor|--monitor-verbose] --all|PACKAGE..."); System.err.println(" bmgr cancel backups"); System.err.println(" bmgr init TRANSPORT..."); + System.err.println(" bmgr activate BOOL"); System.err.println(""); System.err.println("The '--user' option specifies the user on which the operation is run."); System.err.println("It must be the first argument before the operation."); @@ -946,6 +973,11 @@ public class Bmgr { System.err.println(""); System.err.println("The 'init' command initializes the given transports, wiping all data"); System.err.println("from their backing data stores."); + System.err.println(""); + System.err.println("The 'activate' command activates or deactivates the backup service."); + System.err.println("If the argument is 'true' it will be activated, otherwise it will be"); + System.err.println("deactivated. When deactivated, the service will not be running and no"); + System.err.println("operations can be performed until activation."); } private static class BackupMonitor extends IBackupManagerMonitor.Stub { diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java index b9a6f3c08cc4..603c589cd5f6 100644 --- a/services/backup/java/com/android/server/backup/Trampoline.java +++ b/services/backup/java/com/android/server/backup/Trampoline.java @@ -18,6 +18,7 @@ package com.android.server.backup; import static com.android.server.backup.BackupManagerService.TAG; +import android.Manifest; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.admin.DevicePolicyManager; @@ -41,9 +42,10 @@ import android.os.RemoteException; import android.os.SystemProperties; import android.os.Trace; import android.os.UserHandle; -import android.provider.Settings; +import android.os.UserManager; import android.util.Slog; +import com.android.internal.annotations.GuardedBy; import com.android.internal.util.DumpUtils; import java.io.File; @@ -75,19 +77,25 @@ import java.io.PrintWriter; * system user is unlocked before any other users. */ public class Trampoline extends IBackupManager.Stub { - // When this file is present, the backup service is inactive. + /** + * Name of file that disables the backup service. If this file exists, then backup is disabled + * for all users. + */ private static final String BACKUP_SUPPRESS_FILENAME = "backup-suppress"; + /** + * Name of file for non-system users that enables the backup service for the user. Backup is + * disabled by default in non-system users. + */ + private static final String BACKUP_ACTIVATED_FILENAME = "backup-activated"; + // 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; + private final UserManager mUserManager; private final boolean mGlobalDisable; // Lock to write backup suppress files. @@ -104,20 +112,13 @@ public class Trampoline extends IBackupManager.Stub { mHandlerThread = new HandlerThread(BACKUP_THREAD, Process.THREAD_PRIORITY_BACKGROUND); mHandlerThread.start(); mHandler = new Handler(mHandlerThread.getLooper()); + mUserManager = UserManager.get(context); } protected boolean isBackupDisabled() { return SystemProperties.getBoolean(BACKUP_DISABLE_PROPERTY, false); } - private boolean isMultiUserEnabled() { - return Settings.Global.getInt( - mContext.getContentResolver(), - Settings.Global.BACKUP_MULTI_USER_ENABLED, - MULTI_USER_DISABLED) - == MULTI_USER_ENABLED; - } - protected int binderGetCallingUserId() { return Binder.getCallingUserHandle().getIdentifier(); } @@ -126,21 +127,65 @@ public class Trampoline extends IBackupManager.Stub { return Binder.getCallingUid(); } - protected File getSuppressFileForUser(int userId) { - return new File(UserBackupManagerFiles.getBaseStateDir(userId), + /** Stored in the system user's directory. */ + protected File getSuppressFileForSystemUser() { + return new File(UserBackupManagerFiles.getBaseStateDir(UserHandle.USER_SYSTEM), BACKUP_SUPPRESS_FILENAME); } - protected void createBackupSuppressFileForUser(int userId) throws IOException { - synchronized (mStateLock) { - getSuppressFileForUser(userId).getParentFile().mkdirs(); - getSuppressFileForUser(userId).createNewFile(); + /** Stored in the system user's directory and the file is indexed by the user it refers to. */ + protected File getActivatedFileForNonSystemUser(int userId) { + return new File(UserBackupManagerFiles.getBaseStateDir(UserHandle.USER_SYSTEM), + BACKUP_ACTIVATED_FILENAME + "-" + userId); + } + + private void createFile(File file) throws IOException { + if (file.exists()) { + return; + } + + file.getParentFile().mkdirs(); + if (!file.createNewFile()) { + Slog.w(TAG, "Failed to create file " + file.getPath()); } } - private void deleteBackupSuppressFileForUser(int userId) { - if (!getSuppressFileForUser(userId).delete()) { - Slog.w(TAG, "Failed deleting backup suppressed file for user: " + userId); + private void deleteFile(File file) { + if (!file.exists()) { + return; + } + + if (!file.delete()) { + Slog.w(TAG, "Failed to delete file " + file.getPath()); + } + } + + /** + * Deactivates the backup service for user {@code userId}. If this is the system user, it + * creates a suppress file which disables backup for all users. If this is a non-system user, it + * only deactivates backup for that user by deleting its activate file. + */ + @GuardedBy("mStateLock") + private void deactivateBackupForUserLocked(int userId) throws IOException { + if (userId == UserHandle.USER_SYSTEM) { + createFile(getSuppressFileForSystemUser()); + } else { + deleteFile(getActivatedFileForNonSystemUser(userId)); + } + } + + /** + * Enables the backup service for user {@code userId}. If this is the system user, it deletes + * the suppress file. If this is a non-system user, it creates the user's activate file. Note, + * deleting the suppress file does not automatically enable backup for non-system users, they + * need their own activate file in order to participate in the service. + */ + @GuardedBy("mStateLock") + private void activateBackupForUserLocked(int userId) throws IOException { + if (userId == UserHandle.USER_SYSTEM) { + deleteFile(getSuppressFileForSystemUser()); + } else { + createFile(getActivatedFileForNonSystemUser(userId)); } } @@ -148,24 +193,31 @@ public class Trampoline extends IBackupManager.Stub { // admin (device owner or profile owner). private boolean isUserReadyForBackup(int userId) { return mService != null && mService.getServiceUsers().get(userId) != null - && !isBackupSuppressedForUser(userId); + && isBackupActivatedForUser(userId); } - private boolean isBackupSuppressedForUser(int userId) { - // If backup is disabled for system user, it's disabled for all other users on device. - if (getSuppressFileForUser(UserHandle.USER_SYSTEM).exists()) { - return true; - } - if (userId != UserHandle.USER_SYSTEM) { - return getSuppressFileForUser(userId).exists(); + /** + * Backup is activated for the system user if the suppress file does not exist. Backup is + * activated for non-system users if the suppress file does not exist AND the user's activated + * file exists. + */ + private boolean isBackupActivatedForUser(int userId) { + if (getSuppressFileForSystemUser().exists()) { + return false; } - return false; + + return userId == UserHandle.USER_SYSTEM + || getActivatedFileForNonSystemUser(userId).exists(); } protected Context getContext() { return mContext; } + protected UserManager getUserManager() { + return mUserManager; + } + protected BackupManagerService createBackupManagerService() { return new BackupManagerService(mContext, this, mHandlerThread); } @@ -198,23 +250,17 @@ public class Trampoline extends IBackupManager.Stub { /** * Called from {@link BackupManagerService.Lifecycle} when a user {@code userId} is unlocked. - * Starts the backup service for this user if it's the system user or if the service supports - * multi-user. Offloads work onto the handler thread {@link #mHandlerThread} to keep unlock time - * low. + * Starts the backup service for this user if backup is active for this user. Offloads work onto + * the handler thread {@link #mHandlerThread} to keep unlock time low. */ void unlockUser(int userId) { - if (userId != UserHandle.USER_SYSTEM && !isMultiUserEnabled()) { - Slog.i(TAG, "Multi-user disabled, cannot start service for user: " + userId); - return; - } - postToHandler(() -> startServiceForUser(userId)); } private void startServiceForUser(int userId) { // We know that the user is unlocked here because it is called from setBackupServiceActive // and unlockUser which have these guarantees. So we can check if the file exists. - if (mService != null && !isBackupSuppressedForUser(userId)) { + if (mService != null && isBackupActivatedForUser(userId)) { Slog.i(TAG, "Starting service for user: " + userId); mService.startServiceForUser(userId); } @@ -225,11 +271,6 @@ public class Trampoline extends IBackupManager.Stub { * Offloads work onto the handler thread {@link #mHandlerThread} to keep stopping time low. */ void stopUser(int userId) { - if (userId != UserHandle.USER_SYSTEM && !isMultiUserEnabled()) { - Slog.i(TAG, "Multi-user disabled, cannot stop service for user: " + userId); - return; - } - postToHandler( () -> { if (mService != null) { @@ -240,45 +281,63 @@ public class Trampoline extends IBackupManager.Stub { } /** - * 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. - * - * This call will only work if the calling {@code userID} is unlocked. + * The system user and managed profiles can only be acted on by callers in the system or root + * processes. Other users can be acted on by callers who have both android.permission.BACKUP and + * android.permission.INTERACT_ACROSS_USERS_FULL permissions. */ - public void setBackupServiceActive(int userId, boolean makeActive) { - int caller = binderGetCallingUid(); - if (caller != Process.SYSTEM_UID && caller != Process.ROOT_UID) { - throw new SecurityException("No permission to configure backup activity"); + private void enforcePermissionsOnUser(int userId) throws SecurityException { + boolean isRestrictedUser = + userId == UserHandle.USER_SYSTEM + || getUserManager().getUserInfo(userId).isManagedProfile(); + + if (isRestrictedUser) { + int caller = binderGetCallingUid(); + if (caller != Process.SYSTEM_UID && caller != Process.ROOT_UID) { + throw new SecurityException("No permission to configure backup activity"); + } + } else { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.BACKUP, "No permission to configure backup activity"); + mContext.enforceCallingOrSelfPermission( + Manifest.permission.INTERACT_ACROSS_USERS_FULL, + "No permission to configure backup activity"); } + } + + /** + * Only privileged callers should be changing the backup state. Deactivating backup in the + * system user also deactivates backup in all users. We are not guaranteed that {@code userId} + * is unlocked at this point yet, so handle both cases. + */ + public void setBackupServiceActive(int userId, boolean makeActive) { + enforcePermissionsOnUser(userId); if (mGlobalDisable) { Slog.i(TAG, "Backup service not supported"); return; } - if (userId != UserHandle.USER_SYSTEM) { - Slog.i(TAG, "Cannot set backup service activity for non-system user: " + userId); - return; - } - - if (makeActive == isBackupServiceActive(userId)) { - Slog.i(TAG, "No change in backup service activity"); - return; - } - synchronized (mStateLock) { Slog.i(TAG, "Making backup " + (makeActive ? "" : "in") + "active"); if (makeActive) { if (mService == null) { mService = createBackupManagerService(); } - deleteBackupSuppressFileForUser(userId); - startServiceForUser(userId); + try { + activateBackupForUserLocked(userId); + } catch (IOException e) { + Slog.e(TAG, "Unable to persist backup service activity"); + } + + // If the user is unlocked, we can start the backup service for it. Otherwise we + // will start the service when the user is unlocked as part of its unlock callback. + if (getUserManager().isUserUnlocked(userId)) { + startServiceForUser(userId); + } } else { try { //TODO(b/121198006): what if this throws an exception? - createBackupSuppressFileForUser(userId); + deactivateBackupForUserLocked(userId); } catch (IOException e) { Slog.e(TAG, "Unable to persist backup service inactivity"); } 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 ac4a5fe90c06..a3f36b720398 100644 --- a/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java +++ b/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java @@ -24,11 +24,15 @@ import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; +import android.Manifest; import android.annotation.UserIdInt; import android.app.backup.BackupManager; import android.app.backup.IBackupManagerMonitor; @@ -39,21 +43,21 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; +import android.content.pm.UserInfo; import android.os.IBinder; import android.os.ParcelFileDescriptor; import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; +import android.os.UserManager; import android.platform.test.annotations.Presubmit; -import android.provider.Settings; -import android.test.mock.MockContentResolver; import android.util.SparseArray; +import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.internal.util.test.FakeSettingsProvider; - +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -99,10 +103,6 @@ public class TrampolineTest { @Mock private Context mContextMock; @Mock - private File mSuppressFileMock; - @Mock - private File mSuppressFileParentMock; - @Mock private IBinder mAgentMock; @Mock private ParcelFileDescriptor mParcelFileDescriptorMock; @@ -114,181 +114,232 @@ public class TrampolineTest { private IBackupManagerMonitor mBackupManagerMonitorMock; @Mock private PrintWriter mPrintWriterMock; + @Mock + private UserManager mUserManagerMock; + @Mock + private UserInfo mUserInfoMock; private FileDescriptor mFileDescriptorStub = new FileDescriptor(); private TrampolineTestable mTrampoline; - private MockContentResolver mContentResolver; + private File mTestDir; + private File mSuppressFile; + private File mActivatedFile; @Before - public void setUp() { + public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - mUserId = NON_USER_SYSTEM; + mUserId = UserHandle.USER_SYSTEM; SparseArray<UserBackupManagerService> serviceUsers = new SparseArray<>(); - serviceUsers.append(UserHandle.SYSTEM.getIdentifier(), mUserBackupManagerService); + serviceUsers.append(UserHandle.USER_SYSTEM, mUserBackupManagerService); serviceUsers.append(NON_USER_SYSTEM, mUserBackupManagerService); when(mBackupManagerServiceMock.getServiceUsers()).thenReturn(serviceUsers); + when(mUserManagerMock.getUserInfo(UserHandle.USER_SYSTEM)).thenReturn(mUserInfoMock); + when(mUserManagerMock.getUserInfo(NON_USER_SYSTEM)).thenReturn(mUserInfoMock); + TrampolineTestable.sBackupManagerServiceMock = mBackupManagerServiceMock; TrampolineTestable.sCallingUserId = UserHandle.USER_SYSTEM; TrampolineTestable.sCallingUid = Process.SYSTEM_UID; TrampolineTestable.sBackupDisabled = false; + TrampolineTestable.sUserManagerMock = mUserManagerMock; + + mTestDir = InstrumentationRegistry.getContext().getFilesDir(); + mTestDir.mkdirs(); - when(mSuppressFileMock.getParentFile()).thenReturn(mSuppressFileParentMock); + mSuppressFile = new File(mTestDir, "suppress"); + TrampolineTestable.sSuppressFile = mSuppressFile; + + mActivatedFile = new File(mTestDir, "activate-" + NON_USER_SYSTEM); + TrampolineTestable.sActivatedFiles.append(NON_USER_SYSTEM, mActivatedFile); mTrampoline = new TrampolineTestable(mContextMock); + } - mContentResolver = new MockContentResolver(); - mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); - when(mContextMock.getContentResolver()).thenReturn(mContentResolver); + @After + public void tearDown() throws Exception { + mSuppressFile.delete(); + mActivatedFile.delete(); } @Test - public void unlockUser_whenMultiUserSettingDisabled_callsBackupManagerServiceForSystemUser() { - Settings.Global.putInt(mContentResolver, Settings.Global.BACKUP_MULTI_USER_ENABLED, 0); + public void initializeService_successfullyInitializesBackupService() { mTrampoline.initializeService(); - mTrampoline.unlockUser(UserHandle.USER_SYSTEM); - - verify(mBackupManagerServiceMock).startServiceForUser(UserHandle.USER_SYSTEM); + assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); } @Test - public void unlockUser_whenMultiUserSettingDisabled_isIgnoredForNonSystemUser() { - Settings.Global.putInt(mContentResolver, Settings.Global.BACKUP_MULTI_USER_ENABLED, 0); - mTrampoline.initializeService(); + public void initializeService_globallyDisabled_nonInitialized() { + TrampolineTestable.sBackupDisabled = true; + TrampolineTestable trampoline = new TrampolineTestable(mContextMock); - mTrampoline.unlockUser(NON_USER_SYSTEM); + trampoline.initializeService(); - verify(mBackupManagerServiceMock, never()).startServiceForUser(NON_USER_SYSTEM); + assertFalse(trampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); } @Test - public void unlockUser_whenMultiUserSettingEnabled_callsBackupManagerServiceForNonSystemUser() { - Settings.Global.putInt(mContentResolver, Settings.Global.BACKUP_MULTI_USER_ENABLED, 1); + public void initializeService_doesNotStartServiceForUsers() { mTrampoline.initializeService(); - mTrampoline.unlockUser(NON_USER_SYSTEM); + verify(mBackupManagerServiceMock, never()).startServiceForUser(anyInt()); + } - verify(mBackupManagerServiceMock).startServiceForUser(NON_USER_SYSTEM); + @Test + public void isBackupServiceActive_calledBeforeInitialize_returnsFalse() { + assertFalse(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); } @Test - public void stopUser_whenMultiUserSettingDisabled_callsBackupManagerServiceForSystemUser() { - Settings.Global.putInt(mContentResolver, Settings.Global.BACKUP_MULTI_USER_ENABLED, 0); + public void isBackupServiceActive_forSystemUser_returnsTrueWhenActivated() throws Exception { mTrampoline.initializeService(); + mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); - mTrampoline.stopUser(UserHandle.USER_SYSTEM); - - verify(mBackupManagerServiceMock).stopServiceForUser(UserHandle.USER_SYSTEM); + assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); } @Test - public void stopUser_whenMultiUserSettingDisabled_isIgnoredForNonSystemUser() { - Settings.Global.putInt(mContentResolver, Settings.Global.BACKUP_MULTI_USER_ENABLED, 0); + public void isBackupServiceActive_forSystemUser_returnsFalseWhenDeactivated() throws Exception { mTrampoline.initializeService(); + mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false); - mTrampoline.stopUser(NON_USER_SYSTEM); - - verify(mBackupManagerServiceMock, never()).stopServiceForUser(NON_USER_SYSTEM); + assertFalse(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); } @Test - public void stopUser_whenMultiUserSettingEnabled_callsBackupManagerServiceForNonSystemUser() { - Settings.Global.putInt(mContentResolver, Settings.Global.BACKUP_MULTI_USER_ENABLED, 1); - + public void isBackupServiceActive_forNonSystemUser_returnsFalseWhenSystemUserDeactivated() + throws Exception { mTrampoline.initializeService(); + mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false); + mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true); - mTrampoline.stopUser(NON_USER_SYSTEM); - - verify(mBackupManagerServiceMock).stopServiceForUser(NON_USER_SYSTEM); + assertFalse(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM)); } @Test - public void initializeService_successfullyInitializesBackupService() { + public void isBackupServiceActive_forNonSystemUser_returnsFalseWhenNonSystemUserDeactivated() + throws Exception { mTrampoline.initializeService(); + mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); + // Don't activate non-system user. - assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); + assertFalse(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM)); } @Test - public void initializeService_globallyDisabled_nonInitialized() { - TrampolineTestable.sBackupDisabled = true; - TrampolineTestable trampoline = new TrampolineTestable(mContextMock); - - trampoline.initializeService(); + public void + isBackupServiceActive_forNonSystemUser_returnsTrueWhenSystemAndNonSystemUserActivated() + throws Exception { + mTrampoline.initializeService(); + mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); + mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true); - assertFalse(trampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); + assertTrue(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM)); } - // Verify that BackupManagerService is not initialized if suppress file exists. @Test - public void initializeService_suppressFileExists_nonInitialized() throws Exception { - TrampolineTestable trampoline = new TrampolineTestable(mContextMock); - trampoline.createBackupSuppressFileForUser(UserHandle.USER_SYSTEM); - + public void setBackupServiceActive_forSystemUserAndCallerSystemUid_serviceCreated() { + mTrampoline.initializeService(); + TrampolineTestable.sCallingUid = Process.SYSTEM_UID; - trampoline.initializeService(); + mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); - assertFalse(trampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); + assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); } @Test - public void initializeService_doesNotStartServiceForUsers() { + public void setBackupServiceActive_forSystemUserAndCallerRootUid_serviceCreated() { mTrampoline.initializeService(); + TrampolineTestable.sCallingUid = Process.ROOT_UID; - verify(mBackupManagerServiceMock, never()).startServiceForUser(anyInt()); - } + mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); - @Test - public void isBackupServiceActive_calledBeforeInitialize_returnsFalse() { - assertFalse(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); + assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); } @Test - public void isBackupServiceActive_forNonSysUser_whenSysUserIsDeactivated_returnsFalse() { - mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true); - mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false); + public void setBackupServiceActive_forSystemUserAndCallerNonRootNonSystem_throws() { + mTrampoline.initializeService(); + TrampolineTestable.sCallingUid = Process.FIRST_APPLICATION_UID; - assertFalse(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM)); + try { + mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); + fail(); + } catch (SecurityException expected) { + } } @Test - public void setBackupServiceActive_callerSystemUid_serviceCreated() { + public void setBackupServiceActive_forManagedProfileAndCallerSystemUid_serviceCreated() { + when(mUserInfoMock.isManagedProfile()).thenReturn(true); + mTrampoline.initializeService(); TrampolineTestable.sCallingUid = Process.SYSTEM_UID; - mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); + mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true); - assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); + assertTrue(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM)); } @Test - public void setBackupServiceActive_callerRootUid_serviceCreated() { + public void setBackupServiceActive_forManagedProfileAndCallerRootUid_serviceCreated() { + when(mUserInfoMock.isManagedProfile()).thenReturn(true); + mTrampoline.initializeService(); TrampolineTestable.sCallingUid = Process.ROOT_UID; - mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); + mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true); - assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); + assertTrue(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM)); } @Test - public void setBackupServiceActive_callerNonRootNonSystem_securityExceptionThrown() { + public void setBackupServiceActive_forManagedProfileAndCallerNonRootNonSystem_throws() { + when(mUserInfoMock.isManagedProfile()).thenReturn(true); + mTrampoline.initializeService(); TrampolineTestable.sCallingUid = Process.FIRST_APPLICATION_UID; try { - mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); + mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true); fail(); } catch (SecurityException expected) { } + } - assertFalse(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); + @Test + public void setBackupServiceActive_forNonSystemUserAndCallerWithoutBackupPermission_throws() { + doThrow(new SecurityException()) + .when(mContextMock) + .enforceCallingOrSelfPermission(eq(Manifest.permission.BACKUP), anyString()); + mTrampoline.initializeService(); + + try { + mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true); + fail(); + } catch (SecurityException expected) { + } + } + + @Test + public void setBackupServiceActive_forNonSystemUserAndCallerWithoutUserPermission_throws() { + doThrow(new SecurityException()) + .when(mContextMock) + .enforceCallingOrSelfPermission( + eq(Manifest.permission.INTERACT_ACROSS_USERS_FULL), anyString()); + mTrampoline.initializeService(); + + try { + mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true); + fail(); + } catch (SecurityException expected) { + } } @Test public void setBackupServiceActive_backupDisabled_ignored() { TrampolineTestable.sBackupDisabled = true; TrampolineTestable trampoline = new TrampolineTestable(mContextMock); + trampoline.initializeService(); trampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); @@ -296,14 +347,8 @@ public class TrampolineTest { } @Test - public void setBackupServiceActive_nonUserSystem_ignored() { - mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true); - - assertFalse(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM)); - } - - @Test public void setBackupServiceActive_alreadyActive_ignored() { + mTrampoline.initializeService(); mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); assertEquals(1, mTrampoline.getCreateServiceCallsCount()); @@ -315,6 +360,7 @@ public class TrampolineTest { @Test public void setBackupServiceActive_makeNonActive_alreadyNonActive_ignored() { + mTrampoline.initializeService(); mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false); mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, false); @@ -323,6 +369,7 @@ public class TrampolineTest { @Test public void setBackupServiceActive_makeActive_serviceCreatedAndSuppressFileDeleted() { + mTrampoline.initializeService(); mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); assertTrue(mTrampoline.isBackupServiceActive(UserHandle.USER_SYSTEM)); @@ -349,6 +396,21 @@ public class TrampolineTest { } @Test + public void setBackupServiceActive_forOneNonSystemUser_doesNotActivateForAllNonSystemUsers() { + mTrampoline.initializeService(); + int otherUser = NON_USER_SYSTEM + 1; + File activateFile = new File(mTestDir, "activate-" + otherUser); + TrampolineTestable.sActivatedFiles.append(otherUser, activateFile); + mTrampoline.setBackupServiceActive(UserHandle.USER_SYSTEM, true); + + mTrampoline.setBackupServiceActive(NON_USER_SYSTEM, true); + + assertTrue(mTrampoline.isBackupServiceActive(NON_USER_SYSTEM)); + assertFalse(mTrampoline.isBackupServiceActive(otherUser)); + activateFile.delete(); + } + + @Test public void dataChanged_calledBeforeInitialize_ignored() throws Exception { mTrampoline.dataChanged(PACKAGE_NAME); verifyNoMoreInteractions(mBackupManagerServiceMock); @@ -1123,7 +1185,7 @@ public class TrampolineTest { @Test public void requestBackup_forwardedToCallingUserId() throws Exception { TrampolineTestable.sCallingUserId = mUserId; - when(mBackupManagerServiceMock.requestBackup(NON_USER_SYSTEM, PACKAGE_NAMES, + when(mBackupManagerServiceMock.requestBackup(mUserId, PACKAGE_NAMES, mBackupObserverMock, mBackupManagerMonitorMock, 123)).thenReturn(456); mTrampoline.initializeService(); @@ -1227,50 +1289,33 @@ public class TrampolineTest { static int sCallingUserId = -1; static int sCallingUid = -1; static BackupManagerService sBackupManagerServiceMock = null; + static File sSuppressFile = null; + static SparseArray<File> sActivatedFiles = new SparseArray<>(); + static UserManager sUserManagerMock = null; private int mCreateServiceCallsCount = 0; - private SparseArray<FakeFile> mSuppressFiles = new SparseArray<>(); - - private static class FakeFile extends File { - private boolean mExists; - - FakeFile(String pathname) { - super(pathname); - } - - @Override - public boolean exists() { - return mExists; - } - - @Override - public boolean delete() { - mExists = false; - return true; - } - - @Override - public boolean createNewFile() throws IOException { - mExists = true; - return true; - } - } TrampolineTestable(Context context) { super(context); } @Override + protected UserManager getUserManager() { + return sUserManagerMock; + } + + @Override public boolean isBackupDisabled() { return sBackupDisabled; } @Override - public File getSuppressFileForUser(int userId) { - if (mSuppressFiles.get(userId) == null) { - FakeFile file = new FakeFile(Integer.toString(userId)); - mSuppressFiles.append(userId, file); - } - return mSuppressFiles.get(userId); + protected File getSuppressFileForSystemUser() { + return sSuppressFile; + } + + @Override + protected File getActivatedFileForNonSystemUser(int userId) { + return sActivatedFiles.get(userId); } protected int binderGetCallingUserId() { @@ -1289,11 +1334,6 @@ public class TrampolineTest { } @Override - protected void createBackupSuppressFileForUser(int userId) throws IOException { - getSuppressFileForUser(userId).createNewFile(); - } - - @Override protected void postToHandler(Runnable runnable) { runnable.run(); } |