diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 97ad48c968297..767d6eb28db1f 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -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 { diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index 343d04fd32146..a717a90c1c37f 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -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. diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index ee945d5a715f1..36cf989d3a466 100755 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -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); } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 2ce60d05e5410..148c72337be43 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -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) || diff --git a/services/core/java/com/android/server/audio/FadeOutManager.java b/services/core/java/com/android/server/audio/FadeOutManager.java index 9f0a2baea91dc..e08bd673948f8 100644 --- a/services/core/java/com/android/server/audio/FadeOutManager.java +++ b/services/core/java/com/android/server/audio/FadeOutManager.java @@ -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) */ diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java index 1dcfdaec44ecb..03102155952df 100644 --- a/services/core/java/com/android/server/audio/MediaFocusControl.java +++ b/services/core/java/com/android/server/audio/MediaFocusControl.java @@ -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()) { diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java index 47c91e6c23dd9..e71219fd3d26f 100644 --- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java +++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java @@ -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); }