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