content: /*
   * Copyright (C) 2013 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package com.android.server.audio;
  
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.app.AppOpsManager;
  import android.content.Context;
  import android.media.AudioAttributes;
  import android.media.AudioFocusInfo;
  import android.media.AudioManager;
  import android.media.AudioSystem;
  import android.media.IAudioFocusDispatcher;
  import android.media.audiopolicy.IAudioPolicyCallback;
  import android.os.Binder;
  import android.os.Build;
  import android.os.IBinder;
  import android.os.RemoteException;
  import android.util.Log;
  
  import com.android.internal.annotations.GuardedBy;
  
  import java.io.PrintWriter;
  import java.text.DateFormat;
  import java.util.ArrayList;
  import java.util.Date;
  import java.util.HashMap;
  import java.util.Iterator;
  import java.util.LinkedList;
  import java.util.List;
  import java.util.Map.Entry;
  import java.util.Set;
  import java.util.Stack;
  
  /**
   * @hide
   *
   */
  public class MediaFocusControl implements PlayerFocusEnforcer {
  
      private static final String TAG = "MediaFocusControl";
      static final boolean DEBUG = false;
  
      /**
       * set to true so the framework enforces ducking itself, without communicating to apps
       * that they lost focus for most use cases.
       */
      static final boolean ENFORCE_DUCKING = true;
      /**
       * set to true to the framework enforces ducking itself only with apps above a given SDK
       * target level. Is ignored if ENFORCE_DUCKING is false.
       */
      static final boolean ENFORCE_DUCKING_FOR_NEW = true;
      /**
       * the SDK level (included) up to which the framework doesn't enforce ducking itself. Is ignored
       * if ENFORCE_DUCKING_FOR_NEW is false;
       */
      // automatic ducking was introduced for Android O
      static final int DUCKING_IN_APP_SDK_LEVEL = Build.VERSION_CODES.N_MR1;
      /**
       * set to true so the framework enforces muting media/game itself when the device is ringing
       * or in a call.
       */
      static final boolean ENFORCE_MUTING_FOR_RING_OR_CALL = true;
  
      private final Context mContext;
      private final AppOpsManager mAppOps;
      private PlayerFocusEnforcer mFocusEnforcer; // never null
  
      private boolean mRingOrCallActive = false;
  
      private final Object mExtFocusChangeLock = new Object();
      @GuardedBy("mExtFocusChangeLock")
      private long mExtFocusChangeCounter;
  
      protected MediaFocusControl(Context cntxt, PlayerFocusEnforcer pfe) {
          mContext = cntxt;
          mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE);
          mFocusEnforcer = pfe;
      }
  
      protected void dump(PrintWriter pw) {
          pw.println("\nMediaFocusControl dump time: "
                  + DateFormat.getTimeInstance().format(new Date()));
          dumpFocusStack(pw);
          pw.println("\n");
          // log
          mEventLogger.dump(pw);
      }
  
      //=================================================================
      // PlayerFocusEnforcer implementation
      @Override
      public boolean duckPlayers(FocusRequester winner, FocusRequester loser, boolean forceDuck) {
          return mFocusEnforcer.duckPlayers(winner, loser, forceDuck);
      }
  
      @Override
      public void unduckPlayers(FocusRequester winner) {
          mFocusEnforcer.unduckPlayers(winner);
      }
  
      @Override
      public void mutePlayersForCall(int[] usagesToMute) {
          mFocusEnforcer.mutePlayersForCall(usagesToMute);
      }
  
      @Override
      public void unmutePlayersForCall() {
          mFocusEnforcer.unmutePlayersForCall();
      }
  
      //==========================================================================================
      // AudioFocus
      //==========================================================================================
  
      private final static Object mAudioFocusLock = new Object();
  
      /**
       * Arbitrary maximum size of audio focus stack to prevent apps OOM'ing this process.
       */
      private static final int MAX_STACK_SIZE = 100;
  
      private static final AudioEventLogger mEventLogger = new AudioEventLogger(50,
              "focus commands as seen by MediaFocusControl");
  
      /*package*/ void noFocusForSuspendedApp(@NonNull String packageName, int uid) {
          synchronized (mAudioFocusLock) {
              final Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
              List<String> clientsToRemove = new ArrayList<>();
              while (stackIterator.hasNext()) {
                  final FocusRequester focusOwner = stackIterator.next();
                  if (focusOwner.hasSameUid(uid) && focusOwner.hasSamePackage(packageName)) {
                      clientsToRemove.add(focusOwner.getClientId());
                      mEventLogger.log((new AudioEventLogger.StringEvent(
                              "focus owner:" + focusOwner.getClientId()
                                      + " in uid:" + uid + " pack: " + packageName
                                      + " getting AUDIOFOCUS_LOSS due to app suspension"))
                              .printLog(TAG));
                      // make the suspended app lose focus through its focus listener (if any)
                      focusOwner.dispatchFocusChange(AudioManager.AUDIOFOCUS_LOSS);
                  }
              }
              for (String clientToRemove : clientsToRemove) {
                  // update the stack but don't signal the change.
                  removeFocusStackEntry(clientToRemove, false, true);
              }
          }
      }
  
      /*package*/ boolean hasAudioFocusUsers() {
          synchronized (mAudioFocusLock) {
              return !mFocusStack.empty();
          }
      }
  
      /**
       * Discard the current audio focus owner.
       * Notify top of audio focus stack that it lost focus (regardless of possibility to reassign
       * focus), remove it from the stack, and clear the remote control display.
       */
      protected void discardAudioFocusOwner() {
          synchronized(mAudioFocusLock) {
              if (!mFocusStack.empty()) {
                  // notify the current focus owner it lost focus after removing it from stack
                  final FocusRequester exFocusOwner = mFocusStack.pop();
                  exFocusOwner.handleFocusLoss(AudioManager.AUDIOFOCUS_LOSS, null,
                          false /*forceDuck*/);
                  exFocusOwner.release();
              }
          }
      }
  
      @GuardedBy("mAudioFocusLock")
      private void notifyTopOfAudioFocusStack() {
          // notify the top of the stack it gained focus
          if (!mFocusStack.empty()) {
              if (canReassignAudioFocus()) {
                  mFocusStack.peek().handleFocusGain(AudioManager.AUDIOFOCUS_GAIN);
              }
          }
      }
  
      /**
       * Focus is requested, propagate the associated loss throughout the stack.
       * Will also remove entries in the stack that have just received a definitive loss of focus.
       * @param focusGain the new focus gain that will later be added at the top of the stack
       */
      @GuardedBy("mAudioFocusLock")
      private void propagateFocusLossFromGain_syncAf(int focusGain, final FocusRequester fr,
              boolean forceDuck) {
          final List<String> clientsToRemove = new LinkedList<String>();
          // going through the audio focus stack to signal new focus, traversing order doesn't
          // matter as all entries respond to the same external focus gain
          for (FocusRequester focusLoser : mFocusStack) {
              final boolean isDefinitiveLoss =
                      focusLoser.handleFocusLossFromGain(focusGain, fr, forceDuck);
              if (isDefinitiveLoss) {
                  clientsToRemove.add(focusLoser.getClientId());
              }
          }
          for (String clientToRemove : clientsToRemove) {
              removeFocusStackEntry(clientToRemove, false /*signal*/,
                      true /*notifyFocusFollowers*/);
          }
      }
  
      private final Stack<FocusRequester> mFocusStack = new Stack<FocusRequester>();
  
      /**
       * Helper function:
       * Display in the log the current entries in the audio focus stack
       */
      private void dumpFocusStack(PrintWriter pw) {
          pw.println("\nAudio Focus stack entries (last is top of stack):");
          synchronized(mAudioFocusLock) {
              Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
              while(stackIterator.hasNext()) {
                  stackIterator.next().dump(pw);
              }
              pw.println("\n");
              if (mFocusPolicy == null) {
                  pw.println("No external focus policy\n");
              } else {
                  pw.println("External focus policy: "+ mFocusPolicy + ", focus owners:\n");
                  dumpExtFocusPolicyFocusOwners(pw);
              }
          }
          pw.println("\n");
          pw.println(" Notify on duck:  " + mNotifyFocusOwnerOnDuck + "\n");
          pw.println(" In ring or call: " + mRingOrCallActive + "\n");
      }
  
      /**
       * Remove a focus listener from the focus stack.
       * @param clientToRemove the focus listener
       * @param signal if true and the listener was at the top of the focus stack, i.e. it was holding
       *   focus, notify the next item in the stack it gained focus.
       */
      @GuardedBy("mAudioFocusLock")
      private void removeFocusStackEntry(String clientToRemove, boolean signal,
              boolean notifyFocusFollowers) {
          // is the current top of the focus stack abandoning focus? (because of request, not death)
          if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientToRemove))
          {
              //Log.i(TAG, "   removeFocusStackEntry() removing top of stack");
              FocusRequester fr = mFocusStack.pop();
              fr.release();
              if (notifyFocusFollowers) {
                  final AudioFocusInfo afi = fr.toAudioFocusInfo();
                  afi.clearLossReceived();
                  notifyExtPolicyFocusLoss_syncAf(afi, false);
              }
              if (signal) {
                  // notify the new top of the stack it gained focus
                  notifyTopOfAudioFocusStack();
              }
          } else {
              // focus is abandoned by a client that's not at the top of the stack,
              // no need to update focus.
              // (using an iterator on the stack so we can safely remove an entry after having
              //  evaluated it, traversal order doesn't matter here)
              Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
              while(stackIterator.hasNext()) {
                  FocusRequester fr = stackIterator.next();
                  if(fr.hasSameClient(clientToRemove)) {
                      Log.i(TAG, "AudioFocus  removeFocusStackEntry(): removing entry for "
                              + clientToRemove);
                      stackIterator.remove();
                      // stack entry not used anymore, clear references
                      fr.release();
                  }
              }
          }
      }
  
      /**
       * Remove focus listeners from the focus stack for a particular client when it has died.
       */
      @GuardedBy("mAudioFocusLock")
      private void removeFocusStackEntryOnDeath(IBinder cb) {
          // is the owner of the audio focus part of the client to remove?
          boolean isTopOfStackForClientToRemove = !mFocusStack.isEmpty() &&
                  mFocusStack.peek().hasSameBinder(cb);
          // (using an iterator on the stack so we can safely remove an entry after having
          //  evaluated it, traversal order doesn't matter here)
          Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
          while(stackIterator.hasNext()) {
              FocusRequester fr = stackIterator.next();
              if(fr.hasSameBinder(cb)) {
                  Log.i(TAG, "AudioFocus  removeFocusStackEntryOnDeath(): removing entry for " + cb);
                  stackIterator.remove();
                  // stack entry not used anymore, clear references
                  fr.release();
              }
          }
          if (isTopOfStackForClientToRemove) {
              // we removed an entry at the top of the stack:
              //  notify the new top of the stack it gained focus.
              notifyTopOfAudioFocusStack();
          }
      }
  
      /**
       * Helper function for external focus policy:
       * Remove focus listeners from the list of potential focus owners for a particular client when
       * it has died.
       */
      @GuardedBy("mAudioFocusLock")
      private void removeFocusEntryForExtPolicy(IBinder cb) {
          if (mFocusOwnersForFocusPolicy.isEmpty()) {
              return;
          }
          boolean released = false;
          final Set<Entry<String, FocusRequester>> owners = mFocusOwnersForFocusPolicy.entrySet();
          final Iterator<Entry<String, FocusRequester>> ownerIterator = owners.iterator();
          while (ownerIterator.hasNext()) {
              final Entry<String, FocusRequester> owner = ownerIterator.next();
              final FocusRequester fr = owner.getValue();
              if (fr.hasSameBinder(cb)) {
                  ownerIterator.remove();
                  fr.release();
                  notifyExtFocusPolicyFocusAbandon_syncAf(fr.toAudioFocusInfo());
                  break;
              }
          }
      }
  
      /**
       * Helper function:
       * Returns true if the system is in a state where the focus can be reevaluated, false otherwise.
       * The implementation guarantees that a state where focus cannot be immediately reassigned
       * implies that an "locked" focus owner is at the top of the focus stack.
       * Modifications to the implementation that break this assumption will cause focus requests to
       * misbehave when honoring the AudioManager.AUDIOFOCUS_FLAG_DELAY_OK flag.
       */
      private boolean canReassignAudioFocus() {
          // focus requests are rejected during a phone call or when the phone is ringing
          // this is equivalent to IN_VOICE_COMM_FOCUS_ID having the focus
          if (!mFocusStack.isEmpty() && isLockedFocusOwner(mFocusStack.peek())) {
              return false;
          }
          return true;
      }
  
      private boolean isLockedFocusOwner(FocusRequester fr) {
          return (fr.hasSameClient(AudioSystem.IN_VOICE_COMM_FOCUS_ID) || fr.isLockedFocusOwner());
      }
  
      /**
       * Helper function
       * Pre-conditions: focus stack is not empty, there is one or more locked focus owner
       *                 at the top of the focus stack
       * Push the focus requester onto the audio focus stack at the first position immediately
       * following the locked focus owners.
       * @return {@link AudioManager#AUDIOFOCUS_REQUEST_GRANTED} or
       *     {@link AudioManager#AUDIOFOCUS_REQUEST_DELAYED}
       */
      @GuardedBy("mAudioFocusLock")
      private int pushBelowLockedFocusOwners(FocusRequester nfr) {
          int lastLockedFocusOwnerIndex = mFocusStack.size();
          for (int index = mFocusStack.size()-1; index >= 0; index--) {
              if (isLockedFocusOwner(mFocusStack.elementAt(index))) {
                  lastLockedFocusOwnerIndex = index;
              }
          }
          if (lastLockedFocusOwnerIndex == mFocusStack.size()) {
              // this should not happen, but handle it and log an error
              Log.e(TAG, "No exclusive focus owner found in propagateFocusLossFromGain_syncAf()",
                      new Exception());
              // no exclusive owner, push at top of stack, focus is granted, propagate change
              propagateFocusLossFromGain_syncAf(nfr.getGainRequest(), nfr, false /*forceDuck*/);
              mFocusStack.push(nfr);
              return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
          } else {
              mFocusStack.insertElementAt(nfr, lastLockedFocusOwnerIndex);
              return AudioManager.AUDIOFOCUS_REQUEST_DELAYED;
          }
      }
  
      /**
       * Inner class to monitor audio focus client deaths, and remove them from the audio focus
       * stack if necessary.
       */
      protected class AudioFocusDeathHandler implements IBinder.DeathRecipient {
          private IBinder mCb; // To be notified of client's death
  
          AudioFocusDeathHandler(IBinder cb) {
              mCb = cb;
          }
  
          public void binderDied() {
              synchronized(mAudioFocusLock) {
                  if (mFocusPolicy != null) {
                      removeFocusEntryForExtPolicy(mCb);
                  } else {
                      removeFocusStackEntryOnDeath(mCb);
                  }
              }
          }
      }
  
      /**
       * Indicates whether to notify an audio focus owner when it loses focus
       * with {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK} if it will only duck.
       * This variable being false indicates an AudioPolicy has been registered and has signaled
       * it will handle audio ducking.
       */
      private boolean mNotifyFocusOwnerOnDuck = true;
  
      protected void setDuckingInExtPolicyAvailable(boolean available) {
          mNotifyFocusOwnerOnDuck = !available;
      }
  
      boolean mustNotifyFocusOwnerOnDuck() { return mNotifyFocusOwnerOnDuck; }
  
      private ArrayList<IAudioPolicyCallback> mFocusFollowers = new ArrayList<IAudioPolicyCallback>();
  
      void addFocusFollower(IAudioPolicyCallback ff) {
          if (ff == null) {
              return;
          }
          synchronized(mAudioFocusLock) {
              boolean found = false;
              for (IAudioPolicyCallback pcb : mFocusFollowers) {
                  if (pcb.asBinder().equals(ff.asBinder())) {
                      found = true;
                      break;
                  }
              }
              if (found) {
                  return;
              } else {
                  mFocusFollowers.add(ff);
                  notifyExtPolicyCurrentFocusAsync(ff);
              }
          }
      }
  
      void removeFocusFollower(IAudioPolicyCallback ff) {
          if (ff == null) {
              return;
          }
          synchronized(mAudioFocusLock) {
              for (IAudioPolicyCallback pcb : mFocusFollowers) {
                  if (pcb.asBinder().equals(ff.asBinder())) {
                      mFocusFollowers.remove(pcb);
                      break;
                  }
              }
          }
      }
  
      /** The current audio focus policy */
      @GuardedBy("mAudioFocusLock")
      @Nullable private IAudioPolicyCallback mFocusPolicy = null;
      /**
       * The audio focus policy that was registered before a test focus policy was registered
       * during a test
       */
      @GuardedBy("mAudioFocusLock")
      @Nullable private IAudioPolicyCallback mPreviousFocusPolicy = null;
  
      // Since we don't have a stack of focus owners when using an external focus policy, we keep
      // track of all the focus requesters in this map, with their clientId as the key. This is
      // used both for focus dispatch and death handling
      private HashMap<String, FocusRequester> mFocusOwnersForFocusPolicy =
              new HashMap<String, FocusRequester>();
  
      void setFocusPolicy(IAudioPolicyCallback policy, boolean isTestFocusPolicy) {
          if (policy == null) {
              return;
          }
          synchronized (mAudioFocusLock) {
              if (isTestFocusPolicy) {
                  mPreviousFocusPolicy = mFocusPolicy;
              }
              mFocusPolicy = policy;
          }
      }
  
      void unsetFocusPolicy(IAudioPolicyCallback policy, boolean isTestFocusPolicy) {
          if (policy == null) {
              return;
          }
          synchronized (mAudioFocusLock) {
              if (mFocusPolicy == policy) {
                  if (isTestFocusPolicy) {
                      // restore the focus policy that was there before the focus policy test started
                      mFocusPolicy = mPreviousFocusPolicy;
                  } else {
                      mFocusPolicy = null;
                  }
              }
          }
      }
  
      /**
       * @param pcb non null
       */
      void notifyExtPolicyCurrentFocusAsync(IAudioPolicyCallback pcb) {
          final IAudioPolicyCallback pcb2 = pcb;
          final Thread thread = new Thread() {
              @Override
              public void run() {
                  synchronized(mAudioFocusLock) {
                      if (mFocusStack.isEmpty()) {
                          return;
                      }
                      try {
                          pcb2.notifyAudioFocusGrant(mFocusStack.peek().toAudioFocusInfo(),
                                  // top of focus stack always has focus
                                  AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
                      } catch (RemoteException e) {
                          Log.e(TAG, "Can't call notifyAudioFocusGrant() on IAudioPolicyCallback "
                                  + pcb2.asBinder(), e);
                      }
                  }
              }
          };
          thread.start();
      }
  
      /**
       * Called synchronized on mAudioFocusLock
       */
      void notifyExtPolicyFocusGrant_syncAf(AudioFocusInfo afi, int requestResult) {
          for (IAudioPolicyCallback pcb : mFocusFollowers) {
              try {
                  // oneway
                  pcb.notifyAudioFocusGrant(afi, requestResult);
              } catch (RemoteException e) {
                  Log.e(TAG, "Can't call notifyAudioFocusGrant() on IAudioPolicyCallback "
                          + pcb.asBinder(), e);
              }
          }
      }
  
      /**
       * Called synchronized on mAudioFocusLock
       */
      void notifyExtPolicyFocusLoss_syncAf(AudioFocusInfo afi, boolean wasDispatched) {
          for (IAudioPolicyCallback pcb : mFocusFollowers) {
              try {
                  // oneway
                  pcb.notifyAudioFocusLoss(afi, wasDispatched);
              } catch (RemoteException e) {
                  Log.e(TAG, "Can't call notifyAudioFocusLoss() on IAudioPolicyCallback "
                          + pcb.asBinder(), e);
              }
          }
      }
  
      /**
       * Called synchronized on mAudioFocusLock.
       * Can only be called with an external focus policy installed (mFocusPolicy != null)
       * @param afi
       * @param fd
       * @param cb binder of the focus requester
       * @return true if the external audio focus policy (if any) can handle the focus request,
       *     and false if there was any error handling the request (e.g. error talking to policy,
       *     focus requester is already dead)
       */
      boolean notifyExtFocusPolicyFocusRequest_syncAf(AudioFocusInfo afi,
              IAudioFocusDispatcher fd, @NonNull IBinder cb) {
          if (DEBUG) {
              Log.v(TAG, "notifyExtFocusPolicyFocusRequest client="+afi.getClientId()
              + " dispatcher=" + fd);
          }
          synchronized (mExtFocusChangeLock) {
              afi.setGen(mExtFocusChangeCounter++);
          }
          final FocusRequester existingFr = mFocusOwnersForFocusPolicy.get(afi.getClientId());
          boolean keepTrack = false;
          if (existingFr != null) {
              if (!existingFr.hasSameDispatcher(fd)) {
                  existingFr.release();
                  keepTrack = true;
              }
          } else {
              keepTrack = true;
          }
          if (keepTrack) {
              final AudioFocusDeathHandler hdlr = new AudioFocusDeathHandler(cb);
              try {
                  cb.linkToDeath(hdlr, 0);
              } catch (RemoteException e) {
                  // client has already died!
                  return false;
              }
              // new focus (future) focus owner to keep track of
              mFocusOwnersForFocusPolicy.put(afi.getClientId(),
                      new FocusRequester(afi, fd, cb, hdlr, this));
          }
  
          try {
              //oneway
              mFocusPolicy.notifyAudioFocusRequest(afi, AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
              return true;
          } catch (RemoteException e) {
              Log.e(TAG, "Can't call notifyAudioFocusRequest() on IAudioPolicyCallback "
                      + mFocusPolicy.asBinder(), e);
          }
          return false;
      }
  
      void setFocusRequestResultFromExtPolicy(AudioFocusInfo afi, int requestResult) {
          synchronized (mExtFocusChangeLock) {
              if (afi.getGen() > mExtFocusChangeCounter) {
                  return;
              }
          }
          final FocusRequester fr = mFocusOwnersForFocusPolicy.get(afi.getClientId());
          if (fr != null) {
              fr.dispatchFocusResultFromExtPolicy(requestResult);
          }
      }
  
      /**
       * Called synchronized on mAudioFocusLock
       * @param afi
       * @return true if the external audio focus policy (if any) is handling the focus request
       */
      boolean notifyExtFocusPolicyFocusAbandon_syncAf(AudioFocusInfo afi) {
          if (mFocusPolicy == null) {
              return false;
          }
          final FocusRequester fr = mFocusOwnersForFocusPolicy.remove(afi.getClientId());
          if (fr != null) {
              fr.release();
          }
          try {
              //oneway
              mFocusPolicy.notifyAudioFocusAbandon(afi);
          } catch (RemoteException e) {
              Log.e(TAG, "Can't call notifyAudioFocusAbandon() on IAudioPolicyCallback "
                      + mFocusPolicy.asBinder(), e);
          }
          return true;
      }
  
      /** see AudioManager.dispatchFocusChange(AudioFocusInfo afi, int focusChange, AudioPolicy ap) */
      int dispatchFocusChange(AudioFocusInfo afi, int focusChange) {
          if (DEBUG) {
              Log.v(TAG, "dispatchFocusChange " + focusChange + " to afi client="
                      + afi.getClientId());
          }
          synchronized (mAudioFocusLock) {
              if (mFocusPolicy == null) {
                  if (DEBUG) { Log.v(TAG, "> failed: no focus policy" ); }
                  return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
              }
              final FocusRequester fr;
              if (focusChange == AudioManager.AUDIOFOCUS_LOSS) {
                  fr = mFocusOwnersForFocusPolicy.remove(afi.getClientId());
              } else {
                  fr = mFocusOwnersForFocusPolicy.get(afi.getClientId());
              }
              if (fr == null) {
                  if (DEBUG) { Log.v(TAG, "> failed: no such focus requester known" ); }
                  return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
              }
              return fr.dispatchFocusChange(focusChange);
          }
      }
  
      private void dumpExtFocusPolicyFocusOwners(PrintWriter pw) {
          final Set<Entry<String, FocusRequester>> owners = mFocusOwnersForFocusPolicy.entrySet();
          final Iterator<Entry<String, FocusRequester>> ownerIterator = owners.iterator();
          while (ownerIterator.hasNext()) {
              final Entry<String, FocusRequester> owner = ownerIterator.next();
              final FocusRequester fr = owner.getValue();
              fr.dump(pw);
          }
      }
  
      protected int getCurrentAudioFocus() {
          synchronized(mAudioFocusLock) {
              if (mFocusStack.empty()) {
                  return AudioManager.AUDIOFOCUS_NONE;
              } else {
                  return mFocusStack.peek().getGainRequest();
              }
          }
      }
  
      /**
       * Delay after entering ringing or call mode after which the framework will mute streams
       * that are still playing.
       */
      private static final int RING_CALL_MUTING_ENFORCEMENT_DELAY_MS = 100;
  
      /**
       * Usages to mute when the device rings or is in a call
       */
      private final static int[] USAGES_TO_MUTE_IN_RING_OR_CALL =
          { AudioAttributes.USAGE_MEDIA, AudioAttributes.USAGE_GAME };
  
      /**
       * Return the volume ramp time expected before playback with the given AudioAttributes would
       * start after gaining audio focus.
       * @param attr attributes of the sound about to start playing
       * @return time in ms
       */
      protected static int getFocusRampTimeMs(int focusGain, AudioAttributes attr) {
          switch (attr.getUsage()) {
              case AudioAttributes.USAGE_MEDIA:
              case AudioAttributes.USAGE_GAME:
                  return 1000;
              case AudioAttributes.USAGE_ALARM:
              case AudioAttributes.USAGE_NOTIFICATION_RINGTONE:
              case AudioAttributes.USAGE_ASSISTANT:
              case AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY:
              case AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE:
                  return 700;
              case AudioAttributes.USAGE_VOICE_COMMUNICATION:
              case AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING:
              case AudioAttributes.USAGE_NOTIFICATION:
              case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST:
              case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT:
              case AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
              case AudioAttributes.USAGE_NOTIFICATION_EVENT:
              case AudioAttributes.USAGE_ASSISTANCE_SONIFICATION:
                  return 500;
              case AudioAttributes.USAGE_UNKNOWN:
              default:
                  return 0;
          }
      }
  
      /** @see AudioManager#requestAudioFocus(AudioManager.OnAudioFocusChangeListener, int, int, int) */
      protected int requestAudioFocus(@NonNull AudioAttributes aa, int focusChangeHint, IBinder cb,
              IAudioFocusDispatcher fd, @NonNull String clientId, @NonNull String callingPackageName,
              int flags, int sdk, boolean forceDuck) {
          mEventLogger.log((new AudioEventLogger.StringEvent(
                  "requestAudioFocus() from uid/pid " + Binder.getCallingUid()
                      + "/" + Binder.getCallingPid()
                      + " clientId=" + clientId + " callingPack=" + callingPackageName
                      + " req=" + focusChangeHint
                      + " flags=0x" + Integer.toHexString(flags)
                      + " sdk=" + sdk))
                  .printLog(TAG));
          // we need a valid binder callback for clients
          if (!cb.pingBinder()) {
              Log.e(TAG, " AudioFocus DOA client for requestAudioFocus(), aborting.");
              return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
          }
  
          if (mAppOps.noteOp(AppOpsManager.OP_TAKE_AUDIO_FOCUS, Binder.getCallingUid(),
                  callingPackageName) != AppOpsManager.MODE_ALLOWED) {
              return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
          }
  
          synchronized(mAudioFocusLock) {
              if (mFocusStack.size() > MAX_STACK_SIZE) {
                  Log.e(TAG, "Max AudioFocus stack size reached, failing requestAudioFocus()");
                  return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
              }
  
              boolean enteringRingOrCall = !mRingOrCallActive
                      & (AudioSystem.IN_VOICE_COMM_FOCUS_ID.compareTo(clientId) == 0);
              if (enteringRingOrCall) { mRingOrCallActive = true; }
  
              final AudioFocusInfo afiForExtPolicy;
              if (mFocusPolicy != null) {
                  // construct AudioFocusInfo as it will be communicated to audio focus policy
                  afiForExtPolicy = new AudioFocusInfo(aa, Binder.getCallingUid(),
                          clientId, callingPackageName, focusChangeHint, 0 /*lossReceived*/,
                          flags, sdk);
              } else {
                  afiForExtPolicy = null;
              }
  
              // handle delayed focus
              boolean focusGrantDelayed = false;
              if (!canReassignAudioFocus()) {
                  if ((flags & AudioManager.AUDIOFOCUS_FLAG_DELAY_OK) == 0) {
                      return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
                  } else {
                      // request has AUDIOFOCUS_FLAG_DELAY_OK: focus can't be
                      // granted right now, so the requester will be inserted in the focus stack
                      // to receive focus later
                      focusGrantDelayed = true;
                  }
              }
  
              // external focus policy?
              if (mFocusPolicy != null) {
                  if (notifyExtFocusPolicyFocusRequest_syncAf(afiForExtPolicy, fd, cb)) {
                      // stop handling focus request here as it is handled by external audio
                      // focus policy (return code will be handled in AudioManager)
                      return AudioManager.AUDIOFOCUS_REQUEST_WAITING_FOR_EXT_POLICY;
                  } else {
                      // an error occured, client already dead, bail early
                      return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
                  }
              }
  
              // handle the potential premature death of the new holder of the focus
              // (premature death == death before abandoning focus)
              // Register for client death notification
              AudioFocusDeathHandler afdh = new AudioFocusDeathHandler(cb);
  
              try {
                  cb.linkToDeath(afdh, 0);
              } catch (RemoteException e) {
                  // client has already died!
                  Log.w(TAG, "AudioFocus  requestAudioFocus() could not link to "+cb+" binder death");
                  return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
              }
  
              if (!mFocusStack.empty() && mFocusStack.peek().hasSameClient(clientId)) {
                  // if focus is already owned by this client and the reason for acquiring the focus
                  // hasn't changed, don't do anything
                  final FocusRequester fr = mFocusStack.peek();
                  if (fr.getGainRequest() == focusChangeHint && fr.getGrantFlags() == flags) {
                      // unlink death handler so it can be gc'ed.
                      // linkToDeath() creates a JNI global reference preventing collection.
                      cb.unlinkToDeath(afdh, 0);
                      notifyExtPolicyFocusGrant_syncAf(fr.toAudioFocusInfo(),
                              AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
                      return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
                  }
                  // the reason for the audio focus request has changed: remove the current top of
                  // stack and respond as if we had a new focus owner
                  if (!focusGrantDelayed) {
                      mFocusStack.pop();
                      // the entry that was "popped" is the same that was "peeked" above
                      fr.release();
                  }
              }
  
              // focus requester might already be somewhere below in the stack, remove it
              removeFocusStackEntry(clientId, false /* signal */, false /*notifyFocusFollowers*/);
  
              final FocusRequester nfr = new FocusRequester(aa, focusChangeHint, flags, fd, cb,
                      clientId, afdh, callingPackageName, Binder.getCallingUid(), this, sdk);
              if (focusGrantDelayed) {
                  // focusGrantDelayed being true implies we can't reassign focus right now
                  // which implies the focus stack is not empty.
                  final int requestResult = pushBelowLockedFocusOwners(nfr);
                  if (requestResult != AudioManager.AUDIOFOCUS_REQUEST_FAILED) {
                      notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(), requestResult);
                  }
                  return requestResult;
              } else {
                  // propagate the focus change through the stack
                  if (!mFocusStack.empty()) {
                      propagateFocusLossFromGain_syncAf(focusChangeHint, nfr, forceDuck);
                  }
  
                  // push focus requester at the top of the audio focus stack
                  mFocusStack.push(nfr);
                  nfr.handleFocusGainFromRequest(AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
              }
              notifyExtPolicyFocusGrant_syncAf(nfr.toAudioFocusInfo(),
                      AudioManager.AUDIOFOCUS_REQUEST_GRANTED);
  
              if (ENFORCE_MUTING_FOR_RING_OR_CALL & enteringRingOrCall) {
                  runAudioCheckerForRingOrCallAsync(true/*enteringRingOrCall*/);
              }
          }//synchronized(mAudioFocusLock)
  
          return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
      }
  
      /**
       * @see AudioManager#abandonAudioFocus(AudioManager.OnAudioFocusChangeListener, AudioAttributes)
       * */
      protected int abandonAudioFocus(IAudioFocusDispatcher fl, String clientId, AudioAttributes aa,
              String callingPackageName) {
          // AudioAttributes are currently ignored, to be used for zones / a11y
          mEventLogger.log((new AudioEventLogger.StringEvent(
                  "abandonAudioFocus() from uid/pid " + Binder.getCallingUid()
                      + "/" + Binder.getCallingPid()
                      + " clientId=" + clientId))
                  .printLog(TAG));
          try {
              // this will take care of notifying the new focus owner if needed
              synchronized(mAudioFocusLock) {
                  // external focus policy?
                  if (mFocusPolicy != null) {
                      final AudioFocusInfo afi = new AudioFocusInfo(aa, Binder.getCallingUid(),
                              clientId, callingPackageName, 0 /*gainRequest*/, 0 /*lossReceived*/,
                              0 /*flags*/, 0 /* sdk n/a here*/);
                      if (notifyExtFocusPolicyFocusAbandon_syncAf(afi)) {
                          return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
                      }
                  }
  
                  boolean exitingRingOrCall = mRingOrCallActive
                          & (AudioSystem.IN_VOICE_COMM_FOCUS_ID.compareTo(clientId) == 0);
                  if (exitingRingOrCall) { mRingOrCallActive = false; }
  
                  removeFocusStackEntry(clientId, true /*signal*/, true /*notifyFocusFollowers*/);
  
                  if (ENFORCE_MUTING_FOR_RING_OR_CALL & exitingRingOrCall) {
                      runAudioCheckerForRingOrCallAsync(false/*enteringRingOrCall*/);
                  }
              }
          } catch (java.util.ConcurrentModificationException cme) {
              // Catching this exception here is temporary. It is here just to prevent
              // a crash seen when the "Silent" notification is played. This is believed to be fixed
              // but this try catch block is left just to be safe.
              Log.e(TAG, "FATAL EXCEPTION AudioFocus  abandonAudioFocus() caused " + cme);
              cme.printStackTrace();
          }
  
          return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
      }
  
  
      protected void unregisterAudioFocusClient(String clientId) {
          synchronized(mAudioFocusLock) {
              removeFocusStackEntry(clientId, false, true /*notifyFocusFollowers*/);
          }
      }
  
      private void runAudioCheckerForRingOrCallAsync(final boolean enteringRingOrCall) {
          new Thread() {
              public void run() {
                  if (enteringRingOrCall) {
                      try {
                          Thread.sleep(RING_CALL_MUTING_ENFORCEMENT_DELAY_MS);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                  }
                  synchronized (mAudioFocusLock) {
                      // since the new thread starting running the state could have changed, so
                      // we need to check again mRingOrCallActive, not enteringRingOrCall
                      if (mRingOrCallActive) {
                          mFocusEnforcer.mutePlayersForCall(USAGES_TO_MUTE_IN_RING_OR_CALL);
                      } else {
                          mFocusEnforcer.unmutePlayersForCall();
                      }
                  }
              }
          }.start();
      }
  }
  