Jaewan Kim | 92dea33 | 2017-02-02 11:52:08 +0900 | [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 | |
| 19 | import android.content.Context; |
| 20 | import android.media.AudioManager.AudioPlaybackCallback; |
| 21 | import android.media.AudioPlaybackConfiguration; |
| 22 | import android.media.IAudioService; |
| 23 | import android.media.IPlaybackConfigDispatcher; |
| 24 | import android.os.Binder; |
| 25 | import android.os.RemoteException; |
| 26 | import android.os.UserHandle; |
| 27 | import android.util.IntArray; |
| 28 | import android.util.Log; |
| 29 | import android.util.SparseArray; |
| 30 | |
| 31 | import java.io.PrintWriter; |
| 32 | import java.util.ArrayList; |
| 33 | import java.util.HashSet; |
Sungsoo | b365856 | 2017-05-22 17:10:44 +0900 | [diff] [blame] | 34 | import java.util.HashMap; |
Jaewan Kim | 92dea33 | 2017-02-02 11:52:08 +0900 | [diff] [blame] | 35 | import java.util.List; |
Sungsoo | b365856 | 2017-05-22 17:10:44 +0900 | [diff] [blame] | 36 | import java.util.Map; |
Jaewan Kim | 92dea33 | 2017-02-02 11:52:08 +0900 | [diff] [blame] | 37 | import java.util.Set; |
| 38 | |
| 39 | /** |
Sungsoo | b365856 | 2017-05-22 17:10:44 +0900 | [diff] [blame] | 40 | * 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 Kim | 92dea33 | 2017-02-02 11:52:08 +0900 | [diff] [blame] | 43 | */ |
| 44 | class AudioPlaybackMonitor extends IPlaybackConfigDispatcher.Stub { |
| 45 | private static boolean DEBUG = MediaSessionService.DEBUG; |
| 46 | private static String TAG = "AudioPlaybackMonitor"; |
| 47 | |
Sungsoo | b365856 | 2017-05-22 17:10:44 +0900 | [diff] [blame] | 48 | private static AudioPlaybackMonitor sInstance; |
| 49 | |
Jaewan Kim | 92dea33 | 2017-02-02 11:52:08 +0900 | [diff] [blame] | 50 | /** |
| 51 | * Called when audio playback is started for a given UID. |
| 52 | */ |
| 53 | interface OnAudioPlaybackStartedListener { |
| 54 | void onAudioPlaybackStarted(int uid); |
| 55 | } |
| 56 | |
Sungsoo | b365856 | 2017-05-22 17:10:44 +0900 | [diff] [blame] | 57 | /** |
| 58 | * Called when audio player state is changed. |
| 59 | */ |
| 60 | interface OnAudioPlayerActiveStateChangedListener { |
| 61 | void onAudioPlayerActiveStateChanged(int uid, boolean active); |
| 62 | } |
| 63 | |
Jaewan Kim | 92dea33 | 2017-02-02 11:52:08 +0900 | [diff] [blame] | 64 | private final Object mLock = new Object(); |
| 65 | private final Context mContext; |
Sungsoo | b365856 | 2017-05-22 17:10:44 +0900 | [diff] [blame] | 66 | 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 Kim | 92dea33 | 2017-02-02 11:52:08 +0900 | [diff] [blame] | 72 | |
| 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 | |
Sungsoo | b365856 | 2017-05-22 17:10:44 +0900 | [diff] [blame] | 78 | 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 Kim | 92dea33 | 2017-02-02 11:52:08 +0900 | [diff] [blame] | 86 | mContext = context; |
Jaewan Kim | 92dea33 | 2017-02-02 11:52:08 +0900 | [diff] [blame] | 87 | 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 Trivi | 776a399 | 2017-09-12 16:45:34 -0700 | [diff] [blame] | 103 | public void dispatchPlaybackConfigChange(List<AudioPlaybackConfiguration> configs, |
| 104 | boolean flush) { |
| 105 | if (flush) { |
| 106 | Binder.flushPendingCommands(); |
| 107 | } |
Jaewan Kim | 92dea33 | 2017-02-02 11:52:08 +0900 | [diff] [blame] | 108 | final long token = Binder.clearCallingIdentity(); |
| 109 | try { |
Jaewan Kim | 92dea33 | 2017-02-02 11:52:08 +0900 | [diff] [blame] | 110 | List<Integer> newActiveAudioPlaybackClientUids = new ArrayList<>(); |
Sungsoo | b365856 | 2017-05-22 17:10:44 +0900 | [diff] [blame] | 111 | List<OnAudioPlayerActiveStateChangedListener> audioPlayerActiveStateChangedListeners; |
| 112 | List<OnAudioPlaybackStartedListener> audioPlaybackStartedListeners; |
Jaewan Kim | 92dea33 | 2017-02-02 11:52:08 +0900 | [diff] [blame] | 113 | synchronized (mLock) { |
Sungsoo | b365856 | 2017-05-22 17:10:44 +0900 | [diff] [blame] | 114 | // Update mActiveAudioPlaybackClientUids and mSortedAudioPlaybackClientUids, |
| 115 | // and find newly activated audio playbacks. |
Jaewan Kim | 92dea33 | 2017-02-02 11:52:08 +0900 | [diff] [blame] | 116 | 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. |
Sungsoo | b365856 | 2017-05-22 17:10:44 +0900 | [diff] [blame] | 123 | if (!config.isActive() || config.getPlayerType() |
Jaewan Kim | 92dea33 | 2017-02-02 11:52:08 +0900 | [diff] [blame] | 124 | == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) { |
| 125 | continue; |
| 126 | } |
Jaewan Kim | 92dea33 | 2017-02-02 11:52:08 +0900 | [diff] [blame] | 127 | |
Sungsoo | b365856 | 2017-05-22 17:10:44 +0900 | [diff] [blame] | 128 | mActiveAudioPlaybackClientUids.add(config.getClientUid()); |
| 129 | Integer oldState = mAudioPlaybackStates.get(config.getPlayerInterfaceId()); |
| 130 | if (!isActiveState(oldState)) { |
Jaewan Kim | 92dea33 | 2017-02-02 11:52:08 +0900 | [diff] [blame] | 131 | 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 | } |
Sungsoo | b365856 | 2017-05-22 17:10:44 +0900 | [diff] [blame] | 147 | audioPlayerActiveStateChangedListeners = new ArrayList<>( |
| 148 | mAudioPlayerActiveStateChangedListeners); |
| 149 | audioPlaybackStartedListeners = new ArrayList<>(mAudioPlaybackStartedListeners); |
Jaewan Kim | 92dea33 | 2017-02-02 11:52:08 +0900 | [diff] [blame] | 150 | } |
Sungsoo | b365856 | 2017-05-22 17:10:44 +0900 | [diff] [blame] | 151 | // 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 Kim | 92dea33 | 2017-02-02 11:52:08 +0900 | [diff] [blame] | 165 | for (int uid : newActiveAudioPlaybackClientUids) { |
Sungsoo | b365856 | 2017-05-22 17:10:44 +0900 | [diff] [blame] | 166 | 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 Kim | 92dea33 | 2017-02-02 11:52:08 +0900 | [diff] [blame] | 173 | } |
| 174 | } finally { |
| 175 | Binder.restoreCallingIdentity(token); |
| 176 | } |
| 177 | } |
| 178 | |
| 179 | /** |
Sungsoo | b365856 | 2017-05-22 17:10:44 +0900 | [diff] [blame] | 180 | * 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 Kim | 92dea33 | 2017-02-02 11:52:08 +0900 | [diff] [blame] | 218 | * 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 | } |
Sungsoo | b365856 | 2017-05-22 17:10:44 +0900 | [diff] [blame] | 253 | int uid = mSortedAudioPlaybackClientUids.get(i); |
| 254 | if (userId == UserHandle.getUserId(uid) && !isPlaybackActive(uid)) { |
Jaewan Kim | 92dea33 | 2017-02-02 11:52:08 +0900 | [diff] [blame] | 255 | // 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 | } |
Sungsoo | b365856 | 2017-05-22 17:10:44 +0900 | [diff] [blame] | 285 | |
| 286 | private boolean isActiveState(Integer state) { |
| 287 | return state != null && state.equals(AudioPlaybackConfiguration.PLAYER_STATE_STARTED); |
| 288 | } |
Jaewan Kim | 92dea33 | 2017-02-02 11:52:08 +0900 | [diff] [blame] | 289 | } |