import android.app.backup.IBackupObserver;
import android.app.backup.IRestoreObserver;
import android.app.backup.IRestoreSession;
-import android.app.backup.RestoreSet;
import android.app.backup.ISelectBackupTransportCallback;
+import android.app.backup.RestoreSet;
import android.content.ComponentName;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import com.android.internal.annotations.GuardedBy;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.CountDownLatch;
System.err.println(PM_NOT_RUNNING_ERR);
}
if (installedPackages != null) {
- List<String> packages = new ArrayList<>();
- for (PackageInfo pi : installedPackages) {
- try {
- if (mBmgr.isAppEligibleForBackup(pi.packageName)) {
- packages.add(pi.packageName);
- }
- } catch (RemoteException e) {
- System.err.println(e.toString());
- System.err.println(BMGR_NOT_RUNNING_ERR);
- }
+ String[] packages =
+ installedPackages.stream().map(p -> p.packageName).toArray(String[]::new);
+ String[] filteredPackages = {};
+ try {
+ filteredPackages = mBmgr.filterAppsEligibleForBackup(packages);
+ } catch (RemoteException e) {
+ System.err.println(e.toString());
+ System.err.println(BMGR_NOT_RUNNING_ERR);
}
- backupNowPackages(packages, nonIncrementalBackup);
+ backupNowPackages(Arrays.asList(filteredPackages), nonIncrementalBackup);
}
}
/**
* Ask the framework whether this app is eligible for backup.
*
+ * <p>If you are calling this method multiple times, you should instead use
+ * {@link #filterAppsEligibleForBackup(String[])} to save resources.
+ *
* <p>Callers must hold the android.permission.BACKUP permission to use this method.
*
* @param packageName The name of the package.
boolean isAppEligibleForBackup(String packageName);
/**
+ * Filter the packages that are eligible for backup and return the result.
+ *
+ * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+ *
+ * @param packages The list of packages to filter.
+ * @return The packages eligible for backup.
+ */
+ String[] filterAppsEligibleForBackup(in String[] packages);
+
+ /**
* Request an immediate backup, providing an observer to which results of the backup operation
* will be published. The Android backup system will decide for each package whether it will
* be full app data backup or key/value-pair-based backup.
boolean isAppEligibleForBackup(String packageName);
+ String[] filterAppsEligibleForBackup(String[] packages);
+
void dump(FileDescriptor fd, PrintWriter pw, String[] args);
IBackupManager getBackupManagerBinder();
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Random;
}
}
- // Supply the configuration summary string for the given transport. If the name is
- // not one of the available transports, or if the transport does not supply any
- // summary / destination string, the method can return null.
- //
- // This string is used VERBATIM as the summary text of the relevant Settings item!
+ /**
+ * Supply the current destination string for the given transport. If the name is not one of the
+ * registered transports the method will return null.
+ *
+ * <p>This string is used VERBATIM as the summary text of the relevant Settings item.
+ *
+ * @param transportName The name of the registered transport.
+ * @return The current destination string or null if the transport is not registered.
+ */
@Override
public String getDestinationString(String transportName) {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
- "getDestinationString");
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.BACKUP, "getDestinationString");
- final IBackupTransport transport = mTransportManager.getTransportBinder(transportName);
- if (transport != null) {
- try {
- final String text = transport.currentDestinationString();
- if (MORE_DEBUG) Slog.d(TAG, "getDestinationString() returning " + text);
- return text;
- } catch (Exception e) {
- /* fall through to return null */
- Slog.e(TAG, "Unable to get string from transport: " + e.getMessage());
+ try {
+ String string = mTransportManager.getTransportCurrentDestinationString(transportName);
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "getDestinationString() returning " + string);
}
+ return string;
+ } catch (TransportNotRegisteredException e) {
+ Slog.e(TAG, "Unable to get destination string from transport: " + e.getMessage());
+ return null;
}
-
- return null;
}
// Supply the manage-data intent for the given transport.
@Override
public boolean isAppEligibleForBackup(String packageName) {
- mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
- "isAppEligibleForBackup");
- try {
- PackageInfo packageInfo = mPackageManager.getPackageInfo(packageName,
- PackageManager.GET_SIGNATURES);
- if (!AppBackupUtils.appIsEligibleForBackup(packageInfo.applicationInfo,
- mPackageManager) ||
- AppBackupUtils.appIsStopped(packageInfo.applicationInfo) ||
- AppBackupUtils.appIsDisabled(packageInfo.applicationInfo, mPackageManager)) {
- return false;
- }
- IBackupTransport transport = mTransportManager.getCurrentTransportBinder();
- if (transport != null) {
- try {
- return transport.isAppEligibleForBackup(packageInfo,
- AppBackupUtils.appGetsFullBackup(packageInfo));
- } catch (Exception e) {
- Slog.e(TAG, "Unable to ask about eligibility: " + e.getMessage());
- }
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.BACKUP, "isAppEligibleForBackup");
+
+ String callerLogString = "BMS.isAppEligibleForBackup";
+ TransportClient transportClient =
+ mTransportManager.getCurrentTransportClient(callerLogString);
+ boolean eligible =
+ AppBackupUtils.appIsRunningAndEligibleForBackupWithTransport(
+ transportClient, packageName, mPackageManager);
+ if (transportClient != null) {
+ mTransportManager.disposeOfTransportClient(transportClient, callerLogString);
+ }
+ return eligible;
+ }
+
+ @Override
+ public String[] filterAppsEligibleForBackup(String[] packages) {
+ mContext.enforceCallingOrSelfPermission(
+ android.Manifest.permission.BACKUP, "filterAppsEligibleForBackup");
+
+ String callerLogString = "BMS.filterAppsEligibleForBackup";
+ TransportClient transportClient =
+ mTransportManager.getCurrentTransportClient(callerLogString);
+ List<String> eligibleApps = new LinkedList<>();
+ for (String packageName : packages) {
+ if (AppBackupUtils
+ .appIsRunningAndEligibleForBackupWithTransport(
+ transportClient, packageName, mPackageManager)) {
+ eligibleApps.add(packageName);
}
- // If transport is not present we couldn't tell that the package is not eligible.
- return true;
- } catch (NameNotFoundException e) {
- return false;
}
+ if (transportClient != null) {
+ mTransportManager.disposeOfTransportClient(transportClient, callerLogString);
+ }
+ return eligibleApps.toArray(new String[eligibleApps.size()]);
}
@Override
pw.println((t.equals(mTransportManager.getCurrentTransportName()) ? " * "
: " ") + t);
try {
- IBackupTransport transport = mTransportManager.getTransportBinder(t);
File dir = new File(mBaseStateDir,
mTransportManager.getTransportDirName(t));
- pw.println(" destination: " + transport.currentDestinationString());
+ pw.println(" destination: "
+ + mTransportManager.getTransportCurrentDestinationString(t));
pw.println(" intent: "
+ mTransportManager.getTransportConfigurationIntent(t));
for (File f : dir.listFiles()) {
}
@Override
+ public String[] filterAppsEligibleForBackup(String[] packages) {
+ BackupManagerServiceInterface svc = mService;
+ return (svc != null) ? svc.filterAppsEligibleForBackup(packages) : null;
+ }
+
+ @Override
public int requestBackup(String[] packages, IBackupObserver observer,
IBackupManagerMonitor monitor, int flags) throws RemoteException {
BackupManagerServiceInterface svc = mService;
}
/**
+ * Retrieve the current destination string of {@code transportName}.
+ * @throws TransportNotRegisteredException if the transport is not registered.
+ */
+ public String getTransportCurrentDestinationString(String transportName)
+ throws TransportNotRegisteredException {
+ synchronized (mTransportLock) {
+ return getRegisteredTransportDescriptionOrThrowLocked(transportName)
+ .currentDestinationString;
+ }
+ }
+
+ /**
* Retrieve the data management label of {@code transportName}.
* @throws TransportNotRegisteredException if the transport is not registered.
*/
import static com.android.server.backup.RefactoredBackupManagerService.SHARED_BACKUP_AGENT_PACKAGE;
import static com.android.server.backup.RefactoredBackupManagerService.TAG;
+import android.annotation.Nullable;
+import android.app.backup.BackupTransport;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Process;
import android.util.Slog;
+import com.android.internal.backup.IBackupTransport;
import com.android.internal.util.ArrayUtils;
+import com.android.server.backup.transport.TransportClient;
/**
* Utility methods wrapping operations on ApplicationInfo and PackageInfo.
return !appIsDisabled(app, pm);
}
+ /**
+ * Returns whether an app is eligible for backup at runtime. That is, the app has to:
+ * <ol>
+ * <li>Return true for {@link #appIsEligibleForBackup(ApplicationInfo, PackageManager)}
+ * <li>Return false for {@link #appIsStopped(ApplicationInfo)}
+ * <li>Return false for {@link #appIsDisabled(ApplicationInfo, PackageManager)}
+ * <li>Be eligible for the transport via
+ * {@link BackupTransport#isAppEligibleForBackup(PackageInfo, boolean)}
+ * </ol>
+ */
+ public static boolean appIsRunningAndEligibleForBackupWithTransport(
+ @Nullable TransportClient transportClient, String packageName, PackageManager pm) {
+ try {
+ PackageInfo packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
+ ApplicationInfo applicationInfo = packageInfo.applicationInfo;
+ if (!appIsEligibleForBackup(applicationInfo, pm)
+ || appIsStopped(applicationInfo)
+ || appIsDisabled(applicationInfo, pm)) {
+ return false;
+ }
+ if (transportClient != null) {
+ try {
+ IBackupTransport transport =
+ transportClient.connectOrThrow(
+ "AppBackupUtils.appIsEligibleForBackupAtRuntime");
+ return transport.isAppEligibleForBackup(
+ packageInfo, AppBackupUtils.appGetsFullBackup(packageInfo));
+ } catch (Exception e) {
+ Slog.e(TAG, "Unable to ask about eligibility: " + e.getMessage());
+ }
+ }
+ // If transport is not present we couldn't tell that the package is not eligible.
+ return true;
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ }
+
/** Avoid backups of 'disabled' apps. */
public static boolean appIsDisabled(ApplicationInfo app, PackageManager pm) {
switch (pm.getApplicationEnabledSetting(app.packageName)) {
--- /dev/null
+/*
+ * 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;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.expectThrows;
+
+import android.content.ContextWrapper;
+import android.os.HandlerThread;
+import android.platform.test.annotations.Presubmit;
+
+import com.android.server.backup.testing.ShadowAppBackupUtils;
+import com.android.server.backup.testing.TransportTestUtils;
+import com.android.server.backup.testing.TransportTestUtils.TransportData;
+import com.android.server.backup.transport.TransportNotRegisteredException;
+import com.android.server.testing.FrameworkRobolectricTestRunner;
+import com.android.server.testing.SystemLoaderClasses;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowContextWrapper;
+import org.robolectric.shadows.ShadowLog;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+@RunWith(FrameworkRobolectricTestRunner.class)
+@Config(
+ manifest = Config.NONE,
+ sdk = 26,
+ shadows = {ShadowAppBackupUtils.class}
+)
+@SystemLoaderClasses({RefactoredBackupManagerService.class, TransportManager.class})
+@Presubmit
+public class BackupManagerServiceRoboTest {
+ private static final String TAG = "BMSTest";
+ private static final String TRANSPORT_NAME =
+ "com.google.android.gms/.backup.BackupTransportService";
+
+ @Mock private TransportManager mTransportManager;
+ private HandlerThread mBackupThread;
+ private File mBaseStateDir;
+ private File mDataDir;
+ private RefactoredBackupManagerService mBackupManagerService;
+ private ShadowContextWrapper mShadowContext;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mBackupThread = new HandlerThread("backup-test");
+ mBackupThread.setUncaughtExceptionHandler(
+ (t, e) -> ShadowLog.e(TAG, "Uncaught exception in test thread " + t.getName(), e));
+ mBackupThread.start();
+
+ ContextWrapper context = RuntimeEnvironment.application;
+ mShadowContext = Shadows.shadowOf(context);
+
+ File cacheDir = context.getCacheDir();
+ mBaseStateDir = new File(cacheDir, "base_state_dir");
+ mDataDir = new File(cacheDir, "data_dir");
+
+ mBackupManagerService =
+ new RefactoredBackupManagerService(
+ context,
+ new Trampoline(context),
+ mBackupThread,
+ mBaseStateDir,
+ mDataDir,
+ mTransportManager);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ mBackupThread.quit();
+ ShadowAppBackupUtils.reset();
+ }
+
+ @Test
+ public void testDestinationString() throws Exception {
+ mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
+ when(mTransportManager.getTransportCurrentDestinationString(eq(TRANSPORT_NAME)))
+ .thenReturn("destinationString");
+
+ String destination = mBackupManagerService.getDestinationString(TRANSPORT_NAME);
+
+ assertThat(destination).isEqualTo("destinationString");
+ }
+
+ @Test
+ public void testDestinationString_whenTransportNotRegistered() throws Exception {
+ mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
+ when(mTransportManager.getTransportCurrentDestinationString(eq(TRANSPORT_NAME)))
+ .thenThrow(TransportNotRegisteredException.class);
+
+ String destination = mBackupManagerService.getDestinationString(TRANSPORT_NAME);
+
+ assertThat(destination).isNull();
+ }
+
+ @Test
+ public void testDestinationString_withoutPermission() throws Exception {
+ mShadowContext.denyPermissions(android.Manifest.permission.BACKUP);
+ when(mTransportManager.getTransportCurrentDestinationString(eq(TRANSPORT_NAME)))
+ .thenThrow(TransportNotRegisteredException.class);
+
+ expectThrows(
+ SecurityException.class,
+ () -> mBackupManagerService.getDestinationString(TRANSPORT_NAME));
+ }
+
+ @Test
+ public void testIsAppEligibleForBackup_whenAppEligible() throws Exception {
+ mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
+ TransportData transport =
+ TransportTestUtils.setUpCurrentTransport(mTransportManager, TRANSPORT_NAME);
+ ShadowAppBackupUtils.sAppIsRunningAndEligibleForBackupWithTransport = p -> true;
+
+ boolean result = mBackupManagerService.isAppEligibleForBackup("app.package");
+
+ assertThat(result).isTrue();
+ verify(mTransportManager)
+ .disposeOfTransportClient(eq(transport.transportClientMock), any());
+ }
+
+ @Test
+ public void testIsAppEligibleForBackup_whenAppNotEligible() throws Exception {
+ mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
+ TransportTestUtils.setUpCurrentTransport(mTransportManager, TRANSPORT_NAME);
+ ShadowAppBackupUtils.sAppIsRunningAndEligibleForBackupWithTransport = p -> false;
+
+ boolean result = mBackupManagerService.isAppEligibleForBackup("app.package");
+
+ assertThat(result).isFalse();
+ }
+
+ @Test
+ public void testIsAppEligibleForBackup_withoutPermission() throws Exception {
+ mShadowContext.denyPermissions(android.Manifest.permission.BACKUP);
+ TransportTestUtils.setUpCurrentTransport(mTransportManager, TRANSPORT_NAME);
+
+ expectThrows(
+ SecurityException.class,
+ () -> mBackupManagerService.isAppEligibleForBackup("app.package"));
+ }
+
+ @Test
+ public void testFilterAppsEligibleForBackup() throws Exception {
+ mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
+ TransportData transport =
+ TransportTestUtils.setUpCurrentTransport(mTransportManager, TRANSPORT_NAME);
+ Map<String, Boolean> packagesMap = new HashMap<>();
+ packagesMap.put("package.a", true);
+ packagesMap.put("package.b", false);
+ ShadowAppBackupUtils.sAppIsRunningAndEligibleForBackupWithTransport = packagesMap::get;
+ String[] packages = packagesMap.keySet().toArray(new String[packagesMap.size()]);
+
+ String[] filtered = mBackupManagerService.filterAppsEligibleForBackup(packages);
+
+ assertThat(filtered).asList().containsExactly("package.a");
+ verify(mTransportManager)
+ .disposeOfTransportClient(eq(transport.transportClientMock), any());
+ }
+
+ @Test
+ public void testFilterAppsEligibleForBackup_whenNoneIsEligible() throws Exception {
+ mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
+ ShadowAppBackupUtils.sAppIsRunningAndEligibleForBackupWithTransport = p -> false;
+
+ String[] filtered =
+ mBackupManagerService.filterAppsEligibleForBackup(
+ new String[] {"package.a", "package.b"});
+
+ assertThat(filtered).isEmpty();
+ }
+
+ @Test
+ public void testFilterAppsEligibleForBackup_withoutPermission() throws Exception {
+ mShadowContext.denyPermissions(android.Manifest.permission.BACKUP);
+ TransportTestUtils.setUpCurrentTransport(mTransportManager, TRANSPORT_NAME);
+
+ expectThrows(
+ SecurityException.class,
+ () ->
+ mBackupManagerService.filterAppsEligibleForBackup(
+ new String[] {"package.a", "package.b"}));
+ }
+}
import static android.app.backup.BackupTransport.TRANSPORT_ERROR;
import static android.app.backup.BackupTransport.TRANSPORT_OK;
+import static com.android.server.backup.testing.TransportTestUtils.TRANSPORT_NAME;
+import static com.android.server.backup.testing.TransportTestUtils.TRANSPORT_NAMES;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.annotation.Nullable;
import android.app.AlarmManager;
import android.app.Application;
import android.app.PendingIntent;
import com.android.internal.backup.IBackupTransport;
import com.android.server.backup.RefactoredBackupManagerService;
import com.android.server.backup.TransportManager;
+import com.android.server.backup.testing.TransportTestUtils;
+import com.android.server.backup.testing.TransportTestUtils.TransportData;
import com.android.server.backup.transport.TransportClient;
-import com.android.server.backup.transport.TransportNotAvailableException;
import com.android.server.testing.FrameworkRobolectricTestRunner;
import com.android.server.testing.SystemLoaderClasses;
import org.robolectric.annotation.Config;
import java.io.File;
-import java.util.Arrays;
import java.util.List;
@RunWith(FrameworkRobolectricTestRunner.class)
@SystemLoaderClasses({PerformInitializeTaskTest.class, TransportManager.class})
@Presubmit
public class PerformInitializeTaskTest {
- private static final String[] TRANSPORT_NAMES = {
- "android/com.android.internal.backup.LocalTransport",
- "com.google.android.gms/.backup.migrate.service.D2dTransport",
- "com.google.android.gms/.backup.BackupTransportService"
- };
-
- private static final String TRANSPORT_NAME = TRANSPORT_NAMES[0];
-
@Mock private RefactoredBackupManagerService mBackupManagerService;
@Mock private TransportManager mTransportManager;
@Mock private OnTaskFinishedListener mListener;
performInitializeTask.run();
verify(mBackupManagerService)
- .recordInitPending(false, TRANSPORT_NAME, dirName(TRANSPORT_NAME));
+ .recordInitPending(
+ false, TRANSPORT_NAME, TransportTestUtils.dirName(TRANSPORT_NAME));
verify(mBackupManagerService)
- .resetBackupState(eq(new File(mBaseStateDir, dirName(TRANSPORT_NAME))));
+ .resetBackupState(
+ eq(new File(mBaseStateDir, TransportTestUtils.dirName(TRANSPORT_NAME))));
}
@Test
verify(mTransport).initializeDevice();
verify(mTransport, never()).finishBackup();
verify(mBackupManagerService)
- .recordInitPending(true, TRANSPORT_NAME, dirName(TRANSPORT_NAME));
+ .recordInitPending(
+ true, TRANSPORT_NAME, TransportTestUtils.dirName(TRANSPORT_NAME));
}
@Test
verify(mTransport).initializeDevice();
verify(mTransport).finishBackup();
verify(mBackupManagerService)
- .recordInitPending(true, TRANSPORT_NAME, dirName(TRANSPORT_NAME));
+ .recordInitPending(
+ true, TRANSPORT_NAME, TransportTestUtils.dirName(TRANSPORT_NAME));
}
@Test
@Test
public void testRun_whenOnlyOneTransportFails() throws Exception {
- List<TransportData> transports = setUpTransports(TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]);
+ List<TransportData> transports =
+ TransportTestUtils.setUpTransports(
+ mTransportManager, TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]);
configureTransport(transports.get(0).transportMock, TRANSPORT_ERROR, 0);
configureTransport(transports.get(1).transportMock, TRANSPORT_OK, TRANSPORT_OK);
PerformInitializeTask performInitializeTask =
@Test
public void testRun_withMultipleTransports() throws Exception {
- List<TransportData> transports = setUpTransports(TRANSPORT_NAMES);
+ List<TransportData> transports =
+ TransportTestUtils.setUpTransports(mTransportManager, TRANSPORT_NAMES);
configureTransport(transports.get(0).transportMock, TRANSPORT_OK, TRANSPORT_OK);
configureTransport(transports.get(1).transportMock, TRANSPORT_OK, TRANSPORT_OK);
configureTransport(transports.get(2).transportMock, TRANSPORT_OK, TRANSPORT_OK);
@Test
public void testRun_whenOnlyOneTransportFails_disposesAllTransports() throws Exception {
- List<TransportData> transports = setUpTransports(TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]);
+ List<TransportData> transports =
+ TransportTestUtils.setUpTransports(
+ mTransportManager, TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]);
configureTransport(transports.get(0).transportMock, TRANSPORT_ERROR, 0);
configureTransport(transports.get(1).transportMock, TRANSPORT_OK, TRANSPORT_OK);
PerformInitializeTask performInitializeTask =
@Test
public void testRun_whenTransportNotRegistered() throws Exception {
- setUpTransport(new TransportData(TRANSPORT_NAME, null, null));
+ TransportTestUtils.setUpTransports(
+ mTransportManager, new TransportData(TRANSPORT_NAME, null, null));
+
PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
performInitializeTask.run();
@Test
public void testRun_whenOnlyOneTransportNotRegistered() throws Exception {
List<TransportData> transports =
- setUpTransports(
+ TransportTestUtils.setUpTransports(
+ mTransportManager,
new TransportData(TRANSPORT_NAMES[0], null, null),
new TransportData(TRANSPORT_NAMES[1]));
String registeredTransportName = transports.get(1).transportName;
@Test
public void testRun_whenTransportNotAvailable() throws Exception {
TransportClient transportClient = mock(TransportClient.class);
- setUpTransport(new TransportData(TRANSPORT_NAME, null, transportClient));
+ TransportTestUtils.setUpTransports(
+ mTransportManager, new TransportData(TRANSPORT_NAME, null, transportClient));
PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
performInitializeTask.run();
@Test
public void testRun_whenTransportThrowsDeadObjectException() throws Exception {
TransportClient transportClient = mock(TransportClient.class);
- setUpTransport(new TransportData(TRANSPORT_NAME, mTransport, transportClient));
+ TransportTestUtils.setUpTransports(
+ mTransportManager, new TransportData(TRANSPORT_NAME, mTransport, transportClient));
when(mTransport.initializeDevice()).thenThrow(DeadObjectException.class);
PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
when(transportMock.finishBackup()).thenReturn(finishBackupStatus);
}
- private List<TransportData> setUpTransports(String... transportNames) throws Exception {
- return setUpTransports(
- Arrays.stream(transportNames)
- .map(TransportData::new)
- .toArray(TransportData[]::new));
- }
-
- /** @see #setUpTransport(TransportData) */
- private List<TransportData> setUpTransports(TransportData... transports) throws Exception {
- for (TransportData transport : transports) {
- setUpTransport(transport);
- }
- return Arrays.asList(transports);
- }
-
private void setUpTransport(String transportName) throws Exception {
- setUpTransport(new TransportData(transportName, mTransport, mock(TransportClient.class)));
- }
-
- /**
- * Configures transport according to {@link TransportData}:
- *
- * <ul>
- * <li>{@link TransportData#transportMock} {@code null} means {@link
- * TransportClient#connectOrThrow(String)} throws {@link TransportNotAvailableException}.
- * <li>{@link TransportData#transportClientMock} {@code null} means {@link
- * TransportManager#getTransportClient(String, String)} returns {@code null}.
- * </ul>
- */
- private void setUpTransport(TransportData transport) throws Exception {
- String transportName = transport.transportName;
- String transportDirName = dirName(transportName);
- IBackupTransport transportMock = transport.transportMock;
- TransportClient transportClientMock = transport.transportClientMock;
-
- if (transportMock != null) {
- when(transportMock.name()).thenReturn(transportName);
- when(transportMock.transportDirName()).thenReturn(transportDirName);
- }
-
- if (transportClientMock != null) {
- when(transportClientMock.getTransportDirName()).thenReturn(transportDirName);
- if (transportMock != null) {
- when(transportClientMock.connectOrThrow(any())).thenReturn(transportMock);
- } else {
- when(transportClientMock.connectOrThrow(any()))
- .thenThrow(TransportNotAvailableException.class);
- }
- }
-
- when(mTransportManager.getTransportClient(eq(transportName), any()))
- .thenReturn(transportClientMock);
- }
-
- private String dirName(String transportName) {
- return transportName + "_dir_name";
- }
-
- private static class TransportData {
- private final String transportName;
- @Nullable private final IBackupTransport transportMock;
- @Nullable private final TransportClient transportClientMock;
-
- private TransportData(
- String transportName,
- @Nullable IBackupTransport transportMock,
- @Nullable TransportClient transportClientMock) {
- this.transportName = transportName;
- this.transportMock = transportMock;
- this.transportClientMock = transportClientMock;
- }
-
- private TransportData(String transportName) {
- this(transportName, mock(IBackupTransport.class), mock(TransportClient.class));
- }
+ TransportTestUtils.setUpTransport(
+ mTransportManager,
+ new TransportData(transportName, mTransport, mock(TransportClient.class)));
}
}
--- /dev/null
+/*
+ * 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.testing;
+
+import android.annotation.Nullable;
+import android.content.pm.PackageManager;
+
+import com.android.server.backup.transport.TransportClient;
+import com.android.server.backup.utils.AppBackupUtils;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+import java.util.function.Function;
+
+@Implements(AppBackupUtils.class)
+public class ShadowAppBackupUtils {
+ public static Function<String, Boolean> sAppIsRunningAndEligibleForBackupWithTransport;
+ static {
+ reset();
+ }
+
+ @Implementation
+ public static boolean appIsRunningAndEligibleForBackupWithTransport(
+ @Nullable TransportClient transportClient, String packageName, PackageManager pm) {
+ return sAppIsRunningAndEligibleForBackupWithTransport.apply(packageName);
+ }
+
+ public static void reset() {
+ sAppIsRunningAndEligibleForBackupWithTransport = p -> true;
+ }
+}
--- /dev/null
+/*
+ * 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.testing;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.annotation.Nullable;
+
+import com.android.internal.backup.IBackupTransport;
+import com.android.server.backup.TransportManager;
+import com.android.server.backup.transport.TransportClient;
+import com.android.server.backup.transport.TransportNotAvailableException;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class TransportTestUtils {
+ public static final String[] TRANSPORT_NAMES = {
+ "android/com.android.internal.backup.LocalTransport",
+ "com.google.android.gms/.backup.migrate.service.D2dTransport",
+ "com.google.android.gms/.backup.BackupTransportService"
+ };
+
+ public static final String TRANSPORT_NAME = TRANSPORT_NAMES[0];
+
+ public static TransportData setUpCurrentTransport(
+ TransportManager transportManager, String transportName) throws Exception {
+ TransportData transport = setUpTransports(transportManager, transportName).get(0);
+ when(transportManager.getCurrentTransportClient(any()))
+ .thenReturn(transport.transportClientMock);
+ return transport;
+ }
+
+ public static List<TransportData> setUpTransports(
+ TransportManager transportManager, String... transportNames) throws Exception {
+ return setUpTransports(
+ transportManager,
+ Arrays.stream(transportNames)
+ .map(TransportData::new)
+ .toArray(TransportData[]::new));
+ }
+
+ /** @see #setUpTransport(TransportManager, TransportData) */
+ public static List<TransportData> setUpTransports(
+ TransportManager transportManager, TransportData... transports) throws Exception {
+ for (TransportData transport : transports) {
+ setUpTransport(transportManager, transport);
+ }
+ return Arrays.asList(transports);
+ }
+
+ /**
+ * Configures transport according to {@link TransportData}:
+ *
+ * <ul>
+ * <li>{@link TransportData#transportMock} {@code null} means {@link
+ * TransportClient#connectOrThrow(String)} throws {@link TransportNotAvailableException}.
+ * <li>{@link TransportData#transportClientMock} {@code null} means {@link
+ * TransportManager#getTransportClient(String, String)} returns {@code null}.
+ * </ul>
+ */
+ public static void setUpTransport(TransportManager transportManager, TransportData transport)
+ throws Exception {
+ String transportName = transport.transportName;
+ String transportDirName = dirName(transportName);
+ IBackupTransport transportMock = transport.transportMock;
+ TransportClient transportClientMock = transport.transportClientMock;
+
+ if (transportMock != null) {
+ when(transportMock.name()).thenReturn(transportName);
+ when(transportMock.transportDirName()).thenReturn(transportDirName);
+ }
+
+ if (transportClientMock != null) {
+ when(transportClientMock.getTransportDirName()).thenReturn(transportDirName);
+ if (transportMock != null) {
+ when(transportClientMock.connectOrThrow(any())).thenReturn(transportMock);
+ } else {
+ when(transportClientMock.connectOrThrow(any()))
+ .thenThrow(TransportNotAvailableException.class);
+ }
+ }
+
+ when(transportManager.getTransportClient(eq(transportName), any()))
+ .thenReturn(transportClientMock);
+ }
+
+ public static String dirName(String transportName) {
+ return transportName + "_dir_name";
+ }
+
+ public static class TransportData {
+ public final String transportName;
+ @Nullable public final IBackupTransport transportMock;
+ @Nullable public final TransportClient transportClientMock;
+
+ public TransportData(
+ String transportName,
+ @Nullable IBackupTransport transportMock,
+ @Nullable TransportClient transportClientMock) {
+ this.transportName = transportName;
+ this.transportMock = transportMock;
+ this.transportClientMock = transportClientMock;
+ }
+
+ public TransportData(String transportName) {
+ this(transportName, mock(IBackupTransport.class), mock(TransportClient.class));
+ }
+ }
+
+ private TransportTestUtils() {}
+}
@SmallTest
@Presubmit
@RunWith(AndroidJUnit4.class)
+// TODO: Migrate this to Robolectric and merge with BackupManagerServiceRoboTest (and remove 'Robo')
public class BackupManagerServiceTest {
private static final String TAG = "BMSTest";
private static final ComponentName TRANSPORT_COMPONENT =