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