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

Commit 74381b8e authored by Shenqiu Zhang's avatar Shenqiu Zhang
Browse files

Clean up dead RouterRecords after unbinding them

When apps are force killed or the ActivityManager freezes apps and kill
apps unexpectly, some RouterRecords are not cleaned up and their
media route providers might still have route controller instances
running, which might sent updates to the media router framework to
update status to dead routers. In this case, we have a lot of
DeadObjectException errors. Even worse, if there is an active casting
session when the app is force killed, the previous casting session
might interrupt the following casting session. In other words, we can't
cast to a remote device if the previous casting session was ended
unexpectly. Thus, cleaning up the dead RouterRecords is necessary
to ensure smooth casting experience.

Flag: com.android.media.flags.clean_up_dead_router_records_after_unbinding
Test: presubmit and manually tested with demo app
Bug: b/414836668
Fix: b/414836668
Change-Id: Ide8f369a237cd9c2dc7d51c160ad263c602496e3
parent 0a4d8ea1
Loading
Loading
Loading
Loading
+10 −0
Original line number Diff line number Diff line
@@ -10,6 +10,16 @@ flag {
    bug: "275185436"
}

flag {
    name: "clean_up_dead_router_records_after_unbinding"
    namespace: "media_better_together"
    description: "Fixes a bug of notifying unbound router records that causes DeadObjectException."
    bug: "414836668"
    metadata {
        purpose: PURPOSE_BUGFIX
    }
}

