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