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.annotation.NonNull; 20 import android.content.Context; 21 import android.media.AudioManager; 22 import android.media.AudioPlaybackConfiguration; 23 import android.os.Handler; 24 import android.os.Looper; 25 import android.os.Message; 26 import android.os.UserHandle; 27 import android.util.ArrayMap; 28 import android.util.ArraySet; 29 import android.util.Log; 30 31 import com.android.internal.annotations.GuardedBy; 32 33 import java.io.PrintWriter; 34 import java.util.ArrayList; 35 import java.util.List; 36 import java.util.Map; 37 import java.util.Set; 38 39 /** 40 * Monitors the state changes of audio players. 41 */ 42 class AudioPlayerStateMonitor { 43 private static final boolean DEBUG = MediaSessionService.DEBUG; 44 private static String TAG = "AudioPlayerStateMonitor"; 45 46 private static AudioPlayerStateMonitor sInstance; 47 48 /** 49 * Listener for handling the active state changes of audio players. 50 */ 51 interface OnAudioPlayerActiveStateChangedListener { 52 /** 53 * Called when the active state of audio player is changed. 54 * 55 * @param config The audio playback configuration for the audio player for which active 56 * state was changed. If {@param isRemoved} is {@code true}, this holds 57 * outdated information. 58 * @param isRemoved {@code true} if the audio player is removed. 59 */ onAudioPlayerActiveStateChanged( @onNull AudioPlaybackConfiguration config, boolean isRemoved)60 void onAudioPlayerActiveStateChanged( 61 @NonNull AudioPlaybackConfiguration config, boolean isRemoved); 62 } 63 64 private final static class MessageHandler extends Handler { 65 private static final int MSG_AUDIO_PLAYER_ACTIVE_STATE_CHANGED = 1; 66 67 private final OnAudioPlayerActiveStateChangedListener mListener; 68 MessageHandler(Looper looper, OnAudioPlayerActiveStateChangedListener listener)69 MessageHandler(Looper looper, OnAudioPlayerActiveStateChangedListener listener) { 70 super(looper); 71 mListener = listener; 72 } 73 74 @Override handleMessage(Message msg)75 public void handleMessage(Message msg) { 76 switch (msg.what) { 77 case MSG_AUDIO_PLAYER_ACTIVE_STATE_CHANGED: 78 mListener.onAudioPlayerActiveStateChanged((AudioPlaybackConfiguration) msg.obj, 79 msg.arg1 != 0); 80 break; 81 } 82 } 83 sendAudioPlayerActiveStateChangedMessage( final AudioPlaybackConfiguration config, final boolean isRemoved)84 void sendAudioPlayerActiveStateChangedMessage( 85 final AudioPlaybackConfiguration config, final boolean isRemoved) { 86 obtainMessage(MSG_AUDIO_PLAYER_ACTIVE_STATE_CHANGED, 87 isRemoved ? 1 : 0, 0 /* unused */, config).sendToTarget(); 88 } 89 } 90 91 private final Object mLock = new Object(); 92 @GuardedBy("mLock") 93 private final Map<OnAudioPlayerActiveStateChangedListener, MessageHandler> mListenerMap = 94 new ArrayMap<>(); 95 @GuardedBy("mLock") 96 @SuppressWarnings("WeakerAccess") /* synthetic access */ 97 final Set<Integer> mActiveAudioUids = new ArraySet<>(); 98 @GuardedBy("mLock") 99 @SuppressWarnings("WeakerAccess") /* synthetic access */ 100 ArrayMap<Integer, AudioPlaybackConfiguration> mPrevActiveAudioPlaybackConfigs = 101 new ArrayMap<>(); 102 // Sorted array of UIDs that had active audio playback. (i.e. playing an audio/video) 103 // The UID whose audio playback becomes active at the last comes first. 104 // TODO(b/35278867): Find and use unique identifier for apps because apps may share the UID. 105 @GuardedBy("mLock") 106 @SuppressWarnings("WeakerAccess") /* synthetic access */ 107 final List<Integer> mSortedAudioPlaybackClientUids = new ArrayList<>(); 108 getInstance(Context context)109 static AudioPlayerStateMonitor getInstance(Context context) { 110 synchronized (AudioPlayerStateMonitor.class) { 111 if (sInstance == null) { 112 sInstance = new AudioPlayerStateMonitor(context); 113 } 114 return sInstance; 115 } 116 } 117 AudioPlayerStateMonitor(Context context)118 private AudioPlayerStateMonitor(Context context) { 119 AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); 120 am.registerAudioPlaybackCallback(new AudioManagerPlaybackListener(), null); 121 } 122 123 /** 124 * Registers OnAudioPlayerActiveStateChangedListener. 125 */ registerListener( OnAudioPlayerActiveStateChangedListener listener, Handler handler)126 public void registerListener( 127 OnAudioPlayerActiveStateChangedListener listener, Handler handler) { 128 synchronized (mLock) { 129 mListenerMap.put(listener, new MessageHandler((handler == null) ? 130 Looper.myLooper() : handler.getLooper(), listener)); 131 } 132 } 133 134 /** 135 * Unregisters OnAudioPlayerActiveStateChangedListener. 136 */ unregisterListener(OnAudioPlayerActiveStateChangedListener listener)137 public void unregisterListener(OnAudioPlayerActiveStateChangedListener listener) { 138 synchronized (mLock) { 139 mListenerMap.remove(listener); 140 } 141 } 142 143 /** 144 * Returns the sorted list of UIDs that have had active audio playback. (i.e. playing an 145 * audio/video) The UID whose audio is currently playing comes first, then the UID whose audio 146 * playback becomes active at the last comes next. 147 */ getSortedAudioPlaybackClientUids()148 public List<Integer> getSortedAudioPlaybackClientUids() { 149 List<Integer> sortedAudioPlaybackClientUids = new ArrayList(); 150 synchronized (mLock) { 151 sortedAudioPlaybackClientUids.addAll(mSortedAudioPlaybackClientUids); 152 } 153 return sortedAudioPlaybackClientUids; 154 } 155 156 /** 157 * Returns whether the given uid corresponds to the last process to audio or not. 158 * 159 * <p> This is useful for handling the cases where the foreground app is playing media without 160 * a media session. Then, volume events should affect the local music stream rather than other 161 * media sessions. 162 * 163 * @return {@code true} if the given uid corresponds to the last process to audio or 164 * {@code false} otherwise. 165 */ hasUidPlayedAudioLast(int uid)166 public boolean hasUidPlayedAudioLast(int uid) { 167 synchronized (mLock) { 168 return !mSortedAudioPlaybackClientUids.isEmpty() 169 && uid == mSortedAudioPlaybackClientUids.get(0); 170 } 171 } 172 173 /** 174 * Returns if the audio playback is active for the uid. 175 */ isPlaybackActive(int uid)176 public boolean isPlaybackActive(int uid) { 177 synchronized (mLock) { 178 return mActiveAudioUids.contains(uid); 179 } 180 } 181 182 /** 183 * Cleans up the sorted list of audio playback client UIDs with given {@param 184 * mediaButtonSessionUid}. 185 * <p>UIDs whose audio playback are inactive and have started before the media button session's 186 * audio playback cannot be the lastly played media app. So they won't be needed anymore. 187 * 188 * @param mediaButtonSessionUid UID of the media button session. 189 */ cleanUpAudioPlaybackUids(int mediaButtonSessionUid)190 public void cleanUpAudioPlaybackUids(int mediaButtonSessionUid) { 191 synchronized (mLock) { 192 int userId = UserHandle.getUserHandleForUid(mediaButtonSessionUid).getIdentifier(); 193 for (int i = mSortedAudioPlaybackClientUids.size() - 1; i >= 0; i--) { 194 if (mSortedAudioPlaybackClientUids.get(i) == mediaButtonSessionUid) { 195 break; 196 } 197 int uid = mSortedAudioPlaybackClientUids.get(i); 198 if (userId == UserHandle.getUserHandleForUid(uid).getIdentifier() 199 && !isPlaybackActive(uid)) { 200 // Clean up unnecessary UIDs. 201 // It doesn't need to be managed profile aware because it's just to prevent 202 // the list from increasing indefinitely. The media button session updating 203 // shouldn't be affected by cleaning up. 204 mSortedAudioPlaybackClientUids.remove(i); 205 } 206 } 207 } 208 } 209 210 /** 211 * Dumps {@link AudioPlayerStateMonitor}. 212 */ dump(Context context, PrintWriter pw, String prefix)213 public void dump(Context context, PrintWriter pw, String prefix) { 214 synchronized (mLock) { 215 pw.println(prefix + "Audio playback (lastly played comes first)"); 216 String indent = prefix + " "; 217 for (int i = 0; i < mSortedAudioPlaybackClientUids.size(); i++) { 218 int uid = mSortedAudioPlaybackClientUids.get(i); 219 pw.print(indent + "uid=" + uid + " packages="); 220 String[] packages = context.getPackageManager().getPackagesForUid(uid); 221 if (packages != null && packages.length > 0) { 222 for (int j = 0; j < packages.length; j++) { 223 pw.print(packages[j] + " "); 224 } 225 } 226 pw.println(); 227 } 228 } 229 } 230 231 @GuardedBy("mLock") sendAudioPlayerActiveStateChangedMessageLocked( final AudioPlaybackConfiguration config, final boolean isRemoved)232 private void sendAudioPlayerActiveStateChangedMessageLocked( 233 final AudioPlaybackConfiguration config, final boolean isRemoved) { 234 for (MessageHandler messageHandler : mListenerMap.values()) { 235 messageHandler.sendAudioPlayerActiveStateChangedMessage(config, isRemoved); 236 } 237 } 238 239 private class AudioManagerPlaybackListener extends AudioManager.AudioPlaybackCallback { 240 @Override onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configs)241 public void onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configs) { 242 synchronized (mLock) { 243 // Update mActiveAudioUids 244 mActiveAudioUids.clear(); 245 ArrayMap<Integer, AudioPlaybackConfiguration> activeAudioPlaybackConfigs = 246 new ArrayMap<>(); 247 for (AudioPlaybackConfiguration config : configs) { 248 if (config.isActive()) { 249 mActiveAudioUids.add(config.getClientUid()); 250 activeAudioPlaybackConfigs.put(config.getPlayerInterfaceId(), config); 251 } 252 } 253 254 // Update mSortedAudioPlaybackClientUids. 255 for (int i = 0; i < activeAudioPlaybackConfigs.size(); ++i) { 256 AudioPlaybackConfiguration config = activeAudioPlaybackConfigs.valueAt(i); 257 final int uid = config.getClientUid(); 258 if (!mPrevActiveAudioPlaybackConfigs.containsKey( 259 config.getPlayerInterfaceId())) { 260 if (DEBUG) { 261 Log.d(TAG, "Found a new active media playback. " + config); 262 } 263 // New active audio playback. 264 int index = mSortedAudioPlaybackClientUids.indexOf(uid); 265 if (index == 0) { 266 // It's the lastly played music app already. Skip updating. 267 continue; 268 } else if (index > 0) { 269 mSortedAudioPlaybackClientUids.remove(index); 270 } 271 mSortedAudioPlaybackClientUids.add(0, uid); 272 } 273 } 274 275 if (mActiveAudioUids.size() > 0 276 && !mActiveAudioUids.contains(mSortedAudioPlaybackClientUids.get(0))) { 277 int firstActiveUid = -1; 278 int firstActiveUidIndex = -1; 279 for (int i = 1; i < mSortedAudioPlaybackClientUids.size(); ++i) { 280 int uid = mSortedAudioPlaybackClientUids.get(i); 281 if (mActiveAudioUids.contains(uid)) { 282 firstActiveUidIndex = i; 283 firstActiveUid = uid; 284 break; 285 } 286 } 287 for (int i = firstActiveUidIndex; i > 0; --i) { 288 mSortedAudioPlaybackClientUids.set(i, 289 mSortedAudioPlaybackClientUids.get(i - 1)); 290 } 291 mSortedAudioPlaybackClientUids.set(0, firstActiveUid); 292 } 293 294 // Notify the active state change of audio players. 295 for (AudioPlaybackConfiguration config : configs) { 296 final int pii = config.getPlayerInterfaceId(); 297 boolean wasActive = mPrevActiveAudioPlaybackConfigs.remove(pii) != null; 298 if (wasActive != config.isActive()) { 299 sendAudioPlayerActiveStateChangedMessageLocked( 300 config, /* isRemoved */ false); 301 } 302 } 303 for (AudioPlaybackConfiguration config : mPrevActiveAudioPlaybackConfigs.values()) { 304 sendAudioPlayerActiveStateChangedMessageLocked(config, /* isRemoved */ true); 305 } 306 307 // Update mPrevActiveAudioPlaybackConfigs 308 mPrevActiveAudioPlaybackConfigs = activeAudioPlaybackConfigs; 309 } 310 } 311 } 312 } 313