flag {
    name: "disable_set_bluetooth_ad2p_on_calls"
    namespace: "media_better_together"
+84 −13
Original line number Diff line number Diff line
@@ -1295,7 +1295,7 @@ class MediaRouter2ServiceImpl {
            throw new RuntimeException("MediaRouter2 died prematurely.", ex);
        }

        userRecord.mRouterRecords.add(routerRecord);
        userRecord.addRouterRecord(routerRecord);
        mAllRouterRecords.put(binder, routerRecord);

        userRecord.mHandler.sendMessage(
@@ -1328,11 +1328,10 @@ class MediaRouter2ServiceImpl {
        Slog.i(
                TAG,
                TextUtils.formatSimple(
                        "unregisterRouter2 | package: %s, router id: %d, died: %b",
                        routerRecord.mPackageName, routerRecord.mRouterId, died));
                        "unregisterRouter2 | %s, died: %b", routerRecord.getDebugString(), died));

        UserRecord userRecord = routerRecord.mUserRecord;
        userRecord.mRouterRecords.remove(routerRecord);
        userRecord.removeRouterRecord(routerRecord);
        routerRecord.mUserRecord.mHandler.sendMessage(
                obtainMessage(UserHandler::notifyDiscoveryPreferenceChangedToManagers,
                        routerRecord.mUserRecord.mHandler,
@@ -1899,11 +1898,8 @@ class MediaRouter2ServiceImpl {
        Slog.i(
                TAG,
                TextUtils.formatSimple(
                        "unregisterManager | package: %s, user: %d, manager: %d, died: %b",
                        managerRecord.mOwnerPackageName,
                        userRecord.mUserId,
                        managerRecord.mManagerId,
                        died));
                        "unregisterManager | %s, user: %d, died: %b",
                        managerRecord.getDebugString(), userRecord.mUserId, died));

        userRecord.mManagerRecords.remove(managerRecord);
        managerRecord.dispose();
@@ -2330,7 +2326,7 @@ class MediaRouter2ServiceImpl {
    final class UserRecord {
        public final int mUserId;
        //TODO: make records private for thread-safety
        final ArrayList<RouterRecord> mRouterRecords = new ArrayList<>();
        private final ArrayList<RouterRecord> mRouterRecords = new ArrayList<>();
        final ArrayList<ManagerRecord> mManagerRecords = new ArrayList<>();

        // @GuardedBy("mLock")
@@ -2354,6 +2350,17 @@ class MediaRouter2ServiceImpl {
            mHandler.init();
        }

        void addRouterRecord(RouterRecord routerRecord) {
            mRouterRecords.add(routerRecord);
        }

        void removeRouterRecord(RouterRecord routerRecord) {
            mRouterRecords.remove(routerRecord);
            if (Flags.cleanUpDeadRouterRecordsAfterUnbinding()) {
                mHandler.removeRouterRecord(routerRecord);
            }
        }

        // TODO: This assumes that only one router exists in a package.
        //       Do this in Android S or later.
        @GuardedBy("mLock")
@@ -2366,6 +2373,16 @@ class MediaRouter2ServiceImpl {
            return null;
        }

        /** Returns true if the given RouterRecord is binded to the service. */
        boolean isRouterRecordBinded(RouterRecord routerRecordToCheck) {
            for (RouterRecord routerRecord : mRouterRecords) {
                if (routerRecord.mRouterId == routerRecordToCheck.mRouterId) {
                    return true;
                }
            }
            return false;
        }

        // @GuardedBy("mLock")
        public void updateDeviceSuggestionsLocked(
                String packageName,
@@ -3046,6 +3063,12 @@ class MediaRouter2ServiceImpl {
        @Override
        public void onSessionCreated(@NonNull MediaRoute2Provider provider,
                long uniqueRequestId, @NonNull RoutingSessionInfo sessionInfo) {
            Slog.i(
                    TAG,
                    "onSessionCreated with uniqueRequestId: "
                            + uniqueRequestId
                            + ", sessionInfo: "
                            + sessionInfo);
            sendMessage(PooledLambda.obtainMessage(UserHandler::onSessionCreatedOnHandler,
                    this, provider, uniqueRequestId, sessionInfo));
        }
@@ -3112,6 +3135,24 @@ class MediaRouter2ServiceImpl {
            }
        }

        public void removeRouterRecord(RouterRecord routerRecord) {
            for (String sessionId : mSessionToRouterMap.keySet()) {
                RouterRecord routerRecordWithSession = mSessionToRouterMap.get(sessionId);
                if (routerRecordWithSession.mRouterId == routerRecord.mRouterId) {
                    // Release the session associated with the RouterRecord being removed. The
                    // onSessionReleasedOnHandler callback will then remove the RouterRecord from
                    // mSessionToRouterMap.
                    sendMessage(
                            PooledLambda.obtainMessage(
                                    UserHandler::releaseSessionOnHandler,
                                    this,
                                    DUMMY_REQUEST_ID,
                                    routerRecordWithSession,
                                    sessionId));
                }
            }
        }

        public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
            pw.println(prefix + "UserHandler");

@@ -3580,6 +3621,16 @@ class MediaRouter2ServiceImpl {
            }

            mSessionCreationRequests.remove(matchingRequest);

            if (Flags.cleanUpDeadRouterRecordsAfterUnbinding()
                    && !mUserRecord.isRouterRecordBinded(matchingRequest.mRouterRecord)) {
                Slog.w(
                        TAG,
                        "Ignoring session creation request for unbound router:"
                                + matchingRequest.mRouterRecord.getDebugString());
                return;
            }

            // Not to show old session
            MediaRoute2Provider oldProvider =
                    findProvider(matchingRequest.mOldSession.getProviderId());
@@ -3650,8 +3701,11 @@ class MediaRouter2ServiceImpl {
                        + sessionInfo);
                return;
            }
            if (!Flags.cleanUpDeadRouterRecordsAfterUnbinding()
                    || mUserRecord.isRouterRecordBinded(routerRecord)) {
                notifySessionInfoChangedToRouters(Arrays.asList(routerRecord), sessionInfo);
            }
        }

        private void onSessionReleasedOnHandler(@NonNull MediaRoute2Provider provider,
                @NonNull RoutingSessionInfo sessionInfo) {
@@ -3666,8 +3720,16 @@ class MediaRouter2ServiceImpl {
                        + sessionInfo);
                return;
            }

            if (Flags.cleanUpDeadRouterRecordsAfterUnbinding()) {
                if (mUserRecord.isRouterRecordBinded(routerRecord)) {
                    routerRecord.notifySessionReleased(sessionInfo);
                }
                mSessionToRouterMap.remove(sessionInfo.getId());
            } else {
                routerRecord.notifySessionReleased(sessionInfo);
            }
        }

        private void onRequestFailedOnHandler(@NonNull MediaRoute2Provider provider,
                long uniqueRequestId, int reason) {
@@ -3721,6 +3783,15 @@ class MediaRouter2ServiceImpl {

            mSessionCreationRequests.remove(matchingRequest);

            if (Flags.cleanUpDeadRouterRecordsAfterUnbinding()
                    && !mUserRecord.isRouterRecordBinded(matchingRequest.mRouterRecord)) {
                Slog.w(
                        TAG,
                        "handleSessionCreationRequestFailed | Ignoring with unbound router:"
                                + matchingRequest.mRouterRecord.getDebugString());
                return false;
            }

            // Notify the requester about the failure.
            // The call should be made by either MediaRouter2 or MediaRouter2Manager.
            if (matchingRequest.mManagerRequestId == MediaRouter2Manager.REQUEST_ID_NONE) {
@@ -3894,7 +3965,7 @@ class MediaRouter2ServiceImpl {
                } catch (RemoteException ex) {
                    Slog.w(
                            TAG,
                            "Failed to notify preferred features changed."
                            "Failed to notify route listing preference changed."
                                    + " Manager probably died.",
                            ex);
                }