Audio focus: test APIs for focus injection

Add new APIs for testing more audio focus behaviors:
- new methods to request/abandon audio focus on a "fake" focus
  owner for which an arbitrary UID can be injected
- new method to query the fade out duration on focus loss

Bug: 169620099
Test: atest AudioFocusTest#testAudioFocusRequestMediaGainLossWithPlayer
Change-Id: I4be4597a994bde99ed5ef98296a4a8a2d1b02d8a
This commit is contained in:
Jean-Michel Trivi
2021-03-24 11:14:47 -07:00
parent 40b4495f8e
commit 28602096e7
7 changed files with 169 additions and 9 deletions

View File

@@ -1355,9 +1355,12 @@ package android.media {
}
public class AudioManager {
method @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public int abandonAudioFocusForTest(@NonNull android.media.AudioFocusRequest, @NonNull String);
method @Nullable public static android.media.AudioDeviceInfo getDeviceInfoFromType(int);
method @IntRange(from=0) @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public long getFadeOutDurationOnFocusLossMillis(@NonNull android.media.AudioAttributes);
method public boolean hasRegisteredDynamicPolicy();
method @RequiresPermission(anyOf={android.Manifest.permission.MODIFY_AUDIO_ROUTING, android.Manifest.permission.QUERY_AUDIO_STATE}) public boolean isFullVolumeDevice();
method @RequiresPermission("android.permission.QUERY_AUDIO_STATE") public int requestAudioFocusForTest(@NonNull android.media.AudioFocusRequest, @NonNull String, int, int);
}
public static final class AudioRecord.MetricsConstants {

View File

@@ -3811,6 +3811,14 @@ public class AudioManager {
*/
@SystemApi
public static final int AUDIOFOCUS_FLAG_LOCK = 0x1 << 2;
/**
* @hide
* flag set on test API calls,
* see {@link #requestAudioFocusForTest(AudioFocusRequest, String, int, int)},
* note that it isn't used in conjunction with other flags, it is passed as the single
* value for flags */
public static final int AUDIOFOCUS_FLAG_TEST = 0x1 << 3;
/** @hide */
public static final int AUDIOFOCUS_FLAGS_APPS = AUDIOFOCUS_FLAG_DELAY_OK
| AUDIOFOCUS_FLAG_PAUSES_ON_DUCKABLE_LOSS;
@@ -3970,6 +3978,76 @@ public class AudioManager {
return requestAudioFocus(afr, ap);
}
/**
* @hide
* Test API to request audio focus for an arbitrary client operating from a (fake) given UID.
* Used to simulate conditions of the test, not the behavior of the focus requester under test.
* @param afr the parameters of the request
* @param clientFakeId the identifier of the AudioManager the client would be requesting from
* @param clientFakeUid the UID of the client, here an arbitrary int,
* doesn't have to be a real UID
* @param clientTargetSdk the target SDK used by the client
* @return return code indicating status of the request
*/
@TestApi
@RequiresPermission("android.permission.QUERY_AUDIO_STATE")
public @FocusRequestResult int requestAudioFocusForTest(@NonNull AudioFocusRequest afr,
@NonNull String clientFakeId, int clientFakeUid, int clientTargetSdk) {
Objects.requireNonNull(afr);
Objects.requireNonNull(clientFakeId);
try {
return getService().requestAudioFocusForTest(afr.getAudioAttributes(),
afr.getFocusGain(),
mICallBack,
mAudioFocusDispatcher,
clientFakeId, "com.android.test.fakeclient", clientFakeUid, clientTargetSdk);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* @hide
* Test API to abandon audio focus for an arbitrary client.
* Used to simulate conditions of the test, not the behavior of the focus requester under test.
* @param afr the parameters used for the request
* @param clientFakeId clientFakeId the identifier of the AudioManager from which the client
* would be requesting
* @return return code indicating status of the request
*/
@TestApi
@RequiresPermission("android.permission.QUERY_AUDIO_STATE")
public @FocusRequestResult int abandonAudioFocusForTest(@NonNull AudioFocusRequest afr,
@NonNull String clientFakeId) {
Objects.requireNonNull(afr);
Objects.requireNonNull(clientFakeId);
try {
return getService().abandonAudioFocusForTest(mAudioFocusDispatcher,
clientFakeId, afr.getAudioAttributes(), "com.android.test.fakeclient");
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* @hide
* Return the duration of the fade out applied when a player of the given AudioAttributes
* is losing audio focus
* @param aa the AudioAttributes of the player losing focus with {@link #AUDIOFOCUS_LOSS}
* @return a duration in ms, 0 indicates no fade out is applied
*/
@TestApi
@RequiresPermission("android.permission.QUERY_AUDIO_STATE")
public @IntRange(from = 0) long getFadeOutDurationOnFocusLossMillis(@NonNull AudioAttributes aa)
{
Objects.requireNonNull(aa);
try {
return getService().getFadeOutDurationOnFocusLossMillis(aa);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* @hide
* Request or lock audio focus.

View File

@@ -374,4 +374,13 @@ interface IAudioService {
long getAdditionalOutputDeviceDelay(in AudioDeviceAttributes device);
long getMaxAdditionalOutputDeviceDelay(in AudioDeviceAttributes device);
int requestAudioFocusForTest(in AudioAttributes aa, int durationHint, IBinder cb,
in IAudioFocusDispatcher fd, in String clientId, in String callingPackageName,
int uid, int sdk);
int abandonAudioFocusForTest(in IAudioFocusDispatcher fd, in String clientId,
in AudioAttributes aa, in String callingPackageName);
long getFadeOutDurationOnFocusLossMillis(in AudioAttributes aa);
}

View File

@@ -7907,7 +7907,24 @@ public class AudioService extends IAudioService.Stub
mmi.record();
return mMediaFocusControl.requestAudioFocus(aa, durationHint, cb, fd,
clientId, callingPackageName, flags, sdk,
forceFocusDuckingForAccessibility(aa, durationHint, uid));
forceFocusDuckingForAccessibility(aa, durationHint, uid), -1 /*testUid, ignored*/);
}
/** see {@link AudioManager#requestAudioFocusForTest(AudioFocusRequest, String, int, int)} */
public int requestAudioFocusForTest(AudioAttributes aa, int durationHint, IBinder cb,
IAudioFocusDispatcher fd, String clientId, String callingPackageName,
int fakeUid, int sdk) {
if (!enforceQueryAudioStateForTest("focus request")) {
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
}
if (callingPackageName == null || clientId == null || aa == null) {
final String reason = "Invalid null parameter to request audio focus";
Log.e(TAG, reason);
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
}
return mMediaFocusControl.requestAudioFocus(aa, durationHint, cb, fd,
clientId, callingPackageName, AudioManager.AUDIOFOCUS_FLAG_TEST,
sdk, false /*forceDuck*/, fakeUid);
}
public int abandonAudioFocus(IAudioFocusDispatcher fd, String clientId, AudioAttributes aa,
@@ -7927,6 +7944,15 @@ public class AudioService extends IAudioService.Stub
return mMediaFocusControl.abandonAudioFocus(fd, clientId, aa, callingPackageName);
}
/** see {@link AudioManager#abandonAudioFocusForTest(AudioFocusRequest, String)} */
public int abandonAudioFocusForTest(IAudioFocusDispatcher fd, String clientId,
AudioAttributes aa, String callingPackageName) {
if (!enforceQueryAudioStateForTest("focus abandon")) {
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
}
return mMediaFocusControl.abandonAudioFocus(fd, clientId, aa, callingPackageName);
}
public void unregisterAudioFocusClient(String clientId) {
new MediaMetrics.Item(mMetricsId + "focus")
.set(MediaMetrics.Property.CLIENT_NAME, clientId)
@@ -7949,6 +7975,25 @@ public class AudioService extends IAudioService.Stub
return mMediaFocusControl.hasAudioFocusUsers();
}
/** see {@link AudioManager#getFadeOutDurationOnFocusLossMillis(AudioAttributes)} */
public long getFadeOutDurationOnFocusLossMillis(AudioAttributes aa) {
if (!enforceQueryAudioStateForTest("fade out duration")) {
return 0;
}
return mMediaFocusControl.getFadeOutDurationOnFocusLossMillis(aa);
}
private boolean enforceQueryAudioStateForTest(String mssg) {
if (PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission(
Manifest.permission.QUERY_AUDIO_STATE)) {
final String reason = "Doesn't have QUERY_AUDIO_STATE permission for "
+ mssg + " test API";
Log.e(TAG, reason, new Exception());
return false;
}
return true;
}
//==========================================================================================
private boolean readCameraSoundForced() {
return SystemProperties.getBoolean("audio.camerasound.force", false) ||

View File

@@ -97,6 +97,16 @@ public final class FadeOutManager {
return true;
}
static long getFadeOutDurationOnFocusLossMillis(AudioAttributes aa) {
if (ArrayUtils.contains(UNFADEABLE_CONTENT_TYPES, aa.getContentType())) {
return 0;
}
if (!ArrayUtils.contains(FADEABLE_USAGES, aa.getUsage())) {
return 0;
}
return FADE_OUT_DURATION_MS;
}
/**
* Map of uid (key) to faded out apps (value)
*/

View File

@@ -862,11 +862,13 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
* @param forceDuck only true if
* {@link android.media.AudioFocusRequest.Builder#setFocusGain(int)} was set to true for
* accessibility.
* @param testUid ignored if flags is not AudioManager.AUDIOFOCUS_FLAG_TEST (strictly equals to)
* otherwise the UID being injected for testing
* @return
*/
protected int requestAudioFocus(@NonNull AudioAttributes aa, int focusChangeHint, IBinder cb,
IAudioFocusDispatcher fd, @NonNull String clientId, @NonNull String callingPackageName,
int flags, int sdk, boolean forceDuck) {
int flags, int sdk, boolean forceDuck, int testUid) {
new MediaMetrics.Item(mMetricsId)
.setUid(Binder.getCallingUid())
.set(MediaMetrics.Property.CALLING_PACKAGE, callingPackageName)
@@ -878,8 +880,13 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
//.set(MediaMetrics.Property.SDK, sdk)
.record();
// when using the test API, a fake UID can be injected (testUid is ignored otherwise)
// note that the test on flags is not a mask test on purpose, AUDIOFOCUS_FLAG_TEST is
// supposed to be alone in bitfield
final int uid = (flags == AudioManager.AUDIOFOCUS_FLAG_TEST)
? testUid : Binder.getCallingUid();
mEventLogger.log((new AudioEventLogger.StringEvent(
"requestAudioFocus() from uid/pid " + Binder.getCallingUid()
"requestAudioFocus() from uid/pid " + uid
+ "/" + Binder.getCallingPid()
+ " clientId=" + clientId + " callingPack=" + callingPackageName
+ " req=" + focusChangeHint
@@ -892,8 +899,10 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
}
if (mAppOps.noteOp(AppOpsManager.OP_TAKE_AUDIO_FOCUS, Binder.getCallingUid(),
callingPackageName) != AppOpsManager.MODE_ALLOWED) {
if ((flags != AudioManager.AUDIOFOCUS_FLAG_TEST)
// note we're using the real uid for appOp evaluation
&& (mAppOps.noteOp(AppOpsManager.OP_TAKE_AUDIO_FOCUS, Binder.getCallingUid(),
callingPackageName) != AppOpsManager.MODE_ALLOWED)) {
return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
}
@@ -910,7 +919,7 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
final AudioFocusInfo afiForExtPolicy;
if (mFocusPolicy != null) {
// construct AudioFocusInfo as it will be communicated to audio focus policy
afiForExtPolicy = new AudioFocusInfo(aa, Binder.getCallingUid(),
afiForExtPolicy = new AudioFocusInfo(aa, uid,
clientId, callingPackageName, focusChangeHint, 0 /*lossReceived*/,
flags, sdk);
} else {
@@ -980,7 +989,7 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
removeFocusStackEntry(clientId, false /* signal */, false /*notifyFocusFollowers*/);
final FocusRequester nfr = new FocusRequester(aa, focusChangeHint, flags, fd, cb,
clientId, afdh, callingPackageName, Binder.getCallingUid(), this, sdk);
clientId, afdh, callingPackageName, uid, this, sdk);
if (mMultiAudioFocusEnabled
&& (focusChangeHint == AudioManager.AUDIOFOCUS_GAIN)) {
@@ -1143,6 +1152,13 @@ public class MediaFocusControl implements PlayerFocusEnforcer {
return mMultiAudioFocusEnabled;
}
/*package*/ long getFadeOutDurationOnFocusLossMillis(AudioAttributes aa) {
if (!ENFORCE_FADEOUT_FOR_FOCUS_LOSS) {
return 0;
}
return FadeOutManager.getFadeOutDurationOnFocusLossMillis(aa);
}
private void dumpMultiAudioFocus(PrintWriter pw) {
pw.println("Multi Audio Focus enabled :" + mMultiAudioFocusEnabled);
if (!mMultiAudioFocusList.isEmpty()) {

View File

@@ -704,6 +704,7 @@ public final class PlaybackActivityMonitor
// find which players to fade out
synchronized (mPlayerLock) {
if (mPlayers.isEmpty()) {
if (DEBUG) { Log.v(TAG, "no players to fade out"); }
return false;
}
// check if this UID needs to be faded out (return false if not), and gather list of
@@ -731,8 +732,6 @@ public final class PlaybackActivityMonitor
apcsToFadeOut.add(apc);
}
}
//###
//mDuckingManager.duckUid(loser.getClientUid(), apcsToFadeOut);
if (loserHasActivePlayers) {
mFadingManager.fadeOutUid(loser.getClientUid(), apcsToFadeOut);
}