blob: daf3415229ea973a2b7f39e669801a917e664073 [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.backup.transport;
import android.annotation.Nullable;
import android.app.backup.BackupTransport;
import android.app.backup.IBackupManagerMonitor;
import android.app.backup.RestoreDescription;
import android.app.backup.RestoreSet;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Slog;
import com.android.internal.backup.IBackupTransport;
import com.android.internal.infra.AndroidFuture;
import com.android.server.backup.BackupAndRestoreFeatureFlags;
import java.util.ArrayDeque;
import java.util.HashSet;
import java.util.List;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* Client to {@link com.android.internal.backup.IBackupTransport}. Manages the call to the remote
* transport service and delivers the results.
*/
public class BackupTransportClient {
private static final String TAG = "BackupTransportClient";
private final IBackupTransport mTransportBinder;
private final TransportStatusCallbackPool mCallbackPool;
private final TransportFutures mTransportFutures;
BackupTransportClient(IBackupTransport transportBinder) {
mTransportBinder = transportBinder;
mCallbackPool = new TransportStatusCallbackPool();
mTransportFutures = new TransportFutures();
}
/**
* See {@link IBackupTransport#name()}.
*/
public String name() throws RemoteException {
AndroidFuture<String> resultFuture = mTransportFutures.newFuture();
mTransportBinder.name(resultFuture);
return getFutureResult(resultFuture);
}
/**
* See {@link IBackupTransport#configurationIntent()}
*/
public Intent configurationIntent() throws RemoteException {
AndroidFuture<Intent> resultFuture = mTransportFutures.newFuture();
mTransportBinder.configurationIntent(resultFuture);
return getFutureResult(resultFuture);
}
/**
* See {@link IBackupTransport#currentDestinationString()}
*/
public String currentDestinationString() throws RemoteException {
AndroidFuture<String> resultFuture = mTransportFutures.newFuture();
mTransportBinder.currentDestinationString(resultFuture);
return getFutureResult(resultFuture);
}
/**
* See {@link IBackupTransport#dataManagementIntent()}
*/
public Intent dataManagementIntent() throws RemoteException {
AndroidFuture<Intent> resultFuture = mTransportFutures.newFuture();
mTransportBinder.dataManagementIntent(resultFuture);
return getFutureResult(resultFuture);
}
/**
* See {@link IBackupTransport#dataManagementIntentLabel()}
*/
@Nullable
public CharSequence dataManagementIntentLabel() throws RemoteException {
AndroidFuture<CharSequence> resultFuture = mTransportFutures.newFuture();
mTransportBinder.dataManagementIntentLabel(resultFuture);
return getFutureResult(resultFuture);
}
/**
* See {@link IBackupTransport#transportDirName()}
*/
public String transportDirName() throws RemoteException {
AndroidFuture<String> resultFuture = mTransportFutures.newFuture();
mTransportBinder.transportDirName(resultFuture);
return getFutureResult(resultFuture);
}
/**
* See {@link IBackupTransport#initializeDevice()}
*/
public int initializeDevice() throws RemoteException {
TransportStatusCallback callback = mCallbackPool.acquire();
try {
mTransportBinder.initializeDevice(callback);
return callback.getOperationStatus();
} finally {
mCallbackPool.recycle(callback);
}
}
/**
* See {@link IBackupTransport#clearBackupData(PackageInfo)}
*/
public int clearBackupData(PackageInfo packageInfo) throws RemoteException {
TransportStatusCallback callback = mCallbackPool.acquire();
try {
mTransportBinder.clearBackupData(packageInfo, callback);
return callback.getOperationStatus();
} finally {
mCallbackPool.recycle(callback);
}
}
/**
* See {@link IBackupTransport#finishBackup()}
*/
public int finishBackup() throws RemoteException {
TransportStatusCallback callback = mCallbackPool.acquire();
try {
mTransportBinder.finishBackup(callback);
return callback.getOperationStatus();
} finally {
mCallbackPool.recycle(callback);
}
}
/**
* See {@link IBackupTransport#requestBackupTime()}
*/
public long requestBackupTime() throws RemoteException {
AndroidFuture<Long> resultFuture = mTransportFutures.newFuture();
mTransportBinder.requestBackupTime(resultFuture);
Long result = getFutureResult(resultFuture);
return result == null ? BackupTransport.TRANSPORT_ERROR : result;
}
/**
* See {@link IBackupTransport#performBackup(PackageInfo, ParcelFileDescriptor, int)}
*/
public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd, int flags)
throws RemoteException {
TransportStatusCallback callback = mCallbackPool.acquire();
try {
mTransportBinder.performBackup(packageInfo, inFd, flags, callback);
return callback.getOperationStatus();
} finally {
mCallbackPool.recycle(callback);
}
}
/**
* See {@link IBackupTransport#getAvailableRestoreSets()}
*/
public List<RestoreSet> getAvailableRestoreSets() throws RemoteException {
AndroidFuture<List<RestoreSet>> resultFuture = mTransportFutures.newFuture();
mTransportBinder.getAvailableRestoreSets(resultFuture);
List<RestoreSet> result = getFutureResult(resultFuture);
return result;
}
/**
* See {@link IBackupTransport#getCurrentRestoreSet()}
*/
public long getCurrentRestoreSet() throws RemoteException {
AndroidFuture<Long> resultFuture = mTransportFutures.newFuture();
mTransportBinder.getCurrentRestoreSet(resultFuture);
Long result = getFutureResult(resultFuture);
return result == null ? BackupTransport.TRANSPORT_ERROR : result;
}
/**
* See {@link IBackupTransport#startRestore(long, PackageInfo[])}
*/
public int startRestore(long token, PackageInfo[] packages) throws RemoteException {
TransportStatusCallback callback = mCallbackPool.acquire();
try {
mTransportBinder.startRestore(token, packages, callback);
return callback.getOperationStatus();
} finally {
mCallbackPool.recycle(callback);
}
}
/**
* See {@link IBackupTransport#nextRestorePackage()}
*/
public RestoreDescription nextRestorePackage() throws RemoteException {
AndroidFuture<RestoreDescription> resultFuture = mTransportFutures.newFuture();
mTransportBinder.nextRestorePackage(resultFuture);
return getFutureResult(resultFuture);
}
/**
* See {@link IBackupTransport#getRestoreData(ParcelFileDescriptor)}
*/
public int getRestoreData(ParcelFileDescriptor outFd) throws RemoteException {
TransportStatusCallback callback = mCallbackPool.acquire();
try {
mTransportBinder.getRestoreData(outFd, callback);
return callback.getOperationStatus();
} finally {
mCallbackPool.recycle(callback);
}
}
/**
* See {@link IBackupTransport#finishRestore()}
*/
public void finishRestore() throws RemoteException {
TransportStatusCallback callback = mCallbackPool.acquire();
try {
mTransportBinder.finishRestore(callback);
callback.getOperationStatus();
} finally {
mCallbackPool.recycle(callback);
}
}
/**
* See {@link IBackupTransport#requestFullBackupTime()}
*/
public long requestFullBackupTime() throws RemoteException {
AndroidFuture<Long> resultFuture = mTransportFutures.newFuture();
mTransportBinder.requestFullBackupTime(resultFuture);
Long result = getFutureResult(resultFuture);
return result == null ? BackupTransport.TRANSPORT_ERROR : result;
}
/**
* See {@link IBackupTransport#performFullBackup(PackageInfo, ParcelFileDescriptor, int)}
*/
public int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor socket,
int flags) throws RemoteException {
TransportStatusCallback callback = mCallbackPool.acquire();
try {
mTransportBinder.performFullBackup(targetPackage, socket, flags, callback);
return callback.getOperationStatus();
} finally {
mCallbackPool.recycle(callback);
}
}
/**
* See {@link IBackupTransport#checkFullBackupSize(long)}
*/
public int checkFullBackupSize(long size) throws RemoteException {
TransportStatusCallback callback = mCallbackPool.acquire();
try {
mTransportBinder.checkFullBackupSize(size, callback);
return callback.getOperationStatus();
} finally {
mCallbackPool.recycle(callback);
}
}
/**
* See {@link IBackupTransport#sendBackupData(int)}
*/
public int sendBackupData(int numBytes) throws RemoteException {
TransportStatusCallback callback = mCallbackPool.acquire();
mTransportBinder.sendBackupData(numBytes, callback);
try {
return callback.getOperationStatus();
} finally {
mCallbackPool.recycle(callback);
}
}
/**
* See {@link IBackupTransport#cancelFullBackup()}
*/
public void cancelFullBackup() throws RemoteException {
TransportStatusCallback callback = mCallbackPool.acquire();
try {
mTransportBinder.cancelFullBackup(callback);
callback.getOperationStatus();
} finally {
mCallbackPool.recycle(callback);
}
}
/**
* See {@link IBackupTransport#isAppEligibleForBackup(PackageInfo, boolean)}
*/
public boolean isAppEligibleForBackup(PackageInfo targetPackage, boolean isFullBackup)
throws RemoteException {
AndroidFuture<Boolean> resultFuture = mTransportFutures.newFuture();
mTransportBinder.isAppEligibleForBackup(targetPackage, isFullBackup, resultFuture);
Boolean result = getFutureResult(resultFuture);
return result != null && result;
}
/**
* See {@link IBackupTransport#getBackupQuota(String, boolean)}
*/
public long getBackupQuota(String packageName, boolean isFullBackup) throws RemoteException {
AndroidFuture<Long> resultFuture = mTransportFutures.newFuture();
mTransportBinder.getBackupQuota(packageName, isFullBackup, resultFuture);
Long result = getFutureResult(resultFuture);
return result == null ? BackupTransport.TRANSPORT_ERROR : result;
}
/**
* See {@link IBackupTransport#getNextFullRestoreDataChunk(ParcelFileDescriptor)}
*/
public int getNextFullRestoreDataChunk(ParcelFileDescriptor socket) throws RemoteException {
TransportStatusCallback callback = mCallbackPool.acquire();
try {
mTransportBinder.getNextFullRestoreDataChunk(socket, callback);
return callback.getOperationStatus();
} finally {
mCallbackPool.recycle(callback);
}
}
/**
* See {@link IBackupTransport#abortFullRestore()}
*/
public int abortFullRestore() throws RemoteException {
TransportStatusCallback callback = mCallbackPool.acquire();
try {
mTransportBinder.abortFullRestore(callback);
return callback.getOperationStatus();
} finally {
mCallbackPool.recycle(callback);
}
}
/**
* See {@link IBackupTransport#getTransportFlags()}
*/
public int getTransportFlags() throws RemoteException {
AndroidFuture<Integer> resultFuture = mTransportFutures.newFuture();
mTransportBinder.getTransportFlags(resultFuture);
Integer result = getFutureResult(resultFuture);
return result == null ? BackupTransport.TRANSPORT_ERROR : result;
}
/**
* See {@link IBackupTransport#getBackupManagerMonitor()}
*/
public IBackupManagerMonitor getBackupManagerMonitor() throws RemoteException {
AndroidFuture<IBackupManagerMonitor> resultFuture = mTransportFutures.newFuture();
mTransportBinder.getBackupManagerMonitor(resultFuture);
return IBackupManagerMonitor.Stub.asInterface((IBinder) getFutureResult(resultFuture));
}
/**
* Allows the {@link TransportConnection} to notify this client
* if the underlying transport has become unusable. If that happens
* we want to cancel all active futures or callbacks.
*/
void onBecomingUnusable() {
mCallbackPool.cancelActiveCallbacks();
mTransportFutures.cancelActiveFutures();
}
private <T> T getFutureResult(AndroidFuture<T> future) {
try {
return future.get(BackupAndRestoreFeatureFlags.getBackupTransportFutureTimeoutMillis(),
TimeUnit.MILLISECONDS);
} catch (InterruptedException | ExecutionException | TimeoutException
| CancellationException e) {
Slog.w(TAG, "Failed to get result from transport:", e);
return null;
} finally {
mTransportFutures.remove(future);
}
}
private static class TransportFutures {
private final Object mActiveFuturesLock = new Object();
private final Set<AndroidFuture<?>> mActiveFutures = new HashSet<>();
<T> AndroidFuture<T> newFuture() {
AndroidFuture<T> future = new AndroidFuture<>();
synchronized (mActiveFuturesLock) {
mActiveFutures.add(future);
}
return future;
}
<T> void remove(AndroidFuture<T> future) {
synchronized (mActiveFuturesLock) {
mActiveFutures.remove(future);
}
}
void cancelActiveFutures() {
synchronized (mActiveFuturesLock) {
for (AndroidFuture<?> future : mActiveFutures) {
try {
future.cancel(true);
} catch (CancellationException ignored) {
// This is expected, so ignore the exception.
}
}
mActiveFutures.clear();
}
}
}
private static class TransportStatusCallbackPool {
private static final int MAX_POOL_SIZE = 100;
private final Object mPoolLock = new Object();
private final Queue<TransportStatusCallback> mCallbackPool = new ArrayDeque<>();
private final Set<TransportStatusCallback> mActiveCallbacks = new HashSet<>();
TransportStatusCallback acquire() {
synchronized (mPoolLock) {
TransportStatusCallback callback = mCallbackPool.poll();
if (callback == null) {
callback = new TransportStatusCallback();
}
callback.reset();
mActiveCallbacks.add(callback);
return callback;
}
}
void recycle(TransportStatusCallback callback) {
synchronized (mPoolLock) {
mActiveCallbacks.remove(callback);
if (mCallbackPool.size() > MAX_POOL_SIZE) {
Slog.d(TAG, "TransportStatusCallback pool size exceeded");
return;
}
mCallbackPool.add(callback);
}
}
void cancelActiveCallbacks() {
synchronized (mPoolLock) {
for (TransportStatusCallback callback : mActiveCallbacks) {
try {
callback.onOperationCompleteWithStatus(BackupTransport.TRANSPORT_ERROR);
// This waits for status to propagate before the callback is reset.
callback.getOperationStatus();
} catch (RemoteException ex) {
// Nothing we can do.
}
if (mCallbackPool.size() < MAX_POOL_SIZE) {
mCallbackPool.add(callback);
}
}
mActiveCallbacks.clear();
}
}
}
}