| /* |
| * 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.annotation.Nullable; |
| import android.annotation.UserIdInt; |
| import android.annotation.WorkerThread; |
| import android.app.backup.BackupManager; |
| import android.app.backup.BackupTransport; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.pm.ApplicationInfo; |
| import android.content.pm.PackageInfo; |
| import android.content.pm.PackageManager; |
| import android.content.pm.ResolveInfo; |
| import android.os.Bundle; |
| import android.os.RemoteException; |
| import android.util.ArrayMap; |
| import android.util.ArraySet; |
| import android.util.Slog; |
| |
| import com.android.internal.annotations.GuardedBy; |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.backup.IBackupTransport; |
| import com.android.internal.util.Preconditions; |
| import com.android.server.backup.transport.OnTransportRegisteredListener; |
| import com.android.server.backup.transport.TransportClient; |
| import com.android.server.backup.transport.TransportClientManager; |
| import com.android.server.backup.transport.TransportConnectionListener; |
| import com.android.server.backup.transport.TransportNotAvailableException; |
| import com.android.server.backup.transport.TransportNotRegisteredException; |
| import com.android.server.backup.transport.TransportStats; |
| |
| import java.io.PrintWriter; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.function.Consumer; |
| import java.util.function.Predicate; |
| |
| /** Handles in-memory bookkeeping of all BackupTransport objects. */ |
| public class TransportManager { |
| private static final String TAG = "BackupTransportManager"; |
| |
| @VisibleForTesting |
| public static final String SERVICE_ACTION_TRANSPORT_HOST = "android.backup.TRANSPORT_HOST"; |
| |
| private final Intent mTransportServiceIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST); |
| private final @UserIdInt int mUserId; |
| private final PackageManager mPackageManager; |
| private final Set<ComponentName> mTransportWhitelist; |
| private final TransportClientManager mTransportClientManager; |
| private final TransportStats mTransportStats; |
| private OnTransportRegisteredListener mOnTransportRegisteredListener = (c, n) -> {}; |
| |
| /** |
| * Lock for registered transports and currently selected transport. |
| * |
| * <p><b>Warning:</b> No calls to {@link IBackupTransport} or calls that result in transport |
| * code being executed such as {@link TransportClient#connect(String)}} and its variants should |
| * be made with this lock held, risk of deadlock. |
| */ |
| private final Object mTransportLock = new Object(); |
| |
| /** @see #getRegisteredTransportNames() */ |
| @GuardedBy("mTransportLock") |
| private final Map<ComponentName, TransportDescription> mRegisteredTransportsDescriptionMap = |
| new ArrayMap<>(); |
| |
| @GuardedBy("mTransportLock") |
| @Nullable |
| private volatile String mCurrentTransportName; |
| |
| TransportManager(@UserIdInt int userId, Context context, Set<ComponentName> whitelist, |
| String selectedTransport) { |
| mUserId = userId; |
| mPackageManager = context.getPackageManager(); |
| mTransportWhitelist = Preconditions.checkNotNull(whitelist); |
| mCurrentTransportName = selectedTransport; |
| mTransportStats = new TransportStats(); |
| mTransportClientManager = new TransportClientManager(mUserId, context, mTransportStats); |
| } |
| |
| @VisibleForTesting |
| TransportManager( |
| @UserIdInt int userId, |
| Context context, |
| Set<ComponentName> whitelist, |
| String selectedTransport, |
| TransportClientManager transportClientManager) { |
| mUserId = userId; |
| mPackageManager = context.getPackageManager(); |
| mTransportWhitelist = Preconditions.checkNotNull(whitelist); |
| mCurrentTransportName = selectedTransport; |
| mTransportStats = new TransportStats(); |
| mTransportClientManager = transportClientManager; |
| } |
| |
| /* Sets a listener to be called whenever a transport is registered. */ |
| public void setOnTransportRegisteredListener(OnTransportRegisteredListener listener) { |
| mOnTransportRegisteredListener = listener; |
| } |
| |
| @WorkerThread |
| void onPackageAdded(String packageName) { |
| registerTransportsFromPackage(packageName, transportComponent -> true); |
| } |
| |
| void onPackageRemoved(String packageName) { |
| synchronized (mTransportLock) { |
| mRegisteredTransportsDescriptionMap.keySet().removeIf(fromPackageFilter(packageName)); |
| } |
| } |
| |
| @WorkerThread |
| void onPackageChanged(String packageName, String... components) { |
| // Unfortunately this can't be atomic because we risk a deadlock if |
| // registerTransportsFromPackage() is put inside the synchronized block |
| Set<ComponentName> transportComponents = new ArraySet<>(components.length); |
| for (String componentName : components) { |
| transportComponents.add(new ComponentName(packageName, componentName)); |
| } |
| synchronized (mTransportLock) { |
| mRegisteredTransportsDescriptionMap.keySet().removeIf(transportComponents::contains); |
| } |
| registerTransportsFromPackage(packageName, transportComponents::contains); |
| } |
| |
| /** |
| * Returns the {@link ComponentName}s of the registered transports. |
| * |
| * <p>A *registered* transport is a transport that satisfies intent with action |
| * android.backup.TRANSPORT_HOST, returns true for {@link #isTransportTrusted(ComponentName)} |
| * and that we have successfully connected to once. |
| */ |
| ComponentName[] getRegisteredTransportComponents() { |
| synchronized (mTransportLock) { |
| return mRegisteredTransportsDescriptionMap |
| .keySet() |
| .toArray(new ComponentName[mRegisteredTransportsDescriptionMap.size()]); |
| } |
| } |
| |
| /** |
| * Returns the names of the registered transports. |
| * |
| * @see #getRegisteredTransportComponents() |
| */ |
| String[] getRegisteredTransportNames() { |
| synchronized (mTransportLock) { |
| String[] transportNames = new String[mRegisteredTransportsDescriptionMap.size()]; |
| int i = 0; |
| for (TransportDescription description : mRegisteredTransportsDescriptionMap.values()) { |
| transportNames[i] = description.name; |
| i++; |
| } |
| return transportNames; |
| } |
| } |
| |
| /** Returns a set with the whitelisted transports. */ |
| Set<ComponentName> getTransportWhitelist() { |
| return mTransportWhitelist; |
| } |
| |
| /** Returns the name of the selected transport or {@code null} if no transport selected. */ |
| @Nullable |
| public String getCurrentTransportName() { |
| return mCurrentTransportName; |
| } |
| |
| /** |
| * Returns the {@link ComponentName} of the host service of the selected transport or |
| * {@code null} if no transport selected. |
| * |
| * @throws TransportNotRegisteredException if the selected transport is not registered. |
| */ |
| @Nullable |
| public ComponentName getCurrentTransportComponent() |
| throws TransportNotRegisteredException { |
| synchronized (mTransportLock) { |
| if (mCurrentTransportName == null) { |
| return null; |
| } |
| return getRegisteredTransportComponentOrThrowLocked(mCurrentTransportName); |
| } |
| } |
| |
| /** |
| * Returns the transport name associated with {@code transportComponent}. |
| * |
| * @throws TransportNotRegisteredException if the transport is not registered. |
| */ |
| public String getTransportName(ComponentName transportComponent) |
| throws TransportNotRegisteredException { |
| synchronized (mTransportLock) { |
| return getRegisteredTransportDescriptionOrThrowLocked(transportComponent).name; |
| } |
| } |
| |
| /** |
| * Retrieves the transport dir name of {@code transportComponent}. |
| * |
| * @throws TransportNotRegisteredException if the transport is not registered. |
| */ |
| public String getTransportDirName(ComponentName transportComponent) |
| throws TransportNotRegisteredException { |
| synchronized (mTransportLock) { |
| return getRegisteredTransportDescriptionOrThrowLocked(transportComponent) |
| .transportDirName; |
| } |
| } |
| |
| /** |
| * Retrieves the transport dir name of {@code transportName}. |
| * |
| * @throws TransportNotRegisteredException if the transport is not registered. |
| */ |
| public String getTransportDirName(String transportName) throws TransportNotRegisteredException { |
| synchronized (mTransportLock) { |
| return getRegisteredTransportDescriptionOrThrowLocked(transportName).transportDirName; |
| } |
| } |
| |
| /** |
| * Retrieves the configuration intent of {@code transportName}. |
| * |
| * @throws TransportNotRegisteredException if the transport is not registered. |
| */ |
| @Nullable |
| public Intent getTransportConfigurationIntent(String transportName) |
| throws TransportNotRegisteredException { |
| synchronized (mTransportLock) { |
| return getRegisteredTransportDescriptionOrThrowLocked(transportName) |
| .configurationIntent; |
| } |
| } |
| |
| /** |
| * Retrieves the current destination string of {@code transportName}. |
| * |
| * @throws TransportNotRegisteredException if the transport is not registered. |
| */ |
| public String getTransportCurrentDestinationString(String transportName) |
| throws TransportNotRegisteredException { |
| synchronized (mTransportLock) { |
| return getRegisteredTransportDescriptionOrThrowLocked(transportName) |
| .currentDestinationString; |
| } |
| } |
| |
| /** |
| * Retrieves the data management intent of {@code transportName}. |
| * |
| * @throws TransportNotRegisteredException if the transport is not registered. |
| */ |
| @Nullable |
| public Intent getTransportDataManagementIntent(String transportName) |
| throws TransportNotRegisteredException { |
| synchronized (mTransportLock) { |
| return getRegisteredTransportDescriptionOrThrowLocked(transportName) |
| .dataManagementIntent; |
| } |
| } |
| |
| /** |
| * Retrieves the data management label of {@code transportName}. |
| * |
| * @throws TransportNotRegisteredException if the transport is not registered. |
| */ |
| @Nullable |
| public CharSequence getTransportDataManagementLabel(String transportName) |
| throws TransportNotRegisteredException { |
| synchronized (mTransportLock) { |
| return getRegisteredTransportDescriptionOrThrowLocked(transportName) |
| .dataManagementLabel; |
| } |
| } |
| |
| /* Returns true if the transport identified by {@code transportName} is registered. */ |
| public boolean isTransportRegistered(String transportName) { |
| synchronized (mTransportLock) { |
| return getRegisteredTransportEntryLocked(transportName) != null; |
| } |
| } |
| |
| /** |
| * Execute {@code transportConsumer} for each registered transport passing the transport name. |
| * This is called with an internal lock held, ensuring that the transport will remain registered |
| * while {@code transportConsumer} is being executed. Don't do heavy operations in {@code |
| * transportConsumer}. |
| * |
| * <p><b>Warning:</b> Do NOT make any calls to {@link IBackupTransport} or call any variants of |
| * {@link TransportClient#connect(String)} here, otherwise you risk deadlock. |
| */ |
| public void forEachRegisteredTransport(Consumer<String> transportConsumer) { |
| synchronized (mTransportLock) { |
| for (TransportDescription transportDescription : |
| mRegisteredTransportsDescriptionMap.values()) { |
| transportConsumer.accept(transportDescription.name); |
| } |
| } |
| } |
| |
| /** |
| * Updates given values for the transport already registered and identified with {@param |
| * transportComponent}. If the transport is not registered it will log and return. |
| */ |
| public void updateTransportAttributes( |
| ComponentName transportComponent, |
| String name, |
| @Nullable Intent configurationIntent, |
| String currentDestinationString, |
| @Nullable Intent dataManagementIntent, |
| @Nullable CharSequence dataManagementLabel) { |
| synchronized (mTransportLock) { |
| TransportDescription description = |
| mRegisteredTransportsDescriptionMap.get(transportComponent); |
| if (description == null) { |
| Slog.e(TAG, "Transport " + name + " not registered tried to change description"); |
| return; |
| } |
| description.name = name; |
| description.configurationIntent = configurationIntent; |
| description.currentDestinationString = currentDestinationString; |
| description.dataManagementIntent = dataManagementIntent; |
| description.dataManagementLabel = dataManagementLabel; |
| Slog.d(TAG, "Transport " + name + " updated its attributes"); |
| } |
| } |
| |
| @GuardedBy("mTransportLock") |
| private ComponentName getRegisteredTransportComponentOrThrowLocked(String transportName) |
| throws TransportNotRegisteredException { |
| ComponentName transportComponent = getRegisteredTransportComponentLocked(transportName); |
| if (transportComponent == null) { |
| throw new TransportNotRegisteredException(transportName); |
| } |
| return transportComponent; |
| } |
| |
| @GuardedBy("mTransportLock") |
| private TransportDescription getRegisteredTransportDescriptionOrThrowLocked( |
| ComponentName transportComponent) throws TransportNotRegisteredException { |
| TransportDescription description = |
| mRegisteredTransportsDescriptionMap.get(transportComponent); |
| if (description == null) { |
| throw new TransportNotRegisteredException(transportComponent); |
| } |
| return description; |
| } |
| |
| @GuardedBy("mTransportLock") |
| private TransportDescription getRegisteredTransportDescriptionOrThrowLocked( |
| String transportName) throws TransportNotRegisteredException { |
| TransportDescription description = getRegisteredTransportDescriptionLocked(transportName); |
| if (description == null) { |
| throw new TransportNotRegisteredException(transportName); |
| } |
| return description; |
| } |
| |
| @GuardedBy("mTransportLock") |
| @Nullable |
| private ComponentName getRegisteredTransportComponentLocked(String transportName) { |
| Map.Entry<ComponentName, TransportDescription> entry = |
| getRegisteredTransportEntryLocked(transportName); |
| return (entry == null) ? null : entry.getKey(); |
| } |
| |
| @GuardedBy("mTransportLock") |
| @Nullable |
| private TransportDescription getRegisteredTransportDescriptionLocked(String transportName) { |
| Map.Entry<ComponentName, TransportDescription> entry = |
| getRegisteredTransportEntryLocked(transportName); |
| return (entry == null) ? null : entry.getValue(); |
| } |
| |
| @GuardedBy("mTransportLock") |
| @Nullable |
| private Map.Entry<ComponentName, TransportDescription> getRegisteredTransportEntryLocked( |
| String transportName) { |
| for (Map.Entry<ComponentName, TransportDescription> entry : |
| mRegisteredTransportsDescriptionMap.entrySet()) { |
| TransportDescription description = entry.getValue(); |
| if (transportName.equals(description.name)) { |
| return entry; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Returns a {@link TransportClient} for {@code transportName} or {@code null} if not |
| * registered. |
| * |
| * @param transportName The name of the transport. |
| * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check |
| * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more |
| * details. |
| * @return A {@link TransportClient} or null if not registered. |
| */ |
| @Nullable |
| public TransportClient getTransportClient(String transportName, String caller) { |
| try { |
| return getTransportClientOrThrow(transportName, caller); |
| } catch (TransportNotRegisteredException e) { |
| Slog.w(TAG, "Transport " + transportName + " not registered"); |
| return null; |
| } |
| } |
| |
| /** |
| * Returns a {@link TransportClient} for {@code transportName} or throws if not registered. |
| * |
| * @param transportName The name of the transport. |
| * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check |
| * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more |
| * details. |
| * @return A {@link TransportClient}. |
| * @throws TransportNotRegisteredException if the transport is not registered. |
| */ |
| public TransportClient getTransportClientOrThrow(String transportName, String caller) |
| throws TransportNotRegisteredException { |
| synchronized (mTransportLock) { |
| ComponentName component = getRegisteredTransportComponentLocked(transportName); |
| if (component == null) { |
| throw new TransportNotRegisteredException(transportName); |
| } |
| return mTransportClientManager.getTransportClient(component, caller); |
| } |
| } |
| |
| /** |
| * Returns a {@link TransportClient} for the current transport or {@code null} if not |
| * registered. |
| * |
| * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check |
| * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more |
| * details. |
| * @return A {@link TransportClient} or null if not registered. |
| * @throws IllegalStateException if no transport is selected. |
| */ |
| @Nullable |
| public TransportClient getCurrentTransportClient(String caller) { |
| if (mCurrentTransportName == null) { |
| throw new IllegalStateException("No transport selected"); |
| } |
| synchronized (mTransportLock) { |
| return getTransportClient(mCurrentTransportName, caller); |
| } |
| } |
| |
| /** |
| * Returns a {@link TransportClient} for the current transport or throws if not registered. |
| * |
| * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check |
| * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more |
| * details. |
| * @return A {@link TransportClient}. |
| * @throws TransportNotRegisteredException if the transport is not registered. |
| * @throws IllegalStateException if no transport is selected. |
| */ |
| public TransportClient getCurrentTransportClientOrThrow(String caller) |
| throws TransportNotRegisteredException { |
| if (mCurrentTransportName == null) { |
| throw new IllegalStateException("No transport selected"); |
| } |
| synchronized (mTransportLock) { |
| return getTransportClientOrThrow(mCurrentTransportName, caller); |
| } |
| } |
| |
| /** |
| * Disposes of the {@link TransportClient}. |
| * |
| * @param transportClient The {@link TransportClient} to be disposed of. |
| * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check |
| * {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more |
| * details. |
| */ |
| public void disposeOfTransportClient(TransportClient transportClient, String caller) { |
| mTransportClientManager.disposeOfTransportClient(transportClient, caller); |
| } |
| |
| /** |
| * Sets {@code transportName} as selected transport and returns previously selected transport |
| * name. If there was no previous transport it returns null. |
| * |
| * <p>You should NOT call this method in new code. This won't make any checks against {@code |
| * transportName}, putting any operation at risk of a {@link TransportNotRegisteredException} or |
| * another error at the time it's being executed. |
| * |
| * <p>{@link Deprecated} as public, this method can be used as private. |
| */ |
| @Deprecated |
| @Nullable |
| String selectTransport(String transportName) { |
| synchronized (mTransportLock) { |
| String prevTransport = mCurrentTransportName; |
| mCurrentTransportName = transportName; |
| return prevTransport; |
| } |
| } |
| |
| /** |
| * Tries to register the transport if not registered. If successful also selects the transport. |
| * |
| * @param transportComponent Host of the transport. |
| * @return One of {@link BackupManager#SUCCESS}, {@link BackupManager#ERROR_TRANSPORT_INVALID} |
| * or {@link BackupManager#ERROR_TRANSPORT_UNAVAILABLE}. |
| */ |
| @WorkerThread |
| public int registerAndSelectTransport(ComponentName transportComponent) { |
| // If it's already registered we select and return |
| synchronized (mTransportLock) { |
| try { |
| selectTransport(getTransportName(transportComponent)); |
| return BackupManager.SUCCESS; |
| } catch (TransportNotRegisteredException e) { |
| // Fall through and release lock |
| } |
| } |
| |
| // We can't call registerTransport() with the transport lock held |
| int result = registerTransport(transportComponent); |
| if (result != BackupManager.SUCCESS) { |
| return result; |
| } |
| synchronized (mTransportLock) { |
| try { |
| selectTransport(getTransportName(transportComponent)); |
| return BackupManager.SUCCESS; |
| } catch (TransportNotRegisteredException e) { |
| Slog.wtf(TAG, "Transport got unregistered"); |
| return BackupManager.ERROR_TRANSPORT_UNAVAILABLE; |
| } |
| } |
| } |
| |
| @WorkerThread |
| public void registerTransports() { |
| registerTransportsForIntent(mTransportServiceIntent, transportComponent -> true); |
| } |
| |
| @WorkerThread |
| private void registerTransportsFromPackage( |
| String packageName, Predicate<ComponentName> transportComponentFilter) { |
| try { |
| mPackageManager.getPackageInfoAsUser(packageName, 0, mUserId); |
| } catch (PackageManager.NameNotFoundException e) { |
| Slog.e(TAG, "Trying to register transports from package not found " + packageName); |
| return; |
| } |
| |
| registerTransportsForIntent( |
| new Intent(mTransportServiceIntent).setPackage(packageName), |
| transportComponentFilter.and(fromPackageFilter(packageName))); |
| } |
| |
| @WorkerThread |
| private void registerTransportsForIntent( |
| Intent intent, Predicate<ComponentName> transportComponentFilter) { |
| List<ResolveInfo> hosts = |
| mPackageManager.queryIntentServicesAsUser(intent, 0, mUserId); |
| if (hosts == null) { |
| return; |
| } |
| for (ResolveInfo host : hosts) { |
| ComponentName transportComponent = host.serviceInfo.getComponentName(); |
| if (transportComponentFilter.test(transportComponent) |
| && isTransportTrusted(transportComponent)) { |
| registerTransport(transportComponent); |
| } |
| } |
| } |
| |
| /** 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.getPackageInfoAsUser(transport.getPackageName(), 0, mUserId); |
| 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; |
| } |
| |
| /** |
| * Tries to register transport represented by {@code transportComponent}. |
| * |
| * <p><b>Warning:</b> Don't call this with the transport lock held. |
| * |
| * @param transportComponent Host of the transport that we want to register. |
| * @return One of {@link BackupManager#SUCCESS}, {@link BackupManager#ERROR_TRANSPORT_INVALID} |
| * or {@link BackupManager#ERROR_TRANSPORT_UNAVAILABLE}. |
| */ |
| @WorkerThread |
| private int registerTransport(ComponentName transportComponent) { |
| checkCanUseTransport(); |
| |
| if (!isTransportTrusted(transportComponent)) { |
| return BackupManager.ERROR_TRANSPORT_INVALID; |
| } |
| |
| String transportString = transportComponent.flattenToShortString(); |
| String callerLogString = "TransportManager.registerTransport()"; |
| |
| Bundle extras = new Bundle(); |
| extras.putBoolean(BackupTransport.EXTRA_TRANSPORT_REGISTRATION, true); |
| |
| TransportClient transportClient = |
| mTransportClientManager.getTransportClient( |
| transportComponent, extras, callerLogString); |
| final IBackupTransport transport; |
| try { |
| transport = transportClient.connectOrThrow(callerLogString); |
| } catch (TransportNotAvailableException e) { |
| Slog.e(TAG, "Couldn't connect to transport " + transportString + " for registration"); |
| mTransportClientManager.disposeOfTransportClient(transportClient, callerLogString); |
| return BackupManager.ERROR_TRANSPORT_UNAVAILABLE; |
| } |
| |
| int result; |
| try { |
| String transportName = transport.name(); |
| String transportDirName = transport.transportDirName(); |
| registerTransport(transportComponent, transport); |
| // If registerTransport() hasn't thrown... |
| Slog.d(TAG, "Transport " + transportString + " registered"); |
| mOnTransportRegisteredListener.onTransportRegistered(transportName, transportDirName); |
| result = BackupManager.SUCCESS; |
| } catch (RemoteException e) { |
| Slog.e(TAG, "Transport " + transportString + " died while registering"); |
| result = BackupManager.ERROR_TRANSPORT_UNAVAILABLE; |
| } |
| |
| mTransportClientManager.disposeOfTransportClient(transportClient, callerLogString); |
| return result; |
| } |
| |
| /** If {@link RemoteException} is thrown the transport is guaranteed to not be registered. */ |
| private void registerTransport(ComponentName transportComponent, IBackupTransport transport) |
| throws RemoteException { |
| checkCanUseTransport(); |
| |
| TransportDescription description = |
| new TransportDescription( |
| transport.name(), |
| transport.transportDirName(), |
| transport.configurationIntent(), |
| transport.currentDestinationString(), |
| transport.dataManagementIntent(), |
| transport.dataManagementIntentLabel()); |
| synchronized (mTransportLock) { |
| mRegisteredTransportsDescriptionMap.put(transportComponent, description); |
| } |
| } |
| |
| private void checkCanUseTransport() { |
| Preconditions.checkState( |
| !Thread.holdsLock(mTransportLock), "Can't call transport with transport lock held"); |
| } |
| |
| public void dumpTransportClients(PrintWriter pw) { |
| mTransportClientManager.dump(pw); |
| } |
| |
| public void dumpTransportStats(PrintWriter pw) { |
| mTransportStats.dump(pw); |
| } |
| |
| private static Predicate<ComponentName> fromPackageFilter(String packageName) { |
| return transportComponent -> packageName.equals(transportComponent.getPackageName()); |
| } |
| |
| private static class TransportDescription { |
| private String name; |
| private final String transportDirName; |
| @Nullable private Intent configurationIntent; |
| private String currentDestinationString; |
| @Nullable private Intent dataManagementIntent; |
| @Nullable private CharSequence dataManagementLabel; |
| |
| private TransportDescription( |
| String name, |
| String transportDirName, |
| @Nullable Intent configurationIntent, |
| String currentDestinationString, |
| @Nullable Intent dataManagementIntent, |
| @Nullable CharSequence dataManagementLabel) { |
| this.name = name; |
| this.transportDirName = transportDirName; |
| this.configurationIntent = configurationIntent; |
| this.currentDestinationString = currentDestinationString; |
| this.dataManagementIntent = dataManagementIntent; |
| this.dataManagementLabel = dataManagementLabel; |
| } |
| } |
| } |