blob: 61c320bced3c434b9e28e956ce7391e5fb716e88 [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
Insun Kang30be970a2015-11-26 15:35:44 +090019import android.app.ActivityManager;
20import android.app.ActivityManagerNative;
RoboErikd2b8c942014-08-19 11:23:40 -070021import android.media.session.MediaController.PlaybackInfo;
RoboErika8f95142014-05-05 14:23:49 -070022import android.media.session.PlaybackState;
RoboErik42ea7ee2014-05-16 16:27:35 -070023import android.media.session.MediaSession;
Insun Kang30be970a2015-11-26 15:35:44 +090024import android.os.RemoteException;
RoboErika5b02322014-05-07 17:05:49 -070025import android.os.UserHandle;
RoboErika8f95142014-05-05 14:23:49 -070026
27import java.io.PrintWriter;
28import java.util.ArrayList;
Insun Kang30be970a2015-11-26 15:35:44 +090029import java.util.List;
RoboErika8f95142014-05-05 14:23:49 -070030
31/**
32 * Keeps track of media sessions and their priority for notifications, media
Jeff Brown01a500e2014-07-10 22:50:50 -070033 * button dispatch, etc.
RoboErika8f95142014-05-05 14:23:49 -070034 */
35public class MediaSessionStack {
36 /**
37 * These are states that usually indicate the user took an action and should
38 * bump priority regardless of the old state.
39 */
40 private static final int[] ALWAYS_PRIORITY_STATES = {
RoboErik79fa4632014-05-27 16:49:09 -070041 PlaybackState.STATE_FAST_FORWARDING,
42 PlaybackState.STATE_REWINDING,
43 PlaybackState.STATE_SKIPPING_TO_PREVIOUS,
44 PlaybackState.STATE_SKIPPING_TO_NEXT };
RoboErika8f95142014-05-05 14:23:49 -070045 /**
46 * These are states that usually indicate the user took an action if they
47 * were entered from a non-priority state.
48 */
49 private static final int[] TRANSITION_PRIORITY_STATES = {
RoboErik79fa4632014-05-27 16:49:09 -070050 PlaybackState.STATE_BUFFERING,
51 PlaybackState.STATE_CONNECTING,
52 PlaybackState.STATE_PLAYING };
RoboErika8f95142014-05-05 14:23:49 -070053
54 private final ArrayList<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>();
55
RoboErik4646d282014-05-13 10:13:04 -070056 private MediaSessionRecord mGlobalPrioritySession;
57
RoboErik870c5a62014-12-02 15:08:26 -080058 // The last record that either entered one of the playing states or was
59 // added.
60 private MediaSessionRecord mLastInterestingRecord;
RoboErika8f95142014-05-05 14:23:49 -070061 private MediaSessionRecord mCachedButtonReceiver;
62 private MediaSessionRecord mCachedDefault;
RoboErikb69ffd42014-05-30 14:57:59 -070063 private MediaSessionRecord mCachedVolumeDefault;
RoboErika8f95142014-05-05 14:23:49 -070064 private ArrayList<MediaSessionRecord> mCachedActiveList;
65 private ArrayList<MediaSessionRecord> mCachedTransportControlList;
66
67 /**
Insun Kang30be970a2015-11-26 15:35:44 +090068 * Checks if a media session is created from the most recent app.
69 *
70 * @param record A media session record to be examined.
71 * @return true if the media session's package name equals to the most recent app, false
72 * otherwise.
73 */
74 private static boolean isFromMostRecentApp(MediaSessionRecord record) {
75 if (ActivityManager.getCurrentUser() != record.getUserId()) {
76 return false;
77 }
78 try {
79 List<ActivityManager.RecentTaskInfo> tasks =
80 ActivityManagerNative.getDefault().getRecentTasks(1,
81 ActivityManager.RECENT_IGNORE_HOME_STACK_TASKS |
82 ActivityManager.RECENT_IGNORE_UNAVAILABLE |
83 ActivityManager.RECENT_INCLUDE_PROFILES |
84 ActivityManager.RECENT_WITH_EXCLUDED, record.getUserId());
85 if (tasks != null && !tasks.isEmpty()) {
86 ActivityManager.RecentTaskInfo recentTask = tasks.get(0);
87 if (recentTask.baseIntent != null)
88 return recentTask.baseIntent.getComponent().getPackageName()
89 .equals(record.getPackageName());
90 }
91 } catch (RemoteException e) {
92 return false;
93 }
94 return false;
95 }
96
97 /**
RoboErika8f95142014-05-05 14:23:49 -070098 * Add a record to the priority tracker.
99 *
100 * @param record The record to add.
101 */
102 public void addSession(MediaSessionRecord record) {
103 mSessions.add(record);
104 clearCache();
Insun Kang30be970a2015-11-26 15:35:44 +0900105 if (isFromMostRecentApp(record)) {
106 mLastInterestingRecord = record;
107 }
RoboErika8f95142014-05-05 14:23:49 -0700108 }
109
110 /**
111 * Remove a record from the priority tracker.
112 *
113 * @param record The record to remove.
114 */
115 public void removeSession(MediaSessionRecord record) {
116 mSessions.remove(record);
RoboErik4646d282014-05-13 10:13:04 -0700117 if (record == mGlobalPrioritySession) {
118 mGlobalPrioritySession = null;
119 }
RoboErika8f95142014-05-05 14:23:49 -0700120 clearCache();
121 }
122
123 /**
124 * Notify the priority tracker that a session's state changed.
125 *
126 * @param record The record that changed.
127 * @param oldState Its old playback state.
128 * @param newState Its new playback state.
RoboErik2e7a9162014-06-04 16:53:45 -0700129 * @return true if the priority order was updated, false otherwise.
RoboErika8f95142014-05-05 14:23:49 -0700130 */
RoboErik2e7a9162014-06-04 16:53:45 -0700131 public boolean onPlaystateChange(MediaSessionRecord record, int oldState, int newState) {
RoboErika8f95142014-05-05 14:23:49 -0700132 if (shouldUpdatePriority(oldState, newState)) {
133 mSessions.remove(record);
134 mSessions.add(0, record);
135 clearCache();
RoboErik870c5a62014-12-02 15:08:26 -0800136 // This becomes the last interesting record since it entered a
137 // playing state
138 mLastInterestingRecord = record;
RoboErik2e7a9162014-06-04 16:53:45 -0700139 return true;
RoboErik23b11352014-09-24 09:24:54 -0700140 } else if (!MediaSession.isActiveState(newState)) {
141 // Just clear the volume cache when a state goes inactive
RoboErikb69ffd42014-05-30 14:57:59 -0700142 mCachedVolumeDefault = null;
RoboErika8f95142014-05-05 14:23:49 -0700143 }
RoboErik2e7a9162014-06-04 16:53:45 -0700144 return false;
RoboErika8f95142014-05-05 14:23:49 -0700145 }
146
147 /**
148 * Handle any stack changes that need to occur in response to a session
149 * state change. TODO add the old and new session state as params
150 *
151 * @param record The record that changed.
152 */
153 public void onSessionStateChange(MediaSessionRecord record) {
RoboErik4d265982014-08-11 16:50:18 -0700154 if ((record.getFlags() & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) {
155 mGlobalPrioritySession = record;
156 }
RoboErika8f95142014-05-05 14:23:49 -0700157 // For now just clear the cache. Eventually we'll selectively clear
158 // depending on what changed.
159 clearCache();
160 }
161
162 /**
163 * Get the current priority sorted list of active sessions. The most
164 * important session is at index 0 and the least important at size - 1.
165 *
RoboErika5b02322014-05-07 17:05:49 -0700166 * @param userId The user to check.
RoboErika8f95142014-05-05 14:23:49 -0700167 * @return All the active sessions in priority order.
168 */
RoboErika5b02322014-05-07 17:05:49 -0700169 public ArrayList<MediaSessionRecord> getActiveSessions(int userId) {
RoboErika8f95142014-05-05 14:23:49 -0700170 if (mCachedActiveList == null) {
RoboErika5b02322014-05-07 17:05:49 -0700171 mCachedActiveList = getPriorityListLocked(true, 0, userId);
RoboErika8f95142014-05-05 14:23:49 -0700172 }
173 return mCachedActiveList;
174 }
175
176 /**
177 * Get the current priority sorted list of active sessions that use
178 * transport controls. The most important session is at index 0 and the
179 * least important at size -1.
180 *
RoboErika5b02322014-05-07 17:05:49 -0700181 * @param userId The user to check.
RoboErika8f95142014-05-05 14:23:49 -0700182 * @return All the active sessions that handle transport controls in
183 * priority order.
184 */
RoboErika5b02322014-05-07 17:05:49 -0700185 public ArrayList<MediaSessionRecord> getTransportControlSessions(int userId) {
RoboErika8f95142014-05-05 14:23:49 -0700186 if (mCachedTransportControlList == null) {
187 mCachedTransportControlList = getPriorityListLocked(true,
RoboErik42ea7ee2014-05-16 16:27:35 -0700188 MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS, userId);
RoboErika8f95142014-05-05 14:23:49 -0700189 }
190 return mCachedTransportControlList;
191 }
192
193 /**
194 * Get the highest priority active session.
195 *
RoboErika5b02322014-05-07 17:05:49 -0700196 * @param userId The user to check.
RoboErika8f95142014-05-05 14:23:49 -0700197 * @return The current highest priority session or null.
198 */
RoboErika5b02322014-05-07 17:05:49 -0700199 public MediaSessionRecord getDefaultSession(int userId) {
RoboErika8f95142014-05-05 14:23:49 -0700200 if (mCachedDefault != null) {
201 return mCachedDefault;
202 }
RoboErika5b02322014-05-07 17:05:49 -0700203 ArrayList<MediaSessionRecord> records = getPriorityListLocked(true, 0, userId);
RoboErika8f95142014-05-05 14:23:49 -0700204 if (records.size() > 0) {
205 return records.get(0);
206 }
207 return null;
208 }
209
210 /**
211 * Get the highest priority session that can handle media buttons.
212 *
RoboErika5b02322014-05-07 17:05:49 -0700213 * @param userId The user to check.
RoboErik870c5a62014-12-02 15:08:26 -0800214 * @param includeNotPlaying Return a non-playing session if nothing else is
215 * available
RoboErika8f95142014-05-05 14:23:49 -0700216 * @return The default media button session or null.
217 */
RoboErik870c5a62014-12-02 15:08:26 -0800218 public MediaSessionRecord getDefaultMediaButtonSession(int userId, boolean includeNotPlaying) {
RoboErik4646d282014-05-13 10:13:04 -0700219 if (mGlobalPrioritySession != null && mGlobalPrioritySession.isActive()) {
220 return mGlobalPrioritySession;
221 }
RoboErika8f95142014-05-05 14:23:49 -0700222 if (mCachedButtonReceiver != null) {
223 return mCachedButtonReceiver;
224 }
225 ArrayList<MediaSessionRecord> records = getPriorityListLocked(true,
RoboErik42ea7ee2014-05-16 16:27:35 -0700226 MediaSession.FLAG_HANDLES_MEDIA_BUTTONS, userId);
RoboErika8f95142014-05-05 14:23:49 -0700227 if (records.size() > 0) {
RoboErik870c5a62014-12-02 15:08:26 -0800228 MediaSessionRecord record = records.get(0);
229 if (record.isPlaybackActive(false)) {
230 // Since we're going to send a button event to this record make
231 // it the last interesting one.
232 mLastInterestingRecord = record;
233 mCachedButtonReceiver = record;
234 } else if (mLastInterestingRecord != null) {
235 if (records.contains(mLastInterestingRecord)) {
236 mCachedButtonReceiver = mLastInterestingRecord;
237 } else {
238 // That record is no longer used. Clear its reference.
239 mLastInterestingRecord = null;
240 }
241 }
242 if (includeNotPlaying && mCachedButtonReceiver == null) {
243 // If we really want a record and we didn't find one yet use the
244 // highest priority session even if it's not playing.
245 mCachedButtonReceiver = record;
246 }
RoboErika8f95142014-05-05 14:23:49 -0700247 }
248 return mCachedButtonReceiver;
249 }
250
RoboErikb69ffd42014-05-30 14:57:59 -0700251 public MediaSessionRecord getDefaultVolumeSession(int userId) {
252 if (mGlobalPrioritySession != null && mGlobalPrioritySession.isActive()) {
253 return mGlobalPrioritySession;
254 }
255 if (mCachedVolumeDefault != null) {
256 return mCachedVolumeDefault;
257 }
258 ArrayList<MediaSessionRecord> records = getPriorityListLocked(true, 0, userId);
259 int size = records.size();
260 for (int i = 0; i < size; i++) {
261 MediaSessionRecord record = records.get(i);
262 if (record.isPlaybackActive(false)) {
263 mCachedVolumeDefault = record;
264 return record;
265 }
266 }
267 return null;
268 }
269
RoboErik19c95182014-06-23 15:38:48 -0700270 public MediaSessionRecord getDefaultRemoteSession(int userId) {
271 ArrayList<MediaSessionRecord> records = getPriorityListLocked(true, 0, userId);
272
273 int size = records.size();
274 for (int i = 0; i < size; i++) {
275 MediaSessionRecord record = records.get(i);
RoboErikd2b8c942014-08-19 11:23:40 -0700276 if (record.getPlaybackType() == PlaybackInfo.PLAYBACK_TYPE_REMOTE) {
RoboErik19c95182014-06-23 15:38:48 -0700277 return record;
278 }
279 }
280 return null;
281 }
282
RoboErikde9ba392014-09-26 12:51:01 -0700283 public boolean isGlobalPriorityActive() {
284 return mGlobalPrioritySession == null ? false : mGlobalPrioritySession.isActive();
285 }
286
RoboErika5b02322014-05-07 17:05:49 -0700287 public void dump(PrintWriter pw, String prefix) {
288 ArrayList<MediaSessionRecord> sortedSessions = getPriorityListLocked(false, 0,
289 UserHandle.USER_ALL);
RoboErika8f95142014-05-05 14:23:49 -0700290 int count = sortedSessions.size();
RoboErik4d265982014-08-11 16:50:18 -0700291 pw.println(prefix + "Global priority session is " + mGlobalPrioritySession);
RoboErika8f95142014-05-05 14:23:49 -0700292 pw.println(prefix + "Sessions Stack - have " + count + " sessions:");
293 String indent = prefix + " ";
294 for (int i = 0; i < count; i++) {
295 MediaSessionRecord record = sortedSessions.get(i);
296 record.dump(pw, indent);
297 pw.println();
298 }
299 }
300
301 /**
302 * Get a priority sorted list of sessions. Can filter to only return active
303 * sessions or sessions with specific flags.
304 *
305 * @param activeOnly True to only return active sessions, false to return
306 * all sessions.
307 * @param withFlags Only return sessions with all the specified flags set. 0
308 * returns all sessions.
RoboErika5b02322014-05-07 17:05:49 -0700309 * @param userId The user to get sessions for. {@link UserHandle#USER_ALL}
310 * will return sessions for all users.
RoboErika8f95142014-05-05 14:23:49 -0700311 * @return The priority sorted list of sessions.
312 */
RoboErika5b02322014-05-07 17:05:49 -0700313 private ArrayList<MediaSessionRecord> getPriorityListLocked(boolean activeOnly, int withFlags,
314 int userId) {
RoboErika8f95142014-05-05 14:23:49 -0700315 ArrayList<MediaSessionRecord> result = new ArrayList<MediaSessionRecord>();
316 int lastLocalIndex = 0;
317 int lastActiveIndex = 0;
318 int lastPublishedIndex = 0;
319
320 int size = mSessions.size();
321 for (int i = 0; i < size; i++) {
322 final MediaSessionRecord session = mSessions.get(i);
323
RoboErika5b02322014-05-07 17:05:49 -0700324 if (userId != UserHandle.USER_ALL && userId != session.getUserId()) {
325 // Filter out sessions for the wrong user
326 continue;
327 }
RoboErika8f95142014-05-05 14:23:49 -0700328 if ((session.getFlags() & withFlags) != withFlags) {
RoboErika5b02322014-05-07 17:05:49 -0700329 // Filter out sessions with the wrong flags
RoboErika8f95142014-05-05 14:23:49 -0700330 continue;
331 }
332 if (!session.isActive()) {
333 if (!activeOnly) {
334 // If we're getting unpublished as well always put them at
335 // the end
336 result.add(session);
337 }
338 continue;
339 }
340
341 if (session.isSystemPriority()) {
342 // System priority sessions are special and always go at the
343 // front. We expect there to only be one of these at a time.
344 result.add(0, session);
345 lastLocalIndex++;
346 lastActiveIndex++;
347 lastPublishedIndex++;
RoboErikb69ffd42014-05-30 14:57:59 -0700348 } else if (session.isPlaybackActive(true)) {
Jeff Brown01a500e2014-07-10 22:50:50 -0700349 // TODO this with real local route check
350 if (true) {
RoboErika8f95142014-05-05 14:23:49 -0700351 // Active local sessions get top priority
352 result.add(lastLocalIndex, session);
353 lastLocalIndex++;
354 lastActiveIndex++;
355 lastPublishedIndex++;
356 } else {
357 // Then active remote sessions
358 result.add(lastActiveIndex, session);
359 lastActiveIndex++;
360 lastPublishedIndex++;
361 }
362 } else {
363 // inactive sessions go at the end in order of whoever last did
364 // something.
365 result.add(lastPublishedIndex, session);
366 lastPublishedIndex++;
367 }
368 }
369
370 return result;
371 }
372
373 private boolean shouldUpdatePriority(int oldState, int newState) {
374 if (containsState(newState, ALWAYS_PRIORITY_STATES)) {
375 return true;
376 }
377 if (!containsState(oldState, TRANSITION_PRIORITY_STATES)
378 && containsState(newState, TRANSITION_PRIORITY_STATES)) {
379 return true;
380 }
381 return false;
382 }
383
384 private boolean containsState(int state, int[] states) {
385 for (int i = 0; i < states.length; i++) {
386 if (states[i] == state) {
387 return true;
388 }
389 }
390 return false;
391 }
392
393 private void clearCache() {
394 mCachedDefault = null;
RoboErikb69ffd42014-05-30 14:57:59 -0700395 mCachedVolumeDefault = null;
RoboErika8f95142014-05-05 14:23:49 -0700396 mCachedButtonReceiver = null;
397 mCachedActiveList = null;
398 mCachedTransportControlList = null;
399 }
400}