Merge "API to select backup transport"
diff --git a/Android.mk b/Android.mk
index 2539c3d..71b77d5 100644
--- a/Android.mk
+++ b/Android.mk
@@ -107,6 +107,7 @@
core/java/android/app/backup/IFullBackupRestoreObserver.aidl \
core/java/android/app/backup/IRestoreObserver.aidl \
core/java/android/app/backup/IRestoreSession.aidl \
+ core/java/android/app/backup/ISelectBackupTransportCallback.aidl \
core/java/android/app/usage/IStorageStatsManager.aidl \
core/java/android/app/usage/IUsageStatsManager.aidl \
core/java/android/bluetooth/IBluetooth.aidl \
diff --git a/api/system-current.txt b/api/system-current.txt
index b6b25f5..5cd33ba 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -6787,15 +6787,18 @@
method public int requestBackup(java.lang.String[], android.app.backup.BackupObserver);
method public int requestBackup(java.lang.String[], android.app.backup.BackupObserver, int);
method public int requestRestore(android.app.backup.RestoreObserver);
- method public java.lang.String selectBackupTransport(java.lang.String);
+ method public deprecated java.lang.String selectBackupTransport(java.lang.String);
+ method public void selectBackupTransport(android.content.ComponentName, android.app.backup.SelectBackupTransportCallback);
method public void setAutoRestore(boolean);
method public void setBackupEnabled(boolean);
field public static final int ERROR_AGENT_FAILURE = -1003; // 0xfffffc15
field public static final int ERROR_BACKUP_NOT_ALLOWED = -2001; // 0xfffff82f
field public static final int ERROR_PACKAGE_NOT_FOUND = -2002; // 0xfffff82e
field public static final int ERROR_TRANSPORT_ABORTED = -1000; // 0xfffffc18
+ field public static final int ERROR_TRANSPORT_INVALID = -2; // 0xfffffffe
field public static final int ERROR_TRANSPORT_PACKAGE_REJECTED = -1002; // 0xfffffc16
field public static final int ERROR_TRANSPORT_QUOTA_EXCEEDED = -1005; // 0xfffffc13
+ field public static final int ERROR_TRANSPORT_UNAVAILABLE = -1; // 0xffffffff
field public static final int FLAG_NON_INCREMENTAL_BACKUP = 1; // 0x1
field public static final java.lang.String PACKAGE_MANAGER_SENTINEL = "@pm@";
field public static final int SUCCESS = 0; // 0x0
@@ -6910,6 +6913,12 @@
field public long token;
}
+ public abstract class SelectBackupTransportCallback {
+ ctor public SelectBackupTransportCallback();
+ method public void onFailure(int);
+ method public void onSuccess(java.lang.String);
+ }
+
public class SharedPreferencesBackupHelper extends android.app.backup.FileBackupHelperBase implements android.app.backup.BackupHelper {
ctor public SharedPreferencesBackupHelper(android.content.Context, java.lang.String...);
method public void performBackup(android.os.ParcelFileDescriptor, android.app.backup.BackupDataOutput, android.os.ParcelFileDescriptor);
diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
index 780db5e..7e91391 100644
--- a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
+++ b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java
@@ -18,20 +18,24 @@
import android.app.backup.BackupManager;
import android.app.backup.BackupProgress;
-import android.app.backup.RestoreSet;
import android.app.backup.IBackupManager;
import android.app.backup.IBackupObserver;
import android.app.backup.IRestoreObserver;
import android.app.backup.IRestoreSession;
+import android.app.backup.RestoreSet;
+import android.app.backup.ISelectBackupTransportCallback;
+import android.content.ComponentName;
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
+import android.util.Log;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
+import java.util.concurrent.CountDownLatch;
public final class Bmgr {
IBackupManager mBmgr;
@@ -363,6 +367,11 @@
return;
}
+ if ("-c".equals(which)) {
+ doTransportByComponent();
+ return;
+ }
+
String old = mBmgr.selectBackupTransport(which);
if (old == null) {
System.out.println("Unknown transport '" + which
@@ -370,12 +379,50 @@
} else {
System.out.println("Selected transport " + which + " (formerly " + old + ")");
}
+
} catch (RemoteException e) {
System.err.println(e.toString());
System.err.println(BMGR_NOT_RUNNING_ERR);
}
}
+ private void doTransportByComponent() {
+ String which = nextArg();
+ if (which == null) {
+ showUsage();
+ return;
+ }
+
+ final CountDownLatch latch = new CountDownLatch(1);
+
+ try {
+ mBmgr.selectBackupTransportAsync(ComponentName.unflattenFromString(which),
+ new ISelectBackupTransportCallback.Stub() {
+ @Override
+ public void onSuccess(String transportName) {
+ System.out.println("Success. Selected transport: " + transportName);
+ latch.countDown();
+ }
+
+ @Override
+ public void onFailure(int reason) {
+ System.err.println("Failure. error=" + reason);
+ latch.countDown();
+ }
+ });
+ } catch (RemoteException e) {
+ System.err.println(e.toString());
+ System.err.println(BMGR_NOT_RUNNING_ERR);
+ return;
+ }
+
+ try {
+ latch.await();
+ } catch (InterruptedException e) {
+ System.err.println("Operation interrupted.");
+ }
+ }
+
private void doWipe() {
String transport = nextArg();
if (transport == null) {
@@ -427,7 +474,16 @@
}
private void doListTransports() {
+ String arg = nextArg();
+
try {
+ if ("-c".equals(arg)) {
+ for (ComponentName transport : mBmgr.listAllTransportComponents()) {
+ System.out.println(transport.flattenToShortString());
+ }
+ return;
+ }
+
String current = mBmgr.getCurrentTransport();
String[] transports = mBmgr.listAllTransports();
if (transports == null || transports.length == 0) {
@@ -649,9 +705,9 @@
System.err.println(" bmgr backup PACKAGE");
System.err.println(" bmgr enable BOOL");
System.err.println(" bmgr enabled");
- System.err.println(" bmgr list transports");
+ System.err.println(" bmgr list transports [-c]");
System.err.println(" bmgr list sets");
- System.err.println(" bmgr transport WHICH");
+ System.err.println(" bmgr transport WHICH|-c WHICH_COMPONENT");
System.err.println(" bmgr restore TOKEN");
System.err.println(" bmgr restore TOKEN PACKAGE...");
System.err.println(" bmgr restore PACKAGE");
@@ -673,15 +729,18 @@
System.err.println("the backup mechanism.");
System.err.println("");
System.err.println("The 'list transports' command reports the names of the backup transports");
- System.err.println("currently available on the device. These names can be passed as arguments");
+ System.err.println("BackupManager is currently bound to. These names can be passed as arguments");
System.err.println("to the 'transport' and 'wipe' commands. The currently active transport");
- System.err.println("is indicated with a '*' character.");
+ System.err.println("is indicated with a '*' character. If -c flag is used, all available");
+ System.err.println("transport components on the device are listed. These can be used with");
+ System.err.println("the component variant of 'transport' command.");
System.err.println("");
System.err.println("The 'list sets' command reports the token and name of each restore set");
System.err.println("available to the device via the currently active transport.");
System.err.println("");
System.err.println("The 'transport' command designates the named transport as the currently");
- System.err.println("active one. This setting is persistent across reboots.");
+ System.err.println("active one. This setting is persistent across reboots. If -c flag is");
+ System.err.println("specified, the following string is treated as a component name.");
System.err.println("");
System.err.println("The 'restore' command when given just a restore token initiates a full-system");
System.err.println("restore operation from the currently active transport. It will deliver");
diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java
index 540683d..f0abe33 100644
--- a/core/java/android/app/backup/BackupManager.java
+++ b/core/java/android/app/backup/BackupManager.java
@@ -17,6 +17,7 @@
package android.app.backup;
import android.annotation.SystemApi;
+import android.content.ComponentName;
import android.content.Context;
import android.os.Handler;
import android.os.Message;
@@ -157,6 +158,25 @@
@SystemApi
public static final String PACKAGE_MANAGER_SENTINEL = "@pm@";
+
+ /**
+ * This error code is passed to {@link SelectBackupTransportCallback#onFailure(int)}
+ * if the requested transport is unavailable.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int ERROR_TRANSPORT_UNAVAILABLE = -1;
+
+ /**
+ * This error code is passed to {@link SelectBackupTransportCallback#onFailure(int)} if the
+ * requested transport is not a valid BackupTransport.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int ERROR_TRANSPORT_INVALID = -2;
+
private Context mContext;
private static IBackupManager sService;
@@ -390,17 +410,20 @@
}
/**
- * Specify the current backup transport. Callers must hold the
- * android.permission.BACKUP permission to use this method.
+ * Specify the current backup transport.
+ *
+ * <p> Callers must hold the android.permission.BACKUP permission to use this method.
*
* @param transport The name of the transport to select. This should be one
- * of the names returned by {@link #listAllTransports()}.
+ * of the names returned by {@link #listAllTransports()}. This is the String returned by
+ * {@link BackupTransport#name()} for the particular transport.
* @return The name of the previously selected transport. If the given transport
* name is not one of the currently available transports, no change is made to
* the current transport setting and the method returns null.
*
* @hide
*/
+ @Deprecated
@SystemApi
public String selectBackupTransport(String transport) {
checkServiceBinder();
@@ -415,6 +438,34 @@
}
/**
+ * Specify the current backup transport and get notified when the transport is ready to be used.
+ * This method is async because BackupManager might need to bind to the specified transport
+ * which is in a separate process.
+ *
+ * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+ *
+ * @param transport ComponentName of the service hosting the transport. This is different from
+ * the transport's name that is returned by {@link BackupTransport#name()}.
+ * @param listener A listener object to get a callback on the transport being selected.
+ *
+ * @hide
+ */
+ @SystemApi
+ public void selectBackupTransport(ComponentName transport,
+ SelectBackupTransportCallback listener) {
+ checkServiceBinder();
+ if (sService != null) {
+ try {
+ SelectTransportListenerWrapper wrapper = listener == null ?
+ null : new SelectTransportListenerWrapper(mContext, listener);
+ sService.selectBackupTransportAsync(transport, wrapper);
+ } catch (RemoteException e) {
+ Log.e(TAG, "selectBackupTransportAsync() couldn't connect");
+ }
+ }
+ }
+
+ /**
* Schedule an immediate backup attempt for all pending key/value updates. This
* is primarily intended for transports to use when they detect a suitable
* opportunity for doing a backup pass. If there are no pending updates to
@@ -598,4 +649,35 @@
mHandler.obtainMessage(MSG_FINISHED, status, 0));
}
}
+
+ private class SelectTransportListenerWrapper extends ISelectBackupTransportCallback.Stub {
+
+ private final Handler mHandler;
+ private final SelectBackupTransportCallback mListener;
+
+ SelectTransportListenerWrapper(Context context, SelectBackupTransportCallback listener) {
+ mHandler = new Handler(context.getMainLooper());
+ mListener = listener;
+ }
+
+ @Override
+ public void onSuccess(final String transportName) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mListener.onSuccess(transportName);
+ }
+ });
+ }
+
+ @Override
+ public void onFailure(final int reason) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mListener.onFailure(reason);
+ }
+ });
+ }
+ }
}
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index fe23c28..1657e2e 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -19,8 +19,10 @@
import android.app.backup.IBackupObserver;
import android.app.backup.IFullBackupRestoreObserver;
import android.app.backup.IRestoreSession;
+import android.app.backup.ISelectBackupTransportCallback;
import android.os.ParcelFileDescriptor;
import android.content.Intent;
+import android.content.ComponentName;
/**
* Direct interface to the Backup Manager Service that applications invoke on. The only
@@ -217,6 +219,8 @@
*/
String[] listAllTransports();
+ ComponentName[] listAllTransportComponents();
+
/**
* Retrieve the list of whitelisted transport components. Callers do </i>not</i> need
* any special permission.
@@ -238,6 +242,21 @@
String selectBackupTransport(String transport);
/**
+ * Specify the current backup transport and get notified when the transport is ready to be used.
+ * This method is async because BackupManager might need to bind to the specified transport
+ * which is in a separate process.
+ *
+ * <p>Callers must hold the android.permission.BACKUP permission to use this method.
+ *
+ * @param transport ComponentName of the service hosting the transport. This is different from
+ * the transport's name that is returned by {@link BackupTransport#name()}.
+ * @param listener A listener object to get a callback on the transport being selected.
+ *
+ * @hide
+ */
+ void selectBackupTransportAsync(in ComponentName transport, ISelectBackupTransportCallback listener);
+
+ /**
* Get the configuration Intent, if any, from the given transport. Callers must
* hold the android.permission.BACKUP permission in order to use this method.
*
diff --git a/core/java/android/app/backup/ISelectBackupTransportCallback.aidl b/core/java/android/app/backup/ISelectBackupTransportCallback.aidl
new file mode 100644
index 0000000..5de7c5e
--- /dev/null
+++ b/core/java/android/app/backup/ISelectBackupTransportCallback.aidl
@@ -0,0 +1,41 @@
+/*
+ * 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 android.app.backup;
+
+/**
+ * Callback class for receiving success or failure callbacks on selecting a backup transport. These
+ * methods will all be called on your application's main thread.
+ *
+ * @hide
+ */
+oneway interface ISelectBackupTransportCallback {
+
+ /**
+ * Called when BackupManager has successfully bound to the requested transport.
+ *
+ * @param transportName Name of the selected transport. This is the String returned by
+ * {@link BackupTransport#name()}.
+ */
+ void onSuccess(String transportName);
+
+ /**
+ * Called when BackupManager fails to bind to the requested transport.
+ *
+ * @param reason Error code denoting reason for failure.
+ */
+ void onFailure(int reason);
+}
diff --git a/core/java/android/app/backup/SelectBackupTransportCallback.java b/core/java/android/app/backup/SelectBackupTransportCallback.java
new file mode 100644
index 0000000..0c8a0dc
--- /dev/null
+++ b/core/java/android/app/backup/SelectBackupTransportCallback.java
@@ -0,0 +1,44 @@
+/*
+ * 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 android.app.backup;
+
+import android.annotation.SystemApi;
+
+/**
+ * Callback class for receiving success or failure callbacks on selecting a backup transport. These
+ * methods will all be called on your application's main thread.
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class SelectBackupTransportCallback {
+
+ /**
+ * Called when BackupManager has successfully bound to the requested transport.
+ *
+ * @param transportName Name of the selected transport. This is the String returned by
+ * {@link BackupTransport#name()}.
+ */
+ public void onSuccess(String transportName){}
+
+ /**
+ * Called when BackupManager fails to bind to the requested transport.
+ *
+ * @param reason Error code denoting reason for failure.
+ */
+ public void onFailure(int reason){}
+}
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 7e82586..88c05b5 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -34,13 +34,15 @@
import android.app.backup.BackupTransport;
import android.app.backup.FullBackup;
import android.app.backup.FullBackupDataOutput;
-import android.app.backup.IBackupObserver;
-import android.app.backup.RestoreDescription;
-import android.app.backup.RestoreSet;
import android.app.backup.IBackupManager;
+import android.app.backup.IBackupObserver;
import android.app.backup.IFullBackupRestoreObserver;
import android.app.backup.IRestoreObserver;
import android.app.backup.IRestoreSession;
+import android.app.backup.ISelectBackupTransportCallback;
+import android.app.backup.RestoreDescription;
+import android.app.backup.RestoreSet;
+import android.app.backup.SelectBackupTransportCallback;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -55,16 +57,15 @@
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.content.pm.Signature;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.Signature;
import android.database.ContentObserver;
import android.net.Uri;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
+import android.os.Environment.UserEnvironment;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
@@ -79,15 +80,12 @@
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.WorkSource;
-import android.os.Environment.UserEnvironment;
import android.os.storage.IStorageManager;
import android.os.storage.StorageManager;
import android.provider.Settings;
import android.system.ErrnoException;
import android.system.Os;
import android.text.TextUtils;
-import android.util.ArrayMap;
-import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.EventLog;
import android.util.Log;
@@ -105,6 +103,8 @@
import com.android.server.SystemService;
import com.android.server.backup.PackageManagerBackupAgent.Metadata;
+import libcore.io.IoUtils;
+
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
@@ -139,7 +139,6 @@
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
-import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Random;
@@ -166,8 +165,6 @@
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
-import libcore.io.IoUtils;
-
public class BackupManagerService {
private static final String TAG = "BackupManagerService";
@@ -271,6 +268,8 @@
private IStorageManager mStorageManager;
IBackupManager mBackupManagerBinder;
+ private final TransportManager mTransportManager;
+
boolean mEnabled; // access to this is synchronized on 'this'
boolean mProvisioned;
boolean mAutoRestore;
@@ -322,16 +321,6 @@
final Object mClearDataLock = new Object();
volatile boolean mClearingData;
- // Transport bookkeeping
- final ArraySet<ComponentName> mTransportWhitelist;
- final Intent mTransportServiceIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST);
- final ArrayMap<String,String> mTransportNames
- = new ArrayMap<String,String>(); // component name -> registration name
- final ArrayMap<String,IBackupTransport> mTransports
- = new ArrayMap<String,IBackupTransport>(); // registration name -> binder
- final ArrayMap<String,TransportConnection> mTransportConnections
- = new ArrayMap<String,TransportConnection>();
- String mCurrentTransport;
ActiveRestoreSession mActiveRestoreSession;
// Watch the device provisioning operation during setup
@@ -756,7 +745,7 @@
{
mLastBackupPass = System.currentTimeMillis();
- IBackupTransport transport = getTransport(mCurrentTransport);
+ IBackupTransport transport = mTransportManager.getCurrentTransportBinder();
if (transport == null) {
Slog.v(TAG, "Backup requested but no transport available");
synchronized (mQueueLock) {
@@ -1202,32 +1191,19 @@
// Set up our transport options and initialize the default transport
// TODO: Don't create transports that we don't need to?
SystemConfig systemConfig = SystemConfig.getInstance();
- mTransportWhitelist = systemConfig.getBackupTransportWhitelist();
+ Set<ComponentName> transportWhitelist = systemConfig.getBackupTransportWhitelist();
String transport = Settings.Secure.getString(context.getContentResolver(),
Settings.Secure.BACKUP_TRANSPORT);
if (TextUtils.isEmpty(transport)) {
transport = null;
}
- mCurrentTransport = transport;
- if (DEBUG) Slog.v(TAG, "Starting with transport " + mCurrentTransport);
+ String currentTransport = transport;
+ if (DEBUG) Slog.v(TAG, "Starting with transport " + currentTransport);
- // Find all transport hosts and bind to their services
- // TODO: http://b/22388012
- List<ResolveInfo> hosts = mPackageManager.queryIntentServicesAsUser(
- mTransportServiceIntent, 0, UserHandle.USER_SYSTEM);
- if (DEBUG) {
- Slog.v(TAG, "Found transports: " + ((hosts == null) ? "null" : hosts.size()));
- }
- if (hosts != null) {
- for (int i = 0; i < hosts.size(); i++) {
- final ServiceInfo transportService = hosts.get(i).serviceInfo;
- if (MORE_DEBUG) {
- Slog.v(TAG, " " + transportService.packageName + "/" + transportService.name);
- }
- tryBindTransport(transportService);
- }
- }
+ mTransportManager = new TransportManager(context, transportWhitelist, currentTransport,
+ mTransportBoundListener);
+ mTransportManager.registerAllTransports();
// Now that we know about valid backup participants, parse any
// leftover journal files into the pending backup set
@@ -1751,7 +1727,7 @@
mBackupHandler.removeMessages(MSG_RETRY_INIT);
try {
- IBackupTransport transport = getTransport(transportName);
+ IBackupTransport transport = mTransportManager.getTransportBinder(transportName);
if (transport != null) {
String transportDirName = transport.transportDirName();
File stateDir = new File(mBaseStateDir, transportDirName);
@@ -1829,49 +1805,39 @@
}
}
- // Add a transport to our set of available backends. If 'transport' is null, this
- // is an unregistration, and the transport's entry is removed from our bookkeeping.
- private void registerTransport(String name, String component, IBackupTransport transport) {
- synchronized (mTransports) {
- if (DEBUG) Slog.v(TAG, "Registering transport "
- + component + "::" + name + " = " + transport);
- if (transport != null) {
- mTransports.put(name, transport);
- mTransportNames.put(component, name);
- } else {
- mTransports.remove(mTransportNames.get(component));
- mTransportNames.remove(component);
- // Nothing further to do in the unregistration case
- return;
- }
- }
+ 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();
- // 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.
- try {
- String transportName = transport.transportDirName();
- File stateDir = new File(mBaseStateDir, transportName);
- stateDir.mkdirs();
+ File initSentinel = new File(stateDir, INIT_SENTINEL_FILE_NAME);
+ if (initSentinel.exists()) {
+ synchronized (mQueueLock) {
+ mPendingInits.add(name);
- File initSentinel = new File(stateDir, INIT_SENTINEL_FILE_NAME);
- if (initSentinel.exists()) {
- synchronized (mQueueLock) {
- mPendingInits.add(name);
-
- // 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);
+ // 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;
}
- } catch (Exception e) {
- // the transport threw when asked its file naming prefs; declare it invalid
- Slog.e(TAG, "Unable to register transport as " + name);
- mTransportNames.remove(component);
- mTransports.remove(name);
}
- }
+ };
// ----- Track installation/removal of packages -----
BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@@ -1899,75 +1865,17 @@
// At package-changed we only care about looking at new transport states
if (changed) {
- try {
- String[] components =
- intent.getStringArrayExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
+ String[] components =
+ intent.getStringArrayExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
- if (MORE_DEBUG) {
- Slog.i(TAG, "Package " + pkgName + " changed; rechecking");
- for (int i = 0; i < components.length; i++) {
- Slog.i(TAG, " * " + components[i]);
- }
- }
-
- // In general we need to try to bind any time we see a component enable
- // state change, because that change may have made a transport available.
- // However, because we currently only support a single transport component
- // per package, we can skip the bind attempt if the change (a) affects a
- // package known to host a transport, but (b) does not affect the known
- // transport component itself.
- //
- // In addition, if the change *is* to a known transport component, we need
- // to unbind it before retrying the binding.
- boolean tryBind = true;
- synchronized (mTransports) {
- TransportConnection conn = mTransportConnections.get(pkgName);
- if (conn != null) {
- // We have a bound transport in this package; do we need to rebind it?
- final ServiceInfo svc = conn.mTransport;
- ComponentName svcName =
- new ComponentName(svc.packageName, svc.name);
- if (svc.packageName.equals(pkgName)) {
- final String className = svcName.getClassName();
- if (MORE_DEBUG) {
- Slog.i(TAG, "Checking need to rebind " + className);
- }
- // See whether it's the transport component within this package
- boolean isTransport = false;
- for (int i = 0; i < components.length; i++) {
- if (className.equals(components[i])) {
- // Okay, it's an existing transport component.
- final String flatName = svcName.flattenToShortString();
- mContext.unbindService(conn);
- mTransportConnections.remove(pkgName);
- mTransports.remove(mTransportNames.get(flatName));
- mTransportNames.remove(flatName);
- isTransport = true;
- break;
- }
- }
- if (!isTransport) {
- // A non-transport component within a package that is hosting
- // a bound transport
- tryBind = false;
- }
- }
- }
- }
- // and now (re)bind as appropriate
- if (tryBind) {
- if (MORE_DEBUG) {
- Slog.i(TAG, "Yes, need to recheck binding");
- }
- PackageInfo app = mPackageManager.getPackageInfo(pkgName, 0);
- checkForTransportAndBind(app);
- }
- } catch (NameNotFoundException e) {
- // Nope, can't find it - just ignore
- if (MORE_DEBUG) {
- Slog.w(TAG, "Can't find changed package " + pkgName);
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Package " + pkgName + " changed; rechecking");
+ for (int i = 0; i < components.length; i++) {
+ Slog.i(TAG, " * " + components[i]);
}
}
+
+ mTransportManager.onPackageChanged(pkgName, components);
return; // nothing more to do in the PACKAGE_CHANGED case
}
@@ -2015,19 +1923,7 @@
writeFullBackupScheduleAsync();
}
- // Transport maintenance: rebind to known existing transports that have
- // just been updated; and bind to any newly-installed transport services.
- synchronized (mTransports) {
- final TransportConnection conn = mTransportConnections.get(packageName);
- if (conn != null) {
- if (MORE_DEBUG) {
- Slog.i(TAG, "Transport package changed; rebinding");
- }
- bindTransport(conn.mTransport);
- } else {
- checkForTransportAndBind(app);
- }
- }
+ mTransportManager.onPackageAdded(packageName);
} catch (NameNotFoundException e) {
// doesn't really exist; ignore it
@@ -2051,107 +1947,13 @@
removePackageParticipantsLocked(pkgList, uid);
}
}
+ for (String pkgName : pkgList) {
+ mTransportManager.onPackageRemoved(pkgName);
+ }
}
}
};
- // ----- Track connection to transports service -----
- class TransportConnection implements ServiceConnection {
- ServiceInfo mTransport;
-
- public TransportConnection(ServiceInfo transport) {
- mTransport = transport;
- }
-
- @Override
- public void onServiceConnected(ComponentName component, IBinder service) {
- if (DEBUG) Slog.v(TAG, "Connected to transport " + component);
- final String name = component.flattenToShortString();
- try {
- IBackupTransport transport = IBackupTransport.Stub.asInterface(service);
- registerTransport(transport.name(), name, transport);
- EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, name, 1);
- } catch (Exception e) {
- Slog.e(TAG, "Unable to register transport " + component
- + ": " + e.getMessage());
- EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, name, 0);
- }
- }
-
- @Override
- public void onServiceDisconnected(ComponentName component) {
- if (DEBUG) Slog.v(TAG, "Disconnected from transport " + component);
- final String name = component.flattenToShortString();
- EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, name, 0);
- registerTransport(null, name, null);
- }
- };
-
- // Check whether the given package hosts a transport, and bind if so
- void checkForTransportAndBind(PackageInfo pkgInfo) {
- Intent intent = new Intent(mTransportServiceIntent)
- .setPackage(pkgInfo.packageName);
- // TODO: http://b/22388012
- List<ResolveInfo> hosts = mPackageManager.queryIntentServicesAsUser(
- intent, 0, UserHandle.USER_SYSTEM);
- if (hosts != null) {
- final int N = hosts.size();
- for (int i = 0; i < N; i++) {
- final ServiceInfo info = hosts.get(i).serviceInfo;
- tryBindTransport(info);
- }
- }
- }
-
- // Verify that the service exists and is hosted by a privileged app, then proceed to bind
- boolean tryBindTransport(ServiceInfo info) {
- try {
- PackageInfo packInfo = mPackageManager.getPackageInfo(info.packageName, 0);
- if ((packInfo.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED)
- != 0) {
- return bindTransport(info);
- } else {
- Slog.w(TAG, "Transport package " + info.packageName + " not privileged");
- }
- } catch (NameNotFoundException e) {
- Slog.w(TAG, "Problem resolving transport package " + info.packageName);
- }
- return false;
- }
-
- // Actually bind; presumes that we have already validated the transport service
- boolean bindTransport(ServiceInfo transport) {
- ComponentName svcName = new ComponentName(transport.packageName, transport.name);
- if (!mTransportWhitelist.contains(svcName)) {
- Slog.w(TAG, "Proposed transport " + svcName + " not whitelisted; ignoring");
- return false;
- }
-
- if (MORE_DEBUG) {
- Slog.i(TAG, "Binding to transport host " + svcName);
- }
- Intent intent = new Intent(mTransportServiceIntent);
- intent.setComponent(svcName);
-
- TransportConnection connection;
- synchronized (mTransports) {
- connection = mTransportConnections.get(transport.packageName);
- if (null == connection) {
- connection = new TransportConnection(transport);
- mTransportConnections.put(transport.packageName, connection);
- } else {
- // This is a rebind due to package upgrade. The service won't be
- // automatically relaunched for us until we explicitly rebind, but
- // we need to unbind the now-orphaned original connection.
- mContext.unbindService(connection);
- }
- }
- // TODO: http://b/22388012
- return mContext.bindServiceAsUser(intent,
- connection, Context.BIND_AUTO_CREATE,
- UserHandle.SYSTEM);
- }
-
// Add the backup agents in the given packages to our set of known backup participants.
// If 'packageNames' is null, adds all backup agents in the whole system.
void addPackageParticipantsLocked(String[] packageNames) {
@@ -2352,34 +2154,12 @@
}
}
- // Return the given transport
- private IBackupTransport getTransport(String transportName) {
- synchronized (mTransports) {
- IBackupTransport transport = mTransports.get(transportName);
- if (transport == null) {
- Slog.w(TAG, "Requested unavailable transport: " + transportName);
- }
- return transport;
- }
- }
-
// What name is this transport registered under...?
private String getTransportName(IBackupTransport transport) {
if (MORE_DEBUG) {
Slog.v(TAG, "Searching for transport name of " + transport);
}
- synchronized (mTransports) {
- final int N = mTransports.size();
- for (int i = 0; i < N; i++) {
- if (mTransports.valueAt(i).equals(transport)) {
- if (MORE_DEBUG) {
- Slog.v(TAG, " Name found: " + mTransports.keyAt(i));
- }
- return mTransports.keyAt(i);
- }
- }
- }
- return null;
+ return mTransportManager.getTransportName(transport);
}
// fire off a backup agent, blocking until it attaches or times out
@@ -2505,7 +2285,7 @@
throw new IllegalArgumentException("No packages are provided for backup");
}
- IBackupTransport transport = getTransport(mCurrentTransport);
+ IBackupTransport transport = mTransportManager.getCurrentTransportBinder();
if (transport == null) {
sendBackupFinished(observer, BackupManager.ERROR_TRANSPORT_ABORTED);
return BackupManager.ERROR_TRANSPORT_ABORTED;
@@ -3025,7 +2805,7 @@
if (MORE_DEBUG) Slog.d(TAG, "Server requires init; rerunning");
addBackupTrace("init required; rerunning");
try {
- final String name = getTransportName(mTransport);
+ final String name = mTransportManager.getTransportName(mTransport);
if (name != null) {
mPendingInits.add(name);
} else {
@@ -4503,7 +4283,7 @@
return;
}
- IBackupTransport transport = getTransport(mCurrentTransport);
+ IBackupTransport transport = mTransportManager.getCurrentTransportBinder();
if (transport == null) {
Slog.w(TAG, "Transport not present; full data backup not performed");
backupRunStatus = BackupManager.ERROR_TRANSPORT_ABORTED;
@@ -5119,7 +4899,7 @@
headBusy = false;
- if (!fullBackupAllowable(getTransport(mCurrentTransport))) {
+ if (!fullBackupAllowable(mTransportManager.getCurrentTransportBinder())) {
if (MORE_DEBUG) {
Slog.i(TAG, "Preconditions not met; not running full backup");
}
@@ -9115,7 +8895,8 @@
public void run() {
try {
for (String transportName : mQueue) {
- IBackupTransport transport = getTransport(transportName);
+ IBackupTransport transport =
+ mTransportManager.getTransportBinder(transportName);
if (transport == null) {
Slog.e(TAG, "Requested init for " + transportName + " but not found");
continue;
@@ -9312,7 +9093,8 @@
if (MORE_DEBUG) Slog.v(TAG, "Found the app - running clear process");
mBackupHandler.removeMessages(MSG_RETRY_CLEAR);
synchronized (mQueueLock) {
- final IBackupTransport transport = getTransport(transportName);
+ final IBackupTransport transport =
+ mTransportManager.getTransportBinder(transportName);
if (transport == null) {
// transport is currently unavailable -- make sure to retry
Message msg = mBackupHandler.obtainMessage(MSG_RETRY_CLEAR,
@@ -9450,7 +9232,7 @@
throw new IllegalStateException("Restore supported only for the device owner");
}
- if (!fullBackupAllowable(getTransport(mCurrentTransport))) {
+ if (!fullBackupAllowable(mTransportManager.getCurrentTransportBinder())) {
Slog.i(TAG, "Full backup not currently possible -- key/value backup not yet run?");
} else {
if (DEBUG) {
@@ -9718,10 +9500,7 @@
if (wasEnabled && mProvisioned) {
// NOTE: we currently flush every registered transport, not just
// the currently-active one.
- HashSet<String> allTransports;
- synchronized (mTransports) {
- allTransports = new HashSet<String>(mTransports.keySet());
- }
+ String[] allTransports = mTransportManager.getBoundTransportNames();
// build the set of transports for which we are posting an init
for (String transport : allTransports) {
recordInitPendingLocked(true, transport);
@@ -9774,36 +9553,27 @@
public String getCurrentTransport() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"getCurrentTransport");
- if (MORE_DEBUG) Slog.v(TAG, "... getCurrentTransport() returning " + mCurrentTransport);
- return mCurrentTransport;
+ String currentTransport = mTransportManager.getCurrentTransportName();
+ if (MORE_DEBUG) Slog.v(TAG, "... getCurrentTransport() returning " + currentTransport);
+ return currentTransport;
}
// Report all known, available backup transports
public String[] listAllTransports() {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "listAllTransports");
- String[] list = null;
- ArrayList<String> known = new ArrayList<String>();
- for (Map.Entry<String, IBackupTransport> entry : mTransports.entrySet()) {
- if (entry.getValue() != null) {
- known.add(entry.getKey());
- }
- }
+ return mTransportManager.getBoundTransportNames();
+ }
- if (known.size() > 0) {
- list = new String[known.size()];
- known.toArray(list);
- }
- return list;
+ public ComponentName[] listAllTransportComponents() {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
+ "listAllTransportComponents");
+ return mTransportManager.getAllTransportCompenents();
}
public String[] getTransportWhitelist() {
// No permission check, intentionally.
- String[] whitelist = new String[mTransportWhitelist.size()];
- for (int i = mTransportWhitelist.size() - 1; i >= 0; i--) {
- whitelist[i] = mTransportWhitelist.valueAt(i).flattenToShortString();
- }
- return whitelist;
+ return mTransportManager.getTransportWhitelist().toArray(new String[0]);
}
// Select which transport to use for the next backup operation.
@@ -9811,22 +9581,58 @@
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"selectBackupTransport");
- synchronized (mTransports) {
- final long oldId = Binder.clearCallingIdentity();
- try {
- String prevTransport = mCurrentTransport;
- mCurrentTransport = transport;
- Settings.Secure.putString(mContext.getContentResolver(),
- Settings.Secure.BACKUP_TRANSPORT, transport);
- Slog.v(TAG, "selectBackupTransport() set " + mCurrentTransport
- + " returning " + prevTransport);
- return prevTransport;
- } finally {
- Binder.restoreCallingIdentity(oldId);
- }
+ final long oldId = Binder.clearCallingIdentity();
+ try {
+ String prevTransport = mTransportManager.selectTransport(transport);
+ Settings.Secure.putString(mContext.getContentResolver(),
+ Settings.Secure.BACKUP_TRANSPORT, transport);
+ Slog.v(TAG, "selectBackupTransport() set " + mTransportManager.getCurrentTransportName()
+ + " returning " + prevTransport);
+ return prevTransport;
+ } finally {
+ Binder.restoreCallingIdentity(oldId);
}
}
+ public void selectBackupTransportAsync(final ComponentName transport,
+ final ISelectBackupTransportCallback listener) {
+ mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
+ "selectBackupTransportAsync");
+
+ final long oldId = Binder.clearCallingIdentity();
+
+ Slog.v(TAG, "selectBackupTransportAsync() called with transport " +
+ transport.flattenToShortString());
+
+ mTransportManager.ensureTransportReady(transport, new SelectBackupTransportCallback() {
+ @Override
+ public void onSuccess(String transportName) {
+ mTransportManager.selectTransport(transportName);
+ Settings.Secure.putString(mContext.getContentResolver(),
+ Settings.Secure.BACKUP_TRANSPORT,
+ mTransportManager.getCurrentTransportName());
+ Slog.v(TAG, "Transport successfully selected: " + transport.flattenToShortString());
+ try {
+ listener.onSuccess(transportName);
+ } catch (RemoteException e) {
+ // Nothing to do here.
+ }
+ }
+
+ @Override
+ public void onFailure(int reason) {
+ Slog.v(TAG, "Failed to select transport: " + transport.flattenToShortString());
+ try {
+ listener.onFailure(reason);
+ } catch (RemoteException e) {
+ // Nothing to do here.
+ }
+ }
+ });
+
+ Binder.restoreCallingIdentity(oldId);
+ }
+
// Supply the configuration Intent for the given transport. If the name is not one
// of the available transports, or if the transport does not supply any configuration
// UI, the method returns null.
@@ -9834,18 +9640,16 @@
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"getConfigurationIntent");
- synchronized (mTransports) {
- final IBackupTransport transport = mTransports.get(transportName);
- if (transport != null) {
- try {
- final Intent intent = transport.configurationIntent();
- if (MORE_DEBUG) Slog.d(TAG, "getConfigurationIntent() returning config intent "
- + intent);
- return intent;
- } catch (Exception e) {
- /* fall through to return null */
- Slog.e(TAG, "Unable to get configuration intent from transport: " + e.getMessage());
- }
+ final IBackupTransport transport = mTransportManager.getTransportBinder(transportName);
+ if (transport != null) {
+ try {
+ final Intent intent = transport.configurationIntent();
+ if (MORE_DEBUG) Slog.d(TAG, "getConfigurationIntent() returning config intent "
+ + intent);
+ return intent;
+ } catch (Exception e) {
+ /* fall through to return null */
+ Slog.e(TAG, "Unable to get configuration intent from transport: " + e.getMessage());
}
}
@@ -9861,17 +9665,15 @@
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"getDestinationString");
- synchronized (mTransports) {
- final IBackupTransport transport = mTransports.get(transportName);
- if (transport != null) {
- try {
- final String text = transport.currentDestinationString();
- if (MORE_DEBUG) Slog.d(TAG, "getDestinationString() returning " + text);
- return text;
- } catch (Exception e) {
- /* fall through to return null */
- Slog.e(TAG, "Unable to get string from transport: " + e.getMessage());
- }
+ final IBackupTransport transport = mTransportManager.getTransportBinder(transportName);
+ if (transport != null) {
+ try {
+ final String text = transport.currentDestinationString();
+ if (MORE_DEBUG) Slog.d(TAG, "getDestinationString() returning " + text);
+ return text;
+ } catch (Exception e) {
+ /* fall through to return null */
+ Slog.e(TAG, "Unable to get string from transport: " + e.getMessage());
}
}
@@ -9883,18 +9685,16 @@
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"getDataManagementIntent");
- synchronized (mTransports) {
- final IBackupTransport transport = mTransports.get(transportName);
- if (transport != null) {
- try {
- final Intent intent = transport.dataManagementIntent();
- if (MORE_DEBUG) Slog.d(TAG, "getDataManagementIntent() returning intent "
- + intent);
- return intent;
- } catch (Exception e) {
- /* fall through to return null */
- Slog.e(TAG, "Unable to get management intent from transport: " + e.getMessage());
- }
+ final IBackupTransport transport = mTransportManager.getTransportBinder(transportName);
+ if (transport != null) {
+ try {
+ final Intent intent = transport.dataManagementIntent();
+ if (MORE_DEBUG) Slog.d(TAG, "getDataManagementIntent() returning intent "
+ + intent);
+ return intent;
+ } catch (Exception e) {
+ /* fall through to return null */
+ Slog.e(TAG, "Unable to get management intent from transport: " + e.getMessage());
}
}
@@ -9907,17 +9707,15 @@
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
"getDataManagementLabel");
- synchronized (mTransports) {
- final IBackupTransport transport = mTransports.get(transportName);
- if (transport != null) {
- try {
- final String text = transport.dataManagementLabel();
- if (MORE_DEBUG) Slog.d(TAG, "getDataManagementLabel() returning " + text);
- return text;
- } catch (Exception e) {
- /* fall through to return null */
- Slog.e(TAG, "Unable to get management label from transport: " + e.getMessage());
- }
+ final IBackupTransport transport = mTransportManager.getTransportBinder(transportName);
+ if (transport != null) {
+ try {
+ final String text = transport.dataManagementLabel();
+ if (MORE_DEBUG) Slog.d(TAG, "getDataManagementLabel() returning " + text);
+ return text;
+ } catch (Exception e) {
+ /* fall through to return null */
+ Slog.e(TAG, "Unable to get management label from transport: " + e.getMessage());
}
}
@@ -9979,7 +9777,7 @@
}
// Do we have a transport to fetch data for us?
- IBackupTransport transport = getTransport(mCurrentTransport);
+ IBackupTransport transport = mTransportManager.getCurrentTransportBinder();
if (transport == null) {
if (DEBUG) Slog.w(TAG, "No transport");
skip = true;
@@ -10033,7 +9831,7 @@
boolean needPermission = true;
if (transport == null) {
- transport = mCurrentTransport;
+ transport = mTransportManager.getCurrentTransportName();
if (packageName != null) {
PackageInfo app = null;
@@ -10127,7 +9925,7 @@
appIsStopped(packageInfo.applicationInfo)) {
return false;
}
- IBackupTransport transport = getTransport(mCurrentTransport);
+ IBackupTransport transport = mTransportManager.getCurrentTransportBinder();
if (transport != null) {
try {
return transport.isAppEligibleForBackup(packageInfo,
@@ -10156,7 +9954,7 @@
ActiveRestoreSession(String packageName, String transport) {
mPackageName = packageName;
- mRestoreTransport = getTransport(transport);
+ mRestoreTransport = mTransportManager.getTransportBinder(transport);
}
public void markTimedOut() {
@@ -10515,7 +10313,7 @@
pw.println(" next scheduled: " + KeyValueBackupJob.nextScheduled());
pw.println("Transport whitelist:");
- for (ComponentName transport : mTransportWhitelist) {
+ for (ComponentName transport : mTransportManager.getTransportWhitelist()) {
pw.print(" ");
pw.println(transport.flattenToShortString());
}
@@ -10524,9 +10322,9 @@
final String[] transports = listAllTransports();
if (transports != null) {
for (String t : listAllTransports()) {
- pw.println((t.equals(mCurrentTransport) ? " * " : " ") + t);
+ pw.println((t.equals(mTransportManager.getCurrentTransportName()) ? " * " : " ") + t);
try {
- IBackupTransport transport = getTransport(t);
+ IBackupTransport transport = mTransportManager.getTransportBinder(t);
File dir = new File(mBaseStateDir, transport.transportDirName());
pw.println(" destination: " + transport.currentDestinationString());
pw.println(" intent: " + transport.configurationIntent());
diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java
index d677f5e..a1a2c95 100644
--- a/services/backup/java/com/android/server/backup/Trampoline.java
+++ b/services/backup/java/com/android/server/backup/Trampoline.java
@@ -20,6 +20,8 @@
import android.app.backup.IBackupObserver;
import android.app.backup.IFullBackupRestoreObserver;
import android.app.backup.IRestoreSession;
+import android.app.backup.ISelectBackupTransportCallback;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.os.Binder;
@@ -275,6 +277,12 @@
}
@Override
+ public ComponentName[] listAllTransportComponents() throws RemoteException {
+ BackupManagerService svc = mService;
+ return (svc != null) ? svc.listAllTransportComponents() : null;
+ }
+
+ @Override
public String[] getTransportWhitelist() {
BackupManagerService svc = mService;
return (svc != null) ? svc.getTransportWhitelist() : null;
@@ -287,6 +295,15 @@
}
@Override
+ public void selectBackupTransportAsync(ComponentName transport,
+ ISelectBackupTransportCallback listener) throws RemoteException {
+ BackupManagerService svc = mService;
+ if (svc != null) {
+ svc.selectBackupTransportAsync(transport, listener);
+ }
+ }
+
+ @Override
public Intent getConfigurationIntent(String transport) throws RemoteException {
BackupManagerService svc = mService;
return (svc != null) ? svc.getConfigurationIntent(transport) : null;
diff --git a/services/backup/java/com/android/server/backup/TransportManager.java b/services/backup/java/com/android/server/backup/TransportManager.java
new file mode 100644
index 0000000..93d5a1e
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/TransportManager.java
@@ -0,0 +1,410 @@
+/*
+ * 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;
+
+import android.app.backup.BackupManager;
+import android.app.backup.SelectBackupTransportCallback;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.EventLog;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.backup.IBackupTransport;
+import com.android.server.EventLogTags;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Handles in-memory bookkeeping of all BackupTransport objects.
+ */
+class TransportManager {
+
+ private static final String TAG = "BackupTransportManager";
+
+ private static final String SERVICE_ACTION_TRANSPORT_HOST = "android.backup.TRANSPORT_HOST";
+
+ private final Intent mTransportServiceIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST);
+ private final Context mContext;
+ private final PackageManager mPackageManager;
+ private final Set<ComponentName> mTransportWhitelist;
+
+ /**
+ * This listener is called after we bind to any transport. If it returns true, this is a valid
+ * transport.
+ */
+ private final TransportBoundListener mTransportBoundListener;
+
+ private String mCurrentTransportName;
+
+ /** Lock on this before accessing mValidTransports and mBoundTransports. */
+ private final Object mTransportLock = new Object();
+
+ /**
+ * We have detected these transports on the device. Unless in exceptional cases, we are also
+ * bound to all of these.
+ */
+ @GuardedBy("mTransportLock")
+ private final Map<ComponentName, TransportConnection> mValidTransports = new ArrayMap<>();
+
+ /** We are currently bound to these transports. */
+ @GuardedBy("mTransportLock")
+ private final Map<String, ComponentName> mBoundTransports = new ArrayMap<>();
+
+ TransportManager(Context context, Set<ComponentName> whitelist, String defaultTransport,
+ TransportBoundListener listener) {
+ mContext = context;
+ mPackageManager = context.getPackageManager();
+ mTransportWhitelist = whitelist;
+ mCurrentTransportName = defaultTransport;
+ mTransportBoundListener = listener;
+ }
+
+ void onPackageAdded(String packageName) {
+ // New package added. Bind to all transports it contains.
+ synchronized (mTransportLock) {
+ log_verbose("Package added. Binding to all transports. " + packageName);
+ bindToAllInternal(packageName, null /* all components */);
+ }
+ }
+
+ void onPackageRemoved(String packageName) {
+ // Package removed. Remove all its transports from our list. These transports have already
+ // been removed from mBoundTransports because onServiceDisconnected would already been
+ // called on TransportConnection objects.
+ synchronized (mTransportLock) {
+ for (ComponentName transport : mValidTransports.keySet()) {
+ if (transport.getPackageName().equals(packageName)) {
+ TransportConnection removed = mValidTransports.remove(transport);
+ if (removed != null) {
+ mContext.unbindService(removed);
+ log_verbose("Package removed, Removing transport: " +
+ transport.flattenToShortString());
+ }
+ }
+ }
+ }
+ }
+
+ void onPackageChanged(String packageName, String[] components) {
+ synchronized (mTransportLock) {
+ // Remove all changed components from mValidTransports. We'll bind to them again
+ // and re-add them if still valid.
+ for (String component : components) {
+ ComponentName componentName = new ComponentName(packageName, component);
+ TransportConnection removed = mValidTransports.remove(componentName);
+ if (removed != null) {
+ mContext.unbindService(removed);
+ log_verbose("Package changed. Removing transport: " +
+ componentName.flattenToShortString());
+ }
+ }
+ bindToAllInternal(packageName, components);
+ }
+ }
+
+ IBackupTransport getTransportBinder(String transportName) {
+ synchronized (mTransportLock) {
+ ComponentName component = mBoundTransports.get(transportName);
+ if (component == null) {
+ Slog.w(TAG, "Transport " + transportName + " not bound.");
+ return null;
+ }
+ TransportConnection conn = mValidTransports.get(component);
+ if (conn == null) {
+ Slog.w(TAG, "Transport " + transportName + " not valid.");
+ return null;
+ }
+ return conn.getBinder();
+ }
+ }
+
+ IBackupTransport getCurrentTransportBinder() {
+ return getTransportBinder(mCurrentTransportName);
+ }
+
+ String getTransportName(IBackupTransport binder) {
+ synchronized (mTransportLock) {
+ for (TransportConnection conn : mValidTransports.values()) {
+ if (conn.getBinder() == binder) {
+ return conn.getName();
+ }
+ }
+ }
+ return null;
+ }
+
+ String[] getBoundTransportNames() {
+ synchronized (mTransportLock) {
+ return mBoundTransports.keySet().toArray(new String[0]);
+ }
+ }
+
+ ComponentName[] getAllTransportCompenents() {
+ synchronized (mTransportLock) {
+ return mValidTransports.keySet().toArray(new ComponentName[0]);
+ }
+ }
+
+ String getCurrentTransportName() {
+ return mCurrentTransportName;
+ }
+
+ Set<ComponentName> getTransportWhitelist() {
+ return mTransportWhitelist;
+ }
+
+ String selectTransport(String transport) {
+ synchronized (mTransportLock) {
+ String prevTransport = mCurrentTransportName;
+ mCurrentTransportName = transport;
+ return prevTransport;
+ }
+ }
+
+ void ensureTransportReady(ComponentName transportComponent, SelectBackupTransportCallback listener) {
+ synchronized (mTransportLock) {
+ TransportConnection conn = mValidTransports.get(transportComponent);
+ if (conn == null) {
+ listener.onFailure(BackupManager.ERROR_TRANSPORT_UNAVAILABLE);
+ return;
+ }
+ // Transport can be unbound if the process hosting it crashed.
+ conn.bindIfUnbound();
+ conn.addListener(listener);
+ }
+ }
+
+ void registerAllTransports() {
+ bindToAllInternal(null /* all packages */, null /* all components */);
+ }
+
+ /**
+ * Bind to all transports belonging to the given package and the given component list.
+ * null acts a wildcard.
+ *
+ * If packageName is null, bind to all transports in all packages.
+ * If components is null, bind to all transports in the given package.
+ */
+ private void bindToAllInternal(String packageName, String[] components) {
+ PackageInfo pkgInfo = null;
+ if (packageName != null) {
+ try {
+ pkgInfo = mPackageManager.getPackageInfo(packageName, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.w(TAG, "Package not found: " + packageName);
+ return;
+ }
+ }
+
+ Intent intent = new Intent(mTransportServiceIntent);
+ if (packageName != null) {
+ intent.setPackage(packageName);
+ }
+
+ List<ResolveInfo> hosts = mPackageManager.queryIntentServicesAsUser(
+ intent, 0, UserHandle.USER_SYSTEM);
+ if (hosts != null) {
+ for (ResolveInfo host : hosts) {
+ final ServiceInfo info = host.serviceInfo;
+ boolean shouldBind = false;
+ if (components != null && packageName != null) {
+ for (String component : components) {
+ ComponentName cn = new ComponentName(pkgInfo.packageName, component);
+ if (info.getComponentName().equals(cn)) {
+ shouldBind = true;
+ break;
+ }
+ }
+ } else {
+ shouldBind = true;
+ }
+ if (shouldBind && isTransportTrusted(info.getComponentName())) {
+ tryBindTransport(info);
+ }
+ }
+ }
+ }
+
+ /** Transport has to be whitelisted and privileged. */
+ private boolean isTransportTrusted(ComponentName transport) {
+ if (!mTransportWhitelist.contains(transport)) {
+ Slog.w(TAG, "BackupTransport " + transport.flattenToShortString() +
+ " not whitelisted.");
+ return false;
+ }
+ try {
+ PackageInfo packInfo = mPackageManager.getPackageInfo(transport.getPackageName(), 0);
+ if ((packInfo.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED)
+ == 0) {
+ Slog.w(TAG, "Transport package " + transport.getPackageName() + " not privileged");
+ return false;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ Slog.w(TAG, "Package not found.", e);
+ return false;
+ }
+ return true;
+ }
+
+ private void tryBindTransport(ServiceInfo transport) {
+ Slog.d(TAG, "Binding to transport: " + transport.getComponentName().flattenToShortString());
+ // TODO: b/22388012 (Multi user backup and restore)
+ TransportConnection connection = new TransportConnection(transport.getComponentName());
+ if (bindToTransport(transport.getComponentName(), connection)) {
+ synchronized (mTransportLock) {
+ mValidTransports.put(transport.getComponentName(), connection);
+ }
+ } else {
+ Slog.w(TAG, "Couldn't bind to transport " + transport.getComponentName());
+ }
+ }
+
+ private boolean bindToTransport(ComponentName componentName, ServiceConnection connection) {
+ Intent intent = new Intent(mTransportServiceIntent)
+ .setComponent(componentName);
+ return mContext.bindServiceAsUser(intent, connection, Context.BIND_AUTO_CREATE,
+ UserHandle.SYSTEM);
+ }
+
+ private class TransportConnection implements ServiceConnection {
+
+ // Hold mTransportsLock to access these fields so as to provide a consistent view of them.
+ private IBackupTransport mBinder;
+ private final List<SelectBackupTransportCallback> mListeners = new ArrayList<>();
+ private String mTransportName;
+
+ private final ComponentName mTransportComponent;
+
+ private TransportConnection(ComponentName transportComponent) {
+ mTransportComponent = transportComponent;
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName component, IBinder binder) {
+ synchronized (mTransportLock) {
+ mBinder = IBackupTransport.Stub.asInterface(binder);
+ boolean success = false;
+
+ EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE,
+ component.flattenToShortString(), 1);
+
+ try {
+ mTransportName = mBinder.name();
+ // BackupManager requests some fields from the transport. If they are
+ // invalid, throw away this transport.
+ success = mTransportBoundListener.onTransportBound(mBinder);
+ } catch (RemoteException e) {
+ success = false;
+ Slog.e(TAG, "Couldn't get transport name.", e);
+ } finally {
+ if (success) {
+ Slog.d(TAG, "Bound to transport: " + component.flattenToShortString());
+ mBoundTransports.put(mTransportName, component);
+ for (SelectBackupTransportCallback listener : mListeners) {
+ listener.onSuccess(mTransportName);
+ }
+ } else {
+ Slog.w(TAG, "Bound to transport " + component.flattenToShortString() +
+ " but it is invalid");
+ EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE,
+ component.flattenToShortString(), 0);
+ mContext.unbindService(this);
+ mValidTransports.remove(component);
+ mBinder = null;
+ for (SelectBackupTransportCallback listener : mListeners) {
+ listener.onFailure(BackupManager.ERROR_TRANSPORT_INVALID);
+ }
+ }
+ mListeners.clear();
+ }
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName component) {
+ synchronized (mTransportLock) {
+ mBinder = null;
+ mBoundTransports.remove(mTransportName);
+ }
+ EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE,
+ component.flattenToShortString(), 0);
+ Slog.w(TAG, "Disconnected from transport " + component.flattenToShortString());
+ }
+
+ private IBackupTransport getBinder() {
+ synchronized (mTransportLock) {
+ return mBinder;
+ }
+ }
+
+ private String getName() {
+ synchronized (mTransportLock) {
+ return mTransportName;
+ }
+ }
+
+ private void bindIfUnbound() {
+ synchronized (mTransportLock) {
+ if (mBinder == null) {
+ Slog.d(TAG,
+ "Rebinding to transport " + mTransportComponent.flattenToShortString());
+ bindToTransport(mTransportComponent, this);
+ }
+ }
+ }
+
+ private void addListener(SelectBackupTransportCallback listener) {
+ synchronized (mTransportLock) {
+ if (mBinder == null) {
+ // We are waiting for bind to complete. If mBinder is set to null after the bind
+ // is complete due to transport being invalid, we won't find 'this' connection
+ // object in mValidTransports list and this function can't be called.
+ mListeners.add(listener);
+ } else {
+ listener.onSuccess(mTransportName);
+ }
+ }
+ }
+ }
+
+ interface TransportBoundListener {
+ /** Should return true if this is a valid transport. */
+ boolean onTransportBound(IBackupTransport binder);
+ }
+
+ private static void log_verbose(String message) {
+ if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ Slog.v(TAG, message);
+ }
+ }
+}