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 50 protected MediaFocusControl(Context cntxt) { 51 mContext = cntxt; 52 mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE); 53 } 54 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 */ 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 */ 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 */ 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 */ 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 */ 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 // stack entry not used anymore, clear references 164 fr.release(); 165 } 166 } 167 } 168 } 169 170 /** 171 * Helper function: 172 * Called synchronized on mAudioFocusLock 173 * Remove focus listeners from the focus stack for a particular client when it has died. 174 */ 175 private void removeFocusStackEntryOnDeath(IBinder cb) { 176 // is the owner of the audio focus part of the client to remove? 177 boolean isTopOfStackForClientToRemove = !mFocusStack.isEmpty() && 178 mFocusStack.peek().hasSameBinder(cb); 179 // (using an iterator on the stack so we can safely remove an entry after having 180 // evaluated it, traversal order doesn't matter here) 181 Iterator<FocusRequester> stackIterator = mFocusStack.iterator(); 182 while(stackIterator.hasNext()) { 183 FocusRequester fr = stackIterator.next(); 184 if(fr.hasSameBinder(cb)) { 185 Log.i(TAG, "AudioFocus removeFocusStackEntryOnDeath(): removing entry for " + cb); 186 stackIterator.remove(); 187 // stack entry not used anymore, clear references 188 fr.release(); 189 } 190 } 191 if (isTopOfStackForClientToRemove) { 192 // we removed an entry at the top of the stack: 193 // notify the new top of the stack it gained focus. 194 notifyTopOfAudioFocusStack(); 195 } 196 } 197 198 /** 199 * Helper function: 200 * Returns true if the system is in a state where the focus can be reevaluated, false otherwise. 201 * The implementation guarantees that a state where focus cannot be immediately reassigned 202 * implies that an "locked" focus owner is at the top of the focus stack. 203 * Modifications to the implementation that break this assumption will cause focus requests to 204 * misbehave when honoring the AudioManager.AUDIOFOCUS_FLAG_DELAY_OK flag. 205 */ 206 private boolean canReassignAudioFocus() { 207 // focus requests are rejected during a phone call or when the phone is ringing 208 // this is equivalent to IN_VOICE_COMM_FOCUS_ID having the focus 209 if (!mFocusStack.isEmpty() && isLockedFocusOwner(mFocusStack.peek())) { 210 return false; 211 } 212 return true; 213 } 214 215 private boolean isLockedFocusOwner(FocusRequester fr) { 216 return (fr.hasSameClient(AudioSystem.IN_VOICE_COMM_FOCUS_ID) || fr.isLockedFocusOwner()); 217 } 218 219 /** 220 * Helper function 221 * Pre-conditions: focus stack is not empty, there is one or more locked focus owner 222 * at the top of the focus stack 223 * Push the focus requester onto the audio focus stack at the first position immediately 224 * following the locked focus owners. 225 * @return {@link AudioManager#AUDIOFOCUS_REQUEST_GRANTED} or 226 * {@link AudioManager#AUDIOFOCUS_REQUEST_DELAYED} 227 */ 228 private int pushBelowLockedFocusOwners(FocusRequester nfr) { 229 int lastLockedFocusOwnerIndex = mFocusStack.size(); 230 for (int index = mFocusStack.size()-1; index >= 0; index--) { 231 if (isLockedFocusOwner(mFocusStack.elementAt(index))) { 232 lastLockedFocusOwnerIndex = index; 233 } 234 } 235 if (lastLockedFocusOwnerIndex == mFocusStack.size()) { 236 // this should not happen, but handle it and log an error 237 Log.e(TAG, "No exclusive focus owner found in propagateFocusLossFromGain_syncAf()", 238 new Exception()); 239 // no exclusive owner, push at top of stack, focus is granted, propagate change 240 propagateFocusLossFromGain_syncAf(nfr.getGainRequest()); 241 mFocusStack.push(nfr); 242 return AudioManager.AUDIOFOCUS_REQUEST_GRANTED; 243 } else { 244 mFocusStack.insertElementAt(nfr, lastLockedFocusOwnerIndex); 245 return AudioManager.AUDIOFOCUS_REQUEST_DELAYED; 246 } 247 } 248 249 /** 250 * Inner class to monitor audio focus client deaths, and remove them from the audio focus 251 * stack if necessary. 252 */ 253 protected class AudioFocusDeathHandler implements IBinder.DeathRecipient { 254 private IBinder mCb; // To be notified of client's death 255 256 AudioFocusDeathHandler(IBinder cb) { 257 mCb = cb; 258 } 259 260 public void binderDied() { 261 synchronized(mAudioFocusLock) { 262 removeFocusStackEntryOnDeath(mCb); 263 } 264 } 265 } 266 267 /** 268 * Indicates whether to notify an audio focus owner when it loses focus 269 * with {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK} if it will only duck. 270 * This variable being false indicates an AudioPolicy has been registered and has signaled 271 * it will handle audio ducking. 272 */ 273 private boolean mNotifyFocusOwnerOnDuck = true; 274 275 protected void setDuckingInExtPolicyAvailable(boolean available) { 276 mNotifyFocusOwnerOnDuck = !available; 277 } 278 279 boolean mustNotifyFocusOwnerOnDuck() { return mNotifyFocusOwnerOnDuck; } 280 281 private ArrayList<IAudioPolicyCallback> mFocusFollowers = new ArrayList<IAudioPolicyCallback>(); 282 283 void addFocusFollower(IAudioPolicyCallback ff) { 284 if (ff == null) { 285 return; 286 } 287 synchronized(mAudioFocusLock) { 288 boolean found = false; 289 for (IAudioPolicyCallback pcb : mFocusFollowers) { 290 if (pcb.asBinder().equals(ff.asBinder())) { 291 found = true; 292 break; 293 } 294 } 295 if (found) { 296 return; 297 } else { 298 mFocusFollowers.add(ff); 299 notifyExtPolicyCurrentFocusAsync(ff); 300 } 301 } 302 } 303 304 void removeFocusFollower(IAudioPolicyCallback ff) { 305 if (ff == null) { 306 return; 307 } 308 synchronized(mAudioFocusLock) { 309 for (IAudioPolicyCallback pcb : mFocusFollowers) { 310 if (pcb.asBinder().equals(ff.asBinder())) { 311 mFocusFollowers.remove(pcb); 312 break; 313 } 314 } 315 } 316 } 317 318 /** 319 * @param pcb non null 320 */ 321 void notifyExtPolicyCurrentFocusAsync(IAudioPolicyCallback pcb) { 322 final IAudioPolicyCallback pcb2 = pcb; 323 final Thread thread = new Thread() { 324 @Override 325 public void run() { 326 synchronized(mAudioFocusLock) { 327 if (mFocusStack.isEmpty()) { 328 return; 329 } 330 try { 331 pcb2.notifyAudioFocusGrant(mFocusStack.peek().toAudioFocusInfo(), 332 // top of focus stack always has focus 333 AudioManager.AUDIOFOCUS_REQUEST_GRANTED); 334 } catch (RemoteException e) { 335 Log.e(TAG, "Can't call notifyAudioFocusGrant() on IAudioPolicyCallback " 336 + pcb2.asBinder(), e); 337 } 338 } 339 } 340 }; 341 thread.start(); 342 } 343 344 /** 345 * Called synchronized on mAudioFocusLock 346 */ 347 void notifyExtPolicyFocusGrant_syncAf(AudioFocusInfo afi, int requestResult) { 348 for (IAudioPolicyCallback pcb : mFocusFollowers) { 349 try { 350 // oneway 351 pcb.notifyAudioFocusGrant(afi, requestResult); 352 } catch (RemoteException e) { 353 Log.e(TAG, "Can't call notifyAudioFocusGrant() on IAudioPolicyCallback " 354 + pcb.asBinder(), e); 355 } 356 } 357 } 358 359 /** 360 * Called synchronized on mAudioFocusLock 361 */ 362 void notifyExtPolicyFocusLoss_syncAf(AudioFocusInfo afi, boolean wasDispatched) { 363 for (IAudioPolicyCallback pcb : mFocusFollowers) { 364 try { 365 // oneway 366 pcb.notifyAudioFocusLoss(afi, wasDispatched); 367 } catch (RemoteException e) { 368 Log.e(TAG, "Can't call notifyAudioFocusLoss() on IAudioPolicyCallback " 369 + pcb.asBinder(), e); 370 } 371 } 372 } 373 374 protected int getCurrentAudioFocus() { 375 synchronized(mAudioFocusLock) { 376 if (mFocusStack.empty()) { 377 return AudioManager.AUDIOFOCUS_NONE; 378 } else { 379 return mFocusStack.peek().getGainRequest(); 380 } 381 } 382 } 383 384 /** @see AudioManager#requestAudioFocus(AudioManager.OnAudioFocusChangeListener, int, int, int) */ 385 protected int requestAudioFocus(AudioAttributes aa, int focusChangeHint, IBinder cb, 386 IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags) { 387 Log.i(TAG, " AudioFocus requestAudioFocus() from uid/pid " + Binder.getCallingUid() 388 + "/" + Binder.getCallingPid() 389 + " clientId=" + clientId 390 + " req=" + focusChangeHint 391 + " flags=0x" + Integer.toHexString(flags)); 392 // we need a valid binder callback for clients 393 if (!cb.pingBinder()) { 394 Log.e(TAG, " AudioFocus DOA client for requestAudioFocus(), aborting."); 395 return AudioManager.AUDIOFOCUS_REQUEST_FAILED; 396 } 397 398 if (mAppOps.noteOp(AppOpsManager.OP_TAKE_AUDIO_FOCUS, Binder.getCallingUid(), 399 callingPackageName) != AppOpsManager.MODE_ALLOWED) { 400 return AudioManager.AUDIOFOCUS_REQUEST_FAILED; 401 } 402 403 synchronized(mAudioFocusLock) { 404 boolean focusGrantDelayed = false; 405 if (!canReassignAudioFocus()) { 406 if ((flags & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK) == 0) { 407 return AudioManager.AUDIOFOCUS_REQUEST_FAILED; 408 } else { 409 // request has AUDIOFOCUS_FLAG_DELAY_OK: focus can't be 410 // granted right now, so the requester will be inserted in the focus stack 411 // to receive focus later 412 focusGrantDelayed = true; 413 } 414 } 415 416 // handle the potential premature death of the new holder of the focus 417 // (premature death == death before abandoning focus) 418 // Register for client death notification 419 AudioFocusDeathHandler afdh = new AudioFocusDeathHandler(cb); 420 421 try { 422 cb.linkToDeath(afdh, 0); 423 } catch (RemoteException e) { 424 // client has already died! 425 Log.w(TAG, "AudioFocus requestAudioFocus() could not link to "+cb+" binder death"); 426 return AudioManager.AUDIOFOCUS_REQUEST_FAILED; 427 } 428 429 if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientId)) { 430 // if focus is already owned by this client and the reason for acquiring the focus 431 // hasn't changed, don't do anything 432 final FocusRequester fr = mFocusStack.peek(); 433 if (fr.getGainRequest() == focusChangeHint && fr.getGrantFlags() == flags) { 434 // unlink death handler so it can be gc'ed. 435 // linkToDeath() creates a JNI global reference preventing collection. 436 cb.unlinkToDeath(afdh, 0); 437 notifyExtPolicyFocusGrant_syncAf(fr.toAudioFocusInfo(), 438 AudioManager.AUDIOFOCUS_REQUEST_GRANTED); 439 return AudioManager.AUDIOFOCUS_REQUEST_GRANTED; 440 } 441 // the reason for the audio focus request has changed: remove the current top of 442 // stack and respond as if we had a new focus owner 443 if (!focusGrantDelayed) { 444 mFocusStack.pop(); 445 // the entry that was "popped" is the same that was "peeked" above 446 fr.release(); 447 } 448 } 449 450 // focus requester might already be somewhere below in the stack, remove it 451 removeFocusStackEntry(clientId, false /* signal */, false /*notifyFocusFollowers*/); 452 453 final FocusRequester nfr = new FocusRequester(aa, focusChangeHint, flags, fd, cb, 454 clientId, afdh, callingPackageName, Binder.getCallingUid(), this); 455 if (focusGrantDelayed) { 456 // focusGrantDelayed being true implies we can't reassign focus right now 457 // which implies the focus stack is not empty. 458 final int requestResult = pushBelowLockedFocusOwners(nfr); 459 if (requestResult != AudioManager.AUDIOFOCUS_REQUEST_FAILED) { 460 notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(), requestResult); 461 } 462 return requestResult; 463 } else { 464 // propagate the focus change through the stack 465 if (!mFocusStack.empty()) { 466 propagateFocusLossFromGain_syncAf(focusChangeHint); 467 } 468 469 // push focus requester at the top of the audio focus stack 470 mFocusStack.push(nfr); 471 } 472 notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(), 473 AudioManager.AUDIOFOCUS_REQUEST_GRANTED); 474 475 }//synchronized(mAudioFocusLock) 476 477 return AudioManager.AUDIOFOCUS_REQUEST_GRANTED; 478 } 479 480 /** 481 * @see AudioManager#abandonAudioFocus(AudioManager.OnAudioFocusChangeListener, AudioAttributes) 482 * */ 483 protected int abandonAudioFocus(IAudioFocusDispatcher fl, String clientId, AudioAttributes aa) { 484 // AudioAttributes are currently ignored, to be used for zones 485 Log.i(TAG, " AudioFocus abandonAudioFocus() from uid/pid " + Binder.getCallingUid() 486 + "/" + Binder.getCallingPid() 487 + " clientId=" + clientId); 488 try { 489 // this will take care of notifying the new focus owner if needed 490 synchronized(mAudioFocusLock) { 491 removeFocusStackEntry(clientId, true /*signal*/, true /*notifyFocusFollowers*/); 492 } 493 } catch (java.util.ConcurrentModificationException cme) { 494 // Catching this exception here is temporary. It is here just to prevent 495 // a crash seen when the "Silent" notification is played. This is believed to be fixed 496 // but this try catch block is left just to be safe. 497 Log.e(TAG, "FATAL EXCEPTION AudioFocus abandonAudioFocus() caused " + cme); 498 cme.printStackTrace(); 499 } 500 501 return AudioManager.AUDIOFOCUS_REQUEST_GRANTED; 502 } 503 504 505 protected void unregisterAudioFocusClient(String clientId) { 506 synchronized(mAudioFocusLock) { 507 removeFocusStackEntry(clientId, false, true /*notifyFocusFollowers*/); 508 } 509 } 510 511 } 512