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

Commit 117c7f72 authored by Sungsoo Lim's avatar Sungsoo Lim
Browse files

Remove MediaSession2Record

Test: passed MediaComponentsTest
Change-Id: I787c20f3c44f481960203699af563109a022e3d4
parent 17bc6993
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -52,8 +52,8 @@ interface ISessionManager {
    void setOnMediaKeyListener(in IOnMediaKeyListener listener);

    // MediaSession2
    boolean onSessionCreated(in Bundle sessionToken);
    void onSessionDestroyed(in Bundle sessionToken);
    boolean createSession2(in Bundle sessionToken);
    void destroySession2(in Bundle sessionToken);
    List<Bundle> getSessionTokens(boolean activeSessionOnly, boolean sessionServiceOnly);

    void addSessionTokensListener(in ISessionTokensListener listener, int userId,
+4 −4
Original line number Diff line number Diff line
@@ -344,12 +344,12 @@ public final class MediaSessionManager {
     * @hide
     */
    // TODO(jaewan): System API
    public boolean onSessionCreated(@NonNull SessionToken2 token) {
    public boolean createSession2(@NonNull SessionToken2 token) {
        if (token == null) {
            return false;
        }
        try {
            return mService.onSessionCreated(token.toBundle());
            return mService.createSession2(token.toBundle());
        } catch (RemoteException e) {
            Log.wtf(TAG, "Cannot communicate with the service.", e);
        }
@@ -360,12 +360,12 @@ public final class MediaSessionManager {
     * @hide
     */
    // TODO(jaewan): System API
    public void onSessionDestroyed(@NonNull SessionToken2 token) {
    public void destroySession2(@NonNull SessionToken2 token) {
        if (token == null) {
            return;
        }
        try {
            mService.onSessionDestroyed(token.toBundle());
            mService.destroySession2(token.toBundle());
        } catch (RemoteException e) {
            Log.wtf(TAG, "Cannot communicate with the service.", e);
        }
+0 −125
Original line number Diff line number Diff line
/*
 * Copyright 2018 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.media;

import android.annotation.NonNull;
import android.content.Context;
import android.media.MediaController2;
import android.media.MediaSession2;
import android.media.SessionToken2;
import android.util.Log;
import java.util.concurrent.Executor;

/**
 * Records a {@link MediaSession2} and holds {@link MediaController2}.
 * <p>
 * Owner of this object should handle synchronization.
 */
class MediaSession2Record {
    interface SessionDestroyedListener {
        void onSessionDestroyed(MediaSession2Record record);
    }

    private static final String TAG = "Session2Record";
    private static final boolean DEBUG = true; // TODO(jaewan): Change

    private final Context mContext;
    private final SessionToken2 mSessionToken;
    private final SessionDestroyedListener mSessionDestroyedListener;

    // TODO(jaewan): Replace these with the mContext.getMainExecutor()
    private final Executor mMainExecutor;

    private MediaController2 mController;

    /**
     * Constructor
     */
    public MediaSession2Record(@NonNull Context context, @NonNull SessionToken2 token,
            @NonNull SessionDestroyedListener listener) {
        mContext = context;
        mSessionToken = token;
        mSessionDestroyedListener = listener;
        mMainExecutor = (runnable) -> runnable.run();
    }

    public Context getContext() {
        return mContext;
    }

    public void onSessionDestroyed() {
        if (mController != null) {
            mController.close();
            // close() triggers ControllerCallback.onDisconnected() here already.
            mController = null;
        }
    }

    public boolean onSessionCreated(SessionToken2 token) {
        if (mController != null) {
            // Disclaimer: This may fail if following happens for an app.
            //             Step 1) Create a session in the process #1
            //             Step 2) Process #1 is killed
            //             Step 3) Before the death of process #1 is delivered,
            //                     (i.e. ControllerCallback#onDisconnected is called),
            //                     new process is started and create another session with the same
            //                     id in the new process.
            //             Step 4) fail!!! But this is tricky case that wouldn't happen in normal.
            Log.w(TAG, "Cannot create a new session with the id=" + token.getId() + " in the"
                    + " pkg=" + token.getPackageName() + ". ID should be unique in a package");
            return false;
        }
        mController = new MediaController2(mContext, token, mMainExecutor,
                new ControllerCallback());
        return true;
    }

    /**
     * @return token
     */
    public SessionToken2 getToken() {
        return mSessionToken;
    }

    /**
     * @return controller
     */
    public MediaController2 getController() {
        return mController;
    }

    @Override
    public String toString() {
        return getToken() == null
                ? "Token {null}" : "SessionRecord {" + getToken().toString() + "}";
    }

    private class ControllerCallback extends MediaController2.ControllerCallback {
        // This is called on the random thread with no lock. So place ensure followings.
        //   1. Don't touch anything in the parent class that needs synchronization.
        //      All other APIs in the MediaSession2Record assumes that server would use them with
        //      the lock hold.
        //   2. This can be called after the controller registered is closed.
        @Override
        public void onDisconnected() {
            if (DEBUG) {
                Log.d(TAG, "onDisconnected, token=" + getToken());
            }
            mSessionDestroyedListener.onSessionDestroyed(MediaSession2Record.this);
        }
    };
}
+68 −96
Original line number Diff line number Diff line
@@ -42,6 +42,7 @@ import android.media.AudioSystem;
import android.media.IAudioService;
import android.media.IRemoteVolumeController;
import android.media.ISessionTokensListener;
import android.media.MediaController2;
import android.media.MediaLibraryService2;
import android.media.MediaSessionService2;
import android.media.SessionToken2;
@@ -70,6 +71,7 @@ import android.os.UserManager;
import android.provider.Settings;
import android.speech.RecognizerIntent;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
@@ -86,7 +88,10 @@ import com.android.server.Watchdog.Monitor;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * System implementation of MediaSessionManager
@@ -132,14 +137,7 @@ public class MediaSessionService extends SystemService implements Monitor {
    // MediaSession2 support
    // TODO(jaewan): Support multi-user and managed profile.
    // TODO(jaewan): Make it priority list for handling volume/media key.
    private final List<MediaSession2Record> mSessions = new ArrayList<>();

    private final MediaSession2Record.SessionDestroyedListener mSessionDestroyedListener =
            (record) -> {
                synchronized (mLock) {
                    destroySessionLocked(record);
                }
            };
    private final Map<SessionToken2, MediaController2> mSessionRecords = new ArrayMap<>();

    public MediaSessionService(Context context) {
        super(context);
@@ -523,12 +521,13 @@ public class MediaSessionService extends SystemService implements Monitor {
        synchronized (mLock) {
            // List to keep the session services that need be removed because they don't exist
            // in the 'services' above.
            List<MediaSession2Record> removeCandidates = new ArrayList<>();
            for (int i = 0; i < mSessions.size(); i++) {
                if (mSessions.get(i).getToken().getType() != TYPE_SESSION) {
                    removeCandidates.add(mSessions.get(i));
            Set<SessionToken2> sessionTokensToRemove = new HashSet<>(mSessionRecords.keySet());
            for (SessionToken2 token : sessionTokensToRemove) {
                if (token.getType() == TYPE_SESSION) {
                    sessionTokensToRemove.remove(token);
                }
            }

            for (int i = 0; i < services.size(); i++) {
                if (services.get(i) == null || services.get(i).serviceInfo == null) {
                    continue;
@@ -550,57 +549,25 @@ public class MediaSessionService extends SystemService implements Monitor {
                    Log.w(TAG, "Invalid session service", e);
                    continue;
                }
                boolean found = false;
                for (int j = 0; j < mSessions.size(); j++) {
                    if (token.equals(mSessions.get(j).getToken())) {
                        // If the token already exists, keep it in the mSessions.
                        removeCandidates.remove(mSessions.get(j));
                        found = true;
                        break;
                    }
                }
                if (!found) {
                // If the token already exists, keep it in the mSessions by removing from
                // sessionTokensToRemove.
                if (!sessionTokensToRemove.remove(token)) {
                    // New session service is found.
                    MediaSession2Record record = new MediaSession2Record(getContext(),
                            token, mSessionDestroyedListener);
                    mSessions.add(record);
                    mSessionRecords.put(token, null);
                }
            }
            for (int i = 0; i < removeCandidates.size(); i++) {
                removeCandidates.get(i).onSessionDestroyed();
                mSessions.remove(removeCandidates.get(i));
            for (SessionToken2 token : sessionTokensToRemove) {
                mSessionRecords.remove(token);
            }
            removeCandidates.clear();
        }
        if (DEBUG) {
            Log.d(TAG, "Found " + mSessions.size() + " session services");
            for (int i = 0; i < mSessions.size(); i++) {
                Log.d(TAG, "   " + mSessions.get(i).getToken());
            Log.d(TAG, "Found " + mSessionRecords.size() + " session services");
            for (SessionToken2 token : mSessionRecords.keySet()) {
                Log.d(TAG, "   " + token);
            }
        }
    }

    private MediaSession2Record getSessionRecordLocked(int uid, String packageName, String id) {
        for (int i = 0; i < mSessions.size(); i++) {
            SessionToken2 token = mSessions.get(i).getToken();
            if (token.getUid() == uid && token.getPackageName().equals(packageName)
                    && token.getId().equals(id)) {
                return mSessions.get(i);
            }
        }
        return null;
    }

    private void destroySessionLocked(MediaSession2Record record) {
        if (DEBUG) {
            Log.d(TAG, record.toString() + " becomes inactive");
        }
        record.onSessionDestroyed();
        if (record.getToken().getType() == TYPE_SESSION) {
            mSessions.remove(record);
        }
    }

    private void enforcePackageName(String packageName, int uid) {
        if (TextUtils.isEmpty(packageName)) {
            throw new IllegalArgumentException("packageName may not be empty");
@@ -811,6 +778,16 @@ public class MediaSessionService extends SystemService implements Monitor {
        return mUserRecords.get(fullUserId);
    }

    void destroySession2Internal(SessionToken2 token) {
        synchronized (mLock) {
            if (token.getType() == SessionToken2.TYPE_SESSION) {
                mSessionRecords.remove(token);
            } else {
                mSessionRecords.put(token, null);
            }
        }
    }

    /**
     * Information about a full user and its corresponding managed profiles.
     *
@@ -1502,12 +1479,12 @@ public class MediaSessionService extends SystemService implements Monitor {

                // TODO(jaewan): Remove this debug command before ship.
                if (args != null && args.length > 0 && "--purge".equals(args[0])) {
                    mSessions.clear();
                    mSessionRecords.clear();
                }
                pw.println();
                pw.println("Session2: size=" + mSessions.size());
                for (int i = 0; i < mSessions.size(); i++) {
                    pw.println("  " + mSessions.get(i));
                pw.println("Session2: size=" + mSessionRecords.size());
                for (SessionToken2 token : mSessionRecords.keySet()) {
                    pw.println("  " + token);
                }
            }
        }
@@ -1524,28 +1501,25 @@ public class MediaSessionService extends SystemService implements Monitor {
         *     otherwise.
         */
        @Override
        public boolean onSessionCreated(Bundle sessionToken) {
        public boolean createSession2(Bundle sessionToken) {
            final int uid = Binder.getCallingUid();
            final int pid = Binder.getCallingPid();
            final SessionToken2 token = SessionToken2.fromBundle(getContext(), sessionToken);
            if (token == null || token.getUid() != uid) {
                Log.w(TAG, "onSessionCreated failed, expected caller uid=" + token.getUid()
                        + " but from uid=" + uid);
            }
            if (DEBUG) {
                Log.d(TAG, "onSessionCreated " + token);
                Log.d(TAG, "createSession2: " + token);
            }
            synchronized (mLock) {
                MediaSession2Record record = getSessionRecordLocked(
                        uid, token.getPackageName(), token.getId());
                if (record != null) {
                    return record.onSessionCreated(token);
                } else {
                    record = new MediaSession2Record(
                            getContext(), token, mSessionDestroyedListener);
                    mSessions.add(record);
                    return record.onSessionCreated(token);
                MediaController2 controller = mSessionRecords.get(token);
                if (controller != null && controller.isConnected()) {
                    return false;
                }
                Context context = getContext();
                mSessionRecords.put(token, new MediaController2(context, token,
                        context.getMainExecutor(), new ControllerCallback(token)));
                return true;
            }
        }

@@ -1563,29 +1537,17 @@ public class MediaSessionService extends SystemService implements Monitor {
         * @param sessionToken SessionToken2 object in bundled form
         */
        @Override
        public void onSessionDestroyed(Bundle sessionToken) {
        public void destroySession2(Bundle sessionToken) {
            final int uid = Binder.getCallingUid();
            final int pid = Binder.getCallingPid();
            final SessionToken2 token = SessionToken2.fromBundle(getContext(), sessionToken);
            if (token == null || token.getUid() != uid) {
                Log.w(TAG, "onSessionDestroyed failed, expected caller uid=" + token.getUid()
                        + " but from uid=" + uid);
            }
            if (DEBUG) {
                Log.d(TAG, "onSessionDestroyed " + token);
            }
            synchronized (mLock) {
                MediaSession2Record record = getSessionRecordLocked(
                        uid, token.getPackageName(), token.getId());
                if (record != null) {
                    record.onSessionDestroyed();
                } else {
                    if (DEBUG) {
                        Log.d(TAG, "Cannot find a session record to destroy. uid=" + uid
                                + ", pkg=" + token.getPackageName() + ", id=" + token.getId());
                    }
                }
                Log.d(TAG, "destroySession2 " + token);
            }
            destroySession2Internal(token);
        }

        // TODO(jaewan): Protect this API with permission
@@ -1594,19 +1556,15 @@ public class MediaSessionService extends SystemService implements Monitor {
                boolean sessionServiceOnly) throws RemoteException {
            List<Bundle> tokens = new ArrayList<>();
            synchronized (mLock) {
                for (int i = 0; i < mSessions.size(); i++) {
                    MediaSession2Record record = mSessions.get(i);
                    boolean isSessionService = (record.getToken().getType() != TYPE_SESSION);
                    boolean isActive = record.getController() != null;
                    if ((!activeSessionOnly && isSessionService)
                            || (!sessionServiceOnly && isActive)) {
                        SessionToken2 token = record.getToken();
                        if (token != null) {
                            tokens.add(token.toBundle());
                        } else {
                            Log.wtf(TAG, "Null token for record=" + record);
                        }
                for (Map.Entry<SessionToken2, MediaController2> record
                        : mSessionRecords.entrySet()) {
                    boolean isSessionService = (record.getKey().getType() != TYPE_SESSION);
                    boolean isActive = record.getValue() != null;
                    if ((activeSessionOnly && !isActive)
                            || (sessionServiceOnly && !isSessionService) ){
                        continue;
                    }
                    tokens.add(record.getKey().toBundle());
                }
            }
            return tokens;
@@ -2018,4 +1976,18 @@ public class MediaSessionService extends SystemService implements Monitor {
            obtainMessage(MSG_SESSIONS_CHANGED, userIdInteger).sendToTarget();
        }
    }

    private class ControllerCallback extends MediaController2.ControllerCallback {

        private final SessionToken2 mToken;

        ControllerCallback(SessionToken2 token) {
            mToken = token;
        }

        @Override
        public void onDisconnected() {
            destroySession2Internal(mToken);
        }
    };
}