1 /* 2 * Copyright (C) 2023 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.audio; 18 19 import static android.content.pm.PackageManager.MATCH_ANY_USER; 20 21 import static com.android.server.audio.AudioService.MUSICFX_HELPER_MSG_START; 22 23 import android.annotation.NonNull; 24 import android.annotation.RequiresPermission; 25 import android.app.ActivityManager; 26 import android.app.IUidObserver; 27 import android.app.UidObserver; 28 import android.content.ComponentName; 29 import android.content.Context; 30 import android.content.Intent; 31 import android.content.ServiceConnection; 32 import android.content.pm.PackageManager; 33 import android.content.pm.ResolveInfo; 34 import android.content.pm.UserInfo; 35 import android.media.AudioManager; 36 import android.media.audiofx.AudioEffect; 37 import android.os.Binder; 38 import android.os.IBinder; 39 import android.os.Message; 40 import android.os.RemoteException; 41 import android.os.UserHandle; 42 import android.util.Log; 43 import android.util.SparseArray; 44 45 import com.android.internal.annotations.GuardedBy; 46 import com.android.server.audio.AudioService.AudioHandler; 47 48 import java.util.ArrayList; 49 import java.util.List; 50 51 /** 52 * MusicFx management. 53 */ 54 public class MusicFxHelper { 55 private static final String TAG = "AS.MusicFxHelper"; 56 57 @NonNull private final Context mContext; 58 59 @NonNull private final AudioHandler mAudioHandler; 60 61 // Synchronization UidSessionMap access between UidObserver and AudioServiceBroadcastReceiver. 62 private final Object mClientUidMapLock = new Object(); 63 64 private final String mPackageName = this.getClass().getPackage().getName(); 65 66 private final String mMusicFxPackageName = "com.android.musicfx"; 67 68 /*package*/ static final int MSG_EFFECT_CLIENT_GONE = MUSICFX_HELPER_MSG_START + 1; 69 70 // The binder token identifying the UidObserver registration. 71 private IBinder mUidObserverToken = null; 72 73 private boolean mIsBound; 74 75 // Package name and list of open audio sessions for this package 76 private static class PackageSessions { 77 String mPackageName; 78 List<Integer> mSessions; 79 } 80 81 /* 82 * Override of SparseArray class to add bind/unbind and UID observer in the put/remove methods. 83 * 84 * put: 85 * - the first key/value set put into MySparseArray will trigger a procState bump (bindService) 86 * - if no valid observer token exist, will call registerUidObserver for put 87 * - for each new uid put into array, it will be added to uid observer list 88 * 89 * remove: 90 * - for each uid removed from array, it will be removed from uid observer list as well 91 * - if it's the last uid in array, no more MusicFx procState bump (unbindService), uid 92 * observer will also be removed, and observer token reset to null 93 */ 94 private class MySparseArray extends SparseArray<PackageSessions> { 95 96 @RequiresPermission(anyOf = { 97 android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, 98 android.Manifest.permission.INTERACT_ACROSS_USERS, 99 android.Manifest.permission.INTERACT_ACROSS_PROFILES 100 }) 101 @Override put(int uid, PackageSessions pkgSessions)102 public void put(int uid, PackageSessions pkgSessions) { 103 if (size() == 0) { 104 int procState = ActivityManager.PROCESS_STATE_NONEXISTENT; 105 try { 106 procState = ActivityManager.getService().getPackageProcessState( 107 mMusicFxPackageName, mPackageName); 108 } catch (RemoteException e) { 109 Log.e(TAG, "RemoteException with getPackageProcessState: " + e); 110 } 111 if (procState > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) { 112 Intent bindIntent = new Intent().setClassName(mMusicFxPackageName, 113 "com.android.musicfx.KeepAliveService"); 114 mIsBound = true; 115 mContext.bindServiceAsUser( 116 bindIntent, mMusicFxBindConnection, Context.BIND_AUTO_CREATE, 117 UserHandle.of(getCurrentUserId())); 118 Log.i(TAG, "bindService to " + mMusicFxPackageName); 119 } 120 121 Log.i(TAG, mMusicFxPackageName + " procState " + procState); 122 } 123 try { 124 if (mUidObserverToken == null) { 125 mUidObserverToken = ActivityManager.getService().registerUidObserverForUids( 126 mEffectUidObserver, ActivityManager.UID_OBSERVER_GONE, 127 ActivityManager.PROCESS_STATE_UNKNOWN, mPackageName, 128 new int[]{uid}); 129 Log.i(TAG, "registered to observer with UID " + uid); 130 } else if (get(uid) == null) { // addUidToObserver if this is a new UID 131 ActivityManager.getService().addUidToObserver(mUidObserverToken, mPackageName, 132 uid); 133 Log.i(TAG, " UID " + uid + " add to observer"); 134 } 135 } catch (RemoteException e) { 136 Log.e(TAG, "RemoteException with UID observer add/register: " + e); 137 } 138 139 super.put(uid, pkgSessions); 140 } 141 142 @Override remove(int uid)143 public void remove(int uid) { 144 if (get(uid) != null) { 145 try { 146 ActivityManager.getService().removeUidFromObserver(mUidObserverToken, 147 mPackageName, uid); 148 } catch (RemoteException e) { 149 Log.e(TAG, "RemoteException with removeUidFromObserver: " + e); 150 } 151 } 152 153 super.remove(uid); 154 155 // stop foreground service delegate and unregister UID observers with the last UID 156 if (size() == 0) { 157 try { 158 ActivityManager.getService().unregisterUidObserver(mEffectUidObserver); 159 } catch (RemoteException e) { 160 Log.e(TAG, "RemoteException with unregisterUidObserver: " + e); 161 } 162 mUidObserverToken = null; 163 if (mIsBound) { 164 mContext.unbindService(mMusicFxBindConnection); 165 mIsBound = false; 166 Log.i(TAG, "last session closed, unregister UID observer, and unbind " 167 + mMusicFxPackageName); 168 } 169 } 170 } 171 } 172 173 // Hashmap of UID and list of open sessions for this UID. 174 @GuardedBy("mClientUidMapLock") 175 private MySparseArray mClientUidSessionMap = new MySparseArray(); 176 177 // UID observer for effect MusicFx clients 178 private final IUidObserver mEffectUidObserver = new UidObserver() { 179 @Override public void onUidGone(int uid, boolean disabled) { 180 Log.w(TAG, " send MSG_EFFECT_CLIENT_GONE"); 181 mAudioHandler.sendMessageAtTime( 182 mAudioHandler.obtainMessage(MSG_EFFECT_CLIENT_GONE, 183 uid /* arg1 */, 0 /* arg2 */, 184 null /* obj */), 0 /* delay */); 185 } 186 }; 187 188 // BindService connection implementation, we don't need any implementation now 189 private ServiceConnection mMusicFxBindConnection = new ServiceConnection() { 190 @Override 191 public void onServiceConnected(ComponentName name, IBinder service) { 192 Log.d(TAG, " service connected to " + name); 193 } 194 195 @Override 196 public void onServiceDisconnected(ComponentName name) { 197 Log.d(TAG, " service disconnected from " + name); 198 } 199 }; 200 MusicFxHelper(@onNull Context context, @NonNull AudioHandler audioHandler)201 MusicFxHelper(@NonNull Context context, @NonNull AudioHandler audioHandler) { 202 mContext = context; 203 mAudioHandler = audioHandler; 204 } 205 206 /** 207 * Handle the broadcast {@link #ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION} and 208 * {@link #ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION} intents. 209 * 210 * Only intents without target application package {@link android.content.Intent#getPackage} 211 * will be handled by the MusicFxHelper, all intents handled and forwarded by MusicFxHelper 212 * will have the target application package. 213 * 214 * If the intent is {@link #ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION}: 215 * - If the MusicFx process is not running, call bindServiceAsUser with AUTO_CREATE to create. 216 * - If this is the first audio session of MusicFx, call set foreground service delegate. 217 * - If this is the first audio session for a given UID, add the UID into observer. 218 * 219 * If the intent is {@link #ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION} 220 * - The KeepAliveService of MusicFx will be unbound, and MusicFx will not be foreground 221 * delegated anymore if the last session of the last package was closed. 222 * - The Uid Observer will be removed when the last session of a package was closed. 223 */ 224 @RequiresPermission(allOf = {android.Manifest.permission.INTERACT_ACROSS_USERS}) handleAudioEffectBroadcast(Context context, Intent intent)225 public void handleAudioEffectBroadcast(Context context, Intent intent) { 226 String target = intent.getPackage(); 227 if (target != null) { 228 Log.w(TAG, "effect broadcast already targeted to " + target); 229 return; 230 } 231 final PackageManager pm = context.getPackageManager(); 232 // TODO this should target a user-selected panel 233 List<ResolveInfo> ril = pm.queryBroadcastReceivers(intent, 0 /* flags */); 234 if (ril != null && ril.size() != 0) { 235 ResolveInfo ri = ril.get(0); 236 final String senderPackageName = intent.getStringExtra(AudioEffect.EXTRA_PACKAGE_NAME); 237 if (senderPackageName == null) { 238 Log.w(TAG, "Intent package name must not be null"); 239 return; 240 } 241 try { 242 if (ri != null && ri.activityInfo != null && ri.activityInfo.packageName != null) { 243 final int senderUid = pm.getPackageUidAsUser(senderPackageName, 244 PackageManager.PackageInfoFlags.of(MATCH_ANY_USER), getCurrentUserId()); 245 intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); 246 intent.setPackage(ri.activityInfo.packageName); 247 if (setMusicFxServiceWithObserver(intent, senderUid, senderPackageName)) { 248 context.sendBroadcastAsUser(intent, UserHandle.ALL); 249 } 250 return; 251 } 252 } catch (PackageManager.NameNotFoundException e) { 253 Log.e(TAG, "Not able to find UID from package: " + senderPackageName + " error: " 254 + e); 255 } 256 } 257 Log.w(TAG, "couldn't find receiver package for effect intent"); 258 } 259 260 @RequiresPermission(anyOf = { 261 android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, 262 android.Manifest.permission.INTERACT_ACROSS_USERS, 263 android.Manifest.permission.INTERACT_ACROSS_PROFILES 264 }) 265 @GuardedBy("mClientUidMapLock") handleAudioEffectSessionOpen( int senderUid, String senderPackageName, int sessionId)266 private boolean handleAudioEffectSessionOpen( 267 int senderUid, String senderPackageName, int sessionId) { 268 Log.d(TAG, senderPackageName + " UID " + senderUid + " open MusicFx session " + sessionId); 269 270 PackageSessions pkgSessions = mClientUidSessionMap.get(Integer.valueOf(senderUid)); 271 if (pkgSessions != null && pkgSessions.mSessions != null) { 272 if (pkgSessions.mSessions.contains(sessionId)) { 273 Log.e(TAG, "Audio session " + sessionId + " already open for UID: " 274 + senderUid + ", package: " + senderPackageName + ", abort"); 275 return false; 276 } 277 if (!pkgSessions.mPackageName.equals(senderPackageName)) { 278 Log.w(TAG, "Inconsistency package names for UID open: " + senderUid + " prev: " 279 + pkgSessions.mPackageName + ", now: " + senderPackageName); 280 return false; 281 } 282 } else { 283 // first session for this UID, create a new Package/Sessions pair 284 pkgSessions = new PackageSessions(); 285 pkgSessions.mSessions = new ArrayList(); 286 pkgSessions.mPackageName = senderPackageName; 287 } 288 289 pkgSessions.mSessions.add(Integer.valueOf(sessionId)); 290 mClientUidSessionMap.put(Integer.valueOf(senderUid), pkgSessions); 291 return true; 292 } 293 294 @RequiresPermission(anyOf = { 295 android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, 296 android.Manifest.permission.INTERACT_ACROSS_USERS, 297 android.Manifest.permission.INTERACT_ACROSS_PROFILES 298 }) 299 @GuardedBy("mClientUidMapLock") handleAudioEffectSessionClose( int senderUid, String senderPackageName, int sessionId)300 private boolean handleAudioEffectSessionClose( 301 int senderUid, String senderPackageName, int sessionId) { 302 Log.d(TAG, senderPackageName + " UID " + senderUid + " close MusicFx session " + sessionId); 303 304 PackageSessions pkgSessions = mClientUidSessionMap.get(Integer.valueOf(senderUid)); 305 if (pkgSessions == null) { 306 Log.e(TAG, senderPackageName + " UID " + senderUid + " does not exist in map, abort"); 307 return false; 308 } 309 if (!pkgSessions.mPackageName.equals(senderPackageName)) { 310 Log.w(TAG, "Inconsistency package names for UID " + senderUid + " close, prev: " 311 + pkgSessions.mPackageName + ", now: " + senderPackageName); 312 return false; 313 } 314 315 if (pkgSessions.mSessions != null && pkgSessions.mSessions.size() != 0) { 316 if (!pkgSessions.mSessions.contains(sessionId)) { 317 Log.e(TAG, senderPackageName + " UID " + senderUid + " session " + sessionId 318 + " does not exist in map, abort"); 319 return false; 320 } 321 322 pkgSessions.mSessions.remove(Integer.valueOf(sessionId)); 323 } 324 325 if (pkgSessions.mSessions == null || pkgSessions.mSessions.size() == 0) { 326 // remove UID from map as well as the UID observer with the last session close 327 mClientUidSessionMap.remove(Integer.valueOf(senderUid)); 328 } else { 329 mClientUidSessionMap.put(Integer.valueOf(senderUid), pkgSessions); 330 } 331 332 return true; 333 } 334 335 /** 336 * @return true if the intent is validated and handled successfully, false with any error 337 * (invalid sender/intent for example). 338 */ 339 @RequiresPermission(anyOf = { 340 android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, 341 android.Manifest.permission.INTERACT_ACROSS_USERS, 342 android.Manifest.permission.INTERACT_ACROSS_PROFILES 343 }) setMusicFxServiceWithObserver( Intent intent, int senderUid, String packageName)344 private boolean setMusicFxServiceWithObserver( 345 Intent intent, int senderUid, String packageName) { 346 final int session = intent.getIntExtra(AudioEffect.EXTRA_AUDIO_SESSION, 347 AudioManager.AUDIO_SESSION_ID_GENERATE); 348 if (AudioManager.AUDIO_SESSION_ID_GENERATE == session) { 349 Log.e(TAG, packageName + " intent have no invalid audio session"); 350 return false; 351 } 352 353 synchronized (mClientUidMapLock) { 354 if (intent.getAction().equals(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION)) { 355 return handleAudioEffectSessionOpen(senderUid, packageName, session); 356 } else { 357 return handleAudioEffectSessionClose(senderUid, packageName, session); 358 } 359 } 360 } 361 getCurrentUserId()362 private int getCurrentUserId() { 363 final long ident = Binder.clearCallingIdentity(); 364 try { 365 UserInfo currentUser = ActivityManager.getService().getCurrentUser(); 366 return currentUser.id; 367 } catch (RemoteException e) { 368 // Activity manager not running, nothing we can do assume user 0. 369 } finally { 370 Binder.restoreCallingIdentity(ident); 371 } 372 return UserHandle.USER_SYSTEM; 373 } 374 375 376 /** 377 * Handle the UidObserver onUidGone callback of MusicFx clients. 378 * Send close intent for all open audio sessions of this UID. The mClientUidSessionMap will be 379 * updated with the handling of close intent in setMusicFxServiceWithObserver. 380 */ 381 @RequiresPermission(allOf = {android.Manifest.permission.INTERACT_ACROSS_USERS}) handleEffectClientUidGone(int uid)382 private void handleEffectClientUidGone(int uid) { 383 synchronized (mClientUidMapLock) { 384 Log.d(TAG, "handle MSG_EFFECT_CLIENT_GONE uid: " + uid + " mapSize: " 385 + mClientUidSessionMap.size()); 386 // Once the uid is no longer running, close all remain audio session(s) for this UID 387 final PackageSessions pkgSessions = mClientUidSessionMap.get(Integer.valueOf(uid)); 388 if (pkgSessions != null) { 389 Log.i(TAG, "UID " + uid + " gone, closing all sessions"); 390 391 // send close intent for each open session of the gone UID 392 for (Integer sessionId : pkgSessions.mSessions) { 393 Intent closeIntent = 394 new Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION); 395 closeIntent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, pkgSessions.mPackageName); 396 closeIntent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, sessionId); 397 closeIntent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); 398 // set broadcast target 399 closeIntent.setPackage(mMusicFxPackageName); 400 mContext.sendBroadcastAsUser(closeIntent, UserHandle.ALL); 401 } 402 mClientUidSessionMap.remove(Integer.valueOf(uid)); 403 } 404 } 405 } 406 407 @RequiresPermission(allOf = {android.Manifest.permission.INTERACT_ACROSS_USERS}) handleMessage(Message msg)408 /*package*/ void handleMessage(Message msg) { 409 switch (msg.what) { 410 case MSG_EFFECT_CLIENT_GONE: 411 Log.w(TAG, " handle MSG_EFFECT_CLIENT_GONE"); 412 handleEffectClientUidGone(msg.arg1 /* uid */); 413 break; 414 default: 415 Log.e(TAG, "Unexpected msg to handle in MusicFxHelper: " + msg.what); 416 break; 417 } 418 } 419 } 420