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

Commit ae983d49 authored by Kyunglyul Hyun's avatar Kyunglyul Hyun
Browse files

Clean up Session2Record in MCS

This CL cleans up how MediaCommunicationService
manages Session2Record instances.

It removes unnecessary codes and refactor locks.
It also adds/clean up tests for MCM.SessionCallback.

Bug: 183289655
Bug: 182907935
Test: atest MediaCommunicationManagerTest 10 times
Change-Id: Iff4643c7dc207ea14c752bdfb815598dc34f2368
parent 2ae2b96c
Loading
Loading
Loading
Loading
+1 −1
Original line number Original line Diff line number Diff line
@@ -19,7 +19,7 @@ import android.media.Session2Token;
import android.media.MediaParceledListSlice;
import android.media.MediaParceledListSlice;


/** {@hide} */
/** {@hide} */
interface IMediaCommunicationServiceCallback {
oneway interface IMediaCommunicationServiceCallback {
    void onSession2Created(in Session2Token token);
    void onSession2Created(in Session2Token token);
    void onSession2Changed(in MediaParceledListSlice tokens);
    void onSession2Changed(in MediaParceledListSlice tokens);
}
}
+142 −74
Original line number Original line Diff line number Diff line
@@ -65,17 +65,17 @@ public class MediaCommunicationService extends SystemService {


    final Context mContext;
    final Context mContext;


    private final Object mLock = new Object();
    final Object mLock = new Object();
    private final Handler mHandler = new Handler(Looper.getMainLooper());
    final Handler mHandler = new Handler(Looper.getMainLooper());


    @GuardedBy("mLock")
    @GuardedBy("mLock")
    private final SparseIntArray mFullUserIds = new SparseIntArray();
    private final SparseIntArray mFullUserIds = new SparseIntArray();
    @GuardedBy("mLock")
    @GuardedBy("mLock")
    private final SparseArray<FullUserRecord> mUserRecords = new SparseArray<>();
    private final SparseArray<FullUserRecord> mUserRecords = new SparseArray<>();


    private final Executor mRecordExecutor = Executors.newSingleThreadExecutor();
    final Executor mRecordExecutor = Executors.newSingleThreadExecutor();
    @GuardedBy("mLock")
    @GuardedBy("mLock")
    private final List<CallbackRecord> mCallbackRecords = new ArrayList<>();
    final List<CallbackRecord> mCallbackRecords = new ArrayList<>();
    final NotificationManager mNotificationManager;
    final NotificationManager mNotificationManager;


    public MediaCommunicationService(Context context) {
    public MediaCommunicationService(Context context) {
@@ -111,14 +111,14 @@ public class MediaCommunicationService extends SystemService {
            FullUserRecord user = getFullUserRecordLocked(userId);
            FullUserRecord user = getFullUserRecordLocked(userId);
            if (user != null) {
            if (user != null) {
                if (user.getFullUserId() == userId) {
                if (user.getFullUserId() == userId) {
                    user.destroySessionsForUserLocked(UserHandle.ALL.getIdentifier());
                    user.destroyAllSessions();
                    mUserRecords.remove(userId);
                    mUserRecords.remove(userId);
                } else {
                } else {
                    user.destroySessionsForUserLocked(userId);
                    user.destroySessionsForUser(userId);
                }
                }
            }
            }
            updateUser();
        }
        }
        updateUser();
    }
    }


    @Nullable
    @Nullable
