Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6aba9656 authored by Ruslan Tkhakokhov's avatar Ruslan Tkhakokhov
Browse files

Update BackupTransportCallback to use async AIDL

Bug: 202716271
Test: 1. Unit tests WIP
      2. atest CtsBackupTestCases CtsBackupHostTestCases
         GtsBackupTestCases GtsBackupHostTestCases
Change-Id: Ifa45f35a415ef3c5fbab03696456fd7085568a7b
parent 16a8f596
Loading
Loading
Loading
Loading
+195 −31
Original line number Diff line number Diff line
@@ -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);
            }
        }
    }
}
+91 −0
Original line number Diff line number Diff line
/*
 * 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;
    }
}
+73 −0
Original line number Diff line number Diff line
/*
 * 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);
    }
}