diff options
| author | 2021-12-15 11:22:08 +0000 | |
|---|---|---|
| committer | 2021-12-19 18:40:59 +0000 | |
| commit | 6aba9656ec6752d65b53aefad6bf4bb6ec63c4ef (patch) | |
| tree | 300ab81facb7117abff1e0e005dac51bfbccde51 | |
| parent | 16a8f596d53a970338e12925f405d2a16c19fb82 (diff) | |
Update BackupTransportCallback to use async AIDL
Bug: 202716271
Test: 1. Unit tests WIP
2. atest CtsBackupTestCases CtsBackupHostTestCases
GtsBackupTestCases GtsBackupHostTestCases
Change-Id: Ifa45f35a415ef3c5fbab03696456fd7085568a7b
3 files changed, 359 insertions, 31 deletions
diff --git a/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java b/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java index 85ab48c5f7fb..89633373b152 100644 --- a/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java +++ b/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java @@ -17,6 +17,7 @@ package com.android.server.backup.transport; import android.annotation.Nullable; +import android.app.backup.BackupTransport; import android.app.backup.RestoreDescription; import android.app.backup.RestoreSet; import android.content.Intent; @@ -24,50 +25,70 @@ import android.content.pm.PackageInfo; import android.os.Binder; import android.os.ParcelFileDescriptor; import android.os.RemoteException; +import android.util.Slog; +import com.android.internal.annotations.GuardedBy; import com.android.internal.backup.IBackupTransport; +import com.android.internal.infra.AndroidFuture; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.List; +import java.util.Queue; +import java.util.concurrent.ArrayBlockingQueue; +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; BackupTransportClient(IBackupTransport transportBinder) { mTransportBinder = transportBinder; - - // This is a temporary fix to allow blocking calls. - // TODO: b/147702043. Redesign IBackupTransport so as to make the calls non-blocking. - Binder.allowBlocking(mTransportBinder.asBinder()); + mCallbackPool = new TransportStatusCallbackPool(); } /** * See {@link IBackupTransport#name()}. */ public String name() throws RemoteException { - return mTransportBinder.name(); + AndroidFuture<String> resultFuture = new AndroidFuture<>(); + mTransportBinder.name(resultFuture); + return getFutureResult(resultFuture); } /** * See {@link IBackupTransport#configurationIntent()} */ public Intent configurationIntent() throws RemoteException { - return mTransportBinder.configurationIntent(); + AndroidFuture<Intent> resultFuture = new AndroidFuture<>(); + mTransportBinder.configurationIntent(resultFuture); + return getFutureResult(resultFuture); } /** * See {@link IBackupTransport#currentDestinationString()} */ public String currentDestinationString() throws RemoteException { - return mTransportBinder.currentDestinationString(); + AndroidFuture<String> resultFuture = new AndroidFuture<>(); + mTransportBinder.currentDestinationString(resultFuture); + return getFutureResult(resultFuture); } /** * See {@link IBackupTransport#dataManagementIntent()} */ public Intent dataManagementIntent() throws RemoteException { - return mTransportBinder.dataManagementIntent(); + AndroidFuture<Intent> resultFuture = new AndroidFuture<>(); + mTransportBinder.dataManagementIntent(resultFuture); + return getFutureResult(resultFuture); } /** @@ -75,42 +96,67 @@ public class BackupTransportClient { */ @Nullable public CharSequence dataManagementIntentLabel() throws RemoteException { - return mTransportBinder.dataManagementIntentLabel(); + AndroidFuture<CharSequence> resultFuture = new AndroidFuture<>(); + mTransportBinder.dataManagementIntentLabel(resultFuture); + return getFutureResult(resultFuture); } /** * See {@link IBackupTransport#transportDirName()} */ public String transportDirName() throws RemoteException { - return mTransportBinder.transportDirName(); + AndroidFuture<String> resultFuture = new AndroidFuture<>(); + mTransportBinder.transportDirName(resultFuture); + return getFutureResult(resultFuture); } /** * See {@link IBackupTransport#initializeDevice()} */ public int initializeDevice() throws RemoteException { - return mTransportBinder.initializeDevice(); + 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 { - return mTransportBinder.clearBackupData(packageInfo); + 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 { - return mTransportBinder.finishBackup(); + TransportStatusCallback callback = mCallbackPool.acquire(); + try { + mTransportBinder.finishBackup(callback); + return callback.getOperationStatus(); + } finally { + mCallbackPool.recycle(callback); + } } /** * See {@link IBackupTransport#requestBackupTime()} */ public long requestBackupTime() throws RemoteException { - return mTransportBinder.requestBackupTime(); + AndroidFuture<Long> resultFuture = new AndroidFuture<>(); + mTransportBinder.requestBackupTime(resultFuture); + Long result = getFutureResult(resultFuture); + return result == null ? BackupTransport.TRANSPORT_ERROR : result; } /** @@ -118,56 +164,91 @@ public class BackupTransportClient { */ public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd, int flags) throws RemoteException { - return mTransportBinder.performBackup(packageInfo, inFd, flags); + TransportStatusCallback callback = mCallbackPool.acquire(); + try { + mTransportBinder.performBackup(packageInfo, inFd, flags, callback); + return callback.getOperationStatus(); + } finally { + mCallbackPool.recycle(callback); + } } /** * See {@link IBackupTransport#getAvailableRestoreSets()} */ public RestoreSet[] getAvailableRestoreSets() throws RemoteException { - return mTransportBinder.getAvailableRestoreSets(); + AndroidFuture<List<RestoreSet>> resultFuture = new AndroidFuture<>(); + mTransportBinder.getAvailableRestoreSets(resultFuture); + List<RestoreSet> result = getFutureResult(resultFuture); + return result == null ? null : result.toArray(new RestoreSet[] {}); } /** * See {@link IBackupTransport#getCurrentRestoreSet()} */ public long getCurrentRestoreSet() throws RemoteException { - return mTransportBinder.getCurrentRestoreSet(); + AndroidFuture<Long> resultFuture = new AndroidFuture<>(); + 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 { - return mTransportBinder.startRestore(token, packages); + 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 { - return mTransportBinder.nextRestorePackage(); + AndroidFuture<RestoreDescription> resultFuture = new AndroidFuture<>(); + mTransportBinder.nextRestorePackage(resultFuture); + return getFutureResult(resultFuture); } /** * See {@link IBackupTransport#getRestoreData(ParcelFileDescriptor)} */ public int getRestoreData(ParcelFileDescriptor outFd) throws RemoteException { - return mTransportBinder.getRestoreData(outFd); + 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 { - mTransportBinder.finishRestore(); + TransportStatusCallback callback = mCallbackPool.acquire(); + try { + mTransportBinder.finishRestore(callback); + callback.getOperationStatus(); + } finally { + mCallbackPool.recycle(callback); + } } /** * See {@link IBackupTransport#requestFullBackupTime()} */ public long requestFullBackupTime() throws RemoteException { - return mTransportBinder.requestFullBackupTime(); + AndroidFuture<Long> resultFuture = new AndroidFuture<>(); + mTransportBinder.requestFullBackupTime(resultFuture); + Long result = getFutureResult(resultFuture); + return result == null ? BackupTransport.TRANSPORT_ERROR : result; } /** @@ -175,28 +256,52 @@ public class BackupTransportClient { */ public int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor socket, int flags) throws RemoteException { - return mTransportBinder.performFullBackup(targetPackage, socket, flags); + 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 { - return mTransportBinder.checkFullBackupSize(size); + 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 { - return mTransportBinder.sendBackupData(numBytes); + 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 { - mTransportBinder.cancelFullBackup(); + TransportStatusCallback callback = mCallbackPool.acquire(); + try { + mTransportBinder.cancelFullBackup(callback); + callback.getOperationStatus(); + } finally { + mCallbackPool.recycle(callback); + } } /** @@ -204,34 +309,93 @@ public class BackupTransportClient { */ public boolean isAppEligibleForBackup(PackageInfo targetPackage, boolean isFullBackup) throws RemoteException { - return mTransportBinder.isAppEligibleForBackup(targetPackage, isFullBackup); + AndroidFuture<Boolean> resultFuture = new AndroidFuture<>(); + 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 { - return mTransportBinder.getBackupQuota(packageName, isFullBackup); + AndroidFuture<Long> resultFuture = new AndroidFuture<>(); + 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 { - return mTransportBinder.getNextFullRestoreDataChunk(socket); + 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 { - return mTransportBinder.abortFullRestore(); + TransportStatusCallback callback = mCallbackPool.acquire(); + try { + mTransportBinder.abortFullRestore(callback); + return callback.getOperationStatus(); + } finally { + mCallbackPool.recycle(callback); + } } /** * See {@link IBackupTransport#getTransportFlags()} */ public int getTransportFlags() throws RemoteException { - return mTransportBinder.getTransportFlags(); + AndroidFuture<Integer> resultFuture = new AndroidFuture<>(); + mTransportBinder.getTransportFlags(resultFuture); + Integer result = getFutureResult(resultFuture); + return result == null ? BackupTransport.TRANSPORT_ERROR : result; + } + + private <T> T getFutureResult(AndroidFuture<T> future) { + try { + return future.get(600, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + Slog.w(TAG, "Failed to get result from transport:", e); + return null; + } + } + + 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<>(); + + TransportStatusCallback acquire() { + synchronized (mPoolLock) { + if (mCallbackPool.isEmpty()) { + return new TransportStatusCallback(); + } else { + return mCallbackPool.poll(); + } + } + } + + void recycle(TransportStatusCallback callback) { + synchronized (mPoolLock) { + if (mCallbackPool.size() > MAX_POOL_SIZE) { + Slog.d(TAG, "TransportStatusCallback pool size exceeded"); + return; + } + + callback.reset(); + mCallbackPool.add(callback); + } + } } } diff --git a/services/backup/backuplib/java/com/android/server/backup/transport/TransportStatusCallback.java b/services/backup/backuplib/java/com/android/server/backup/transport/TransportStatusCallback.java new file mode 100644 index 000000000000..a55178c27eef --- /dev/null +++ b/services/backup/backuplib/java/com/android/server/backup/transport/TransportStatusCallback.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2021 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.app.backup.BackupTransport; +import android.os.RemoteException; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.backup.ITransportStatusCallback; + +public class TransportStatusCallback extends ITransportStatusCallback.Stub { + private static final String TAG = "TransportStatusCallback"; + private static final int TIMEOUT_MILLIS = 600 * 1000; // 10 minutes. + private static final int OPERATION_STATUS_DEFAULT = 0; + + private final int mOperationTimeout; + + @GuardedBy("this") + private int mOperationStatus = OPERATION_STATUS_DEFAULT; + @GuardedBy("this") + private boolean mHasCompletedOperation = false; + + public TransportStatusCallback() { + mOperationTimeout = TIMEOUT_MILLIS; + } + + @VisibleForTesting + TransportStatusCallback(int operationTimeout) { + mOperationTimeout = operationTimeout; + } + + @Override + public synchronized void onOperationCompleteWithStatus(int status) throws RemoteException { + mHasCompletedOperation = true; + mOperationStatus = status; + + notifyAll(); + } + + @Override + public synchronized void onOperationComplete() throws RemoteException { + onOperationCompleteWithStatus(OPERATION_STATUS_DEFAULT); + } + + synchronized int getOperationStatus() { + if (mHasCompletedOperation) { + return mOperationStatus; + } + + long timeoutLeft = mOperationTimeout; + try { + while (!mHasCompletedOperation && timeoutLeft > 0) { + long waitStartTime = System.currentTimeMillis(); + wait(timeoutLeft); + if (mHasCompletedOperation) { + return mOperationStatus; + } + timeoutLeft -= System.currentTimeMillis() - waitStartTime; + } + + Slog.w(TAG, "Couldn't get operation status from transport"); + return BackupTransport.TRANSPORT_ERROR; + } catch (InterruptedException e) { + Slog.w(TAG, "Couldn't get operation status from transport: ", e); + return BackupTransport.TRANSPORT_ERROR; + } finally { + reset(); + } + } + + synchronized void reset() { + mHasCompletedOperation = false; + mOperationStatus = OPERATION_STATUS_DEFAULT; + } +} diff --git a/services/tests/servicestests/src/com/android/server/backup/transport/TransportStatusCallbackTest.java b/services/tests/servicestests/src/com/android/server/backup/transport/TransportStatusCallbackTest.java new file mode 100644 index 000000000000..7f7901f893a3 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/backup/transport/TransportStatusCallbackTest.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2021 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 static com.google.common.truth.Truth.assertThat; + +import android.app.backup.BackupTransport; +import android.platform.test.annotations.Presubmit; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@Presubmit +@RunWith(AndroidJUnit4.class) +public class TransportStatusCallbackTest { + private static final int OPERATION_TIMEOUT_MILLIS = 10; + private static final int OPERATION_COMPLETE_STATUS = 123; + + private TransportStatusCallback mTransportStatusCallback; + + @Before + public void setUp() { + mTransportStatusCallback = new TransportStatusCallback(); + } + + @Test + public void testGetOperationStatus_withPreCompletedOperation_returnsStatus() throws Exception { + mTransportStatusCallback.onOperationCompleteWithStatus(OPERATION_COMPLETE_STATUS); + + int result = mTransportStatusCallback.getOperationStatus(); + + assertThat(result).isEqualTo(OPERATION_COMPLETE_STATUS); + } + + @Test + public void testGetOperationStatus_completeOperation_returnsStatus() throws Exception { + Thread thread = new Thread(() -> { + int result = mTransportStatusCallback.getOperationStatus(); + assertThat(result).isEqualTo(OPERATION_COMPLETE_STATUS); + }); + thread.start(); + + mTransportStatusCallback.onOperationCompleteWithStatus(OPERATION_COMPLETE_STATUS); + + thread.join(); + } + + @Test + public void testGetOperationStatus_operationTimesOut_returnsError() throws Exception { + TransportStatusCallback callback = new TransportStatusCallback(OPERATION_TIMEOUT_MILLIS); + + int result = callback.getOperationStatus(); + + assertThat(result).isEqualTo(BackupTransport.TRANSPORT_ERROR); + } +} |