Merge "Binding on-demand #1: TransportClient infra + PerformBackupTask usage"
This commit is contained in:
committed by
Android (Google) Code Review
commit
86d0573340
@@ -48,7 +48,6 @@ import android.app.backup.IBackupObserver;
|
||||
import android.app.backup.IFullBackupRestoreObserver;
|
||||
import android.app.backup.IRestoreSession;
|
||||
import android.app.backup.ISelectBackupTransportCallback;
|
||||
import android.app.backup.SelectBackupTransportCallback;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ComponentName;
|
||||
@@ -103,6 +102,7 @@ import com.android.server.backup.fullbackup.PerformFullTransportBackupTask;
|
||||
import com.android.server.backup.internal.BackupHandler;
|
||||
import com.android.server.backup.internal.BackupRequest;
|
||||
import com.android.server.backup.internal.ClearDataObserver;
|
||||
import com.android.server.backup.internal.OnTaskFinishedListener;
|
||||
import com.android.server.backup.internal.Operation;
|
||||
import com.android.server.backup.internal.PerformInitializeTask;
|
||||
import com.android.server.backup.internal.ProvisionedObserver;
|
||||
@@ -117,6 +117,7 @@ import com.android.server.backup.params.ClearRetryParams;
|
||||
import com.android.server.backup.params.RestoreParams;
|
||||
import com.android.server.backup.restore.ActiveRestoreSession;
|
||||
import com.android.server.backup.restore.PerformUnifiedRestoreTask;
|
||||
import com.android.server.backup.transport.TransportClient;
|
||||
import com.android.server.backup.utils.AppBackupUtils;
|
||||
import com.android.server.backup.utils.BackupManagerMonitorUtils;
|
||||
import com.android.server.backup.utils.BackupObserverUtils;
|
||||
@@ -1585,8 +1586,27 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
|
||||
return BackupManager.ERROR_BACKUP_NOT_ALLOWED;
|
||||
}
|
||||
|
||||
// We're using pieces of the new binding on-demand infra-structure and the old always-bound
|
||||
// infra-structure below this comment. The TransportManager.getCurrentTransportClient() line
|
||||
// is using the new one and TransportManager.getCurrentTransportBinder() is using the old.
|
||||
// This is weird but there is a reason.
|
||||
// This is the natural place to put TransportManager.getCurrentTransportClient() because of
|
||||
// the null handling below that should be the same for TransportClient.
|
||||
// TransportClient.connect() would return a IBackupTransport for us (instead of using the
|
||||
// old infra), but it may block and we don't want this in this thread.
|
||||
// The only usage of transport in this method is for transport.transportDirName(). When the
|
||||
// push-from-transport part of binding on-demand is in place we will replace the calls for
|
||||
// IBackupTransport.transportDirName() with calls for
|
||||
// TransportManager.transportDirName(transportName) or similar. So we'll leave the old piece
|
||||
// here until we implement that.
|
||||
// TODO(brufino): Remove always-bound code mTransportManager.getCurrentTransportBinder()
|
||||
TransportClient transportClient =
|
||||
mTransportManager.getCurrentTransportClient("BMS.requestBackup()");
|
||||
IBackupTransport transport = mTransportManager.getCurrentTransportBinder();
|
||||
if (transport == null) {
|
||||
if (transportClient == null || transport == null) {
|
||||
if (transportClient != null) {
|
||||
mTransportManager.disposeOfTransportClient(transportClient, "BMS.requestBackup()");
|
||||
}
|
||||
BackupObserverUtils.sendBackupFinished(observer, BackupManager.ERROR_TRANSPORT_ABORTED);
|
||||
monitor = BackupManagerMonitorUtils.monitorEvent(monitor,
|
||||
BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_IS_NULL,
|
||||
@@ -1594,6 +1614,9 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
|
||||
return BackupManager.ERROR_TRANSPORT_ABORTED;
|
||||
}
|
||||
|
||||
OnTaskFinishedListener listener =
|
||||
caller -> mTransportManager.disposeOfTransportClient(transportClient, caller);
|
||||
|
||||
ArrayList<String> fullBackupList = new ArrayList<>();
|
||||
ArrayList<String> kvBackupList = new ArrayList<>();
|
||||
for (String packageName : packages) {
|
||||
@@ -1640,8 +1663,8 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
|
||||
boolean nonIncrementalBackup = (flags & BackupManager.FLAG_NON_INCREMENTAL_BACKUP) != 0;
|
||||
|
||||
Message msg = mBackupHandler.obtainMessage(MSG_REQUEST_BACKUP);
|
||||
msg.obj = new BackupParams(transport, dirName, kvBackupList, fullBackupList, observer,
|
||||
monitor, true, nonIncrementalBackup);
|
||||
msg.obj = new BackupParams(transportClient, dirName, kvBackupList, fullBackupList, observer,
|
||||
monitor, listener, true, nonIncrementalBackup);
|
||||
mBackupHandler.sendMessage(msg);
|
||||
return BackupManager.SUCCESS;
|
||||
}
|
||||
|
||||
@@ -16,8 +16,9 @@
|
||||
|
||||
package com.android.server.backup;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.app.backup.BackupManager;
|
||||
import android.app.backup.SelectBackupTransportCallback;
|
||||
import android.app.backup.BackupTransport;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@@ -44,9 +45,11 @@ import com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.backup.IBackupTransport;
|
||||
import com.android.server.EventLogTags;
|
||||
import com.android.server.backup.transport.TransportClient;
|
||||
import com.android.server.backup.transport.TransportClientManager;
|
||||
import com.android.server.backup.transport.TransportConnectionListener;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -60,8 +63,7 @@ public class TransportManager {
|
||||
private static final String TAG = "BackupTransportManager";
|
||||
|
||||
@VisibleForTesting
|
||||
/* package */ static final String SERVICE_ACTION_TRANSPORT_HOST =
|
||||
"android.backup.TRANSPORT_HOST";
|
||||
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
|
||||
@@ -72,6 +74,7 @@ public class TransportManager {
|
||||
private final PackageManager mPackageManager;
|
||||
private final Set<ComponentName> 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
|
||||
@@ -95,6 +98,10 @@ public class TransportManager {
|
||||
@GuardedBy("mTransportLock")
|
||||
private final Map<String, ComponentName> mBoundTransports = new ArrayMap<>();
|
||||
|
||||
/** Names of transports we've bound to at least once */
|
||||
@GuardedBy("mTransportLock")
|
||||
private final Map<String, ComponentName> mTransportsByName = new ArrayMap<>();
|
||||
|
||||
/**
|
||||
* Callback interface for {@link #ensureTransportReady(ComponentName, TransportReadyCallback)}.
|
||||
*/
|
||||
@@ -123,6 +130,7 @@ public class TransportManager {
|
||||
mCurrentTransportName = defaultTransport;
|
||||
mTransportBoundListener = listener;
|
||||
mHandler = new RebindOnTimeoutHandler(looper);
|
||||
mTransportClientManager = new TransportClientManager(context);
|
||||
}
|
||||
|
||||
void onPackageAdded(String packageName) {
|
||||
@@ -204,6 +212,67 @@ public class TransportManager {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the transport name associated with {@param transportClient} or {@code null} if not
|
||||
* found.
|
||||
*/
|
||||
@Nullable
|
||||
public String getTransportName(TransportClient transportClient) {
|
||||
ComponentName transportComponent = transportClient.getTransportComponent();
|
||||
synchronized (mTransportLock) {
|
||||
for (Map.Entry<String, ComponentName> transportEntry : mTransportsByName.entrySet()) {
|
||||
if (transportEntry.getValue().equals(transportComponent)) {
|
||||
return transportEntry.getKey();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link TransportClient} for {@param transportName} or {@code null} if not found.
|
||||
*
|
||||
* @param transportName The name of the transport as returned by {@link BackupTransport#name()}.
|
||||
* @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.
|
||||
*/
|
||||
@Nullable
|
||||
public TransportClient getTransportClient(String transportName, String caller) {
|
||||
ComponentName transportComponent = mTransportsByName.get(transportName);
|
||||
if (transportComponent == null) {
|
||||
Slog.w(TAG, "Transport " + transportName + " not registered");
|
||||
return null;
|
||||
}
|
||||
return mTransportClientManager.getTransportClient(transportComponent, caller);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link TransportClient} for the current transport or null if not found.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
@Nullable
|
||||
public TransportClient getCurrentTransportClient(String caller) {
|
||||
return getTransportClient(mCurrentTransportName, caller);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes of the {@link TransportClient}.
|
||||
*
|
||||
* @param transportClient The {@link TransportClient} to be disposed of.
|
||||
* @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
|
||||
* {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
|
||||
* details.
|
||||
*/
|
||||
public void disposeOfTransportClient(TransportClient transportClient, String caller) {
|
||||
mTransportClientManager.disposeOfTransportClient(transportClient, caller);
|
||||
}
|
||||
|
||||
String[] getBoundTransportNames() {
|
||||
synchronized (mTransportLock) {
|
||||
return mBoundTransports.keySet().toArray(new String[mBoundTransports.size()]);
|
||||
@@ -374,6 +443,7 @@ public class TransportManager {
|
||||
String componentShortString = component.flattenToShortString().intern();
|
||||
if (success) {
|
||||
Slog.d(TAG, "Bound to transport: " + componentShortString);
|
||||
mTransportsByName.put(mTransportName, component);
|
||||
mBoundTransports.put(mTransportName, component);
|
||||
for (TransportReadyCallback listener : mListeners) {
|
||||
listener.onSuccess(mTransportName);
|
||||
@@ -528,7 +598,7 @@ public class TransportManager {
|
||||
// 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 UserHandle createSystemUserHandle() {
|
||||
public static UserHandle createSystemUserHandle() {
|
||||
return new UserHandle(UserHandle.USER_SYSTEM);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,8 @@ import com.android.server.EventLogTags;
|
||||
import com.android.server.backup.BackupRestoreTask;
|
||||
import com.android.server.backup.DataChangedJournal;
|
||||
import com.android.server.backup.RefactoredBackupManagerService;
|
||||
import com.android.server.backup.transport.TransportClient;
|
||||
import com.android.server.backup.TransportManager;
|
||||
import com.android.server.backup.fullbackup.PerformAdbBackupTask;
|
||||
import com.android.server.backup.fullbackup.PerformFullTransportBackupTask;
|
||||
import com.android.server.backup.params.AdbBackupParams;
|
||||
@@ -51,10 +53,8 @@ import com.android.server.backup.params.RestoreParams;
|
||||
import com.android.server.backup.restore.PerformAdbRestoreTask;
|
||||
import com.android.server.backup.restore.PerformUnifiedRestoreTask;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
|
||||
/**
|
||||
* Asynchronous backup/restore handler thread.
|
||||
@@ -81,7 +81,7 @@ public class BackupHandler extends Handler {
|
||||
public static final int MSG_BACKUP_RESTORE_STEP = 20;
|
||||
public static final int MSG_OP_COMPLETE = 21;
|
||||
|
||||
private RefactoredBackupManagerService backupManagerService;
|
||||
private final RefactoredBackupManagerService backupManagerService;
|
||||
|
||||
public BackupHandler(
|
||||
RefactoredBackupManagerService backupManagerService, Looper looper) {
|
||||
@@ -91,13 +91,23 @@ public class BackupHandler extends Handler {
|
||||
|
||||
public void handleMessage(Message msg) {
|
||||
|
||||
TransportManager transportManager = backupManagerService.getTransportManager();
|
||||
switch (msg.what) {
|
||||
case MSG_RUN_BACKUP: {
|
||||
backupManagerService.setLastBackupPass(System.currentTimeMillis());
|
||||
|
||||
String callerLogString = "BH/MSG_RUN_BACKUP";
|
||||
TransportClient transportClient =
|
||||
transportManager.getCurrentTransportClient(callerLogString);
|
||||
IBackupTransport transport =
|
||||
backupManagerService.getTransportManager().getCurrentTransportBinder();
|
||||
transportClient != null
|
||||
? transportClient.connect(callerLogString)
|
||||
: null;
|
||||
if (transport == null) {
|
||||
if (transportClient != null) {
|
||||
transportManager
|
||||
.disposeOfTransportClient(transportClient, callerLogString);
|
||||
}
|
||||
Slog.v(TAG, "Backup requested but no transport available");
|
||||
synchronized (backupManagerService.getQueueLock()) {
|
||||
backupManagerService.setBackupRunning(false);
|
||||
@@ -138,9 +148,13 @@ public class BackupHandler extends Handler {
|
||||
// Spin up a backup state sequence and set it running
|
||||
try {
|
||||
String dirName = transport.transportDirName();
|
||||
OnTaskFinishedListener listener =
|
||||
caller ->
|
||||
transportManager
|
||||
.disposeOfTransportClient(transportClient, caller);
|
||||
PerformBackupTask pbt = new PerformBackupTask(
|
||||
backupManagerService, transport, dirName, queue,
|
||||
oldJournal, null, null, Collections.<String>emptyList(), false,
|
||||
backupManagerService, transportClient, dirName, queue,
|
||||
oldJournal, null, null, listener, Collections.emptyList(), false,
|
||||
false /* nonIncremental */);
|
||||
Message pbtMessage = obtainMessage(MSG_BACKUP_RESTORE_STEP, pbt);
|
||||
sendMessage(pbtMessage);
|
||||
@@ -157,6 +171,7 @@ public class BackupHandler extends Handler {
|
||||
}
|
||||
|
||||
if (!staged) {
|
||||
transportManager.disposeOfTransportClient(transportClient, callerLogString);
|
||||
// if we didn't actually hand off the wakelock, rewind until next time
|
||||
synchronized (backupManagerService.getQueueLock()) {
|
||||
backupManagerService.setBackupRunning(false);
|
||||
@@ -382,9 +397,9 @@ public class BackupHandler extends Handler {
|
||||
|
||||
PerformBackupTask pbt = new PerformBackupTask(
|
||||
backupManagerService,
|
||||
params.transport, params.dirName,
|
||||
kvQueue, null, params.observer, params.monitor, params.fullPackages, true,
|
||||
params.nonIncrementalBackup);
|
||||
params.transportClient, params.dirName,
|
||||
kvQueue, null, params.observer, params.monitor, params.listener,
|
||||
params.fullPackages, true, params.nonIncrementalBackup);
|
||||
Message pbtMessage = obtainMessage(MSG_BACKUP_RESTORE_STEP, pbt);
|
||||
sendMessage(pbtMessage);
|
||||
break;
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.internal;
|
||||
|
||||
import com.android.server.backup.transport.TransportClient;
|
||||
import com.android.server.backup.transport.TransportConnectionListener;
|
||||
|
||||
/** Listener to be called when a task finishes, successfully or not. */
|
||||
public interface OnTaskFinishedListener {
|
||||
OnTaskFinishedListener NOP = caller -> {};
|
||||
|
||||
/**
|
||||
* Called when a task finishes, successfully or not.
|
||||
*
|
||||
* @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
|
||||
* {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
|
||||
* details.
|
||||
*/
|
||||
void onFinished(String caller);
|
||||
}
|
||||
@@ -63,6 +63,8 @@ import com.android.server.backup.KeyValueBackupJob;
|
||||
import com.android.server.backup.PackageManagerBackupAgent;
|
||||
import com.android.server.backup.RefactoredBackupManagerService;
|
||||
import com.android.server.backup.fullbackup.PerformFullTransportBackupTask;
|
||||
import com.android.server.backup.transport.TransportClient;
|
||||
import com.android.server.backup.transport.TransportUtils;
|
||||
import com.android.server.backup.utils.AppBackupUtils;
|
||||
import com.android.server.backup.utils.BackupManagerMonitorUtils;
|
||||
import com.android.server.backup.utils.BackupObserverUtils;
|
||||
@@ -112,7 +114,6 @@ public class PerformBackupTask implements BackupRestoreTask {
|
||||
private RefactoredBackupManagerService backupManagerService;
|
||||
private final Object mCancelLock = new Object();
|
||||
|
||||
IBackupTransport mTransport;
|
||||
ArrayList<BackupRequest> mQueue;
|
||||
ArrayList<BackupRequest> mOriginalQueue;
|
||||
File mStateDir;
|
||||
@@ -122,6 +123,8 @@ public class PerformBackupTask implements BackupRestoreTask {
|
||||
IBackupObserver mObserver;
|
||||
IBackupManagerMonitor mMonitor;
|
||||
|
||||
private final TransportClient mTransportClient;
|
||||
private final OnTaskFinishedListener mListener;
|
||||
private final PerformFullTransportBackupTask mFullBackupTask;
|
||||
private final int mCurrentOpToken;
|
||||
private volatile int mEphemeralOpToken;
|
||||
@@ -143,17 +146,19 @@ public class PerformBackupTask implements BackupRestoreTask {
|
||||
private volatile boolean mCancelAll;
|
||||
|
||||
public PerformBackupTask(RefactoredBackupManagerService backupManagerService,
|
||||
IBackupTransport transport, String dirName,
|
||||
TransportClient transportClient, String dirName,
|
||||
ArrayList<BackupRequest> queue, @Nullable DataChangedJournal journal,
|
||||
IBackupObserver observer, IBackupManagerMonitor monitor,
|
||||
List<String> pendingFullBackups, boolean userInitiated, boolean nonIncremental) {
|
||||
@Nullable OnTaskFinishedListener listener, List<String> pendingFullBackups,
|
||||
boolean userInitiated, boolean nonIncremental) {
|
||||
this.backupManagerService = backupManagerService;
|
||||
mTransport = transport;
|
||||
mTransportClient = transportClient;
|
||||
mOriginalQueue = queue;
|
||||
mQueue = new ArrayList<>();
|
||||
mJournal = journal;
|
||||
mObserver = observer;
|
||||
mMonitor = monitor;
|
||||
mListener = (listener != null) ? listener : OnTaskFinishedListener.NOP;
|
||||
mPendingFullBackups = pendingFullBackups;
|
||||
mUserInitiated = userInitiated;
|
||||
mNonIncremental = nonIncremental;
|
||||
@@ -289,10 +294,10 @@ public class PerformBackupTask implements BackupRestoreTask {
|
||||
if (DEBUG) {
|
||||
Slog.v(TAG, "Beginning backup of " + mQueue.size() + " targets");
|
||||
}
|
||||
|
||||
File pmState = new File(mStateDir, PACKAGE_MANAGER_SENTINEL);
|
||||
try {
|
||||
final String transportName = mTransport.transportDirName();
|
||||
IBackupTransport transport = mTransportClient.connectOrThrow("PBT.beginBackup()");
|
||||
final String transportName = transport.transportDirName();
|
||||
EventLog.writeEvent(EventLogTags.BACKUP_START, transportName);
|
||||
|
||||
// If we haven't stored package manager metadata yet, we must init the transport.
|
||||
@@ -300,7 +305,7 @@ public class PerformBackupTask implements BackupRestoreTask {
|
||||
Slog.i(TAG, "Initializing (wiping) backup state and transport storage");
|
||||
backupManagerService.addBackupTrace("initializing transport " + transportName);
|
||||
backupManagerService.resetBackupState(mStateDir); // Just to make sure.
|
||||
mStatus = mTransport.initializeDevice();
|
||||
mStatus = transport.initializeDevice();
|
||||
|
||||
backupManagerService.addBackupTrace("transport.initializeDevice() == " + mStatus);
|
||||
if (mStatus == BackupTransport.TRANSPORT_OK) {
|
||||
@@ -324,7 +329,7 @@ public class PerformBackupTask implements BackupRestoreTask {
|
||||
PackageManagerBackupAgent pmAgent = backupManagerService.makeMetadataAgent();
|
||||
mStatus = invokeAgentForBackup(
|
||||
PACKAGE_MANAGER_SENTINEL,
|
||||
IBackupAgent.Stub.asInterface(pmAgent.onBind()), mTransport);
|
||||
IBackupAgent.Stub.asInterface(pmAgent.onBind()));
|
||||
backupManagerService.addBackupTrace("PMBA invoke: " + mStatus);
|
||||
|
||||
// Because the PMBA is a local instance, it has already executed its
|
||||
@@ -445,7 +450,7 @@ public class PerformBackupTask implements BackupRestoreTask {
|
||||
backupManagerService.addBackupTrace("agent bound; a? = " + (agent != null));
|
||||
if (agent != null) {
|
||||
mAgentBinder = agent;
|
||||
mStatus = invokeAgentForBackup(request.packageName, agent, mTransport);
|
||||
mStatus = invokeAgentForBackup(request.packageName, agent);
|
||||
// at this point we'll either get a completion callback from the
|
||||
// agent, or a timeout message on the main handler. either way, we're
|
||||
// done here as long as we're successful so far.
|
||||
@@ -526,11 +531,14 @@ public class PerformBackupTask implements BackupRestoreTask {
|
||||
// If everything actually went through and this is the first time we've
|
||||
// done a backup, we can now record what the current backup dataset token
|
||||
// is.
|
||||
String callerLogString = "PBT.finalizeBackup()";
|
||||
if ((backupManagerService.getCurrentToken() == 0) && (mStatus
|
||||
== BackupTransport.TRANSPORT_OK)) {
|
||||
backupManagerService.addBackupTrace("success; recording token");
|
||||
try {
|
||||
backupManagerService.setCurrentToken(mTransport.getCurrentRestoreSet());
|
||||
IBackupTransport transport =
|
||||
mTransportClient.connectOrThrow(callerLogString);
|
||||
backupManagerService.setCurrentToken(transport.getCurrentRestoreSet());
|
||||
backupManagerService.writeRestoreTokens();
|
||||
} catch (Exception e) {
|
||||
// nothing for it at this point, unfortunately, but this will be
|
||||
@@ -553,13 +561,13 @@ public class PerformBackupTask implements BackupRestoreTask {
|
||||
backupManagerService.addBackupTrace("init required; rerunning");
|
||||
try {
|
||||
final String name = backupManagerService.getTransportManager().getTransportName(
|
||||
mTransport);
|
||||
mTransportClient);
|
||||
if (name != null) {
|
||||
backupManagerService.getPendingInits().add(name);
|
||||
} else {
|
||||
if (DEBUG) {
|
||||
Slog.w(TAG, "Couldn't find name of transport " + mTransport
|
||||
+ " for init");
|
||||
Slog.w(TAG, "Couldn't find name of transport "
|
||||
+ mTransportClient.getTransportComponent() + " for init");
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
@@ -577,17 +585,21 @@ public class PerformBackupTask implements BackupRestoreTask {
|
||||
|
||||
if (!mCancelAll && mStatus == BackupTransport.TRANSPORT_OK &&
|
||||
mPendingFullBackups != null && !mPendingFullBackups.isEmpty()) {
|
||||
// TODO(brufino): Move the onFinish() call to the full-backup task
|
||||
mListener.onFinished(callerLogString);
|
||||
Slog.d(TAG, "Starting full backups for: " + mPendingFullBackups);
|
||||
// Acquiring wakelock for PerformFullTransportBackupTask before its start.
|
||||
backupManagerService.getWakelock().acquire();
|
||||
(new Thread(mFullBackupTask, "full-transport-requested")).start();
|
||||
} else if (mCancelAll) {
|
||||
mListener.onFinished(callerLogString);
|
||||
if (mFullBackupTask != null) {
|
||||
mFullBackupTask.unregisterTask();
|
||||
}
|
||||
BackupObserverUtils.sendBackupFinished(mObserver,
|
||||
BackupManager.ERROR_BACKUP_CANCELLED);
|
||||
} else {
|
||||
mListener.onFinished(callerLogString);
|
||||
mFullBackupTask.unregisterTask();
|
||||
switch (mStatus) {
|
||||
case BackupTransport.TRANSPORT_OK:
|
||||
@@ -619,8 +631,7 @@ public class PerformBackupTask implements BackupRestoreTask {
|
||||
|
||||
// Invoke an agent's doBackup() and start a timeout message spinning on the main
|
||||
// handler in case it doesn't get back to us.
|
||||
int invokeAgentForBackup(String packageName, IBackupAgent agent,
|
||||
IBackupTransport transport) {
|
||||
int invokeAgentForBackup(String packageName, IBackupAgent agent) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "invokeAgentForBackup on " + packageName);
|
||||
}
|
||||
@@ -671,7 +682,10 @@ public class PerformBackupTask implements BackupRestoreTask {
|
||||
ParcelFileDescriptor.MODE_CREATE |
|
||||
ParcelFileDescriptor.MODE_TRUNCATE);
|
||||
|
||||
final long quota = mTransport.getBackupQuota(packageName, false /* isFullBackup */);
|
||||
IBackupTransport transport =
|
||||
mTransportClient.connectOrThrow("PBT.invokeAgentForBackup()");
|
||||
|
||||
final long quota = transport.getBackupQuota(packageName, false /* isFullBackup */);
|
||||
callingAgent = true;
|
||||
|
||||
// Initiate the target's backup pass
|
||||
@@ -888,10 +902,12 @@ public class PerformBackupTask implements BackupRestoreTask {
|
||||
clearAgentState();
|
||||
backupManagerService.addBackupTrace("operation complete");
|
||||
|
||||
IBackupTransport transport = mTransportClient.connect("PBT.operationComplete()");
|
||||
ParcelFileDescriptor backupData = null;
|
||||
mStatus = BackupTransport.TRANSPORT_OK;
|
||||
long size = 0;
|
||||
try {
|
||||
TransportUtils.checkTransport(transport);
|
||||
size = mBackupDataName.length();
|
||||
if (size > 0) {
|
||||
if (mStatus == BackupTransport.TRANSPORT_OK) {
|
||||
@@ -899,7 +915,7 @@ public class PerformBackupTask implements BackupRestoreTask {
|
||||
ParcelFileDescriptor.MODE_READ_ONLY);
|
||||
backupManagerService.addBackupTrace("sending data to transport");
|
||||
int flags = mUserInitiated ? BackupTransport.FLAG_USER_INITIATED : 0;
|
||||
mStatus = mTransport.performBackup(mCurrentPackage, backupData, flags);
|
||||
mStatus = transport.performBackup(mCurrentPackage, backupData, flags);
|
||||
}
|
||||
|
||||
// TODO - We call finishBackup() for each application backed up, because
|
||||
@@ -910,7 +926,7 @@ public class PerformBackupTask implements BackupRestoreTask {
|
||||
backupManagerService.addBackupTrace("data delivered: " + mStatus);
|
||||
if (mStatus == BackupTransport.TRANSPORT_OK) {
|
||||
backupManagerService.addBackupTrace("finishing op on transport");
|
||||
mStatus = mTransport.finishBackup();
|
||||
mStatus = transport.finishBackup();
|
||||
backupManagerService.addBackupTrace("finished: " + mStatus);
|
||||
} else if (mStatus == BackupTransport.TRANSPORT_PACKAGE_REJECTED) {
|
||||
backupManagerService.addBackupTrace("transport rejected package");
|
||||
@@ -981,8 +997,8 @@ public class PerformBackupTask implements BackupRestoreTask {
|
||||
}
|
||||
if (mAgentBinder != null) {
|
||||
try {
|
||||
long quota = mTransport.getBackupQuota(mCurrentPackage.packageName,
|
||||
false);
|
||||
TransportUtils.checkTransport(transport);
|
||||
long quota = transport.getBackupQuota(mCurrentPackage.packageName, false);
|
||||
mAgentBinder.doQuotaExceeded(size, quota);
|
||||
} catch (Exception e) {
|
||||
Slog.e(TAG, "Unable to notify about quota exceeded: " + e.getMessage());
|
||||
@@ -1052,7 +1068,9 @@ public class PerformBackupTask implements BackupRestoreTask {
|
||||
// by way of retry/backoff time.
|
||||
long delay;
|
||||
try {
|
||||
delay = mTransport.requestBackupTime();
|
||||
IBackupTransport transport =
|
||||
mTransportClient.connectOrThrow("PBT.revertAndEndBackup()");
|
||||
delay = transport.requestBackupTime();
|
||||
} catch (Exception e) {
|
||||
Slog.w(TAG, "Unable to contact transport for recommended backoff: " + e.getMessage());
|
||||
delay = 0; // use the scheduler's default
|
||||
|
||||
@@ -19,30 +19,34 @@ package com.android.server.backup.params;
|
||||
import android.app.backup.IBackupManagerMonitor;
|
||||
import android.app.backup.IBackupObserver;
|
||||
|
||||
import com.android.internal.backup.IBackupTransport;
|
||||
import com.android.server.backup.internal.OnTaskFinishedListener;
|
||||
import com.android.server.backup.transport.TransportClient;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class BackupParams {
|
||||
|
||||
public IBackupTransport transport;
|
||||
public TransportClient transportClient;
|
||||
public String dirName;
|
||||
public ArrayList<String> kvPackages;
|
||||
public ArrayList<String> fullPackages;
|
||||
public IBackupObserver observer;
|
||||
public IBackupManagerMonitor monitor;
|
||||
public OnTaskFinishedListener listener;
|
||||
public boolean userInitiated;
|
||||
public boolean nonIncrementalBackup;
|
||||
|
||||
public BackupParams(IBackupTransport transport, String dirName, ArrayList<String> kvPackages,
|
||||
ArrayList<String> fullPackages, IBackupObserver observer,
|
||||
IBackupManagerMonitor monitor, boolean userInitiated, boolean nonIncrementalBackup) {
|
||||
this.transport = transport;
|
||||
public BackupParams(TransportClient transportClient, String dirName,
|
||||
ArrayList<String> kvPackages, ArrayList<String> fullPackages, IBackupObserver observer,
|
||||
IBackupManagerMonitor monitor, OnTaskFinishedListener listener, boolean userInitiated,
|
||||
boolean nonIncrementalBackup) {
|
||||
this.transportClient = transportClient;
|
||||
this.dirName = dirName;
|
||||
this.kvPackages = kvPackages;
|
||||
this.fullPackages = fullPackages;
|
||||
this.observer = observer;
|
||||
this.monitor = monitor;
|
||||
this.listener = listener;
|
||||
this.userInitiated = userInitiated;
|
||||
this.nonIncrementalBackup = nonIncrementalBackup;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,468 @@
|
||||
/*
|
||||
* 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.transport;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.WorkerThread;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.os.DeadObjectException;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.UserHandle;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
|
||||
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.backup.TransportManager;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
/**
|
||||
* A {@link TransportClient} manages the connection to an {@link IBackupTransport} service, obtained
|
||||
* via the {@param bindIntent} parameter provided in the constructor. A {@link TransportClient} is
|
||||
* responsible for only one connection to the transport service, not more.
|
||||
*
|
||||
* <p>After retrieved using {@link TransportManager#getTransportClient(String, String)}, you can
|
||||
* call either {@link #connect(String)}, if you can block your thread, or {@link
|
||||
* #connectAsync(TransportConnectionListener, String)}, otherwise, to obtain a {@link
|
||||
* IBackupTransport} instance. It's meant to be passed around as a token to a connected transport.
|
||||
* When the connection is not needed anymore you should call {@link #unbind(String)} or indirectly
|
||||
* via {@link TransportManager#disposeOfTransportClient(TransportClient, String)}.
|
||||
*
|
||||
* <p>DO NOT forget to unbind otherwise there will be dangling connections floating around.
|
||||
*
|
||||
* <p>This class is thread-safe.
|
||||
*
|
||||
* @see TransportManager
|
||||
*/
|
||||
public class TransportClient {
|
||||
private static final String TAG = "TransportClient";
|
||||
|
||||
private final Context mContext;
|
||||
private final Intent mBindIntent;
|
||||
private final String mIdentifier;
|
||||
private final ComponentName mTransportComponent;
|
||||
private final Handler mListenerHandler;
|
||||
private final String mPrefixForLog;
|
||||
private final Object mStateLock = new Object();
|
||||
|
||||
@GuardedBy("mStateLock")
|
||||
private final Map<TransportConnectionListener, String> mListeners = new ArrayMap<>();
|
||||
|
||||
@GuardedBy("mStateLock")
|
||||
@State
|
||||
private int mState = State.IDLE;
|
||||
|
||||
@GuardedBy("mStateLock")
|
||||
private volatile IBackupTransport mTransport;
|
||||
|
||||
TransportClient(
|
||||
Context context,
|
||||
Intent bindIntent,
|
||||
ComponentName transportComponent,
|
||||
String identifier) {
|
||||
this(context, bindIntent, transportComponent, identifier, Handler.getMain());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
TransportClient(
|
||||
Context context,
|
||||
Intent bindIntent,
|
||||
ComponentName transportComponent,
|
||||
String identifier,
|
||||
Handler listenerHandler) {
|
||||
mContext = context;
|
||||
mTransportComponent = transportComponent;
|
||||
mBindIntent = bindIntent;
|
||||
mIdentifier = identifier;
|
||||
mListenerHandler = listenerHandler;
|
||||
|
||||
// For logging
|
||||
String classNameForLog = mTransportComponent.getShortClassName().replaceFirst(".*\\.", "");
|
||||
mPrefixForLog = classNameForLog + "#" + mIdentifier + ": ";
|
||||
}
|
||||
|
||||
public ComponentName getTransportComponent() {
|
||||
return mTransportComponent;
|
||||
}
|
||||
|
||||
// Calls to onServiceDisconnected() or onBindingDied() turn TransportClient UNUSABLE. After one
|
||||
// of these calls, if a binding happen again the new service can be a different instance. Since
|
||||
// transports are stateful, we don't want a new instance responding for an old instance's state.
|
||||
private ServiceConnection mConnection =
|
||||
new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName componentName, IBinder binder) {
|
||||
IBackupTransport transport = IBackupTransport.Stub.asInterface(binder);
|
||||
synchronized (mStateLock) {
|
||||
checkStateIntegrityLocked();
|
||||
|
||||
if (mState != State.UNUSABLE) {
|
||||
log(Log.DEBUG, "Transport connected");
|
||||
setStateLocked(State.CONNECTED, transport);
|
||||
notifyListenersAndClearLocked(transport);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName componentName) {
|
||||
synchronized (mStateLock) {
|
||||
log(Log.ERROR, "Service disconnected: client UNUSABLE");
|
||||
setStateLocked(State.UNUSABLE, null);
|
||||
// After unbindService() no calls back to mConnection
|
||||
mContext.unbindService(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindingDied(ComponentName name) {
|
||||
synchronized (mStateLock) {
|
||||
checkStateIntegrityLocked();
|
||||
|
||||
log(Log.ERROR, "Binding died: client UNUSABLE");
|
||||
// After unbindService() no calls back to mConnection
|
||||
switch (mState) {
|
||||
case State.UNUSABLE:
|
||||
break;
|
||||
case State.IDLE:
|
||||
log(Log.ERROR, "Unexpected state transition IDLE => UNUSABLE");
|
||||
setStateLocked(State.UNUSABLE, null);
|
||||
break;
|
||||
case State.BOUND_AND_CONNECTING:
|
||||
setStateLocked(State.UNUSABLE, null);
|
||||
mContext.unbindService(this);
|
||||
notifyListenersAndClearLocked(null);
|
||||
break;
|
||||
case State.CONNECTED:
|
||||
setStateLocked(State.UNUSABLE, null);
|
||||
mContext.unbindService(this);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Attempts to connect to the transport (if needed).
|
||||
*
|
||||
* <p>Note that being bound is not the same as connected. To be connected you also need to be
|
||||
* bound. You go from nothing to bound, then to bound and connected. To have a usable transport
|
||||
* binder instance you need to be connected. This method will attempt to connect and return an
|
||||
* usable transport binder regardless of the state of the object, it may already be connected,
|
||||
* or bound but not connected, not bound at all or even unusable.
|
||||
*
|
||||
* <p>So, a {@link Context#bindServiceAsUser(Intent, ServiceConnection, int, UserHandle)} (or
|
||||
* one of its variants) can be called or not depending on the inner state. However, it won't be
|
||||
* called again if we're already bound. For example, if one was already requested but the
|
||||
* framework has not yet returned (meaning we're bound but still trying to connect) it won't
|
||||
* trigger another one, just piggyback on the original request.
|
||||
*
|
||||
* <p>It's guaranteed that you are going to get a call back to {@param listener} after this
|
||||
* call. However, the {@param IBackupTransport} parameter, the transport binder, is not
|
||||
* guaranteed to be non-null, or if it's non-null it's not guaranteed to be usable - i.e. it can
|
||||
* throw {@link DeadObjectException}s on method calls. You should check for both in your code.
|
||||
* The reasons for a null transport binder are:
|
||||
*
|
||||
* <ul>
|
||||
* <li>Some code called {@link #unbind(String)} before you got a callback.
|
||||
* <li>The framework had already called {@link
|
||||
* ServiceConnection#onServiceDisconnected(ComponentName)} or {@link
|
||||
* ServiceConnection#onBindingDied(ComponentName)} on this object's connection before.
|
||||
* Check the documentation of those methods for when that happens.
|
||||
* <li>The framework returns false for {@link Context#bindServiceAsUser(Intent,
|
||||
* ServiceConnection, int, UserHandle)} (or one of its variants). Check documentation for
|
||||
* when this happens.
|
||||
* </ul>
|
||||
*
|
||||
* For unusable transport binders check {@link DeadObjectException}.
|
||||
*
|
||||
* @param listener The listener that will be called with the (possibly null or unusable) {@link
|
||||
* IBackupTransport} instance and this {@link TransportClient} object.
|
||||
* @param caller A {@link String} identifying the caller for logging/debugging purposes. This
|
||||
* should be a human-readable short string that is easily identifiable in the logs. Ideally
|
||||
* TAG.methodName(), where TAG is the one used in logcat. In cases where this is is not very
|
||||
* descriptive like MyHandler.handleMessage() you should put something that someone reading
|
||||
* the code would understand, like MyHandler/MSG_FOO.
|
||||
* @see #connect(String)
|
||||
* @see DeadObjectException
|
||||
* @see ServiceConnection#onServiceConnected(ComponentName, IBinder)
|
||||
* @see ServiceConnection#onServiceDisconnected(ComponentName)
|
||||
* @see Context#bindServiceAsUser(Intent, ServiceConnection, int, UserHandle)
|
||||
*/
|
||||
public void connectAsync(TransportConnectionListener listener, String caller) {
|
||||
synchronized (mStateLock) {
|
||||
checkStateIntegrityLocked();
|
||||
|
||||
switch (mState) {
|
||||
case State.UNUSABLE:
|
||||
log(Log.DEBUG, caller, "Async connect: UNUSABLE client");
|
||||
notifyListener(listener, null, caller);
|
||||
break;
|
||||
case State.IDLE:
|
||||
boolean hasBound =
|
||||
mContext.bindServiceAsUser(
|
||||
mBindIntent,
|
||||
mConnection,
|
||||
Context.BIND_AUTO_CREATE,
|
||||
TransportManager.createSystemUserHandle());
|
||||
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
|
||||
// onBindingDied().
|
||||
log(Log.DEBUG, caller, "Async connect: service bound, connecting");
|
||||
setStateLocked(State.BOUND_AND_CONNECTING, null);
|
||||
mListeners.put(listener, caller);
|
||||
} else {
|
||||
log(Log.ERROR, "Async connect: bindService returned false");
|
||||
// mState remains State.IDLE
|
||||
mContext.unbindService(mConnection);
|
||||
notifyListener(listener, null, caller);
|
||||
}
|
||||
break;
|
||||
case State.BOUND_AND_CONNECTING:
|
||||
log(Log.DEBUG, caller, "Async connect: already connecting, adding listener");
|
||||
mListeners.put(listener, caller);
|
||||
break;
|
||||
case State.CONNECTED:
|
||||
log(Log.DEBUG, caller, "Async connect: reusing transport");
|
||||
notifyListener(listener, mTransport, caller);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the transport binding.
|
||||
*
|
||||
* @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
|
||||
* {@link #connectAsync(TransportConnectionListener, String)} for more details.
|
||||
*/
|
||||
public void unbind(String caller) {
|
||||
synchronized (mStateLock) {
|
||||
checkStateIntegrityLocked();
|
||||
|
||||
log(Log.DEBUG, caller, "Unbind requested (was " + stateToString(mState) + ")");
|
||||
switch (mState) {
|
||||
case State.UNUSABLE:
|
||||
case State.IDLE:
|
||||
break;
|
||||
case State.BOUND_AND_CONNECTING:
|
||||
setStateLocked(State.IDLE, null);
|
||||
// After unbindService() no calls back to mConnection
|
||||
mContext.unbindService(mConnection);
|
||||
notifyListenersAndClearLocked(null);
|
||||
break;
|
||||
case State.CONNECTED:
|
||||
setStateLocked(State.IDLE, null);
|
||||
mContext.unbindService(mConnection);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to connect to the transport (if needed) and returns it.
|
||||
*
|
||||
* <p>Synchronous version of {@link #connectAsync(TransportConnectionListener, String)}. The
|
||||
* same observations about state are valid here. Also, what was said about the {@link
|
||||
* IBackupTransport} parameter of {@link TransportConnectionListener} now apply to the return
|
||||
* value of this method.
|
||||
*
|
||||
* <p>This is a potentially blocking operation, so be sure to call this carefully on the correct
|
||||
* threads. You can't call this from the process main-thread (it throws an exception if you do
|
||||
* so).
|
||||
*
|
||||
* <p>In most cases only the first call to this method will block, the following calls should
|
||||
* return instantly. However, this is not guaranteed.
|
||||
*
|
||||
* @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
|
||||
* {@link #connectAsync(TransportConnectionListener, String)} for more details.
|
||||
* @return A {@link IBackupTransport} transport binder instance or null. If it's non-null it can
|
||||
* still be unusable - throws {@link DeadObjectException} on method calls
|
||||
*/
|
||||
@WorkerThread
|
||||
@Nullable
|
||||
public IBackupTransport connect(String caller) {
|
||||
// If called on the main-thread this could deadlock waiting because calls to
|
||||
// ServiceConnection are on the main-thread as well
|
||||
Preconditions.checkState(
|
||||
!Looper.getMainLooper().isCurrentThread(), "Can't call connect() on main thread");
|
||||
|
||||
IBackupTransport transport = mTransport;
|
||||
if (transport != null) {
|
||||
log(Log.DEBUG, caller, "Sync connect: reusing transport");
|
||||
return transport;
|
||||
}
|
||||
|
||||
// If it's already UNUSABLE we return straight away, no need to go to main-thread
|
||||
synchronized (mStateLock) {
|
||||
if (mState == State.UNUSABLE) {
|
||||
log(Log.DEBUG, caller, "Sync connect: UNUSABLE client");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
CompletableFuture<IBackupTransport> transportFuture = new CompletableFuture<>();
|
||||
TransportConnectionListener requestListener =
|
||||
(requestedTransport, transportClient) ->
|
||||
transportFuture.complete(requestedTransport);
|
||||
|
||||
log(Log.DEBUG, caller, "Sync connect: calling async");
|
||||
connectAsync(requestListener, caller);
|
||||
|
||||
try {
|
||||
return transportFuture.get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
String error = e.getClass().getSimpleName();
|
||||
log(Log.ERROR, caller, error + " while waiting for transport: " + e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to connect to the transport, if it fails throws {@link TransportNotAvailableException}.
|
||||
*
|
||||
* <p>Same as {@link #connect(String)} except it throws instead of returning null.
|
||||
*
|
||||
* @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
|
||||
* {@link #connectAsync(TransportConnectionListener, String)} for more details.
|
||||
* @return A {@link IBackupTransport} transport binder instance.
|
||||
* @see #connect(String)
|
||||
* @throws TransportNotAvailableException if connection attempt fails.
|
||||
*/
|
||||
@WorkerThread
|
||||
public IBackupTransport connectOrThrow(String caller) throws TransportNotAvailableException {
|
||||
IBackupTransport transport = connect(caller);
|
||||
if (transport == null) {
|
||||
log(Log.ERROR, caller, "Transport connection failed");
|
||||
throw new TransportNotAvailableException();
|
||||
}
|
||||
return transport;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TransportClient{"
|
||||
+ mTransportComponent.flattenToShortString()
|
||||
+ "#"
|
||||
+ mIdentifier
|
||||
+ "}";
|
||||
}
|
||||
|
||||
private void notifyListener(
|
||||
TransportConnectionListener listener, IBackupTransport transport, String caller) {
|
||||
log(Log.VERBOSE, caller, "Notifying listener of transport = " + transport);
|
||||
mListenerHandler.post(() -> listener.onTransportConnectionResult(transport, this));
|
||||
}
|
||||
|
||||
@GuardedBy("mStateLock")
|
||||
private void notifyListenersAndClearLocked(IBackupTransport transport) {
|
||||
for (Map.Entry<TransportConnectionListener, String> entry : mListeners.entrySet()) {
|
||||
TransportConnectionListener listener = entry.getKey();
|
||||
String caller = entry.getValue();
|
||||
notifyListener(listener, transport, caller);
|
||||
}
|
||||
mListeners.clear();
|
||||
}
|
||||
|
||||
@GuardedBy("mStateLock")
|
||||
private void setStateLocked(@State int state, @Nullable IBackupTransport transport) {
|
||||
log(Log.VERBOSE, "State: " + stateToString(mState) + " => " + stateToString(state));
|
||||
mState = state;
|
||||
mTransport = transport;
|
||||
}
|
||||
|
||||
@GuardedBy("mStateLock")
|
||||
private void checkStateIntegrityLocked() {
|
||||
switch (mState) {
|
||||
case State.UNUSABLE:
|
||||
checkState(mListeners.isEmpty(), "Unexpected listeners when state = UNUSABLE");
|
||||
checkState(
|
||||
mTransport == null, "Transport expected to be null when state = UNUSABLE");
|
||||
case State.IDLE:
|
||||
checkState(mListeners.isEmpty(), "Unexpected listeners when state = IDLE");
|
||||
checkState(mTransport == null, "Transport expected to be null when state = IDLE");
|
||||
break;
|
||||
case State.BOUND_AND_CONNECTING:
|
||||
checkState(
|
||||
mTransport == null,
|
||||
"Transport expected to be null when state = BOUND_AND_CONNECTING");
|
||||
break;
|
||||
case State.CONNECTED:
|
||||
checkState(mListeners.isEmpty(), "Unexpected listeners when state = CONNECTED");
|
||||
checkState(
|
||||
mTransport != null,
|
||||
"Transport expected to be non-null when state = CONNECTED");
|
||||
break;
|
||||
default:
|
||||
checkState(false, "Unexpected state = " + stateToString(mState));
|
||||
}
|
||||
}
|
||||
|
||||
private void checkState(boolean assertion, String message) {
|
||||
if (!assertion) {
|
||||
log(Log.ERROR, message);
|
||||
}
|
||||
}
|
||||
|
||||
private String stateToString(@State int state) {
|
||||
switch (state) {
|
||||
case State.UNUSABLE:
|
||||
return "UNUSABLE";
|
||||
case State.IDLE:
|
||||
return "IDLE";
|
||||
case State.BOUND_AND_CONNECTING:
|
||||
return "BOUND_AND_CONNECTING";
|
||||
case State.CONNECTED:
|
||||
return "CONNECTED";
|
||||
default:
|
||||
return "<UNKNOWN = " + state + ">";
|
||||
}
|
||||
}
|
||||
|
||||
private void log(int priority, String message) {
|
||||
TransportUtils.log(priority, TAG, message);
|
||||
}
|
||||
|
||||
private void log(int priority, String caller, String msg) {
|
||||
TransportUtils.log(priority, TAG, mPrefixForLog, caller, msg);
|
||||
// TODO(brufino): Log in internal list for dump
|
||||
// CharSequence time = DateFormat.format("yyyy-MM-dd HH:mm:ss", System.currentTimeMillis());
|
||||
}
|
||||
|
||||
@IntDef({State.UNUSABLE, State.IDLE, State.BOUND_AND_CONNECTING, State.CONNECTED})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
private @interface State {
|
||||
int UNUSABLE = 0;
|
||||
int IDLE = 1;
|
||||
int BOUND_AND_CONNECTING = 2;
|
||||
int CONNECTED = 3;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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.transport;
|
||||
|
||||
import static com.android.server.backup.TransportManager.SERVICE_ACTION_TRANSPORT_HOST;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.server.backup.TransportManager;
|
||||
|
||||
/**
|
||||
* Manages the creation and disposal of {@link TransportClient}s. The only class that should use
|
||||
* this is {@link TransportManager}, all the other usages should go to {@link TransportManager}.
|
||||
*
|
||||
* <p>TODO(brufino): Implement pool of TransportClients
|
||||
*/
|
||||
public class TransportClientManager {
|
||||
private static final String TAG = "TransportClientManager";
|
||||
|
||||
private final Context mContext;
|
||||
private final Object mTransportClientsLock = new Object();
|
||||
private int mTransportClientsCreated = 0;
|
||||
|
||||
public TransportClientManager(Context context) {
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a {@link TransportClient} for the transport identified by {@param
|
||||
* transportComponent}.
|
||||
*
|
||||
* @param transportComponent The {@link ComponentName} 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}.
|
||||
*/
|
||||
public TransportClient getTransportClient(ComponentName transportComponent, String caller) {
|
||||
Intent bindIntent =
|
||||
new Intent(SERVICE_ACTION_TRANSPORT_HOST).setComponent(transportComponent);
|
||||
synchronized (mTransportClientsLock) {
|
||||
TransportClient transportClient =
|
||||
new TransportClient(
|
||||
mContext,
|
||||
bindIntent,
|
||||
transportComponent,
|
||||
Integer.toString(mTransportClientsCreated));
|
||||
mTransportClientsCreated++;
|
||||
TransportUtils.log(Log.DEBUG, TAG, caller, "Retrieving " + transportClient);
|
||||
return transportClient;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disposes of the {@link TransportClient}.
|
||||
*
|
||||
* @param transportClient The {@link TransportClient} to be disposed of.
|
||||
* @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
|
||||
* {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
|
||||
* details.
|
||||
*/
|
||||
public void disposeOfTransportClient(TransportClient transportClient, String caller) {
|
||||
TransportUtils.log(Log.DEBUG, TAG, caller, "Disposing of " + transportClient);
|
||||
transportClient.unbind(caller);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.transport;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
|
||||
import com.android.internal.backup.IBackupTransport;
|
||||
|
||||
/**
|
||||
* Listener to be called by {@link TransportClient#connectAsync(TransportConnectionListener,
|
||||
* String)}.
|
||||
*/
|
||||
public interface TransportConnectionListener {
|
||||
/**
|
||||
* Called when {@link TransportClient} has a transport binder available or that it decided it
|
||||
* couldn't obtain one, in which case {@param transport} is null.
|
||||
*
|
||||
* @param transport A {@link IBackupTransport} transport binder or null.
|
||||
* @param transportClient The {@link TransportClient} used to retrieve this transport binder.
|
||||
*/
|
||||
void onTransportConnectionResult(
|
||||
@Nullable IBackupTransport transport, TransportClient transportClient);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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.transport;
|
||||
|
||||
import com.android.internal.backup.IBackupTransport;
|
||||
|
||||
/**
|
||||
* Exception thrown when the {@link IBackupTransport} is not available. This happen when a {@link
|
||||
* TransportClient} connection attempt fails. Check {@link
|
||||
* TransportClient#connectAsync(TransportConnectionListener, String)} for when that happens.
|
||||
*
|
||||
* @see TransportClient#connectAsync(TransportConnectionListener, String)
|
||||
*/
|
||||
class TransportNotAvailableException extends Exception {
|
||||
TransportNotAvailableException() {
|
||||
super("Transport not available");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* 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.transport;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.os.DeadObjectException;
|
||||
import android.util.Log;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.backup.IBackupTransport;
|
||||
|
||||
/** Utility methods for transport-related operations. */
|
||||
public class TransportUtils {
|
||||
|
||||
/**
|
||||
* Throws {@link TransportNotAvailableException} if {@param transport} is null. The semantics is
|
||||
* similar to a {@link DeadObjectException} coming from a dead transport binder.
|
||||
*/
|
||||
public static IBackupTransport checkTransport(@Nullable IBackupTransport transport)
|
||||
throws TransportNotAvailableException {
|
||||
if (transport == null) {
|
||||
throw new TransportNotAvailableException();
|
||||
}
|
||||
return transport;
|
||||
}
|
||||
|
||||
static void log(int priority, String tag, String message) {
|
||||
log(priority, tag, null, message);
|
||||
}
|
||||
|
||||
static void log(int priority, String tag, @Nullable String caller, String message) {
|
||||
log(priority, tag, "", caller, message);
|
||||
}
|
||||
|
||||
static void log(
|
||||
int priority, String tag, String prefix, @Nullable String caller, String message) {
|
||||
if (Log.isLoggable(tag, priority)) {
|
||||
if (caller != null) {
|
||||
prefix += "[" + caller + "] ";
|
||||
}
|
||||
Slog.println(priority, tag, prefix + message);
|
||||
}
|
||||
}
|
||||
|
||||
private TransportUtils() {}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
/*
|
||||
* 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.transport;
|
||||
|
||||
import static com.android.server.backup.TransportManager.SERVICE_ACTION_TRANSPORT_HOST;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.isNull;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.robolectric.Shadows.shadowOf;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.UserHandle;
|
||||
import android.platform.test.annotations.Presubmit;
|
||||
|
||||
import com.android.internal.backup.IBackupTransport;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
import org.robolectric.shadows.ShadowLooper;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@Config(manifest = Config.NONE, sdk = 23)
|
||||
@Presubmit
|
||||
public class TransportClientTest {
|
||||
private static final String PACKAGE_NAME = "some.package.name";
|
||||
private static final ComponentName TRANSPORT_COMPONENT =
|
||||
new ComponentName(PACKAGE_NAME, PACKAGE_NAME + ".transport.Transport");
|
||||
|
||||
@Mock private Context mContext;
|
||||
@Mock private TransportConnectionListener mTransportConnectionListener;
|
||||
@Mock private TransportConnectionListener mTransportConnectionListener2;
|
||||
@Mock private IBackupTransport.Stub mIBackupTransport;
|
||||
private TransportClient mTransportClient;
|
||||
private Intent mBindIntent;
|
||||
private ShadowLooper mShadowLooper;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
Looper mainLooper = Looper.getMainLooper();
|
||||
mShadowLooper = shadowOf(mainLooper);
|
||||
mBindIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST).setComponent(TRANSPORT_COMPONENT);
|
||||
mTransportClient =
|
||||
new TransportClient(
|
||||
mContext, mBindIntent, TRANSPORT_COMPONENT, "1", new Handler(mainLooper));
|
||||
|
||||
when(mContext.bindServiceAsUser(
|
||||
eq(mBindIntent),
|
||||
any(ServiceConnection.class),
|
||||
anyInt(),
|
||||
any(UserHandle.class)))
|
||||
.thenReturn(true);
|
||||
}
|
||||
|
||||
// TODO: Testing implementation? Remove?
|
||||
@Test
|
||||
public void testConnectAsync_callsBindService() throws Exception {
|
||||
mTransportClient.connectAsync(mTransportConnectionListener, "caller");
|
||||
|
||||
verify(mContext)
|
||||
.bindServiceAsUser(
|
||||
eq(mBindIntent),
|
||||
any(ServiceConnection.class),
|
||||
anyInt(),
|
||||
any(UserHandle.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConnectAsync_callsListenerWhenConnected() throws Exception {
|
||||
mTransportClient.connectAsync(mTransportConnectionListener, "caller");
|
||||
|
||||
// Simulate framework connecting
|
||||
ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
|
||||
connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport);
|
||||
|
||||
mShadowLooper.runToEndOfTasks();
|
||||
verify(mTransportConnectionListener)
|
||||
.onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportClient));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConnectAsync_whenPendingConnection_callsAllListenersWhenConnected()
|
||||
throws Exception {
|
||||
mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
|
||||
ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
|
||||
|
||||
mTransportClient.connectAsync(mTransportConnectionListener2, "caller2");
|
||||
|
||||
connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport);
|
||||
|
||||
mShadowLooper.runToEndOfTasks();
|
||||
verify(mTransportConnectionListener)
|
||||
.onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportClient));
|
||||
verify(mTransportConnectionListener2)
|
||||
.onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportClient));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConnectAsync_whenAlreadyConnected_callsListener() throws Exception {
|
||||
mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
|
||||
ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
|
||||
connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport);
|
||||
|
||||
mTransportClient.connectAsync(mTransportConnectionListener2, "caller2");
|
||||
|
||||
mShadowLooper.runToEndOfTasks();
|
||||
verify(mTransportConnectionListener2)
|
||||
.onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportClient));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConnectAsync_whenFrameworkDoesntBind_callsListener() throws Exception {
|
||||
when(mContext.bindServiceAsUser(
|
||||
eq(mBindIntent),
|
||||
any(ServiceConnection.class),
|
||||
anyInt(),
|
||||
any(UserHandle.class)))
|
||||
.thenReturn(false);
|
||||
|
||||
mTransportClient.connectAsync(mTransportConnectionListener, "caller");
|
||||
|
||||
mShadowLooper.runToEndOfTasks();
|
||||
verify(mTransportConnectionListener)
|
||||
.onTransportConnectionResult(isNull(), eq(mTransportClient));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConnectAsync_whenFrameworkDoesntBind_releasesConnection() throws Exception {
|
||||
when(mContext.bindServiceAsUser(
|
||||
eq(mBindIntent),
|
||||
any(ServiceConnection.class),
|
||||
anyInt(),
|
||||
any(UserHandle.class)))
|
||||
.thenReturn(false);
|
||||
|
||||
mTransportClient.connectAsync(mTransportConnectionListener, "caller");
|
||||
|
||||
ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
|
||||
verify(mContext).unbindService(eq(connection));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConnectAsync_afterServiceDisconnectedBeforeNewConnection_callsListener()
|
||||
throws Exception {
|
||||
mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
|
||||
ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
|
||||
connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport);
|
||||
connection.onServiceDisconnected(TRANSPORT_COMPONENT);
|
||||
|
||||
mTransportClient.connectAsync(mTransportConnectionListener2, "caller1");
|
||||
|
||||
verify(mTransportConnectionListener2)
|
||||
.onTransportConnectionResult(isNull(), eq(mTransportClient));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConnectAsync_afterServiceDisconnectedAfterNewConnection_callsListener()
|
||||
throws Exception {
|
||||
mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
|
||||
ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
|
||||
connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport);
|
||||
connection.onServiceDisconnected(TRANSPORT_COMPONENT);
|
||||
connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport);
|
||||
|
||||
mTransportClient.connectAsync(mTransportConnectionListener2, "caller1");
|
||||
|
||||
// Yes, it should return null because the object became unusable, check design doc
|
||||
verify(mTransportConnectionListener2)
|
||||
.onTransportConnectionResult(isNull(), eq(mTransportClient));
|
||||
}
|
||||
|
||||
// TODO(b/69153972): Support SDK 26 API (ServiceConnection.inBindingDied) for transport tests
|
||||
/*@Test
|
||||
public void testConnectAsync_callsListenerIfBindingDies() throws Exception {
|
||||
mTransportClient.connectAsync(mTransportListener, "caller");
|
||||
|
||||
ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
|
||||
connection.onBindingDied(TRANSPORT_COMPONENT);
|
||||
|
||||
mShadowLooper.runToEndOfTasks();
|
||||
verify(mTransportListener).onTransportBound(isNull(), eq(mTransportClient));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testConnectAsync_whenPendingConnection_callsListenersIfBindingDies()
|
||||
throws Exception {
|
||||
mTransportClient.connectAsync(mTransportListener, "caller1");
|
||||
ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
|
||||
|
||||
mTransportClient.connectAsync(mTransportListener2, "caller2");
|
||||
|
||||
connection.onBindingDied(TRANSPORT_COMPONENT);
|
||||
|
||||
mShadowLooper.runToEndOfTasks();
|
||||
verify(mTransportListener).onTransportBound(isNull(), eq(mTransportClient));
|
||||
verify(mTransportListener2).onTransportBound(isNull(), eq(mTransportClient));
|
||||
}*/
|
||||
|
||||
private ServiceConnection verifyBindServiceAsUserAndCaptureServiceConnection(Context context) {
|
||||
ArgumentCaptor<ServiceConnection> connectionCaptor =
|
||||
ArgumentCaptor.forClass(ServiceConnection.class);
|
||||
verify(context)
|
||||
.bindServiceAsUser(
|
||||
any(Intent.class),
|
||||
connectionCaptor.capture(),
|
||||
anyInt(),
|
||||
any(UserHandle.class));
|
||||
return connectionCaptor.getValue();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user