Loading api/system-current.txt +1 −0 Original line number Diff line number Diff line Loading @@ -55,6 +55,7 @@ package android { field public static final String CAPTURE_AUDIO_HOTWORD = "android.permission.CAPTURE_AUDIO_HOTWORD"; field public static final String CAPTURE_MEDIA_OUTPUT = "android.permission.CAPTURE_MEDIA_OUTPUT"; field public static final String CAPTURE_TV_INPUT = "android.permission.CAPTURE_TV_INPUT"; field public static final String CAPTURE_VOICE_COMMUNICATION_OUTPUT = "android.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT"; field public static final String CHANGE_APP_IDLE_STATE = "android.permission.CHANGE_APP_IDLE_STATE"; field public static final String CHANGE_DEVICE_IDLE_TEMP_WHITELIST = "android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST"; field public static final String CLEAR_APP_USER_DATA = "android.permission.CLEAR_APP_USER_DATA"; Loading core/res/AndroidManifest.xml +18 −0 Original line number Diff line number Diff line Loading @@ -3793,6 +3793,24 @@ <permission android:name="android.permission.CAPTURE_MEDIA_OUTPUT" android:protectionLevel="signature|privileged" /> <!-- @SystemApi Allows an application to capture the audio played by other apps with the {@code USAGE_VOICE_COMMUNICATION} usage. The application may opt out of capturing by setting an allow capture policy of {@link android.media.AudioAttributes#ALLOW_CAPTURE_BY_NONE}. There are strong restriction listed at {@link android.media.AudioAttributes#ALLOW_CAPTURE_BY_SYSTEM} on what an app can do with the captured audio. See {@code CAPTURE_AUDIO_OUTPUT} and {@code CAPTURE_MEDIA_OUTPUT} for capturing audio use cases other than voice communication playback. <p>Not for use by third-party applications.</p> @hide --> <permission android:name="android.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT" android:protectionLevel="signature|privileged" /> <!-- @SystemApi Allows an application to capture audio for hotword detection. <p>Not for use by third-party applications.</p> @hide --> Loading services/core/java/com/android/server/audio/AudioService.java +55 −2 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import static android.media.AudioManager.RINGER_MODE_NORMAL; import static android.media.AudioManager.RINGER_MODE_SILENT; import static android.media.AudioManager.RINGER_MODE_VIBRATE; import static android.media.AudioManager.STREAM_SYSTEM; import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE; import static android.os.Process.FIRST_APPLICATION_UID; import static android.provider.Settings.Secure.VOLUME_HUSH_MUTE; import static android.provider.Settings.Secure.VOLUME_HUSH_OFF; Loading Loading @@ -89,6 +90,7 @@ import android.media.PlayerBase; import android.media.VolumePolicy; import android.media.audiofx.AudioEffect; import android.media.audiopolicy.AudioMix; import android.media.audiopolicy.AudioMixingRule.AudioMixMatchCriterion; import android.media.audiopolicy.AudioPolicy; import android.media.audiopolicy.AudioPolicyConfig; import android.media.audiopolicy.AudioProductStrategy; Loading Loading @@ -6774,6 +6776,7 @@ public class AudioService extends IAudioService.Stub boolean requireValidProjection = false; boolean requireCaptureAudioOrMediaOutputPerm = false; boolean requireVoiceComunicationOutputPerm = false; boolean requireModifyRouting = false; if (hasFocusAccess || isVolumeController) { Loading @@ -6783,8 +6786,15 @@ public class AudioService extends IAudioService.Stub requireModifyRouting |= true; } for (AudioMix mix : policyConfig.getMixes()) { // If mix is requesting a privileged capture if (mix.getRule().allowPrivilegedPlaybackCapture()) { // If mix is trying to capture USAGE_VOICE_COMMUNICATION using playback capture if (isVoiceCommunicationPlaybackCaptureMix(mix)) { // then it must have CAPTURE_USAGE_VOICE_COMMUNICATION_OUTPUT permission requireVoiceComunicationOutputPerm |= true; } // If mix is requesting privileged capture and is capturing at // least one usage which is not USAGE_VOICE_COMMUNICATION. if (mix.getRule().allowPrivilegedPlaybackCapture() && isNonVoiceCommunicationCaptureMix(mix)) { // then it must have CAPTURE_MEDIA_OUTPUT or CAPTURE_AUDIO_OUTPUT permission requireCaptureAudioOrMediaOutputPerm |= true; // and its format must be low quality enough Loading Loading @@ -6812,6 +6822,14 @@ public class AudioService extends IAudioService.Stub return false; } if (requireVoiceComunicationOutputPerm && !callerHasPermission( android.Manifest.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT)) { Log.e(TAG, "Privileged audio capture for voice communication requires " + "CAPTURE_VOICE_COMMUNICATION_OUTPUT system permission"); return false; } if (requireValidProjection && !canProjectAudio(projection)) { return false; } Loading @@ -6825,6 +6843,41 @@ public class AudioService extends IAudioService.Stub return true; } /** * Checks whether a given AudioMix is used for playback capture * (has the ROUTE_FLAG_LOOP_BACK_RENDER flag) and has a matching * criterion for USAGE_VOICE_COMMUNICATION. */ private boolean isVoiceCommunicationPlaybackCaptureMix(AudioMix mix) { if (mix.getRouteFlags() != mix.ROUTE_FLAG_LOOP_BACK_RENDER) { return false; } for (AudioMixMatchCriterion criterion : mix.getRule().getCriteria()) { if (criterion.getRule() == RULE_MATCH_ATTRIBUTE_USAGE && criterion.getAudioAttributes().getUsage() == AudioAttributes.USAGE_VOICE_COMMUNICATION) { return true; } } return false; } /** * Checks whether a given AudioMix has a matching * criterion for a usage which is not USAGE_VOICE_COMMUNICATION. */ private boolean isNonVoiceCommunicationCaptureMix(AudioMix mix) { for (AudioMixMatchCriterion criterion : mix.getRule().getCriteria()) { if (criterion.getRule() == RULE_MATCH_ATTRIBUTE_USAGE && criterion.getAudioAttributes().getUsage() != AudioAttributes.USAGE_VOICE_COMMUNICATION) { return true; } } return false; } private boolean callerHasPermission(String permission) { return mContext.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED; } Loading Loading
api/system-current.txt +1 −0 Original line number Diff line number Diff line Loading @@ -55,6 +55,7 @@ package android { field public static final String CAPTURE_AUDIO_HOTWORD = "android.permission.CAPTURE_AUDIO_HOTWORD"; field public static final String CAPTURE_MEDIA_OUTPUT = "android.permission.CAPTURE_MEDIA_OUTPUT"; field public static final String CAPTURE_TV_INPUT = "android.permission.CAPTURE_TV_INPUT"; field public static final String CAPTURE_VOICE_COMMUNICATION_OUTPUT = "android.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT"; field public static final String CHANGE_APP_IDLE_STATE = "android.permission.CHANGE_APP_IDLE_STATE"; field public static final String CHANGE_DEVICE_IDLE_TEMP_WHITELIST = "android.permission.CHANGE_DEVICE_IDLE_TEMP_WHITELIST"; field public static final String CLEAR_APP_USER_DATA = "android.permission.CLEAR_APP_USER_DATA"; Loading
core/res/AndroidManifest.xml +18 −0 Original line number Diff line number Diff line Loading @@ -3793,6 +3793,24 @@ <permission android:name="android.permission.CAPTURE_MEDIA_OUTPUT" android:protectionLevel="signature|privileged" /> <!-- @SystemApi Allows an application to capture the audio played by other apps with the {@code USAGE_VOICE_COMMUNICATION} usage. The application may opt out of capturing by setting an allow capture policy of {@link android.media.AudioAttributes#ALLOW_CAPTURE_BY_NONE}. There are strong restriction listed at {@link android.media.AudioAttributes#ALLOW_CAPTURE_BY_SYSTEM} on what an app can do with the captured audio. See {@code CAPTURE_AUDIO_OUTPUT} and {@code CAPTURE_MEDIA_OUTPUT} for capturing audio use cases other than voice communication playback. <p>Not for use by third-party applications.</p> @hide --> <permission android:name="android.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT" android:protectionLevel="signature|privileged" /> <!-- @SystemApi Allows an application to capture audio for hotword detection. <p>Not for use by third-party applications.</p> @hide --> Loading
services/core/java/com/android/server/audio/AudioService.java +55 −2 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import static android.media.AudioManager.RINGER_MODE_NORMAL; import static android.media.AudioManager.RINGER_MODE_SILENT; import static android.media.AudioManager.RINGER_MODE_VIBRATE; import static android.media.AudioManager.STREAM_SYSTEM; import static android.media.audiopolicy.AudioMixingRule.RULE_MATCH_ATTRIBUTE_USAGE; import static android.os.Process.FIRST_APPLICATION_UID; import static android.provider.Settings.Secure.VOLUME_HUSH_MUTE; import static android.provider.Settings.Secure.VOLUME_HUSH_OFF; Loading Loading @@ -89,6 +90,7 @@ import android.media.PlayerBase; import android.media.VolumePolicy; import android.media.audiofx.AudioEffect; import android.media.audiopolicy.AudioMix; import android.media.audiopolicy.AudioMixingRule.AudioMixMatchCriterion; import android.media.audiopolicy.AudioPolicy; import android.media.audiopolicy.AudioPolicyConfig; import android.media.audiopolicy.AudioProductStrategy; Loading Loading @@ -6774,6 +6776,7 @@ public class AudioService extends IAudioService.Stub boolean requireValidProjection = false; boolean requireCaptureAudioOrMediaOutputPerm = false; boolean requireVoiceComunicationOutputPerm = false; boolean requireModifyRouting = false; if (hasFocusAccess || isVolumeController) { Loading @@ -6783,8 +6786,15 @@ public class AudioService extends IAudioService.Stub requireModifyRouting |= true; } for (AudioMix mix : policyConfig.getMixes()) { // If mix is requesting a privileged capture if (mix.getRule().allowPrivilegedPlaybackCapture()) { // If mix is trying to capture USAGE_VOICE_COMMUNICATION using playback capture if (isVoiceCommunicationPlaybackCaptureMix(mix)) { // then it must have CAPTURE_USAGE_VOICE_COMMUNICATION_OUTPUT permission requireVoiceComunicationOutputPerm |= true; } // If mix is requesting privileged capture and is capturing at // least one usage which is not USAGE_VOICE_COMMUNICATION. if (mix.getRule().allowPrivilegedPlaybackCapture() && isNonVoiceCommunicationCaptureMix(mix)) { // then it must have CAPTURE_MEDIA_OUTPUT or CAPTURE_AUDIO_OUTPUT permission requireCaptureAudioOrMediaOutputPerm |= true; // and its format must be low quality enough Loading Loading @@ -6812,6 +6822,14 @@ public class AudioService extends IAudioService.Stub return false; } if (requireVoiceComunicationOutputPerm && !callerHasPermission( android.Manifest.permission.CAPTURE_VOICE_COMMUNICATION_OUTPUT)) { Log.e(TAG, "Privileged audio capture for voice communication requires " + "CAPTURE_VOICE_COMMUNICATION_OUTPUT system permission"); return false; } if (requireValidProjection && !canProjectAudio(projection)) { return false; } Loading @@ -6825,6 +6843,41 @@ public class AudioService extends IAudioService.Stub return true; } /** * Checks whether a given AudioMix is used for playback capture * (has the ROUTE_FLAG_LOOP_BACK_RENDER flag) and has a matching * criterion for USAGE_VOICE_COMMUNICATION. */ private boolean isVoiceCommunicationPlaybackCaptureMix(AudioMix mix) { if (mix.getRouteFlags() != mix.ROUTE_FLAG_LOOP_BACK_RENDER) { return false; } for (AudioMixMatchCriterion criterion : mix.getRule().getCriteria()) { if (criterion.getRule() == RULE_MATCH_ATTRIBUTE_USAGE && criterion.getAudioAttributes().getUsage() == AudioAttributes.USAGE_VOICE_COMMUNICATION) { return true; } } return false; } /** * Checks whether a given AudioMix has a matching * criterion for a usage which is not USAGE_VOICE_COMMUNICATION. */ private boolean isNonVoiceCommunicationCaptureMix(AudioMix mix) { for (AudioMixMatchCriterion criterion : mix.getRule().getCriteria()) { if (criterion.getRule() == RULE_MATCH_ATTRIBUTE_USAGE && criterion.getAudioAttributes().getUsage() != AudioAttributes.USAGE_VOICE_COMMUNICATION) { return true; } } return false; } private boolean callerHasPermission(String permission) { return mContext.checkCallingPermission(permission) == PackageManager.PERMISSION_GRANTED; } Loading