Audio Focus Arbitration Policy


1. List of key classes and variables

android10 audio focus arbitration key class and variable list

variable type illustrate
AudioFocusInfo kind Describe focal applicant attributes
FocusEntry inner class Encapsulation of AudioFocusInfo and Context
sInteractionMatrix Two-dimensional array Arbitration focus application results
mFocusHolders Global variables, HashMap save the current focus holder
mFocusLosers Global variables, HashMap Save applications that temporarily lose focus and wait to regain focus
losers local variable, ArrayList Saves applications that lose focus but the type of focus has not been determined
blocked local variable, ArrayList Save applications in mFocusLosers that can be preempted by current applicants
permanentlyLost local variable, ArrayList Save applications that permanently lose focus

2. Application focus

/packages/services/Car/service/src/com/android/car/audio/CarAudioFocus.java

申请焦点的入口函数:private int evaluateFocusRequestLocked(AudioFocusInfo afi)

//新建两个boolean类型参数用来描述焦点申请者属性:
//permanent:描述申请者是否申请永久获取焦点(即申请类型是否为AUDIOFOCUS_GAIN)
//allowDucking:描述申请者是否申请暂时获取焦点且允许混音(即申请类型是否为AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK)

final boolean permanent =(afi.getGainRequest() == AudioManager.AUDIOFOCUS_GAIN);
final boolean allowDucking=(afi.getGainRequest()==AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK);

//Convert from audio attributes "usage" to HAL level "context"
final int requestedContext = mCarAudioService.getContextForUsage(afi.getAttributes().getUsage());

//防止已经拥有或者暂时失去焦点的引用再次申请焦点,新建两个FocusEntry:
//replacedCurrentEntry:当已经拥有焦点的应用再次申请焦点时,对这个变量赋值,如果申请成功则删除//replacedCurrentEntry,用新的申请代替
//replacedBlockedEntry:当暂时失去焦点的应用再次申请焦点时,对这个变量赋值,如果申请成功则删除//replacedBlockedEntry,用新的申请代替
FocusEntry replacedCurrentEntry = null;
FocusEntry replacedBlockedEntry = null;

Next it will traverse mFocusHolders

首先判断如果当前申请者的requestedContext是NOTIFICATION并且mFocusHolders已经存在一个暂时独占焦点的申请者(AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE),则直接返回申请失败
if ((requestedContext == ContextNumber.NOTIFICATION) &&
    (entry.mAfi.getGainRequest() == AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)) 
{   return AudioManager.AUDIOFOCUS_REQUEST_FAILED;  }


接着判断是否是同一个应用申请的焦点,如果是同一个应用且申请的是同一个Context类型的焦点,则将目前mFocusHolders中的此应用的entry赋值给replacedCurrentEntry,之后如果焦点申请成功则会将这个entry删除并将新的加入,如果申请的是不同的Context类型,则直接返回申请失败
            if (afi.getClientId().equals(entry.mAfi.getClientId())) {
                if (entry.mAudioContext == requestedContext) {
                    // This is a request from a current focus holder.
                    // Abandon the previous request (without sending a LOSS notification to it),
                    // and don't check the interaction matrix for it.
                    Slog.i(TAG, "Replacing accepted request from same client");
                    replacedCurrentEntry = entry;
                    continue;
                } else {
                    // Trivially reject a request for a different USAGE
                    Slog.e(TAG, "Client " + entry.getClientId() + " has already requested focus "
                            + "for " + entry.mAfi.getAttributes().usageToString() + " - cannot "
                            + "request focus for " + afi.getAttributes().usageToString() + " on "
                            + "same listener.");
                    return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
                }
            }

3 Focus Arbitration

Let’s take a look at our focus arbitration matrix first:

