blob: 803dee296fb2bbcc719f862d793ddbaae09f2e48 [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;
RoboErik42ea7ee2014-05-16 16:27:35 -070020import android.media.session.MediaSession;
RoboErika5b02322014-05-07 17:05:49 -070021import android.os.UserHandle;
RoboErika8f95142014-05-05 14:23:49 -070022
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 = {
RoboErik79fa4632014-05-27 16:49:09 -070036 PlaybackState.STATE_FAST_FORWARDING,
37 PlaybackState.STATE_REWINDING,
38 PlaybackState.STATE_SKIPPING_TO_PREVIOUS,
39 PlaybackState.STATE_SKIPPING_TO_NEXT };
RoboErika8f95142014-05-05 14:23:49 -070040 /**
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 = {
RoboErik79fa4632014-05-27 16:49:09 -070045 PlaybackState.STATE_BUFFERING,
46 PlaybackState.STATE_CONNECTING,
47 PlaybackState.STATE_PLAYING };
RoboErika8f95142014-05-05 14:23:49 -070048
49 private final ArrayList<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
50
RoboErik4646d282014-05-13 10:13:04 -070051 private MediaSessionRecord mGlobalPrioritySession;
52
RoboErika8f95142014-05-05 14:23:49 -070053 private MediaSessionRecord mCachedButtonReceiver;
54 private MediaSessionRecord mCachedDefault;
RoboErikb69ffd42014-05-30 14:57:59 -070055 private MediaSessionRecord mCachedVolumeDefault;
RoboErika8f95142014-05-05 14:23:49 -070056 private ArrayList<MediaSessionRecord> mCachedActiveList;
57 private ArrayList<MediaSessionRecord> mCachedTransportControlList;
58
59 /**
60 * Add a record to the priority tracker.
61 *
62 * @param record The record to add.
63 */
64 public void addSession(MediaSessionRecord record) {
65 mSessions.add(record);
RoboErik42ea7ee2014-05-16 16:27:35 -070066 if ((record.getFlags() & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) {
RoboErik4646d282014-05-13 10:13:04 -070067 mGlobalPrioritySession = record;
68 }
RoboErika8f95142014-05-05 14:23:49 -070069 clearCache();
70 }
71
72 /**
73 * Remove a record from the priority tracker.
74 *
75 * @param record The record to remove.
76 */
77 public void removeSession(MediaSessionRecord record) {
78 mSessions.remove(record);
RoboErik4646d282014-05-13 10:13:04 -070079 if (record == mGlobalPrioritySession) {
80 mGlobalPrioritySession = null;
81 }
RoboErika8f95142014-05-05 14:23:49 -070082 clearCache();
83 }
84
85 /**
86 * Notify the priority tracker that a session's state changed.
87 *
88 * @param record The record that changed.
89 * @param oldState Its old playback state.
90 * @param newState Its new playback state.
91 */
92 public void onPlaystateChange(MediaSessionRecord record, int oldState, int newState) {
93 if (shouldUpdatePriority(oldState, newState)) {
94 mSessions.remove(record);
95 mSessions.add(0, record);
96 clearCache();
RoboErikb69ffd42014-05-30 14:57:59 -070097 } else if (newState == PlaybackState.STATE_PAUSED) {
98 // Just clear the volume cache in this case
99 mCachedVolumeDefault = null;
RoboErika8f95142014-05-05 14:23:49 -0700100 }
101 }
102
103 /**
104 * Handle any stack changes that need to occur in response to a session
105 * state change. TODO add the old and new session state as params
106 *
107 * @param record The record that changed.
108 */
109 public void onSessionStateChange(MediaSessionRecord record) {
110 // For now just clear the cache. Eventually we'll selectively clear
111 // depending on what changed.
112 clearCache();
113 }
114
115 /**
116 * Get the current priority sorted list of active sessions. The most
117 * important session is at index 0 and the least important at size - 1.
118 *
RoboErika5b02322014-05-07 17:05:49 -0700119 * @param userId The user to check.
RoboErika8f95142014-05-05 14:23:49 -0700120 * @return All the active sessions in priority order.
121 */
RoboErika5b02322014-05-07 17:05:49 -0700122 public ArrayList<MediaSessionRecord> getActiveSessions(int userId) {
RoboErika8f95142014-05-05 14:23:49 -0700123 if (mCachedActiveList == null) {
RoboErika5b02322014-05-07 17:05:49 -0700124 mCachedActiveList = getPriorityListLocked(true, 0, userId);
RoboErika8f95142014-05-05 14:23:49 -0700125 }
126 return mCachedActiveList;
127 }
128
129 /**
130 * Get the current priority sorted list of active sessions that use
131 * transport controls. The most important session is at index 0 and the
132 * least important at size -1.
133 *
RoboErika5b02322014-05-07 17:05:49 -0700134 * @param userId The user to check.
RoboErika8f95142014-05-05 14:23:49 -0700135 * @return All the active sessions that handle transport controls in
136 * priority order.
137 */
RoboErika5b02322014-05-07 17:05:49 -0700138 public ArrayList<MediaSessionRecord> getTransportControlSessions(int userId) {
RoboErika8f95142014-05-05 14:23:49 -0700139 if (mCachedTransportControlList == null) {
140 mCachedTransportControlList = getPriorityListLocked(true,
RoboErik42ea7ee2014-05-16 16:27:35 -0700141 MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS, userId);
RoboErika8f95142014-05-05 14:23:49 -0700142 }
143 return mCachedTransportControlList;
144 }
145
146 /**
147 * Get the highest priority active session.
148 *
RoboErika5b02322014-05-07 17:05:49 -0700149 * @param userId The user to check.
RoboErika8f95142014-05-05 14:23:49 -0700150 * @return The current highest priority session or null.
151 */
RoboErika5b02322014-05-07 17:05:49 -0700152 public MediaSessionRecord getDefaultSession(int userId) {
RoboErika8f95142014-05-05 14:23:49 -0700153 if (mCachedDefault != null) {
154 return mCachedDefault;
155 }
RoboErika5b02322014-05-07 17:05:49 -0700156 ArrayList<MediaSessionRecord> records = getPriorityListLocked(true, 0, userId);
RoboErika8f95142014-05-05 14:23:49 -0700157 if (records.size() > 0) {
158 return records.get(0);
159 }
160 return null;
161 }
162
163 /**
164 * Get the highest priority session that can handle media buttons.
165 *
RoboErika5b02322014-05-07 17:05:49 -0700166 * @param userId The user to check.
RoboErika8f95142014-05-05 14:23:49 -0700167 * @return The default media button session or null.
168 */
RoboErika5b02322014-05-07 17:05:49 -0700169 public MediaSessionRecord getDefaultMediaButtonSession(int userId) {
RoboErik4646d282014-05-13 10:13:04 -0700170 if (mGlobalPrioritySession != null && mGlobalPrioritySession.isActive()) {
171 return mGlobalPrioritySession;
172 }
RoboErika8f95142014-05-05 14:23:49 -0700173 if (mCachedButtonReceiver != null) {
174 return mCachedButtonReceiver;
175 }
176 ArrayList<MediaSessionRecord> records = getPriorityListLocked(true,
RoboErik42ea7ee2014-05-16 16:27:35 -0700177 MediaSession.FLAG_HANDLES_MEDIA_BUTTONS, userId);
RoboErika8f95142014-05-05 14:23:49 -0700178 if (records.size() > 0) {
179 mCachedButtonReceiver = records.get(0);
180 }
181 return mCachedButtonReceiver;
182 }
183
RoboErikb69ffd42014-05-30 14:57:59 -0700184 public MediaSessionRecord getDefaultVolumeSession(int userId) {
185 if (mGlobalPrioritySession != null && mGlobalPrioritySession.isActive()) {
186 return mGlobalPrioritySession;
187 }
188 if (mCachedVolumeDefault != null) {
189 return mCachedVolumeDefault;
190 }
191 ArrayList<MediaSessionRecord> records = getPriorityListLocked(true, 0, userId);
192 int size = records.size();
193 for (int i = 0; i < size; i++) {
194 MediaSessionRecord record = records.get(i);
195 if (record.isPlaybackActive(false)) {
196 mCachedVolumeDefault = record;
197 return record;
198 }
199 }
200 return null;
201 }
202
RoboErika5b02322014-05-07 17:05:49 -0700203 public void dump(PrintWriter pw, String prefix) {
204 ArrayList<MediaSessionRecord> sortedSessions = getPriorityListLocked(false, 0,
205 UserHandle.USER_ALL);
RoboErika8f95142014-05-05 14:23:49 -0700206 int count = sortedSessions.size();
207 pw.println(prefix + "Sessions Stack - have " + count + " sessions:");
208 String indent = prefix + " ";
209 for (int i = 0; i < count; i++) {
210 MediaSessionRecord record = sortedSessions.get(i);
211 record.dump(pw, indent);
212 pw.println();
213 }
214 }
215
216 /**
217 * Get a priority sorted list of sessions. Can filter to only return active
218 * sessions or sessions with specific flags.
219 *
220 * @param activeOnly True to only return active sessions, false to return
221 * all sessions.
222 * @param withFlags Only return sessions with all the specified flags set. 0
223 * returns all sessions.
RoboErika5b02322014-05-07 17:05:49 -0700224 * @param userId The user to get sessions for. {@link UserHandle#USER_ALL}
225 * will return sessions for all users.
RoboErika8f95142014-05-05 14:23:49 -0700226 * @return The priority sorted list of sessions.
227 */
RoboErika5b02322014-05-07 17:05:49 -0700228 private ArrayList<MediaSessionRecord> getPriorityListLocked(boolean activeOnly, int withFlags,
229 int userId) {
RoboErika8f95142014-05-05 14:23:49 -0700230 ArrayList<MediaSessionRecord> result = new ArrayList<MediaSessionRecord>();
231 int lastLocalIndex = 0;
232 int lastActiveIndex = 0;
233 int lastPublishedIndex = 0;
234
235 int size = mSessions.size();
236 for (int i = 0; i < size; i++) {
237 final MediaSessionRecord session = mSessions.get(i);
238
RoboErika5b02322014-05-07 17:05:49 -0700239 if (userId != UserHandle.USER_ALL && userId != session.getUserId()) {
240 // Filter out sessions for the wrong user
241 continue;
242 }
RoboErika8f95142014-05-05 14:23:49 -0700243 if ((session.getFlags() & withFlags) != withFlags) {
RoboErika5b02322014-05-07 17:05:49 -0700244 // Filter out sessions with the wrong flags
RoboErika8f95142014-05-05 14:23:49 -0700245 continue;
246 }
247 if (!session.isActive()) {
248 if (!activeOnly) {
249 // If we're getting unpublished as well always put them at
250 // the end
251 result.add(session);
252 }
253 continue;
254 }
255
256 if (session.isSystemPriority()) {
257 // System priority sessions are special and always go at the
258 // front. We expect there to only be one of these at a time.
259 result.add(0, session);
260 lastLocalIndex++;
261 lastActiveIndex++;
262 lastPublishedIndex++;
RoboErikb69ffd42014-05-30 14:57:59 -0700263 } else if (session.isPlaybackActive(true)) {
RoboErika8f95142014-05-05 14:23:49 -0700264 // TODO replace getRoute() == null with real local route check
265 if(session.getRoute() == null) {
266 // Active local sessions get top priority
267 result.add(lastLocalIndex, session);
268 lastLocalIndex++;
269 lastActiveIndex++;
270 lastPublishedIndex++;
271 } else {
272 // Then active remote sessions
273 result.add(lastActiveIndex, session);
274 lastActiveIndex++;
275 lastPublishedIndex++;
276 }
277 } else {
278 // inactive sessions go at the end in order of whoever last did
279 // something.
280 result.add(lastPublishedIndex, session);
281 lastPublishedIndex++;
282 }
283 }
284
285 return result;
286 }
287
288 private boolean shouldUpdatePriority(int oldState, int newState) {
289 if (containsState(newState, ALWAYS_PRIORITY_STATES)) {
290 return true;
291 }
292 if (!containsState(oldState, TRANSITION_PRIORITY_STATES)
293 && containsState(newState, TRANSITION_PRIORITY_STATES)) {
294 return true;
295 }
296 return false;
297 }
298
299 private boolean containsState(int state, int[] states) {
300 for (int i = 0; i < states.length; i++) {
301 if (states[i] == state) {
302 return true;
303 }
304 }
305 return false;
306 }
307
308 private void clearCache() {
309 mCachedDefault = null;
RoboErikb69ffd42014-05-30 14:57:59 -0700310 mCachedVolumeDefault = null;
RoboErika8f95142014-05-05 14:23:49 -0700311 mCachedButtonReceiver = null;
312 mCachedActiveList = null;
313 mCachedTransportControlList = null;
314 }
315}