diff options
-rw-r--r-- | Android.mk | 1 | ||||
-rw-r--r-- | api/system-current.txt | 11 | ||||
-rw-r--r-- | cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java | 71 | ||||
-rw-r--r-- | core/java/android/app/backup/BackupManager.java | 88 | ||||
-rw-r--r-- | core/java/android/app/backup/IBackupManager.aidl | 19 | ||||
-rw-r--r-- | core/java/android/app/backup/ISelectBackupTransportCallback.aidl | 41 | ||||
-rw-r--r-- | core/java/android/app/backup/SelectBackupTransportCallback.java | 44 | ||||
-rw-r--r-- | services/backup/java/com/android/server/backup/BackupManagerService.java | 548 | ||||
-rw-r--r-- | services/backup/java/com/android/server/backup/Trampoline.java | 17 | ||||
-rw-r--r-- | services/backup/java/com/android/server/backup/TransportManager.java | 410 |
10 files changed, 865 insertions, 385 deletions
diff --git a/Android.mk b/Android.mk index 21bd76b76a62..2ccdc6dee188 100644 --- a/Android.mk +++ b/Android.mk @@ -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 \ diff --git a/api/system-current.txt b/api/system-current.txt index 37fca366450e..144bce82fa8f 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -6768,15 +6768,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 @@ -6891,6 +6894,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); diff --git a/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java b/cmds/bmgr/src/com/android/commands/bmgr/Bmgr.java index 780db5e26c35..7e913913dfae 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 @@ 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"); diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java index 540683deeecb..f0abe33c4df3 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 @@ 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(); @@ -415,6 +438,34 @@ public class BackupManager { } /** + * 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 @@ 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); + } + }); + } + } } diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl index fe23c288c7ce..1657e2e98698 100644 --- a/core/java/android/app/backup/IBackupManager.aidl +++ b/core/java/android/app/backup/IBackupManager.aidl @@ -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. @@ -238,6 +242,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. * diff --git a/core/java/android/app/backup/ISelectBackupTransportCallback.aidl b/core/java/android/app/backup/ISelectBackupTransportCallback.aidl new file mode 100644 index 000000000000..5de7c5e1ed6a --- /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 000000000000..0c8a0dcb86ef --- /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 7e825860ead3..88c05b55116b 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.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; - } - } - - // 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(); + 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(); - 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,20 +9581,56 @@ 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; + 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, transport); - Slog.v(TAG, "selectBackupTransport() set " + mCurrentTransport - + " returning " + prevTransport); - return prevTransport; - } finally { - Binder.restoreCallingIdentity(oldId); + 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 @@ -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()); diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java index d677f5ee04a8..a1a2c95e1eac 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.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; @@ -275,6 +277,12 @@ public class Trampoline extends IBackupManager.Stub { } @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 @@ public class Trampoline extends IBackupManager.Stub { } @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 000000000000..93d5a1ea8880 --- /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); + } + } +} |