blob: 3327b36536d9eb779d8a5296e4ac05f11589ee42 [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 */
Jaewan Kim8f729082016-06-21 12:36:26 +090035class MediaSessionStack {
RoboErika8f95142014-05-05 14:23:49 -070036 /**
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.
Jaewan Kim8f729082016-06-21 12:36:26 +090071 * @return {@code true} if the media session's package name equals to the most recent app, false
72 * otherwise.
Insun Kang30be970a2015-11-26 15:35:44 +090073 */
74 private static boolean isFromMostRecentApp(MediaSessionRecord record) {
Insun Kang30be970a2015-11-26 15:35:44 +090075 try {
76 List<ActivityManager.RecentTaskInfo> tasks =
77 ActivityManagerNative.getDefault().getRecentTasks(1,
78 ActivityManager.RECENT_IGNORE_HOME_STACK_TASKS |
79 ActivityManager.RECENT_IGNORE_UNAVAILABLE |
80 ActivityManager.RECENT_INCLUDE_PROFILES |
Jeff Sharkey479212c2016-06-29 16:00:55 -060081 ActivityManager.RECENT_WITH_EXCLUDED, record.getUserId()).getList();
Insun Kang30be970a2015-11-26 15:35:44 +090082 if (tasks != null && !tasks.isEmpty()) {
83 ActivityManager.RecentTaskInfo recentTask = tasks.get(0);
Jaewan Kim8f729082016-06-21 12:36:26 +090084 if (recentTask.userId == record.getUserId() && recentTask.baseIntent != null) {
Insun Kang30be970a2015-11-26 15:35:44 +090085 return recentTask.baseIntent.getComponent().getPackageName()
86 .equals(record.getPackageName());
Jaewan Kim8f729082016-06-21 12:36:26 +090087 }
Insun Kang30be970a2015-11-26 15:35:44 +090088 }
89 } catch (RemoteException e) {
90 return false;
91 }
92 return false;
93 }
94
95 /**
RoboErika8f95142014-05-05 14:23:49 -070096 * Add a record to the priority tracker.
97 *
98 * @param record The record to add.
Jaewan Kim8f729082016-06-21 12:36:26 +090099 * @param fromForegroundUser {@code true} if the session is created by the foreground user.
RoboErika8f95142014-05-05 14:23:49 -0700100 */
Jaewan Kim8f729082016-06-21 12:36:26 +0900101 public void addSession(MediaSessionRecord record, boolean fromForegroundUser) {
RoboErika8f95142014-05-05 14:23:49 -0700102 mSessions.add(record);
103 clearCache();
Jaewan Kim8f729082016-06-21 12:36:26 +0900104 if (fromForegroundUser && isFromMostRecentApp(record)) {
Insun Kang30be970a2015-11-26 15:35:44 +0900105 mLastInterestingRecord = record;
106 }
RoboErika8f95142014-05-05 14:23:49 -0700107 }
108
109 /**
110 * Remove a record from the priority tracker.
111 *
112 * @param record The record to remove.
113 */
114 public void removeSession(MediaSessionRecord record) {
115 mSessions.remove(record);
RoboErik4646d282014-05-13 10:13:04 -0700116 if (record == mGlobalPrioritySession) {
117 mGlobalPrioritySession = null;
118 }
RoboErika8f95142014-05-05 14:23:49 -0700119 clearCache();
120 }
121
122 /**
123 * Notify the priority tracker that a session's state changed.
124 *
125 * @param record The record that changed.
126 * @param oldState Its old playback state.
127 * @param newState Its new playback state.
RoboErik2e7a9162014-06-04 16:53:45 -0700128 * @return true if the priority order was updated, false otherwise.
RoboErika8f95142014-05-05 14:23:49 -0700129 */
RoboErik2e7a9162014-06-04 16:53:45 -0700130 public boolean onPlaystateChange(MediaSessionRecord record, int oldState, int newState) {
RoboErika8f95142014-05-05 14:23:49 -0700131 if (shouldUpdatePriority(oldState, newState)) {
132 mSessions.remove(record);
133 mSessions.add(0, record);
134 clearCache();
RoboErik870c5a62014-12-02 15:08:26 -0800135 // This becomes the last interesting record since it entered a
136 // playing state
137 mLastInterestingRecord = record;
RoboErik2e7a9162014-06-04 16:53:45 -0700138 return true;
RoboErik23b11352014-09-24 09:24:54 -0700139 } else if (!MediaSession.isActiveState(newState)) {
140 // Just clear the volume cache when a state goes inactive
RoboErikb69ffd42014-05-30 14:57:59 -0700141 mCachedVolumeDefault = null;
RoboErika8f95142014-05-05 14:23:49 -0700142 }
RoboErik2e7a9162014-06-04 16:53:45 -0700143 return false;
RoboErika8f95142014-05-05 14:23:49 -0700144 }
145
146 /**
147 * Handle any stack changes that need to occur in response to a session
148 * state change. TODO add the old and new session state as params
149 *
150 * @param record The record that changed.
151 */
152 public void onSessionStateChange(MediaSessionRecord record) {
RoboErik4d265982014-08-11 16:50:18 -0700153 if ((record.getFlags() & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) {
154 mGlobalPrioritySession = record;
155 }
RoboErika8f95142014-05-05 14:23:49 -0700156 // For now just clear the cache. Eventually we'll selectively clear
157 // depending on what changed.
158 clearCache();
159 }
160
161 /**
162 * Get the current priority sorted list of active sessions. The most
163 * important session is at index 0 and the least important at size - 1.
164 *
RoboErika5b02322014-05-07 17:05:49 -0700165 * @param userId The user to check.
RoboErika8f95142014-05-05 14:23:49 -0700166 * @return All the active sessions in priority order.
167 */
RoboErika5b02322014-05-07 17:05:49 -0700168 public ArrayList<MediaSessionRecord> getActiveSessions(int userId) {
RoboErika8f95142014-05-05 14:23:49 -0700169 if (mCachedActiveList == null) {
RoboErika5b02322014-05-07 17:05:49 -0700170 mCachedActiveList = getPriorityListLocked(true, 0, userId);
RoboErika8f95142014-05-05 14:23:49 -0700171 }
172 return mCachedActiveList;
173 }
174
175 /**
176 * Get the current priority sorted list of active sessions that use
177 * transport controls. The most important session is at index 0 and the
178 * least important at size -1.
179 *
RoboErika5b02322014-05-07 17:05:49 -0700180 * @param userId The user to check.
RoboErika8f95142014-05-05 14:23:49 -0700181 * @return All the active sessions that handle transport controls in
182 * priority order.
183 */
RoboErika5b02322014-05-07 17:05:49 -0700184 public ArrayList<MediaSessionRecord> getTransportControlSessions(int userId) {
RoboErika8f95142014-05-05 14:23:49 -0700185 if (mCachedTransportControlList == null) {
186 mCachedTransportControlList = getPriorityListLocked(true,
RoboErik42ea7ee2014-05-16 16:27:35 -0700187 MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS, userId);
RoboErika8f95142014-05-05 14:23:49 -0700188 }
189 return mCachedTransportControlList;
190 }
191
192 /**
193 * Get the highest priority active session.
194 *
RoboErika5b02322014-05-07 17:05:49 -0700195 * @param userId The user to check.
RoboErika8f95142014-05-05 14:23:49 -0700196 * @return The current highest priority session or null.
197 */
RoboErika5b02322014-05-07 17:05:49 -0700198 public MediaSessionRecord getDefaultSession(int userId) {
RoboErika8f95142014-05-05 14:23:49 -0700199 if (mCachedDefault != null) {
200 return mCachedDefault;
201 }
RoboErika5b02322014-05-07 17:05:49 -0700202 ArrayList<MediaSessionRecord> records = getPriorityListLocked(true, 0, userId);
RoboErika8f95142014-05-05 14:23:49 -0700203 if (records.size() > 0) {
204 return records.get(0);
205 }
206 return null;
207 }
208
209 /**
210 * Get the highest priority session that can handle media buttons.
211 *
Jaewan Kim8f729082016-06-21 12:36:26 +0900212 * @param userIdList The user lists to check.
RoboErik870c5a62014-12-02 15:08:26 -0800213 * @param includeNotPlaying Return a non-playing session if nothing else is
214 * available
RoboErika8f95142014-05-05 14:23:49 -0700215 * @return The default media button session or null.
216 */
Jaewan Kim8f729082016-06-21 12:36:26 +0900217 public MediaSessionRecord getDefaultMediaButtonSession(
218 List<Integer> userIdList, 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,
Jaewan Kim8f729082016-06-21 12:36:26 +0900226 MediaSession.FLAG_HANDLES_MEDIA_BUTTONS, userIdList);
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
Jaewan Kim8f729082016-06-21 12:36:26 +0900251 public MediaSessionRecord getDefaultVolumeSession(List<Integer> userIdList) {
RoboErikb69ffd42014-05-30 14:57:59 -0700252 if (mGlobalPrioritySession != null && mGlobalPrioritySession.isActive()) {
253 return mGlobalPrioritySession;
254 }
255 if (mCachedVolumeDefault != null) {
256 return mCachedVolumeDefault;
257 }
Jaewan Kim8f729082016-06-21 12:36:26 +0900258 ArrayList<MediaSessionRecord> records = getPriorityListLocked(true, 0, userIdList);
RoboErikb69ffd42014-05-30 14:57:59 -0700259 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
Jaewan Kim8f729082016-06-21 12:36:26 +0900301 private ArrayList<MediaSessionRecord> getPriorityListLocked(boolean activeOnly, int withFlags,
302 int userId) {
303 List<Integer> userIdList = new ArrayList<>();
304 userIdList.add(userId);
305 return getPriorityListLocked(activeOnly, withFlags, userIdList);
306 }
307
RoboErika8f95142014-05-05 14:23:49 -0700308 /**
309 * Get a priority sorted list of sessions. Can filter to only return active
310 * sessions or sessions with specific flags.
311 *
312 * @param activeOnly True to only return active sessions, false to return
313 * all sessions.
314 * @param withFlags Only return sessions with all the specified flags set. 0
315 * returns all sessions.
Jaewan Kim8f729082016-06-21 12:36:26 +0900316 * @param userIdList The user to get sessions for. {@link UserHandle#USER_ALL}
RoboErika5b02322014-05-07 17:05:49 -0700317 * will return sessions for all users.
RoboErika8f95142014-05-05 14:23:49 -0700318 * @return The priority sorted list of sessions.
319 */
RoboErika5b02322014-05-07 17:05:49 -0700320 private ArrayList<MediaSessionRecord> getPriorityListLocked(boolean activeOnly, int withFlags,
Jaewan Kim8f729082016-06-21 12:36:26 +0900321 List<Integer> userIdList) {
RoboErika8f95142014-05-05 14:23:49 -0700322 ArrayList<MediaSessionRecord> result = new ArrayList<MediaSessionRecord>();
323 int lastLocalIndex = 0;
324 int lastActiveIndex = 0;
325 int lastPublishedIndex = 0;
326
Jaewan Kim8f729082016-06-21 12:36:26 +0900327 boolean filterUser = !userIdList.contains(UserHandle.USER_ALL);
RoboErika8f95142014-05-05 14:23:49 -0700328 int size = mSessions.size();
329 for (int i = 0; i < size; i++) {
330 final MediaSessionRecord session = mSessions.get(i);
331
Jaewan Kim8f729082016-06-21 12:36:26 +0900332 if (filterUser && !userIdList.contains(session.getUserId())) {
RoboErika5b02322014-05-07 17:05:49 -0700333 // Filter out sessions for the wrong user
334 continue;
335 }
RoboErika8f95142014-05-05 14:23:49 -0700336 if ((session.getFlags() & withFlags) != withFlags) {
RoboErika5b02322014-05-07 17:05:49 -0700337 // Filter out sessions with the wrong flags
RoboErika8f95142014-05-05 14:23:49 -0700338 continue;
339 }
340 if (!session.isActive()) {
341 if (!activeOnly) {
342 // If we're getting unpublished as well always put them at
343 // the end
344 result.add(session);
345 }
346 continue;
347 }
348
349 if (session.isSystemPriority()) {
350 // System priority sessions are special and always go at the
351 // front. We expect there to only be one of these at a time.
352 result.add(0, session);
353 lastLocalIndex++;
354 lastActiveIndex++;
355 lastPublishedIndex++;
RoboErikb69ffd42014-05-30 14:57:59 -0700356 } else if (session.isPlaybackActive(true)) {
Jeff Brown01a500e2014-07-10 22:50:50 -0700357 // TODO this with real local route check
358 if (true) {
RoboErika8f95142014-05-05 14:23:49 -0700359 // Active local sessions get top priority
360 result.add(lastLocalIndex, session);
361 lastLocalIndex++;
362 lastActiveIndex++;
363 lastPublishedIndex++;
364 } else {
365 // Then active remote sessions
366 result.add(lastActiveIndex, session);
367 lastActiveIndex++;
368 lastPublishedIndex++;
369 }
370 } else {
371 // inactive sessions go at the end in order of whoever last did
372 // something.
373 result.add(lastPublishedIndex, session);
374 lastPublishedIndex++;
375 }
376 }
377
378 return result;
379 }
380
381 private boolean shouldUpdatePriority(int oldState, int newState) {
382 if (containsState(newState, ALWAYS_PRIORITY_STATES)) {
383 return true;
384 }
385 if (!containsState(oldState, TRANSITION_PRIORITY_STATES)
386 && containsState(newState, TRANSITION_PRIORITY_STATES)) {
387 return true;
388 }
389 return false;
390 }
391
392 private boolean containsState(int state, int[] states) {
393 for (int i = 0; i < states.length; i++) {
394 if (states[i] == state) {
395 return true;
396 }
397 }
398 return false;
399 }
400
401 private void clearCache() {
402 mCachedDefault = null;
RoboErikb69ffd42014-05-30 14:57:59 -0700403 mCachedVolumeDefault = null;
RoboErika8f95142014-05-05 14:23:49 -0700404 mCachedButtonReceiver = null;
405 mCachedActiveList = null;
406 mCachedTransportControlList = null;
407 }
408}