MediaSessionStack.java revision 19c9518f6a817d53d5234de0020313cab6950b2f
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.PlaybackState; 20import android.media.session.MediaSession; 21import android.os.UserHandle; 22 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 = { 36 PlaybackState.STATE_FAST_FORWARDING, 37 PlaybackState.STATE_REWINDING, 38 PlaybackState.STATE_SKIPPING_TO_PREVIOUS, 39 PlaybackState.STATE_SKIPPING_TO_NEXT }; 40 /** 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 = { 45 PlaybackState.STATE_BUFFERING, 46 PlaybackState.STATE_CONNECTING, 47 PlaybackState.STATE_PLAYING }; 48 49 private final ArrayList<MediaSessionRecord> mSessions = new ArrayList<MediaSessionRecord>(); 50 51 private MediaSessionRecord mGlobalPrioritySession; 52 53 private MediaSessionRecord mCachedButtonReceiver; 54 private MediaSessionRecord mCachedDefault; 55 private MediaSessionRecord mCachedVolumeDefault; 56 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); 66 if ((record.getFlags() & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) { 67 mGlobalPrioritySession = record; 68 } 69 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); 79 if (record == mGlobalPrioritySession) { 80 mGlobalPrioritySession = null; 81 } 82 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 * @return true if the priority order was updated, false otherwise. 92 */ 93 public boolean onPlaystateChange(MediaSessionRecord record, int oldState, int newState) { 94 if (shouldUpdatePriority(oldState, newState)) { 95 mSessions.remove(record); 96 mSessions.add(0, record); 97 clearCache(); 98 return true; 99 } else if (newState == PlaybackState.STATE_PAUSED) { 100 // Just clear the volume cache in this case 101 mCachedVolumeDefault = null; 102 } 103 return false; 104 } 105 106 /** 107 * Handle any stack changes that need to occur in response to a session 108 * state change. TODO add the old and new session state as params 109 * 110 * @param record The record that changed. 111 */ 112 public void onSessionStateChange(MediaSessionRecord record) { 113 // For now just clear the cache. Eventually we'll selectively clear 114 // depending on what changed. 115 clearCache(); 116 } 117 118 /** 119 * Get the current priority sorted list of active sessions. The most 120 * important session is at index 0 and the least important at size - 1. 121 * 122 * @param userId The user to check. 123 * @return All the active sessions in priority order. 124 */ 125 public ArrayList<MediaSessionRecord> getActiveSessions(int userId) { 126 if (mCachedActiveList == null) { 127 mCachedActiveList = getPriorityListLocked(true, 0, userId); 128 } 129 return mCachedActiveList; 130 } 131 132 /** 133 * Get the current priority sorted list of active sessions that use 134 * transport controls. The most important session is at index 0 and the 135 * least important at size -1. 136 * 137 * @param userId The user to check. 138 * @return All the active sessions that handle transport controls in 139 * priority order. 140 */ 141 public ArrayList<MediaSessionRecord> getTransportControlSessions(int userId) { 142 if (mCachedTransportControlList == null) { 143 mCachedTransportControlList = getPriorityListLocked(true, 144 MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS, userId); 145 } 146 return mCachedTransportControlList; 147 } 148 149 /** 150 * Get the highest priority active session. 151 * 152 * @param userId The user to check. 153 * @return The current highest priority session or null. 154 */ 155 public MediaSessionRecord getDefaultSession(int userId) { 156 if (mCachedDefault != null) { 157 return mCachedDefault; 158 } 159 ArrayList<MediaSessionRecord> records = getPriorityListLocked(true, 0, userId); 160 if (records.size() > 0) { 161 return records.get(0); 162 } 163 return null; 164 } 165 166 /** 167 * Get the highest priority session that can handle media buttons. 168 * 169 * @param userId The user to check. 170 * @return The default media button session or null. 171 */ 172 public MediaSessionRecord getDefaultMediaButtonSession(int userId) { 173 if (mGlobalPrioritySession != null && mGlobalPrioritySession.isActive()) { 174 return mGlobalPrioritySession; 175 } 176 if (mCachedButtonReceiver != null) { 177 return mCachedButtonReceiver; 178 } 179 ArrayList<MediaSessionRecord> records = getPriorityListLocked(true, 180 MediaSession.FLAG_HANDLES_MEDIA_BUTTONS, userId); 181 if (records.size() > 0) { 182 mCachedButtonReceiver = records.get(0); 183 } 184 return mCachedButtonReceiver; 185 } 186 187 public MediaSessionRecord getDefaultVolumeSession(int userId) { 188 if (mGlobalPrioritySession != null && mGlobalPrioritySession.isActive()) { 189 return mGlobalPrioritySession; 190 } 191 if (mCachedVolumeDefault != null) { 192 return mCachedVolumeDefault; 193 } 194 ArrayList<MediaSessionRecord> records = getPriorityListLocked(true, 0, userId); 195 int size = records.size(); 196 for (int i = 0; i < size; i++) { 197 MediaSessionRecord record = records.get(i); 198 if (record.isPlaybackActive(false)) { 199 mCachedVolumeDefault = record; 200 return record; 201 } 202 } 203 return null; 204 } 205 206 public MediaSessionRecord getDefaultRemoteSession(int userId) { 207 ArrayList<MediaSessionRecord> records = getPriorityListLocked(true, 0, userId); 208 209 int size = records.size(); 210 for (int i = 0; i < size; i++) { 211 MediaSessionRecord record = records.get(i); 212 if (record.getPlaybackType() == MediaSession.PLAYBACK_TYPE_REMOTE) { 213 return record; 214 } 215 } 216 return null; 217 } 218 219 public void dump(PrintWriter pw, String prefix) { 220 ArrayList<MediaSessionRecord> sortedSessions = getPriorityListLocked(false, 0, 221 UserHandle.USER_ALL); 222 int count = sortedSessions.size(); 223 pw.println(prefix + "Sessions Stack - have " + count + " sessions:"); 224 String indent = prefix + " "; 225 for (int i = 0; i < count; i++) { 226 MediaSessionRecord record = sortedSessions.get(i); 227 record.dump(pw, indent); 228 pw.println(); 229 } 230 } 231 232 /** 233 * Get a priority sorted list of sessions. Can filter to only return active 234 * sessions or sessions with specific flags. 235 * 236 * @param activeOnly True to only return active sessions, false to return 237 * all sessions. 238 * @param withFlags Only return sessions with all the specified flags set. 0 239 * returns all sessions. 240 * @param userId The user to get sessions for. {@link UserHandle#USER_ALL} 241 * will return sessions for all users. 242 * @return The priority sorted list of sessions. 243 */ 244 private ArrayList<MediaSessionRecord> getPriorityListLocked(boolean activeOnly, int withFlags, 245 int userId) { 246 ArrayList<MediaSessionRecord> result = new ArrayList<MediaSessionRecord>(); 247 int lastLocalIndex = 0; 248 int lastActiveIndex = 0; 249 int lastPublishedIndex = 0; 250 251 int size = mSessions.size(); 252 for (int i = 0; i < size; i++) { 253 final MediaSessionRecord session = mSessions.get(i); 254 255 if (userId != UserHandle.USER_ALL && userId != session.getUserId()) { 256 // Filter out sessions for the wrong user 257 continue; 258 } 259 if ((session.getFlags() & withFlags) != withFlags) { 260 // Filter out sessions with the wrong flags 261 continue; 262 } 263 if (!session.isActive()) { 264 if (!activeOnly) { 265 // If we're getting unpublished as well always put them at 266 // the end 267 result.add(session); 268 } 269 continue; 270 } 271 272 if (session.isSystemPriority()) { 273 // System priority sessions are special and always go at the 274 // front. We expect there to only be one of these at a time. 275 result.add(0, session); 276 lastLocalIndex++; 277 lastActiveIndex++; 278 lastPublishedIndex++; 279 } else if (session.isPlaybackActive(true)) { 280 // TODO replace getRoute() == null with real local route check 281 if(session.getRoute() == null) { 282 // Active local sessions get top priority 283 result.add(lastLocalIndex, session); 284 lastLocalIndex++; 285 lastActiveIndex++; 286 lastPublishedIndex++; 287 } else { 288 // Then active remote sessions 289 result.add(lastActiveIndex, session); 290 lastActiveIndex++; 291 lastPublishedIndex++; 292 } 293 } else { 294 // inactive sessions go at the end in order of whoever last did 295 // something. 296 result.add(lastPublishedIndex, session); 297 lastPublishedIndex++; 298 } 299 } 300 301 return result; 302 } 303 304 private boolean shouldUpdatePriority(int oldState, int newState) { 305 if (containsState(newState, ALWAYS_PRIORITY_STATES)) { 306 return true; 307 } 308 if (!containsState(oldState, TRANSITION_PRIORITY_STATES) 309 && containsState(newState, TRANSITION_PRIORITY_STATES)) { 310 return true; 311 } 312 return false; 313 } 314 315 private boolean containsState(int state, int[] states) { 316 for (int i = 0; i < states.length; i++) { 317 if (states[i] == state) { 318 return true; 319 } 320 } 321 return false; 322 } 323 324 private void clearCache() { 325 mCachedDefault = null; 326 mCachedVolumeDefault = null; 327 mCachedButtonReceiver = null; 328 mCachedActiveList = null; 329 mCachedTransportControlList = null; 330 } 331} 332