blob: 791ee821b357d16b436b830d28c4e7b8858d6224 [file] [log] [blame]
Jaewan Kim92dea332017-02-02 11:52:08 +09001/*
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
19import android.content.Context;
20import android.media.AudioManager.AudioPlaybackCallback;
21import android.media.AudioPlaybackConfiguration;
22import android.media.IAudioService;
23import android.media.IPlaybackConfigDispatcher;
24import android.os.Binder;
25import android.os.RemoteException;
26import android.os.UserHandle;
27import android.util.IntArray;
28import android.util.Log;
29import android.util.SparseArray;
30
31import java.io.PrintWriter;
32import java.util.ArrayList;
33import java.util.HashSet;
Sungsoob3658562017-05-22 17:10:44 +090034import java.util.HashMap;
Jaewan Kim92dea332017-02-02 11:52:08 +090035import java.util.List;
Sungsoob3658562017-05-22 17:10:44 +090036import java.util.Map;
Jaewan Kim92dea332017-02-02 11:52:08 +090037import java.util.Set;
38
39/**
Sungsoob3658562017-05-22 17:10:44 +090040 * Monitors changes in audio playback, and notify the newly started audio playback through the
41 * {@link OnAudioPlaybackStartedListener} and the activeness change through the
42 * {@link OnAudioPlaybackActiveStateListener}.
Jaewan Kim92dea332017-02-02 11:52:08 +090043 */
44class AudioPlaybackMonitor extends IPlaybackConfigDispatcher.Stub {
45 private static boolean DEBUG = MediaSessionService.DEBUG;
46 private static String TAG = "AudioPlaybackMonitor";
47
Sungsoob3658562017-05-22 17:10:44 +090048 private static AudioPlaybackMonitor sInstance;
49
Jaewan Kim92dea332017-02-02 11:52:08 +090050 /**
51 * Called when audio playback is started for a given UID.
52 */
53 interface OnAudioPlaybackStartedListener {
54 void onAudioPlaybackStarted(int uid);
55 }
56
Sungsoob3658562017-05-22 17:10:44 +090057 /**
58 * Called when audio player state is changed.
59 */
60 interface OnAudioPlayerActiveStateChangedListener {
61 void onAudioPlayerActiveStateChanged(int uid, boolean active);
62 }
63
Jaewan Kim92dea332017-02-02 11:52:08 +090064 private final Object mLock = new Object();
65 private final Context mContext;
Sungsoob3658562017-05-22 17:10:44 +090066 private final List<OnAudioPlaybackStartedListener> mAudioPlaybackStartedListeners
67 = new ArrayList<>();
68 private final List<OnAudioPlayerActiveStateChangedListener>
69 mAudioPlayerActiveStateChangedListeners = new ArrayList<>();
70 private final Map<Integer, Integer> mAudioPlaybackStates = new HashMap<>();
71 private final Set<Integer> mActiveAudioPlaybackClientUids = new HashSet<>();
Jaewan Kim92dea332017-02-02 11:52:08 +090072
73 // Sorted array of UIDs that had active audio playback. (i.e. playing an audio/video)
74 // The UID whose audio playback becomes active at the last comes first.
75 // TODO(b/35278867): Find and use unique identifier for apps because apps may share the UID.
76 private final IntArray mSortedAudioPlaybackClientUids = new IntArray();
77
Sungsoob3658562017-05-22 17:10:44 +090078 static AudioPlaybackMonitor getInstance(Context context, IAudioService audioService) {
79 if (sInstance == null) {
80 sInstance = new AudioPlaybackMonitor(context, audioService);
81 }
82 return sInstance;
83 }
84
85 private AudioPlaybackMonitor(Context context, IAudioService audioService) {
Jaewan Kim92dea332017-02-02 11:52:08 +090086 mContext = context;
Jaewan Kim92dea332017-02-02 11:52:08 +090087 try {
88 audioService.registerPlaybackCallback(this);
89 } catch (RemoteException e) {
90 Log.wtf(TAG, "Failed to register playback callback", e);
91 }
92 }
93
94 /**
95 * Called when the {@link AudioPlaybackConfiguration} is updated.
96 * <p>If an app starts audio playback, the app's local media session will be the media button
97 * session. If the app has multiple media sessions, the playback active local session will be
98 * picked.
99 *
100 * @param configs List of the current audio playback configuration
101 */
102 @Override
Jean-Michel Trivi776a3992017-09-12 16:45:34 -0700103 public void dispatchPlaybackConfigChange(List<AudioPlaybackConfiguration> configs,
104 boolean flush) {
105 if (flush) {
106 Binder.flushPendingCommands();
107 }
Jaewan Kim92dea332017-02-02 11:52:08 +0900108 final long token = Binder.clearCallingIdentity();
109 try {
Jaewan Kim92dea332017-02-02 11:52:08 +0900110 List<Integer> newActiveAudioPlaybackClientUids = new ArrayList<>();
Sungsoob3658562017-05-22 17:10:44 +0900111 List<OnAudioPlayerActiveStateChangedListener> audioPlayerActiveStateChangedListeners;
112 List<OnAudioPlaybackStartedListener> audioPlaybackStartedListeners;
Jaewan Kim92dea332017-02-02 11:52:08 +0900113 synchronized (mLock) {
Sungsoob3658562017-05-22 17:10:44 +0900114 // Update mActiveAudioPlaybackClientUids and mSortedAudioPlaybackClientUids,
115 // and find newly activated audio playbacks.
Jaewan Kim92dea332017-02-02 11:52:08 +0900116 mActiveAudioPlaybackClientUids.clear();
117 for (AudioPlaybackConfiguration config : configs) {
118 // Ignore inactive (i.e. not playing) or PLAYER_TYPE_JAM_SOUNDPOOL
119 // (i.e. playback from the SoundPool class which is only for sound effects)
120 // playback.
121 // Note that we shouldn't ignore PLAYER_TYPE_UNKNOWN because it might be OEM
122 // specific audio/video players.
Sungsoob3658562017-05-22 17:10:44 +0900123 if (!config.isActive() || config.getPlayerType()
Jaewan Kim92dea332017-02-02 11:52:08 +0900124 == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
125 continue;
126 }
Jaewan Kim92dea332017-02-02 11:52:08 +0900127
Sungsoob3658562017-05-22 17:10:44 +0900128 mActiveAudioPlaybackClientUids.add(config.getClientUid());
129 Integer oldState = mAudioPlaybackStates.get(config.getPlayerInterfaceId());
130 if (!isActiveState(oldState)) {
Jaewan Kim92dea332017-02-02 11:52:08 +0900131 if (DEBUG) {
132 Log.d(TAG, "Found a new active media playback. " +
133 AudioPlaybackConfiguration.toLogFriendlyString(config));
134 }
135 // New active audio playback.
136 newActiveAudioPlaybackClientUids.add(config.getClientUid());
137 int index = mSortedAudioPlaybackClientUids.indexOf(config.getClientUid());
138 if (index == 0) {
139 // It's the lastly played music app already. Skip updating.
140 continue;
141 } else if (index > 0) {
142 mSortedAudioPlaybackClientUids.remove(index);
143 }
144 mSortedAudioPlaybackClientUids.add(0, config.getClientUid());
145 }
146 }
Sungsoob3658562017-05-22 17:10:44 +0900147 audioPlayerActiveStateChangedListeners = new ArrayList<>(
148 mAudioPlayerActiveStateChangedListeners);
149 audioPlaybackStartedListeners = new ArrayList<>(mAudioPlaybackStartedListeners);
Jaewan Kim92dea332017-02-02 11:52:08 +0900150 }
Sungsoob3658562017-05-22 17:10:44 +0900151 // Notify the change of audio playback states.
152 for (AudioPlaybackConfiguration config : configs) {
153 boolean wasActive = isActiveState(
154 mAudioPlaybackStates.get(config.getPlayerInterfaceId()));
155 boolean isActive = config.isActive();
156 if (wasActive != isActive) {
157 for (OnAudioPlayerActiveStateChangedListener listener
158 : audioPlayerActiveStateChangedListeners) {
159 listener.onAudioPlayerActiveStateChanged(config.getClientUid(),
160 isActive);
161 }
162 }
163 }
164 // Notify the start of audio playback
Jaewan Kim92dea332017-02-02 11:52:08 +0900165 for (int uid : newActiveAudioPlaybackClientUids) {
Sungsoob3658562017-05-22 17:10:44 +0900166 for (OnAudioPlaybackStartedListener listener : audioPlaybackStartedListeners) {
167 listener.onAudioPlaybackStarted(uid);
168 }
169 }
170 mAudioPlaybackStates.clear();
171 for (AudioPlaybackConfiguration config : configs) {
172 mAudioPlaybackStates.put(config.getPlayerInterfaceId(), config.getPlayerState());
Jaewan Kim92dea332017-02-02 11:52:08 +0900173 }
174 } finally {
175 Binder.restoreCallingIdentity(token);
176 }
177 }
178
179 /**
Sungsoob3658562017-05-22 17:10:44 +0900180 * Registers OnAudioPlaybackStartedListener.
181 */
182 public void registerOnAudioPlaybackStartedListener(OnAudioPlaybackStartedListener listener) {
183 synchronized (mLock) {
184 mAudioPlaybackStartedListeners.add(listener);
185 }
186 }
187
188 /**
189 * Unregisters OnAudioPlaybackStartedListener.
190 */
191 public void unregisterOnAudioPlaybackStartedListener(OnAudioPlaybackStartedListener listener) {
192 synchronized (mLock) {
193 mAudioPlaybackStartedListeners.remove(listener);
194 }
195 }
196
197 /**
198 * Registers OnAudioPlayerActiveStateChangedListener.
199 */
200 public void registerOnAudioPlayerActiveStateChangedListener(
201 OnAudioPlayerActiveStateChangedListener listener) {
202 synchronized (mLock) {
203 mAudioPlayerActiveStateChangedListeners.add(listener);
204 }
205 }
206
207 /**
208 * Unregisters OnAudioPlayerActiveStateChangedListener.
209 */
210 public void unregisterOnAudioPlayerActiveStateChangedListener(
211 OnAudioPlayerActiveStateChangedListener listener) {
212 synchronized (mLock) {
213 mAudioPlayerActiveStateChangedListeners.remove(listener);
214 }
215 }
216
217 /**
Jaewan Kim92dea332017-02-02 11:52:08 +0900218 * Returns the sorted list of UIDs that have had active audio playback. (i.e. playing an
219 * audio/video) The UID whose audio playback becomes active at the last comes first.
220 */
221 public IntArray getSortedAudioPlaybackClientUids() {
222 IntArray sortedAudioPlaybackClientUids = new IntArray();
223 synchronized (mLock) {
224 sortedAudioPlaybackClientUids.addAll(mSortedAudioPlaybackClientUids);
225 }
226 return sortedAudioPlaybackClientUids;
227 }
228
229 /**
230 * Returns if the audio playback is active for the uid.
231 */
232 public boolean isPlaybackActive(int uid) {
233 synchronized (mLock) {
234 return mActiveAudioPlaybackClientUids.contains(uid);
235 }
236 }
237
238 /**
239 * Cleans up the sorted list of audio playback client UIDs with given {@param
240 * mediaButtonSessionUid}.
241 * <p>UIDs whose audio playback started after the media button session's audio playback
242 * cannot be the lastly played media app. So they won't needed anymore.
243 *
244 * @param mediaButtonSessionUid UID of the media button session.
245 */
246 public void cleanUpAudioPlaybackUids(int mediaButtonSessionUid) {
247 synchronized (mLock) {
248 int userId = UserHandle.getUserId(mediaButtonSessionUid);
249 for (int i = mSortedAudioPlaybackClientUids.size() - 1; i >= 0; i--) {
250 if (mSortedAudioPlaybackClientUids.get(i) == mediaButtonSessionUid) {
251 break;
252 }
Sungsoob3658562017-05-22 17:10:44 +0900253 int uid = mSortedAudioPlaybackClientUids.get(i);
254 if (userId == UserHandle.getUserId(uid) && !isPlaybackActive(uid)) {
Jaewan Kim92dea332017-02-02 11:52:08 +0900255 // Clean up unnecessary UIDs.
256 // It doesn't need to be managed profile aware because it's just to prevent
257 // the list from increasing indefinitely. The media button session updating
258 // shouldn't be affected by cleaning up.
259 mSortedAudioPlaybackClientUids.remove(i);
260 }
261 }
262 }
263 }
264
265 /**
266 * Dumps {@link AudioPlaybackMonitor}.
267 */
268 public void dump(PrintWriter pw, String prefix) {
269 synchronized (mLock) {
270 pw.println(prefix + "Audio playback (lastly played comes first)");
271 String indent = prefix + " ";
272 for (int i = 0; i < mSortedAudioPlaybackClientUids.size(); i++) {
273 int uid = mSortedAudioPlaybackClientUids.get(i);
274 pw.print(indent + "uid=" + uid + " packages=");
275 String[] packages = mContext.getPackageManager().getPackagesForUid(uid);
276 if (packages != null && packages.length > 0) {
277 for (int j = 0; j < packages.length; j++) {
278 pw.print(packages[j] + " ");
279 }
280 }
281 pw.println();
282 }
283 }
284 }
Sungsoob3658562017-05-22 17:10:44 +0900285
286 private boolean isActiveState(Integer state) {
287 return state != null && state.equals(AudioPlaybackConfiguration.PLAYER_STATE_STARTED);
288 }
Jaewan Kim92dea332017-02-02 11:52:08 +0900289}