MediaSessionStack.java revision a8f951462791a16f47e8c07e552232f31dcefac5
1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.media;
18
19import android.media.session.PlaybackState;
20import android.media.session.Session;
21import android.text.TextUtils;
22
23import java.io.PrintWriter;
24import java.util.ArrayList;
25
26/**
27 * Keeps track of media sessions and their priority for notifications, media
28 * button routing, etc.
29 */
30public class MediaSessionStack {
31    /**
32     * These are states that usually indicate the user took an action and should
33     * bump priority regardless of the old state.
34     */
35    private static final int[] ALWAYS_PRIORITY_STATES = {
36            PlaybackState.PLAYSTATE_FAST_FORWARDING,
37            PlaybackState.PLAYSTATE_REWINDING,
38            PlaybackState.PLAYSTATE_SKIPPING_BACKWARDS,
39            PlaybackState.PLAYSTATE_SKIPPING_FORWARDS };
40    /**
41     * These are states that usually indicate the user took an action if they
42     * were entered from a non-priority state.
43     */
44    private static final int[] TRANSITION_PRIORITY_STATES = {
45            PlaybackState.PLAYSTATE_BUFFERING,
46            PlaybackState.PLAYSTATE_CONNECTING,
47            PlaybackState.PLAYSTATE_PLAYING };
48
49    private final ArrayList<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
50
51    private MediaSessionRecord mCachedButtonReceiver;
52    private MediaSessionRecord mCachedDefault;
53    private ArrayList<MediaSessionRecord> mCachedActiveList;
54    private ArrayList<MediaSessionRecord> mCachedTransportControlList;
55
56    /**
57     * Add a record to the priority tracker.
58     *
59     * @param record The record to add.
60     */
61    public void addSession(MediaSessionRecord record) {
62        mSessions.add(record);
63        clearCache();
64    }
65
66    /**
67     * Remove a record from the priority tracker.
68     *
69     * @param record The record to remove.
70     */
71    public void removeSession(MediaSessionRecord record) {
72        mSessions.remove(record);
73        clearCache();
74    }
75
76    /**
77     * Notify the priority tracker that a session's state changed.
78     *
79     * @param record The record that changed.
80     * @param oldState Its old playback state.
81     * @param newState Its new playback state.
82     */
83    public void onPlaystateChange(MediaSessionRecord record, int oldState, int newState) {
84        if (shouldUpdatePriority(oldState, newState)) {
85            mSessions.remove(record);
86            mSessions.add(0, record);
87            clearCache();
88        }
89    }
90
91    /**
92     * Handle any stack changes that need to occur in response to a session
93     * state change. TODO add the old and new session state as params
94     *
95     * @param record The record that changed.
96     */
97    public void onSessionStateChange(MediaSessionRecord record) {
98        // For now just clear the cache. Eventually we'll selectively clear
99        // depending on what changed.
100        clearCache();
101    }
102
103    /**
104     * Get the current priority sorted list of active sessions. The most
105     * important session is at index 0 and the least important at size - 1.
106     *
107     * @return All the active sessions in priority order.
108     */
109    public ArrayList<MediaSessionRecord> getActiveSessions() {
110        if (mCachedActiveList == null) {
111            mCachedActiveList = getPriorityListLocked(true, 0);
112        }
113        return mCachedActiveList;
114    }
115
116    /**
117     * Get the current priority sorted list of active sessions that use
118     * transport controls. The most important session is at index 0 and the
119     * least important at size -1.
120     *
121     * @return All the active sessions that handle transport controls in
122     *         priority order.
123     */
124    public ArrayList<MediaSessionRecord> getTransportControlSessions() {
125        if (mCachedTransportControlList == null) {
126            mCachedTransportControlList = getPriorityListLocked(true,
127                    Session.FLAG_HANDLES_TRANSPORT_CONTROLS);
128        }
129        return mCachedTransportControlList;
130    }
131
132    /**
133     * Get the highest priority active session.
134     *
135     * @return The current highest priority session or null.
136     */
137    public MediaSessionRecord getDefaultSession() {
138        if (mCachedDefault != null) {
139            return mCachedDefault;
140        }
141        ArrayList<MediaSessionRecord> records = getPriorityListLocked(true, 0);
142        if (records.size() > 0) {
143            return records.get(0);
144        }
145        return null;
146    }
147
148    /**
149     * Get the highest priority session that can handle media buttons.
150     *
151     * @return The default media button session or null.
152     */
153    public MediaSessionRecord getDefaultMediaButtonSession() {
154        if (mCachedButtonReceiver != null) {
155            return mCachedButtonReceiver;
156        }
157        ArrayList<MediaSessionRecord> records = getPriorityListLocked(true,
158                Session.FLAG_HANDLES_MEDIA_BUTTONS);
159        if (records.size() > 0) {
160            mCachedButtonReceiver = records.get(0);
161        }
162        return mCachedButtonReceiver;
163    }
164
165    public void dumpLocked(PrintWriter pw, String prefix) {
166        ArrayList<MediaSessionRecord> sortedSessions = getPriorityListLocked(false, 0);
167        int count = sortedSessions.size();
168        pw.println(prefix + "Sessions Stack - have " + count + " sessions:");
169        String indent = prefix + "  ";
170        for (int i = 0; i < count; i++) {
171            MediaSessionRecord record = sortedSessions.get(i);
172            record.dump(pw, indent);
173            pw.println();
174        }
175    }
176
177    /**
178     * Get a priority sorted list of sessions. Can filter to only return active
179     * sessions or sessions with specific flags.
180     *
181     * @param activeOnly True to only return active sessions, false to return
182     *            all sessions.
183     * @param withFlags Only return sessions with all the specified flags set. 0
184     *            returns all sessions.
185     * @return The priority sorted list of sessions.
186     */
187    private ArrayList<MediaSessionRecord> getPriorityListLocked(boolean activeOnly, int withFlags) {
188        ArrayList<MediaSessionRecord> result = new ArrayList<MediaSessionRecord>();
189        int lastLocalIndex = 0;
190        int lastActiveIndex = 0;
191        int lastPublishedIndex = 0;
192
193        int size = mSessions.size();
194        for (int i = 0; i < size; i++) {
195            final MediaSessionRecord session = mSessions.get(i);
196
197            if ((session.getFlags() & withFlags) != withFlags) {
198                continue;
199            }
200            if (!session.isActive()) {
201                if (!activeOnly) {
202                    // If we're getting unpublished as well always put them at
203                    // the end
204                    result.add(session);
205                }
206                continue;
207            }
208
209            if (session.isSystemPriority()) {
210                // System priority sessions are special and always go at the
211                // front. We expect there to only be one of these at a time.
212                result.add(0, session);
213                lastLocalIndex++;
214                lastActiveIndex++;
215                lastPublishedIndex++;
216            } else if (session.isPlaybackActive()) {
217                // TODO replace getRoute() == null with real local route check
218                if(session.getRoute() == null) {
219                    // Active local sessions get top priority
220                    result.add(lastLocalIndex, session);
221                    lastLocalIndex++;
222                    lastActiveIndex++;
223                    lastPublishedIndex++;
224                } else {
225                    // Then active remote sessions
226                    result.add(lastActiveIndex, session);
227                    lastActiveIndex++;
228                    lastPublishedIndex++;
229                }
230            } else {
231                // inactive sessions go at the end in order of whoever last did
232                // something.
233                result.add(lastPublishedIndex, session);
234                lastPublishedIndex++;
235            }
236        }
237
238        return result;
239    }
240
241    private boolean shouldUpdatePriority(int oldState, int newState) {
242        if (containsState(newState, ALWAYS_PRIORITY_STATES)) {
243            return true;
244        }
245        if (!containsState(oldState, TRANSITION_PRIORITY_STATES)
246                && containsState(newState, TRANSITION_PRIORITY_STATES)) {
247            return true;
248        }
249        return false;
250    }
251
252    private boolean containsState(int state, int[] states) {
253        for (int i = 0; i < states.length; i++) {
254            if (states[i] == state) {
255                return true;
256            }
257        }
258        return false;
259    }
260
261    private void clearCache() {
262        mCachedDefault = null;
263        mCachedButtonReceiver = null;
264        mCachedActiveList = null;
265        mCachedTransportControlList = null;
266    }
267}
268