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 if the audio playback is active for the uid. 158 */ isPlaybackActive(int uid)159 public boolean isPlaybackActive(int uid) { 160 synchronized (mLock) { 161 return mActiveAudioUids.contains(uid); 162 } 163 } 164 165 /** 166 * Cleans up the sorted list of audio playback client UIDs with given {@param 167 * mediaButtonSessionUid}. 168 * <p>UIDs whose audio playback are inactive and have started before the media button session's 169 * audio playback cannot be the lastly played media app. So they won't be needed anymore. 170 * 171 * @param mediaButtonSessionUid UID of the media button session. 172 */ cleanUpAudioPlaybackUids(int mediaButtonSessionUid)173 public void cleanUpAudioPlaybackUids(int mediaButtonSessionUid) { 174 synchronized (mLock) { 175 int userId = UserHandle.getUserHandleForUid(mediaButtonSessionUid).getIdentifier(); 176 for (int i = mSortedAudioPlaybackClientUids.size() - 1; i >= 0; i--) { 177 if (mSortedAudioPlaybackClientUids.get(i) == mediaButtonSessionUid) { 178 break; 179 } 180 int uid = mSortedAudioPlaybackClientUids.get(i); 181 if (userId == UserHandle.getUserHandleForUid(uid).getIdentifier() 182 && !isPlaybackActive(uid)) { 183 // Clean up unnecessary UIDs. 184 // It doesn't need to be managed profile aware because it's just to prevent 185 // the list from increasing indefinitely. The media button session updating 186 // shouldn't be affected by cleaning up. 187 mSortedAudioPlaybackClientUids.remove(i); 188 } 189 } 190 } 191 } 192 193 /** 194 * Dumps {@link AudioPlayerStateMonitor}. 195 */ dump(Context context, PrintWriter pw, String prefix)196 public void dump(Context context, PrintWriter pw, String prefix) { 197 synchronized (mLock) { 198 pw.println(prefix + "Audio playback (lastly played comes first)"); 199 String indent = prefix + " "; 200 for (int i = 0; i < mSortedAudioPlaybackClientUids.size(); i++) { 201 int uid = mSortedAudioPlaybackClientUids.get(i); 202 pw.print(indent + "uid=" + uid + " packages="); 203 String[] packages = context.getPackageManager().getPackagesForUid(uid); 204 if (packages != null && packages.length > 0) { 205 for (int j = 0; j < packages.length; j++) { 206 pw.print(packages[j] + " "); 207 } 208 } 209 pw.println(); 210 } 211 } 212 } 213 214 @GuardedBy("mLock") sendAudioPlayerActiveStateChangedMessageLocked( final AudioPlaybackConfiguration config, final boolean isRemoved)215 private void sendAudioPlayerActiveStateChangedMessageLocked( 216 final AudioPlaybackConfiguration config, final boolean isRemoved) { 217 for (MessageHandler messageHandler : mListenerMap.values()) { 218 messageHandler.sendAudioPlayerActiveStateChangedMessage(config, isRemoved); 219 } 220 } 221 222 private class AudioManagerPlaybackListener extends AudioManager.AudioPlaybackCallback { 223 @Override onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configs)224 public void onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configs) { 225 synchronized (mLock) { 226 // Update mActiveAudioUids 227 mActiveAudioUids.clear(); 228 ArrayMap<Integer, AudioPlaybackConfiguration> activeAudioPlaybackConfigs = 229 new ArrayMap<>(); 230 for (AudioPlaybackConfiguration config : configs) { 231 if (config.isActive()) { 232 mActiveAudioUids.add(config.getClientUid()); 233 activeAudioPlaybackConfigs.put(config.getPlayerInterfaceId(), config); 234 } 235 } 236 237 // Update mSortedAuioPlaybackClientUids. 238 for (int i = 0; i < activeAudioPlaybackConfigs.size(); ++i) { 239 AudioPlaybackConfiguration config = activeAudioPlaybackConfigs.valueAt(i); 240 final int uid = config.getClientUid(); 241 if (!mPrevActiveAudioPlaybackConfigs.containsKey( 242 config.getPlayerInterfaceId())) { 243 if (DEBUG) { 244 Log.d(TAG, "Found a new active media playback. " + config); 245 } 246 // New active audio playback. 247 int index = mSortedAudioPlaybackClientUids.indexOf(uid); 248 if (index == 0) { 249 // It's the lastly played music app already. Skip updating. 250 continue; 251 } else if (index > 0) { 252 mSortedAudioPlaybackClientUids.remove(index); 253 } 254 mSortedAudioPlaybackClientUids.add(0, uid); 255 } 256 } 257 258 if (mActiveAudioUids.size() > 0 259 && !mActiveAudioUids.contains(mSortedAudioPlaybackClientUids.get(0))) { 260 int firstActiveUid = -1; 261 int firstActiveUidIndex = -1; 262 for (int i = 1; i < mSortedAudioPlaybackClientUids.size(); ++i) { 263 int uid = mSortedAudioPlaybackClientUids.get(i); 264 if (mActiveAudioUids.contains(uid)) { 265 firstActiveUidIndex = i; 266 firstActiveUid = uid; 267 break; 268 } 269 } 270 for (int i = firstActiveUidIndex; i > 0; --i) { 271 mSortedAudioPlaybackClientUids.set(i, 272 mSortedAudioPlaybackClientUids.get(i - 1)); 273 } 274 mSortedAudioPlaybackClientUids.set(0, firstActiveUid); 275 } 276 277 // Notify the active state change of audio players. 278 for (AudioPlaybackConfiguration config : configs) { 279 final int pii = config.getPlayerInterfaceId(); 280 boolean wasActive = mPrevActiveAudioPlaybackConfigs.remove(pii) != null; 281 if (wasActive != config.isActive()) { 282 sendAudioPlayerActiveStateChangedMessageLocked( 283 config, /* isRemoved */ false); 284 } 285 } 286 for (AudioPlaybackConfiguration config : mPrevActiveAudioPlaybackConfigs.values()) { 287 sendAudioPlayerActiveStateChangedMessageLocked(config, /* isRemoved */ true); 288 } 289 290 // Update mPrevActiveAudioPlaybackConfigs 291 mPrevActiveAudioPlaybackConfigs = activeAudioPlaybackConfigs; 292 } 293 } 294 } 295 } 296