/packages/services/Car/service/src/com/android/car/audio/FocusInteraction.java

      private static int sInteractionMatrix[][] = {
          // Row selected by playing sound (labels along the right)
          // Column selected by incoming request (labels along the top)
          // Cell value is one of INTERACTION_REJECT, INTERACTION_EXCLUSIVE, INTERACTION_CONCURRENT
          // Invalid, Music, Nav, Voice, Ring, Call, Alarm, Notification, System
          {  0,       0,     0,   0,     0,    0,    0,     0,            0 }, // Invalid
          {  0,       1,     2,   1,     1,    1,    1,     2,            2 }, // Music
          {  0,       2,     2,   1,     2,    1,    2,     2,            2 }, // Nav
          {  0,       2,     0,   2,     1,    1,    0,     0,            0 }, // Voice
          {  0,       0,     2,   2,     2,    2,    0,     0,            2 }, // Ring
          {  0,       0,     2,   0,     2,    2,    2,     2,            0 }, // Context
          {  0,       2,     2,   1,     1,    1,    2,     2,            2 }, // Alarm
          {  0,       2,     2,   1,     1,    1,    2,     2,            2 }, // Notification
          {  0,       2,     2,   1,     1,    1,    2,     2,            2 }, // System
      };

Use three numbers to represent the three results of focus arbitration:

 // Values ​​for the internal interaction matrix we use to make focus decisions
 static final int INTERACTION_REJECT = 0; // Focus not granted
 static final int INTERACTION_EXCLUSIVE=1; // Focus granted, others loose focus accepts focus applications
 static final int INTERACTION_CONCURRENT = 2; // Focus granted, others keep focus Two applications are accepted at the same time.

If the judgment result is INTERACTION_REJECT, return the application failure, if it is INTERACTION_EXCLUSIVE, add the current focus holder to losers, if it is INTERACTION_CONCURRENT, then judge again:

1) Whether the focal applicant does not accept remixes;

2) Whether the current focus holder does not accept the remix;

3) Whether the focus holder holds the RECEIVE_CAR_AUDIO_DUCKING_EVENTS flag (I don’t know what it means, and I generally don’t use it). If any of them match, the current focus holder will be added to losers. If none of them match, it will appear At the same time, two applications occupy the focus and play at the same time

        for (FocusEntry entry : mFocusHolders.values()) {
            Slog.d(TAG, "Evaluating focus holder: " + entry.toString());
            
            // If this request is for Notifications and a current focus holder has specified
            // AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE, then reject the request.
            // This matches the hardwired behavior in the default audio policy engine which apps
            // might expect (The interaction matrix doesn't have any provision for dealing with
            // override flags like this).
            if ((requestedContext == ContextNumber.NOTIFICATION) &&
                    (entry.mAfi.getGainRequest() ==
                            AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE)) {
                return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
            }

            // We don't allow sharing listeners (client IDs) between two concurrent requests
            // (because the app would have no way to know to which request a later event applied)
            if (afi.getClientId().equals(entry.mAfi.getClientId())) {
                if (entry.mAudioContext == requestedContext) {
                    // This is a request from a current focus holder.
                    // Abandon the previous request (without sending a LOSS notification to it),
                    // and don't check the interaction matrix for it.
                    Slog.i(TAG, "Replacing accepted request from same client");
                    replacedCurrentEntry = entry;
                    continue;
                } else {
                    // Trivially reject a request for a different USAGE
                    Slog.e(TAG, "Client " + entry.getClientId() + " has already requested focus "
                            + "for " + entry.mAfi.getAttributes().usageToString() + " - cannot "
                            + "request focus for " + afi.getAttributes().usageToString() + " on "
                            + "same listener.");
                    return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
                }
            }

            // Check the interaction matrix for the relationship between this entry and the request
            switch (sInteractionMatrix[entry.mAudioContext][requestedContext]) {
                case INTERACTION_REJECT:
                    Slog.d(TAG, "INTERACTION_REJECT");

                    // This request is rejected, so nothing further to do
                    return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
                case INTERACTION_EXCLUSIVE:
                    Slog.d(TAG, "INTERACTION_EXCLUSIVE");
                    // The new request will cause this existing entry to lose focus
                    losers.add(entry);
                    break;
                case INTERACTION_CONCURRENT:
                    // If ducking isn't allowed by the focus requestor, then everybody else
                    // must get a LOSS.
                    // If a focus holder has set the AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS flag,
                    // they must get a LOSS message even if ducking would otherwise be allowed.
                    // If a focus holder holds the RECEIVE_CAR_AUDIO_DUCKING_EVENTS permission,
                    // they must receive all audio focus losses.
                    Slog.d(TAG, "INTERACTION_CONCURRENT");
                    if (!allowDucking
                            || entry.wantsPauseInsteadOfDucking()
                            || entry.receivesDuckEvents()) {
                        losers.add(entry);
                    }
                    break;
                default:
                    Slog.e(TAG, "Bad interaction matrix value - rejecting");
                    return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
            }
        }

