Merge "AudioPolicy: focus stack query, send focus loss APIs"

This commit is contained in:
TreeHugger Robot
2021-12-13 21:41:49 +00:00
committed by Android (Google) Code Review
5 changed files with 114 additions and 1 deletions

View File

@@ -5875,9 +5875,11 @@ package android.media.audiopolicy {
method public android.media.AudioTrack createAudioTrackSource(android.media.audiopolicy.AudioMix) throws java.lang.IllegalArgumentException;
method public int detachMixes(@NonNull java.util.List<android.media.audiopolicy.AudioMix>);
method public int getFocusDuckingBehavior();
method @NonNull @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public java.util.List<android.media.AudioFocusInfo> getFocusStack();
method public int getStatus();
method public boolean removeUidDeviceAffinity(int);
method public boolean removeUserIdDeviceAffinity(int);
method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean sendFocusLoss(@NonNull android.media.AudioFocusInfo) throws java.lang.IllegalStateException;
method public int setFocusDuckingBehavior(int) throws java.lang.IllegalArgumentException, java.lang.IllegalStateException;
method public void setRegistration(String);
method public boolean setUidDeviceAffinity(int, @NonNull java.util.List<android.media.AudioDeviceInfo>);

View File

@@ -460,4 +460,8 @@ interface IAudioService {
boolean register);
void setTestDeviceConnectionState(in AudioDeviceAttributes device, boolean connected);
List<AudioFocusInfo> getFocusStack();
boolean sendFocusLoss(in AudioFocusInfo focusLoser, in IAudioPolicyCallback apcb);
}

View File

@@ -19,6 +19,7 @@ package android.media.audiopolicy;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.annotation.UserIdInt;
@@ -230,7 +231,7 @@ public class AudioPolicy {
* If set to {@code true}, it is mandatory to set an
* {@link AudioPolicy.AudioPolicyFocusListener} in order to successfully build
* an {@code AudioPolicy} instance.
* @param enforce true if the policy will govern audio focus decisions.
* @param isFocusPolicy true if the policy will govern audio focus decisions.
* @return the same Builder instance.
*/
@NonNull
@@ -722,6 +723,45 @@ public class AudioPolicy {
}
}
/**
* Returns the list of entries in the focus stack.
* The list is ordered with increasing rank of focus ownership, where the last entry is at the
* top of the focus stack and is the current focus owner.
* @return the ordered list of focus owners
* @see AudioManager#registerAudioPolicy(AudioPolicy)
*/
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
public @NonNull List<AudioFocusInfo> getFocusStack() {
try {
return getService().getFocusStack();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Send AUDIOFOCUS_LOSS to a specific stack entry, causing it to be notified of the focus
* loss, and for it to exit the focus stack (its focus listener will not be invoked after that).
* This operation is only valid for a registered policy (with
* {@link AudioManager#registerAudioPolicy(AudioPolicy)}) that is also set as the policy focus
* listener (with {@link Builder#setAudioPolicyFocusListener(AudioPolicyFocusListener)}.
* @param focusLoser the stack entry that is exiting the stack through a focus loss
* @return false if the focusLoser wasn't found in the stack, true otherwise
* @throws IllegalStateException if used on an unregistered policy, or a registered policy
* with no {@link AudioPolicyFocusListener} set
* @see AudioManager#registerAudioPolicy(AudioPolicy)
* @see Builder#setAudioPolicyStatusListener(AudioPolicyStatusListener)
*/
@RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
public boolean sendFocusLoss(@NonNull AudioFocusInfo focusLoser) throws IllegalStateException {
Objects.requireNonNull(focusLoser);
try {
return getService().sendFocusLoss(focusLoser, cb());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Create an {@link AudioRecord} instance that is associated with the given {@link AudioMix}.
* Audio buffers recorded through the created instance will contain the mix of the audio

View File

@@ -10142,6 +10142,27 @@ public class AudioService extends IAudioService.Stub
return AudioManager.SUCCESS;
}
/** @see AudioPolicy#getFocusStack() */
public List<AudioFocusInfo> getFocusStack() {
enforceModifyAudioRoutingPermission();
return mMediaFocusControl.getFocusStack();
}
/** @see AudioPolicy#sendFocusLoss */
public boolean sendFocusLoss(@NonNull AudioFocusInfo focusLoser,
@NonNull IAudioPolicyCallback apcb) {
Objects.requireNonNull(focusLoser);
Objects.requireNonNull(apcb);
enforceModifyAudioRoutingPermission();
if (!mAudioPolicies.containsKey(apcb.asBinder())) {
throw new IllegalStateException("Only registered AudioPolicy can change focus");
}
if (!mAudioPolicies.get(apcb.asBinder()).mHasFocusListener) {
throw new IllegalStateException("AudioPolicy must have focus listener to change focus");
}
return mMediaFocusControl.sendFocusLoss(focusLoser);
}
/** see AudioManager.hasRegisteredDynamicPolicy */
public boolean hasRegisteredDynamicPolicy() {
synchronized (mAudioPolicies) {

View File

@@ -27,6 +27,7 @@ import android.media.AudioManager;
import android.media.AudioSystem;
import android.media.IAudioFocusDispatcher;
import android.media.MediaMetrics;
import android.media.audiopolicy.AudioPolicy;
import android.media.audiopolicy.IAudioPolicyCallback;
import android.os.Binder;
import android.os.Build;
@@ -221,6 +222,51 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
}
}
/**
* Return a copy of the focus stack for external consumption (composed of AudioFocusInfo
* instead of FocusRequester instances)
* @return a SystemApi-friendly version of the focus stack, in the same order (last entry
* is top of focus stack, i.e. latest focus owner)
* @see AudioPolicy#getFocusStack()
*/
@NonNull List<AudioFocusInfo> getFocusStack() {
synchronized (mAudioFocusLock) {
final ArrayList<AudioFocusInfo> stack = new ArrayList<>(mFocusStack.size());
for (FocusRequester fr : mFocusStack) {
stack.add(fr.toAudioFocusInfo());
}
return stack;
}
}
/**
* Send AUDIOFOCUS_LOSS to a specific stack entry.
* Note this method is supporting an external API, and is restricted to LOSS in order to
* prevent allowing the stack to be in an invalid state (e.g. entry inside stack has focus)
* @param focusLoser the stack entry that is exiting the stack through a focus loss
* @return false if the focusLoser wasn't found in the stack, true otherwise
* @see AudioPolicy#sendFocusLoss(AudioFocusInfo)
*/
boolean sendFocusLoss(@NonNull AudioFocusInfo focusLoser) {
synchronized (mAudioFocusLock) {
FocusRequester loserToRemove = null;
for (FocusRequester fr : mFocusStack) {
if (fr.getClientId().equals(focusLoser.getClientId())) {
fr.handleFocusLoss(AudioManager.AUDIOFOCUS_LOSS, null,
false /*forceDuck*/);
loserToRemove = fr;
break;
}
}
if (loserToRemove != null) {
mFocusStack.remove(loserToRemove);
loserToRemove.release();
return true;
}
}
return false;
}
@GuardedBy("mAudioFocusLock")
private void notifyTopOfAudioFocusStack() {
// notify the top of the stack it gained focus