1a8f951462791a16f47e8c07e552232f31dcefac5RoboErik/*
2a8f951462791a16f47e8c07e552232f31dcefac5RoboErik * Copyright (C) 2014 The Android Open Source Project
3a8f951462791a16f47e8c07e552232f31dcefac5RoboErik *
4a8f951462791a16f47e8c07e552232f31dcefac5RoboErik * Licensed under the Apache License, Version 2.0 (the "License");
5a8f951462791a16f47e8c07e552232f31dcefac5RoboErik * you may not use this file except in compliance with the License.
6a8f951462791a16f47e8c07e552232f31dcefac5RoboErik * You may obtain a copy of the License at
7a8f951462791a16f47e8c07e552232f31dcefac5RoboErik *
8a8f951462791a16f47e8c07e552232f31dcefac5RoboErik *      http://www.apache.org/licenses/LICENSE-2.0
9a8f951462791a16f47e8c07e552232f31dcefac5RoboErik *
10a8f951462791a16f47e8c07e552232f31dcefac5RoboErik * Unless required by applicable law or agreed to in writing, software
11a8f951462791a16f47e8c07e552232f31dcefac5RoboErik * distributed under the License is distributed on an "AS IS" BASIS,
12a8f951462791a16f47e8c07e552232f31dcefac5RoboErik * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13a8f951462791a16f47e8c07e552232f31dcefac5RoboErik * See the License for the specific language governing permissions and
14a8f951462791a16f47e8c07e552232f31dcefac5RoboErik * limitations under the License.
15a8f951462791a16f47e8c07e552232f31dcefac5RoboErik */
16a8f951462791a16f47e8c07e552232f31dcefac5RoboErik
17a8f951462791a16f47e8c07e552232f31dcefac5RoboErikpackage com.android.server.media;
18a8f951462791a16f47e8c07e552232f31dcefac5RoboErik
19d2b8c947ddfc6349a3ae6c3968b422b9cf50d7edRoboErikimport android.media.session.MediaController.PlaybackInfo;
2042ea7eecd149161ed192d3029f0d77d1d08a4aa5RoboErikimport android.media.session.MediaSession;
2192dea33bfebed04533264b06e036d04cc16b9608Jaewan Kimimport android.media.session.PlaybackState;
2292dea33bfebed04533264b06e036d04cc16b9608Jaewan Kimimport android.os.Debug;
23a5b02329209be355eafadbdf9ee685ffa58d3148RoboErikimport android.os.UserHandle;
2492dea33bfebed04533264b06e036d04cc16b9608Jaewan Kimimport android.util.IntArray;
2592dea33bfebed04533264b06e036d04cc16b9608Jaewan Kimimport android.util.Log;
26aceef919807a7a3205d09470f23b91c0aa41aa7bJaewan Kimimport android.util.SparseArray;
27a8f951462791a16f47e8c07e552232f31dcefac5RoboErik
28a8f951462791a16f47e8c07e552232f31dcefac5RoboErikimport java.io.PrintWriter;
29a8f951462791a16f47e8c07e552232f31dcefac5RoboErikimport java.util.ArrayList;
3030be970a8ecd984ace75354e00a8d969577d18e9Insun Kangimport java.util.List;
31a8f951462791a16f47e8c07e552232f31dcefac5RoboErik
32a8f951462791a16f47e8c07e552232f31dcefac5RoboErik/**
33a8f951462791a16f47e8c07e552232f31dcefac5RoboErik * Keeps track of media sessions and their priority for notifications, media
3401a500ed1c6ae3fff66678144ae637aa8cad0eccJeff Brown * button dispatch, etc.
35a7dce19b613b52894d36044ae6e88a57572d79e5Jaewan Kim * <p>This class isn't thread-safe. The caller should take care of the synchronization.
36a8f951462791a16f47e8c07e552232f31dcefac5RoboErik */
378f729087dfde6b1ceb6a451a5950e80176a7da2dJaewan Kimclass MediaSessionStack {
3892dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim    private static final boolean DEBUG = MediaSessionService.DEBUG;
3992dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim    private static final String TAG = "MediaSessionStack";
4092dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim
4192dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim    /**
42aceef919807a7a3205d09470f23b91c0aa41aa7bJaewan Kim     * Listen the change in the media button session.
4392dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim     */
4492dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim    interface OnMediaButtonSessionChangedListener {
4592dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim        /**
4692dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim         * Called when the media button session is changed.
4792dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim         */
4892dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim        void onMediaButtonSessionChanged(MediaSessionRecord oldMediaButtonSession,
4992dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim                MediaSessionRecord newMediaButtonSession);
5092dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim    }
5192dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim
52a8f951462791a16f47e8c07e552232f31dcefac5RoboErik    /**
53a8f951462791a16f47e8c07e552232f31dcefac5RoboErik     * These are states that usually indicate the user took an action and should
54a8f951462791a16f47e8c07e552232f31dcefac5RoboErik     * bump priority regardless of the old state.
55a8f951462791a16f47e8c07e552232f31dcefac5RoboErik     */
56a8f951462791a16f47e8c07e552232f31dcefac5RoboErik    private static final int[] ALWAYS_PRIORITY_STATES = {
5779fa4630bbca7c6c251eea99fe8997e4b45beceeRoboErik            PlaybackState.STATE_FAST_FORWARDING,
5879fa4630bbca7c6c251eea99fe8997e4b45beceeRoboErik            PlaybackState.STATE_REWINDING,
5979fa4630bbca7c6c251eea99fe8997e4b45beceeRoboErik            PlaybackState.STATE_SKIPPING_TO_PREVIOUS,
6079fa4630bbca7c6c251eea99fe8997e4b45beceeRoboErik            PlaybackState.STATE_SKIPPING_TO_NEXT };
61a8f951462791a16f47e8c07e552232f31dcefac5RoboErik    /**
62a8f951462791a16f47e8c07e552232f31dcefac5RoboErik     * These are states that usually indicate the user took an action if they
63a8f951462791a16f47e8c07e552232f31dcefac5RoboErik     * were entered from a non-priority state.
64a8f951462791a16f47e8c07e552232f31dcefac5RoboErik     */
65a8f951462791a16f47e8c07e552232f31dcefac5RoboErik    private static final int[] TRANSITION_PRIORITY_STATES = {
6679fa4630bbca7c6c251eea99fe8997e4b45beceeRoboErik            PlaybackState.STATE_BUFFERING,
6779fa4630bbca7c6c251eea99fe8997e4b45beceeRoboErik            PlaybackState.STATE_CONNECTING,
6879fa4630bbca7c6c251eea99fe8997e4b45beceeRoboErik            PlaybackState.STATE_PLAYING };
69a8f951462791a16f47e8c07e552232f31dcefac5RoboErik
7092dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim    /**
7192dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim     * Sorted list of the media sessions.
7292dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim     * The session of which PlaybackState is changed to ALWAYS_PRIORITY_STATES or
7392dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim     * TRANSITION_PRIORITY_STATES comes first.
7492dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim     * @see #shouldUpdatePriority
7592dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim     */
7692dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim    private final List<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
7792dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim
78875e697f9c6624f34851e09b4bf1ccef810f59e9Sungsoo Lim    private final AudioPlayerStateMonitor mAudioPlayerStateMonitor;
7992dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim    private final OnMediaButtonSessionChangedListener mOnMediaButtonSessionChangedListener;
8092dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim
8192dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim    /**
8292dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim     * The media button session which receives media key events.
8392dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim     * It could be null if the previous media buttion session is released.
8492dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim     */
8592dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim    private MediaSessionRecord mMediaButtonSession;
86a8f951462791a16f47e8c07e552232f31dcefac5RoboErik
87b69ffd4dc2c8fa85e0064151141ebeee90de471eRoboErik    private MediaSessionRecord mCachedVolumeDefault;
88aceef919807a7a3205d09470f23b91c0aa41aa7bJaewan Kim
89aceef919807a7a3205d09470f23b91c0aa41aa7bJaewan Kim    /**
90aceef919807a7a3205d09470f23b91c0aa41aa7bJaewan Kim     * Cache the result of the {@link #getActiveSessions} per user.
91aceef919807a7a3205d09470f23b91c0aa41aa7bJaewan Kim     */
92aceef919807a7a3205d09470f23b91c0aa41aa7bJaewan Kim    private final SparseArray<ArrayList<MediaSessionRecord>> mCachedActiveLists =
93aceef919807a7a3205d09470f23b91c0aa41aa7bJaewan Kim            new SparseArray<>();
94a8f951462791a16f47e8c07e552232f31dcefac5RoboErik
95875e697f9c6624f34851e09b4bf1ccef810f59e9Sungsoo Lim    MediaSessionStack(AudioPlayerStateMonitor monitor, OnMediaButtonSessionChangedListener listener) {
96875e697f9c6624f34851e09b4bf1ccef810f59e9Sungsoo Lim        mAudioPlayerStateMonitor = monitor;
9792dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim        mOnMediaButtonSessionChangedListener = listener;
9830be970a8ecd984ace75354e00a8d969577d18e9Insun Kang    }
9930be970a8ecd984ace75354e00a8d969577d18e9Insun Kang
10030be970a8ecd984ace75354e00a8d969577d18e9Insun Kang    /**
101a8f951462791a16f47e8c07e552232f31dcefac5RoboErik     * Add a record to the priority tracker.
102a8f951462791a16f47e8c07e552232f31dcefac5RoboErik     *
103a8f951462791a16f47e8c07e552232f31dcefac5RoboErik     * @param record The record to add.
104a8f951462791a16f47e8c07e552232f31dcefac5RoboErik     */
10592dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim    public void addSession(MediaSessionRecord record) {
106a8f951462791a16f47e8c07e552232f31dcefac5RoboErik        mSessions.add(record);
107aceef919807a7a3205d09470f23b91c0aa41aa7bJaewan Kim        clearCache(record.getUserId());
10892dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim
10992dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim        // Update the media button session.
11092dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim        // The added session could be the session from the package with the audio playback.
11192dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim        // This can happen if an app starts audio playback before creating media session.
11292dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim        updateMediaButtonSessionIfNeeded();
113a8f951462791a16f47e8c07e552232f31dcefac5RoboErik    }
114a8f951462791a16f47e8c07e552232f31dcefac5RoboErik
115a8f951462791a16f47e8c07e552232f31dcefac5RoboErik    /**
116a8f951462791a16f47e8c07e552232f31dcefac5RoboErik     * Remove a record from the priority tracker.
117a8f951462791a16f47e8c07e552232f31dcefac5RoboErik     *
118a8f951462791a16f47e8c07e552232f31dcefac5RoboErik     * @param record The record to remove.
119a8f951462791a16f47e8c07e552232f31dcefac5RoboErik     */
120a8f951462791a16f47e8c07e552232f31dcefac5RoboErik    public void removeSession(MediaSessionRecord record) {
121a8f951462791a16f47e8c07e552232f31dcefac5RoboErik        mSessions.remove(record);
12292dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim        if (mMediaButtonSession == record) {
123fc1f7ab8ad2f6c01b804cdf41097766a30c89ca9Jaewan Kim            // When the media button session is removed, nullify the media button session and do not
124fc1f7ab8ad2f6c01b804cdf41097766a30c89ca9Jaewan Kim            // search for the alternative media session within the app. It's because the alternative
125fc1f7ab8ad2f6c01b804cdf41097766a30c89ca9Jaewan Kim            // media session might be a dummy which isn't able to handle the media key events.
12698e4aafc8a5a7c39c538f504e59c3c2b5cbb8445Jaewan Kim            updateMediaButtonSession(null);
12792dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim        }
128aceef919807a7a3205d09470f23b91c0aa41aa7bJaewan Kim        clearCache(record.getUserId());
129a8f951462791a16f47e8c07e552232f31dcefac5RoboErik    }
130a8f951462791a16f47e8c07e552232f31dcefac5RoboErik
131a8f951462791a16f47e8c07e552232f31dcefac5RoboErik    /**
132a7dce19b613b52894d36044ae6e88a57572d79e5Jaewan Kim     * Return if the record exists in the priority tracker.
133a7dce19b613b52894d36044ae6e88a57572d79e5Jaewan Kim     */
134a7dce19b613b52894d36044ae6e88a57572d79e5Jaewan Kim    public boolean contains(MediaSessionRecord record) {
135a7dce19b613b52894d36044ae6e88a57572d79e5Jaewan Kim        return mSessions.contains(record);
136a7dce19b613b52894d36044ae6e88a57572d79e5Jaewan Kim    }
137a7dce19b613b52894d36044ae6e88a57572d79e5Jaewan Kim
138a7dce19b613b52894d36044ae6e88a57572d79e5Jaewan Kim    /**
13992dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim     * Notify the priority tracker that a session's playback state changed.
140a8f951462791a16f47e8c07e552232f31dcefac5RoboErik     *
141a8f951462791a16f47e8c07e552232f31dcefac5RoboErik     * @param record The record that changed.
142a8f951462791a16f47e8c07e552232f31dcefac5RoboErik     * @param oldState Its old playback state.
143a8f951462791a16f47e8c07e552232f31dcefac5RoboErik     * @param newState Its new playback state.
144a8f951462791a16f47e8c07e552232f31dcefac5RoboErik     */
14592dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim    public void onPlaystateChanged(MediaSessionRecord record, int oldState, int newState) {
146a8f951462791a16f47e8c07e552232f31dcefac5RoboErik        if (shouldUpdatePriority(oldState, newState)) {
147a8f951462791a16f47e8c07e552232f31dcefac5RoboErik            mSessions.remove(record);
148a8f951462791a16f47e8c07e552232f31dcefac5RoboErik            mSessions.add(0, record);
149aceef919807a7a3205d09470f23b91c0aa41aa7bJaewan Kim            clearCache(record.getUserId());
15023b113592a5f461ec66026cbf8bce253cb8d3a46RoboErik        } else if (!MediaSession.isActiveState(newState)) {
15123b113592a5f461ec66026cbf8bce253cb8d3a46RoboErik            // Just clear the volume cache when a state goes inactive
152b69ffd4dc2c8fa85e0064151141ebeee90de471eRoboErik            mCachedVolumeDefault = null;
153a8f951462791a16f47e8c07e552232f31dcefac5RoboErik        }
15492dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim
15539f479fe7e35028663e9663a9684a5d3600e8198Sungsoo        // In most cases, playback state isn't needed for finding media button session,
15692dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim        // but we only use it as a hint if an app has multiple local media sessions.
15792dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim        // In that case, we pick the media session whose PlaybackState matches
15892dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim        // the audio playback configuration.
15992dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim        if (mMediaButtonSession != null && mMediaButtonSession.getUid() == record.getUid()) {
160fc1f7ab8ad2f6c01b804cdf41097766a30c89ca9Jaewan Kim            MediaSessionRecord newMediaButtonSession =
161fc1f7ab8ad2f6c01b804cdf41097766a30c89ca9Jaewan Kim                    findMediaButtonSession(mMediaButtonSession.getUid());
162fc1f7ab8ad2f6c01b804cdf41097766a30c89ca9Jaewan Kim            if (newMediaButtonSession != mMediaButtonSession) {
16398e4aafc8a5a7c39c538f504e59c3c2b5cbb8445Jaewan Kim                updateMediaButtonSession(newMediaButtonSession);
164fc1f7ab8ad2f6c01b804cdf41097766a30c89ca9Jaewan Kim            }
16592dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim        }
166a8f951462791a16f47e8c07e552232f31dcefac5RoboErik    }
167a8f951462791a16f47e8c07e552232f31dcefac5RoboErik
168a8f951462791a16f47e8c07e552232f31dcefac5RoboErik    /**
16992dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim     * Handle the change in activeness for a session.
170a8f951462791a16f47e8c07e552232f31dcefac5RoboErik     *
171a8f951462791a16f47e8c07e552232f31dcefac5RoboErik     * @param record The record that changed.
172a8f951462791a16f47e8c07e552232f31dcefac5RoboErik     */
173a8f951462791a16f47e8c07e552232f31dcefac5RoboErik    public void onSessionStateChange(MediaSessionRecord record) {
174a8f951462791a16f47e8c07e552232f31dcefac5RoboErik        // For now just clear the cache. Eventually we'll selectively clear
175a8f951462791a16f47e8c07e552232f31dcefac5RoboErik        // depending on what changed.
176aceef919807a7a3205d09470f23b91c0aa41aa7bJaewan Kim        clearCache(record.getUserId());
177a8f951462791a16f47e8c07e552232f31dcefac5RoboErik    }
178a8f951462791a16f47e8c07e552232f31dcefac5RoboErik
179a8f951462791a16f47e8c07e552232f31dcefac5RoboErik    /**
18092dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim     * Update the media button session if needed.
18192dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim     * <p>The media button session is the session that will receive the media button events.
18292dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim     * <p>We send the media button events to the lastly played app. If the app has the media
18392dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim     * session, the session will receive the media button events.
18492dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim     */
18592dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim    public void updateMediaButtonSessionIfNeeded() {
18692dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim        if (DEBUG) {
18792dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim            Log.d(TAG, "updateMediaButtonSessionIfNeeded, callers=" + Debug.getCallers(2));
18892dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim        }
189875e697f9c6624f34851e09b4bf1ccef810f59e9Sungsoo Lim        IntArray audioPlaybackUids = mAudioPlayerStateMonitor.getSortedAudioPlaybackClientUids();
19092dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim        for (int i = 0; i < audioPlaybackUids.size(); i++) {
19192dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim            MediaSessionRecord mediaButtonSession =
19292dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim                    findMediaButtonSession(audioPlaybackUids.get(i));
19392dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim            if (mediaButtonSession != null) {
19492dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim                // Found the media button session.
195875e697f9c6624f34851e09b4bf1ccef810f59e9Sungsoo Lim                mAudioPlayerStateMonitor.cleanUpAudioPlaybackUids(mediaButtonSession.getUid());
19692dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim                if (mMediaButtonSession != mediaButtonSession) {
19798e4aafc8a5a7c39c538f504e59c3c2b5cbb8445Jaewan Kim                    updateMediaButtonSession(mediaButtonSession);
19892dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim                }
19992dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim                return;
20092dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim            }
20192dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim        }
20292dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim    }
20392dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim
20492dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim    /**
20592dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim     * Find the media button session with the given {@param uid}.
20639f479fe7e35028663e9663a9684a5d3600e8198Sungsoo     * If the app has multiple media sessions, the media session whose playback state is not null
20739f479fe7e35028663e9663a9684a5d3600e8198Sungsoo     * and matches the audio playback state becomes the media button session. Otherwise the top
20839f479fe7e35028663e9663a9684a5d3600e8198Sungsoo     * priority session becomes the media button session.
20992dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim     *
21092dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim     * @return The media button session. Returns {@code null} if the app doesn't have a media
21192dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim     *   session.
21292dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim     */
21392dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim    private MediaSessionRecord findMediaButtonSession(int uid) {
21492dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim        MediaSessionRecord mediaButtonSession = null;
21592dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim        for (MediaSessionRecord session : mSessions) {
216745e28cb0b52eaf93da752af2b70062f5ff3ca89Jaewan Kim            if (uid == session.getUid()) {
21739f479fe7e35028663e9663a9684a5d3600e8198Sungsoo                if (session.getPlaybackState() != null && session.isPlaybackActive() ==
218875e697f9c6624f34851e09b4bf1ccef810f59e9Sungsoo Lim                        mAudioPlayerStateMonitor.isPlaybackActive(session.getUid())) {
21992dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim                    // If there's a media session whose PlaybackState matches
22092dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim                    // the audio playback state, return it immediately.
22192dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim                    return session;
22292dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim                }
22392dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim                if (mediaButtonSession == null) {
22492dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim                    // Among the media sessions whose PlaybackState doesn't match
22592dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim                    // the audio playback state, pick the top priority.
22692dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim                    mediaButtonSession = session;
22792dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim                }
22892dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim            }
22992dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim        }
23092dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim        return mediaButtonSession;
23192dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim    }
23292dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim
23392dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim    /**
234a8f951462791a16f47e8c07e552232f31dcefac5RoboErik     * Get the current priority sorted list of active sessions. The most
235a8f951462791a16f47e8c07e552232f31dcefac5RoboErik     * important session is at index 0 and the least important at size - 1.
236a8f951462791a16f47e8c07e552232f31dcefac5RoboErik     *
237aceef919807a7a3205d09470f23b91c0aa41aa7bJaewan Kim     * @param userId The user to check. It can be {@link UserHandle#USER_ALL} to get all sessions
238aceef919807a7a3205d09470f23b91c0aa41aa7bJaewan Kim     *    for all users in this {@link MediaSessionStack}.
239a8f951462791a16f47e8c07e552232f31dcefac5RoboErik     * @return All the active sessions in priority order.
240a8f951462791a16f47e8c07e552232f31dcefac5RoboErik     */
241a5b02329209be355eafadbdf9ee685ffa58d3148RoboErik    public ArrayList<MediaSessionRecord> getActiveSessions(int userId) {
242aceef919807a7a3205d09470f23b91c0aa41aa7bJaewan Kim        ArrayList<MediaSessionRecord> cachedActiveList = mCachedActiveLists.get(userId);
243aceef919807a7a3205d09470f23b91c0aa41aa7bJaewan Kim        if (cachedActiveList == null) {
244aceef919807a7a3205d09470f23b91c0aa41aa7bJaewan Kim            cachedActiveList = getPriorityList(true, userId);
245aceef919807a7a3205d09470f23b91c0aa41aa7bJaewan Kim            mCachedActiveLists.put(userId, cachedActiveList);
246a8f951462791a16f47e8c07e552232f31dcefac5RoboErik        }
247aceef919807a7a3205d09470f23b91c0aa41aa7bJaewan Kim        return cachedActiveList;
248a8f951462791a16f47e8c07e552232f31dcefac5RoboErik    }
249a8f951462791a16f47e8c07e552232f31dcefac5RoboErik
250a8f951462791a16f47e8c07e552232f31dcefac5RoboErik    /**
25192dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim     * Get the media button session which receives the media button events.
252a8f951462791a16f47e8c07e552232f31dcefac5RoboErik     *
25392dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim     * @return The media button session or null.
254a8f951462791a16f47e8c07e552232f31dcefac5RoboErik     */
25592dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim    public MediaSessionRecord getMediaButtonSession() {
25692dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim        return mMediaButtonSession;
257a8f951462791a16f47e8c07e552232f31dcefac5RoboErik    }
258a8f951462791a16f47e8c07e552232f31dcefac5RoboErik
25998e4aafc8a5a7c39c538f504e59c3c2b5cbb8445Jaewan Kim    private void updateMediaButtonSession(MediaSessionRecord newMediaButtonSession) {
26098e4aafc8a5a7c39c538f504e59c3c2b5cbb8445Jaewan Kim        MediaSessionRecord oldMediaButtonSession = mMediaButtonSession;
26198e4aafc8a5a7c39c538f504e59c3c2b5cbb8445Jaewan Kim        mMediaButtonSession = newMediaButtonSession;
26298e4aafc8a5a7c39c538f504e59c3c2b5cbb8445Jaewan Kim        mOnMediaButtonSessionChangedListener.onMediaButtonSessionChanged(
26398e4aafc8a5a7c39c538f504e59c3c2b5cbb8445Jaewan Kim                oldMediaButtonSession, newMediaButtonSession);
26498e4aafc8a5a7c39c538f504e59c3c2b5cbb8445Jaewan Kim    }
26598e4aafc8a5a7c39c538f504e59c3c2b5cbb8445Jaewan Kim
266a7dce19b613b52894d36044ae6e88a57572d79e5Jaewan Kim    public MediaSessionRecord getDefaultVolumeSession() {
267b69ffd4dc2c8fa85e0064151141ebeee90de471eRoboErik        if (mCachedVolumeDefault != null) {
268b69ffd4dc2c8fa85e0064151141ebeee90de471eRoboErik            return mCachedVolumeDefault;
269b69ffd4dc2c8fa85e0064151141ebeee90de471eRoboErik        }
27092dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim        ArrayList<MediaSessionRecord> records = getPriorityList(true, UserHandle.USER_ALL);
271b69ffd4dc2c8fa85e0064151141ebeee90de471eRoboErik        int size = records.size();
272b69ffd4dc2c8fa85e0064151141ebeee90de471eRoboErik        for (int i = 0; i < size; i++) {
273b69ffd4dc2c8fa85e0064151141ebeee90de471eRoboErik            MediaSessionRecord record = records.get(i);
27492dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim            if (record.isPlaybackActive()) {
275b69ffd4dc2c8fa85e0064151141ebeee90de471eRoboErik                mCachedVolumeDefault = record;
276b69ffd4dc2c8fa85e0064151141ebeee90de471eRoboErik                return record;
277b69ffd4dc2c8fa85e0064151141ebeee90de471eRoboErik            }
278b69ffd4dc2c8fa85e0064151141ebeee90de471eRoboErik        }
279b69ffd4dc2c8fa85e0064151141ebeee90de471eRoboErik        return null;
280b69ffd4dc2c8fa85e0064151141ebeee90de471eRoboErik    }
281b69ffd4dc2c8fa85e0064151141ebeee90de471eRoboErik
28219c9518f6a817d53d5234de0020313cab6950b2fRoboErik    public MediaSessionRecord getDefaultRemoteSession(int userId) {
28392dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim        ArrayList<MediaSessionRecord> records = getPriorityList(true, userId);
28419c9518f6a817d53d5234de0020313cab6950b2fRoboErik
28519c9518f6a817d53d5234de0020313cab6950b2fRoboErik        int size = records.size();
28619c9518f6a817d53d5234de0020313cab6950b2fRoboErik        for (int i = 0; i < size; i++) {
28719c9518f6a817d53d5234de0020313cab6950b2fRoboErik            MediaSessionRecord record = records.get(i);
288d2b8c947ddfc6349a3ae6c3968b422b9cf50d7edRoboErik            if (record.getPlaybackType() == PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
28919c9518f6a817d53d5234de0020313cab6950b2fRoboErik                return record;
29019c9518f6a817d53d5234de0020313cab6950b2fRoboErik            }
29119c9518f6a817d53d5234de0020313cab6950b2fRoboErik        }
29219c9518f6a817d53d5234de0020313cab6950b2fRoboErik        return null;
29319c9518f6a817d53d5234de0020313cab6950b2fRoboErik    }
29419c9518f6a817d53d5234de0020313cab6950b2fRoboErik
295a5b02329209be355eafadbdf9ee685ffa58d3148RoboErik    public void dump(PrintWriter pw, String prefix) {
29692dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim        ArrayList<MediaSessionRecord> sortedSessions = getPriorityList(false,
297a5b02329209be355eafadbdf9ee685ffa58d3148RoboErik                UserHandle.USER_ALL);
298a8f951462791a16f47e8c07e552232f31dcefac5RoboErik        int count = sortedSessions.size();
29992dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim        pw.println(prefix + "Media button session is " + mMediaButtonSession);
300a8f951462791a16f47e8c07e552232f31dcefac5RoboErik        pw.println(prefix + "Sessions Stack - have " + count + " sessions:");
301a8f951462791a16f47e8c07e552232f31dcefac5RoboErik        String indent = prefix + "  ";
302a8f951462791a16f47e8c07e552232f31dcefac5RoboErik        for (int i = 0; i < count; i++) {
303a8f951462791a16f47e8c07e552232f31dcefac5RoboErik            MediaSessionRecord record = sortedSessions.get(i);
304a8f951462791a16f47e8c07e552232f31dcefac5RoboErik            record.dump(pw, indent);
305a8f951462791a16f47e8c07e552232f31dcefac5RoboErik            pw.println();
306a8f951462791a16f47e8c07e552232f31dcefac5RoboErik        }
307a8f951462791a16f47e8c07e552232f31dcefac5RoboErik    }
308a8f951462791a16f47e8c07e552232f31dcefac5RoboErik
309a8f951462791a16f47e8c07e552232f31dcefac5RoboErik    /**
310a8f951462791a16f47e8c07e552232f31dcefac5RoboErik     * Get a priority sorted list of sessions. Can filter to only return active
31192dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim     * sessions or sessions.
31292dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim     * <p>Here's the priority order.
31392dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim     * <li>Active sessions whose PlaybackState is active</li>
31492dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim     * <li>Active sessions whose PlaybackState is inactive</li>
31592dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim     * <li>Inactive sessions</li>
316a8f951462791a16f47e8c07e552232f31dcefac5RoboErik     *
317a8f951462791a16f47e8c07e552232f31dcefac5RoboErik     * @param activeOnly True to only return active sessions, false to return
318a8f951462791a16f47e8c07e552232f31dcefac5RoboErik     *            all sessions.
319a7dce19b613b52894d36044ae6e88a57572d79e5Jaewan Kim     * @param userId The user to get sessions for. {@link UserHandle#USER_ALL}
320a5b02329209be355eafadbdf9ee685ffa58d3148RoboErik     *            will return sessions for all users.
321a8f951462791a16f47e8c07e552232f31dcefac5RoboErik     * @return The priority sorted list of sessions.
322a8f951462791a16f47e8c07e552232f31dcefac5RoboErik     */
32392dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim    public ArrayList<MediaSessionRecord> getPriorityList(boolean activeOnly, int userId) {
324a8f951462791a16f47e8c07e552232f31dcefac5RoboErik        ArrayList<MediaSessionRecord> result = new ArrayList<MediaSessionRecord>();
32592dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim        int lastPlaybackActiveIndex = 0;
326a8f951462791a16f47e8c07e552232f31dcefac5RoboErik        int lastActiveIndex = 0;
327a8f951462791a16f47e8c07e552232f31dcefac5RoboErik
328a8f951462791a16f47e8c07e552232f31dcefac5RoboErik        int size = mSessions.size();
329a8f951462791a16f47e8c07e552232f31dcefac5RoboErik        for (int i = 0; i < size; i++) {
330a8f951462791a16f47e8c07e552232f31dcefac5RoboErik            final MediaSessionRecord session = mSessions.get(i);
331a8f951462791a16f47e8c07e552232f31dcefac5RoboErik
332a7dce19b613b52894d36044ae6e88a57572d79e5Jaewan Kim            if (userId != UserHandle.USER_ALL && userId != session.getUserId()) {
333a5b02329209be355eafadbdf9ee685ffa58d3148RoboErik                // Filter out sessions for the wrong user
334a5b02329209be355eafadbdf9ee685ffa58d3148RoboErik                continue;
335a5b02329209be355eafadbdf9ee685ffa58d3148RoboErik            }
33692dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim
337a8f951462791a16f47e8c07e552232f31dcefac5RoboErik            if (!session.isActive()) {
338a8f951462791a16f47e8c07e552232f31dcefac5RoboErik                if (!activeOnly) {
339a8f951462791a16f47e8c07e552232f31dcefac5RoboErik                    // If we're getting unpublished as well always put them at
340a8f951462791a16f47e8c07e552232f31dcefac5RoboErik                    // the end
341a8f951462791a16f47e8c07e552232f31dcefac5RoboErik                    result.add(session);
342a8f951462791a16f47e8c07e552232f31dcefac5RoboErik                }
343a8f951462791a16f47e8c07e552232f31dcefac5RoboErik                continue;
344a8f951462791a16f47e8c07e552232f31dcefac5RoboErik            }
345a8f951462791a16f47e8c07e552232f31dcefac5RoboErik
346101b4d5e74f5840a9ac3a6cbfa9f62f2ce048a06Jaewan Kim            if (session.isPlaybackActive()) {
34792dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim                result.add(lastPlaybackActiveIndex++, session);
348a8f951462791a16f47e8c07e552232f31dcefac5RoboErik                lastActiveIndex++;
349a8f951462791a16f47e8c07e552232f31dcefac5RoboErik            } else {
35092dea33bfebed04533264b06e036d04cc16b9608Jaewan Kim                result.add(lastActiveIndex++, session);
351a8f951462791a16f47e8c07e552232f31dcefac5RoboErik            }
352a8f951462791a16f47e8c07e552232f31dcefac5RoboErik        }
353a8f951462791a16f47e8c07e552232f31dcefac5RoboErik
354a8f951462791a16f47e8c07e552232f31dcefac5RoboErik        return result;
355a8f951462791a16f47e8c07e552232f31dcefac5RoboErik    }
356a8f951462791a16f47e8c07e552232f31dcefac5RoboErik
357a8f951462791a16f47e8c07e552232f31dcefac5RoboErik    private boolean shouldUpdatePriority(int oldState, int newState) {
358a8f951462791a16f47e8c07e552232f31dcefac5RoboErik        if (containsState(newState, ALWAYS_PRIORITY_STATES)) {
359a8f951462791a16f47e8c07e552232f31dcefac5RoboErik            return true;
360a8f951462791a16f47e8c07e552232f31dcefac5RoboErik        }
361a8f951462791a16f47e8c07e552232f31dcefac5RoboErik        if (!containsState(oldState, TRANSITION_PRIORITY_STATES)
362a8f951462791a16f47e8c07e552232f31dcefac5RoboErik                && containsState(newState, TRANSITION_PRIORITY_STATES)) {
363a8f951462791a16f47e8c07e552232f31dcefac5RoboErik            return true;
364a8f951462791a16f47e8c07e552232f31dcefac5RoboErik        }
365a8f951462791a16f47e8c07e552232f31dcefac5RoboErik        return false;
366a8f951462791a16f47e8c07e552232f31dcefac5RoboErik    }
367a8f951462791a16f47e8c07e552232f31dcefac5RoboErik
368a8f951462791a16f47e8c07e552232f31dcefac5RoboErik    private boolean containsState(int state, int[] states) {
369a8f951462791a16f47e8c07e552232f31dcefac5RoboErik        for (int i = 0; i < states.length; i++) {
370a8f951462791a16f47e8c07e552232f31dcefac5RoboErik            if (states[i] == state) {
371a8f951462791a16f47e8c07e552232f31dcefac5RoboErik                return true;
372a8f951462791a16f47e8c07e552232f31dcefac5RoboErik            }
373a8f951462791a16f47e8c07e552232f31dcefac5RoboErik        }
374a8f951462791a16f47e8c07e552232f31dcefac5RoboErik        return false;
375a8f951462791a16f47e8c07e552232f31dcefac5RoboErik    }
376a8f951462791a16f47e8c07e552232f31dcefac5RoboErik
377aceef919807a7a3205d09470f23b91c0aa41aa7bJaewan Kim    private void clearCache(int userId) {
378b69ffd4dc2c8fa85e0064151141ebeee90de471eRoboErik        mCachedVolumeDefault = null;
379aceef919807a7a3205d09470f23b91c0aa41aa7bJaewan Kim        mCachedActiveLists.remove(userId);
380aceef919807a7a3205d09470f23b91c0aa41aa7bJaewan Kim        // mCachedActiveLists may also include the list of sessions for UserHandle.USER_ALL,
381aceef919807a7a3205d09470f23b91c0aa41aa7bJaewan Kim        // so they also need to be cleared.
382aceef919807a7a3205d09470f23b91c0aa41aa7bJaewan Kim        mCachedActiveLists.remove(UserHandle.USER_ALL);
383a8f951462791a16f47e8c07e552232f31dcefac5RoboErik    }
384a8f951462791a16f47e8c07e552232f31dcefac5RoboErik}
385