1 /* 2 * Copyright (C) 2017 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 17 package com.android.server.media; 18 19 import android.content.Context; 20 import android.media.AudioManager.AudioPlaybackCallback; 21 import android.media.AudioPlaybackConfiguration; 22 import android.media.IAudioService; 23 import android.media.IPlaybackConfigDispatcher; 24 import android.os.Binder; 25 import android.os.RemoteException; 26 import android.os.UserHandle; 27 import android.util.IntArray; 28 import android.util.Log; 29 import android.util.SparseArray; 30 31 import java.io.PrintWriter; 32 import java.util.ArrayList; 33 import java.util.HashSet; 34 import java.util.HashMap; 35 import java.util.List; 36 import java.util.Map; 37 import java.util.Set; 38 39 /** 40 * Monitors changes in audio playback, and notify the newly started audio playback through the 41 * {@link OnAudioPlaybackStartedListener} and the activeness change through the 42 * {@link OnAudioPlaybackActiveStateListener}. 43 */ 44 class AudioPlaybackMonitor extends IPlaybackConfigDispatcher.Stub { 45 private static boolean DEBUG = MediaSessionService.DEBUG; 46 private static String TAG = "AudioPlaybackMonitor"; 47 48 private static AudioPlaybackMonitor sInstance; 49 50 /** 51 * Called when audio playback is started for a given UID. 52 */ 53 interface OnAudioPlaybackStartedListener { 54 void onAudioPlaybackStarted(int uid); 55 } 56 57 /** 58 * Called when audio player state is changed. 59 */ 60 interface OnAudioPlayerActiveStateChangedListener { 61 void onAudioPlayerActiveStateChanged(int uid, boolean active); 62 } 63 64 private final Object mLock = new Object(); 65 private final Context mContext; 66 private final List<OnAudioPlaybackStartedListener> mAudioPlaybackStartedListeners 67 = new ArrayList<>(); 68 private final List<OnAudioPlayerActiveStateChangedListener> 69 mAudioPlayerActiveStateChangedListeners = new ArrayList<>(); 70 private final Map<Integer, Integer> mAudioPlaybackStates = new HashMap<>(); 71 private final Set<Integer> mActiveAudioPlaybackClientUids = new HashSet<>(); 72 73 // Sorted array of UIDs that had active audio playback. (i.e. playing an audio/video) 74 // The UID whose audio playback becomes active at the last comes first. 75 // TODO(b/35278867): Find and use unique identifier for apps because apps may share the UID. 76 private final IntArray mSortedAudioPlaybackClientUids = new IntArray(); 77 78 static AudioPlaybackMonitor getInstance(Context context, IAudioService audioService) { 79 if (sInstance == null) { 80 sInstance = new AudioPlaybackMonitor(context, audioService); 81 } 82 return sInstance; 83 } 84 85 private AudioPlaybackMonitor(Context context, IAudioService audioService) { 86 mContext = context; 87 try { 88 audioService.registerPlaybackCallback(this); 89 } catch (RemoteException e) { 90 Log.wtf(TAG, "Failed to register playback callback", e); 91 } 92 } 93 94 /** 95 * Called when the {@link AudioPlaybackConfiguration} is updated. 96 * <p>If an app starts audio playback, the app's local media session will be the media button 97 * session. If the app has multiple media sessions, the playback active local session will be 98 * picked. 99 * 100 * @param configs List of the current audio playback configuration 101 */ 102 @Override 103 public void dispatchPlaybackConfigChange(List<AudioPlaybackConfiguration> configs, 104 boolean flush) { 105 if (flush) { 106 Binder.flushPendingCommands(); 107 } 108 final long token = Binder.clearCallingIdentity(); 109 try { 110 List<Integer> newActiveAudioPlaybackClientUids = new ArrayList<>(); 111 List<OnAudioPlayerActiveStateChangedListener> audioPlayerActiveStateChangedListeners; 112 List<OnAudioPlaybackStartedListener> audioPlaybackStartedListeners; 113 synchronized (mLock) { 114 // Update mActiveAudioPlaybackClientUids and mSortedAudioPlaybackClientUids, 115 // and find newly activated audio playbacks. 116 mActiveAudioPlaybackClientUids.clear(); 117 for (AudioPlaybackConfiguration config : configs) { 118 // Ignore inactive (i.e. not playing) or PLAYER_TYPE_JAM_SOUNDPOOL 119 // (i.e. playback from the SoundPool class which is only for sound effects) 120 // playback. 121 // Note that we shouldn't ignore PLAYER_TYPE_UNKNOWN because it might be OEM 122 // specific audio/video players. 123 if (!config.isActive() || config.getPlayerType() 124 == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) { 125 continue; 126 } 127 128 mActiveAudioPlaybackClientUids.add(config.getClientUid()); 129 Integer oldState = mAudioPlaybackStates.get(config.getPlayerInterfaceId()); 130 if (!isActiveState(oldState)) { 131 if (DEBUG) { 132 Log.d(TAG, "Found a new active media playback. " + 133 AudioPlaybackConfiguration.toLogFriendlyString(config)); 134 } 135 // New active audio playback. 136 newActiveAudioPlaybackClientUids.add(config.getClientUid()); 137 int index = mSortedAudioPlaybackClientUids.indexOf(config.getClientUid()); 138 if (index == 0) { 139 // It's the lastly played music app already. Skip updating. 140 continue; 141 } else if (index > 0) { 142 mSortedAudioPlaybackClientUids.remove(index); 143 } 144 mSortedAudioPlaybackClientUids.add(0, config.getClientUid()); 145 } 146 } 147 audioPlayerActiveStateChangedListeners = new ArrayList<>( 148 mAudioPlayerActiveStateChangedListeners); 149 audioPlaybackStartedListeners = new ArrayList<>(mAudioPlaybackStartedListeners); 150 } 151 // Notify the change of audio playback states. 152 for (AudioPlaybackConfiguration config : configs) { 153 boolean wasActive = isActiveState( 154 mAudioPlaybackStates.get(config.getPlayerInterfaceId())); 155 boolean isActive = config.isActive(); 156 if (wasActive != isActive) { 157 for (OnAudioPlayerActiveStateChangedListener listener 158 : audioPlayerActiveStateChangedListeners) { 159 listener.onAudioPlayerActiveStateChanged(config.getClientUid(), 160 isActive); 161 } 162 } 163 } 164 // Notify the start of audio playback 165 for (int uid : newActiveAudioPlaybackClientUids) { 166 for (OnAudioPlaybackStartedListener listener : audioPlaybackStartedListeners) { 167 listener.onAudioPlaybackStarted(uid); 168 } 169 } 170 mAudioPlaybackStates.clear(); 171 for (AudioPlaybackConfiguration config : configs) { 172 mAudioPlaybackStates.put(config.getPlayerInterfaceId(), config.getPlayerState()); 173 } 174 } finally { 175 Binder.restoreCallingIdentity(token); 176 } 177 } 178 179 /** 180 * Registers OnAudioPlaybackStartedListener. 181 */ 182 public void registerOnAudioPlaybackStartedListener(OnAudioPlaybackStartedListener listener) { 183 synchronized (mLock) { 184 mAudioPlaybackStartedListeners.add(listener); 185 } 186 } 187 188 /** 189 * Unregisters OnAudioPlaybackStartedListener. 190 */ 191 public void unregisterOnAudioPlaybackStartedListener(OnAudioPlaybackStartedListener listener) { 192 synchronized (mLock) { 193 mAudioPlaybackStartedListeners.remove(listener); 194 } 195 } 196 197 /** 198 * Registers OnAudioPlayerActiveStateChangedListener. 199 */ 200 public void registerOnAudioPlayerActiveStateChangedListener( 201 OnAudioPlayerActiveStateChangedListener listener) { 202 synchronized (mLock) { 203 mAudioPlayerActiveStateChangedListeners.add(listener); 204 } 205 } 206 207 /** 208 * Unregisters OnAudioPlayerActiveStateChangedListener. 209 */ 210 public void unregisterOnAudioPlayerActiveStateChangedListener( 211 OnAudioPlayerActiveStateChangedListener listener) { 212 synchronized (mLock) { 213 mAudioPlayerActiveStateChangedListeners.remove(listener); 214 } 215 } 216 217 /** 218 * Returns the sorted list of UIDs that have had active audio playback. (i.e. playing an 219 * audio/video) The UID whose audio playback becomes active at the last comes first. 220 */ 221 public IntArray getSortedAudioPlaybackClientUids() { 222 IntArray sortedAudioPlaybackClientUids = new IntArray(); 223 synchronized (mLock) { 224 sortedAudioPlaybackClientUids.addAll(mSortedAudioPlaybackClientUids); 225 } 226 return sortedAudioPlaybackClientUids; 227 } 228 229 /** 230 * Returns if the audio playback is active for the uid. 231 */ 232 public boolean isPlaybackActive(int uid) { 233 synchronized (mLock) { 234 return mActiveAudioPlaybackClientUids.contains(uid); 235 } 236 } 237 238 /** 239 * Cleans up the sorted list of audio playback client UIDs with given {@param 240 * mediaButtonSessionUid}. 241 * <p>UIDs whose audio playback started after the media button session's audio playback 242 * cannot be the lastly played media app. So they won't needed anymore. 243 * 244 * @param mediaButtonSessionUid UID of the media button session. 245 */ 246 public void cleanUpAudioPlaybackUids(int mediaButtonSessionUid) { 247 synchronized (mLock) { 248 int userId = UserHandle.getUserId(mediaButtonSessionUid); 249 for (int i = mSortedAudioPlaybackClientUids.size() - 1; i >= 0; i--) { 250 if (mSortedAudioPlaybackClientUids.get(i) == mediaButtonSessionUid) { 251 break; 252 } 253 int uid = mSortedAudioPlaybackClientUids.get(i); 254 if (userId == UserHandle.getUserId(uid) && !isPlaybackActive(uid)) { 255 // Clean up unnecessary UIDs. 256 // It doesn't need to be managed profile aware because it's just to prevent 257 // the list from increasing indefinitely. The media button session updating 258 // shouldn't be affected by cleaning up. 259 mSortedAudioPlaybackClientUids.remove(i); 260 } 261 } 262 } 263 } 264 265 /** 266 * Dumps {@link AudioPlaybackMonitor}. 267 */ 268 public void dump(PrintWriter pw, String prefix) { 269 synchronized (mLock) { 270 pw.println(prefix + "Audio playback (lastly played comes first)"); 271 String indent = prefix + " "; 272 for (int i = 0; i < mSortedAudioPlaybackClientUids.size(); i++) { 273 int uid = mSortedAudioPlaybackClientUids.get(i); 274 pw.print(indent + "uid=" + uid + " packages="); 275 String[] packages = mContext.getPackageManager().getPackagesForUid(uid); 276 if (packages != null && packages.length > 0) { 277 for (int j = 0; j < packages.length; j++) { 278 pw.print(packages[j] + " "); 279 } 280 } 281 pw.println(); 282 } 283 } 284 } 285 286 private boolean isActiveState(Integer state) { 287 return state != null && state.equals(AudioPlaybackConfiguration.PLAYER_STATE_STARTED); 288 } 289 } 290