Merge "Binding on-demand #10: Remove permanent binding and add registration"

This commit is contained in:
TreeHugger Robot
2018-01-16 18:39:27 +00:00
committed by Android (Google) Code Review
14 changed files with 1375 additions and 1452 deletions

View File

@@ -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<ComponentName> 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");

View File

@@ -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);
}

View File

@@ -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

View File

@@ -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<String, Boolean> 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<TransportData> 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;
}
}

View File

@@ -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<TransportData> transports =
TransportTestUtils.setUpTransports(
mTransportManager, TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]);
configureTransport(transports.get(0).transportMock, TRANSPORT_ERROR, 0);
configureTransport(transports.get(1).transportMock, TRANSPORT_OK, TRANSPORT_OK);
TransportData transport1 = backupTransport();
TransportData transport2 = d2dTransport();
List<TransportMock> 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<TransportData> transports =
TransportTestUtils.setUpTransports(mTransportManager, TRANSPORT_NAMES);
configureTransport(transports.get(0).transportMock, TRANSPORT_OK, TRANSPORT_OK);
configureTransport(transports.get(1).transportMock, TRANSPORT_OK, TRANSPORT_OK);
configureTransport(transports.get(2).transportMock, TRANSPORT_OK, TRANSPORT_OK);
PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAMES);
List<TransportMock> 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<TransportData> 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<TransportData> transports =
TransportTestUtils.setUpTransports(
mTransportManager, TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]);
configureTransport(transports.get(0).transportMock, TRANSPORT_ERROR, 0);
configureTransport(transports.get(1).transportMock, TRANSPORT_OK, TRANSPORT_OK);
TransportData transport1 = backupTransport();
TransportData transport2 = d2dTransport();
List<TransportMock> 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<TransportData> 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<TransportMock> 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;
}
}

View File

@@ -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.
*
* <p><b>Warning:</b>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.
*
* <p><b>Warning:</b>DON'T use this outside tests. A wrapped checked exception is just a failure
* in a test.
*/
public static <T> T uncheck(Callable<T> 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() {}
}

View File

@@ -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<IBackupTransport> 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();
}
}

View File

@@ -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);
}
}

View File

@@ -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<TransportData> 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<TransportData> setUpTransports(
public static List<TransportMock> 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}:
*
* <ul>
* <li>{@link TransportData#transportMock} {@code null} means transport not available.
* <li>{@link TransportData#transportClientMock} {@code null} means transport not registered.
* </ul>
*/
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() {}

View File

@@ -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);
}
}

View File

@@ -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<ResolveInfo> queryIntentServicesAsUser(Intent intent, int flags, int userId) {
return queryIntentServices(intent, flags);

View File

@@ -96,7 +96,7 @@ public class BackupManagerServiceTest {
createBackupManagerService();
verify(mTransportManager)
.setTransportBoundListener(any(TransportManager.TransportBoundListener.class));
.setOnTransportRegisteredListener(any());
}
@Test