Next, mFocusLosers will be traversed , which stores applicants who have temporarily lost focus, and these applicants are likely to lose focus permanently due to the application behavior of the current applicant. The traversal process is exactly the same as above, and the arbitration result is INTERACTION_EXCLUSIVE or INTERACTION_CONCURRENT, and the corresponding entry will be put into blocked. The code is not listed, it is basically the same as above.

If a focus application is still not rejected, congratulations, it will get the audio focus, and create a new FocusEntry to save it:

        // Now that we've decided we'll grant focus, construct our new FocusEntry
        FocusEntry newEntry = new FocusEntry(afi, requestedContext);

  

Create a new ArrayList: permanentlyLost to save applications that permanently lose focus:

        // These entries have permanently lost focus as a result of this request, so they
        // should be removed from all blocker lists.
        ArrayList<FocusEntry> permanentlyLost = new ArrayList<>();

   

If replacedCurrentEntry or replacedBlockedEntry is not equal to null, remove them from the queue and replace it with the newly applied newEntry

        if (replacedCurrentEntry != null) {
            mFocusHolders.remove(replacedCurrentEntry.getClientId());
            permanentlyLost.add(replacedCurrentEntry);
        }
        if (replacedBlockedEntry != null) {
            mFocusLosers.remove(replacedBlockedEntry.getClientId());
            permanentlyLost.add(replacedBlockedEntry);
        }

  

Then traverse the entry in blocked , if the current focus applicant wants to occupy the focus permanently, the entry will lose the focus permanently, if the current focus applicant only temporarily occupies the focus, then judge whether the current focus applicant does not allow the mixing and mReceivedLossTransientCanDuck of this entry Whether the variable is true, if all are satisfied, this entry is not exhausted, and can continue to be stored in mFocusLosers, but it will be added a new mBlockers, that is, the current focus applicant, after all mBlockers are removed , this entry will get the focus

          // Now that we're sure we'll accept this request, update any requests which we would
          // block but are already out of focus but waiting to come back
          for (FocusEntry entry : blocked) {
              // If we're out of focus it must be because somebody is blocking us
              assert !entry.mBlockers.isEmpty();
 
              if (permanent) {
                  // This entry has now lost focus forever
                  sendFocusLoss(entry, AudioManager.AUDIOFOCUS_LOSS);
                  entry.mReceivedLossTransientCanDuck = false;
                  final FocusEntry deadEntry = mFocusLosers.remove(entry.mAfi.getClientId());
                  assert deadEntry != null;
                  permanentlyLost.add(entry);
              } else {
                  if (!allowDucking && entry.mReceivedLossTransientCanDuck) {
                      // This entry was previously allowed to duck, but can no longer do so.
                      Log.i(TAG, "Converting duckable loss to non-duckable for "
                              + entry.getClientId());
                      sendFocusLoss(entry, AudioManager.AUDIOFOCUS_LOSS_TRANSIENT);
                      entry.mReceivedLossTransientCanDuck = false;
                  }
                  // Note that this new request is yet one more reason we can't (yet) have focus
                  entry.mBlockers.add(newEntry);
             }
          }

