Android 13 adds further customizations to improve the user experience and increase compatibility with TV devices. Some of the highlights for this release include performance and quality improvements and continued advancements to how users interact with Android TV.
See the following sections for more information about what's included.
Performance & quality
Anticipatory audio routes
You can now anticipate device audio attribute support and prepare tracks for
the active audio device. You can use
getDirectPlaybackSupport()
to check whether direct playback is supported on the currently routed audio
device for a given format and attributes:
Kotlin
val format = AudioFormat.Builder() .setEncoding(AudioFormat.ENCODING_E_AC3) .setChannelMask(AudioFormat.CHANNEL_OUT_5POINT1) .setSampleRate(48000) .build() val attributes = AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_MEDIA) .build() if (AudioManager.getDirectPlaybackSupport(format, attributes) != AudioManager.DIRECT_PLAYBACK_NOT_SUPPORTED ) { // The format and attributes are supported for direct playback // on the currently active routed audio path } else { // The format and attributes are NOT supported for direct playback // on the currently active routed audio path }
Java
AudioFormat format = new AudioFormat.Builder() .setEncoding(AudioFormat.ENCODING_E_AC3) .setChannelMask(AudioFormat.CHANNEL_OUT_5POINT1) .setSampleRate(48000) .build(); AudioAttributes attributes = new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_MEDIA) .build(); if (AudioManager.getDirectPlaybackSupport(format, attributes) != AudioManager.DIRECT_PLAYBACK_NOT_SUPPORTED) { // The format and attributes are supported for direct playback // on the currently active routed audio path } else { // The format and attributes are NOT supported for direct playback // on the currently active routed audio path }
Alternatively, you can query which profiles are supported for direct media playback through the currently routed audio device. This excludes any profiles that are unsupported or would be, for instance, transcoded by the Android framework:
Kotlin
private fun findBestAudioFormat(audioAttributes: AudioAttributes): AudioFormat { val preferredFormats = listOf( AudioFormat.ENCODING_E_AC3, AudioFormat.ENCODING_AC3, AudioFormat.ENCODING_PCM_16BIT, AudioFormat.ENCODING_DEFAULT ) val audioProfiles = audioManager.getDirectProfilesForAttributes(audioAttributes) val bestAudioProfile = preferredFormats.firstNotNullOf { format -> audioProfiles.firstOrNull { it.format == format } } val sampleRate = findBestSampleRate(bestAudioProfile) val channelMask = findBestChannelMask(bestAudioProfile) return AudioFormat.Builder() .setEncoding(bestAudioProfile.format) .setSampleRate(sampleRate) .setChannelMask(channelMask) .build() }
Java
private AudioFormat findBestAudioFormat(AudioAttributes audioAttributes) { Stream<Integer> preferredFormats = Stream.<Integer>builder() .add(AudioFormat.ENCODING_E_AC3) .add(AudioFormat.ENCODING_AC3) .add(AudioFormat.ENCODING_PCM_16BIT) .add(AudioFormat.ENCODING_DEFAULT) .build(); Stream<AudioProfile> audioProfiles = audioManager.getDirectProfilesForAttributes(audioAttributes).stream(); AudioProfile bestAudioProfile = (AudioProfile) preferredFormats.map(format -> audioProfiles.filter(profile -> profile.getFormat() == format) .findFirst() .orElseThrow(NoSuchElementException::new) ); Integer sampleRate = findBestSampleRate(bestAudioProfile); Integer channelMask = findBestChannelMask(bestAudioProfile); return new AudioFormat.Builder() .setEncoding(bestAudioProfile.getFormat()) .setSampleRate(sampleRate) .setChannelMask(channelMask) .build(); }
In this example, preferredFormats
is a list of
AudioFormat
instances. It is ordered with the
most preferred first in the list, and the least preferred last.
getDirectProfilesForAttributes()
returns a list of supported
AudioProfile
objectss for the currently routed audio device with the
supplied AudioAttributes
. The list of preferred AudioFormat
items is
iterated through until a matching supported AudioProfile
is found. This
AudioProfile
is stored as bestAudioProfile
. Optimum sample rates and
channel masks are determined from bestAudioProfile
. Finally, an appropriate
AudioFormat
instance is created.
HDMI state surfaced to MediaSession
HDMI state changes are now surfaced to the MediaSession
lifecycle. If
you handle these events accurately, then playback is stopped if the HDMI device
is powered off.
Input & accessibility
Keyboard layouts API
From Android 13 (API level 33) onwards, you can determine keyboard layouts using
getKeyCodeForKeyLocation()
. For example, your game supports movement
using the WASD keys, but this may not work correctly on an AZERTY keyboard,
which has the A and W keys in different locations. You can get the keycodes for
the keys you expect at certain positions:
Kotlin
val inputManager: InputManager? = requireActivity().getSystemService() inputManager?.inputDeviceIds?.map { inputManager.getInputDevice(it) } ?.firstOrNull { it.keyboardType == InputDevice.KEYBOARD_TYPE_ALPHABETIC } ?.let { inputDevice -> keyUp = inputDevice.getKeyCodeForKeyLocation(KeyEvent.KEYCODE_W) keyLeft = inputDevice.getKeyCodeForKeyLocation(KeyEvent.KEYCODE_A) keyDown = inputDevice.getKeyCodeForKeyLocation(KeyEvent.KEYCODE_S) keyRight = inputDevice.getKeyCodeForKeyLocation(KeyEvent.KEYCODE_D) }
Java
InputManager inputManager = requireActivity().getSystemService(InputManager.class); InputDevice inputDevice = Arrays.stream(inputManager.getInputDeviceIds()) .mapToObj(inputManager::getInputDevice) .filter( device -> device.getKeyboardType() == InputDevice.KEYBOARD_TYPE_ALPHABETIC) .filter(Objects::nonNull) .findFirst() .orElse(null); if (inputDevice != null) { keyUp = inputDevice.getKeyCodeForKeyLocation(KeyEvent.KEYCODE_W); keyLeft = inputDevice.getKeyCodeForKeyLocation(KeyEvent.KEYCODE_A); keyDown = inputDevice.getKeyCodeForKeyLocation(KeyEvent.KEYCODE_S); keyRight = inputDevice.getKeyCodeForKeyLocation(KeyEvent.KEYCODE_D); }
In this example, with an AZERTY keyboard, keyUp
is set to
KeyEvent.KEYCODE_Z
, keyLeft
is set to KeyEvent.KEYCODE_Q
, while keyDown
and keyRight
are set to KeyEvent.KEYCODE_S
and KeyEvent.KEYCODE_D
respectively. You can now create key event handlers for these key codes and
implement the expected behavior.
Audio descriptions
Android 13 (API level 33) introduces a new system-wide accessibility preference
that allows users to enable audio descriptions across all apps.
Android TV apps can follow the user’s preferences by
querying it with isAudioDescriptionRequested()
.
Kotlin
private lateinit var accessibilityManager: AccessibilityManager // In onCreate(): accessibilityManager = getSystemService(AccessibilityManager::class.java) // Where your media player is initialized if (am.isAudioDescriptionRequested) { // User has requested to enable audio descriptions }
Java
private AccessibilityManager accessibilityManager; // In onCreate(): accessibilityManager = getSystemService(AccessibilityManager.class); // Where your media player is initialized if(accessibilityManager.isAudioDescriptionRequested()) { // User has requested to enable audio descriptions }
Android TV apps can monitor user’s preference change by
adding a listener to
AccessbilityManager
.
Kotlin
private val listener = AccessibilityManager.AudioDescriptionRequestedChangeListener { enabled -> // Preference changed; reflect its state in your media player } override fun onStart() { super.onStart() accessibilityManager.addAudioDescriptionRequestedChangeListener(mainExecutor, listener) } override fun onStop() { super.onStop() accessibilityManager.removeAudioDescriptionRequestedChangeListener(listener) }
Java
private AccessibilityManager.AudioDescriptionRequestedChangeListener listener = enabled -> { // Preference changed; reflect its state in your media player }; @Override protected void onStart() { super.onStart(); accessibilityManager.addAudioDescriptionRequestedChangeListener(getMainExecutor(), listener); } @Override protected void onStop() { super.onStop(); accessibilityManager.removeAudioDescriptionRequestedChangeListener(listener); }
Validation Tools
- Media Controller Test allows you to test the intricacies of media playback on Android and helps verify your media session implementation.
- MediaSession Validator provides an easy and automated way to verify your media session integration on Android TV.