blob: 603d7cfb4525015919bbcaa6a2e13d5f357d3153 [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
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 Lim2afdbc42017-11-01 13:45:59 +0900160 return mActiveAudioUids.contains(uid);
Sungsoo Lim875e6972017-11-03 02:22:35 +0000161 }
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 Gampea36dc622018-02-05 17:19:22 -0800212 @GuardedBy("mLock")
Sungsoo Lim2afdbc42017-11-01 13:45:59 +0900213 private void sendAudioPlayerActiveStateChangedMessageLocked(
214 final AudioPlaybackConfiguration config, final boolean isRemoved) {
Sungsoo Lim875e6972017-11-03 02:22:35 +0000215 for (MessageHandler messageHandler : mListenerMap.values()) {
Sungsoo Lim2afdbc42017-11-01 13:45:59 +0900216 messageHandler.sendAudioPlayerActiveStateChangedMessage(config, isRemoved);
Sungsoo Lim875e6972017-11-03 02:22:35 +0000217 }
218 }
Sungsoo Limd4d11872019-04-04 02:19:52 +0900219
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 Lim875e6972017-11-03 02:22:35 +0000274}