1 /* 2 * Copyright (C) 2013 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 android.app.AppOpsManager; 20 import android.content.Context; 21 import android.media.AudioAttributes; 22 import android.media.AudioFocusInfo; 23 import android.media.AudioManager; 24 import android.media.AudioSystem; 25 import android.media.IAudioFocusDispatcher; 26 import android.media.audiopolicy.IAudioPolicyCallback; 27 import android.os.Binder; 28 import android.os.IBinder; 29 import android.os.RemoteException; 30 import android.util.Log; 31 32 import java.io.PrintWriter; 33 import java.util.ArrayList; 34 import java.util.Date; 35 import java.util.Iterator; 36 import java.util.Stack; 37 import java.text.DateFormat; 38 39 /** 40 * @hide 41 * 42 */ 43 public class MediaFocusControl { 44 45 private static final String TAG = "MediaFocusControl"; 46 47 private final Context mContext; 48 private final AppOpsManager mAppOps; 49 MediaFocusControl(Context cntxt)50 protected MediaFocusControl(Context cntxt) { 51 mContext = cntxt; 52 mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE); 53 } 54 dump(PrintWriter pw)55 protected void dump(PrintWriter pw) { 56 pw.println("\nMediaFocusControl dump time: " 57 + DateFormat.getTimeInstance().format(new Date())); 58 dumpFocusStack(pw); 59 } 60 61 62 //========================================================================================== 63 // AudioFocus 64 //========================================================================================== 65 66 private final static Object mAudioFocusLock = new Object(); 67 68 /** 69 * Discard the current audio focus owner. 70 * Notify top of audio focus stack that it lost focus (regardless of possibility to reassign 71 * focus), remove it from the stack, and clear the remote control display. 72 */ discardAudioFocusOwner()73 protected void discardAudioFocusOwner() { 74 synchronized(mAudioFocusLock) { 75 if (!mFocusStack.empty()) { 76 // notify the current focus owner it lost focus after removing it from stack 77 final FocusRequester exFocusOwner = mFocusStack.pop(); 78 exFocusOwner.handleFocusLoss(AudioManager.AUDIOFOCUS_LOSS); 79 exFocusOwner.release(); 80 } 81 } 82 } 83 84 /** 85 * Called synchronized on mAudioFocusLock 86 */ notifyTopOfAudioFocusStack()87 private void notifyTopOfAudioFocusStack() { 88 // notify the top of the stack it gained focus 89 if (!mFocusStack.empty()) { 90 if (canReassignAudioFocus()) { 91 mFocusStack.peek().handleFocusGain(AudioManager.AUDIOFOCUS_GAIN); 92 } 93 } 94 } 95 96 /** 97 * Focus is requested, propagate the associated loss throughout the stack. 98 * @param focusGain the new focus gain that will later be added at the top of the stack 99 */ propagateFocusLossFromGain_syncAf(int focusGain)100 private void propagateFocusLossFromGain_syncAf(int focusGain) { 101 // going through the audio focus stack to signal new focus, traversing order doesn't 102 // matter as all entries respond to the same external focus gain 103 Iterator<FocusRequester> stackIterator = mFocusStack.iterator(); 104 while(stackIterator.hasNext()) { 105 stackIterator.next().handleExternalFocusGain(focusGain); 106 } 107 } 108 109 private final Stack<FocusRequester> mFocusStack = new Stack<FocusRequester>(); 110 111 /** 112 * Helper function: 113 * Display in the log the current entries in the audio focus stack 114 */ dumpFocusStack(PrintWriter pw)115 private void dumpFocusStack(PrintWriter pw) { 116 pw.println("\nAudio Focus stack entries (last is top of stack):"); 117 synchronized(mAudioFocusLock) { 118 Iterator<FocusRequester> stackIterator = mFocusStack.iterator(); 119 while(stackIterator.hasNext()) { 120 stackIterator.next().dump(pw); 121 } 122 } 123 pw.println("\n Notify on duck: " + mNotifyFocusOwnerOnDuck +"\n"); 124 } 125 126 /** 127 * Helper function: 128 * Called synchronized on mAudioFocusLock 129 * Remove a focus listener from the focus stack. 130 * @param clientToRemove the focus listener 131 * @param signal if true and the listener was at the top of the focus stack, i.e. it was holding 132 * focus, notify the next item in the stack it gained focus. 133 */ removeFocusStackEntry(String clientToRemove, boolean signal, boolean notifyFocusFollowers)134 private void removeFocusStackEntry(String clientToRemove, boolean signal, 135 boolean notifyFocusFollowers) { 136 // is the current top of the focus stack abandoning focus? (because of request, not death) 137 if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientToRemove)) 138 { 139 //Log.i(TAG, " removeFocusStackEntry() removing top of stack"); 140 FocusRequester fr = mFocusStack.pop(); 141 fr.release(); 142 if (notifyFocusFollowers) { 143 final AudioFocusInfo afi = fr.toAudioFocusInfo(); 144 afi.clearLossReceived(); 145 notifyExtPolicyFocusLoss_syncAf(afi, false); 146 } 147 if (signal) { 148 // notify the new top of the stack it gained focus 149 notifyTopOfAudioFocusStack(); 150 } 151 } else { 152 // focus is abandoned by a client that's not at the top of the stack, 153 // no need to update focus. 154 // (using an iterator on the stack so we can safely remove an entry after having 155 // evaluated it, traversal order doesn't matter here) 156 Iterator<FocusRequester> stackIterator = mFocusStack.iterator(); 157 while(stackIterator.hasNext()) { 158 FocusRequester fr = stackIterator.next(); 159 if(fr.hasSameClient(clientToRemove)) { 160 Log.i(TAG, "AudioFocus removeFocusStackEntry(): removing entry for " 161 + clientToRemove); 162 stackIterator.remove(); 163 fr.release(); 164 } 165 } 166 } 167 } 168 169 /** 170 * Helper function: 171 * Called synchronized on mAudioFocusLock 172 * Remove focus listeners from the focus stack for a particular client when it has died. 173 */ removeFocusStackEntryForClient(IBinder cb)174 private void removeFocusStackEntryForClient(IBinder cb) { 175 // is the owner of the audio focus part of the client to remove? 176 boolean isTopOfStackForClientToRemove = !mFocusStack.isEmpty() && 177 mFocusStack.peek().hasSameBinder(cb); 178 // (using an iterator on the stack so we can safely remove an entry after having 179 // evaluated it, traversal order doesn't matter here) 180 Iterator<FocusRequester> stackIterator = mFocusStack.iterator(); 181 while(stackIterator.hasNext()) { 182 FocusRequester fr = stackIterator.next(); 183 if(fr.hasSameBinder(cb)) { 184 Log.i(TAG, "AudioFocus removeFocusStackEntry(): removing entry for " + cb); 185 stackIterator.remove(); 186 // the client just died, no need to unlink to its death 187 } 188 } 189 if (isTopOfStackForClientToRemove) { 190 // we removed an entry at the top of the stack: 191 // notify the new top of the stack it gained focus. 192 notifyTopOfAudioFocusStack(); 193 } 194 } 195 196 /** 197 * Helper function: 198 * Returns true if the system is in a state where the focus can be reevaluated, false otherwise. 199 * The implementation guarantees that a state where focus cannot be immediately reassigned 200 * implies that an "locked" focus owner is at the top of the focus stack. 201 * Modifications to the implementation that break this assumption will cause focus requests to 202 * misbehave when honoring the AudioManager.AUDIOFOCUS_FLAG_DELAY_OK flag. 203 */ canReassignAudioFocus()204 private boolean canReassignAudioFocus() { 205 // focus requests are rejected during a phone call or when the phone is ringing 206 // this is equivalent to IN_VOICE_COMM_FOCUS_ID having the focus 207 if (!mFocusStack.isEmpty() && isLockedFocusOwner(mFocusStack.peek())) { 208 return false; 209 } 210 return true; 211 } 212 isLockedFocusOwner(FocusRequester fr)213 private boolean isLockedFocusOwner(FocusRequester fr) { 214 return (fr.hasSameClient(AudioSystem.IN_VOICE_COMM_FOCUS_ID) || fr.isLockedFocusOwner()); 215 } 216 217 /** 218 * Helper function 219 * Pre-conditions: focus stack is not empty, there is one or more locked focus owner 220 * at the top of the focus stack 221 * Push the focus requester onto the audio focus stack at the first position immediately 222 * following the locked focus owners. 223 * @return {@link AudioManager#AUDIOFOCUS_REQUEST_GRANTED} or 224 * {@link AudioManager#AUDIOFOCUS_REQUEST_DELAYED} 225 */ pushBelowLockedFocusOwners(FocusRequester nfr)226 private int pushBelowLockedFocusOwners(FocusRequester nfr) { 227 int lastLockedFocusOwnerIndex = mFocusStack.size(); 228 for (int index = mFocusStack.size()-1; index >= 0; index--) { 229 if (isLockedFocusOwner(mFocusStack.elementAt(index))) { 230 lastLockedFocusOwnerIndex = index; 231 } 232 } 233 if (lastLockedFocusOwnerIndex == mFocusStack.size()) { 234 // this should not happen, but handle it and log an error 235 Log.e(TAG, "No exclusive focus owner found in propagateFocusLossFromGain_syncAf()", 236 new Exception()); 237 // no exclusive owner, push at top of stack, focus is granted, propagate change 238 propagateFocusLossFromGain_syncAf(nfr.getGainRequest()); 239 mFocusStack.push(nfr); 240 return AudioManager.AUDIOFOCUS_REQUEST_GRANTED; 241 } else { 242 mFocusStack.insertElementAt(nfr, lastLockedFocusOwnerIndex); 243 return AudioManager.AUDIOFOCUS_REQUEST_DELAYED; 244 } 245 } 246 247 /** 248 * Inner class to monitor audio focus client deaths, and remove them from the audio focus 249 * stack if necessary. 250 */ 251 protected class AudioFocusDeathHandler implements IBinder.DeathRecipient { 252 private IBinder mCb; // To be notified of client's death 253 AudioFocusDeathHandler(IBinder cb)254 AudioFocusDeathHandler(IBinder cb) { 255 mCb = cb; 256 } 257 binderDied()258 public void binderDied() { 259 synchronized(mAudioFocusLock) { 260 Log.w(TAG, " AudioFocus audio focus client died"); 261 removeFocusStackEntryForClient(mCb); 262 } 263 } 264 getBinder()265 public IBinder getBinder() { 266 return mCb; 267 } 268 } 269 270 /** 271 * Indicates whether to notify an audio focus owner when it loses focus 272 * with {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK} if it will only duck. 273 * This variable being false indicates an AudioPolicy has been registered and has signaled 274 * it will handle audio ducking. 275 */ 276 private boolean mNotifyFocusOwnerOnDuck = true; 277 setDuckingInExtPolicyAvailable(boolean available)278 protected void setDuckingInExtPolicyAvailable(boolean available) { 279 mNotifyFocusOwnerOnDuck = !available; 280 } 281 mustNotifyFocusOwnerOnDuck()282 boolean mustNotifyFocusOwnerOnDuck() { return mNotifyFocusOwnerOnDuck; } 283 284 private ArrayList<IAudioPolicyCallback> mFocusFollowers = new ArrayList<IAudioPolicyCallback>(); 285 addFocusFollower(IAudioPolicyCallback ff)286 void addFocusFollower(IAudioPolicyCallback ff) { 287 if (ff == null) { 288 return; 289 } 290 synchronized(mAudioFocusLock) { 291 boolean found = false; 292 for (IAudioPolicyCallback pcb : mFocusFollowers) { 293 if (pcb.asBinder().equals(ff.asBinder())) { 294 found = true; 295 break; 296 } 297 } 298 if (found) { 299 return; 300 } else { 301 mFocusFollowers.add(ff); 302 notifyExtPolicyCurrentFocusAsync(ff); 303 } 304 } 305 } 306 removeFocusFollower(IAudioPolicyCallback ff)307 void removeFocusFollower(IAudioPolicyCallback ff) { 308 if (ff == null) { 309 return; 310 } 311 synchronized(mAudioFocusLock) { 312 for (IAudioPolicyCallback pcb : mFocusFollowers) { 313 if (pcb.asBinder().equals(ff.asBinder())) { 314 mFocusFollowers.remove(pcb); 315 break; 316 } 317 } 318 } 319 } 320 321 /** 322 * @param pcb non null 323 */ notifyExtPolicyCurrentFocusAsync(IAudioPolicyCallback pcb)324 void notifyExtPolicyCurrentFocusAsync(IAudioPolicyCallback pcb) { 325 final IAudioPolicyCallback pcb2 = pcb; 326 final Thread thread = new Thread() { 327 @Override 328 public void run() { 329 synchronized(mAudioFocusLock) { 330 if (mFocusStack.isEmpty()) { 331 return; 332 } 333 try { 334 pcb2.notifyAudioFocusGrant(mFocusStack.peek().toAudioFocusInfo(), 335 // top of focus stack always has focus 336 AudioManager.AUDIOFOCUS_REQUEST_GRANTED); 337 } catch (RemoteException e) { 338 Log.e(TAG, "Can't call notifyAudioFocusGrant() on IAudioPolicyCallback " 339 + pcb2.asBinder(), e); 340 } 341 } 342 } 343 }; 344 thread.start(); 345 } 346 347 /** 348 * Called synchronized on mAudioFocusLock 349 */ notifyExtPolicyFocusGrant_syncAf(AudioFocusInfo afi, int requestResult)350 void notifyExtPolicyFocusGrant_syncAf(AudioFocusInfo afi, int requestResult) { 351 for (IAudioPolicyCallback pcb : mFocusFollowers) { 352 try { 353 // oneway 354 pcb.notifyAudioFocusGrant(afi, requestResult); 355 } catch (RemoteException e) { 356 Log.e(TAG, "Can't call notifyAudioFocusGrant() on IAudioPolicyCallback " 357 + pcb.asBinder(), e); 358 } 359 } 360 } 361 362 /** 363 * Called synchronized on mAudioFocusLock 364 */ notifyExtPolicyFocusLoss_syncAf(AudioFocusInfo afi, boolean wasDispatched)365 void notifyExtPolicyFocusLoss_syncAf(AudioFocusInfo afi, boolean wasDispatched) { 366 for (IAudioPolicyCallback pcb : mFocusFollowers) { 367 try { 368 // oneway 369 pcb.notifyAudioFocusLoss(afi, wasDispatched); 370 } catch (RemoteException e) { 371 Log.e(TAG, "Can't call notifyAudioFocusLoss() on IAudioPolicyCallback " 372 + pcb.asBinder(), e); 373 } 374 } 375 } 376 getCurrentAudioFocus()377 protected int getCurrentAudioFocus() { 378 synchronized(mAudioFocusLock) { 379 if (mFocusStack.empty()) { 380 return AudioManager.AUDIOFOCUS_NONE; 381 } else { 382 return mFocusStack.peek().getGainRequest(); 383 } 384 } 385 } 386 387 /** @see AudioManager#requestAudioFocus(AudioManager.OnAudioFocusChangeListener, int, int, int) */ requestAudioFocus(AudioAttributes aa, int focusChangeHint, IBinder cb, IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags)388 protected int requestAudioFocus(AudioAttributes aa, int focusChangeHint, IBinder cb, 389 IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags) { 390 Log.i(TAG, " AudioFocus requestAudioFocus() from uid/pid " + Binder.getCallingUid() 391 + "/" + Binder.getCallingPid() 392 + " clientId=" + clientId 393 + " req=" + focusChangeHint 394 + " flags=0x" + Integer.toHexString(flags)); 395 // we need a valid binder callback for clients 396 if (!cb.pingBinder()) { 397 Log.e(TAG, " AudioFocus DOA client for requestAudioFocus(), aborting."); 398 return AudioManager.AUDIOFOCUS_REQUEST_FAILED; 399 } 400 401 if (mAppOps.noteOp(AppOpsManager.OP_TAKE_AUDIO_FOCUS, Binder.getCallingUid(), 402 callingPackageName) != AppOpsManager.MODE_ALLOWED) { 403 return AudioManager.AUDIOFOCUS_REQUEST_FAILED; 404 } 405 406 synchronized(mAudioFocusLock) { 407 boolean focusGrantDelayed = false; 408 if (!canReassignAudioFocus()) { 409 if ((flags & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK) == 0) { 410 return AudioManager.AUDIOFOCUS_REQUEST_FAILED; 411 } else { 412 // request has AUDIOFOCUS_FLAG_DELAY_OK: focus can't be 413 // granted right now, so the requester will be inserted in the focus stack 414 // to receive focus later 415 focusGrantDelayed = true; 416 } 417 } 418 419 // handle the potential premature death of the new holder of the focus 420 // (premature death == death before abandoning focus) 421 // Register for client death notification 422 AudioFocusDeathHandler afdh = new AudioFocusDeathHandler(cb); 423 try { 424 cb.linkToDeath(afdh, 0); 425 } catch (RemoteException e) { 426 // client has already died! 427 Log.w(TAG, "AudioFocus requestAudioFocus() could not link to "+cb+" binder death"); 428 return AudioManager.AUDIOFOCUS_REQUEST_FAILED; 429 } 430 431 if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientId)) { 432 // if focus is already owned by this client and the reason for acquiring the focus 433 // hasn't changed, don't do anything 434 final FocusRequester fr = mFocusStack.peek(); 435 if (fr.getGainRequest() == focusChangeHint && fr.getGrantFlags() == flags) { 436 // unlink death handler so it can be gc'ed. 437 // linkToDeath() creates a JNI global reference preventing collection. 438 cb.unlinkToDeath(afdh, 0); 439 notifyExtPolicyFocusGrant_syncAf(fr.toAudioFocusInfo(), 440 AudioManager.AUDIOFOCUS_REQUEST_GRANTED); 441 return AudioManager.AUDIOFOCUS_REQUEST_GRANTED; 442 } 443 // the reason for the audio focus request has changed: remove the current top of 444 // stack and respond as if we had a new focus owner 445 if (!focusGrantDelayed) { 446 mFocusStack.pop(); 447 // the entry that was "popped" is the same that was "peeked" above 448 fr.release(); 449 } 450 } 451 452 // focus requester might already be somewhere below in the stack, remove it 453 removeFocusStackEntry(clientId, false /* signal */, false /*notifyFocusFollowers*/); 454 455 final FocusRequester nfr = new FocusRequester(aa, focusChangeHint, flags, fd, cb, 456 clientId, afdh, callingPackageName, Binder.getCallingUid(), this); 457 if (focusGrantDelayed) { 458 // focusGrantDelayed being true implies we can't reassign focus right now 459 // which implies the focus stack is not empty. 460 final int requestResult = pushBelowLockedFocusOwners(nfr); 461 if (requestResult != AudioManager.AUDIOFOCUS_REQUEST_FAILED) { 462 notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(), requestResult); 463 } 464 return requestResult; 465 } else { 466 // propagate the focus change through the stack 467 if (!mFocusStack.empty()) { 468 propagateFocusLossFromGain_syncAf(focusChangeHint); 469 } 470 471 // push focus requester at the top of the audio focus stack 472 mFocusStack.push(nfr); 473 } 474 notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(), 475 AudioManager.AUDIOFOCUS_REQUEST_GRANTED); 476 477 }//synchronized(mAudioFocusLock) 478 479 return AudioManager.AUDIOFOCUS_REQUEST_GRANTED; 480 } 481 482 /** 483 * @see AudioManager#abandonAudioFocus(AudioManager.OnAudioFocusChangeListener, AudioAttributes) 484 * */ abandonAudioFocus(IAudioFocusDispatcher fl, String clientId, AudioAttributes aa)485 protected int abandonAudioFocus(IAudioFocusDispatcher fl, String clientId, AudioAttributes aa) { 486 // AudioAttributes are currently ignored, to be used for zones 487 Log.i(TAG, " AudioFocus abandonAudioFocus() from uid/pid " + Binder.getCallingUid() 488 + "/" + Binder.getCallingPid() 489 + " clientId=" + clientId); 490 try { 491 // this will take care of notifying the new focus owner if needed 492 synchronized(mAudioFocusLock) { 493 removeFocusStackEntry(clientId, true /*signal*/, true /*notifyFocusFollowers*/); 494 } 495 } catch (java.util.ConcurrentModificationException cme) { 496 // Catching this exception here is temporary. It is here just to prevent 497 // a crash seen when the "Silent" notification is played. This is believed to be fixed 498 // but this try catch block is left just to be safe. 499 Log.e(TAG, "FATAL EXCEPTION AudioFocus abandonAudioFocus() caused " + cme); 500 cme.printStackTrace(); 501 } 502 503 return AudioManager.AUDIOFOCUS_REQUEST_GRANTED; 504 } 505 506 unregisterAudioFocusClient(String clientId)507 protected void unregisterAudioFocusClient(String clientId) { 508 synchronized(mAudioFocusLock) { 509 removeFocusStackEntry(clientId, false, true /*notifyFocusFollowers*/); 510 } 511 } 512 513 } 514