MediaSessionStack.java revision 42ea7eecd149161ed192d3029f0d77d1d08a4aa5
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.MediaSession;
21import android.os.UserHandle;
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 mGlobalPrioritySession;
52
53    private MediaSessionRecord mCachedButtonReceiver;
54    private MediaSessionRecord mCachedDefault;
55    private ArrayList<MediaSessionRecord> mCachedActiveList;
56    private ArrayList<MediaSessionRecord> mCachedTransportControlList;
57
58    /**
59     * Add a record to the priority tracker.
60     *
61     * @param record The record to add.
62     */
63    public void addSession(MediaSessionRecord record) {
64        mSessions.add(record);
65        if ((record.getFlags() & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) {
66            mGlobalPrioritySession = record;
67        }
68        clearCache();
69    }
70
71    /**
72     * Remove a record from the priority tracker.
73     *
74     * @param record The record to remove.
75     */
76    public void removeSession(MediaSessionRecord record) {
77        mSessions.remove(record);
78        if (record == mGlobalPrioritySession) {
79            mGlobalPrioritySession = null;
80        }
81        clearCache();
82    }
83
84    /**
85     * Notify the priority tracker that a session's state changed.
86     *
87     * @param record The record that changed.
88     * @param oldState Its old playback state.
89     * @param newState Its new playback state.
90     */
91    public void onPlaystateChange(MediaSessionRecord record, int oldState, int newState) {
92        if (shouldUpdatePriority(oldState, newState)) {
93            mSessions.remove(record);
94            mSessions.add(0, record);
95            clearCache();
96        }
97    }
98
99    /**
100     * Handle any stack changes that need to occur in response to a session
101     * state change. TODO add the old and new session state as params
102     *
103     * @param record The record that changed.
104     */
105    public void onSessionStateChange(MediaSessionRecord record) {
106        // For now just clear the cache. Eventually we'll selectively clear
107        // depending on what changed.
108        clearCache();
109    }
110
111    /**
112     * Get the current priority sorted list of active sessions. The most
113     * important session is at index 0 and the least important at size - 1.
114     *
115     * @param userId The user to check.
116     * @return All the active sessions in priority order.
117     */
118    public ArrayList<MediaSessionRecord> getActiveSessions(int userId) {
119        if (mCachedActiveList == null) {
120            mCachedActiveList = getPriorityListLocked(true, 0, userId);
121        }
122        return mCachedActiveList;
123    }
124
125    /**
126     * Get the current priority sorted list of active sessions that use
127     * transport controls. The most important session is at index 0 and the
128     * least important at size -1.
129     *
130     * @param userId The user to check.
131     * @return All the active sessions that handle transport controls in
132     *         priority order.
133     */
134    public ArrayList<MediaSessionRecord> getTransportControlSessions(int userId) {
135        if (mCachedTransportControlList == null) {
136            mCachedTransportControlList = getPriorityListLocked(true,
137                    MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS, userId);
138        }
139        return mCachedTransportControlList;
140    }
141
142    /**
143     * Get the highest priority active session.
144     *
145     * @param userId The user to check.
146     * @return The current highest priority session or null.
147     */
148    public MediaSessionRecord getDefaultSession(int userId) {
149        if (mCachedDefault != null) {
150            return mCachedDefault;
151        }
152        ArrayList<MediaSessionRecord> records = getPriorityListLocked(true, 0, userId);
153        if (records.size() > 0) {
154            return records.get(0);
155        }
156        return null;
157    }
158
159    /**
160     * Get the highest priority session that can handle media buttons.
161     *
162     * @param userId The user to check.
163     * @return The default media button session or null.
164     */
165    public MediaSessionRecord getDefaultMediaButtonSession(int userId) {
166        if (mGlobalPrioritySession != null && mGlobalPrioritySession.isActive()) {
167            return mGlobalPrioritySession;
168        }
169        if (mCachedButtonReceiver != null) {
170            return mCachedButtonReceiver;
171        }
172        ArrayList<MediaSessionRecord> records = getPriorityListLocked(true,
173                MediaSession.FLAG_HANDLES_MEDIA_BUTTONS, userId);
174        if (records.size() > 0) {
175            mCachedButtonReceiver = records.get(0);
176        }
177        return mCachedButtonReceiver;
178    }
179
180    public void dump(PrintWriter pw, String prefix) {
181        ArrayList<MediaSessionRecord> sortedSessions = getPriorityListLocked(false, 0,
182                UserHandle.USER_ALL);
183        int count = sortedSessions.size();
184        pw.println(prefix + "Sessions Stack - have " + count + " sessions:");
185        String indent = prefix + "  ";
186        for (int i = 0; i < count; i++) {
187            MediaSessionRecord record = sortedSessions.get(i);
188            record.dump(pw, indent);
189            pw.println();
190        }
191    }
192
193    /**
194     * Get a priority sorted list of sessions. Can filter to only return active
195     * sessions or sessions with specific flags.
196     *
197     * @param activeOnly True to only return active sessions, false to return
198     *            all sessions.
199     * @param withFlags Only return sessions with all the specified flags set. 0
200     *            returns all sessions.
201     * @param userId The user to get sessions for. {@link UserHandle#USER_ALL}
202     *            will return sessions for all users.
203     * @return The priority sorted list of sessions.
204     */
205    private ArrayList<MediaSessionRecord> getPriorityListLocked(boolean activeOnly, int withFlags,
206            int userId) {
207        ArrayList<MediaSessionRecord> result = new ArrayList<MediaSessionRecord>();
208        int lastLocalIndex = 0;
209        int lastActiveIndex = 0;
210        int lastPublishedIndex = 0;
211
212        int size = mSessions.size();
213        for (int i = 0; i < size; i++) {
214            final MediaSessionRecord session = mSessions.get(i);
215
216            if (userId != UserHandle.USER_ALL && userId != session.getUserId()) {
217                // Filter out sessions for the wrong user
218                continue;
219            }
220            if ((session.getFlags() & withFlags) != withFlags) {
221                // Filter out sessions with the wrong flags
222                continue;
223            }
224            if (!session.isActive()) {
225                if (!activeOnly) {
226                    // If we're getting unpublished as well always put them at
227                    // the end
228                    result.add(session);
229                }
230                continue;
231            }
232
233            if (session.isSystemPriority()) {
234                // System priority sessions are special and always go at the
235                // front. We expect there to only be one of these at a time.
236                result.add(0, session);
237                lastLocalIndex++;
238                lastActiveIndex++;
239                lastPublishedIndex++;
240            } else if (session.isPlaybackActive()) {
241                // TODO replace getRoute() == null with real local route check
242                if(session.getRoute() == null) {
243                    // Active local sessions get top priority
244                    result.add(lastLocalIndex, session);
245                    lastLocalIndex++;
246                    lastActiveIndex++;
247                    lastPublishedIndex++;
248                } else {
249                    // Then active remote sessions
250                    result.add(lastActiveIndex, session);
251                    lastActiveIndex++;
252                    lastPublishedIndex++;
253                }
254            } else {
255                // inactive sessions go at the end in order of whoever last did
256                // something.
257                result.add(lastPublishedIndex, session);
258                lastPublishedIndex++;
259            }
260        }
261
262        return result;
263    }
264
265    private boolean shouldUpdatePriority(int oldState, int newState) {
266        if (containsState(newState, ALWAYS_PRIORITY_STATES)) {
267            return true;
268        }
269        if (!containsState(oldState, TRANSITION_PRIORITY_STATES)
270                && containsState(newState, TRANSITION_PRIORITY_STATES)) {
271            return true;
272        }
273        return false;
274    }
275
276    private boolean containsState(int state, int[] states) {
277        for (int i = 0; i < states.length; i++) {
278            if (states[i] == state) {
279                return true;
280            }
281        }
282        return false;
283    }
284
285    private void clearCache() {
286        mCachedDefault = null;
287        mCachedButtonReceiver = null;
288        mCachedActiveList = null;
289        mCachedTransportControlList = null;
290    }
291}
292