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