diff --git a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java index 03591a812bddd..350d7af78a4ca 100644 --- a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java @@ -207,6 +207,10 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter public static final String BACKUP_FINISHED_ACTION = "android.intent.action.BACKUP_FINISHED"; public static final String BACKUP_FINISHED_PACKAGE_EXTRA = "packageName"; + // Time delay for initialization operations that can be delayed so as not to consume too much CPU + // on bring-up and increase time-to-UI. + private static final long INITIALIZATION_DELAY_MILLIS = 3000; + // Timeout interval for deciding that a bind or clear-data has taken too long private static final long TIMEOUT_INTERVAL = 10 * 1000; @@ -282,6 +286,9 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter private final BackupPasswordManager mBackupPasswordManager; + // Time when we post the transport registration operation + private final long mRegisterTransportsRequestedTime; + @GuardedBy("mPendingRestores") private boolean mIsRestoreInProgress; @GuardedBy("mPendingRestores") @@ -735,6 +742,9 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter // Set up our transport options and initialize the default transport SystemConfig systemConfig = SystemConfig.getInstance(); Set transportWhitelist = systemConfig.getBackupTransportWhitelist(); + if (transportWhitelist == null) { + transportWhitelist = Collections.emptySet(); + } String transport = Settings.Secure.getString( @@ -749,8 +759,7 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter new TransportManager( context, transportWhitelist, - transport, - backupThread.getLooper()); + transport); // If encrypted file systems is enabled or disabled, this call will return the // correct directory. @@ -852,12 +861,14 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter } mTransportManager = transportManager; - mTransportManager.setTransportBoundListener(mTransportBoundListener); - mTransportManager.registerAllTransports(); + mTransportManager.setOnTransportRegisteredListener(this::onTransportRegistered); + mRegisterTransportsRequestedTime = SystemClock.elapsedRealtime(); + mBackupHandler.postDelayed( + mTransportManager::registerTransports, INITIALIZATION_DELAY_MILLIS); - // Now that we know about valid backup participants, parse any - // leftover journal files into the pending backup set - mBackupHandler.post(this::parseLeftoverJournals); + // Now that we know about valid backup participants, parse any leftover journal files into + // the pending backup set + mBackupHandler.postDelayed(this::parseLeftoverJournals, INITIALIZATION_DELAY_MILLIS); // Power management mWakelock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*backup*"); @@ -1151,39 +1162,28 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter } } - private TransportManager.TransportBoundListener mTransportBoundListener = - new TransportManager.TransportBoundListener() { - @Override - public boolean onTransportBound(IBackupTransport transport) { - // If the init sentinel file exists, we need to be sure to perform the init - // as soon as practical. We also create the state directory at registration - // time to ensure it's present from the outset. - String name = null; - try { - name = transport.name(); - String transportDirName = transport.transportDirName(); - File stateDir = new File(mBaseStateDir, transportDirName); - stateDir.mkdirs(); + private void onTransportRegistered(String transportName, String transportDirName) { + if (DEBUG) { + long timeMs = SystemClock.elapsedRealtime() - mRegisterTransportsRequestedTime; + Slog.d(TAG, "Transport " + transportName + " registered " + timeMs + + "ms after first request (delay = " + INITIALIZATION_DELAY_MILLIS + "ms)"); + } - File initSentinel = new File(stateDir, INIT_SENTINEL_FILE_NAME); - if (initSentinel.exists()) { - synchronized (mQueueLock) { - mPendingInits.add(name); + File stateDir = new File(mBaseStateDir, transportDirName); + stateDir.mkdirs(); - // TODO: pick a better starting time than now + 1 minute - long delay = 1000 * 60; // one minute, in milliseconds - mAlarmManager.set(AlarmManager.RTC_WAKEUP, - System.currentTimeMillis() + delay, mRunInitIntent); - } - } - return true; - } catch (Exception e) { - // the transport threw when asked its file naming prefs; declare it invalid - Slog.w(TAG, "Failed to regiser transport: " + name); - return false; - } - } - }; + File initSentinel = new File(stateDir, INIT_SENTINEL_FILE_NAME); + if (initSentinel.exists()) { + synchronized (mQueueLock) { + mPendingInits.add(transportName); + + // TODO: pick a better starting time than now + 1 minute + long delay = 1000 * 60; // one minute, in milliseconds + mAlarmManager.set(AlarmManager.RTC_WAKEUP, + System.currentTimeMillis() + delay, mRunInitIntent); + } + } + } // ----- Track installation/removal of packages ----- private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @@ -2891,14 +2891,14 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "listAllTransports"); - return mTransportManager.getBoundTransportNames(); + return mTransportManager.getRegisteredTransportNames(); } @Override public ComponentName[] listAllTransportComponents() { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "listAllTransportComponents"); - return mTransportManager.getAllTransportComponents(); + return mTransportManager.getRegisteredTransportComponents(); } @Override @@ -3003,6 +3003,8 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter /** Selects transport {@code transportName} and returns previous selected transport. */ @Override + @Deprecated + @Nullable public String selectBackupTransport(String transportName) { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.BACKUP, "selectBackupTransport"); diff --git a/services/backup/java/com/android/server/backup/TransportManager.java b/services/backup/java/com/android/server/backup/TransportManager.java index 34b8935939ff7..09456b68e1c0f 100644 --- a/services/backup/java/com/android/server/backup/TransportManager.java +++ b/services/backup/java/com/android/server/backup/TransportManager.java @@ -16,93 +16,56 @@ package com.android.server.backup; -import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; - import android.annotation.Nullable; +import android.annotation.WorkerThread; import android.app.backup.BackupManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.ServiceConnection; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; -import android.content.pm.ServiceInfo; -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; -import android.os.Message; import android.os.RemoteException; import android.os.UserHandle; -import android.provider.Settings; import android.util.ArrayMap; -import android.util.ArraySet; import android.util.EventLog; -import android.util.Log; import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.backup.IBackupTransport; +import com.android.internal.util.Preconditions; import com.android.server.EventLogTags; +import com.android.server.backup.transport.OnTransportRegisteredListener; import com.android.server.backup.transport.TransportClient; import com.android.server.backup.transport.TransportClientManager; import com.android.server.backup.transport.TransportConnectionListener; import com.android.server.backup.transport.TransportNotAvailableException; import com.android.server.backup.transport.TransportNotRegisteredException; -import java.util.ArrayList; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Consumer; import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; -/** - * Handles in-memory bookkeeping of all BackupTransport objects. - */ +/** Handles in-memory bookkeeping of all BackupTransport objects. */ public class TransportManager { - private static final String TAG = "BackupTransportManager"; @VisibleForTesting public static final String SERVICE_ACTION_TRANSPORT_HOST = "android.backup.TRANSPORT_HOST"; - private static final long REBINDING_TIMEOUT_UNPROVISIONED_MS = 30 * 1000; // 30 sec - private static final long REBINDING_TIMEOUT_PROVISIONED_MS = 5 * 60 * 1000; // 5 mins - private static final int REBINDING_TIMEOUT_MSG = 1; - private final Intent mTransportServiceIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST); private final Context mContext; private final PackageManager mPackageManager; private final Set mTransportWhitelist; - private final Handler mHandler; private final TransportClientManager mTransportClientManager; - - /** - * This listener is called after we bind to any transport. If it returns true, this is a valid - * transport. - */ - private TransportBoundListener mTransportBoundListener; - private final Object mTransportLock = new Object(); - - /** - * We have detected these transports on the device. Unless in exceptional cases, we are also - * bound to all of these. - */ - @GuardedBy("mTransportLock") - private final Map mValidTransports = new ArrayMap<>(); - - /** We are currently bound to these transports. */ - @GuardedBy("mTransportLock") - private final Map mBoundTransports = new ArrayMap<>(); - - /** @see #getEligibleTransportComponents() */ - @GuardedBy("mTransportLock") - private final Set mEligibleTransports = new ArraySet<>(); + private OnTransportRegisteredListener mOnTransportRegisteredListener = (c, n) -> {}; /** @see #getRegisteredTransportNames() */ @GuardedBy("mTransportLock") @@ -110,120 +73,98 @@ public class TransportManager { new ArrayMap<>(); @GuardedBy("mTransportLock") + @Nullable private volatile String mCurrentTransportName; - TransportManager( - Context context, - Set whitelist, - String defaultTransport, - TransportBoundListener listener, - Looper looper) { - this(context, whitelist, defaultTransport, looper); - mTransportBoundListener = listener; + TransportManager(Context context, Set whitelist, String selectedTransport) { + this(context, whitelist, selectedTransport, new TransportClientManager(context)); } + @VisibleForTesting TransportManager( Context context, Set whitelist, - String defaultTransport, - Looper looper) { + String selectedTransport, + TransportClientManager transportClientManager) { mContext = context; mPackageManager = context.getPackageManager(); - if (whitelist != null) { - mTransportWhitelist = whitelist; - } else { - mTransportWhitelist = new ArraySet<>(); - } - mCurrentTransportName = defaultTransport; - mHandler = new RebindOnTimeoutHandler(looper); - mTransportClientManager = new TransportClientManager(context); + mTransportWhitelist = Preconditions.checkNotNull(whitelist); + mCurrentTransportName = selectedTransport; + mTransportClientManager = transportClientManager; } - public void setTransportBoundListener(TransportBoundListener transportBoundListener) { - mTransportBoundListener = transportBoundListener; + /* Sets a listener to be called whenever a transport is registered. */ + public void setOnTransportRegisteredListener(OnTransportRegisteredListener listener) { + mOnTransportRegisteredListener = listener; } + @WorkerThread void onPackageAdded(String packageName) { - // New package added. Bind to all transports it contains. - synchronized (mTransportLock) { - log_verbose("Package added. Binding to all transports. " + packageName); - bindToAllInternal(packageName, null /* all components */); - } + registerTransportsFromPackage(packageName, transportComponent -> true); } void onPackageRemoved(String packageName) { - // Package removed. Remove all its transports from our list. These transports have already - // been removed from mBoundTransports because onServiceDisconnected would already been - // called on TransportConnection objects. synchronized (mTransportLock) { - Iterator> iter = - mValidTransports.entrySet().iterator(); - while (iter.hasNext()) { - Map.Entry validTransport = iter.next(); - ComponentName componentName = validTransport.getKey(); - if (componentName.getPackageName().equals(packageName)) { - TransportConnection transportConnection = validTransport.getValue(); - iter.remove(); - if (transportConnection != null) { - mContext.unbindService(transportConnection); - log_verbose("Package removed, removing transport: " - + componentName.flattenToShortString()); - } - } - } - removeTransportsIfLocked( - componentName -> packageName.equals(componentName.getPackageName())); + mRegisteredTransportsDescriptionMap.keySet().removeIf(fromPackageFilter(packageName)); } } - void onPackageChanged(String packageName, String[] components) { + @WorkerThread + void onPackageChanged(String packageName, String... components) { synchronized (mTransportLock) { - // Remove all changed components from mValidTransports. We'll bind to them again - // and re-add them if still valid. - Set transportsToBeRemoved = new ArraySet<>(); - for (String component : components) { - ComponentName componentName = new ComponentName(packageName, component); - transportsToBeRemoved.add(componentName); - TransportConnection removed = mValidTransports.remove(componentName); - if (removed != null) { - mContext.unbindService(removed); - log_verbose("Package changed. Removing transport: " + - componentName.flattenToShortString()); - } - } - removeTransportsIfLocked(transportsToBeRemoved::contains); - bindToAllInternal(packageName, components); + Set transportComponents = + Stream.of(components) + .map(component -> new ComponentName(packageName, component)) + .collect(Collectors.toSet()); + + mRegisteredTransportsDescriptionMap.keySet().removeIf(transportComponents::contains); + registerTransportsFromPackage(packageName, transportComponents::contains); } } - @GuardedBy("mTransportLock") - private void removeTransportsIfLocked(Predicate filter) { - mEligibleTransports.removeIf(filter); - mRegisteredTransportsDescriptionMap.keySet().removeIf(filter); - } - - public IBackupTransport getTransportBinder(String transportName) { + /** + * Returns the {@link ComponentName}s of the registered transports. + * + *

