MediaSessionStack.java revision 23b113592a5f461ec66026cbf8bce253cb8d3a46
1/* 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.MediaController.PlaybackInfo; 20import android.media.session.PlaybackState; 21import android.media.session.MediaSession; 22import android.os.UserHandle; 23 24import java.io.PrintWriter; 25import java.util.ArrayList; 26 27/** 28 * Keeps track of media sessions and their priority for notifications, media 29 * button dispatch, etc. 30 */ 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 = { 37 PlaybackState.STATE_FAST_FORWARDING, 38 PlaybackState.STATE_REWINDING, 39 PlaybackState.STATE_SKIPPING_TO_PREVIOUS, 40 PlaybackState.STATE_SKIPPING_TO_NEXT }; 41 /** 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 = { 46 PlaybackState.STATE_BUFFERING, 47 PlaybackState.STATE_CONNECTING, 48 PlaybackState.STATE_PLAYING }; 49 50 private final ArrayList<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>(); 51 52 private MediaSessionRecord mGlobalPrioritySession; 53 54 private MediaSessionRecord mCachedButtonReceiver; 55 private MediaSessionRecord mCachedDefault; 56 private MediaSessionRecord mCachedVolumeDefault; 57 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); 77 if (record == mGlobalPrioritySession) { 78 mGlobalPrioritySession = null; 79 } 80 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. 89 * @return true if the priority order was updated, false otherwise. 90 */ 91 public boolean onPlaystateChange(MediaSessionRecord record, int oldState, int newState) { 92 if (shouldUpdatePriority(oldState, newState)) { 93 mSessions.remove(record); 94 mSessions.add(0, record); 95 clearCache(); 96 return true; 97 } else if (!MediaSession.isActiveState(newState)) { 98 // Just clear the volume cache when a state goes inactive 99 mCachedVolumeDefault = null; 100 } 101 return false; 102 } 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) { 111 if ((record.getFlags() & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) { 112 mGlobalPrioritySession = record; 113 } 114 // 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 * 123 * @param userId The user to check. 124 * @return All the active sessions in priority order. 125 */ 126 public ArrayList<MediaSessionRecord> getActiveSessions(int userId) { 127 if (mCachedActiveList == null) { 128 mCachedActiveList = getPriorityListLocked(true, 0, userId); 129 } 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 * 138 * @param userId The user to check. 139 * @return All the active sessions that handle transport controls in 140 * priority order. 141 */ 142 public ArrayList<MediaSessionRecord> getTransportControlSessions(int userId) { 143 if (mCachedTransportControlList == null) { 144 mCachedTransportControlList = getPriorityListLocked(true, 145 MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS, userId); 146 } 147 return mCachedTransportControlList; 148 } 149 150 /** 151 * Get the highest priority active session. 152 * 153 * @param userId The user to check. 154 * @return The current highest priority session or null. 155 */ 156 public MediaSessionRecord getDefaultSession(int userId) { 157 if (mCachedDefault != null) { 158 return mCachedDefault; 159 } 160 ArrayList<MediaSessionRecord> records = getPriorityListLocked(true, 0, userId); 161 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 * 170 * @param userId The user to check. 171 * @return The default media button session or null. 172 */ 173 public MediaSessionRecord getDefaultMediaButtonSession(int userId) { 174 if (mGlobalPrioritySession != null && mGlobalPrioritySession.isActive()) { 175 return mGlobalPrioritySession; 176 } 177 if (mCachedButtonReceiver != null) { 178 return mCachedButtonReceiver; 179 } 180 ArrayList<MediaSessionRecord> records = getPriorityListLocked(true, 181 MediaSession.FLAG_HANDLES_MEDIA_BUTTONS, userId); 182 if (records.size() > 0) { 183 mCachedButtonReceiver = records.get(0); 184 } 185 return mCachedButtonReceiver; 186 } 187 188 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 207 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); 213 if (record.getPlaybackType() == PlaybackInfo.PLAYBACK_TYPE_REMOTE) { 214 return record; 215 } 216 } 217 return null; 218 } 219 220 public void dump(PrintWriter pw, String prefix) { 221 ArrayList<MediaSessionRecord> sortedSessions = getPriorityListLocked(false, 0, 222 UserHandle.USER_ALL); 223 int count = sortedSessions.size(); 224 pw.println(prefix + "Global priority session is " + mGlobalPrioritySession); 225 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. 242 * @param userId The user to get sessions for. {@link UserHandle#USER_ALL} 243 * will return sessions for all users. 244 * @return The priority sorted list of sessions. 245 */ 246 private ArrayList<MediaSessionRecord> getPriorityListLocked(boolean activeOnly, int withFlags, 247 int userId) { 248 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 257 if (userId != UserHandle.USER_ALL && userId != session.getUserId()) { 258 // Filter out sessions for the wrong user 259 continue; 260 } 261 if ((session.getFlags() & withFlags) != withFlags) { 262 // Filter out sessions with the wrong flags 263 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++; 281 } else if (session.isPlaybackActive(true)) { 282 // TODO this with real local route check 283 if (true) { 284 // 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; 328 mCachedVolumeDefault = null; 329 mCachedButtonReceiver = null; 330 mCachedActiveList = null; 331 mCachedTransportControlList = null; 332 } 333} 334