After traversing the entry in losers, two things will be done here :

    Determine the type of entry losing focus:
    If the current focus applicant wants to occupy the focus permanently, the type of losing focus is AUDIOFOCUS_LOSS; if the current focus applicant only temporarily occupies the focus, and allows audio mixing and holds the RECEIVE_CAR_AUDIO_DUCKING_EVENTS flag, then the type of losing focus It is AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK and the mReceivedLossTransientCanDuck variable of the entry is set to true. In other cases, the type of lost focus is AUDIOFOCUS_LOSS_TRANSIENT
    to determine whether the entry loses focus temporarily or permanently:
    if the current focus applicant wants to occupy the focus permanently, the entry loses focus permanently. If not, The entry temporarily loses focus

          // Notify and update any requests which are now losing focus as a result of the new request
          for (FocusEntry entry : losers) {
              // If we have focus (but are about to loose it), nobody should be blocking us yet
              assert entry.mBlockers.isEmpty();
 
              int lossType;
              if (permanent) {
                  lossType = AudioManager.AUDIOFOCUS_LOSS;
              } else if (allowDucking && entry.receivesDuckEvents()) {
                  lossType = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK;
                  entry.mReceivedLossTransientCanDuck = true;
              } else {
                  lossType = AudioManager.AUDIOFOCUS_LOSS_TRANSIENT;
              }
              sendFocusLoss(entry, lossType);
 
              // The entry no longer holds focus, so take it out of the holders list
              mFocusHolders.remove(entry.mAfi.getClientId());
 
              if (permanent) {
                  permanentlyLost.add(entry);
              } else {
                  // Add ourselves to the list of requests waiting to get focus back and
                  // note why we lost focus so we can tell when it's time to get it back
                  mFocusLosers.put(entry.mAfi.getClientId(), entry);
                  entry.mBlockers.add(newEntry);
              }
          }

Next, it will traverse permanentlyLost and execute the removeFocusEntryAndRestoreUnblockedWaiters(entry) method for all entries in this container. This method will traverse mFocusLosers and remove the incoming entry from the mBlockers of the entry in mFocusLosers. If there is any entry in mFocusLosers mBlockers becomes empty, it means that all obstacles preventing it from getting the focus have disappeared, and it should get the focus at this time:

        // Now that all new blockers have been added, clear out any other requests that have been
        // permanently lost as a result of this request. Treat them as abandoned - if they're on
        // any blocker lists, remove them. If any focus requests become unblocked as a result,
        // re-grant them. (This can happen when a GAIN_TRANSIENT_MAY_DUCK request replaces a
        // GAIN_TRANSIENT request from the same listener.)
        for (FocusEntry entry : permanentlyLost) {
            Slog.d(TAG, "Cleaning up entry " + entry.getClientId());
            removeFocusEntryAndRestoreUnblockedWaiters(entry);
        }

  

    private void removeFocusEntryAndRestoreUnblockedWaiters(FocusEntry deadEntry) {
        // Remove this entry from the blocking list of any pending requests
        Iterator<FocusEntry> it = mFocusLosers.values().iterator();
        while (it.hasNext()) {
            FocusEntry entry = it.next();

            // Remove the retiring entry from all blocker lists
            entry.mBlockers.remove(deadEntry);

            // Any entry whose blocking list becomes empty should regain focus
            if (entry.mBlockers.isEmpty()) {
                Slog.i(TAG, "Restoring unblocked entry " + entry.getClientId());
                // Pull this entry out of the focus losers list
                it.remove();

                // Add it back into the focus holders list
                mFocusHolders.put(entry.getClientId(), entry);

                dispatchFocusGained(entry.mAfi);
            }
        }
    }

finally! Put the focus of the current application into mFocusHolders and return AUDIOFOCUS_REQUEST_GRANTED

          // Finally, add the request we're granting to the focus holders' list
          mFocusHolders.put(afi.getClientId(), newEntry);
 
          Log.i(TAG, "AUDIOFOCUS_REQUEST_GRANTED");
          return AudioManager.AUDIOFOCUS_REQUEST_GRANTED;

Abandoned focus applications opposed to application focus:

      public synchronized void onAudioFocusAbandon(AudioFocusInfo afi) {
          Log.i(TAG, "onAudioFocusAbandon " + afi.getClientId());
 
          FocusEntry deadEntry = removeFocusEntry(afi);
 
          if (deadEntry != null) {
              removeFocusEntryAndRestoreUnblockedWaiters(deadEntry);
          }
      }

Call removeFocusEntry, this method will look for the deadEntry to give up in mFocusHolders and mFocusLosers, if not found, print a warning, if it is successful, it will continue to execute removeFocusEntryAndRestoreUnblockedWaiters
 

Guess you like

Origin blog.csdn.net/wangbuji/article/details/125810909