MediaSessionStack.java revision 2e7a9167aeefeb451f8d8c769175b9a0163744f3
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 void dump(PrintWriter pw, String prefix) { 207 ArrayList<MediaSessionRecord> sortedSessions = getPriorityListLocked(false, 0, 208 UserHandle.USER_ALL); 209 int count = sortedSessions.size(); 210 pw.println(prefix + "Sessions Stack - have " + count + " sessions:"); 211 String indent = prefix + " "; 212 for (int i = 0; i < count; i++) { 213 MediaSessionRecord record = sortedSessions.get(i); 214 record.dump(pw, indent); 215 pw.println(); 216 } 217 } 218 219 /** 220 * Get a priority sorted list of sessions. Can filter to only return active 221 * sessions or sessions with specific flags. 222 * 223 * @param activeOnly True to only return active sessions, false to return 224 * all sessions. 225 * @param withFlags Only return sessions with all the specified flags set. 0 226 * returns all sessions. 227 * @param userId The user to get sessions for. {@link UserHandle#USER_ALL} 228 * will return sessions for all users. 229 * @return The priority sorted list of sessions. 230 */ 231 private ArrayList<MediaSessionRecord> getPriorityListLocked(boolean activeOnly, int withFlags, 232 int userId) { 233 ArrayList<MediaSessionRecord> result = new ArrayList<MediaSessionRecord>(); 234 int lastLocalIndex = 0; 235 int lastActiveIndex = 0; 236 int lastPublishedIndex = 0; 237 238 int size = mSessions.size(); 239 for (int i = 0; i < size; i++) { 240 final MediaSessionRecord session = mSessions.get(i); 241 242 if (userId != UserHandle.USER_ALL && userId != session.getUserId()) { 243 // Filter out sessions for the wrong user 244 continue; 245 } 246 if ((session.getFlags() & withFlags) != withFlags) { 247 // Filter out sessions with the wrong flags 248 continue; 249 } 250 if (!session.isActive()) { 251 if (!activeOnly) { 252 // If we're getting unpublished as well always put them at 253 // the end 254 result.add(session); 255 } 256 continue; 257 } 258 259 if (session.isSystemPriority()) { 260 // System priority sessions are special and always go at the 261 // front. We expect there to only be one of these at a time. 262 result.add(0, session); 263 lastLocalIndex++; 264 lastActiveIndex++; 265 lastPublishedIndex++; 266 } else if (session.isPlaybackActive(true)) { 267 // TODO replace getRoute() == null with real local route check 268 if(session.getRoute() == null) { 269 // Active local sessions get top priority 270 result.add(lastLocalIndex, session); 271 lastLocalIndex++; 272 lastActiveIndex++; 273 lastPublishedIndex++; 274 } else { 275 // Then active remote sessions 276 result.add(lastActiveIndex, session); 277 lastActiveIndex++; 278 lastPublishedIndex++; 279 } 280 } else { 281 // inactive sessions go at the end in order of whoever last did 282 // something. 283 result.add(lastPublishedIndex, session); 284 lastPublishedIndex++; 285 } 286 } 287 288 return result; 289 } 290 291 private boolean shouldUpdatePriority(int oldState, int newState) { 292 if (containsState(newState, ALWAYS_PRIORITY_STATES)) { 293 return true; 294 } 295 if (!containsState(oldState, TRANSITION_PRIORITY_STATES) 296 && containsState(newState, TRANSITION_PRIORITY_STATES)) { 297 return true; 298 } 299 return false; 300 } 301 302 private boolean containsState(int state, int[] states) { 303 for (int i = 0; i < states.length; i++) { 304 if (states[i] == state) { 305 return true; 306 } 307 } 308 return false; 309 } 310 311 private void clearCache() { 312 mCachedDefault = null; 313 mCachedVolumeDefault = null; 314 mCachedButtonReceiver = null; 315 mCachedActiveList = null; 316 mCachedTransportControlList = null; 317 } 318} 319