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