blob: e464be7c16b25825edcdc90c09ef2e078ed1db34 [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
RoboErikd2b8c942014-08-19 11:23:40 -070019import android.media.session.MediaController.PlaybackInfo;
RoboErika8f95142014-05-05 14:23:49 -070020import android.media.session.PlaybackState;
RoboErik42ea7ee2014-05-16 16:27:35 -070021import android.media.session.MediaSession;
RoboErika5b02322014-05-07 17:05:49 -070022import android.os.UserHandle;
RoboErika8f95142014-05-05 14:23:49 -070023
24import java.io.PrintWriter;
25import java.util.ArrayList;
26
27/**
28 * Keeps track of media sessions and their priority for notifications, media
Jeff Brown01a500e2014-07-10 22:50:50 -070029 * button dispatch, etc.
RoboErika8f95142014-05-05 14:23:49 -070030 */
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 = {
RoboErik79fa4632014-05-27 16:49:09 -070037 PlaybackState.STATE_FAST_FORWARDING,
38 PlaybackState.STATE_REWINDING,
39 PlaybackState.STATE_SKIPPING_TO_PREVIOUS,
40 PlaybackState.STATE_SKIPPING_TO_NEXT };
RoboErika8f95142014-05-05 14:23:49 -070041 /**
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 = {
RoboErik79fa4632014-05-27 16:49:09 -070046 PlaybackState.STATE_BUFFERING,
47 PlaybackState.STATE_CONNECTING,
48 PlaybackState.STATE_PLAYING };
RoboErika8f95142014-05-05 14:23:49 -070049
50 private final ArrayList<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
51
RoboErik4646d282014-05-13 10:13:04 -070052 private MediaSessionRecord mGlobalPrioritySession;
53
RoboErika8f95142014-05-05 14:23:49 -070054 private MediaSessionRecord mCachedButtonReceiver;
55 private MediaSessionRecord mCachedDefault;
RoboErikb69ffd42014-05-30 14:57:59 -070056 private MediaSessionRecord mCachedVolumeDefault;
RoboErika8f95142014-05-05 14:23:49 -070057 private ArrayList<MediaSessionRecord> mCachedActiveList;
58 private ArrayList<MediaSessionRecord> mCachedTransportControlList;
59
60 /**
61 * Add a record to the priority tracker.
62 *
63 * @param record The record to add.
64 */
65 public void addSession(MediaSessionRecord record) {
66 mSessions.add(record);
67 clearCache();
68 }
69
70 /**
71 * Remove a record from the priority tracker.
72 *
73 * @param record The record to remove.
74 */
75 public void removeSession(MediaSessionRecord record) {
76 mSessions.remove(record);
RoboErik4646d282014-05-13 10:13:04 -070077 if (record == mGlobalPrioritySession) {
78 mGlobalPrioritySession = null;
79 }
RoboErika8f95142014-05-05 14:23:49 -070080 clearCache();
81 }
82
83 /**
84 * Notify the priority tracker that a session's state changed.
85 *
86 * @param record The record that changed.
87 * @param oldState Its old playback state.
88 * @param newState Its new playback state.
RoboErik2e7a9162014-06-04 16:53:45 -070089 * @return true if the priority order was updated, false otherwise.
RoboErika8f95142014-05-05 14:23:49 -070090 */
RoboErik2e7a9162014-06-04 16:53:45 -070091 public boolean onPlaystateChange(MediaSessionRecord record, int oldState, int newState) {
RoboErika8f95142014-05-05 14:23:49 -070092 if (shouldUpdatePriority(oldState, newState)) {
93 mSessions.remove(record);
94 mSessions.add(0, record);
95 clearCache();
RoboErik2e7a9162014-06-04 16:53:45 -070096 return true;
RoboErik23b11352014-09-24 09:24:54 -070097 } else if (!MediaSession.isActiveState(newState)) {
98 // Just clear the volume cache when a state goes inactive
RoboErikb69ffd42014-05-30 14:57:59 -070099 mCachedVolumeDefault = null;
RoboErika8f95142014-05-05 14:23:49 -0700100 }
RoboErik2e7a9162014-06-04 16:53:45 -0700101 return false;
RoboErika8f95142014-05-05 14:23:49 -0700102 }
103
104 /**
105 * Handle any stack changes that need to occur in response to a session
106 * state change. TODO add the old and new session state as params
107 *
108 * @param record The record that changed.
109 */
110 public void onSessionStateChange(MediaSessionRecord record) {
RoboErik4d265982014-08-11 16:50:18 -0700111 if ((record.getFlags() & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) {
112 mGlobalPrioritySession = record;
113 }
RoboErika8f95142014-05-05 14:23:49 -0700114 // For now just clear the cache. Eventually we'll selectively clear
115 // depending on what changed.
116 clearCache();
117 }
118
119 /**
120 * Get the current priority sorted list of active sessions. The most
121 * important session is at index 0 and the 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 in priority order.
125 */
RoboErika5b02322014-05-07 17:05:49 -0700126 public ArrayList<MediaSessionRecord> getActiveSessions(int userId) {
RoboErika8f95142014-05-05 14:23:49 -0700127 if (mCachedActiveList == null) {
RoboErika5b02322014-05-07 17:05:49 -0700128 mCachedActiveList = getPriorityListLocked(true, 0, userId);
RoboErika8f95142014-05-05 14:23:49 -0700129 }
130 return mCachedActiveList;
131 }
132
133 /**
134 * Get the current priority sorted list of active sessions that use
135 * transport controls. The most important session is at index 0 and the
136 * least important at size -1.
137 *
RoboErika5b02322014-05-07 17:05:49 -0700138 * @param userId The user to check.
RoboErika8f95142014-05-05 14:23:49 -0700139 * @return All the active sessions that handle transport controls in
140 * priority order.
141 */
RoboErika5b02322014-05-07 17:05:49 -0700142 public ArrayList<MediaSessionRecord> getTransportControlSessions(int userId) {
RoboErika8f95142014-05-05 14:23:49 -0700143 if (mCachedTransportControlList == null) {
144 mCachedTransportControlList = getPriorityListLocked(true,
RoboErik42ea7ee2014-05-16 16:27:35 -0700145 MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS, userId);
RoboErika8f95142014-05-05 14:23:49 -0700146 }
147 return mCachedTransportControlList;
148 }
149
150 /**
151 * Get the highest priority active session.
152 *
RoboErika5b02322014-05-07 17:05:49 -0700153 * @param userId The user to check.
RoboErika8f95142014-05-05 14:23:49 -0700154 * @return The current highest priority session or null.
155 */
RoboErika5b02322014-05-07 17:05:49 -0700156 public MediaSessionRecord getDefaultSession(int userId) {
RoboErika8f95142014-05-05 14:23:49 -0700157 if (mCachedDefault != null) {
158 return mCachedDefault;
159 }
RoboErika5b02322014-05-07 17:05:49 -0700160 ArrayList<MediaSessionRecord> records = getPriorityListLocked(true, 0, userId);
RoboErika8f95142014-05-05 14:23:49 -0700161 if (records.size() > 0) {
162 return records.get(0);
163 }
164 return null;
165 }
166
167 /**
168 * Get the highest priority session that can handle media buttons.
169 *
RoboErika5b02322014-05-07 17:05:49 -0700170 * @param userId The user to check.
RoboErika8f95142014-05-05 14:23:49 -0700171 * @return The default media button session or null.
172 */
RoboErika5b02322014-05-07 17:05:49 -0700173 public MediaSessionRecord getDefaultMediaButtonSession(int userId) {
RoboErik4646d282014-05-13 10:13:04 -0700174 if (mGlobalPrioritySession != null && mGlobalPrioritySession.isActive()) {
175 return mGlobalPrioritySession;
176 }
RoboErika8f95142014-05-05 14:23:49 -0700177 if (mCachedButtonReceiver != null) {
178 return mCachedButtonReceiver;
179 }
180 ArrayList<MediaSessionRecord> records = getPriorityListLocked(true,
RoboErik42ea7ee2014-05-16 16:27:35 -0700181 MediaSession.FLAG_HANDLES_MEDIA_BUTTONS, userId);
RoboErika8f95142014-05-05 14:23:49 -0700182 if (records.size() > 0) {
183 mCachedButtonReceiver = records.get(0);
184 }
185 return mCachedButtonReceiver;
186 }
187
RoboErikb69ffd42014-05-30 14:57:59 -0700188 public MediaSessionRecord getDefaultVolumeSession(int userId) {
189 if (mGlobalPrioritySession != null && mGlobalPrioritySession.isActive()) {
190 return mGlobalPrioritySession;
191 }
192 if (mCachedVolumeDefault != null) {
193 return mCachedVolumeDefault;
194 }
195 ArrayList<MediaSessionRecord> records = getPriorityListLocked(true, 0, userId);
196 int size = records.size();
197 for (int i = 0; i < size; i++) {
198 MediaSessionRecord record = records.get(i);
199 if (record.isPlaybackActive(false)) {
200 mCachedVolumeDefault = record;
201 return record;
202 }
203 }
204 return null;
205 }
206
RoboErik19c95182014-06-23 15:38:48 -0700207 public MediaSessionRecord getDefaultRemoteSession(int userId) {
208 ArrayList<MediaSessionRecord> records = getPriorityListLocked(true, 0, userId);
209
210 int size = records.size();
211 for (int i = 0; i < size; i++) {
212 MediaSessionRecord record = records.get(i);
RoboErikd2b8c942014-08-19 11:23:40 -0700213 if (record.getPlaybackType() == PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
RoboErik19c95182014-06-23 15:38:48 -0700214 return record;
215 }
216 }
217 return null;
218 }
219
RoboErika5b02322014-05-07 17:05:49 -0700220 public void dump(PrintWriter pw, String prefix) {
221 ArrayList<MediaSessionRecord> sortedSessions = getPriorityListLocked(false, 0,
222 UserHandle.USER_ALL);
RoboErika8f95142014-05-05 14:23:49 -0700223 int count = sortedSessions.size();
RoboErik4d265982014-08-11 16:50:18 -0700224 pw.println(prefix + "Global priority session is " + mGlobalPrioritySession);
RoboErika8f95142014-05-05 14:23:49 -0700225 pw.println(prefix + "Sessions Stack - have " + count + " sessions:");
226 String indent = prefix + " ";
227 for (int i = 0; i < count; i++) {
228 MediaSessionRecord record = sortedSessions.get(i);
229 record.dump(pw, indent);
230 pw.println();
231 }
232 }
233
234 /**
235 * Get a priority sorted list of sessions. Can filter to only return active
236 * sessions or sessions with specific flags.
237 *
238 * @param activeOnly True to only return active sessions, false to return
239 * all sessions.
240 * @param withFlags Only return sessions with all the specified flags set. 0
241 * returns all sessions.
RoboErika5b02322014-05-07 17:05:49 -0700242 * @param userId The user to get sessions for. {@link UserHandle#USER_ALL}
243 * will return sessions for all users.
RoboErika8f95142014-05-05 14:23:49 -0700244 * @return The priority sorted list of sessions.
245 */
RoboErika5b02322014-05-07 17:05:49 -0700246 private ArrayList<MediaSessionRecord> getPriorityListLocked(boolean activeOnly, int withFlags,
247 int userId) {
RoboErika8f95142014-05-05 14:23:49 -0700248 ArrayList<MediaSessionRecord> result = new ArrayList<MediaSessionRecord>();
249 int lastLocalIndex = 0;
250 int lastActiveIndex = 0;
251 int lastPublishedIndex = 0;
252
253 int size = mSessions.size();
254 for (int i = 0; i < size; i++) {
255 final MediaSessionRecord session = mSessions.get(i);
256
RoboErika5b02322014-05-07 17:05:49 -0700257 if (userId != UserHandle.USER_ALL && userId != session.getUserId()) {
258 // Filter out sessions for the wrong user
259 continue;
260 }
RoboErika8f95142014-05-05 14:23:49 -0700261 if ((session.getFlags() & withFlags) != withFlags) {
RoboErika5b02322014-05-07 17:05:49 -0700262 // Filter out sessions with the wrong flags
RoboErika8f95142014-05-05 14:23:49 -0700263 continue;
264 }
265 if (!session.isActive()) {
266 if (!activeOnly) {
267 // If we're getting unpublished as well always put them at
268 // the end
269 result.add(session);
270 }
271 continue;
272 }
273
274 if (session.isSystemPriority()) {
275 // System priority sessions are special and always go at the
276 // front. We expect there to only be one of these at a time.
277 result.add(0, session);
278 lastLocalIndex++;
279 lastActiveIndex++;
280 lastPublishedIndex++;
RoboErikb69ffd42014-05-30 14:57:59 -0700281 } else if (session.isPlaybackActive(true)) {
Jeff Brown01a500e2014-07-10 22:50:50 -0700282 // TODO this with real local route check
283 if (true) {
RoboErika8f95142014-05-05 14:23:49 -0700284 // Active local sessions get top priority
285 result.add(lastLocalIndex, session);
286 lastLocalIndex++;
287 lastActiveIndex++;
288 lastPublishedIndex++;
289 } else {
290 // Then active remote sessions
291 result.add(lastActiveIndex, session);
292 lastActiveIndex++;
293 lastPublishedIndex++;
294 }
295 } else {
296 // inactive sessions go at the end in order of whoever last did
297 // something.
298 result.add(lastPublishedIndex, session);
299 lastPublishedIndex++;
300 }
301 }
302
303 return result;
304 }
305
306 private boolean shouldUpdatePriority(int oldState, int newState) {
307 if (containsState(newState, ALWAYS_PRIORITY_STATES)) {
308 return true;
309 }
310 if (!containsState(oldState, TRANSITION_PRIORITY_STATES)
311 && containsState(newState, TRANSITION_PRIORITY_STATES)) {
312 return true;
313 }
314 return false;
315 }
316
317 private boolean containsState(int state, int[] states) {
318 for (int i = 0; i < states.length; i++) {
319 if (states[i] == state) {
320 return true;
321 }
322 }
323 return false;
324 }
325
326 private void clearCache() {
327 mCachedDefault = null;
RoboErikb69ffd42014-05-30 14:57:59 -0700328 mCachedVolumeDefault = null;
RoboErika8f95142014-05-05 14:23:49 -0700329 mCachedButtonReceiver = null;
330 mCachedActiveList = null;
331 mCachedTransportControlList = null;
332 }
333}