@@ -134,6 +134,22 @@ public class MediaCommunicationService extends SystemService {
        return null;
        return null;
    }
    }


    List<Session2Token> getSession2TokensLocked(int userId) {
        List<Session2Token> list = new ArrayList<>();
        if (userId == ALL.getIdentifier()) {
            int size = mUserRecords.size();
            for (int i = 0; i < size; i++) {
                list.addAll(mUserRecords.valueAt(i).getAllSession2Tokens());
            }
        } else {
            FullUserRecord user = getFullUserRecordLocked(userId);
            if (user != null) {
                list.addAll(user.getSession2Tokens(userId));
            }
        }
        return list;
    }

    private FullUserRecord getFullUserRecordLocked(int userId) {
    private FullUserRecord getFullUserRecordLocked(int userId) {
        int fullUserId = mFullUserIds.get(userId, -1);
        int fullUserId = mFullUserIds.get(userId, -1);
        if (fullUserId < 0) {
        if (fullUserId < 0) {
@@ -188,7 +204,8 @@ public class MediaCommunicationService extends SystemService {
        }
        }
    }
    }


    void dispatchSessionCreated(Session2Token token) {
    void dispatchSession2Created(Session2Token token) {
        synchronized (mLock) {
            for (CallbackRecord record : mCallbackRecords) {
            for (CallbackRecord record : mCallbackRecords) {
                if (record.mUserId != ALL.getIdentifier()
                if (record.mUserId != ALL.getIdentifier()
                        && record.mUserId != getUserHandleForUid(token.getUid()).getIdentifier()) {
                        && record.mUserId != getUserHandleForUid(token.getUid()).getIdentifier()) {
@@ -197,18 +214,44 @@ public class MediaCommunicationService extends SystemService {
                try {
                try {
                    record.mCallback.onSession2Created(token);
                    record.mCallback.onSession2Created(token);
                } catch (RemoteException e) {
                } catch (RemoteException e) {
                e.printStackTrace();
                    Log.w(TAG, "Failed to notify session2 token created " + record);
                }
            }
            }
        }
        }
    }
    }


    void onSessionDied(Session2Record record) {
    void dispatchSession2Changed(int userId) {
        MediaParceledListSlice<Session2Token> allSession2Tokens;
        MediaParceledListSlice<Session2Token> userSession2Tokens;

        synchronized (mLock) {
        synchronized (mLock) {
            destroySessionLocked(record);
            allSession2Tokens =
                    new MediaParceledListSlice<>(getSession2TokensLocked(ALL.getIdentifier()));
            userSession2Tokens = new MediaParceledListSlice<>(getSession2TokensLocked(userId));
        }
        allSession2Tokens.setInlineCountLimit(1);
        userSession2Tokens.setInlineCountLimit(1);

        synchronized (mLock) {
            for (CallbackRecord record : mCallbackRecords) {
                if (record.mUserId == ALL.getIdentifier()) {
                    try {
                        record.mCallback.onSession2Changed(allSession2Tokens);
                    } catch (RemoteException e) {
                        Log.w(TAG, "Failed to notify session2 tokens changed " + record);
                    }
                } else if (record.mUserId == userId) {
                    try {
                        record.mCallback.onSession2Changed(userSession2Tokens);
                    } catch (RemoteException e) {
                        Log.w(TAG, "Failed to notify session2 tokens changed " + record);
                    }
                }
            }
        }
        }
    }
    }


    private void destroySessionLocked(Session2Record session) {
    void onSessionDied(Session2Record session) {
        if (DEBUG) {
        if (DEBUG) {
            Log.d(TAG, "Destroying " + session);
            Log.d(TAG, "Destroying " + session);
        }
        }
@@ -217,12 +260,10 @@ public class MediaCommunicationService extends SystemService {
            return;
            return;
        }
        }


        FullUserRecord user = getFullUserRecordLocked(session.getUserId());
        FullUserRecord user = session.getFullUser();

        if (user != null) {
        if (user != null) {
            user.removeSession(session);
            user.removeSession(session);
        }
        }

        session.close();
        session.close();
    }
    }


@@ -241,17 +282,17 @@ public class MediaCommunicationService extends SystemService {
                    throw new SecurityException("Unexpected Session2Token's UID, expected=" + uid
                    throw new SecurityException("Unexpected Session2Token's UID, expected=" + uid
                            + " but actually=" + sessionToken.getUid());
                            + " but actually=" + sessionToken.getUid());
                }
                }
                synchronized (mLock) {
                FullUserRecord user;
                int userId = getUserHandleForUid(sessionToken.getUid()).getIdentifier();
                int userId = getUserHandleForUid(sessionToken.getUid()).getIdentifier();
                    FullUserRecord user = getFullUserRecordLocked(userId);
                synchronized (mLock) {
                    user = getFullUserRecordLocked(userId);
                }
                if (user == null) {
                if (user == null) {
                    Log.w(TAG, "notifySession2Created: Ignore session of an unknown user");
                    Log.w(TAG, "notifySession2Created: Ignore session of an unknown user");
                    return;
                    return;
                }
                }
                user.addSession(new Session2Record(MediaCommunicationService.this,
                user.addSession(new Session2Record(MediaCommunicationService.this,
                            sessionToken, mRecordExecutor));
                        user, sessionToken, mRecordExecutor));
                    mHandler.post(() -> dispatchSessionCreated(sessionToken));
                }
            } finally {
            } finally {
                Binder.restoreCallingIdentity(token);
                Binder.restoreCallingIdentity(token);
            }
            }
