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

Commit 5a484dc4 authored by Bernardo Rufino's avatar Bernardo Rufino
Browse files

Binding on-demand #9: selectBackupTransport[Async]

Migrate selectBackupTransport()/selectBackupTransportAsync() to binding
on-demand. To mimic the bind-if-needed behavior that existed before for
selectBackupTransportAsync we now register-if-needed in the selection.
This means a new registerTransport() method was created in the
TransportManager. I intend to use this method with only few
modifications for the first-binding code.

Change-Id: I39661cff0f7b2f8a27da37905dcd93e0aa9b1178
Ref: http://go/br-binding-on-demand
Bug: 17140907
Test: m -j RunFrameworksServicesRoboTest
Test: adb shell bmgr transport <transport>, observe logs
Test: Robolectric tests for TransportManager will go in registration CL
parent 985cdcb8
Loading
Loading
Loading
Loading
+49 −44
Original line number Diff line number Diff line
@@ -3001,64 +3001,64 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
        }
    }

    // Select which transport to use for the next backup operation.
    /** Selects transport {@code transportName} and returns previous selected transport. */
    @Override
    public String selectBackupTransport(String transport) {
        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
                "selectBackupTransport");
    public String selectBackupTransport(String transportName) {
        mContext.enforceCallingOrSelfPermission(
                android.Manifest.permission.BACKUP, "selectBackupTransport");

        final long oldId = Binder.clearCallingIdentity();
        try {
            String prevTransport = mTransportManager.selectTransport(transport);
            updateStateForTransport(transport);
            Slog.v(TAG, "selectBackupTransport() set " + mTransportManager.getCurrentTransportName()
                    + " returning " + prevTransport);
            return prevTransport;
            String previousTransportName = mTransportManager.selectTransport(transportName);
            updateStateForTransport(transportName);
            Slog.v(TAG, "selectBackupTransport(transport = " + transportName
                    + "): previous transport = " + previousTransportName);
            return previousTransportName;
        } finally {
            Binder.restoreCallingIdentity(oldId);
        }
    }

    @Override
    public void selectBackupTransportAsync(final ComponentName transport,
            final ISelectBackupTransportCallback listener) {
        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
                "selectBackupTransportAsync");
    public void selectBackupTransportAsync(
            ComponentName transportComponent, 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 TransportManager.TransportReadyCallback() {
                    @Override
                    public void onSuccess(String transportName) {
                        mTransportManager.selectTransport(transportName);
                        updateStateForTransport(mTransportManager.getCurrentTransportName());
                        Slog.v(TAG, "Transport successfully selected: "
                                + transport.flattenToShortString());
        try {
                            listener.onSuccess(transportName);
                        } catch (RemoteException e) {
                            // Nothing to do here.
            String transportString = transportComponent.flattenToShortString();
            Slog.v(TAG, "selectBackupTransportAsync(transport = " + transportString + ")");
            mBackupHandler.post(
                    () -> {
                        String transportName = null;
                        int result =
                                mTransportManager.registerAndSelectTransport(transportComponent);
                        if (result == BackupManager.SUCCESS) {
                            try {
                                transportName =
                                        mTransportManager.getTransportName(transportComponent);
                                updateStateForTransport(transportName);
                            } catch (TransportNotRegisteredException e) {
                                Slog.e(TAG, "Transport got unregistered");
                                result = BackupManager.ERROR_TRANSPORT_UNAVAILABLE;
                            }
                        }

                    @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.
                            if (transportName != null) {
                                listener.onSuccess(transportName);
                            } else {
                                listener.onFailure(result);
                            }
                        } catch (RemoteException e) {
                            Slog.e(TAG, "ISelectBackupTransportCallback listener not available");
                        }
                    });

        } finally {
            Binder.restoreCallingIdentity(oldId);
        }
    }

    private void updateStateForTransport(String newTransportName) {
        // Publish the name change
@@ -3066,18 +3066,23 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
                Settings.Secure.BACKUP_TRANSPORT, newTransportName);

        // And update our current-dataset bookkeeping
        IBackupTransport transport = mTransportManager.getTransportBinder(newTransportName);
        if (transport != null) {
        String callerLogString = "BMS.updateStateForTransport()";
        TransportClient transportClient =
                mTransportManager.getTransportClient(newTransportName, callerLogString);
        if (transportClient != null) {
            try {
                IBackupTransport transport = transportClient.connectOrThrow(callerLogString);
                mCurrentToken = transport.getCurrentRestoreSet();
            } catch (Exception e) {
                // Oops.  We can't know the current dataset token, so reset and figure it out
                // when we do the next k/v backup operation on this transport.
                mCurrentToken = 0;
                Slog.w(TAG, "Transport " + newTransportName + " not available: current token = 0");
            }
        } else {
            // The named transport isn't bound at this particular moment, so we can't
            // know yet what its current dataset token is.  Reset as above.
            Slog.w(TAG, "Transport " + newTransportName + " not registered: current token = 0");
            // The named transport isn't registered, so we can't know what its current dataset token
            // is. Reset as above.
            mCurrentToken = 0;
        }
    }
+104 −87
Original line number Diff line number Diff line
@@ -112,23 +112,6 @@ public class TransportManager {
    @GuardedBy("mTransportLock")
    private volatile String mCurrentTransportName;


    /**
     * Callback interface for {@link #ensureTransportReady(ComponentName, TransportReadyCallback)}.
     */
    public interface TransportReadyCallback {

        /**
         * Will be called when the transport is ready.
         */
        void onSuccess(String transportName);

        /**
         * Will be called when it's not possible to make transport ready.
         */
        void onFailure(int reason);
    }

    TransportManager(
            Context context,
            Set<ComponentName> whitelist,
@@ -239,6 +222,17 @@ public class TransportManager {
        return getTransportBinder(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;
        }
    }

    /**
     * Retrieve the configuration intent of {@code transportName}.
     * @throws TransportNotRegisteredException if the transport is not registered.
@@ -340,23 +334,6 @@ public class TransportManager {
        return null;
    }

    /**
     * Returns the transport name associated with {@param transportComponent} or {@code null} if not
     * found.
     */
    @Nullable
    public String getTransportName(ComponentName transportComponent) {
        synchronized (mTransportLock) {
            TransportDescription description =
                    mRegisteredTransportsDescriptionMap.get(transportComponent);
            if (description == null) {
                Slog.e(TAG, "Trying to find name of unregistered transport " + transportComponent);
                return null;
            }
            return description.name;
        }
    }

    @GuardedBy("mTransportLock")
    @Nullable
    private ComponentName getRegisteredTransportComponentLocked(String transportName) {
@@ -552,29 +529,6 @@ public class TransportManager {
        return mCurrentTransportName;
    }

    String selectTransport(String transport) {
        synchronized (mTransportLock) {
            String prevTransport = mCurrentTransportName;
            mCurrentTransportName = transport;
            return prevTransport;
        }
    }

    void ensureTransportReady(ComponentName transportComponent,
            TransportReadyCallback 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);
        }
    }


    // This is for mocking, Mockito can't mock if package-protected and in the same package but
    // different class loaders. Checked with the debugger and class loaders are different
    // See https://github.com/mockito/mockito/issues/796
@@ -674,6 +628,90 @@ public class TransportManager {
                createSystemUserHandle());
    }

    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}.
     */
    public int registerAndSelectTransport(ComponentName transportComponent) {
        synchronized (mTransportLock) {
            if (!mRegisteredTransportsDescriptionMap.containsKey(transportComponent)) {
                int result = registerTransport(transportComponent);
                if (result != BackupManager.SUCCESS) {
                    return result;
                }
            }

            try {
                selectTransport(getTransportName(transportComponent));
                return BackupManager.SUCCESS;
            } catch (TransportNotRegisteredException e) {
                // Shouldn't happen because we are holding the lock
                Slog.wtf(TAG, "Transport unexpectedly not registered");
                return BackupManager.ERROR_TRANSPORT_UNAVAILABLE;
            }
        }
    }

    /**
     * Tries to register transport represented by {@code transportComponent}.
     *
     * @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}.
     */
    private int registerTransport(ComponentName transportComponent) {
        String transportString = transportComponent.flattenToShortString();

        String callerLogString = "TransportManager.registerTransport()";
        TransportClient transportClient =
                mTransportClientManager.getTransportClient(transportComponent, 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;
        }

        EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, transportString, 1);

        int result;
        if (isTransportValid(transport)) {
            try {
                registerTransport(transportComponent, transport);
                // If registerTransport() hasn't thrown...
                result = BackupManager.SUCCESS;
            } catch (RemoteException e) {
                Slog.e(TAG, "Transport " + transportString + " died while registering");
                result = BackupManager.ERROR_TRANSPORT_UNAVAILABLE;
            }
        } else {
            Slog.w(TAG, "Can't register invalid transport " + transportString);
            result = BackupManager.ERROR_TRANSPORT_INVALID;
        }

        mTransportClientManager.disposeOfTransportClient(transportClient, callerLogString);
        if (result == BackupManager.SUCCESS) {
            Slog.d(TAG, "Transport " + transportString + " registered");
        } else {
            EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, transportString, 0);
        }
        return result;
    }

    /** If {@link RemoteException} is thrown the transport is guaranteed to not be registered. */
    private void registerTransport(ComponentName transportComponent, IBackupTransport transport)
            throws RemoteException {
@@ -690,11 +728,18 @@ public class TransportManager {
        }
    }

    private boolean isTransportValid(IBackupTransport transport) {
        if (mTransportBoundListener == null) {
            Slog.w(TAG, "setTransportBoundListener() not called, assuming transport invalid");
            return false;
        }
        return mTransportBoundListener.onTransportBound(transport);
    }

    private class TransportConnection implements ServiceConnection {

        // Hold mTransportLock to access these fields so as to provide a consistent view of them.
        private volatile IBackupTransport mBinder;
        private final List<TransportReadyCallback> mListeners = new ArrayList<>();
        private volatile String mTransportName;

        private final ComponentName mTransportComponent;
@@ -716,15 +761,7 @@ public class TransportManager {
                    mTransportName = mBinder.name();
                    // BackupManager requests some fields from the transport. If they are
                    // invalid, throw away this transport.
                    final boolean valid;
                    if (mTransportBoundListener != null) {
                        valid = mTransportBoundListener.onTransportBound(mBinder);
                    } else {
                        Slog.w(TAG, "setTransportBoundListener() not called, assuming transport "
                                + component + " valid");
                        valid = true;
                    }
                    if (valid) {
                    if (isTransportValid(mBinder)) {
                        // We're now using the always-bound connection to do the registration but
                        // when we remove the always-bound code this will be in the first binding
                        // TODO: Move registration to first binding
@@ -742,9 +779,6 @@ public class TransportManager {
                    if (success) {
                        Slog.d(TAG, "Bound to transport: " + componentShortString);
                        mBoundTransports.put(mTransportName, component);
                        for (TransportReadyCallback listener : mListeners) {
                            listener.onSuccess(mTransportName);
                        }
                        // cancel rebinding on timeout for this component as we've already connected
                        mHandler.removeMessages(REBINDING_TIMEOUT_MSG, componentShortString);
                    } else {
@@ -756,11 +790,7 @@ public class TransportManager {
                        mValidTransports.remove(component);
                        mEligibleTransports.remove(component);
                        mBinder = null;
                        for (TransportReadyCallback listener : mListeners) {
                            listener.onFailure(BackupManager.ERROR_TRANSPORT_INVALID);
                        }
                    }
                    mListeners.clear();
                }
            }
        }
@@ -815,19 +845,6 @@ public class TransportManager {
            }
        }

        private void addListener(TransportReadyCallback 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);
                }
            }
        }

        private long getRebindTimeout() {
            final boolean isDeviceProvisioned = Settings.Global.getInt(
                    mContext.getContentResolver(),
+2 −9
Original line number Diff line number Diff line
@@ -560,16 +560,9 @@ public class PerformBackupTask implements BackupRestoreTask {
                }
                backupManagerService.addBackupTrace("init required; rerunning");
                try {
                    final String name = backupManagerService.getTransportManager()
                    String name = backupManagerService.getTransportManager()
                            .getTransportName(mTransportClient.getTransportComponent());
                    if (name != null) {
                    backupManagerService.getPendingInits().add(name);
                    } else {
                        if (DEBUG) {
                            Slog.w(TAG, "Couldn't find name of transport "
                                    + mTransportClient.getTransportComponent() + " for init");
                        }
                    }
                } catch (Exception e) {
                    Slog.w(TAG, "Failed to query transport name for init: " + e.getMessage());
                    // swallow it and proceed; we don't rely on this
+131 −5

File changed.

Preview size limit exceeded, changes collapsed.

+0 −58
Original line number Diff line number Diff line
@@ -24,7 +24,6 @@ import static org.robolectric.shadow.api.Shadow.extract;
import static org.testng.Assert.expectThrows;

import android.annotation.Nullable;
import android.app.backup.BackupManager;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -40,7 +39,6 @@ import com.android.server.backup.testing.ShadowBackupTransportStub;
import com.android.server.backup.testing.ShadowContextImplForBackup;
import com.android.server.backup.testing.ShadowPackageManagerForBackup;
import com.android.server.backup.testing.TransportBoundListenerStub;
import com.android.server.backup.testing.TransportReadyCallbackStub;
import com.android.server.backup.transport.TransportClient;
import com.android.server.backup.transport.TransportNotRegisteredException;
import com.android.server.testing.FrameworkRobolectricTestRunner;
@@ -87,9 +85,6 @@ public class TransportManagerTest {
    private final TransportBoundListenerStub mTransportBoundListenerStub =
            new TransportBoundListenerStub(true);

    private final TransportReadyCallbackStub mTransportReadyCallbackStub =
            new TransportReadyCallbackStub();

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
@@ -465,59 +460,6 @@ public class TransportManagerTest {
        assertThat(transportManager.selectTransport(mTransport1.name)).isEqualTo(mTransport2.name);
    }

    @Test
    public void ensureTransportReady_transportNotYetBound_callsListenerOnFailure()
            throws Exception {
        setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2),
                ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);

        TransportManager transportManager = new TransportManager(
                RuntimeEnvironment.application.getApplicationContext(),
                new HashSet<>(Arrays.asList(mTransport1.componentName, mTransport2.componentName)),
                mTransport1.name,
                mTransportBoundListenerStub,
                ShadowLooper.getMainLooper());

        transportManager.ensureTransportReady(mTransport1.componentName,
                mTransportReadyCallbackStub);

        assertThat(mTransportReadyCallbackStub.getSuccessCalls()).isEmpty();
        assertThat(mTransportReadyCallbackStub.getFailureCalls()).containsExactlyElementsIn(
                Collections.singleton(
                        BackupManager.ERROR_TRANSPORT_UNAVAILABLE));
    }

    @Test
    public void ensureTransportReady_transportCannotBeBound_callsListenerOnFailure()
            throws Exception {
        TransportManager transportManager =
                createTransportManagerAndSetUpTransports(Collections.singletonList(mTransport2),
                        Collections.singletonList(mTransport1), mTransport1.name);

        transportManager.ensureTransportReady(mTransport1.componentName,
                mTransportReadyCallbackStub);

        assertThat(mTransportReadyCallbackStub.getSuccessCalls()).isEmpty();
        assertThat(mTransportReadyCallbackStub.getFailureCalls()).containsExactlyElementsIn(
                Collections.singleton(
                        BackupManager.ERROR_TRANSPORT_UNAVAILABLE));
    }

    @Test
    public void ensureTransportReady_transportsAlreadyBound_callsListenerOnSuccess()
            throws Exception {
        TransportManager transportManager =
                createTransportManagerAndSetUpTransports(Collections.singletonList(mTransport2),
                        Collections.singletonList(mTransport1), mTransport1.name);

        transportManager.ensureTransportReady(mTransport2.componentName,
                mTransportReadyCallbackStub);

        assertThat(mTransportReadyCallbackStub.getSuccessCalls()).containsExactlyElementsIn(
                Collections.singleton(mTransport2.name));
        assertThat(mTransportReadyCallbackStub.getFailureCalls()).isEmpty();
    }

    @Test
    public void getTransportClient_forRegisteredTransport_returnCorrectly() throws Exception {
        TransportManager transportManager =
Loading