A *registered* transport is a transport that satisfies intent with action + * android.backup.TRANSPORT_HOST, returns true for {@link #isTransportTrusted(ComponentName)} + * and that we have successfully connected to once. + */ + ComponentName[] getRegisteredTransportComponents() { synchronized (mTransportLock) { - ComponentName component = mBoundTransports.get(transportName); - if (component == null) { - Slog.w(TAG, "Transport " + transportName + " not bound."); - return null; - } - TransportConnection conn = mValidTransports.get(component); - if (conn == null) { - Slog.w(TAG, "Transport " + transportName + " not valid."); - return null; - } - return conn.getBinder(); + return mRegisteredTransportsDescriptionMap + .keySet() + .toArray(new ComponentName[mRegisteredTransportsDescriptionMap.size()]); } } - public IBackupTransport getCurrentTransportBinder() { - return getTransportBinder(mCurrentTransportName); + /** + * Returns the names of the registered transports. + * + * @see #getRegisteredTransportComponents() + */ + String[] getRegisteredTransportNames() { + synchronized (mTransportLock) { + return mRegisteredTransportsDescriptionMap + .values() + .stream() + .map(transportDescription -> transportDescription.name) + .toArray(String[]::new); + } + } + + /** Returns a set with the whitelisted transports. */ + Set getTransportWhitelist() { + return mTransportWhitelist; + } + + @Nullable + String getCurrentTransportName() { + return mCurrentTransportName; } /** * Returns the transport name associated with {@code transportComponent}. + * * @throws TransportNotRegisteredException if the transport is not registered. */ public String getTransportName(ComponentName transportComponent) @@ -234,7 +175,32 @@ public class TransportManager { } /** - * Retrieve the configuration intent of {@code transportName}. + * Retrieves the transport dir name of {@code transportComponent}. + * + * @throws TransportNotRegisteredException if the transport is not registered. + */ + public String getTransportDirName(ComponentName transportComponent) + throws TransportNotRegisteredException { + synchronized (mTransportLock) { + return getRegisteredTransportDescriptionOrThrowLocked(transportComponent) + .transportDirName; + } + } + + /** + * Retrieves the transport dir name of {@code transportName}. + * + * @throws TransportNotRegisteredException if the transport is not registered. + */ + public String getTransportDirName(String transportName) throws TransportNotRegisteredException { + synchronized (mTransportLock) { + return getRegisteredTransportDescriptionOrThrowLocked(transportName).transportDirName; + } + } + + /** + * Retrieves the configuration intent of {@code transportName}. + * * @throws TransportNotRegisteredException if the transport is not registered. */ @Nullable @@ -247,7 +213,21 @@ public class TransportManager { } /** - * Retrieve the data management intent of {@code transportName}. + * Retrieves 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; + } + } + + /** + * Retrieves the data management intent of {@code transportName}. + * * @throws TransportNotRegisteredException if the transport is not registered. */ @Nullable @@ -260,19 +240,8 @@ 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}. + * Retrieves the data management label of {@code transportName}. + * * @throws TransportNotRegisteredException if the transport is not registered. */ @Nullable @@ -284,54 +253,74 @@ public class TransportManager { } } - /** - * Retrieve the transport dir name of {@code transportName}. - * @throws TransportNotRegisteredException if the transport is not registered. - */ - public String getTransportDirName(String transportName) - throws TransportNotRegisteredException { + /* Returns true if the transport identified by {@code transportName} is registered. */ + public boolean isTransportRegistered(String transportName) { synchronized (mTransportLock) { - return getRegisteredTransportDescriptionOrThrowLocked(transportName) - .transportDirName; - } - } - - /** - * Retrieve the transport dir name of {@code transportComponent}. - * @throws TransportNotRegisteredException if the transport is not registered. - */ - public String getTransportDirName(ComponentName transportComponent) - throws TransportNotRegisteredException { - synchronized (mTransportLock) { - return getRegisteredTransportDescriptionOrThrowLocked(transportComponent) - .transportDirName; + return getRegisteredTransportEntryLocked(transportName) != null; } } /** * Execute {@code transportConsumer} for each registered transport passing the transport name. * This is called with an internal lock held, ensuring that the transport will remain registered - * while {@code transportConsumer} is being executed. Don't do heavy operations in - * {@code transportConsumer}. + * while {@code transportConsumer} is being executed. Don't do heavy operations in {@code + * transportConsumer}. */ public void forEachRegisteredTransport(Consumer transportConsumer) { synchronized (mTransportLock) { - for (TransportDescription transportDescription - : mRegisteredTransportsDescriptionMap.values()) { + for (TransportDescription transportDescription : + mRegisteredTransportsDescriptionMap.values()) { transportConsumer.accept(transportDescription.name); } } } - public String getTransportName(IBackupTransport binder) { + /** + * Updates given values for the transport already registered and identified with {@param + * transportComponent}. If the transport is not registered it will log and return. + */ + public void updateTransportAttributes( + ComponentName transportComponent, + String name, + @Nullable Intent configurationIntent, + String currentDestinationString, + @Nullable Intent dataManagementIntent, + @Nullable String dataManagementLabel) { synchronized (mTransportLock) { - for (TransportConnection conn : mValidTransports.values()) { - if (conn.getBinder() == binder) { - return conn.getName(); - } + TransportDescription description = + mRegisteredTransportsDescriptionMap.get(transportComponent); + if (description == null) { + Slog.e(TAG, "Transport " + name + " not registered tried to change description"); + return; } + description.name = name; + description.configurationIntent = configurationIntent; + description.currentDestinationString = currentDestinationString; + description.dataManagementIntent = dataManagementIntent; + description.dataManagementLabel = dataManagementLabel; + Slog.d(TAG, "Transport " + name + " updated its attributes"); } - return null; + } + + @GuardedBy("mTransportLock") + private TransportDescription getRegisteredTransportDescriptionOrThrowLocked( + ComponentName transportComponent) throws TransportNotRegisteredException { + TransportDescription description = + mRegisteredTransportsDescriptionMap.get(transportComponent); + if (description == null) { + throw new TransportNotRegisteredException(transportComponent); + } + return description; + } + + @GuardedBy("mTransportLock") + private TransportDescription getRegisteredTransportDescriptionOrThrowLocked( + String transportName) throws TransportNotRegisteredException { + TransportDescription description = getRegisteredTransportDescriptionLocked(transportName); + if (description == null) { + throw new TransportNotRegisteredException(transportName); + } + return description; } @GuardedBy("mTransportLock") @@ -350,22 +339,12 @@ public class TransportManager { return (entry == null) ? null : entry.getValue(); } - @GuardedBy("mTransportLock") - private TransportDescription getRegisteredTransportDescriptionOrThrowLocked( - String transportName) throws TransportNotRegisteredException { - TransportDescription description = getRegisteredTransportDescriptionLocked(transportName); - if (description == null) { - throw new TransportNotRegisteredException(transportName); - } - return description; - } - @GuardedBy("mTransportLock") @Nullable private Map.Entry getRegisteredTransportEntryLocked( String transportName) { - for (Map.Entry entry - : mRegisteredTransportsDescriptionMap.entrySet()) { + for (Map.Entry entry : + mRegisteredTransportsDescriptionMap.entrySet()) { TransportDescription description = entry.getValue(); if (transportName.equals(description.name)) { return entry; @@ -374,17 +353,16 @@ public class TransportManager { return null; } - @GuardedBy("mTransportLock") - private TransportDescription getRegisteredTransportDescriptionOrThrowLocked( - ComponentName transportComponent) throws TransportNotRegisteredException { - TransportDescription description = - mRegisteredTransportsDescriptionMap.get(transportComponent); - if (description == null) { - throw new TransportNotRegisteredException(transportComponent); - } - return description; - } - + /** + * Returns a {@link TransportClient} for {@code transportName} or {@code null} if not + * registered. + * + * @param transportName The name of the transport. + * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check + * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more + * details. + * @return A {@link TransportClient} or null if not registered. + */ @Nullable public TransportClient getTransportClient(String transportName, String caller) { try { @@ -395,6 +373,16 @@ public class TransportManager { } } + /** + * Returns a {@link TransportClient} for {@code transportName} or throws if not registered. + * + * @param transportName The name of the transport. + * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check + * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more + * details. + * @return A {@link TransportClient}. + * @throws TransportNotRegisteredException if the transport is not registered. + */ public TransportClient getTransportClientOrThrow(String transportName, String caller) throws TransportNotRegisteredException { synchronized (mTransportLock) { @@ -406,19 +394,14 @@ public class TransportManager { } } - public boolean isTransportRegistered(String transportName) { - synchronized (mTransportLock) { - return getRegisteredTransportEntryLocked(transportName) != null; - } - } - /** - * Returns a {@link TransportClient} for the current transport or null if not found. + * Returns a {@link TransportClient} for the current transport or {@code null} if not + * registered. * * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more * details. - * @return A {@link TransportClient} or null if not found. + * @return A {@link TransportClient} or null if not registered. */ @Nullable public TransportClient getCurrentTransportClient(String caller) { @@ -455,130 +438,88 @@ public class TransportManager { mTransportClientManager.disposeOfTransportClient(transportClient, caller); } - String[] getBoundTransportNames() { - synchronized (mTransportLock) { - return mBoundTransports.keySet().toArray(new String[mBoundTransports.size()]); - } - } - - ComponentName[] getAllTransportComponents() { - synchronized (mTransportLock) { - return mValidTransports.keySet().toArray(new ComponentName[mValidTransports.size()]); - } - } - /** - * An *eligible* transport is a service component that satisfies intent with action - * android.backup.TRANSPORT_HOST and returns true for - * {@link #isTransportTrusted(ComponentName)}. It may be registered or not registered. - * This method returns the {@link ComponentName}s of those transports. - */ - ComponentName[] getEligibleTransportComponents() { - synchronized (mTransportLock) { - return mEligibleTransports.toArray(new ComponentName[mEligibleTransports.size()]); - } - } - - Set getTransportWhitelist() { - return mTransportWhitelist; - } - - /** - * A *registered* transport is an eligible transport that has been successfully connected and - * that returned true for method - * {@link TransportBoundListener#onTransportBound(IBackupTransport)} of TransportBoundListener - * provided in the constructor. This method returns the names of the registered transports. - */ - String[] getRegisteredTransportNames() { - synchronized (mTransportLock) { - return mRegisteredTransportsDescriptionMap.values().stream() - .map(transportDescription -> transportDescription.name) - .toArray(String[]::new); - } - } - - /** - * Updates given values for the transport already registered and identified with - * {@param transportComponent}. If the transport is not registered it will log and return. - */ - public void updateTransportAttributes( - ComponentName transportComponent, - String name, - @Nullable Intent configurationIntent, - String currentDestinationString, - @Nullable Intent dataManagementIntent, - @Nullable String dataManagementLabel) { - synchronized (mTransportLock) { - TransportDescription description = - mRegisteredTransportsDescriptionMap.get(transportComponent); - if (description == null) { - Slog.e(TAG, "Transport " + name + " not registered tried to change description"); - return; - } - description.name = name; - description.configurationIntent = configurationIntent; - description.currentDestinationString = currentDestinationString; - description.dataManagementIntent = dataManagementIntent; - description.dataManagementLabel = dataManagementLabel; - Slog.d(TAG, "Transport " + name + " updated its attributes"); - } - } - - @Nullable - String getCurrentTransportName() { - return mCurrentTransportName; - } - - // This is for mocking, Mockito can't mock if package-protected and in the same package but - // different class loaders. Checked with the debugger and class loaders are different - // See https://github.com/mockito/mockito/issues/796 - @VisibleForTesting(visibility = PACKAGE) - public void registerAllTransports() { - bindToAllInternal(null /* all packages */, null /* all components */); - } - - /** - * Bind to all transports belonging to the given package and the given component list. - * null acts a wildcard. + * Sets {@code transportName} as selected transport and returns previously selected transport + * name. If there was no previous transport it returns null. * - * If packageName is null, bind to all transports in all packages. - * If components is null, bind to all transports in the given package. + *

You should NOT call this method in new code. This won't make any checks against {@code + * transportName}, putting any operation at risk of a {@link TransportNotRegisteredException} or + * another error at the time it's being executed. + * + *

{@link Deprecated} as public, this method can be used as private. */ - private void bindToAllInternal(String packageName, String[] components) { - PackageInfo pkgInfo = null; - if (packageName != null) { + @Deprecated + @Nullable + String selectTransport(String transportName) { + synchronized (mTransportLock) { + String prevTransport = mCurrentTransportName; + mCurrentTransportName = transportName; + return prevTransport; + } + } + + /** + * Tries to register the transport if not registered. If successful also selects the transport. + * + * @param transportComponent Host of the transport. + * @return One of {@link BackupManager#SUCCESS}, {@link BackupManager#ERROR_TRANSPORT_INVALID} + * or {@link BackupManager#ERROR_TRANSPORT_UNAVAILABLE}. + */ + @WorkerThread + public int registerAndSelectTransport(ComponentName transportComponent) { + synchronized (mTransportLock) { + if (!mRegisteredTransportsDescriptionMap.containsKey(transportComponent)) { + int result = registerTransport(transportComponent); + if (result != BackupManager.SUCCESS) { + return result; + } + } + try { - pkgInfo = mPackageManager.getPackageInfo(packageName, 0); - } catch (PackageManager.NameNotFoundException e) { - Slog.w(TAG, "Package not found: " + packageName); - return; + selectTransport(getTransportName(transportComponent)); + return BackupManager.SUCCESS; + } catch (TransportNotRegisteredException e) { + // Shouldn't happen because we are holding the lock + Slog.wtf(TAG, "Transport unexpectedly not registered"); + return BackupManager.ERROR_TRANSPORT_UNAVAILABLE; } } + } - Intent intent = new Intent(mTransportServiceIntent); - if (packageName != null) { - intent.setPackage(packageName); + @WorkerThread + public void registerTransports() { + registerTransportsForIntent(mTransportServiceIntent, transportComponent -> true); + } + + @WorkerThread + private void registerTransportsFromPackage( + String packageName, Predicate transportComponentFilter) { + try { + mPackageManager.getPackageInfo(packageName, 0); + } catch (PackageManager.NameNotFoundException e) { + Slog.e(TAG, "Trying to register transports from package not found " + packageName); + return; } - List hosts = mPackageManager.queryIntentServicesAsUser( - intent, 0, UserHandle.USER_SYSTEM); - if (hosts != null) { + registerTransportsForIntent( + new Intent(mTransportServiceIntent).setPackage(packageName), + transportComponentFilter.and(fromPackageFilter(packageName))); + } + + @WorkerThread + private void registerTransportsForIntent( + Intent intent, Predicate transportComponentFilter) { + List hosts = + mPackageManager.queryIntentServicesAsUser(intent, 0, UserHandle.USER_SYSTEM); + if (hosts == null) { + return; + } + synchronized (mTransportLock) { for (ResolveInfo host : hosts) { - final ComponentName infoComponentName = getComponentName(host.serviceInfo); - boolean shouldBind = false; - if (components != null && packageName != null) { - for (String component : components) { - ComponentName cn = new ComponentName(pkgInfo.packageName, component); - if (infoComponentName.equals(cn)) { - shouldBind = true; - break; - } - } - } else { - shouldBind = true; - } - if (shouldBind && isTransportTrusted(infoComponentName)) { - tryBindTransport(infoComponentName); + ComponentName transportComponent = host.serviceInfo.getComponentName(); + if (transportComponentFilter.test(transportComponent) + && isTransportTrusted(transportComponent)) { + registerTransport(transportComponent); } } } @@ -605,64 +546,6 @@ public class TransportManager { return true; } - private void tryBindTransport(ComponentName transportComponentName) { - Slog.d(TAG, "Binding to transport: " + transportComponentName.flattenToShortString()); - // TODO: b/22388012 (Multi user backup and restore) - TransportConnection connection = new TransportConnection(transportComponentName); - synchronized (mTransportLock) { - mEligibleTransports.add(transportComponentName); - } - if (bindToTransport(transportComponentName, connection)) { - synchronized (mTransportLock) { - mValidTransports.put(transportComponentName, connection); - } - } else { - Slog.w(TAG, "Couldn't bind to transport " + transportComponentName); - } - } - - private boolean bindToTransport(ComponentName componentName, ServiceConnection connection) { - Intent intent = new Intent(mTransportServiceIntent) - .setComponent(componentName); - return mContext.bindServiceAsUser(intent, connection, Context.BIND_AUTO_CREATE, - createSystemUserHandle()); - } - - String selectTransport(String transportName) { - synchronized (mTransportLock) { - String prevTransport = mCurrentTransportName; - mCurrentTransportName = transportName; - return prevTransport; - } - } - - /** - * Tries to register the transport if not registered. If successful also selects the transport. - * - * @param transportComponent Host of the transport. - * @return One of {@link BackupManager#SUCCESS}, {@link BackupManager#ERROR_TRANSPORT_INVALID} - * or {@link BackupManager#ERROR_TRANSPORT_UNAVAILABLE}. - */ - public int registerAndSelectTransport(ComponentName transportComponent) { - synchronized (mTransportLock) { - if (!mRegisteredTransportsDescriptionMap.containsKey(transportComponent)) { - int result = registerTransport(transportComponent); - if (result != BackupManager.SUCCESS) { - return result; - } - } - - try { - selectTransport(getTransportName(transportComponent)); - return BackupManager.SUCCESS; - } catch (TransportNotRegisteredException e) { - // Shouldn't happen because we are holding the lock - Slog.wtf(TAG, "Transport unexpectedly not registered"); - return BackupManager.ERROR_TRANSPORT_UNAVAILABLE; - } - } - } - /** * Tries to register transport represented by {@code transportComponent}. * @@ -670,7 +553,12 @@ public class TransportManager { * @return One of {@link BackupManager#SUCCESS}, {@link BackupManager#ERROR_TRANSPORT_INVALID} * or {@link BackupManager#ERROR_TRANSPORT_UNAVAILABLE}. */ + @WorkerThread private int registerTransport(ComponentName transportComponent) { + if (!isTransportTrusted(transportComponent)) { + return BackupManager.ERROR_TRANSPORT_INVALID; + } + String transportString = transportComponent.flattenToShortString(); String callerLogString = "TransportManager.registerTransport()"; @@ -689,26 +577,21 @@ public class TransportManager { EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, transportString, 1); int result; - if (isTransportValid(transport)) { - try { - registerTransport(transportComponent, transport); - // If registerTransport() hasn't thrown... - result = BackupManager.SUCCESS; - } catch (RemoteException e) { - Slog.e(TAG, "Transport " + transportString + " died while registering"); - result = BackupManager.ERROR_TRANSPORT_UNAVAILABLE; - } - } else { - Slog.w(TAG, "Can't register invalid transport " + transportString); - result = BackupManager.ERROR_TRANSPORT_INVALID; + try { + String transportName = transport.name(); + String transportDirName = transport.transportDirName(); + registerTransport(transportComponent, transport); + // If registerTransport() hasn't thrown... + Slog.d(TAG, "Transport " + transportString + " registered"); + mOnTransportRegisteredListener.onTransportRegistered(transportName, transportDirName); + result = BackupManager.SUCCESS; + } catch (RemoteException e) { + Slog.e(TAG, "Transport " + transportString + " died while registering"); + EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, transportString, 0); + result = BackupManager.ERROR_TRANSPORT_UNAVAILABLE; } mTransportClientManager.disposeOfTransportClient(transportClient, callerLogString); - if (result == BackupManager.SUCCESS) { - Slog.d(TAG, "Transport " + transportString + " registered"); - } else { - EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, transportString, 0); - } return result; } @@ -717,204 +600,20 @@ public class TransportManager { throws RemoteException { synchronized (mTransportLock) { String name = transport.name(); - TransportDescription description = new TransportDescription( - name, - transport.transportDirName(), - transport.configurationIntent(), - transport.currentDestinationString(), - transport.dataManagementIntent(), - transport.dataManagementLabel()); + TransportDescription description = + new TransportDescription( + name, + transport.transportDirName(), + transport.configurationIntent(), + transport.currentDestinationString(), + transport.dataManagementIntent(), + transport.dataManagementLabel()); mRegisteredTransportsDescriptionMap.put(transportComponent, description); } } - private boolean isTransportValid(IBackupTransport transport) { - if (mTransportBoundListener == null) { - Slog.w(TAG, "setTransportBoundListener() not called, assuming transport invalid"); - return false; - } - return mTransportBoundListener.onTransportBound(transport); - } - - private class TransportConnection implements ServiceConnection { - - // Hold mTransportLock to access these fields so as to provide a consistent view of them. - private volatile IBackupTransport mBinder; - private volatile String mTransportName; - - private final ComponentName mTransportComponent; - - private TransportConnection(ComponentName transportComponent) { - mTransportComponent = transportComponent; - } - - @Override - public void onServiceConnected(ComponentName component, IBinder binder) { - synchronized (mTransportLock) { - mBinder = IBackupTransport.Stub.asInterface(binder); - boolean success = false; - - EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, - component.flattenToShortString(), 1); - - try { - mTransportName = mBinder.name(); - // BackupManager requests some fields from the transport. If they are - // invalid, throw away this transport. - if (isTransportValid(mBinder)) { - // We're now using the always-bound connection to do the registration but - // when we remove the always-bound code this will be in the first binding - // TODO: Move registration to first binding - registerTransport(component, mBinder); - // If registerTransport() hasn't thrown... - success = true; - } - } catch (RemoteException e) { - success = false; - Slog.e(TAG, "Couldn't get transport name.", e); - } finally { - // we need to intern() the String of the component, so that we can use it with - // Handler's removeMessages(), which uses == operator to compare the tokens - String componentShortString = component.flattenToShortString().intern(); - if (success) { - Slog.d(TAG, "Bound to transport: " + componentShortString); - mBoundTransports.put(mTransportName, component); - // cancel rebinding on timeout for this component as we've already connected - mHandler.removeMessages(REBINDING_TIMEOUT_MSG, componentShortString); - } else { - Slog.w(TAG, "Bound to transport " + componentShortString + - " but it is invalid"); - EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, - componentShortString, 0); - mContext.unbindService(this); - mValidTransports.remove(component); - mEligibleTransports.remove(component); - mBinder = null; - } - } - } - } - - @Override - public void onServiceDisconnected(ComponentName component) { - synchronized (mTransportLock) { - mBinder = null; - mBoundTransports.remove(mTransportName); - } - String componentShortString = component.flattenToShortString(); - EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, componentShortString, 0); - Slog.w(TAG, "Disconnected from transport " + componentShortString); - scheduleRebindTimeout(component); - } - - /** - * We'll attempt to explicitly rebind to a transport if it hasn't happened automatically - * for a few minutes after the binding went away. - */ - private void scheduleRebindTimeout(ComponentName component) { - // we need to intern() the String of the component, so that we can use it with Handler's - // removeMessages(), which uses == operator to compare the tokens - final String componentShortString = component.flattenToShortString().intern(); - final long rebindTimeout = getRebindTimeout(); - mHandler.removeMessages(REBINDING_TIMEOUT_MSG, componentShortString); - Message msg = mHandler.obtainMessage(REBINDING_TIMEOUT_MSG); - msg.obj = componentShortString; - mHandler.sendMessageDelayed(msg, rebindTimeout); - Slog.d(TAG, "Scheduled explicit rebinding for " + componentShortString + " in " - + rebindTimeout + "ms"); - } - - // Intentionally not synchronized -- the variable is volatile and changes to its value - // are inside synchronized blocks, providing a memory sync barrier; and this method - // does not touch any other state protected by that lock. - private IBackupTransport getBinder() { - return mBinder; - } - - // Intentionally not synchronized; same as getBinder() - private String getName() { - return mTransportName; - } - - // Intentionally not synchronized; same as getBinder() - private void bindIfUnbound() { - if (mBinder == null) { - Slog.d(TAG, - "Rebinding to transport " + mTransportComponent.flattenToShortString()); - bindToTransport(mTransportComponent, this); - } - } - - private long getRebindTimeout() { - final boolean isDeviceProvisioned = Settings.Global.getInt( - mContext.getContentResolver(), - Settings.Global.DEVICE_PROVISIONED, 0) != 0; - return isDeviceProvisioned - ? REBINDING_TIMEOUT_PROVISIONED_MS - : REBINDING_TIMEOUT_UNPROVISIONED_MS; - } - } - - public interface TransportBoundListener { - /** Should return true if this is a valid transport. */ - boolean onTransportBound(IBackupTransport binder); - } - - private class RebindOnTimeoutHandler extends Handler { - - RebindOnTimeoutHandler(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message msg) { - if (msg.what == REBINDING_TIMEOUT_MSG) { - String componentShortString = (String) msg.obj; - ComponentName transportComponent = - ComponentName.unflattenFromString(componentShortString); - synchronized (mTransportLock) { - if (mBoundTransports.containsValue(transportComponent)) { - Slog.d(TAG, "Explicit rebinding timeout passed, but already bound to " - + componentShortString + " so not attempting to rebind"); - return; - } - Slog.d(TAG, "Explicit rebinding timeout passed, attempting rebinding to: " - + componentShortString); - // unbind the existing (broken) connection - TransportConnection conn = mValidTransports.get(transportComponent); - if (conn != null) { - mContext.unbindService(conn); - Slog.d(TAG, "Unbinding the existing (broken) connection to transport: " - + componentShortString); - } - } - // rebind to transport - tryBindTransport(transportComponent); - } else { - Slog.e(TAG, "Unknown message sent to RebindOnTimeoutHandler, msg.what: " - + msg.what); - } - } - } - - private static void log_verbose(String message) { - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Slog.v(TAG, message); - } - } - - // These only exists to make it testable with Robolectric, which is not updated to API level 24 - // yet. - // TODO: Get rid of this once Robolectric is updated. - private static ComponentName getComponentName(ServiceInfo serviceInfo) { - return new ComponentName(serviceInfo.packageName, serviceInfo.name); - } - - // These only exists to make it testable with Robolectric, which is not updated to API level 24 - // yet. - // TODO: Get rid of this once Robolectric is updated. - public static UserHandle createSystemUserHandle() { - return new UserHandle(UserHandle.USER_SYSTEM); + private static Predicate fromPackageFilter(String packageName) { + return transportComponent -> packageName.equals(transportComponent.getPackageName()); } private static class TransportDescription { diff --git a/services/backup/java/com/android/server/backup/transport/OnTransportRegisteredListener.java b/services/backup/java/com/android/server/backup/transport/OnTransportRegisteredListener.java new file mode 100644 index 0000000000000..391ec2d7f2945 --- /dev/null +++ b/services/backup/java/com/android/server/backup/transport/OnTransportRegisteredListener.java @@ -0,0 +1,33 @@ +/* + * 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.transport; + +import com.android.server.backup.TransportManager; + +/** + * Listener called when a transport is registered with the {@link TransportManager}. Can be set + * using {@link TransportManager#setOnTransportRegisteredListener(OnTransportRegisteredListener)}. + */ +@FunctionalInterface +public interface OnTransportRegisteredListener { + /** + * Called when a transport is successfully registered. + * @param transportName The name of the transport. + * @param transportDirName The dir name of the transport. + */ + public void onTransportRegistered(String transportName, String transportDirName); +} diff --git a/services/backup/java/com/android/server/backup/transport/TransportClient.java b/services/backup/java/com/android/server/backup/transport/TransportClient.java index 7bd9111028cbe..bd4a0bb570728 100644 --- a/services/backup/java/com/android/server/backup/transport/TransportClient.java +++ b/services/backup/java/com/android/server/backup/transport/TransportClient.java @@ -236,7 +236,7 @@ public class TransportClient { mBindIntent, mConnection, Context.BIND_AUTO_CREATE, - TransportManager.createSystemUserHandle()); + UserHandle.SYSTEM); if (hasBound) { // We don't need to set a time-out because we are guaranteed to get a call // back in ServiceConnection, either an onServiceConnected() or diff --git a/services/robotests/src/com/android/server/backup/BackupManagerServiceRoboTest.java b/services/robotests/src/com/android/server/backup/BackupManagerServiceRoboTest.java index f9ebd28418cd0..bf58224637463 100644 --- a/services/robotests/src/com/android/server/backup/BackupManagerServiceRoboTest.java +++ b/services/robotests/src/com/android/server/backup/BackupManagerServiceRoboTest.java @@ -16,7 +16,11 @@ package com.android.server.backup; -import static com.android.server.backup.testing.TransportTestUtils.TRANSPORT_NAMES; +import static com.android.server.backup.testing.TransportData.backupTransport; +import static com.android.server.backup.testing.TransportData.d2dTransport; +import static com.android.server.backup.testing.TransportData.localTransport; +import static com.android.server.backup.testing.TransportTestUtils.setUpCurrentTransport; +import static com.android.server.backup.testing.TransportTestUtils.setUpTransports; import static com.google.common.truth.Truth.assertThat; @@ -24,6 +28,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.robolectric.Shadows.shadowOf; @@ -39,8 +44,8 @@ import android.platform.test.annotations.Presubmit; import android.provider.Settings; 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.testing.TransportData; +import com.android.server.backup.testing.TransportTestUtils.TransportMock; import com.android.server.backup.transport.TransportNotRegisteredException; import com.android.server.testing.FrameworkRobolectricTestRunner; import com.android.server.testing.SystemLoaderClasses; @@ -57,10 +62,10 @@ import org.robolectric.shadows.ShadowContextWrapper; import org.robolectric.shadows.ShadowLog; import org.robolectric.shadows.ShadowLooper; import org.robolectric.shadows.ShadowSettings; +import org.robolectric.shadows.ShadowSystemClock; import java.io.File; import java.util.HashMap; -import java.util.List; import java.util.Map; @RunWith(FrameworkRobolectricTestRunner.class) @@ -73,22 +78,24 @@ import java.util.Map; @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 ShadowLooper mShadowBackupLooper; private File mBaseStateDir; private File mDataDir; - private RefactoredBackupManagerService mBackupManagerService; private ShadowContextWrapper mShadowContext; private Context mContext; + private TransportData mTransport; + private String mTransportName; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + mTransport = backupTransport(); + mTransportName = mTransport.transportName; + mBackupThread = new HandlerThread("backup-test"); mBackupThread.setUncaughtExceptionHandler( (t, e) -> ShadowLog.e(TAG, "Uncaught exception in test thread " + t.getName(), e)); @@ -102,15 +109,6 @@ public class BackupManagerServiceRoboTest { File cacheDir = mContext.getCacheDir(); mBaseStateDir = new File(cacheDir, "base_state_dir"); mDataDir = new File(cacheDir, "data_dir"); - - mBackupManagerService = - new RefactoredBackupManagerService( - mContext, - new Trampoline(mContext), - mBackupThread, - mBaseStateDir, - mDataDir, - mTransportManager); } @After @@ -124,10 +122,12 @@ public class BackupManagerServiceRoboTest { @Test public void testDestinationString() throws Exception { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); - when(mTransportManager.getTransportCurrentDestinationString(eq(TRANSPORT_NAME))) + when(mTransportManager.getTransportCurrentDestinationString(eq(mTransportName))) .thenReturn("destinationString"); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); - String destination = mBackupManagerService.getDestinationString(TRANSPORT_NAME); + String destination = backupManagerService.getDestinationString(mTransportName); assertThat(destination).isEqualTo("destinationString"); } @@ -135,10 +135,12 @@ public class BackupManagerServiceRoboTest { @Test public void testDestinationString_whenTransportNotRegistered() throws Exception { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); - when(mTransportManager.getTransportCurrentDestinationString(eq(TRANSPORT_NAME))) + when(mTransportManager.getTransportCurrentDestinationString(eq(mTransportName))) .thenThrow(TransportNotRegisteredException.class); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); - String destination = mBackupManagerService.getDestinationString(TRANSPORT_NAME); + String destination = backupManagerService.getDestinationString(mTransportName); assertThat(destination).isNull(); } @@ -146,12 +148,14 @@ public class BackupManagerServiceRoboTest { @Test public void testDestinationString_withoutPermission() throws Exception { mShadowContext.denyPermissions(android.Manifest.permission.BACKUP); - when(mTransportManager.getTransportCurrentDestinationString(eq(TRANSPORT_NAME))) + when(mTransportManager.getTransportCurrentDestinationString(eq(mTransportName))) .thenThrow(TransportNotRegisteredException.class); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); expectThrows( SecurityException.class, - () -> mBackupManagerService.getDestinationString(TRANSPORT_NAME)); + () -> backupManagerService.getDestinationString(mTransportName)); } /* Tests for app eligibility */ @@ -159,24 +163,28 @@ public class BackupManagerServiceRoboTest { @Test public void testIsAppEligibleForBackup_whenAppEligible() throws Exception { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); - TransportData transport = - TransportTestUtils.setUpCurrentTransport(mTransportManager, TRANSPORT_NAME); + TransportMock transportMock = setUpCurrentTransport(mTransportManager, backupTransport()); ShadowAppBackupUtils.sAppIsRunningAndEligibleForBackupWithTransport = p -> true; + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); - boolean result = mBackupManagerService.isAppEligibleForBackup("app.package"); + boolean result = backupManagerService.isAppEligibleForBackup("app.package"); assertThat(result).isTrue(); + verify(mTransportManager) - .disposeOfTransportClient(eq(transport.transportClientMock), any()); + .disposeOfTransportClient(eq(transportMock.transportClient), any()); } @Test public void testIsAppEligibleForBackup_whenAppNotEligible() throws Exception { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); - TransportTestUtils.setUpCurrentTransport(mTransportManager, TRANSPORT_NAME); + setUpCurrentTransport(mTransportManager, mTransport); ShadowAppBackupUtils.sAppIsRunningAndEligibleForBackupWithTransport = p -> false; + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); - boolean result = mBackupManagerService.isAppEligibleForBackup("app.package"); + boolean result = backupManagerService.isAppEligibleForBackup("app.package"); assertThat(result).isFalse(); } @@ -184,38 +192,43 @@ public class BackupManagerServiceRoboTest { @Test public void testIsAppEligibleForBackup_withoutPermission() throws Exception { mShadowContext.denyPermissions(android.Manifest.permission.BACKUP); - TransportTestUtils.setUpCurrentTransport(mTransportManager, TRANSPORT_NAME); + setUpCurrentTransport(mTransportManager, mTransport); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); expectThrows( SecurityException.class, - () -> mBackupManagerService.isAppEligibleForBackup("app.package")); + () -> backupManagerService.isAppEligibleForBackup("app.package")); } @Test public void testFilterAppsEligibleForBackup() throws Exception { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); - TransportData transport = - TransportTestUtils.setUpCurrentTransport(mTransportManager, TRANSPORT_NAME); + TransportMock transportMock = setUpCurrentTransport(mTransportManager, mTransport); Map packagesMap = new HashMap<>(); packagesMap.put("package.a", true); packagesMap.put("package.b", false); ShadowAppBackupUtils.sAppIsRunningAndEligibleForBackupWithTransport = packagesMap::get; + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); String[] packages = packagesMap.keySet().toArray(new String[packagesMap.size()]); - String[] filtered = mBackupManagerService.filterAppsEligibleForBackup(packages); + String[] filtered = backupManagerService.filterAppsEligibleForBackup(packages); assertThat(filtered).asList().containsExactly("package.a"); verify(mTransportManager) - .disposeOfTransportClient(eq(transport.transportClientMock), any()); + .disposeOfTransportClient(eq(transportMock.transportClient), any()); } @Test public void testFilterAppsEligibleForBackup_whenNoneIsEligible() throws Exception { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); ShadowAppBackupUtils.sAppIsRunningAndEligibleForBackupWithTransport = p -> false; + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); String[] filtered = - mBackupManagerService.filterAppsEligibleForBackup( + backupManagerService.filterAppsEligibleForBackup( new String[] {"package.a", "package.b"}); assertThat(filtered).isEmpty(); @@ -224,12 +237,14 @@ public class BackupManagerServiceRoboTest { @Test public void testFilterAppsEligibleForBackup_withoutPermission() throws Exception { mShadowContext.denyPermissions(android.Manifest.permission.BACKUP); - TransportTestUtils.setUpCurrentTransport(mTransportManager, TRANSPORT_NAME); + setUpCurrentTransport(mTransportManager, mTransport); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); expectThrows( SecurityException.class, () -> - mBackupManagerService.filterAppsEligibleForBackup( + backupManagerService.filterAppsEligibleForBackup( new String[] {"package.a", "package.b"})); } @@ -238,14 +253,12 @@ public class BackupManagerServiceRoboTest { private TransportData mNewTransport; private TransportData mOldTransport; private ComponentName mNewTransportComponent; - private ISelectBackupTransportCallback mCallback; private void setUpForSelectTransport() throws Exception { - List transports = - TransportTestUtils.setUpTransports(mTransportManager, TRANSPORT_NAMES); - mNewTransport = transports.get(0); - mNewTransportComponent = mNewTransport.transportClientMock.getTransportComponent(); - mOldTransport = transports.get(1); + mNewTransport = backupTransport(); + mNewTransportComponent = mNewTransport.getTransportComponent(); + mOldTransport = d2dTransport(); + setUpTransports(mTransportManager, mNewTransport, mOldTransport, localTransport()); when(mTransportManager.selectTransport(eq(mNewTransport.transportName))) .thenReturn(mOldTransport.transportName); } @@ -254,9 +267,11 @@ public class BackupManagerServiceRoboTest { public void testSelectBackupTransport() throws Exception { setUpForSelectTransport(); mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); String oldTransport = - mBackupManagerService.selectBackupTransport(mNewTransport.transportName); + backupManagerService.selectBackupTransport(mNewTransport.transportName); assertThat(getSettingsTransport()).isEqualTo(mNewTransport.transportName); assertThat(oldTransport).isEqualTo(mOldTransport.transportName); @@ -266,10 +281,12 @@ public class BackupManagerServiceRoboTest { public void testSelectBackupTransport_withoutPermission() throws Exception { setUpForSelectTransport(); mShadowContext.denyPermissions(android.Manifest.permission.BACKUP); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); expectThrows( SecurityException.class, - () -> mBackupManagerService.selectBackupTransport(mNewTransport.transportName)); + () -> backupManagerService.selectBackupTransport(mNewTransport.transportName)); } @Test @@ -278,9 +295,11 @@ public class BackupManagerServiceRoboTest { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); when(mTransportManager.registerAndSelectTransport(eq(mNewTransportComponent))) .thenReturn(BackupManager.SUCCESS); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); ISelectBackupTransportCallback callback = mock(ISelectBackupTransportCallback.class); - mBackupManagerService.selectBackupTransportAsync(mNewTransportComponent, callback); + backupManagerService.selectBackupTransportAsync(mNewTransportComponent, callback); mShadowBackupLooper.runToEndOfTasks(); assertThat(getSettingsTransport()).isEqualTo(mNewTransport.transportName); @@ -293,9 +312,11 @@ public class BackupManagerServiceRoboTest { mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); when(mTransportManager.registerAndSelectTransport(eq(mNewTransportComponent))) .thenReturn(BackupManager.ERROR_TRANSPORT_UNAVAILABLE); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); ISelectBackupTransportCallback callback = mock(ISelectBackupTransportCallback.class); - mBackupManagerService.selectBackupTransportAsync(mNewTransportComponent, callback); + backupManagerService.selectBackupTransportAsync(mNewTransportComponent, callback); mShadowBackupLooper.runToEndOfTasks(); assertThat(getSettingsTransport()).isNotEqualTo(mNewTransport.transportName); @@ -304,19 +325,19 @@ public class BackupManagerServiceRoboTest { @Test public void testSelectBackupTransportAsync_whenTransportGetsUnregistered() throws Exception { - TransportTestUtils.setUpTransports( - mTransportManager, new TransportData(TRANSPORT_NAME, null, null)); - ComponentName newTransportComponent = - TransportTestUtils.transportComponentName(TRANSPORT_NAME); + setUpTransports(mTransportManager, mTransport.unregistered()); + ComponentName newTransportComponent = mTransport.getTransportComponent(); mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); when(mTransportManager.registerAndSelectTransport(eq(newTransportComponent))) .thenReturn(BackupManager.SUCCESS); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); ISelectBackupTransportCallback callback = mock(ISelectBackupTransportCallback.class); - mBackupManagerService.selectBackupTransportAsync(newTransportComponent, callback); + backupManagerService.selectBackupTransportAsync(newTransportComponent, callback); mShadowBackupLooper.runToEndOfTasks(); - assertThat(getSettingsTransport()).isNotEqualTo(TRANSPORT_NAME); + assertThat(getSettingsTransport()).isNotEqualTo(mTransportName); verify(callback).onFailure(anyInt()); } @@ -324,13 +345,14 @@ public class BackupManagerServiceRoboTest { public void testSelectBackupTransportAsync_withoutPermission() throws Exception { setUpForSelectTransport(); mShadowContext.denyPermissions(android.Manifest.permission.BACKUP); - ComponentName newTransportComponent = - mNewTransport.transportClientMock.getTransportComponent(); + RefactoredBackupManagerService backupManagerService = + createInitializedBackupManagerService(); + ComponentName newTransportComponent = mNewTransport.getTransportComponent(); expectThrows( SecurityException.class, () -> - mBackupManagerService.selectBackupTransportAsync( + backupManagerService.selectBackupTransportAsync( newTransportComponent, mock(ISelectBackupTransportCallback.class))); } @@ -338,4 +360,55 @@ public class BackupManagerServiceRoboTest { return ShadowSettings.ShadowSecure.getString( mContext.getContentResolver(), Settings.Secure.BACKUP_TRANSPORT); } + + /* Miscellaneous tests */ + + @Test + public void testConstructor_postRegisterTransports() { + mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); + + createBackupManagerService(); + + mShadowBackupLooper.runToEndOfTasks(); + verify(mTransportManager).registerTransports(); + } + + @Test + public void testConstructor_doesNotRegisterTransportsSynchronously() { + mShadowContext.grantPermissions(android.Manifest.permission.BACKUP); + + createBackupManagerService(); + + // Operations posted to mBackupThread only run with mShadowBackupLooper.runToEndOfTasks() + verify(mTransportManager, never()).registerTransports(); + } + + private RefactoredBackupManagerService createBackupManagerService() { + return new RefactoredBackupManagerService( + mContext, + new Trampoline(mContext), + mBackupThread, + mBaseStateDir, + mDataDir, + mTransportManager); + } + + private RefactoredBackupManagerService createInitializedBackupManagerService() { + RefactoredBackupManagerService backupManagerService = + new RefactoredBackupManagerService( + mContext, + new Trampoline(mContext), + mBackupThread, + mBaseStateDir, + mDataDir, + mTransportManager); + mShadowBackupLooper.runToEndOfTasks(); + // Handler instances have their own clock, so advancing looper (with runToEndOfTasks()) + // above does NOT advance the handlers' clock, hence whenever a handler post messages with + // specific time to the looper the time of those messages will be before the looper's time. + // To fix this we advance SystemClock as well since that is from where the handlers read + // time. + ShadowSystemClock.setCurrentTimeMillis(mShadowBackupLooper.getScheduler().getCurrentTime()); + return backupManagerService; + } } diff --git a/services/robotests/src/com/android/server/backup/TransportManagerTest.java b/services/robotests/src/com/android/server/backup/TransportManagerTest.java index acd670f6748c4..cf0bc235dc10c 100644 --- a/services/robotests/src/com/android/server/backup/TransportManagerTest.java +++ b/services/robotests/src/com/android/server/backup/TransportManagerTest.java @@ -16,108 +16,97 @@ package com.android.server.backup; +import static com.android.server.backup.testing.TransportData.genericTransport; +import static com.android.server.backup.testing.TransportTestUtils.mockTransport; +import static com.android.server.backup.testing.TransportTestUtils.setUpTransportsForTransportManager; + import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.mock; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.robolectric.shadow.api.Shadow.extract; import static org.testng.Assert.expectThrows; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toSet; +import static java.util.stream.Stream.concat; + import android.annotation.Nullable; import android.content.ComponentName; +import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; -import android.content.pm.ResolveInfo; -import android.content.pm.ServiceInfo; -import android.os.IBinder; -import android.os.RemoteException; import android.platform.test.annotations.Presubmit; -import com.android.internal.backup.IBackupTransport; -import com.android.server.backup.testing.ShadowBackupTransportStub; import com.android.server.backup.testing.ShadowContextImplForBackup; -import com.android.server.backup.testing.ShadowPackageManagerForBackup; -import com.android.server.backup.testing.TransportBoundListenerStub; +import com.android.server.testing.shadows.FrameworkShadowPackageManager; +import com.android.server.backup.testing.TransportData; +import com.android.server.backup.testing.TransportTestUtils.TransportMock; +import com.android.server.backup.transport.OnTransportRegisteredListener; import com.android.server.backup.transport.TransportClient; +import com.android.server.backup.transport.TransportClientManager; import com.android.server.backup.transport.TransportNotRegisteredException; import com.android.server.testing.FrameworkRobolectricTestRunner; import com.android.server.testing.SystemLoaderClasses; +import com.android.server.testing.shadows.FrameworkShadowContextImpl; 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.annotation.Config; -import org.robolectric.shadows.ShadowLog; -import org.robolectric.shadows.ShadowLooper; import org.robolectric.shadows.ShadowPackageManager; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Stream; @RunWith(FrameworkRobolectricTestRunner.class) @Config( - manifest = Config.NONE, - sdk = 26, - shadows = { - ShadowContextImplForBackup.class, - ShadowBackupTransportStub.class, - ShadowPackageManagerForBackup.class - } + manifest = Config.NONE, + sdk = 26, + shadows = {FrameworkShadowPackageManager.class, FrameworkShadowContextImpl.class} ) @SystemLoaderClasses({TransportManager.class}) @Presubmit public class TransportManagerTest { - private static final String PACKAGE_NAME = "some.package.name"; - private static final String ANOTHER_PACKAGE_NAME = "another.package.name"; + private static final String PACKAGE_A = "some.package.a"; + private static final String PACKAGE_B = "some.package.b"; - private TransportInfo mTransport1; - private TransportInfo mTransport2; + @Mock private OnTransportRegisteredListener mListener; + @Mock private TransportClientManager mTransportClientManager; + private TransportData mTransportA1; + private TransportData mTransportA2; + private TransportData mTransportB1; - private ShadowPackageManager mPackageManagerShadow; - - private final TransportBoundListenerStub mTransportBoundListenerStub = - new TransportBoundListenerStub(true); + private ShadowPackageManager mShadowPackageManager; + private Context mContext; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); - ShadowLog.stream = System.out; - - mPackageManagerShadow = - (ShadowPackageManagerForBackup) + mShadowPackageManager = + (FrameworkShadowPackageManager) extract(RuntimeEnvironment.application.getPackageManager()); + mContext = RuntimeEnvironment.application.getApplicationContext(); - mTransport1 = new TransportInfo( - PACKAGE_NAME, - "transport1.name", - new Intent(), - "currentDestinationString", - new Intent(), - "dataManagementLabel"); - mTransport2 = new TransportInfo( - PACKAGE_NAME, - "transport2.name", - new Intent(), - "currentDestinationString", - new Intent(), - "dataManagementLabel"); - - ShadowContextImplForBackup.sComponentBinderMap.put(mTransport1.componentName, - mTransport1.binder); - ShadowContextImplForBackup.sComponentBinderMap.put(mTransport2.componentName, - mTransport2.binder); - ShadowBackupTransportStub.sBinderTransportMap.put( - mTransport1.binder, mTransport1.binderInterface); - ShadowBackupTransportStub.sBinderTransportMap.put( - mTransport2.binder, mTransport2.binderInterface); + mTransportA1 = genericTransport(PACKAGE_A, "TransportFoo"); + mTransportA2 = genericTransport(PACKAGE_A, "TransportBar"); + mTransportB1 = genericTransport(PACKAGE_B, "TransportBaz"); } @After @@ -126,560 +115,441 @@ public class TransportManagerTest { } @Test - public void onPackageAdded_bindsToAllTransports() throws Exception { - setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2), - ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); - - TransportManager transportManager = new TransportManager( - RuntimeEnvironment.application.getApplicationContext(), - new HashSet<>(Arrays.asList( - mTransport1.componentName, mTransport2.componentName)), - null /* defaultTransport */, - mTransportBoundListenerStub, - ShadowLooper.getMainLooper()); - transportManager.onPackageAdded(PACKAGE_NAME); - - assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.componentName, mTransport2.componentName)); - assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.name, mTransport2.name)); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface)) - .isTrue(); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface)) - .isTrue(); - } - - @Test - public void onPackageAdded_oneTransportUnavailable_bindsToOnlyOneTransport() throws Exception { - setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2), - ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); - - ShadowContextImplForBackup.sUnbindableComponents.add(mTransport1.componentName); - - TransportManager transportManager = new TransportManager( - RuntimeEnvironment.application.getApplicationContext(), - new HashSet<>(Arrays.asList( - mTransport1.componentName, mTransport2.componentName)), - null /* defaultTransport */, - mTransportBoundListenerStub, - ShadowLooper.getMainLooper()); - transportManager.onPackageAdded(PACKAGE_NAME); - - assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn( - Collections.singleton(mTransport2.componentName)); - assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn( - Collections.singleton(mTransport2.name)); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface)) - .isFalse(); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface)) - .isTrue(); - } - - @Test - public void onPackageAdded_whitelistIsNull_doesNotBindToTransports() throws Exception { - setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2), - ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); - - TransportManager transportManager = new TransportManager( - RuntimeEnvironment.application.getApplicationContext(), - null /* whitelist */, - null /* defaultTransport */, - mTransportBoundListenerStub, - ShadowLooper.getMainLooper()); - transportManager.onPackageAdded(PACKAGE_NAME); - - assertThat(transportManager.getAllTransportComponents()).isEmpty(); - assertThat(transportManager.getBoundTransportNames()).isEmpty(); - assertThat(mTransportBoundListenerStub.isCalled()).isFalse(); - } - - @Test - public void onPackageAdded_onlyOneTransportWhitelisted_onlyConnectsToWhitelistedTransport() - throws Exception { - setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2), - ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); - - TransportManager transportManager = new TransportManager( - RuntimeEnvironment.application.getApplicationContext(), - new HashSet<>(Collections.singleton(mTransport2.componentName)), - null /* defaultTransport */, - mTransportBoundListenerStub, - ShadowLooper.getMainLooper()); - transportManager.onPackageAdded(PACKAGE_NAME); - - assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn( - Collections.singleton(mTransport2.componentName)); - assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn( - Collections.singleton(mTransport2.name)); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface)) - .isFalse(); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface)) - .isTrue(); - } - - @Test - public void onPackageAdded_appIsNotPrivileged_doesNotBindToTransports() throws Exception { - setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2), 0); - - TransportManager transportManager = new TransportManager( - RuntimeEnvironment.application.getApplicationContext(), - new HashSet<>(Arrays.asList( - mTransport1.componentName, mTransport2.componentName)), - null /* defaultTransport */, - mTransportBoundListenerStub, - ShadowLooper.getMainLooper()); - transportManager.onPackageAdded(PACKAGE_NAME); - - assertThat(transportManager.getAllTransportComponents()).isEmpty(); - assertThat(transportManager.getBoundTransportNames()).isEmpty(); - assertThat(mTransportBoundListenerStub.isCalled()).isFalse(); - } - - @Test - public void onPackageRemoved_transportsUnbound() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); - - transportManager.onPackageRemoved(PACKAGE_NAME); - - assertThat(transportManager.getAllTransportComponents()).isEmpty(); - assertThat(transportManager.getBoundTransportNames()).isEmpty(); - } - - @Test - public void onPackageRemoved_incorrectPackageName_nothingHappens() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); - - transportManager.onPackageRemoved(ANOTHER_PACKAGE_NAME); - - assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.componentName, mTransport2.componentName)); - assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.name, mTransport2.name)); - } - - @Test - public void onPackageChanged_oneComponentChanged_onlyOneTransportRebound() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); - - transportManager.onPackageChanged(PACKAGE_NAME, new String[]{mTransport2.name}); - - assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.componentName, mTransport2.componentName)); - assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.name, mTransport2.name)); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface)) - .isFalse(); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface)) - .isTrue(); - } - - @Test - public void onPackageChanged_nothingChanged_noTransportsRebound() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); - - transportManager.onPackageChanged(PACKAGE_NAME, new String[0]); - - assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.componentName, mTransport2.componentName)); - assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.name, mTransport2.name)); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface)) - .isFalse(); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface)) - .isFalse(); - } - - @Test - public void onPackageChanged_unexpectedComponentChanged_noTransportsRebound() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); - - transportManager.onPackageChanged(PACKAGE_NAME, new String[]{"unexpected.component"}); - - assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.componentName, mTransport2.componentName)); - assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.name, mTransport2.name)); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface)) - .isFalse(); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface)) - .isFalse(); - } - - @Test - public void onPackageChanged_transportsRebound() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); - - transportManager.onPackageChanged(PACKAGE_NAME, new String[]{mTransport2.name}); - - assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.componentName, mTransport2.componentName)); - assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn( - Arrays.asList(mTransport1.name, mTransport2.name)); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface)) - .isFalse(); - assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface)) - .isTrue(); - } - - @Test - public void getTransportBinder_returnsCorrectBinder() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); - - assertThat(transportManager.getTransportBinder(mTransport1.name)).isEqualTo( - mTransport1.binderInterface); - assertThat(transportManager.getTransportBinder(mTransport2.name)).isEqualTo( - mTransport2.binderInterface); - } - - @Test - public void getTransportBinder_incorrectTransportName_returnsNull() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); - - assertThat(transportManager.getTransportBinder("incorrect.transport")).isNull(); - } - - @Test - public void getTransportBinder_oneTransportUnavailable_returnsCorrectBinder() throws Exception { + public void testRegisterTransports() throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpPackage(PACKAGE_B, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2, mTransportB1); TransportManager transportManager = - createTransportManagerAndSetUpTransports(Collections.singletonList(mTransport2), - Collections.singletonList(mTransport1), mTransport1.name); + createTransportManager(mTransportA1, mTransportA2, mTransportB1); - assertThat(transportManager.getTransportBinder(mTransport1.name)).isNull(); - assertThat(transportManager.getTransportBinder(mTransport2.name)).isEqualTo( - mTransport2.binderInterface); + transportManager.registerTransports(); + + assertRegisteredTransports( + transportManager, asList(mTransportA1, mTransportA2, mTransportB1)); + + verify(mListener) + .onTransportRegistered(mTransportA1.transportName, mTransportA1.transportDirName); + verify(mListener) + .onTransportRegistered(mTransportA2.transportName, mTransportA2.transportDirName); + verify(mListener) + .onTransportRegistered(mTransportB1.transportName, mTransportB1.transportDirName); } @Test - public void getCurrentTransport_selectTransportNotCalled_returnsDefaultTransport() + public void + testRegisterTransports_whenOneTransportUnavailable_doesNotRegisterUnavailableTransport() + throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + TransportData transport1 = mTransportA1.unavailable(); + TransportData transport2 = mTransportA2; + setUpTransports(transport1, transport2); + TransportManager transportManager = createTransportManager(transport1, transport2); + + transportManager.registerTransports(); + + assertRegisteredTransports(transportManager, singletonList(transport2)); + verify(mListener, never()) + .onTransportRegistered(transport1.transportName, transport1.transportDirName); + verify(mListener) + .onTransportRegistered(transport2.transportName, transport2.transportDirName); + } + + @Test + public void testRegisterTransports_whenWhitelistIsEmpty_doesNotRegisterTransports() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = createTransportManager(null); - assertThat(transportManager.getCurrentTransportName()).isEqualTo(mTransport1.name); + transportManager.registerTransports(); + + assertRegisteredTransports(transportManager, emptyList()); + verify(mListener, never()).onTransportRegistered(any(), any()); } @Test - public void getCurrentTransport_selectTransportCalled_returnsCorrectTransport() + public void + testRegisterTransports_whenOnlyOneTransportWhitelisted_onlyRegistersWhitelistedTransport() + throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = createTransportManager(null, mTransportA1); + + transportManager.registerTransports(); + + assertRegisteredTransports(transportManager, singletonList(mTransportA1)); + verify(mListener) + .onTransportRegistered(mTransportA1.transportName, mTransportA1.transportDirName); + verify(mListener, never()) + .onTransportRegistered(mTransportA2.transportName, mTransportA2.transportDirName); + } + + @Test + public void testRegisterTransports_whenAppIsNotPrivileged_doesNotRegisterTransports() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); - - assertThat(transportManager.getCurrentTransportName()).isEqualTo(mTransport1.name); - - transportManager.selectTransport(mTransport2.name); - - assertThat(transportManager.getCurrentTransportName()).isEqualTo(mTransport2.name); - } - - @Test - public void getCurrentTransportBinder_returnsCorrectBinder() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); - - assertThat(transportManager.getCurrentTransportBinder()) - .isEqualTo(mTransport1.binderInterface); - } - - @Test - public void getCurrentTransportBinder_transportNotBound_returnsNull() throws Exception { + // Note ApplicationInfo.PRIVATE_FLAG_PRIVILEGED is missing from flags + setUpPackage(PACKAGE_A, 0); + setUpTransports(mTransportA1, mTransportA2); TransportManager transportManager = - createTransportManagerAndSetUpTransports(Collections.singletonList(mTransport2), - Collections.singletonList(mTransport1), mTransport2.name); + createTransportManager(null, mTransportA1, mTransportA2); - transportManager.selectTransport(mTransport1.name); + transportManager.registerTransports(); - assertThat(transportManager.getCurrentTransportBinder()).isNull(); + assertRegisteredTransports(transportManager, emptyList()); + verify(mListener, never()).onTransportRegistered(any(), any()); } @Test - public void getTransportName_returnsCorrectTransportName() throws Exception { - TransportManager transportManager = createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); + public void testOnPackageAdded_registerTransports() throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1); + TransportManager transportManager = createTransportManager(mTransportA1); - assertThat(transportManager.getTransportName(mTransport1.binderInterface)) - .isEqualTo(mTransport1.name); - assertThat(transportManager.getTransportName(mTransport2.binderInterface)) - .isEqualTo(mTransport2.name); + transportManager.onPackageAdded(PACKAGE_A); + + assertRegisteredTransports(transportManager, asList(mTransportA1)); + verify(mListener) + .onTransportRegistered(mTransportA1.transportName, mTransportA1.transportDirName); } @Test - public void getTransportName_transportNotBound_returnsNull() throws Exception { - TransportManager transportManager = - createTransportManagerAndSetUpTransports(Collections.singletonList(mTransport2), - Collections.singletonList(mTransport1), mTransport1.name); + public void testOnPackageRemoved_unregisterTransports() throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpPackage(PACKAGE_B, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportB1); + TransportManager transportManager = createTransportManager(mTransportA1, mTransportB1); + transportManager.registerTransports(); - assertThat(transportManager.getTransportName(mTransport1.binderInterface)).isNull(); - assertThat(transportManager.getTransportName(mTransport2.binderInterface)) - .isEqualTo(mTransport2.name); + transportManager.onPackageRemoved(PACKAGE_A); + + assertRegisteredTransports(transportManager, singletonList(mTransportB1)); } @Test - public void getTransportWhitelist_returnsCorrectWhiteList() throws Exception { - TransportManager transportManager = new TransportManager( - RuntimeEnvironment.application.getApplicationContext(), - new HashSet<>(Arrays.asList(mTransport1.componentName, mTransport2.componentName)), - mTransport1.name, - mTransportBoundListenerStub, - ShadowLooper.getMainLooper()); + public void testOnPackageRemoved_whenUnknownPackage_nothingHappens() throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1); + TransportManager transportManager = createTransportManager(mTransportA1); + transportManager.registerTransports(); - assertThat(transportManager.getTransportWhitelist()).containsExactlyElementsIn( - Arrays.asList(mTransport1.componentName, mTransport2.componentName)); + transportManager.onPackageRemoved(PACKAGE_A + "unknown"); + + assertRegisteredTransports(transportManager, singletonList(mTransportA1)); } @Test - public void getTransportWhitelist_whiteListIsNull_returnsEmptyArray() throws Exception { - TransportManager transportManager = new TransportManager( - RuntimeEnvironment.application.getApplicationContext(), - null /* whitelist */, - mTransport1.name, - mTransportBoundListenerStub, - ShadowLooper.getMainLooper()); - - assertThat(transportManager.getTransportWhitelist()).isEmpty(); - } - - @Test - public void selectTransport_setsTransportCorrectlyAndReturnsPreviousTransport() + public void testOnPackageChanged_whenOneComponentChanged_onlyOneTransportReRegistered() throws Exception { - TransportManager transportManager = new TransportManager( - RuntimeEnvironment.application.getApplicationContext(), - null /* whitelist */, - mTransport1.name, - mTransportBoundListenerStub, - ShadowLooper.getMainLooper()); + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2); + transportManager.registerTransports(); + // Reset listener to verify calls after registerTransports() above + reset(mListener); - assertThat(transportManager.selectTransport(mTransport2.name)).isEqualTo(mTransport1.name); - assertThat(transportManager.selectTransport(mTransport1.name)).isEqualTo(mTransport2.name); + transportManager.onPackageChanged( + PACKAGE_A, mTransportA1.getTransportComponent().getClassName()); + + assertRegisteredTransports(transportManager, asList(mTransportA1, mTransportA2)); + verify(mListener) + .onTransportRegistered(mTransportA1.transportName, mTransportA1.transportDirName); + verify(mListener, never()) + .onTransportRegistered(mTransportA2.transportName, mTransportA2.transportDirName); } @Test - public void getTransportClient_forRegisteredTransport_returnCorrectly() throws Exception { + public void testOnPackageChanged_whenNoComponentsChanged_doesNotRegisterTransports() + throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1); + TransportManager transportManager = createTransportManager(mTransportA1); + transportManager.registerTransports(); + reset(mListener); + + transportManager.onPackageChanged(PACKAGE_A); + + assertRegisteredTransports(transportManager, singletonList(mTransportA1)); + verify(mListener, never()).onTransportRegistered(any(), any()); + } + + @Test + public void testOnPackageChanged_whenUnknownComponentChanged_noTransportsRegistered() + throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1); + TransportManager transportManager = createTransportManager(mTransportA1); + transportManager.registerTransports(); + reset(mListener); + + transportManager.onPackageChanged(PACKAGE_A, PACKAGE_A + ".UnknownComponent"); + + assertRegisteredTransports(transportManager, singletonList(mTransportA1)); + verify(mListener, never()).onTransportRegistered(any(), any()); + } + + @Test + public void testOnPackageChanged_reRegisterTransports() throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2); + transportManager.registerTransports(); + reset(mListener); + + transportManager.onPackageChanged( + PACKAGE_A, + mTransportA1.getTransportComponent().getClassName(), + mTransportA2.getTransportComponent().getClassName()); + + assertRegisteredTransports(transportManager, asList(mTransportA1, mTransportA2)); + verify(mListener) + .onTransportRegistered(mTransportA1.transportName, mTransportA1.transportDirName); + verify(mListener) + .onTransportRegistered(mTransportA2.transportName, mTransportA2.transportDirName); + } + + @Test + public void testGetCurrentTransportName_whenSelectTransportNotCalled_returnsDefaultTransport() + throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2); + transportManager.registerTransports(); + + String currentTransportName = transportManager.getCurrentTransportName(); + + assertThat(currentTransportName).isEqualTo(mTransportA1.transportName); + } + + @Test + public void testGetCurrentTransport_whenSelectTransportCalled_returnsSelectedTransport() + throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2); + transportManager.registerTransports(); + transportManager.selectTransport(mTransportA2.transportName); + + String currentTransportName = transportManager.getCurrentTransportName(); + + assertThat(currentTransportName).isEqualTo(mTransportA2.transportName); + } + + @Test + public void testGetTransportWhitelist() throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2); + + Set transportWhitelist = transportManager.getTransportWhitelist(); + + assertThat(transportWhitelist) + .containsExactlyElementsIn( + asList( + mTransportA1.getTransportComponent(), + mTransportA2.getTransportComponent())); + } + + @Test + public void testSelectTransport() throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); TransportManager transportManager = - createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); + createTransportManager(null, mTransportA1, mTransportA2); + + String transport1 = transportManager.selectTransport(mTransportA1.transportName); + String transport2 = transportManager.selectTransport(mTransportA2.transportName); + + assertThat(transport1).isNull(); + assertThat(transport2).isEqualTo(mTransportA1.transportName); + } + + @Test + public void testGetTransportClient_forRegisteredTransport() throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2); + transportManager.registerTransports(); TransportClient transportClient = - transportManager.getTransportClient(mTransport1.name, "caller"); + transportManager.getTransportClient(mTransportA1.transportName, "caller"); - assertThat(transportClient.getTransportComponent()).isEqualTo(mTransport1.componentName); + assertThat(transportClient.getTransportComponent()) + .isEqualTo(mTransportA1.getTransportComponent()); } @Test - public void getTransportClient_forOldNameOfTransportThatChangedName_returnsNull() + public void testGetTransportClient_forOldNameOfTransportThatChangedName_returnsNull() throws Exception { - TransportManager transportManager = - createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2); + transportManager.registerTransports(); transportManager.updateTransportAttributes( - mTransport1.componentName, "newName", null, "destinationString", null, null); + mTransportA1.getTransportComponent(), + "newName", + null, + "destinationString", + null, + null); TransportClient transportClient = - transportManager.getTransportClient(mTransport1.name, "caller"); + transportManager.getTransportClient(mTransportA1.transportName, "caller"); assertThat(transportClient).isNull(); } @Test - public void getTransportClient_forNewNameOfTransportThatChangedName_returnsCorrectly() + public void testGetTransportClient_forNewNameOfTransportThatChangedName_returnsCorrectly() throws Exception { - TransportManager transportManager = - createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2); + transportManager.registerTransports(); transportManager.updateTransportAttributes( - mTransport1.componentName, "newName", null, "destinationString", null, null); + mTransportA1.getTransportComponent(), + "newName", + null, + "destinationString", + null, + null); - TransportClient transportClient = - transportManager.getTransportClient("newName", "caller"); + TransportClient transportClient = transportManager.getTransportClient("newName", "caller"); - assertThat(transportClient.getTransportComponent()).isEqualTo(mTransport1.componentName); + assertThat(transportClient.getTransportComponent()) + .isEqualTo(mTransportA1.getTransportComponent()); } @Test - public void getTransportName_forTransportThatChangedName_returnsNewName() - throws Exception { - TransportManager transportManager = - createTransportManagerAndSetUpTransports( - Arrays.asList(mTransport1, mTransport2), mTransport1.name); + public void testGetTransportName_forTransportThatChangedName_returnsNewName() throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1, mTransportA2); + TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2); + transportManager.registerTransports(); transportManager.updateTransportAttributes( - mTransport1.componentName, "newName", null, "destinationString", null, null); + mTransportA1.getTransportComponent(), + "newName", + null, + "destinationString", + null, + null); - String transportName = transportManager.getTransportName(mTransport1.componentName); + String transportName = + transportManager.getTransportName(mTransportA1.getTransportComponent()); assertThat(transportName).isEqualTo("newName"); } @Test - public void isTransportRegistered_returnsCorrectly() throws Exception { - TransportManager transportManager = - createTransportManagerAndSetUpTransports( - Collections.singletonList(mTransport1), - Collections.singletonList(mTransport2), - mTransport1.name); + public void testIsTransportRegistered() throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1); + TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2); + transportManager.registerTransports(); - assertThat(transportManager.isTransportRegistered(mTransport1.name)).isTrue(); - assertThat(transportManager.isTransportRegistered(mTransport2.name)).isFalse(); + boolean isTransportA1Registered = + transportManager.isTransportRegistered(mTransportA1.transportName); + boolean isTransportA2Registered = + transportManager.isTransportRegistered(mTransportA2.transportName); + + assertThat(isTransportA1Registered).isTrue(); + assertThat(isTransportA2Registered).isFalse(); } @Test - public void getTransportAttributes_forRegisteredTransport_returnsCorrectValues() + public void testGetTransportAttributes_forRegisteredTransport_returnsCorrectValues() throws Exception { - TransportManager transportManager = - createTransportManagerAndSetUpTransports( - Collections.singletonList(mTransport1), - mTransport1.name); + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1); + TransportManager transportManager = createTransportManager(mTransportA1); + transportManager.registerTransports(); - assertThat(transportManager.getTransportConfigurationIntent(mTransport1.name)) - .isEqualTo(mTransport1.binderInterface.configurationIntent()); - assertThat(transportManager.getTransportDataManagementIntent(mTransport1.name)) - .isEqualTo(mTransport1.binderInterface.dataManagementIntent()); - assertThat(transportManager.getTransportDataManagementLabel(mTransport1.name)) - .isEqualTo(mTransport1.binderInterface.dataManagementLabel()); - assertThat(transportManager.getTransportDirName(mTransport1.name)) - .isEqualTo(mTransport1.binderInterface.transportDirName()); + Intent configurationIntent = + transportManager.getTransportConfigurationIntent(mTransportA1.transportName); + Intent dataManagementIntent = + transportManager.getTransportDataManagementIntent(mTransportA1.transportName); + String dataManagementLabel = + transportManager.getTransportDataManagementLabel(mTransportA1.transportName); + String transportDirName = transportManager.getTransportDirName(mTransportA1.transportName); + + assertThat(configurationIntent).isEqualTo(mTransportA1.configurationIntent); + assertThat(dataManagementIntent).isEqualTo(mTransportA1.dataManagementIntent); + assertThat(dataManagementLabel).isEqualTo(mTransportA1.dataManagementLabel); + assertThat(transportDirName).isEqualTo(mTransportA1.transportDirName); } @Test - public void getTransportAttributes_forUnregisteredTransport_throws() - throws Exception { - TransportManager transportManager = - createTransportManagerAndSetUpTransports( - Collections.singletonList(mTransport1), - Collections.singletonList(mTransport2), - mTransport1.name); + public void testGetTransportAttributes_forUnregisteredTransport_throws() throws Exception { + setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); + setUpTransports(mTransportA1); + TransportManager transportManager = createTransportManager(mTransportA1); + transportManager.registerTransports(); expectThrows( TransportNotRegisteredException.class, - () -> transportManager.getTransportConfigurationIntent(mTransport2.name)); + () -> transportManager.getTransportConfigurationIntent(mTransportA2.transportName)); expectThrows( TransportNotRegisteredException.class, - () -> transportManager.getTransportDataManagementIntent( - mTransport2.name)); + () -> + transportManager.getTransportDataManagementIntent( + mTransportA2.transportName)); expectThrows( TransportNotRegisteredException.class, - () -> transportManager.getTransportDataManagementLabel(mTransport2.name)); + () -> transportManager.getTransportDataManagementLabel(mTransportA2.transportName)); expectThrows( TransportNotRegisteredException.class, - () -> transportManager.getTransportDirName(mTransport2.name)); + () -> transportManager.getTransportDirName(mTransportA2.transportName)); } - private void setUpPackageWithTransports(String packageName, List transports, - int flags) throws Exception { + private List setUpTransports(TransportData... transports) throws Exception { + setUpTransportsForTransportManager(mShadowPackageManager, transports); + List transportMocks = new ArrayList<>(transports.length); + for (TransportData transport : transports) { + TransportMock transportMock = mockTransport(transport); + when(mTransportClientManager.getTransportClient( + eq(transport.getTransportComponent()), any())) + .thenReturn(transportMock.transportClient); + transportMocks.add(transportMock); + } + return transportMocks; + } + + private void setUpPackage(String packageName, int flags) { PackageInfo packageInfo = new PackageInfo(); packageInfo.packageName = packageName; packageInfo.applicationInfo = new ApplicationInfo(); packageInfo.applicationInfo.privateFlags = flags; - - mPackageManagerShadow.addPackage(packageInfo); - - List transportsInfo = new ArrayList<>(); - for (TransportInfo transport : transports) { - ResolveInfo info = new ResolveInfo(); - info.serviceInfo = new ServiceInfo(); - info.serviceInfo.packageName = packageName; - info.serviceInfo.name = transport.name; - transportsInfo.add(info); - } - - Intent intent = new Intent(TransportManager.SERVICE_ACTION_TRANSPORT_HOST); - intent.setPackage(packageName); - - mPackageManagerShadow.addResolveInfoForIntent(intent, transportsInfo); + mShadowPackageManager.addPackage(packageInfo); } - private TransportManager createTransportManagerAndSetUpTransports( - List availableTransports, String defaultTransportName) throws Exception { - return createTransportManagerAndSetUpTransports(availableTransports, - Collections.emptyList(), defaultTransportName); - } - - private TransportManager createTransportManagerAndSetUpTransports( - List availableTransports, List unavailableTransports, - String defaultTransportName) - throws Exception { - List availableTransportsNames = new ArrayList<>(); - List availableTransportsComponentNames = new ArrayList<>(); - for (TransportInfo transport : availableTransports) { - availableTransportsNames.add(transport.name); - availableTransportsComponentNames.add(transport.componentName); - } - - List allTransportsComponentNames = new ArrayList<>(); - allTransportsComponentNames.addAll(availableTransportsComponentNames); - for (TransportInfo transport : unavailableTransports) { - allTransportsComponentNames.add(transport.componentName); - } - - for (TransportInfo transport : unavailableTransports) { - ShadowContextImplForBackup.sUnbindableComponents.add(transport.componentName); - } - - setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2), - ApplicationInfo.PRIVATE_FLAG_PRIVILEGED); - - TransportManager transportManager = new TransportManager( - RuntimeEnvironment.application.getApplicationContext(), - new HashSet<>(allTransportsComponentNames), - defaultTransportName, - mTransportBoundListenerStub, - ShadowLooper.getMainLooper()); - transportManager.onPackageAdded(PACKAGE_NAME); - - assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn( - availableTransportsComponentNames); - assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn( - availableTransportsNames); - for (TransportInfo transport : availableTransports) { - assertThat(mTransportBoundListenerStub.isCalledForTransport(transport.binderInterface)) - .isTrue(); - } - for (TransportInfo transport : unavailableTransports) { - assertThat(mTransportBoundListenerStub.isCalledForTransport(transport.binderInterface)) - .isFalse(); - } - - mTransportBoundListenerStub.resetState(); - + private TransportManager createTransportManager( + @Nullable TransportData selectedTransport, TransportData... transports) { + Set whitelist = + concat(Stream.of(selectedTransport), Stream.of(transports)) + .filter(Objects::nonNull) + .map(TransportData::getTransportComponent) + .collect(toSet()); + TransportManager transportManager = + new TransportManager( + mContext, + whitelist, + selectedTransport != null ? selectedTransport.transportName : null, + mTransportClientManager); + transportManager.setOnTransportRegisteredListener(mListener); return transportManager; } - private static class TransportInfo { - public final String packageName; - public final String name; - public final ComponentName componentName; - public final IBackupTransport binderInterface; - public final IBinder binder; - - TransportInfo( - String packageName, - String name, - @Nullable Intent configurationIntent, - String currentDestinationString, - @Nullable Intent dataManagementIntent, - String dataManagementLabel) { - this.packageName = packageName; - this.name = name; - this.componentName = new ComponentName(packageName, name); - this.binder = mock(IBinder.class); - IBackupTransport transport = mock(IBackupTransport.class); - try { - when(transport.name()).thenReturn(name); - when(transport.configurationIntent()).thenReturn(configurationIntent); - when(transport.currentDestinationString()).thenReturn(currentDestinationString); - when(transport.dataManagementIntent()).thenReturn(dataManagementIntent); - when(transport.dataManagementLabel()).thenReturn(dataManagementLabel); - } catch (RemoteException e) { - // Only here to mock methods that throw RemoteException - } - this.binderInterface = transport; - } + private void assertRegisteredTransports( + TransportManager transportManager, List transports) { + assertThat(transportManager.getRegisteredTransportComponents()) + .asList() + .containsExactlyElementsIn( + transports + .stream() + .map(TransportData::getTransportComponent) + .collect(toList())); + assertThat(transportManager.getRegisteredTransportNames()) + .asList() + .containsExactlyElementsIn( + transports.stream().map(t -> t.transportName).collect(toList())); } - } diff --git a/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java b/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java index dfca9010130f2..ace0441c8a4a7 100644 --- a/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java +++ b/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java @@ -19,8 +19,10 @@ 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.android.server.backup.testing.TransportData.backupTransport; +import static com.android.server.backup.testing.TransportData.d2dTransport; +import static com.android.server.backup.testing.TransportData.localTransport; +import static com.android.server.backup.testing.TransportTestUtils.setUpTransports; import static com.google.common.truth.Truth.assertThat; @@ -28,7 +30,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -44,7 +45,8 @@ 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.testing.TransportData; +import com.android.server.backup.testing.TransportTestUtils.TransportMock; import com.android.server.backup.transport.TransportClient; import com.android.server.testing.FrameworkRobolectricTestRunner; import com.android.server.testing.SystemLoaderClasses; @@ -58,7 +60,10 @@ import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import java.io.File; +import java.util.Arrays; +import java.util.Iterator; import java.util.List; +import java.util.stream.Stream; @RunWith(FrameworkRobolectricTestRunner.class) @Config(manifest = Config.NONE, sdk = 26) @@ -68,16 +73,21 @@ public class PerformInitializeTaskTest { @Mock private RefactoredBackupManagerService mBackupManagerService; @Mock private TransportManager mTransportManager; @Mock private OnTaskFinishedListener mListener; - @Mock private IBackupTransport mTransport; + @Mock private IBackupTransport mTransportBinder; @Mock private IBackupObserver mObserver; @Mock private AlarmManager mAlarmManager; @Mock private PendingIntent mRunInitIntent; private File mBaseStateDir; + private TransportData mTransport; + private String mTransportName; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + mTransport = backupTransport(); + mTransportName = mTransport.transportName; + Application context = RuntimeEnvironment.application; mBaseStateDir = new File(context.getCacheDir(), "base_state_dir"); assertThat(mBaseStateDir.mkdir()).isTrue(); @@ -88,82 +98,76 @@ public class PerformInitializeTaskTest { @Test public void testRun_callsTransportCorrectly() throws Exception { - setUpTransport(TRANSPORT_NAME); - configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_OK); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + setUpTransport(mTransport); + configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_OK); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); - verify(mTransport).initializeDevice(); - verify(mTransport).finishBackup(); + verify(mTransportBinder).initializeDevice(); + verify(mTransportBinder).finishBackup(); } @Test public void testRun_callsBackupManagerCorrectly() throws Exception { - setUpTransport(TRANSPORT_NAME); - configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_OK); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + setUpTransport(mTransport); + configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_OK); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); verify(mBackupManagerService) - .recordInitPending( - false, TRANSPORT_NAME, TransportTestUtils.transportDirName(TRANSPORT_NAME)); + .recordInitPending(false, mTransportName, mTransport.transportDirName); verify(mBackupManagerService) - .resetBackupState( - eq( - new File( - mBaseStateDir, - TransportTestUtils.transportDirName(TRANSPORT_NAME)))); + .resetBackupState(eq(new File(mBaseStateDir, mTransport.transportDirName))); } @Test public void testRun_callsObserverAndListenerCorrectly() throws Exception { - setUpTransport(TRANSPORT_NAME); - configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_OK); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + setUpTransport(mTransport); + configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_OK); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); - verify(mObserver).onResult(eq(TRANSPORT_NAME), eq(TRANSPORT_OK)); + verify(mObserver).onResult(eq(mTransportName), eq(TRANSPORT_OK)); verify(mObserver).backupFinished(eq(TRANSPORT_OK)); verify(mListener).onFinished(any()); } @Test public void testRun_whenInitializeDeviceFails() throws Exception { - setUpTransport(TRANSPORT_NAME); - configureTransport(mTransport, TRANSPORT_ERROR, 0); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + setUpTransport(mTransport); + configureTransport(mTransportBinder, TRANSPORT_ERROR, 0); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); - verify(mTransport).initializeDevice(); - verify(mTransport, never()).finishBackup(); + verify(mTransportBinder).initializeDevice(); + verify(mTransportBinder, never()).finishBackup(); verify(mBackupManagerService) - .recordInitPending( - true, TRANSPORT_NAME, TransportTestUtils.transportDirName(TRANSPORT_NAME)); + .recordInitPending(true, mTransportName, mTransport.transportDirName); } @Test public void testRun_whenInitializeDeviceFails_callsObserverAndListenerCorrectly() throws Exception { - setUpTransport(TRANSPORT_NAME); - configureTransport(mTransport, TRANSPORT_ERROR, 0); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + setUpTransport(mTransport); + configureTransport(mTransportBinder, TRANSPORT_ERROR, 0); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); - verify(mObserver).onResult(eq(TRANSPORT_NAME), eq(TRANSPORT_ERROR)); + verify(mObserver).onResult(eq(mTransportName), eq(TRANSPORT_ERROR)); verify(mObserver).backupFinished(eq(TRANSPORT_ERROR)); verify(mListener).onFinished(any()); } @Test public void testRun_whenInitializeDeviceFails_schedulesAlarm() throws Exception { - setUpTransport(TRANSPORT_NAME); - configureTransport(mTransport, TRANSPORT_ERROR, 0); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + setUpTransport(mTransport); + configureTransport(mTransportBinder, TRANSPORT_ERROR, 0); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); @@ -172,37 +176,36 @@ public class PerformInitializeTaskTest { @Test public void testRun_whenFinishBackupFails() throws Exception { - setUpTransport(TRANSPORT_NAME); - configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_ERROR); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + setUpTransport(mTransport); + configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_ERROR); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); - verify(mTransport).initializeDevice(); - verify(mTransport).finishBackup(); + verify(mTransportBinder).initializeDevice(); + verify(mTransportBinder).finishBackup(); verify(mBackupManagerService) - .recordInitPending( - true, TRANSPORT_NAME, TransportTestUtils.transportDirName(TRANSPORT_NAME)); + .recordInitPending(true, mTransportName, mTransport.transportDirName); } @Test public void testRun_whenFinishBackupFails_callsObserverAndListenerCorrectly() throws Exception { - setUpTransport(TRANSPORT_NAME); - configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_ERROR); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + setUpTransport(mTransport); + configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_ERROR); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); - verify(mObserver).onResult(eq(TRANSPORT_NAME), eq(TRANSPORT_ERROR)); + verify(mObserver).onResult(eq(mTransportName), eq(TRANSPORT_ERROR)); verify(mObserver).backupFinished(eq(TRANSPORT_ERROR)); verify(mListener).onFinished(any()); } @Test public void testRun_whenFinishBackupFails_schedulesAlarm() throws Exception { - setUpTransport(TRANSPORT_NAME); - configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_ERROR); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + setUpTransport(mTransport); + configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_ERROR); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); @@ -211,64 +214,76 @@ public class PerformInitializeTaskTest { @Test public void testRun_whenOnlyOneTransportFails() throws Exception { - List 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); + TransportData transport1 = backupTransport(); + TransportData transport2 = d2dTransport(); + List transportMocks = + setUpTransports(mTransportManager, transport1, transport2); + configureTransport(transportMocks.get(0).transport, TRANSPORT_ERROR, 0); + configureTransport(transportMocks.get(1).transport, TRANSPORT_OK, TRANSPORT_OK); PerformInitializeTask performInitializeTask = - createPerformInitializeTask(TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]); + createPerformInitializeTask(transport1.transportName, transport2.transportName); performInitializeTask.run(); - verify(transports.get(1).transportMock).initializeDevice(); - verify(mObserver).onResult(eq(TRANSPORT_NAMES[0]), eq(TRANSPORT_ERROR)); - verify(mObserver).onResult(eq(TRANSPORT_NAMES[1]), eq(TRANSPORT_OK)); + verify(transportMocks.get(1).transport).initializeDevice(); + verify(mObserver).onResult(eq(transport1.transportName), eq(TRANSPORT_ERROR)); + verify(mObserver).onResult(eq(transport2.transportName), eq(TRANSPORT_OK)); verify(mObserver).backupFinished(eq(TRANSPORT_ERROR)); } @Test public void testRun_withMultipleTransports() throws Exception { - List 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); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAMES); + List transportMocks = + setUpTransports( + mTransportManager, backupTransport(), d2dTransport(), localTransport()); + configureTransport(transportMocks.get(0).transport, TRANSPORT_OK, TRANSPORT_OK); + configureTransport(transportMocks.get(1).transport, TRANSPORT_OK, TRANSPORT_OK); + configureTransport(transportMocks.get(2).transport, TRANSPORT_OK, TRANSPORT_OK); + String[] transportNames = + Stream.of(new TransportData[] {backupTransport(), d2dTransport(), localTransport()}) + .map(t -> t.transportName) + .toArray(String[]::new); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(transportNames); performInitializeTask.run(); - for (TransportData transport : transports) { + Iterator transportsIterator = + Arrays.asList( + new TransportData[] { + backupTransport(), d2dTransport(), localTransport() + }) + .iterator(); + for (TransportMock transportMock : transportMocks) { + TransportData transport = transportsIterator.next(); verify(mTransportManager).getTransportClient(eq(transport.transportName), any()); verify(mTransportManager) - .disposeOfTransportClient(eq(transport.transportClientMock), any()); + .disposeOfTransportClient(eq(transportMock.transportClient), any()); } } @Test public void testRun_whenOnlyOneTransportFails_disposesAllTransports() throws Exception { - List 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); + TransportData transport1 = backupTransport(); + TransportData transport2 = d2dTransport(); + List transportMocks = + setUpTransports(mTransportManager, transport1, transport2); + configureTransport(transportMocks.get(0).transport, TRANSPORT_ERROR, 0); + configureTransport(transportMocks.get(1).transport, TRANSPORT_OK, TRANSPORT_OK); PerformInitializeTask performInitializeTask = - createPerformInitializeTask(TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]); + createPerformInitializeTask(transport1.transportName, transport2.transportName); performInitializeTask.run(); verify(mTransportManager) - .disposeOfTransportClient(eq(transports.get(0).transportClientMock), any()); + .disposeOfTransportClient(eq(transportMocks.get(0).transportClient), any()); verify(mTransportManager) - .disposeOfTransportClient(eq(transports.get(1).transportClientMock), any()); + .disposeOfTransportClient(eq(transportMocks.get(1).transportClient), any()); } @Test public void testRun_whenTransportNotRegistered() throws Exception { - TransportTestUtils.setUpTransports( - mTransportManager, new TransportData(TRANSPORT_NAME, null, null)); - - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + setUpTransports(mTransportManager, mTransport.unregistered()); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); @@ -279,16 +294,15 @@ public class PerformInitializeTaskTest { @Test public void testRun_whenOnlyOneTransportNotRegistered() throws Exception { - List transports = - TransportTestUtils.setUpTransports( - mTransportManager, - new TransportData(TRANSPORT_NAMES[0], null, null), - new TransportData(TRANSPORT_NAMES[1])); - String registeredTransportName = transports.get(1).transportName; - IBackupTransport registeredTransport = transports.get(1).transportMock; - TransportClient registeredTransportClient = transports.get(1).transportClientMock; + TransportData transport1 = backupTransport().unregistered(); + TransportData transport2 = d2dTransport(); + List transportMocks = + setUpTransports(mTransportManager, transport1, transport2); + String registeredTransportName = transport2.transportName; + IBackupTransport registeredTransport = transportMocks.get(1).transport; + TransportClient registeredTransportClient = transportMocks.get(1).transportClient; PerformInitializeTask performInitializeTask = - createPerformInitializeTask(TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]); + createPerformInitializeTask(transport1.transportName, transport2.transportName); performInitializeTask.run(); @@ -299,25 +313,24 @@ public class PerformInitializeTaskTest { @Test public void testRun_whenTransportNotAvailable() throws Exception { - TransportClient transportClient = mock(TransportClient.class); - TransportTestUtils.setUpTransports( - mTransportManager, new TransportData(TRANSPORT_NAME, null, transportClient)); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + TransportMock transportMock = setUpTransport(mTransport.unavailable()); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); - verify(mTransportManager).disposeOfTransportClient(eq(transportClient), any()); + verify(mTransportManager) + .disposeOfTransportClient(eq(transportMock.transportClient), any()); verify(mObserver).backupFinished(eq(TRANSPORT_ERROR)); verify(mListener).onFinished(any()); } @Test public void testRun_whenTransportThrowsDeadObjectException() throws Exception { - TransportClient transportClient = mock(TransportClient.class); - TransportTestUtils.setUpTransports( - mTransportManager, new TransportData(TRANSPORT_NAME, mTransport, transportClient)); - when(mTransport.initializeDevice()).thenThrow(DeadObjectException.class); - PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME); + TransportMock transportMock = setUpTransport(mTransport); + IBackupTransport transport = transportMock.transport; + TransportClient transportClient = transportMock.transportClient; + when(transport.initializeDevice()).thenThrow(DeadObjectException.class); + PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName); performInitializeTask.run(); @@ -343,9 +356,10 @@ public class PerformInitializeTaskTest { when(transportMock.finishBackup()).thenReturn(finishBackupStatus); } - private void setUpTransport(String transportName) throws Exception { - TransportTestUtils.setUpTransport( - mTransportManager, - new TransportData(transportName, mTransport, mock(TransportClient.class))); + private TransportMock setUpTransport(TransportData transport) throws Exception { + TransportMock transportMock = + TransportTestUtils.setUpTransport(mTransportManager, transport); + mTransportBinder = transportMock.transport; + return transportMock; } } diff --git a/services/robotests/src/com/android/server/backup/testing/TestUtils.java b/services/robotests/src/com/android/server/backup/testing/TestUtils.java new file mode 100644 index 0000000000000..1be298d2c8c55 --- /dev/null +++ b/services/robotests/src/com/android/server/backup/testing/TestUtils.java @@ -0,0 +1,68 @@ +/* + * 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 com.android.internal.util.FunctionalUtils.ThrowingRunnable; + +import java.util.concurrent.Callable; + +public class TestUtils { + /** + * Calls {@link Runnable#run()} and returns if no exception is thrown. Otherwise, if the + * exception is unchecked, rethrow it; if it's checked wrap in a {@link RuntimeException} and + * throw. + * + *

Warning:DON'T use this outside tests. A wrapped checked exception is just a failure + * in a test. + */ + public static void uncheck(ThrowingRunnable runnable) { + try { + runnable.runOrThrow(); + } catch (Exception e) { + throw wrapIfChecked(e); + } + } + + /** + * Calls {@link Callable#call()} and returns the value if no exception is thrown. Otherwise, if + * the exception is unchecked, rethrow it; if it's checked wrap in a {@link RuntimeException} + * and throw. + * + *

Warning:DON'T use this outside tests. A wrapped checked exception is just a failure + * in a test. + */ + public static T uncheck(Callable callable) { + try { + return callable.call(); + } catch (Exception e) { + throw wrapIfChecked(e); + } + } + + /** + * Wrap {@code e} in a {@link RuntimeException} only if it's not one already, in which case it's + * returned. + */ + public static RuntimeException wrapIfChecked(Exception e) { + if (e instanceof RuntimeException) { + return (RuntimeException) e; + } + return new RuntimeException(e); + } + + private TestUtils() {} +} diff --git a/services/robotests/src/com/android/server/backup/testing/TransportBoundListenerStub.java b/services/robotests/src/com/android/server/backup/testing/TransportBoundListenerStub.java deleted file mode 100644 index 84ac2c212854d..0000000000000 --- a/services/robotests/src/com/android/server/backup/testing/TransportBoundListenerStub.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (C) 2017 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 com.android.internal.backup.IBackupTransport; -import com.android.server.backup.TransportManager; - -import java.util.HashSet; -import java.util.Set; - -/** - * Stub implementation of TransportBoundListener, which returns given result and can tell whether - * it was called for given transport. - */ -public class TransportBoundListenerStub implements - TransportManager.TransportBoundListener { - private boolean mAlwaysReturnSuccess; - private Set mTransportsCalledFor = new HashSet<>(); - - public TransportBoundListenerStub(boolean alwaysReturnSuccess) { - this.mAlwaysReturnSuccess = alwaysReturnSuccess; - } - - @Override - public boolean onTransportBound(IBackupTransport binder) { - mTransportsCalledFor.add(binder); - return mAlwaysReturnSuccess; - } - - /** - * Returns whether the listener was called for the specified transport at least once. - */ - public boolean isCalledForTransport(IBackupTransport binder) { - return mTransportsCalledFor.contains(binder); - } - - /** - * Returns whether the listener was called at least once. - */ - public boolean isCalled() { - return !mTransportsCalledFor.isEmpty(); - } - - /** - * Resets listener calls. - */ - public void resetState() { - mTransportsCalledFor.clear(); - } -} diff --git a/services/robotests/src/com/android/server/backup/testing/TransportData.java b/services/robotests/src/com/android/server/backup/testing/TransportData.java new file mode 100644 index 0000000000000..9feaa8efcf3d4 --- /dev/null +++ b/services/robotests/src/com/android/server/backup/testing/TransportData.java @@ -0,0 +1,149 @@ +/* + * 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.ComponentName; +import android.content.Intent; + +public class TransportData { + // No constants since new Intent() can't be called in static context because of Robolectric + public static TransportData backupTransport() { + return new TransportData( + "com.google.android.gms/.backup.BackupTransportService", + "com.google.android.gms/.backup.BackupTransportService", + "com.google.android.gms.backup.BackupTransportService", + new Intent(), + "user@gmail.com", + new Intent(), + "Google Account"); + } + + public static TransportData d2dTransport() { + return new TransportData( + "com.google.android.gms/.backup.migrate.service.D2dTransport", + "com.google.android.gms/.backup.component.D2dTransportService", + "d2dMigrateTransport", + null, + "Moving data to new device", + null, + ""); + } + + public static TransportData localTransport() { + return new TransportData( + "android/com.android.internal.backup.LocalTransport", + "android/com.android.internal.backup.LocalTransportService", + "com.android.internal.backup.LocalTransport", + null, + "Backing up to debug-only private cache", + null, + ""); + } + + public static TransportData genericTransport(String packageName, String className) { + return new TransportData( + packageName + "/." + className, + packageName + "/." + className + "Service", + packageName + "." + className, + new Intent(), + "currentDestinationString", + new Intent(), + "dataManagementLabel"); + } + + @TransportTestUtils.TransportStatus + public int transportStatus; + public final String transportName; + private final String transportComponentShort; + @Nullable + public String transportDirName; + @Nullable public Intent configurationIntent; + @Nullable public String currentDestinationString; + @Nullable public Intent dataManagementIntent; + @Nullable public String dataManagementLabel; + + private TransportData( + @TransportTestUtils.TransportStatus int transportStatus, + String transportName, + String transportComponentShort, + String transportDirName, + Intent configurationIntent, + String currentDestinationString, + Intent dataManagementIntent, + String dataManagementLabel) { + this.transportStatus = transportStatus; + this.transportName = transportName; + this.transportComponentShort = transportComponentShort; + this.transportDirName = transportDirName; + this.configurationIntent = configurationIntent; + this.currentDestinationString = currentDestinationString; + this.dataManagementIntent = dataManagementIntent; + this.dataManagementLabel = dataManagementLabel; + } + + public TransportData( + String transportName, + String transportComponentShort, + String transportDirName, + Intent configurationIntent, + String currentDestinationString, + Intent dataManagementIntent, + String dataManagementLabel) { + this( + TransportTestUtils.TransportStatus.REGISTERED_AVAILABLE, + transportName, + transportComponentShort, + transportDirName, + configurationIntent, + currentDestinationString, + dataManagementIntent, + dataManagementLabel); + } + + /** + * Not field because otherwise we'd have to call ComponentName::new in static context and + * Robolectric does not like this. + */ + public ComponentName getTransportComponent() { + return ComponentName.unflattenFromString(transportComponentShort); + } + + public TransportData unavailable() { + return new TransportData( + TransportTestUtils.TransportStatus.REGISTERED_UNAVAILABLE, + transportName, + transportComponentShort, + transportDirName, + configurationIntent, + currentDestinationString, + dataManagementIntent, + dataManagementLabel); + } + + public TransportData unregistered() { + return new TransportData( + TransportTestUtils.TransportStatus.UNREGISTERED, + transportName, + transportComponentShort, + transportDirName, + configurationIntent, + currentDestinationString, + dataManagementIntent, + dataManagementLabel); + } +} diff --git a/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java b/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java index 9770e407ec721..e1dc7b5e151e2 100644 --- a/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java +++ b/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java @@ -16,13 +16,23 @@ package com.android.server.backup.testing; +import static com.android.server.backup.testing.TestUtils.uncheck; + +import static org.junit.Assert.fail; 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 static java.util.stream.Collectors.toList; + import android.annotation.Nullable; import android.content.ComponentName; +import android.content.Intent; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.os.RemoteException; +import android.support.annotation.IntDef; import com.android.internal.backup.IBackupTransport; import com.android.server.backup.TransportManager; @@ -30,85 +40,82 @@ import com.android.server.backup.transport.TransportClient; import com.android.server.backup.transport.TransportNotAvailableException; import com.android.server.backup.transport.TransportNotRegisteredException; -import java.util.Arrays; +import org.robolectric.shadows.ShadowPackageManager; + import java.util.List; +import java.util.stream.Stream; 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" - }; + /** + * Differently from {@link #setUpTransports(TransportManager, TransportData...)}, which + * configures {@link TransportManager}, this is meant to mock the environment for a real + * TransportManager. + */ + public static void setUpTransportsForTransportManager( + ShadowPackageManager shadowPackageManager, TransportData... transports) + throws Exception { + for (TransportData transport : transports) { + ComponentName transportComponent = transport.getTransportComponent(); + String packageName = transportComponent.getPackageName(); + ResolveInfo resolveInfo = resolveInfo(transportComponent); + shadowPackageManager.addResolveInfoForIntent(transportIntent(), resolveInfo); + shadowPackageManager.addResolveInfoForIntent( + transportIntent().setPackage(packageName), resolveInfo); + } + } - public static final String TRANSPORT_NAME = TRANSPORT_NAMES[0]; + private static Intent transportIntent() { + return new Intent(TransportManager.SERVICE_ACTION_TRANSPORT_HOST); + } - /** {@code transportName} has to be in the {@link ComponentName} format (with '/') */ - 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; + private static ResolveInfo resolveInfo(ComponentName transportComponent) { + ResolveInfo resolveInfo = new ResolveInfo(); + resolveInfo.serviceInfo = new ServiceInfo(); + resolveInfo.serviceInfo.packageName = transportComponent.getPackageName(); + resolveInfo.serviceInfo.name = transportComponent.getClassName(); + return resolveInfo; } /** {@code transportName} has to be in the {@link ComponentName} format (with '/') */ - public static List setUpTransports( - TransportManager transportManager, String... transportNames) throws Exception { - return setUpTransports( - transportManager, - Arrays.stream(transportNames) - .map(TransportData::new) - .toArray(TransportData[]::new)); + public static TransportMock setUpCurrentTransport( + TransportManager transportManager, TransportData transport) throws Exception { + TransportMock transportMock = setUpTransports(transportManager, transport).get(0); + if (transportMock.transportClient != null) { + when(transportManager.getCurrentTransportClient(any())) + .thenReturn(transportMock.transportClient); + } + return transportMock; } /** @see #setUpTransport(TransportManager, TransportData) */ - public static List setUpTransports( + public static List setUpTransports( TransportManager transportManager, TransportData... transports) throws Exception { - for (TransportData transport : transports) { - setUpTransport(transportManager, transport); - } - return Arrays.asList(transports); + return Stream.of(transports) + .map(transport -> uncheck(() -> setUpTransport(transportManager, transport))) + .collect(toList()); } - /** - * Configures transport according to {@link TransportData}: - * - *

    - *
  • {@link TransportData#transportMock} {@code null} means transport not available. - *
  • {@link TransportData#transportClientMock} {@code null} means transport not registered. - *
- */ - public static void setUpTransport(TransportManager transportManager, TransportData transport) - throws Exception { + public static TransportMock setUpTransport( + TransportManager transportManager, TransportData transport) throws Exception { + int status = transport.transportStatus; String transportName = transport.transportName; - String transportDirName = transportDirName(transportName); - ComponentName transportComponent = transportComponentName(transportName); - IBackupTransport transportMock = transport.transportMock; - TransportClient transportClientMock = transport.transportClientMock; + ComponentName transportComponent = transport.getTransportComponent(); + String transportDirName = transport.transportDirName; - if (transportClientMock != null) { + TransportMock transportMock = mockTransport(transport); + if (status == TransportStatus.REGISTERED_AVAILABLE + || status == TransportStatus.REGISTERED_UNAVAILABLE) { // Transport registered when(transportManager.getTransportClient(eq(transportName), any())) - .thenReturn(transportClientMock); + .thenReturn(transportMock.transportClient); when(transportManager.getTransportClientOrThrow(eq(transportName), any())) - .thenReturn(transportClientMock); + .thenReturn(transportMock.transportClient); when(transportManager.getTransportName(transportComponent)).thenReturn(transportName); when(transportManager.getTransportDirName(eq(transportName))) .thenReturn(transportDirName); when(transportManager.getTransportDirName(eq(transportComponent))) .thenReturn(transportDirName); - when(transportClientMock.getTransportComponent()).thenReturn(transportComponent); - - if (transportMock != null) { - // Transport registered and available - when(transportClientMock.connectOrThrow(any())).thenReturn(transportMock); - when(transportMock.name()).thenReturn(transportName); - when(transportMock.transportDirName()).thenReturn(transportDirName); - } else { - // Transport registered but unavailable - when(transportClientMock.connectOrThrow(any())) - .thenThrow(TransportNotAvailableException.class); - } + // TODO: Mock rest of description methods } else { // Transport not registered when(transportManager.getTransportClient(eq(transportName), any())).thenReturn(null); @@ -121,34 +128,73 @@ public class TransportTestUtils { when(transportManager.getTransportDirName(eq(transportComponent))) .thenThrow(TransportNotRegisteredException.class); } + return transportMock; } - /** {@code transportName} has to be in the {@link ComponentName} format (with '/') */ - public static ComponentName transportComponentName(String transportName) { - return ComponentName.unflattenFromString(transportName); - } + public static TransportMock mockTransport(TransportData transport) throws Exception { + final TransportClient transportClientMock; + int status = transport.transportStatus; + ComponentName transportComponent = transport.getTransportComponent(); + if (status == TransportStatus.REGISTERED_AVAILABLE + || status == TransportStatus.REGISTERED_UNAVAILABLE) { + // Transport registered + transportClientMock = mock(TransportClient.class); + when(transportClientMock.getTransportComponent()).thenReturn(transportComponent); + if (status == TransportStatus.REGISTERED_AVAILABLE) { + // Transport registered and available + IBackupTransport transportMock = mockTransportBinder(transport); + when(transportClientMock.connectOrThrow(any())).thenReturn(transportMock); - public static String transportDirName(String transportName) { - return transportName + "_dir_name"; - } + return new TransportMock(transportClientMock, transportMock); + } else { + // Transport registered but unavailable + when(transportClientMock.connectOrThrow(any())) + .thenThrow(TransportNotAvailableException.class); - 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; + return new TransportMock(transportClientMock, null); + } + } else { + // Transport not registered + return new TransportMock(null, null); } + } - public TransportData(String transportName) { - this(transportName, mock(IBackupTransport.class), mock(TransportClient.class)); + private static IBackupTransport mockTransportBinder(TransportData transport) throws Exception { + IBackupTransport transportBinder = mock(IBackupTransport.class); + try { + when(transportBinder.name()).thenReturn(transport.transportName); + when(transportBinder.transportDirName()).thenReturn(transport.transportDirName); + when(transportBinder.configurationIntent()).thenReturn(transport.configurationIntent); + when(transportBinder.currentDestinationString()) + .thenReturn(transport.currentDestinationString); + when(transportBinder.dataManagementIntent()).thenReturn(transport.dataManagementIntent); + when(transportBinder.dataManagementLabel()).thenReturn(transport.dataManagementLabel); + } catch (RemoteException e) { + fail("RemoteException?"); } + return transportBinder; + } + + public static class TransportMock { + @Nullable public final TransportClient transportClient; + @Nullable public final IBackupTransport transport; + + private TransportMock( + @Nullable TransportClient transportClient, @Nullable IBackupTransport transport) { + this.transportClient = transportClient; + this.transport = transport; + } + } + + @IntDef({ + TransportStatus.REGISTERED_AVAILABLE, + TransportStatus.REGISTERED_UNAVAILABLE, + TransportStatus.UNREGISTERED + }) + public @interface TransportStatus { + int REGISTERED_AVAILABLE = 0; + int REGISTERED_UNAVAILABLE = 1; + int UNREGISTERED = 2; } private TransportTestUtils() {} diff --git a/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowContextImpl.java b/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowContextImpl.java new file mode 100644 index 0000000000000..6d220737f2fbd --- /dev/null +++ b/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowContextImpl.java @@ -0,0 +1,37 @@ +/* + * 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.testing.shadows; + +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.UserHandle; + +import org.robolectric.annotation.Implementation; +import org.robolectric.annotation.Implements; +import org.robolectric.shadows.ShadowContextImpl; + +@Implements(className = ShadowContextImpl.CLASS_NAME, inheritImplementationMethods = true) +public class FrameworkShadowContextImpl extends ShadowContextImpl { + @Implementation + public boolean bindServiceAsUser( + Intent service, + ServiceConnection connection, + int flags, + UserHandle user) { + return bindService(service, connection, flags); + } +} diff --git a/services/robotests/src/com/android/server/backup/testing/ShadowPackageManagerForBackup.java b/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowPackageManager.java similarity index 84% rename from services/robotests/src/com/android/server/backup/testing/ShadowPackageManagerForBackup.java rename to services/robotests/src/com/android/server/testing/shadows/FrameworkShadowPackageManager.java index b64b59d238d16..5cdbe7ff09ba9 100644 --- a/services/robotests/src/com/android/server/backup/testing/ShadowPackageManagerForBackup.java +++ b/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowPackageManager.java @@ -14,22 +14,18 @@ * limitations under the License */ -package com.android.server.backup.testing; +package com.android.server.testing.shadows; import android.app.ApplicationPackageManager; import android.content.Intent; import android.content.pm.ResolveInfo; - +import java.util.List; import org.robolectric.annotation.Implements; import org.robolectric.shadows.ShadowApplicationPackageManager; -import java.util.List; - -/** - * Implementation of PackageManager for Robolectric which handles queryIntentServicesAsUser(). - */ +/** Extension of ShadowApplicationPackageManager */ @Implements(value = ApplicationPackageManager.class, inheritImplementationMethods = true) -public class ShadowPackageManagerForBackup extends ShadowApplicationPackageManager { +public class FrameworkShadowPackageManager extends ShadowApplicationPackageManager { @Override public List queryIntentServicesAsUser(Intent intent, int flags, int userId) { return queryIntentServices(intent, flags); diff --git a/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java index 766d30d9f9a5c..7b4441ae30e60 100644 --- a/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java @@ -96,7 +96,7 @@ public class BackupManagerServiceTest { createBackupManagerService(); verify(mTransportManager) - .setTransportBoundListener(any(TransportManager.TransportBoundListener.class)); + .setOnTransportRegisteredListener(any()); } @Test