blob: 1e1818d10df8dc18658c500b25ff93eb3bf881f4 [file] [log] [blame]
RoboErika8f95142014-05-05 14:23:49 -07001/*
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;
RoboErika5b02322014-05-07 17:05:49 -070021import android.os.UserHandle;
RoboErika8f95142014-05-05 14:23:49 -070022import 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 *
RoboErika5b02322014-05-07 17:05:49 -0700108 * @param userId The user to check.
RoboErika8f95142014-05-05 14:23:49 -0700109 * @return All the active sessions in priority order.
110 */
RoboErika5b02322014-05-07 17:05:49 -0700111 public ArrayList<MediaSessionRecord> getActiveSessions(int userId) {
RoboErika8f95142014-05-05 14:23:49 -0700112 if (mCachedActiveList == null) {
RoboErika5b02322014-05-07 17:05:49 -0700113 mCachedActiveList = getPriorityListLocked(true, 0, userId);
RoboErika8f95142014-05-05 14:23:49 -0700114 }
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 *
RoboErika5b02322014-05-07 17:05:49 -0700123 * @param userId The user to check.
RoboErika8f95142014-05-05 14:23:49 -0700124 * @return All the active sessions that handle transport controls in
125 * priority order.
126 */
RoboErika5b02322014-05-07 17:05:49 -0700127 public ArrayList<MediaSessionRecord> getTransportControlSessions(int userId) {
RoboErika8f95142014-05-05 14:23:49 -0700128 if (mCachedTransportControlList == null) {
129 mCachedTransportControlList = getPriorityListLocked(true,
RoboErika5b02322014-05-07 17:05:49 -0700130 Session.FLAG_HANDLES_TRANSPORT_CONTROLS, userId);
RoboErika8f95142014-05-05 14:23:49 -0700131 }
132 return mCachedTransportControlList;
133 }
134
135 /**
136 * Get the highest priority active session.
137 *
RoboErika5b02322014-05-07 17:05:49 -0700138 * @param userId The user to check.
RoboErika8f95142014-05-05 14:23:49 -0700139 * @return The current highest priority session or null.
140 */
RoboErika5b02322014-05-07 17:05:49 -0700141 public MediaSessionRecord getDefaultSession(int userId) {
RoboErika8f95142014-05-05 14:23:49 -0700142 if (mCachedDefault != null) {
143 return mCachedDefault;
144 }
RoboErika5b02322014-05-07 17:05:49 -0700145 ArrayList<MediaSessionRecord> records = getPriorityListLocked(true, 0, userId);
RoboErika8f95142014-05-05 14:23:49 -0700146 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 *
RoboErika5b02322014-05-07 17:05:49 -0700155 * @param userId The user to check.
RoboErika8f95142014-05-05 14:23:49 -0700156 * @return The default media button session or null.
157 */
RoboErika5b02322014-05-07 17:05:49 -0700158 public MediaSessionRecord getDefaultMediaButtonSession(int userId) {
RoboErika8f95142014-05-05 14:23:49 -0700159 if (mCachedButtonReceiver != null) {
160 return mCachedButtonReceiver;
161 }
162 ArrayList<MediaSessionRecord> records = getPriorityListLocked(true,
RoboErika5b02322014-05-07 17:05:49 -0700163 Session.FLAG_HANDLES_MEDIA_BUTTONS, userId);
RoboErika8f95142014-05-05 14:23:49 -0700164 if (records.size() > 0) {
165 mCachedButtonReceiver = records.get(0);
166 }
167 return mCachedButtonReceiver;
168 }
169
RoboErika5b02322014-05-07 17:05:49 -0700170 public void dump(PrintWriter pw, String prefix) {
171 ArrayList<MediaSessionRecord> sortedSessions = getPriorityListLocked(false, 0,
172 UserHandle.USER_ALL);
RoboErika8f95142014-05-05 14:23:49 -0700173 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.
RoboErika5b02322014-05-07 17:05:49 -0700191 * @param userId The user to get sessions for. {@link UserHandle#USER_ALL}
192 * will return sessions for all users.
RoboErika8f95142014-05-05 14:23:49 -0700193 * @return The priority sorted list of sessions.
194 */
RoboErika5b02322014-05-07 17:05:49 -0700195 private ArrayList<MediaSessionRecord> getPriorityListLocked(boolean activeOnly, int withFlags,
196 int userId) {
RoboErika8f95142014-05-05 14:23:49 -0700197 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
RoboErika5b02322014-05-07 17:05:49 -0700206 if (userId != UserHandle.USER_ALL && userId != session.getUserId()) {
207 // Filter out sessions for the wrong user
208 continue;
209 }
RoboErika8f95142014-05-05 14:23:49 -0700210 if ((session.getFlags() & withFlags) != withFlags) {
RoboErika5b02322014-05-07 17:05:49 -0700211 // Filter out sessions with the wrong flags
RoboErika8f95142014-05-05 14:23:49 -0700212 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}