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

Commit 7ca7a14c authored by Evan Chen's avatar Evan Chen
Browse files

Implment CDM rebinding when binderDied

1. BT devices always rebind
2. SelfManaged devices:
 a) primary service binderDied: no rebind, application recall
 `notifyAppear`
 b) secondary service binderDied: rebind in 10s
 c) both primary and secondary services binderDied : no rebind

Test: atest CtsCompanionDeviceManagerCoreTestCases
      atest CtsCompanionDeviceManagerUiAutomationTestCases
      atest CtsOsTestCases:CompanionDeviceManagerTest
Bug: 218613015
Change-Id: I90f8cf0f8fa71aeb2186ff7b71797771d640611a
parent a39cb4ae
Loading
Loading
Loading
Loading
+118 −42
Original line number Diff line number Diff line
@@ -31,9 +31,10 @@ import android.util.SparseArray;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.infra.PerUser;
import com.android.internal.util.CollectionUtils;
import com.android.server.companion.presence.CompanionDevicePresenceMonitor;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -69,32 +70,22 @@ public class CompanionApplicationController {

    private static final long REBIND_TIMEOUT = 10 * 1000; // 10 sec

    interface Callback {
        /**
         * @return {@code true} if should schedule rebinding.
         *         {@code false} if we do not need to rebind.
         */
        boolean onCompanionApplicationBindingDied(
                @UserIdInt int userId, @NonNull String packageName);

        /**
         * Callback after timeout for previously scheduled rebind has passed.
         */
        void onRebindCompanionApplicationTimeout(
                @UserIdInt int userId, @NonNull String packageName);
    }

    private final @NonNull Context mContext;
    private final @NonNull Callback mCallback;
    private final @NonNull AssociationStore mAssociationStore;
    private final @NonNull CompanionDevicePresenceMonitor mDevicePresenceMonitor;
    private final @NonNull CompanionServicesRegister mCompanionServicesRegister;

    @GuardedBy("mBoundCompanionApplications")
    private final @NonNull AndroidPackageMap<List<CompanionDeviceServiceConnector>>
            mBoundCompanionApplications;
    @GuardedBy("mScheduledForRebindingCompanionApplications")
    private final @NonNull AndroidPackageMap<Boolean> mScheduledForRebindingCompanionApplications;

    CompanionApplicationController(Context context, Callback callback) {
    CompanionApplicationController(Context context, AssociationStore associationStore,
            CompanionDevicePresenceMonitor companionDevicePresenceMonitor) {
        mContext = context;
        mCallback = callback;
        mAssociationStore = associationStore;
        mDevicePresenceMonitor = companionDevicePresenceMonitor;
        mCompanionServicesRegister = new CompanionServicesRegister();
        mBoundCompanionApplications = new AndroidPackageMap<>();
        mScheduledForRebindingCompanionApplications = new AndroidPackageMap<>();
@@ -125,21 +116,26 @@ public class CompanionApplicationController {
            return;
        }

        final List<CompanionDeviceServiceConnector> serviceConnectors;
        final List<CompanionDeviceServiceConnector> serviceConnectors = new ArrayList<>();
        synchronized (mBoundCompanionApplications) {
            if (mBoundCompanionApplications.containsValueForPackage(userId, packageName)) {
                if (DEBUG) Log.e(TAG, "u" + userId + "/" + packageName + " is ALREADY bound.");
                return;
            }

            serviceConnectors = CollectionUtils.map(companionServices, componentName ->
                            CompanionDeviceServiceConnector.newInstance(mContext, userId,
                                    componentName, isSelfManaged));
            for (int i = 0; i < companionServices.size(); i++) {
                boolean isPrimary = i == 0;
                serviceConnectors.add(CompanionDeviceServiceConnector.newInstance(mContext, userId,
                        companionServices.get(i), isSelfManaged, isPrimary));
            }

            mBoundCompanionApplications.setValueForPackage(userId, packageName, serviceConnectors);
        }

        // The first connector in the list is always the primary connector: set a listener to it.
        serviceConnectors.get(0).setListener(this::onPrimaryServiceBindingDied);
        // Set listeners for both Primary and Secondary connectors.
        for (CompanionDeviceServiceConnector serviceConnector : serviceConnectors) {
            serviceConnector.setListener(this::onBinderDied);
        }

        // Now "bind" all the connectors: the primary one and the rest of them.
        for (CompanionDeviceServiceConnector serviceConnector : serviceConnectors) {
@@ -154,9 +150,15 @@ public class CompanionApplicationController {
        if (DEBUG) Log.i(TAG, "unbind() u" + userId + "/" + packageName);

        final List<CompanionDeviceServiceConnector> serviceConnectors;

        synchronized (mBoundCompanionApplications) {
            serviceConnectors = mBoundCompanionApplications.removePackage(userId, packageName);
        }

        synchronized (mScheduledForRebindingCompanionApplications) {
            mScheduledForRebindingCompanionApplications.removePackage(userId, packageName);
        }

        if (serviceConnectors == null) {
            if (DEBUG) {
                Log.e(TAG, "unbindCompanionApplication(): "
@@ -180,24 +182,59 @@ public class CompanionApplicationController {
        }
    }

    private void scheduleRebinding(@UserIdInt int userId, @NonNull String packageName) {
        mScheduledForRebindingCompanionApplications.setValueForPackage(userId, packageName, true);
    private void scheduleRebinding(@UserIdInt int userId, @NonNull String packageName,
            CompanionDeviceServiceConnector serviceConnector) {
        Slog.i(TAG, "scheduleRebinding() " + userId + "/" + packageName);

        if (isRebindingCompanionApplicationScheduled(userId, packageName)) {
            if (DEBUG) {
                Log.i(TAG, "CompanionApplication rebinding has been scheduled, skipping "
                        + serviceConnector.getComponentName());
            }
            return;
        }

        if (serviceConnector.isPrimary()) {
            synchronized (mScheduledForRebindingCompanionApplications) {
                mScheduledForRebindingCompanionApplications.setValueForPackage(
                        userId, packageName, true);
            }
        }

        // Rebinding in 10 seconds.
        Handler.getMain().postDelayed(() ->
                onRebindingCompanionApplicationTimeout(userId, packageName), REBIND_TIMEOUT);
                onRebindingCompanionApplicationTimeout(userId, packageName, serviceConnector),
                REBIND_TIMEOUT);
    }

    boolean isRebindingCompanionApplicationScheduled(
    private boolean isRebindingCompanionApplicationScheduled(
            @UserIdInt int userId, @NonNull String packageName) {
        return mScheduledForRebindingCompanionApplications
                .containsValueForPackage(userId, packageName);
        synchronized (mScheduledForRebindingCompanionApplications) {
            return mScheduledForRebindingCompanionApplications.containsValueForPackage(
                    userId, packageName);
        }
    }

    private void onRebindingCompanionApplicationTimeout(
            @UserIdInt int userId, @NonNull String packageName) {
            @UserIdInt int userId, @NonNull String packageName,
            @NonNull CompanionDeviceServiceConnector serviceConnector) {
        // Re-mark the application is bound.
        if (serviceConnector.isPrimary()) {
            synchronized (mBoundCompanionApplications) {
                if (!mBoundCompanionApplications.containsValueForPackage(userId, packageName)) {
                    List<CompanionDeviceServiceConnector> serviceConnectors =
                            Collections.singletonList(serviceConnector);
                    mBoundCompanionApplications.setValueForPackage(userId, packageName,
                            serviceConnectors);
                }
            }

            synchronized (mScheduledForRebindingCompanionApplications) {
                mScheduledForRebindingCompanionApplications.removePackage(userId, packageName);
            }
        }

        mCallback.onRebindCompanionApplicationTimeout(userId, packageName);
        serviceConnector.connect();
    }

    void notifyCompanionApplicationDeviceAppeared(AssociationInfo association) {
@@ -266,19 +303,27 @@ public class CompanionApplicationController {
        }
    }

    private void onPrimaryServiceBindingDied(@UserIdInt int userId, @NonNull String packageName) {
        if (DEBUG) Log.i(TAG, "onPrimaryServiceBindingDied() u" + userId + "/" + packageName);
    /**
     * Rebinding for Self-Managed secondary services OR Non-Self-Managed services.
     */
    private void onBinderDied(@UserIdInt int userId, @NonNull String packageName,
            @NonNull CompanionDeviceServiceConnector serviceConnector) {

        boolean isPrimary = serviceConnector.isPrimary();
        Slog.i(TAG, "onBinderDied() u" + userId + "/" + packageName + " isPrimary: " + isPrimary);

        // First: mark as NOT bound.
        // First: Only mark not BOUND for primary service.
        synchronized (mBoundCompanionApplications) {
            if (serviceConnector.isPrimary()) {
                mBoundCompanionApplications.removePackage(userId, packageName);
            }
        }

        // Second: schedule rebinding if needed.
        final boolean shouldScheduleRebind = shouldScheduleRebind(userId, packageName, isPrimary);

        // Second: invoke callback, schedule rebinding if needed.
        final boolean shouldScheduleRebind =
                mCallback.onCompanionApplicationBindingDied(userId, packageName);
        if (shouldScheduleRebind) {
            scheduleRebinding(userId, packageName);
            scheduleRebinding(userId, packageName, serviceConnector);
        }
    }

@@ -291,6 +336,37 @@ public class CompanionApplicationController {
        return connectors != null ? connectors.get(0) : null;
    }

    private boolean shouldScheduleRebind(int userId, String packageName, boolean isPrimary) {
        // Make sure do not schedule rebind for the case ServiceConnector still gets callback after
        // app is uninstalled.
        boolean stillAssociated = false;

        for (AssociationInfo ai :
                mAssociationStore.getAssociationsForPackage(userId, packageName)) {
            final int associationId = ai.getId();
            stillAssociated = true;

            if (ai.isSelfManaged()) {
                // Do not rebind if primary one is died for selfManaged application.
                if (isPrimary
                        && mDevicePresenceMonitor.isDevicePresent(associationId)) {
                    mDevicePresenceMonitor.onSelfManagedDeviceReporterBinderDied(associationId);
                    return false;
                }
                // Do not rebind if both primary and secondary services are died for
                // selfManaged application.
                if (!isCompanionApplicationBound(userId, packageName)) {
                    return false;
                }
            } else if (ai.isNotifyOnDeviceNearby()) {
                // Always rebind for non-selfManaged devices.
                return true;
            }
        }

        return stillAssociated;
    }

    private class CompanionServicesRegister extends PerUser<Map<String, List<ComponentName>>> {
        @Override
        public synchronized @NonNull Map<String, List<ComponentName>> forUser(
+1 −33
Original line number Diff line number Diff line
@@ -233,7 +233,7 @@ public class CompanionDeviceManagerService extends SystemService {
        mAssociationRequestsProcessor = new AssociationRequestsProcessor(
                /* cdmService */this, mAssociationStore);
        mCompanionAppController = new CompanionApplicationController(
                context, mApplicationControllerCallback);
                context, mAssociationStore, mDevicePresenceMonitor);
        mTransportManager = new CompanionTransportManager(context);
        mSystemDataTransferProcessor = new SystemDataTransferProcessor(this, mAssociationStore,
                mSystemDataTransferRequestStore, mTransportManager);
@@ -387,25 +387,6 @@ public class CompanionDeviceManagerService extends SystemService {
        mCompanionAppController.unbindCompanionApplication(userId, packageName);
    }

    private boolean onCompanionApplicationBindingDiedInternal(
            @UserIdInt int userId, @NonNull String packageName) {
        for (AssociationInfo ai :
                mAssociationStore.getAssociationsForPackage(userId, packageName)) {
            final int associationId = ai.getId();
            if (ai.isSelfManaged()
                    && mDevicePresenceMonitor.isDevicePresent(associationId)) {
                mDevicePresenceMonitor.onSelfManagedDeviceReporterBinderDied(associationId);
            }
        }
        // TODO(b/218613015): implement.
        return false;
    }

    private void onRebindCompanionApplicationTimeoutInternal(
            @UserIdInt int userId, @NonNull String packageName) {
        // TODO(b/218613015): implement.
    }

    /**
     * @return whether the package should be bound (i.e. at least one of the devices associated with
     *         the package is currently present).
@@ -1287,19 +1268,6 @@ public class CompanionDeviceManagerService extends SystemService {
        }
    };

    private final CompanionApplicationController.Callback mApplicationControllerCallback =
            new CompanionApplicationController.Callback() {
        @Override
        public boolean onCompanionApplicationBindingDied(int userId, @NonNull String packageName) {
            return onCompanionApplicationBindingDiedInternal(userId, packageName);
        }

        @Override
        public void onRebindCompanionApplicationTimeout(int userId, @NonNull String packageName) {
            onRebindCompanionApplicationTimeoutInternal(userId, packageName);
        }
    };

    private final PackageMonitor mPackageMonitor = new PackageMonitor() {
        @Override
        public void onPackageRemoved(String packageName, int uid) {
+18 −15
Original line number Diff line number Diff line
@@ -48,7 +48,8 @@ class CompanionDeviceServiceConnector extends ServiceConnector.Impl<ICompanionDe

    /** Listener for changes to the state of the {@link CompanionDeviceServiceConnector}  */
    interface Listener {
        void onBindingDied(@UserIdInt int userId, @NonNull String packageName);
        void onBindingDied(@UserIdInt int userId, @NonNull String packageName,
                @NonNull CompanionDeviceServiceConnector serviceConnector);
    }

    private final @UserIdInt int mUserId;
@@ -57,6 +58,7 @@ class CompanionDeviceServiceConnector extends ServiceConnector.Impl<ICompanionDe
    // installs a listener to the primary ServiceConnector), hence we should always null-check the
    // reference before calling on it.
    private @Nullable Listener mListener;
    private boolean mIsPrimary;

    /**
     * Create a CompanionDeviceServiceConnector instance.
@@ -74,17 +76,20 @@ class CompanionDeviceServiceConnector extends ServiceConnector.Impl<ICompanionDe
     * service importance level should be higher than 125.
     */
    static CompanionDeviceServiceConnector newInstance(@NonNull Context context,
            @UserIdInt int userId, @NonNull ComponentName componentName, boolean isSelfManaged) {
            @UserIdInt int userId, @NonNull ComponentName componentName, boolean isSelfManaged,
            boolean isPrimary) {
        final int bindingFlags = isSelfManaged ? BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE
                : BIND_ALMOST_PERCEPTIBLE;
        return new CompanionDeviceServiceConnector(context, userId, componentName, bindingFlags);
        return new CompanionDeviceServiceConnector(
                context, userId, componentName, bindingFlags, isPrimary);
    }

    private CompanionDeviceServiceConnector(@NonNull Context context, @UserIdInt int userId,
            @NonNull ComponentName componentName, int bindingFlags) {
            @NonNull ComponentName componentName, int bindingFlags, boolean isPrimary) {
        super(context, buildIntent(componentName), bindingFlags, userId, null);
        mUserId = userId;
        mComponentName = componentName;
        mIsPrimary = isPrimary;
    }

    void setListener(@Nullable Listener listener) {
@@ -110,6 +115,14 @@ class CompanionDeviceServiceConnector extends ServiceConnector.Impl<ICompanionDe
        post(it -> unbind());
    }

    boolean isPrimary() {
        return mIsPrimary;
    }

    ComponentName getComponentName() {
        return mComponentName;
    }

    @Override
    protected void onServiceConnectionStatusChanged(
            @NonNull ICompanionDeviceService service, boolean isConnected) {
@@ -119,16 +132,6 @@ class CompanionDeviceServiceConnector extends ServiceConnector.Impl<ICompanionDe
        }
    }

    // This method is only called when app is force-closed via settings,
    // but does not handle crashes. Binder death should be handled in #binderDied()
    @Override
    public void onBindingDied(@NonNull ComponentName name) {
        // IMPORTANT: call super! this will also invoke binderDied()
        super.onBindingDied(name);

        if (DEBUG) Log.d(TAG, "onBindingDied() " + mComponentName.toShortString());
    }

    @Override
    public void binderDied() {
        super.binderDied();
@@ -137,7 +140,7 @@ class CompanionDeviceServiceConnector extends ServiceConnector.Impl<ICompanionDe

        // Handle primary process being killed
        if (mListener != null) {
            mListener.onBindingDied(mUserId, mComponentName.getPackageName());
            mListener.onBindingDied(mUserId, mComponentName.getPackageName(), this);
        }
    }