@@ -299,10 +340,11 @@ public class MediaCommunicationService extends SystemService {
                int resolvedUserId = handleIncomingUser(pid, uid, userId, null);
                int resolvedUserId = handleIncomingUser(pid, uid, userId, null);
                List<Session2Token> result;
                List<Session2Token> result;
                synchronized (mLock) {
                synchronized (mLock) {
                    FullUserRecord user = getFullUserRecordLocked(userId);
                    result = getSession2TokensLocked(resolvedUserId);
                    result = user.getSession2Tokens(resolvedUserId);
                }
                }
                return new MediaParceledListSlice(result);
                MediaParceledListSlice parceledListSlice = new MediaParceledListSlice<>(result);
                parceledListSlice.setInlineCountLimit(1);
                return parceledListSlice;
            } finally {
            } finally {
                Binder.restoreCallingIdentity(token);
                Binder.restoreCallingIdentity(token);
            }
            }
@@ -427,7 +469,8 @@ public class MediaCommunicationService extends SystemService {


    final class FullUserRecord {
    final class FullUserRecord {
        private final int mFullUserId;
        private final int mFullUserId;
        /** Sorted list of media sessions */
        private final Object mUserLock = new Object();
        @GuardedBy("mUserLock")
        private final List<Session2Record> mSessionRecords = new ArrayList<>();
        private final List<Session2Record> mSessionRecords = new ArrayList<>();


        FullUserRecord(int fullUserId) {
        FullUserRecord(int fullUserId) {
@@ -435,11 +478,18 @@ public class MediaCommunicationService extends SystemService {
        }
        }


        public void addSession(Session2Record record) {
        public void addSession(Session2Record record) {
            synchronized (mUserLock) {
                mSessionRecords.add(record);
                mSessionRecords.add(record);
            }
            }
            mHandler.post(() -> dispatchSession2Created(record.mSessionToken));
            mHandler.post(() -> dispatchSession2Changed(mFullUserId));
        }


        public void removeSession(Session2Record record) {
        private void removeSession(Session2Record record) {
            synchronized (mUserLock) {
                mSessionRecords.remove(record);
                mSessionRecords.remove(record);
            }
            mHandler.post(() -> dispatchSession2Changed(mFullUserId));
            //TODO: Handle if the removed session was the media button session.
            //TODO: Handle if the removed session was the media button session.
        }
        }


@@ -447,42 +497,68 @@ public class MediaCommunicationService extends SystemService {
            return mFullUserId;
            return mFullUserId;
        }
        }


        public List<Session2Token> getAllSession2Tokens() {
            synchronized (mUserLock) {
                return mSessionRecords.stream()
                        .map(Session2Record::getSessionToken)
                        .collect(Collectors.toList());
            }
        }

        public List<Session2Token> getSession2Tokens(int userId) {
        public List<Session2Token> getSession2Tokens(int userId) {
            synchronized (mUserLock) {
                return mSessionRecords.stream()
                return mSessionRecords.stream()
                    .filter(record -> record.isActive()
                        .filter(record -> record.getUserId() == userId)
                            && (userId == UserHandle.ALL.getIdentifier()
                                    || record.getUserId() == userId))
                        .map(Session2Record::getSessionToken)
                        .map(Session2Record::getSessionToken)
                        .collect(Collectors.toList());
                        .collect(Collectors.toList());
            }
            }
        }


        public void destroySessionsForUserLocked(int userId) {
        public void destroyAllSessions() {
            synchronized (mLock) {
            synchronized (mUserLock) {
                for (Session2Record record : mSessionRecords) {
                for (Session2Record session : mSessionRecords) {
                    if (userId == UserHandle.ALL.getIdentifier()
                    session.close();
                            || record.getUserId() == userId) {
                }
                        destroySessionLocked(record);
                mSessionRecords.clear();
            }
            }
            mHandler.post(() -> dispatchSession2Changed(mFullUserId));
        }
        }

        public void destroySessionsForUser(int userId) {
            boolean changed = false;
            synchronized (mUserLock) {
                for (int i = mSessionRecords.size() - 1; i >= 0; i--) {
                    Session2Record session = mSessionRecords.get(i);
                    if (session.getUserId() == userId) {
                        mSessionRecords.remove(i);
                        session.close();
                        changed = true;
                    }
                }
            }
            if (changed) {
                mHandler.post(() -> dispatchSession2Changed(mFullUserId));
            }
            }
        }
        }
    }
    }


    static final class Session2Record {
    static final class Session2Record {
        private final Session2Token mSessionToken;
        final Session2Token mSessionToken;
        private final Object mLock = new Object();
        final Object mSession2RecordLock = new Object();
        private final WeakReference<MediaCommunicationService> mServiceRef;
        final WeakReference<MediaCommunicationService> mServiceRef;
        @GuardedBy("mLock")
        final WeakReference<FullUserRecord> mFullUserRef;
        @GuardedBy("mSession2RecordLock")
        private final MediaController2 mController;
        private final MediaController2 mController;


        @GuardedBy("mLock")
        @GuardedBy("mSession2RecordLock")
        private boolean mIsConnected;
        boolean mIsConnected;
        @GuardedBy("mLock")
        @GuardedBy("mSession2RecordLock")
        private boolean mIsClosed;
        private boolean mIsClosed;


        Session2Record(MediaCommunicationService service, Session2Token token,
        Session2Record(MediaCommunicationService service, FullUserRecord fullUser,
                Executor controllerExecutor) {
                Session2Token token, Executor controllerExecutor) {
            mServiceRef = new WeakReference<>(service);
            mServiceRef = new WeakReference<>(service);
            mFullUserRef = new WeakReference<>(fullUser);
            mSessionToken = token;
            mSessionToken = token;
            mController = new MediaController2.Builder(service.getContext(), token)
            mController = new MediaController2.Builder(service.getContext(), token)
                    .setControllerCallback(controllerExecutor, new Controller2Callback())
                    .setControllerCallback(controllerExecutor, new Controller2Callback())
@@ -493,23 +569,19 @@ public class MediaCommunicationService extends SystemService {
            return UserHandle.getUserHandleForUid(mSessionToken.getUid()).getIdentifier();
            return UserHandle.getUserHandleForUid(mSessionToken.getUid()).getIdentifier();
        }
        }


        public boolean isActive() {
        public FullUserRecord getFullUser() {
            synchronized (mLock) {
            return mFullUserRef.get();
                return mIsConnected;
            }
        }
        }


        public boolean isClosed() {
        public boolean isClosed() {
            synchronized (mLock) {
            synchronized (mSession2RecordLock) {
                return mIsClosed;
                return mIsClosed;
            }
            }
        }
        }


        public void close() {
        public void close() {
            synchronized (mLock) {
            synchronized (mSession2RecordLock) {
                mIsClosed = true;
                mIsClosed = true;
                // Call close regardless of the mIsConnected. This may be called when it's not yet
                // connected.
                mController.close();
                mController.close();
            }
            }
        }
        }
@@ -525,13 +597,9 @@ public class MediaCommunicationService extends SystemService {
                if (DEBUG) {
                if (DEBUG) {
                    Log.d(TAG, "connected to " + mSessionToken + ", allowed=" + allowedCommands);
                    Log.d(TAG, "connected to " + mSessionToken + ", allowed=" + allowedCommands);
                }
                }
                synchronized (mLock) {
                synchronized (mSession2RecordLock) {
                    mIsConnected = true;
                    mIsConnected = true;
                }
                }
                MediaCommunicationService service = mServiceRef.get();
                if (service != null) {
                    //TODO: notify session state changed
                }
            }
            }


            @Override
            @Override
@@ -539,7 +607,7 @@ public class MediaCommunicationService extends SystemService {
                if (DEBUG) {
                if (DEBUG) {
                    Log.d(TAG, "disconnected from " + mSessionToken);
                    Log.d(TAG, "disconnected from " + mSessionToken);
                }
                }
                synchronized (mLock) {
                synchronized (mSession2RecordLock) {
                    mIsConnected = false;
                    mIsConnected = false;
                }
                }
                MediaCommunicationService service = mServiceRef.get();
                MediaCommunicationService service = mServiceRef.get();