Sungsoo Lim | 875e697 | 2017-11-03 02:22:35 +0000 | [diff] [blame] | 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 | |
Sungsoo Lim | 2afdbc4 | 2017-11-01 13:45:59 +0900 | [diff] [blame] | 19 | import android.annotation.NonNull; |
Sungsoo Lim | 875e697 | 2017-11-03 02:22:35 +0000 | [diff] [blame] | 20 | import android.content.Context; |
Sungsoo Lim | d4d1187 | 2019-04-04 02:19:52 +0900 | [diff] [blame^] | 21 | import android.media.AudioManager; |
Sungsoo Lim | 875e697 | 2017-11-03 02:22:35 +0000 | [diff] [blame] | 22 | import android.media.AudioPlaybackConfiguration; |
Sungsoo Lim | 875e697 | 2017-11-03 02:22:35 +0000 | [diff] [blame] | 23 | import android.os.Handler; |
| 24 | import android.os.Looper; |
| 25 | import android.os.Message; |
Sungsoo Lim | 875e697 | 2017-11-03 02:22:35 +0000 | [diff] [blame] | 26 | import android.os.UserHandle; |
Sungsoo Lim | 2afdbc4 | 2017-11-01 13:45:59 +0900 | [diff] [blame] | 27 | import android.util.ArrayMap; |
| 28 | import android.util.ArraySet; |
Sungsoo Lim | 875e697 | 2017-11-03 02:22:35 +0000 | [diff] [blame] | 29 | import android.util.IntArray; |
| 30 | import android.util.Log; |
| 31 | |
| 32 | import com.android.internal.annotations.GuardedBy; |
| 33 | |
| 34 | import java.io.PrintWriter; |
Sungsoo Lim | 875e697 | 2017-11-03 02:22:35 +0000 | [diff] [blame] | 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 | */ |
Sungsoo Lim | d4d1187 | 2019-04-04 02:19:52 +0900 | [diff] [blame^] | 42 | class AudioPlayerStateMonitor { |
Sungsoo Lim | 875e697 | 2017-11-03 02:22:35 +0000 | [diff] [blame] | 43 | private static boolean DEBUG = MediaSessionService.DEBUG; |
| 44 | private static String TAG = "AudioPlayerStateMonitor"; |
| 45 | |
Sungsoo Lim | d4d1187 | 2019-04-04 02:19:52 +0900 | [diff] [blame^] | 46 | private static AudioPlayerStateMonitor sInstance; |
Sungsoo Lim | 875e697 | 2017-11-03 02:22:35 +0000 | [diff] [blame] | 47 | |
| 48 | /** |
Sungsoo Lim | 2afdbc4 | 2017-11-01 13:45:59 +0900 | [diff] [blame] | 49 | * Listener for handling the active state changes of audio players. |
Sungsoo Lim | 875e697 | 2017-11-03 02:22:35 +0000 | [diff] [blame] | 50 | */ |
Sungsoo Lim | 2afdbc4 | 2017-11-01 13:45:59 +0900 | [diff] [blame] | 51 | interface OnAudioPlayerActiveStateChangedListener { |
| 52 | /** |
| 53 | * Called when the active state of audio player is changed. |
| 54 | * |
Sungsoo Lim | 90f4c895 | 2017-11-21 13:12:24 +0900 | [diff] [blame] | 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. |
Sungsoo Lim | 2afdbc4 | 2017-11-01 13:45:59 +0900 | [diff] [blame] | 58 | * @param isRemoved {@code true} if the audio player is removed. |
| 59 | */ |
| 60 | void onAudioPlayerActiveStateChanged( |
| 61 | @NonNull AudioPlaybackConfiguration config, boolean isRemoved); |
Sungsoo Lim | 875e697 | 2017-11-03 02:22:35 +0000 | [diff] [blame] | 62 | } |
| 63 | |
| 64 | private final static class MessageHandler extends Handler { |
Sungsoo Lim | 2afdbc4 | 2017-11-01 13:45:59 +0900 | [diff] [blame] | 65 | private static final int MSG_AUDIO_PLAYER_ACTIVE_STATE_CHANGED = 1; |
Sungsoo Lim | 875e697 | 2017-11-03 02:22:35 +0000 | [diff] [blame] | 66 | |
Sungsoo Lim | 2afdbc4 | 2017-11-01 13:45:59 +0900 | [diff] [blame] | 67 | private final OnAudioPlayerActiveStateChangedListener mListener; |
Sungsoo Lim | 875e697 | 2017-11-03 02:22:35 +0000 | [diff] [blame] | 68 | |
Sungsoo Lim | 90f4c895 | 2017-11-21 13:12:24 +0900 | [diff] [blame] | 69 | MessageHandler(Looper looper, OnAudioPlayerActiveStateChangedListener listener) { |
Sungsoo Lim | 875e697 | 2017-11-03 02:22:35 +0000 | [diff] [blame] | 70 | super(looper); |
Sungsoo Lim | 2afdbc4 | 2017-11-01 13:45:59 +0900 | [diff] [blame] | 71 | mListener = listener; |
Sungsoo Lim | 875e697 | 2017-11-03 02:22:35 +0000 | [diff] [blame] | 72 | } |
| 73 | |
| 74 | @Override |
| 75 | public void handleMessage(Message msg) { |
| 76 | switch (msg.what) { |
Sungsoo Lim | 2afdbc4 | 2017-11-01 13:45:59 +0900 | [diff] [blame] | 77 | case MSG_AUDIO_PLAYER_ACTIVE_STATE_CHANGED: |
| 78 | mListener.onAudioPlayerActiveStateChanged((AudioPlaybackConfiguration) msg.obj, |
| 79 | msg.arg1 != 0); |
Sungsoo Lim | 875e697 | 2017-11-03 02:22:35 +0000 | [diff] [blame] | 80 | break; |
| 81 | } |
| 82 | } |
| 83 | |
Sungsoo Lim | 90f4c895 | 2017-11-21 13:12:24 +0900 | [diff] [blame] | 84 | void sendAudioPlayerActiveStateChangedMessage( |
Sungsoo Lim | 2afdbc4 | 2017-11-01 13:45:59 +0900 | [diff] [blame] | 85 | final AudioPlaybackConfiguration config, final boolean isRemoved) { |
| 86 | obtainMessage(MSG_AUDIO_PLAYER_ACTIVE_STATE_CHANGED, |
| 87 | isRemoved ? 1 : 0, 0 /* unused */, config).sendToTarget(); |
Sungsoo Lim | 875e697 | 2017-11-03 02:22:35 +0000 | [diff] [blame] | 88 | } |
| 89 | } |
| 90 | |
| 91 | private final Object mLock = new Object(); |
| 92 | @GuardedBy("mLock") |
Sungsoo Lim | 2afdbc4 | 2017-11-01 13:45:59 +0900 | [diff] [blame] | 93 | private final Map<OnAudioPlayerActiveStateChangedListener, MessageHandler> mListenerMap = |
| 94 | new ArrayMap<>(); |
Sungsoo Lim | 875e697 | 2017-11-03 02:22:35 +0000 | [diff] [blame] | 95 | @GuardedBy("mLock") |
Sungsoo Lim | d4d1187 | 2019-04-04 02:19:52 +0900 | [diff] [blame^] | 96 | @SuppressWarnings("WeakerAccess") /* synthetic access */ |
| 97 | final Set<Integer> mActiveAudioUids = new ArraySet<>(); |
Sungsoo Lim | 875e697 | 2017-11-03 02:22:35 +0000 | [diff] [blame] | 98 | @GuardedBy("mLock") |
Sungsoo Lim | d4d1187 | 2019-04-04 02:19:52 +0900 | [diff] [blame^] | 99 | @SuppressWarnings("WeakerAccess") /* synthetic access */ |
| 100 | ArrayMap<Integer, AudioPlaybackConfiguration> mPrevActiveAudioPlaybackConfigs = |
Sungsoo Lim | 2afdbc4 | 2017-11-01 13:45:59 +0900 | [diff] [blame] | 101 | new ArrayMap<>(); |
Sungsoo Lim | 875e697 | 2017-11-03 02:22:35 +0000 | [diff] [blame] | 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") |
Sungsoo Lim | d4d1187 | 2019-04-04 02:19:52 +0900 | [diff] [blame^] | 106 | @SuppressWarnings("WeakerAccess") /* synthetic access */ |
| 107 | final IntArray mSortedAudioPlaybackClientUids = new IntArray(); |
Sungsoo Lim | 875e697 | 2017-11-03 02:22:35 +0000 | [diff] [blame] | 108 | |
Sungsoo Lim | d4d1187 | 2019-04-04 02:19:52 +0900 | [diff] [blame^] | 109 | static AudioPlayerStateMonitor getInstance(Context context) { |
| 110 | synchronized (AudioPlayerStateMonitor.class) { |
| 111 | if (sInstance == null) { |
| 112 | sInstance = new AudioPlayerStateMonitor(context); |
Sungsoo Lim | 875e697 | 2017-11-03 02:22:35 +0000 | [diff] [blame] | 113 | } |
Sungsoo Lim | d4d1187 | 2019-04-04 02:19:52 +0900 | [diff] [blame^] | 114 | return sInstance; |
Sungsoo Lim | 875e697 | 2017-11-03 02:22:35 +0000 | [diff] [blame] | 115 | } |
| 116 | } |
| 117 | |
Sungsoo Lim | d4d1187 | 2019-04-04 02:19:52 +0900 | [diff] [blame^] | 118 | private AudioPlayerStateMonitor(Context context) { |
| 119 | AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); |
| 120 | am.registerAudioPlaybackCallback(new AudioManagerPlaybackListener(), null); |
| 121 | } |
| 122 | |
Sungsoo Lim | 875e697 | 2017-11-03 02:22:35 +0000 | [diff] [blame] | 123 | /** |
Sungsoo Lim | 2afdbc4 | 2017-11-01 13:45:59 +0900 | [diff] [blame] | 124 | * Registers OnAudioPlayerActiveStateChangedListener. |
Sungsoo Lim | 875e697 | 2017-11-03 02:22:35 +0000 | [diff] [blame] | 125 | */ |
Sungsoo Lim | 2afdbc4 | 2017-11-01 13:45:59 +0900 | [diff] [blame] | 126 | public void registerListener( |
| 127 | OnAudioPlayerActiveStateChangedListener listener, Handler handler) { |
Sungsoo Lim | 875e697 | 2017-11-03 02:22:35 +0000 | [diff] [blame] | 128 | synchronized (mLock) { |
| 129 | mListenerMap.put(listener, new MessageHandler((handler == null) ? |
| 130 | Looper.myLooper() : handler.getLooper(), listener)); |
| 131 | } |
| 132 | } |
| 133 | |
| 134 | /** |
Sungsoo Lim | 2afdbc4 | 2017-11-01 13:45:59 +0900 | [diff] [blame] | 135 | * Unregisters OnAudioPlayerActiveStateChangedListener. |
Sungsoo Lim | 875e697 | 2017-11-03 02:22:35 +0000 | [diff] [blame] | 136 | */ |
Sungsoo Lim | 2afdbc4 | 2017-11-01 13:45:59 +0900 | [diff] [blame] | 137 | public void unregisterListener(OnAudioPlayerActiveStateChangedListener listener) { |
Sungsoo Lim | 875e697 | 2017-11-03 02:22:35 +0000 | [diff] [blame] | 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 playback becomes active at the last comes first. |
| 146 | */ |
| 147 | public IntArray getSortedAudioPlaybackClientUids() { |
| 148 | IntArray sortedAudioPlaybackClientUids = new IntArray(); |
| 149 | synchronized (mLock) { |
| 150 | sortedAudioPlaybackClientUids.addAll(mSortedAudioPlaybackClientUids); |
| 151 | } |
| 152 | return sortedAudioPlaybackClientUids; |
| 153 | } |
| 154 | |
| 155 | /** |
| 156 | * Returns if the audio playback is active for the uid. |
| 157 | */ |
| 158 | public boolean isPlaybackActive(int uid) { |
| 159 | synchronized (mLock) { |
Sungsoo Lim | 2afdbc4 | 2017-11-01 13:45:59 +0900 | [diff] [blame] | 160 | return mActiveAudioUids.contains(uid); |
Sungsoo Lim | 875e697 | 2017-11-03 02:22:35 +0000 | [diff] [blame] | 161 | } |
| 162 | } |
| 163 | |
| 164 | /** |
| 165 | * Cleans up the sorted list of audio playback client UIDs with given {@param |
| 166 | * mediaButtonSessionUid}. |
| 167 | * <p>UIDs whose audio playback are inactive and have started before the media button session's |
| 168 | * audio playback cannot be the lastly played media app. So they won't needed anymore. |
| 169 | * |
| 170 | * @param mediaButtonSessionUid UID of the media button session. |
| 171 | */ |
| 172 | public void cleanUpAudioPlaybackUids(int mediaButtonSessionUid) { |
| 173 | synchronized (mLock) { |
| 174 | int userId = UserHandle.getUserId(mediaButtonSessionUid); |
| 175 | for (int i = mSortedAudioPlaybackClientUids.size() - 1; i >= 0; i--) { |
| 176 | if (mSortedAudioPlaybackClientUids.get(i) == mediaButtonSessionUid) { |
| 177 | break; |
| 178 | } |
| 179 | int uid = mSortedAudioPlaybackClientUids.get(i); |
| 180 | if (userId == UserHandle.getUserId(uid) && !isPlaybackActive(uid)) { |
| 181 | // Clean up unnecessary UIDs. |
| 182 | // It doesn't need to be managed profile aware because it's just to prevent |
| 183 | // the list from increasing indefinitely. The media button session updating |
| 184 | // shouldn't be affected by cleaning up. |
| 185 | mSortedAudioPlaybackClientUids.remove(i); |
| 186 | } |
| 187 | } |
| 188 | } |
| 189 | } |
| 190 | |
| 191 | /** |
| 192 | * Dumps {@link AudioPlayerStateMonitor}. |
| 193 | */ |
| 194 | public void dump(Context context, PrintWriter pw, String prefix) { |
| 195 | synchronized (mLock) { |
| 196 | pw.println(prefix + "Audio playback (lastly played comes first)"); |
| 197 | String indent = prefix + " "; |
| 198 | for (int i = 0; i < mSortedAudioPlaybackClientUids.size(); i++) { |
| 199 | int uid = mSortedAudioPlaybackClientUids.get(i); |
| 200 | pw.print(indent + "uid=" + uid + " packages="); |
| 201 | String[] packages = context.getPackageManager().getPackagesForUid(uid); |
| 202 | if (packages != null && packages.length > 0) { |
| 203 | for (int j = 0; j < packages.length; j++) { |
| 204 | pw.print(packages[j] + " "); |
| 205 | } |
| 206 | } |
| 207 | pw.println(); |
| 208 | } |
| 209 | } |
| 210 | } |
| 211 | |
Andreas Gampe | a36dc62 | 2018-02-05 17:19:22 -0800 | [diff] [blame] | 212 | @GuardedBy("mLock") |
Sungsoo Lim | 2afdbc4 | 2017-11-01 13:45:59 +0900 | [diff] [blame] | 213 | private void sendAudioPlayerActiveStateChangedMessageLocked( |
| 214 | final AudioPlaybackConfiguration config, final boolean isRemoved) { |
Sungsoo Lim | 875e697 | 2017-11-03 02:22:35 +0000 | [diff] [blame] | 215 | for (MessageHandler messageHandler : mListenerMap.values()) { |
Sungsoo Lim | 2afdbc4 | 2017-11-01 13:45:59 +0900 | [diff] [blame] | 216 | messageHandler.sendAudioPlayerActiveStateChangedMessage(config, isRemoved); |
Sungsoo Lim | 875e697 | 2017-11-03 02:22:35 +0000 | [diff] [blame] | 217 | } |
| 218 | } |
Sungsoo Lim | d4d1187 | 2019-04-04 02:19:52 +0900 | [diff] [blame^] | 219 | |
| 220 | private class AudioManagerPlaybackListener extends AudioManager.AudioPlaybackCallback { |
| 221 | @Override |
| 222 | public void onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configs) { |
| 223 | synchronized (mLock) { |
| 224 | // Update mActiveAudioUids |
| 225 | mActiveAudioUids.clear(); |
| 226 | ArrayMap<Integer, AudioPlaybackConfiguration> activeAudioPlaybackConfigs = |
| 227 | new ArrayMap<>(); |
| 228 | for (AudioPlaybackConfiguration config : configs) { |
| 229 | if (config.isActive()) { |
| 230 | mActiveAudioUids.add(config.getClientUid()); |
| 231 | activeAudioPlaybackConfigs.put(config.getPlayerInterfaceId(), config); |
| 232 | } |
| 233 | } |
| 234 | |
| 235 | // Update mSortedAuioPlaybackClientUids. |
| 236 | for (int i = 0; i < activeAudioPlaybackConfigs.size(); ++i) { |
| 237 | AudioPlaybackConfiguration config = activeAudioPlaybackConfigs.valueAt(i); |
| 238 | final int uid = config.getClientUid(); |
| 239 | if (!mPrevActiveAudioPlaybackConfigs.containsKey( |
| 240 | config.getPlayerInterfaceId())) { |
| 241 | if (DEBUG) { |
| 242 | Log.d(TAG, "Found a new active media playback. " |
| 243 | + AudioPlaybackConfiguration.toLogFriendlyString(config)); |
| 244 | } |
| 245 | // New active audio playback. |
| 246 | int index = mSortedAudioPlaybackClientUids.indexOf(uid); |
| 247 | if (index == 0) { |
| 248 | // It's the lastly played music app already. Skip updating. |
| 249 | continue; |
| 250 | } else if (index > 0) { |
| 251 | mSortedAudioPlaybackClientUids.remove(index); |
| 252 | } |
| 253 | mSortedAudioPlaybackClientUids.add(0, uid); |
| 254 | } |
| 255 | } |
| 256 | // Notify the active state change of audio players. |
| 257 | for (AudioPlaybackConfiguration config : configs) { |
| 258 | final int pii = config.getPlayerInterfaceId(); |
| 259 | boolean wasActive = mPrevActiveAudioPlaybackConfigs.remove(pii) != null; |
| 260 | if (wasActive != config.isActive()) { |
| 261 | sendAudioPlayerActiveStateChangedMessageLocked( |
| 262 | config, /* isRemoved */ false); |
| 263 | } |
| 264 | } |
| 265 | for (AudioPlaybackConfiguration config : mPrevActiveAudioPlaybackConfigs.values()) { |
| 266 | sendAudioPlayerActiveStateChangedMessageLocked(config, /* isRemoved */ true); |
| 267 | } |
| 268 | |
| 269 | // Update mPrevActiveAudioPlaybackConfigs |
| 270 | mPrevActiveAudioPlaybackConfigs = activeAudioPlaybackConfigs; |
| 271 | } |
| 272 | } |
| 273 | } |
Sungsoo Lim | 875e697 | 2017-11-03 02:22:35 +0000 | [diff] [blame] | 274 | } |