OSDN Git Service

Binding on-demand #8: Miscellaneous usages
authorBernardo Rufino <brufino@google.com>
Tue, 2 Jan 2018 16:01:53 +0000 (16:01 +0000)
committerBernardo Rufino <brufino@google.com>
Fri, 5 Jan 2018 16:33:33 +0000 (16:33 +0000)
Migrate usages of the transport binder to binding on-demand:
* getDestinationString()
* isAppEligibleForBackup()
* dump()

For getDestinationString() we'll be introducing an invisible bug for
people that haven't updated GMSCore to include the usage of
updateTransportAttributes() API introduced in earlier CL. The bug is
that that text won't change, it'll remain constant. It's invisible
because currently only place that uses that method is Settings in some
circumstances that depend on the transport, and those circunstances
don't happen with our transports. Check http://ag/1831025.

For isAppEligibleForBackup(), a new filterAppsEligibleForBackup() is
created and there we bind on-demand.

Change-Id: Idc9e31f0e8eda8531e204c05a84fafdaf0247d08
Ref: http://go/br-binding-on-demand
Bug: 17140907
Test: adb shell dumpsys backup, observe destination of transports
Test: adb shell bmgr backupnow --all, observe only eligible apps got backed-up
Test: Force-loaded settings screen and observed destination string
Test: m -j RunFrameworksServicesRoboTests

12 files changed:
cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
core/java/android/app/backup/IBackupManager.aidl
services/backup/java/com/android/server/backup/BackupManagerServiceInterface.java
services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
services/backup/java/com/android/server/backup/Trampoline.java
services/backup/java/com/android/server/backup/TransportManager.java
services/backup/java/com/android/server/backup/utils/AppBackupUtils.java
services/robotests/src/com/android/server/backup/BackupManagerServiceRoboTest.java [new file with mode: 0644]
services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java
services/robotests/src/com/android/server/backup/testing/ShadowAppBackupUtils.java [new file with mode: 0644]
services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java [new file with mode: 0644]
services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java

index b61cdec..e87a78e 100644 (file)
@@ -23,8 +23,8 @@ import android.app.backup.IBackupManager;
 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;
@@ -37,6 +37,7 @@ import android.util.ArraySet;
 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;
@@ -339,18 +340,16 @@ public final class Bmgr {
             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);
         }
     }
 
index c42a898..792cb5f 100644 (file)
@@ -402,6 +402,9 @@ interface IBackupManager {
     /**
      * 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.
@@ -410,6 +413,16 @@ interface IBackupManager {
     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.
index 86462d8..7b021c6 100644 (file)
@@ -186,6 +186,8 @@ public interface BackupManagerServiceInterface {
 
   boolean isAppEligibleForBackup(String packageName);
 
+  String[] filterAppsEligibleForBackup(String[] packages);
+
   void dump(FileDescriptor fd, PrintWriter pw, String[] args);
 
   IBackupManager getBackupManagerBinder();
index 3a37459..6024e38 100644 (file)
@@ -148,6 +148,7 @@ import java.util.Collections;
 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;
@@ -3093,29 +3094,30 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
         }
     }
 
-    // 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.
@@ -3385,31 +3387,41 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
 
     @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
@@ -3473,10 +3485,10 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
                     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()) {
index 94a2627..a628c9d 100644 (file)
@@ -452,6 +452,12 @@ public class Trampoline extends IBackupManager.Stub {
     }
 
     @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;
index fbdb183..58270a6 100644 (file)
@@ -265,6 +265,18 @@ public class TransportManager {
     }
 
     /**
+     * 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.
      */
index d7cac77..dbf1a82 100644 (file)
@@ -20,6 +20,8 @@ import static com.android.server.backup.RefactoredBackupManagerService.MORE_DEBU
 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;
@@ -27,7 +29,9 @@ import android.content.pm.Signature;
 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.
@@ -71,6 +75,44 @@ public class AppBackupUtils {
         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)) {
diff --git a/services/robotests/src/com/android/server/backup/BackupManagerServiceRoboTest.java b/services/robotests/src/com/android/server/backup/BackupManagerServiceRoboTest.java
new file mode 100644 (file)
index 0000000..d9e8f0f
--- /dev/null
@@ -0,0 +1,215 @@
+/*
+ * 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"}));
+    }
+}
index 73f1c2f..a5bff3e 100644 (file)
@@ -19,6 +19,9 @@ package com.android.server.backup.internal;
 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;
@@ -30,7 +33,6 @@ import static org.mockito.Mockito.never;
 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;
@@ -41,8 +43,9 @@ import android.platform.test.annotations.Presubmit;
 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;
 
@@ -55,7 +58,6 @@ import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
 
 import java.io.File;
-import java.util.Arrays;
 import java.util.List;
 
 @RunWith(FrameworkRobolectricTestRunner.class)
@@ -63,14 +65,6 @@ import java.util.List;
 @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;
@@ -113,9 +107,11 @@ public class PerformInitializeTaskTest {
         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
@@ -142,7 +138,8 @@ public class PerformInitializeTaskTest {
         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
@@ -181,7 +178,8 @@ public class PerformInitializeTaskTest {
         verify(mTransport).initializeDevice();
         verify(mTransport).finishBackup();
         verify(mBackupManagerService)
-                .recordInitPending(true, TRANSPORT_NAME, dirName(TRANSPORT_NAME));
+                .recordInitPending(
+                        true, TRANSPORT_NAME, TransportTestUtils.dirName(TRANSPORT_NAME));
     }
 
     @Test
@@ -210,7 +208,9 @@ public class PerformInitializeTaskTest {
 
     @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 =
@@ -226,7 +226,8 @@ public class PerformInitializeTaskTest {
 
     @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);
@@ -243,7 +244,9 @@ public class PerformInitializeTaskTest {
 
     @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 =
@@ -259,7 +262,9 @@ public class PerformInitializeTaskTest {
 
     @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();
@@ -272,7 +277,8 @@ public class PerformInitializeTaskTest {
     @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;
@@ -291,7 +297,8 @@ public class PerformInitializeTaskTest {
     @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();
@@ -304,7 +311,8 @@ public class PerformInitializeTaskTest {
     @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);
 
@@ -332,80 +340,9 @@ public class PerformInitializeTaskTest {
         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)));
     }
 }
diff --git a/services/robotests/src/com/android/server/backup/testing/ShadowAppBackupUtils.java b/services/robotests/src/com/android/server/backup/testing/ShadowAppBackupUtils.java
new file mode 100644 (file)
index 0000000..73cb4c0
--- /dev/null
@@ -0,0 +1,46 @@
+/*
+ * 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;
+    }
+}
diff --git a/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java b/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java
new file mode 100644 (file)
index 0000000..b784159
--- /dev/null
@@ -0,0 +1,129 @@
+/*
+ * 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() {}
+}
index f4c5442..766d30d 100644 (file)
@@ -48,6 +48,7 @@ import java.io.File;
 @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 =