MediaSessionStack.java revision 42ea7eecd149161ed192d3029f0d77d1d08a4aa5
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.PLAYSTATE_FAST_FORWARDING, 37 PlaybackState.PLAYSTATE_REWINDING, 38 PlaybackState.PLAYSTATE_SKIPPING_BACKWARDS, 39 PlaybackState.PLAYSTATE_SKIPPING_FORWARDS }; 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.PLAYSTATE_BUFFERING, 46 PlaybackState.PLAYSTATE_CONNECTING, 47 PlaybackState.PLAYSTATE_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 ArrayList<MediaSessionRecord> mCachedActiveList; 56 private ArrayList<MediaSessionRecord> mCachedTransportControlList; 57 58 /** 59 * Add a record to the priority tracker. 60 * 61 * @param record The record to add. 62 */ 63 public void addSession(MediaSessionRecord record) { 64 mSessions.add(record); 65 if ((record.getFlags() & MediaSession.FLAG_EXCLUSIVE_GLOBAL_PRIORITY) != 0) { 66 mGlobalPrioritySession = record; 67 } 68 clearCache(); 69 } 70 71 /** 72 * Remove a record from the priority tracker. 73 * 74 * @param record The record to remove. 75 */ 76 public void removeSession(MediaSessionRecord record) { 77 mSessions.remove(record); 78 if (record == mGlobalPrioritySession) { 79 mGlobalPrioritySession = null; 80 } 81 clearCache(); 82 } 83 84 /** 85 * Notify the priority tracker that a session's state changed. 86 * 87 * @param record The record that changed. 88 * @param oldState Its old playback state. 89 * @param newState Its new playback state. 90 */ 91 public void onPlaystateChange(MediaSessionRecord record, int oldState, int newState) { 92 if (shouldUpdatePriority(oldState, newState)) { 93 mSessions.remove(record); 94 mSessions.add(0, record); 95 clearCache(); 96 } 97 } 98 99 /** 100 * Handle any stack changes that need to occur in response to a session 101 * state change. TODO add the old and new session state as params 102 * 103 * @param record The record that changed. 104 */ 105 public void onSessionStateChange(MediaSessionRecord record) { 106 // For now just clear the cache. Eventually we'll selectively clear 107 // depending on what changed. 108 clearCache(); 109 } 110 111 /** 112 * Get the current priority sorted list of active sessions. The most 113 * important session is at index 0 and the least important at size - 1. 114 * 115 * @param userId The user to check. 116 * @return All the active sessions in priority order. 117 */ 118 public ArrayList<MediaSessionRecord> getActiveSessions(int userId) { 119 if (mCachedActiveList == null) { 120 mCachedActiveList = getPriorityListLocked(true, 0, userId); 121 } 122 return mCachedActiveList; 123 } 124 125 /** 126 * Get the current priority sorted list of active sessions that use 127 * transport controls. The most important session is at index 0 and the 128 * least important at size -1. 129 * 130 * @param userId The user to check. 131 * @return All the active sessions that handle transport controls in 132 * priority order. 133 */ 134 public ArrayList<MediaSessionRecord> getTransportControlSessions(int userId) { 135 if (mCachedTransportControlList == null) { 136 mCachedTransportControlList = getPriorityListLocked(true, 137 MediaSession.FLAG_HANDLES_TRANSPORT_CONTROLS, userId); 138 } 139 return mCachedTransportControlList; 140 } 141 142 /** 143 * Get the highest priority active session. 144 * 145 * @param userId The user to check. 146 * @return The current highest priority session or null. 147 */ 148 public MediaSessionRecord getDefaultSession(int userId) { 149 if (mCachedDefault != null) { 150 return mCachedDefault; 151 } 152 ArrayList<MediaSessionRecord> records = getPriorityListLocked(true, 0, userId); 153 if (records.size() > 0) { 154 return records.get(0); 155 } 156 return null; 157 } 158 159 /** 160 * Get the highest priority session that can handle media buttons. 161 * 162 * @param userId The user to check. 163 * @return The default media button session or null. 164 */ 165 public MediaSessionRecord getDefaultMediaButtonSession(int userId) { 166 if (mGlobalPrioritySession != null && mGlobalPrioritySession.isActive()) { 167 return mGlobalPrioritySession; 168 } 169 if (mCachedButtonReceiver != null) { 170 return mCachedButtonReceiver; 171 } 172 ArrayList<MediaSessionRecord> records = getPriorityListLocked(true, 173 MediaSession.FLAG_HANDLES_MEDIA_BUTTONS, userId); 174 if (records.size() > 0) { 175 mCachedButtonReceiver = records.get(0); 176 } 177 return mCachedButtonReceiver; 178 } 179 180 public void dump(PrintWriter pw, String prefix) { 181 ArrayList<MediaSessionRecord> sortedSessions = getPriorityListLocked(false, 0, 182 UserHandle.USER_ALL); 183 int count = sortedSessions.size(); 184 pw.println(prefix + "Sessions Stack - have " + count + " sessions:"); 185 String indent = prefix + " "; 186 for (int i = 0; i < count; i++) { 187 MediaSessionRecord record = sortedSessions.get(i); 188 record.dump(pw, indent); 189 pw.println(); 190 } 191 } 192 193 /** 194 * Get a priority sorted list of sessions. Can filter to only return active 195 * sessions or sessions with specific flags. 196 * 197 * @param activeOnly True to only return active sessions, false to return 198 * all sessions. 199 * @param withFlags Only return sessions with all the specified flags set. 0 200 * returns all sessions. 201 * @param userId The user to get sessions for. {@link UserHandle#USER_ALL} 202 * will return sessions for all users. 203 * @return The priority sorted list of sessions. 204 */ 205 private ArrayList<MediaSessionRecord> getPriorityListLocked(boolean activeOnly, int withFlags, 206 int userId) { 207 ArrayList<MediaSessionRecord> result = new ArrayList<MediaSessionRecord>(); 208 int lastLocalIndex = 0; 209 int lastActiveIndex = 0; 210 int lastPublishedIndex = 0; 211 212 int size = mSessions.size(); 213 for (int i = 0; i < size; i++) { 214 final MediaSessionRecord session = mSessions.get(i); 215 216 if (userId != UserHandle.USER_ALL && userId != session.getUserId()) { 217 // Filter out sessions for the wrong user 218 continue; 219 } 220 if ((session.getFlags() & withFlags) != withFlags) { 221 // Filter out sessions with the wrong flags 222 continue; 223 } 224 if (!session.isActive()) { 225 if (!activeOnly) { 226 // If we're getting unpublished as well always put them at 227 // the end 228 result.add(session); 229 } 230 continue; 231 } 232 233 if (session.isSystemPriority()) { 234 // System priority sessions are special and always go at the 235 // front. We expect there to only be one of these at a time. 236 result.add(0, session); 237 lastLocalIndex++; 238 lastActiveIndex++; 239 lastPublishedIndex++; 240 } else if (session.isPlaybackActive()) { 241 // TODO replace getRoute() == null with real local route check 242 if(session.getRoute() == null) { 243 // Active local sessions get top priority 244 result.add(lastLocalIndex, session); 245 lastLocalIndex++; 246 lastActiveIndex++; 247 lastPublishedIndex++; 248 } else { 249 // Then active remote sessions 250 result.add(lastActiveIndex, session); 251 lastActiveIndex++; 252 lastPublishedIndex++; 253 } 254 } else { 255 // inactive sessions go at the end in order of whoever last did 256 // something. 257 result.add(lastPublishedIndex, session); 258 lastPublishedIndex++; 259 } 260 } 261 262 return result; 263 } 264 265 private boolean shouldUpdatePriority(int oldState, int newState) { 266 if (containsState(newState, ALWAYS_PRIORITY_STATES)) { 267 return true; 268 } 269 if (!containsState(oldState, TRANSITION_PRIORITY_STATES) 270 && containsState(newState, TRANSITION_PRIORITY_STATES)) { 271 return true; 272 } 273 return false; 274 } 275 276 private boolean containsState(int state, int[] states) { 277 for (int i = 0; i < states.length; i++) { 278 if (states[i] == state) { 279 return true; 280 } 281 } 282 return false; 283 } 284 285 private void clearCache() { 286 mCachedDefault = null; 287 mCachedButtonReceiver = null; 288 mCachedActiveList = null; 289 mCachedTransportControlList = null; 290 } 291} 292