blob: 56236f883e53d6da8742cbe0e1996dea0735f3f3 [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;
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);
RoboErik42ea7ee2014-05-16 16:27:35 -070065 if ((record.getFlags() & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) {
RoboErik4646d282014-05-13 10:13:04 -070066 mGlobalPrioritySession = record;
67 }
RoboErika8f95142014-05-05 14:23:49 -070068 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);
RoboErik4646d282014-05-13 10:13:04 -070078 if (record == mGlobalPrioritySession) {
79 mGlobalPrioritySession = null;
80 }
RoboErika8f95142014-05-05 14:23:49 -070081 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 *
RoboErika5b02322014-05-07 17:05:49 -0700115 * @param userId The user to check.
RoboErika8f95142014-05-05 14:23:49 -0700116 * @return All the active sessions in priority order.
117 */
RoboErika5b02322014-05-07 17:05:49 -0700118 public ArrayList<MediaSessionRecord> getActiveSessions(int userId) {
RoboErika8f95142014-05-05 14:23:49 -0700119 if (mCachedActiveList == null) {
RoboErika5b02322014-05-07 17:05:49 -0700120 mCachedActiveList = getPriorityListLocked(true, 0, userId);
RoboErika8f95142014-05-05 14:23:49 -0700121 }
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 *
RoboErika5b02322014-05-07 17:05:49 -0700130 * @param userId The user to check.
RoboErika8f95142014-05-05 14:23:49 -0700131 * @return All the active sessions that handle transport controls in
132 * priority order.
133 */
RoboErika5b02322014-05-07 17:05:49 -0700134 public ArrayList<MediaSessionRecord> getTransportControlSessions(int userId) {
RoboErika8f95142014-05-05 14:23:49 -0700135 if (mCachedTransportControlList == null) {
136 mCachedTransportControlList = getPriorityListLocked(true,
RoboErik42ea7ee2014-05-16 16:27:35 -0700137 MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS, userId);
RoboErika8f95142014-05-05 14:23:49 -0700138 }
139 return mCachedTransportControlList;
140 }
141
142 /**
143 * Get the highest priority active session.
144 *
RoboErika5b02322014-05-07 17:05:49 -0700145 * @param userId The user to check.
RoboErika8f95142014-05-05 14:23:49 -0700146 * @return The current highest priority session or null.
147 */
RoboErika5b02322014-05-07 17:05:49 -0700148 public MediaSessionRecord getDefaultSession(int userId) {
RoboErika8f95142014-05-05 14:23:49 -0700149 if (mCachedDefault != null) {
150 return mCachedDefault;
151 }
RoboErika5b02322014-05-07 17:05:49 -0700152 ArrayList<MediaSessionRecord> records = getPriorityListLocked(true, 0, userId);
RoboErika8f95142014-05-05 14:23:49 -0700153 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 *
RoboErika5b02322014-05-07 17:05:49 -0700162 * @param userId The user to check.
RoboErika8f95142014-05-05 14:23:49 -0700163 * @return The default media button session or null.
164 */
RoboErika5b02322014-05-07 17:05:49 -0700165 public MediaSessionRecord getDefaultMediaButtonSession(int userId) {
RoboErik4646d282014-05-13 10:13:04 -0700166 if (mGlobalPrioritySession != null && mGlobalPrioritySession.isActive()) {
167 return mGlobalPrioritySession;
168 }
RoboErika8f95142014-05-05 14:23:49 -0700169 if (mCachedButtonReceiver != null) {
170 return mCachedButtonReceiver;
171 }
172 ArrayList<MediaSessionRecord> records = getPriorityListLocked(true,
RoboErik42ea7ee2014-05-16 16:27:35 -0700173 MediaSession.FLAG_HANDLES_MEDIA_BUTTONS, userId);
RoboErika8f95142014-05-05 14:23:49 -0700174 if (records.size() > 0) {
175 mCachedButtonReceiver = records.get(0);
176 }
177 return mCachedButtonReceiver;
178 }
179
RoboErika5b02322014-05-07 17:05:49 -0700180 public void dump(PrintWriter pw, String prefix) {
181 ArrayList<MediaSessionRecord> sortedSessions = getPriorityListLocked(false, 0,
182 UserHandle.USER_ALL);
RoboErika8f95142014-05-05 14:23:49 -0700183 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.
RoboErika5b02322014-05-07 17:05:49 -0700201 * @param userId The user to get sessions for. {@link UserHandle#USER_ALL}
202 * will return sessions for all users.
RoboErika8f95142014-05-05 14:23:49 -0700203 * @return The priority sorted list of sessions.
204 */
RoboErika5b02322014-05-07 17:05:49 -0700205 private ArrayList<MediaSessionRecord> getPriorityListLocked(boolean activeOnly, int withFlags,
206 int userId) {
RoboErika8f95142014-05-05 14:23:49 -0700207 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
RoboErika5b02322014-05-07 17:05:49 -0700216 if (userId != UserHandle.USER_ALL && userId != session.getUserId()) {
217 // Filter out sessions for the wrong user
218 continue;
219 }
RoboErika8f95142014-05-05 14:23:49 -0700220 if ((session.getFlags() & withFlags) != withFlags) {
RoboErika5b02322014-05-07 17:05:49 -0700221 // Filter out sessions with the wrong flags
RoboErika8f95142014-05-05 14:23:49 -0700222 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}