blob: 1d391775e55035476b74d9c43d348bda5cd619c7 [file] [log] [blame]
Sungsoo Lim875e6972017-11-03 02:22:35 +00001/*
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
17package com.android.server.media;
18
Sungsoo Lim2afdbc42017-11-01 13:45:59 +090019import android.annotation.NonNull;
Sungsoo Lim875e6972017-11-03 02:22:35 +000020import android.content.Context;
Sungsoo Limd4d11872019-04-04 02:19:52 +090021import android.media.AudioManager;
Sungsoo Lim875e6972017-11-03 02:22:35 +000022import android.media.AudioPlaybackConfiguration;
Sungsoo Lim875e6972017-11-03 02:22:35 +000023import android.os.Handler;
24import android.os.Looper;
25import android.os.Message;
Sungsoo Lim875e6972017-11-03 02:22:35 +000026import android.os.UserHandle;
Sungsoo Lim2afdbc42017-11-01 13:45:59 +090027import android.util.ArrayMap;
28import android.util.ArraySet;
Sungsoo Lim875e6972017-11-03 02:22:35 +000029import android.util.IntArray;
30import android.util.Log;
31
32import com.android.internal.annotations.GuardedBy;
33
34import java.io.PrintWriter;
Sungsoo Lim875e6972017-11-03 02:22:35 +000035import java.util.List;
36import java.util.Map;
37import java.util.Set;
38
39/**
40 * Monitors the state changes of audio players.
41 */
Sungsoo Limd4d11872019-04-04 02:19:52 +090042class AudioPlayerStateMonitor {
Sungsoo Lim875e6972017-11-03 02:22:35 +000043 private static boolean DEBUG = MediaSessionService.DEBUG;
44 private static String TAG = "AudioPlayerStateMonitor";
45
Sungsoo Limd4d11872019-04-04 02:19:52 +090046 private static AudioPlayerStateMonitor sInstance;
Sungsoo Lim875e6972017-11-03 02:22:35 +000047
48 /**
Sungsoo Lim2afdbc42017-11-01 13:45:59 +090049 * Listener for handling the active state changes of audio players.
Sungsoo Lim875e6972017-11-03 02:22:35 +000050 */
Sungsoo Lim2afdbc42017-11-01 13:45:59 +090051 interface OnAudioPlayerActiveStateChangedListener {
52 /**
53 * Called when the active state of audio player is changed.
54 *
Sungsoo Lim90f4c8952017-11-21 13:12:24 +090055 * @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 Lim2afdbc42017-11-01 13:45:59 +090058 * @param isRemoved {@code true} if the audio player is removed.
59 */
60 void onAudioPlayerActiveStateChanged(
61 @NonNull AudioPlaybackConfiguration config, boolean isRemoved);
Sungsoo Lim875e6972017-11-03 02:22:35 +000062 }
63
64 private final static class MessageHandler extends Handler {
Sungsoo Lim2afdbc42017-11-01 13:45:59 +090065 private static final int MSG_AUDIO_PLAYER_ACTIVE_STATE_CHANGED = 1;
Sungsoo Lim875e6972017-11-03 02:22:35 +000066
Sungsoo Lim2afdbc42017-11-01 13:45:59 +090067 private final OnAudioPlayerActiveStateChangedListener mListener;
Sungsoo Lim875e6972017-11-03 02:22:35 +000068
Sungsoo Lim90f4c8952017-11-21 13:12:24 +090069 MessageHandler(Looper looper, OnAudioPlayerActiveStateChangedListener listener) {
Sungsoo Lim875e6972017-11-03 02:22:35 +000070 super(looper);
Sungsoo Lim2afdbc42017-11-01 13:45:59 +090071 mListener = listener;
Sungsoo Lim875e6972017-11-03 02:22:35 +000072 }
73
74 @Override
75 public void handleMessage(Message msg) {
76 switch (msg.what) {
Sungsoo Lim2afdbc42017-11-01 13:45:59 +090077 case MSG_AUDIO_PLAYER_ACTIVE_STATE_CHANGED:
78 mListener.onAudioPlayerActiveStateChanged((AudioPlaybackConfiguration) msg.obj,
79 msg.arg1 != 0);
Sungsoo Lim875e6972017-11-03 02:22:35 +000080 break;
81 }
82 }
83
Sungsoo Lim90f4c8952017-11-21 13:12:24 +090084 void sendAudioPlayerActiveStateChangedMessage(
Sungsoo Lim2afdbc42017-11-01 13:45:59 +090085 final AudioPlaybackConfiguration config, final boolean isRemoved) {
86 obtainMessage(MSG_AUDIO_PLAYER_ACTIVE_STATE_CHANGED,
87 isRemoved ? 1 : 0, 0 /* unused */, config).sendToTarget();
Sungsoo Lim875e6972017-11-03 02:22:35 +000088 }
89 }
90
91 private final Object mLock = new Object();
92 @GuardedBy("mLock")
Sungsoo Lim2afdbc42017-11-01 13:45:59 +090093 private final Map<OnAudioPlayerActiveStateChangedListener, MessageHandler> mListenerMap =
94 new ArrayMap<>();
Sungsoo Lim875e6972017-11-03 02:22:35 +000095 @GuardedBy("mLock")
Sungsoo Limd4d11872019-04-04 02:19:52 +090096 @SuppressWarnings("WeakerAccess") /* synthetic access */
97 final Set<Integer> mActiveAudioUids = new ArraySet<>();
Sungsoo Lim875e6972017-11-03 02:22:35 +000098 @GuardedBy("mLock")
Sungsoo Limd4d11872019-04-04 02:19:52 +090099 @SuppressWarnings("WeakerAccess") /* synthetic access */
100 ArrayMap<Integer, AudioPlaybackConfiguration> mPrevActiveAudioPlaybackConfigs =
Sungsoo Lim2afdbc42017-11-01 13:45:59 +0900101 new ArrayMap<>();
Sungsoo Lim875e6972017-11-03 02:22:35 +0000102 // 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 Limd4d11872019-04-04 02:19:52 +0900106 @SuppressWarnings("WeakerAccess") /* synthetic access */
107 final IntArray mSortedAudioPlaybackClientUids = new IntArray();
Sungsoo Lim875e6972017-11-03 02:22:35 +0000108
Sungsoo Limd4d11872019-04-04 02:19:52 +0900109 static AudioPlayerStateMonitor getInstance(Context context) {
110 synchronized (AudioPlayerStateMonitor.class) {
111 if (sInstance == null) {
112 sInstance = new AudioPlayerStateMonitor(context);
Sungsoo Lim875e6972017-11-03 02:22:35 +0000113 }
Sungsoo Limd4d11872019-04-04 02:19:52 +0900114 return sInstance;
Sungsoo Lim875e6972017-11-03 02:22:35 +0000115 }
116 }
117
Sungsoo Limd4d11872019-04-04 02:19:52 +0900118 private AudioPlayerStateMonitor(Context context) {
119 AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
120 am.registerAudioPlaybackCallback(new AudioManagerPlaybackListener(), null);
121 }
122
Sungsoo Lim875e6972017-11-03 02:22:35 +0000123 /**
Sungsoo Lim2afdbc42017-11-01 13:45:59 +0900124 * Registers OnAudioPlayerActiveStateChangedListener.
Sungsoo Lim875e6972017-11-03 02:22:35 +0000125 */
Sungsoo Lim2afdbc42017-11-01 13:45:59 +0900126 public void registerListener(
127 OnAudioPlayerActiveStateChangedListener listener, Handler handler) {
Sungsoo Lim875e6972017-11-03 02:22:35 +0000128 synchronized (mLock) {
129 mListenerMap.put(listener, new MessageHandler((handler == null) ?
130 Looper.myLooper() : handler.getLooper(), listener));
131 }
132 }
133
134 /**
Sungsoo Lim2afdbc42017-11-01 13:45:59 +0900135 * Unregisters OnAudioPlayerActiveStateChangedListener.
Sungsoo Lim875e6972017-11-03 02:22:35 +0000136 */
Sungsoo Lim2afdbc42017-11-01 13:45:59 +0900137 public void unregisterListener(OnAudioPlayerActiveStateChangedListener listener) {
Sungsoo Lim875e6972017-11-03 02:22:35 +0000138 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
Sungsoo Limcc197722019-04-12 19:10:56 +0900145 * 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.
Sungsoo Lim875e6972017-11-03 02:22:35 +0000147 */
148 public IntArray getSortedAudioPlaybackClientUids() {
149 IntArray sortedAudioPlaybackClientUids = new IntArray();
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 */
159 public boolean isPlaybackActive(int uid) {
160 synchronized (mLock) {
Sungsoo Lim2afdbc42017-11-01 13:45:59 +0900161 return mActiveAudioUids.contains(uid);
Sungsoo Lim875e6972017-11-03 02:22:35 +0000162 }
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 needed anymore.
170 *
171 * @param mediaButtonSessionUid UID of the media button session.
172 */
173 public void cleanUpAudioPlaybackUids(int mediaButtonSessionUid) {
174 synchronized (mLock) {
175 int userId = UserHandle.getUserId(mediaButtonSessionUid);
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.getUserId(uid) && !isPlaybackActive(uid)) {
182 // Clean up unnecessary UIDs.
183 // It doesn't need to be managed profile aware because it's just to prevent
184 // the list from increasing indefinitely. The media button session updating
185 // shouldn't be affected by cleaning up.
186 mSortedAudioPlaybackClientUids.remove(i);
187 }
188 }
189 }
190 }
191
192 /**
193 * Dumps {@link AudioPlayerStateMonitor}.
194 */
195 public void dump(Context context, PrintWriter pw, String prefix) {
196 synchronized (mLock) {
197 pw.println(prefix + "Audio playback (lastly played comes first)");
198 String indent = prefix + " ";
199 for (int i = 0; i < mSortedAudioPlaybackClientUids.size(); i++) {
200 int uid = mSortedAudioPlaybackClientUids.get(i);
201 pw.print(indent + "uid=" + uid + " packages=");
202 String[] packages = context.getPackageManager().getPackagesForUid(uid);
203 if (packages != null && packages.length > 0) {
204 for (int j = 0; j < packages.length; j++) {
205 pw.print(packages[j] + " ");
206 }
207 }
208 pw.println();
209 }
210 }
211 }
212
Andreas Gampea36dc622018-02-05 17:19:22 -0800213 @GuardedBy("mLock")
Sungsoo Lim2afdbc42017-11-01 13:45:59 +0900214 private void sendAudioPlayerActiveStateChangedMessageLocked(
215 final AudioPlaybackConfiguration config, final boolean isRemoved) {
Sungsoo Lim875e6972017-11-03 02:22:35 +0000216 for (MessageHandler messageHandler : mListenerMap.values()) {
Sungsoo Lim2afdbc42017-11-01 13:45:59 +0900217 messageHandler.sendAudioPlayerActiveStateChangedMessage(config, isRemoved);
Sungsoo Lim875e6972017-11-03 02:22:35 +0000218 }
219 }
Sungsoo Limd4d11872019-04-04 02:19:52 +0900220
221 private class AudioManagerPlaybackListener extends AudioManager.AudioPlaybackCallback {
222 @Override
223 public void onPlaybackConfigChanged(List<AudioPlaybackConfiguration> configs) {
224 synchronized (mLock) {
225 // Update mActiveAudioUids
226 mActiveAudioUids.clear();
227 ArrayMap<Integer, AudioPlaybackConfiguration> activeAudioPlaybackConfigs =
228 new ArrayMap<>();
229 for (AudioPlaybackConfiguration config : configs) {
230 if (config.isActive()) {
231 mActiveAudioUids.add(config.getClientUid());
232 activeAudioPlaybackConfigs.put(config.getPlayerInterfaceId(), config);
233 }
234 }
235
236 // Update mSortedAuioPlaybackClientUids.
237 for (int i = 0; i < activeAudioPlaybackConfigs.size(); ++i) {
238 AudioPlaybackConfiguration config = activeAudioPlaybackConfigs.valueAt(i);
239 final int uid = config.getClientUid();
240 if (!mPrevActiveAudioPlaybackConfigs.containsKey(
241 config.getPlayerInterfaceId())) {
242 if (DEBUG) {
Rahul Sabniseeef8652019-12-20 14:43:34 -0800243 Log.d(TAG, "Found a new active media playback. " + config);
Sungsoo Limd4d11872019-04-04 02:19:52 +0900244 }
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 }
Sungsoo Limcc197722019-04-12 19:10:56 +0900256
257 if (mActiveAudioUids.size() > 0
258 && !mActiveAudioUids.contains(mSortedAudioPlaybackClientUids.get(0))) {
259 int firstActiveUid = -1;
260 int firatActiveUidIndex = -1;
261 for (int i = 1; i < mSortedAudioPlaybackClientUids.size(); ++i) {
262 int uid = mSortedAudioPlaybackClientUids.get(i);
263 if (mActiveAudioUids.contains(uid)) {
264 firatActiveUidIndex = i;
265 firstActiveUid = uid;
266 break;
267 }
268 }
269 for (int i = firatActiveUidIndex; i > 0; --i) {
270 mSortedAudioPlaybackClientUids.set(i,
271 mSortedAudioPlaybackClientUids.get(i - 1));
272 }
273 mSortedAudioPlaybackClientUids.set(0, firstActiveUid);
274 }
275
Sungsoo Limd4d11872019-04-04 02:19:52 +0900276 // Notify the active state change of audio players.
277 for (AudioPlaybackConfiguration config : configs) {
278 final int pii = config.getPlayerInterfaceId();
279 boolean wasActive = mPrevActiveAudioPlaybackConfigs.remove(pii) != null;
280 if (wasActive != config.isActive()) {
281 sendAudioPlayerActiveStateChangedMessageLocked(
282 config, /* isRemoved */ false);
283 }
284 }
285 for (AudioPlaybackConfiguration config : mPrevActiveAudioPlaybackConfigs.values()) {
286 sendAudioPlayerActiveStateChangedMessageLocked(config, /* isRemoved */ true);
287 }
288
289 // Update mPrevActiveAudioPlaybackConfigs
290 mPrevActiveAudioPlaybackConfigs = activeAudioPlaybackConfigs;
291 }
292 }
293 }
Sungsoo Lim875e6972017-11-03 02:22:35 +0000294}