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; 34 import java.util.List; 35 import java.util.Set; 36 37 /** 38 * Monitors changes in audio playback and notify the newly started audio playback through the 39 * {@link OnAudioPlaybackStartedListener}. 40 */ 41 class AudioPlaybackMonitor extends IPlaybackConfigDispatcher.Stub { 42 private static boolean DEBUG = MediaSessionService.DEBUG; 43 private static String TAG = "AudioPlaybackMonitor"; 44 45 /** 46 * Called when audio playback is started for a given UID. 47 */ 48 interface OnAudioPlaybackStartedListener { 49 void onAudioPlaybackStarted(int uid); 50 } 51 52 private final Object mLock = new Object(); 53 private final Context mContext; 54 private final OnAudioPlaybackStartedListener mListener; 55 56 private Set<Integer> mActiveAudioPlaybackPlayerInterfaceIds = new HashSet<>(); 57 private Set<Integer> mActiveAudioPlaybackClientUids = new HashSet<>(); 58 59 // Sorted array of UIDs that had active audio playback. (i.e. playing an audio/video) 60 // The UID whose audio playback becomes active at the last comes first. 61 // TODO(b/35278867): Find and use unique identifier for apps because apps may share the UID. 62 private final IntArray mSortedAudioPlaybackClientUids = new IntArray(); 63 64 AudioPlaybackMonitor(Context context, IAudioService audioService, 65 OnAudioPlaybackStartedListener listener) { 66 mContext = context; 67 mListener = listener; 68 try { 69 audioService.registerPlaybackCallback(this); 70 } catch (RemoteException e) { 71 Log.wtf(TAG, "Failed to register playback callback", e); 72 } 73 } 74 75 /** 76 * Called when the {@link AudioPlaybackConfiguration} is updated. 77 * <p>If an app starts audio playback, the app's local media session will be the media button 78 * session. If the app has multiple media sessions, the playback active local session will be 79 * picked. 80 * 81 * @param configs List of the current audio playback configuration 82 */ 83 @Override 84 public void dispatchPlaybackConfigChange(List<AudioPlaybackConfiguration> configs) { 85 final long token = Binder.clearCallingIdentity(); 86 try { 87 Set<Integer> newActiveAudioPlaybackPlayerInterfaceIds = new HashSet<>(); 88 List<Integer> newActiveAudioPlaybackClientUids = new ArrayList<>(); 89 synchronized (mLock) { 90 mActiveAudioPlaybackClientUids.clear(); 91 for (AudioPlaybackConfiguration config : configs) { 92 // Ignore inactive (i.e. not playing) or PLAYER_TYPE_JAM_SOUNDPOOL 93 // (i.e. playback from the SoundPool class which is only for sound effects) 94 // playback. 95 // Note that we shouldn't ignore PLAYER_TYPE_UNKNOWN because it might be OEM 96 // specific audio/video players. 97 if (!config.isActive() 98 || config.getPlayerType() 99 == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) { 100 continue; 101 } 102 mActiveAudioPlaybackClientUids.add(config.getClientUid()); 103 104 newActiveAudioPlaybackPlayerInterfaceIds.add(config.getPlayerInterfaceId()); 105 if (!mActiveAudioPlaybackPlayerInterfaceIds.contains( 106 config.getPlayerInterfaceId())) { 107 if (DEBUG) { 108 Log.d(TAG, "Found a new active media playback. " + 109 AudioPlaybackConfiguration.toLogFriendlyString(config)); 110 } 111 // New active audio playback. 112 newActiveAudioPlaybackClientUids.add(config.getClientUid()); 113 int index = mSortedAudioPlaybackClientUids.indexOf(config.getClientUid()); 114 if (index == 0) { 115 // It's the lastly played music app already. Skip updating. 116 continue; 117 } else if (index > 0) { 118 mSortedAudioPlaybackClientUids.remove(index); 119 } 120 mSortedAudioPlaybackClientUids.add(0, config.getClientUid()); 121 } 122 } 123 mActiveAudioPlaybackPlayerInterfaceIds.clear(); 124 mActiveAudioPlaybackPlayerInterfaceIds = newActiveAudioPlaybackPlayerInterfaceIds; 125 } 126 for (int uid : newActiveAudioPlaybackClientUids) { 127 mListener.onAudioPlaybackStarted(uid); 128 } 129 } finally { 130 Binder.restoreCallingIdentity(token); 131 } 132 } 133 134 /** 135 * Returns the sorted list of UIDs that have had active audio playback. (i.e. playing an 136 * audio/video) The UID whose audio playback becomes active at the last comes first. 137 */ 138 public IntArray getSortedAudioPlaybackClientUids() { 139 IntArray sortedAudioPlaybackClientUids = new IntArray(); 140 synchronized (mLock) { 141 sortedAudioPlaybackClientUids.addAll(mSortedAudioPlaybackClientUids); 142 } 143 return sortedAudioPlaybackClientUids; 144 } 145 146 /** 147 * Returns if the audio playback is active for the uid. 148 */ 149 public boolean isPlaybackActive(int uid) { 150 synchronized (mLock) { 151 return mActiveAudioPlaybackClientUids.contains(uid); 152 } 153 } 154 155 /** 156 * Cleans up the sorted list of audio playback client UIDs with given {@param 157 * mediaButtonSessionUid}. 158 * <p>UIDs whose audio playback started after the media button session's audio playback 159 * cannot be the lastly played media app. So they won't needed anymore. 160 * 161 * @param mediaButtonSessionUid UID of the media button session. 162 */ 163 public void cleanUpAudioPlaybackUids(int mediaButtonSessionUid) { 164 synchronized (mLock) { 165 int userId = UserHandle.getUserId(mediaButtonSessionUid); 166 for (int i = mSortedAudioPlaybackClientUids.size() - 1; i >= 0; i--) { 167 if (mSortedAudioPlaybackClientUids.get(i) == mediaButtonSessionUid) { 168 break; 169 } 170 if (userId == UserHandle.getUserId(mSortedAudioPlaybackClientUids.get(i))) { 171 // Clean up unnecessary UIDs. 172 // It doesn't need to be managed profile aware because it's just to prevent 173 // the list from increasing indefinitely. The media button session updating 174 // shouldn't be affected by cleaning up. 175 mSortedAudioPlaybackClientUids.remove(i); 176 } 177 } 178 } 179 } 180 181 /** 182 * Dumps {@link AudioPlaybackMonitor}. 183 */ 184 public void dump(PrintWriter pw, String prefix) { 185 synchronized (mLock) { 186 pw.println(prefix + "Audio playback (lastly played comes first)"); 187 String indent = prefix + " "; 188 for (int i = 0; i < mSortedAudioPlaybackClientUids.size(); i++) { 189 int uid = mSortedAudioPlaybackClientUids.get(i); 190 pw.print(indent + "uid=" + uid + " packages="); 191 String[] packages = mContext.getPackageManager().getPackagesForUid(uid); 192 if (packages != null && packages.length > 0) { 193 for (int j = 0; j < packages.length; j++) { 194 pw.print(packages[j] + " "); 195 } 196 } 197 pw.println(); 198 } 199 } 200 } 201 } 202