[Multi-user] Add -user param to adb backup/restore

Add an optional parameter -user to provide ID of the user for which to
run backup/restore operation. Add robolectric test to verify the
new parameter is proccessed correctly.

Bug: 119908153
Test: 1) atest BackupTest
      2) atest BackupManagerServiceTest
      3) atest TrampolineTest
      4) atest GtsBackupTestCases
      5) atest GtsBackupHostTestCases
      6) Manual:
        - Run "adb backup -all" and verify that backup is successfull
        - Run "adb restore" and verify that restore is successfull
        - Run "adb backup -all -user 10" and verify that backup faield as
          it's only currently supported for system user
        - Run "adb restore -user 10" and verify that restore failed as it's
          only currently supported for system user

Change-Id: I6dbf9c87eedd5a72da0446beff7d2551f98f2654
This commit is contained in:
Ruslan Tkhakokhov
2018-12-03 17:28:39 +00:00
parent 1982ca78e3
commit a2fe6c5bb9
9 changed files with 273 additions and 53 deletions

View File

@@ -16,13 +16,17 @@
package com.android.commands.bu;
import android.annotation.UserIdInt;
import android.app.backup.IBackupManager;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.system.OsConstants;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import java.io.IOException;
import java.util.ArrayList;
@@ -33,35 +37,50 @@ public final class Backup {
int mNextArg;
IBackupManager mBackupManager;
@VisibleForTesting
Backup(IBackupManager backupManager) {
mBackupManager = backupManager;
}
Backup() {
mBackupManager = IBackupManager.Stub.asInterface(ServiceManager.getService("backup"));
}
public static void main(String[] args) {
Log.d(TAG, "Beginning: " + args[0]);
mArgs = args;
try {
new Backup().run();
new Backup().run(args);
} catch (Exception e) {
Log.e(TAG, "Error running backup/restore", e);
}
Log.d(TAG, "Finished.");
}
public void run() {
mBackupManager = IBackupManager.Stub.asInterface(ServiceManager.getService("backup"));
public void run(String[] args) {
if (mBackupManager == null) {
Log.e(TAG, "Can't obtain Backup Manager binder");
return;
}
Log.d(TAG, "Beginning: " + args[0]);
mArgs = args;
int userId = parseUserId();
if (!isBackupActiveForUser(userId)) {
Log.e(TAG, "BackupManager is not available for user " + userId);
return;
}
String arg = nextArg();
if (arg.equals("backup")) {
doBackup(OsConstants.STDOUT_FILENO);
doBackup(OsConstants.STDOUT_FILENO, userId);
} else if (arg.equals("restore")) {
doRestore(OsConstants.STDIN_FILENO);
doRestore(OsConstants.STDIN_FILENO, userId);
} else {
showUsage();
}
}
private void doBackup(int socketFd) {
private void doBackup(int socketFd, @UserIdInt int userId) {
ArrayList<String> packages = new ArrayList<String>();
boolean saveApks = false;
boolean saveObbs = false;
@@ -105,6 +124,10 @@ public final class Backup {
doKeyValue = true;
} else if ("-nokeyvalue".equals(arg)) {
doKeyValue = false;
} else if ("-user".equals(arg)) {
// User ID has been processed in run(), ignore the next argument.
nextArg();
continue;
} else {
Log.w(TAG, "Unknown backup flag " + arg);
continue;
@@ -128,7 +151,7 @@ public final class Backup {
try {
fd = ParcelFileDescriptor.adoptFd(socketFd);
String[] packArray = new String[packages.size()];
mBackupManager.adbBackup(fd, saveApks, saveObbs, saveShared, doWidgets, doEverything,
mBackupManager.adbBackup(userId, fd, saveApks, saveObbs, saveShared, doWidgets, doEverything,
allIncludesSystem, doCompress, doKeyValue, packages.toArray(packArray));
} catch (RemoteException e) {
Log.e(TAG, "Unable to invoke backup manager for backup");
@@ -143,12 +166,12 @@ public final class Backup {
}
}
private void doRestore(int socketFd) {
private void doRestore(int socketFd, @UserIdInt int userId) {
// No arguments to restore
ParcelFileDescriptor fd = null;
try {
fd = ParcelFileDescriptor.adoptFd(socketFd);
mBackupManager.adbRestore(fd);
mBackupManager.adbRestore(userId, fd);
} catch (RemoteException e) {
Log.e(TAG, "Unable to invoke backup manager for restore");
} finally {
@@ -160,11 +183,31 @@ public final class Backup {
}
}
private @UserIdInt int parseUserId() {
for (int argNumber = 0; argNumber < mArgs.length - 1; argNumber++) {
if ("-user".equals(mArgs[argNumber])) {
return UserHandle.parseUserArg(mArgs[argNumber + 1]);
}
}
return UserHandle.USER_SYSTEM;
}
private boolean isBackupActiveForUser(int userId) {
try {
return mBackupManager.isBackupServiceActive(userId);
} catch (RemoteException e) {
Log.e(TAG, "Could not access BackupManager: " + e.toString());
return false;
}
}
private static void showUsage() {
System.err.println(" backup [-f FILE] [-apk|-noapk] [-obb|-noobb] [-shared|-noshared] [-all]");
System.err.println(" [-system|-nosystem] [-keyvalue|-nokeyvalue] [PACKAGE...]");
System.err.println(" backup [-user USER_ID] [-f FILE] [-apk|-noapk] [-obb|-noobb] [-shared|-noshared]");
System.err.println(" [-all] [-system|-nosystem] [-keyvalue|-nokeyvalue] [PACKAGE...]");
System.err.println(" write an archive of the device's data to FILE [default=backup.adb]");
System.err.println(" package list optional if -all/-shared are supplied");
System.err.println(" -user: user ID for which to perform the operation (default - system user)");
System.err.println(" -apk/-noapk: do/don't back up .apk files (default -noapk)");
System.err.println(" -obb/-noobb: do/don't back up .obb files (default -noobb)");
System.err.println(" -shared|-noshared: do/don't back up shared storage (default -noshared)");
@@ -172,7 +215,8 @@ public final class Backup {
System.err.println(" -system|-nosystem: include system apps in -all (default -system)");
System.err.println(" -keyvalue|-nokeyvalue: include apps that perform key/value backups.");
System.err.println(" (default -nokeyvalue)");
System.err.println(" restore FILE restore device contents from FILE");
System.err.println(" restore [-user USER_ID] FILE restore device contents from FILE");
System.err.println(" -user: user ID for which to perform the operation (default - system user)");
}
private String nextArg() {

View File

@@ -189,8 +189,11 @@ interface IBackupManager {
* completed.
*
* <p>Callers must hold the android.permission.BACKUP permission to use this method.
* If the {@code userId} is different from the calling user id, then the caller must hold the
* android.permission.INTERACT_ACROSS_USERS_FULL permission.
*
* @param fd The file descriptor to which a 'tar' file stream is to be written
* @param userId User id for which backup should be performed.
* @param fd The file descriptor to which a 'tar' file stream is to be written.
* @param includeApks If <code>true</code>, the resulting tar stream will include the
* application .apk files themselves as well as their data.
* @param includeObbs If <code>true</code>, the resulting tar stream will include any
@@ -209,7 +212,7 @@ interface IBackupManager {
* @param packageNames The package names of the apps whose data (and optionally .apk files)
* are to be backed up. The <code>allApps</code> parameter supersedes this.
*/
void adbBackup(in ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs,
void adbBackup(int userId, in ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs,
boolean includeShared, boolean doWidgets, boolean allApps, boolean allIncludesSystem,
boolean doCompress, boolean doKeyValue, in String[] packageNames);
@@ -227,8 +230,12 @@ interface IBackupManager {
* Currently only used by the 'adb restore' command.
*
* <p>Callers must hold the android.permission.BACKUP permission to use this method.
* If the {@code userId} is different from the calling user id, then the caller must hold the
* android.permission.INTERACT_ACROSS_USERS_FULL.
*
* @param userId User id for which restore should be performed.
*/
void adbRestore(in ParcelFileDescriptor fd);
void adbRestore(int userId, in ParcelFileDescriptor fd);
/**
* Confirm that the requested full backup/restore operation can proceed. The system will

View File

@@ -477,6 +477,7 @@ public class BackupManagerService {
* requires on-screen confirmation by the user.
*/
public void adbBackup(
@UserIdInt int userId,
ParcelFileDescriptor fd,
boolean includeApks,
boolean includeObbs,
@@ -487,6 +488,8 @@ public class BackupManagerService {
boolean doCompress,
boolean doKeyValue,
String[] packageNames) {
enforceCallingPermissionOnUserId(userId, "adbBackup");
mUserBackupManagerService.adbBackup(
fd,
includeApks,
@@ -505,7 +508,9 @@ public class BackupManagerService {
* is synchronous and does not return to the caller until the restore has been completed. It
* requires on-screen confirmation by the user.
*/
public void adbRestore(ParcelFileDescriptor fd) {
public void adbRestore(@UserIdInt int userId, ParcelFileDescriptor fd) {
enforceCallingPermissionOnUserId(userId, "setBackupEnabled");
mUserBackupManagerService.adbRestore(fd);
}

View File

@@ -389,14 +389,13 @@ public class Trampoline extends IBackupManager.Stub {
backupNowForUser(binderGetCallingUserId());
}
@Override
public void adbBackup(ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs,
boolean includeShared, boolean doWidgets, boolean allApps,
boolean allIncludesSystem, boolean doCompress, boolean doKeyValue, String[] packageNames)
throws RemoteException {
public void adbBackup(@UserIdInt int userId, ParcelFileDescriptor fd,
boolean includeApks, boolean includeObbs, boolean includeShared, boolean doWidgets,
boolean allApps, boolean allIncludesSystem, boolean doCompress, boolean doKeyValue,
String[] packageNames) throws RemoteException {
BackupManagerService svc = mService;
if (svc != null) {
svc.adbBackup(fd, includeApks, includeObbs, includeShared, doWidgets,
svc.adbBackup(userId, fd, includeApks, includeObbs, includeShared, doWidgets,
allApps, allIncludesSystem, doCompress, doKeyValue, packageNames);
}
}
@@ -410,10 +409,10 @@ public class Trampoline extends IBackupManager.Stub {
}
@Override
public void adbRestore(ParcelFileDescriptor fd) throws RemoteException {
public void adbRestore(@UserIdInt int userId, ParcelFileDescriptor fd) throws RemoteException {
BackupManagerService svc = mService;
if (svc != null) {
svc.adbRestore(fd);
svc.adbRestore(userId, fd);
}
}

View File

@@ -37,6 +37,7 @@ import static com.android.server.backup.internal.BackupHandler.MSG_RUN_RESTORE;
import static com.android.server.backup.internal.BackupHandler.MSG_SCHEDULE_BACKUP_PACKAGE;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
import android.app.AlarmManager;
import android.app.AppGlobals;
@@ -2423,9 +2424,9 @@ public class UserBackupManagerService {
* return to the caller until the backup has been completed. It requires on-screen confirmation
* by the user.
*/
public void adbBackup(ParcelFileDescriptor fd, boolean includeApks, boolean includeObbs,
boolean includeShared, boolean doWidgets, boolean doAllApps, boolean includeSystem,
boolean compress, boolean doKeyValue, String[] pkgList) {
public void adbBackup(ParcelFileDescriptor fd, boolean includeApks,
boolean includeObbs, boolean includeShared, boolean doWidgets, boolean doAllApps,
boolean includeSystem, boolean compress, boolean doKeyValue, String[] pkgList) {
mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "adbBackup");
final int callingUserHandle = UserHandle.getCallingUserId();

View File

@@ -27,6 +27,7 @@ LOCAL_PRIVILEGED_MODULE := true
LOCAL_STATIC_JAVA_LIBRARIES := \
bmgrlib \
bu \
services.backup \
services.core \
services.net

View File

@@ -0,0 +1,61 @@
/*
* 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.commands.bu;
import static org.mockito.Mockito.verify;
import android.app.backup.IBackupManager;
import android.os.UserHandle;
import android.platform.test.annotations.Presubmit;
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.annotation.Config;
import org.robolectric.shadows.ShadowParcelFileDescriptor;
/** Unit tests for {@link com.android.commands.bu.Backup}. */
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {ShadowParcelFileDescriptor.class})
@Presubmit
public class AdbBackupTest {
@Mock private IBackupManager mBackupManager;
private Backup mBackup;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mBackup = new Backup(mBackupManager);
}
@Test
public void testRun_whenUserNotSpecified_callsAdbBackupAsSystemUser() throws Exception {
mBackup.run(new String[] {"backup", "-all"});
verify(mBackupManager).isBackupServiceActive(UserHandle.USER_SYSTEM);
}
@Test
public void testRun_whenUserSpecified_callsBackupManagerAsSpecifiedUser() throws Exception {
mBackup.run(new String[] {"backup", "-user", "10", "-all"});
verify(mBackupManager).isBackupServiceActive(10);
}
}

View File

@@ -65,6 +65,8 @@ public class BackupManagerServiceTest {
private static final String TEST_PACKAGE = "package";
private static final String TEST_TRANSPORT = "transport";
private static final String[] ADB_TEST_PACKAGES = {TEST_PACKAGE};
private static final int NON_USER_SYSTEM = UserHandle.USER_SYSTEM + 1;
private ShadowContextWrapper mShadowContext;
@@ -555,16 +557,47 @@ public class BackupManagerServiceTest {
verify(mUserBackupManagerService).hasBackupPassword();
}
/** Test that the backup service routes methods correctly to the user that requests it. */
/**
* Test verifying that {@link BackupManagerService#adbBackup(ParcelFileDescriptor, int, boolean,
* boolean, boolean, boolean, boolean, boolean, boolean, boolean, String[])} throws a
* {@link SecurityException} if the caller does not have INTERACT_ACROSS_USERS_FULL permission.
*/
@Test
public void testAdbBackup_callsAdbBackupForUser() throws Exception {
File testFile = new File(mContext.getFilesDir(), "test");
testFile.createNewFile();
ParcelFileDescriptor parcelFileDescriptor =
ParcelFileDescriptor.open(testFile, ParcelFileDescriptor.MODE_READ_WRITE);
String[] packages = {TEST_PACKAGE};
public void testAdbBackup_withoutPermission_throwsSecurityException() {
mShadowContext.denyPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
expectThrows(SecurityException.class,
() ->
mBackupManagerService.adbBackup(
/* userId */ mUserId,
/* parcelFileDescriptor*/ null,
/* includeApks */ true,
/* includeObbs */ true,
/* includeShared */ true,
/* doWidgets */ true,
/* doAllApps */ true,
/* includeSystem */ true,
/* doCompress */ true,
/* doKeyValue */ true,
null));
}
/**
* Test verifying that {@link BackupManagerService#adbBackup(ParcelFileDescriptor, int, boolean,
* boolean, boolean, boolean, boolean, boolean, boolean, boolean, String[])} does not require
* the caller to have INTERACT_ACROSS_USERS_FULL permission when the calling user id is the
* same as the target user id.
*/
@Test
public void testAdbBackup_whenCallingUserIsTargetUser_doesntNeedPermission() throws Exception {
ShadowBinder.setCallingUserHandle(UserHandle.of(mUserId));
mShadowContext.denyPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
ParcelFileDescriptor parcelFileDescriptor = getFileDescriptorForAdbTest();
mBackupManagerService.adbBackup(
/* userId */ mUserId,
parcelFileDescriptor,
/* includeApks */ true,
/* includeObbs */ true,
@@ -574,7 +607,7 @@ public class BackupManagerServiceTest {
/* includeSystem */ true,
/* doCompress */ true,
/* doKeyValue */ true,
packages);
ADB_TEST_PACKAGES);
verify(mUserBackupManagerService)
.adbBackup(
@@ -587,18 +620,82 @@ public class BackupManagerServiceTest {
/* includeSystem */ true,
/* doCompress */ true,
/* doKeyValue */ true,
packages);
ADB_TEST_PACKAGES);
}
/** Test that the backup service routes methods correctly to the user that requests it. */
@Test
public void testAdbBackup_callsAdbBackupForUser() throws Exception {
mShadowContext.grantPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
ParcelFileDescriptor parcelFileDescriptor = getFileDescriptorForAdbTest();
mBackupManagerService.adbBackup(
/* userId */ mUserId,
parcelFileDescriptor,
/* includeApks */ true,
/* includeObbs */ true,
/* includeShared */ true,
/* doWidgets */ true,
/* doAllApps */ true,
/* includeSystem */ true,
/* doCompress */ true,
/* doKeyValue */ true,
ADB_TEST_PACKAGES);
verify(mUserBackupManagerService)
.adbBackup(
parcelFileDescriptor,
/* includeApks */ true,
/* includeObbs */ true,
/* includeShared */ true,
/* doWidgets */ true,
/* doAllApps */ true,
/* includeSystem */ true,
/* doCompress */ true,
/* doKeyValue */ true,
ADB_TEST_PACKAGES);
}
/**
* Test verifying that {@link BackupManagerService#adbRestore(ParcelFileDescriptor, int)} throws
* a {@link SecurityException} if the caller does not have INTERACT_ACROSS_USERS_FULL
* permission.
*/
@Test
public void testAdbRestore_withoutPermission_throwsSecurityException() {
mShadowContext.denyPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
expectThrows(SecurityException.class,
() -> mBackupManagerService.adbRestore(mUserId, null));
}
/**
* Test verifying that {@link BackupManagerService#adbRestore(ParcelFileDescriptor, int)} does
* not require the caller to have INTERACT_ACROSS_USERS_FULL permission when the calling user id
* is the same as the target user id.
*/
@Test
public void testAdbRestore_whenCallingUserIsTargetUser_doesntNeedPermission() throws Exception {
ShadowBinder.setCallingUserHandle(UserHandle.of(mUserId));
mShadowContext.denyPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
ParcelFileDescriptor parcelFileDescriptor = getFileDescriptorForAdbTest();
mBackupManagerService.adbRestore(mUserId, parcelFileDescriptor);
verify(mUserBackupManagerService).adbRestore(parcelFileDescriptor);
}
/** Test that the backup service routes methods correctly to the user that requests it. */
@Test
public void testAdbRestore_callsAdbRestoreForUser() throws Exception {
File testFile = new File(mContext.getFilesDir(), "test");
testFile.createNewFile();
ParcelFileDescriptor parcelFileDescriptor =
ParcelFileDescriptor.open(testFile, ParcelFileDescriptor.MODE_READ_WRITE);
mShadowContext.grantPermissions(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
mBackupManagerService.adbRestore(parcelFileDescriptor);
ParcelFileDescriptor parcelFileDescriptor = getFileDescriptorForAdbTest();
mBackupManagerService.adbRestore(mUserId, parcelFileDescriptor);
verify(mUserBackupManagerService).adbRestore(parcelFileDescriptor);
}
@@ -638,4 +735,10 @@ public class BackupManagerServiceTest {
verify(mUserBackupManagerService).dump(fileDescriptor, printWriter, args);
}
private ParcelFileDescriptor getFileDescriptorForAdbTest() throws Exception {
File testFile = new File(mContext.getFilesDir(), "test");
testFile.createNewFile();
return ParcelFileDescriptor.open(testFile, ParcelFileDescriptor.MODE_READ_WRITE);
}
}

View File

@@ -487,8 +487,8 @@ public class TrampolineTest {
@Test
public void adbBackup_calledBeforeInitialize_ignored() throws RemoteException {
mTrampoline.adbBackup(mParcelFileDescriptorMock, true, true, true, true, true, true, true,
true,
mTrampoline.adbBackup(mUserId, mParcelFileDescriptorMock, true, true,
true, true, true, true, true, true,
PACKAGE_NAMES);
verifyNoMoreInteractions(mBackupManagerServiceMock);
}
@@ -496,12 +496,11 @@ public class TrampolineTest {
@Test
public void adbBackup_forwarded() throws RemoteException {
mTrampoline.initializeService(UserHandle.USER_SYSTEM);
mTrampoline.adbBackup(mParcelFileDescriptorMock, true, true, true, true, true, true, true,
true,
mTrampoline.adbBackup(mUserId, mParcelFileDescriptorMock, true, true,
true, true, true, true, true, true,
PACKAGE_NAMES);
verify(mBackupManagerServiceMock).adbBackup(mParcelFileDescriptorMock, true, true, true,
true,
true, true, true, true, PACKAGE_NAMES);
verify(mBackupManagerServiceMock).adbBackup(mUserId, mParcelFileDescriptorMock, true,
true, true, true, true, true, true, true, PACKAGE_NAMES);
}
@Test
@@ -519,15 +518,15 @@ public class TrampolineTest {
@Test
public void adbRestore_calledBeforeInitialize_ignored() throws RemoteException {
mTrampoline.adbRestore(mParcelFileDescriptorMock);
mTrampoline.adbRestore(mUserId, mParcelFileDescriptorMock);
verifyNoMoreInteractions(mBackupManagerServiceMock);
}
@Test
public void adbRestore_forwarded() throws RemoteException {
mTrampoline.initializeService(UserHandle.USER_SYSTEM);
mTrampoline.adbRestore(mParcelFileDescriptorMock);
verify(mBackupManagerServiceMock).adbRestore(mParcelFileDescriptorMock);
mTrampoline.adbRestore(mUserId, mParcelFileDescriptorMock);
verify(mBackupManagerServiceMock).adbRestore(mUserId, mParcelFileDescriptorMock);
}
@Test