Merge "API to select backup transport"

This commit is contained in:
Shreyas Basarge
2017-01-24 22:02:24 +00:00
committed by Android (Google) Code Review
10 changed files with 866 additions and 386 deletions

View File

@@ -107,6 +107,7 @@ LOCAL_SRC_FILES += \
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 \

View File

@@ -6787,15 +6787,18 @@ package android.app.backup {
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 @@ package android.app.backup {
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);

View File

@@ -18,20 +18,24 @@ package com.android.commands.bmgr;
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 @@ public final class Bmgr {
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 @@ public final class Bmgr {
} 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 @@ public final class Bmgr {
}
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 @@ public final class Bmgr {
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 @@ public final class Bmgr {
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");

View File

@@ -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 @@ public class BackupManager {
@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 @@ public class BackupManager {
}
/**
* 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();
@@ -414,6 +437,34 @@ public class BackupManager {
return null;
}
/**
* 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
@@ -598,4 +649,35 @@ public class BackupManager {
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);
}
});
}
}
}

View File

@@ -19,8 +19,10 @@ package android.app.backup;
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 @@ interface IBackupManager {
*/
String[] listAllTransports();
ComponentName[] listAllTransportComponents();
/**
* Retrieve the list of whitelisted transport components. Callers do </i>not</i> need
* any special permission.
@@ -237,6 +241,21 @@ interface IBackupManager {
*/
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.

View File

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

View File

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

View File

@@ -34,13 +34,15 @@ import android.app.backup.BackupProgress;
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.IPackageDeleteObserver;
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.ServiceManager;
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.SystemConfig;
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.HashMap;
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.IvParameterSpec;
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 @@ public class BackupManagerService {
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 @@ public class BackupManagerService {
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 @@ public class BackupManagerService {
{
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 @@ public class BackupManagerService {
// 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 @@ public class BackupManagerService {
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 @@ public class BackupManagerService {
}
}
// 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 @@ public class BackupManagerService {
// 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 @@ public class BackupManagerService {
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 @@ public class BackupManagerService {
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 @@ public class BackupManagerService {
}
}
// 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 @@ public class BackupManagerService {
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 @@ public class BackupManagerService {
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 @@ public class BackupManagerService {
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 @@ public class BackupManagerService {
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 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
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, " + got " + nRead + "; now wanting " + (size - soF
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 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
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 (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
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 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
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 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
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 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
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 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
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 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
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 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
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 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
}
// 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 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
boolean needPermission = true;
if (transport == null) {
transport = mCurrentTransport;
transport = mTransportManager.getCurrentTransportName();
if (packageName != null) {
PackageInfo app = null;
@@ -10127,7 +9925,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
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 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
ActiveRestoreSession(String packageName, String transport) {
mPackageName = packageName;
mRestoreTransport = getTransport(transport);
mRestoreTransport = mTransportManager.getTransportBinder(transport);
}
public void markTimedOut() {
@@ -10515,7 +10313,7 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
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 @@ if (MORE_DEBUG) Slog.v(TAG, " + got " + nRead + "; now wanting " + (size - soF
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());

View File

@@ -20,6 +20,8 @@ import android.app.backup.IBackupManager;
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;
@@ -274,6 +276,12 @@ public class Trampoline extends IBackupManager.Stub {
return (svc != null) ? svc.listAllTransports() : null;
}
@Override
public ComponentName[] listAllTransportComponents() throws RemoteException {
BackupManagerService svc = mService;
return (svc != null) ? svc.listAllTransportComponents() : null;
}
@Override
public String[] getTransportWhitelist() {
BackupManagerService svc = mService;
@@ -286,6 +294,15 @@ public class Trampoline extends IBackupManager.Stub {
return (svc != null) ? svc.selectBackupTransport(transport) : null;
}
@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;

View File

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