Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,13 @@ validatorPush = "1.0.0-alpha08"
version-catalog-update = "1.0.0"
watchfaceComplicationsDataSourceKtx = "1.2.1"
wear = "1.3.0"
wearComposeFoundation = "1.5.0-rc02"
wearComposeMaterial = "1.5.0-rc02"
wearComposeMaterial3 = "1.5.0-rc02"
wearComposeFoundation = "1.5.2"
wearComposeMaterial = "1.5.2"
wearComposeMaterial3 = "1.5.2"
wearOngoing = "1.1.0"
wearToolingPreview = "1.0.0"
webkit = "1.14.0"
media3Ui = "1.8.0"

[libraries]
accompanist-adaptive = "com.google.accompanist:accompanist-adaptive:0.37.3"
Expand Down Expand Up @@ -196,6 +197,7 @@ play-services-wearable = { module = "com.google.android.gms:play-services-wearab
validator-push = { module = "com.google.android.wearable.watchface.validator:validator-push", version.ref = "validatorPush" }
wear-compose-material = { module = "androidx.wear.compose:compose-material", version.ref = "wearComposeMaterial" }
wear-compose-material3 = { module = "androidx.wear.compose:compose-material3", version.ref = "wearComposeMaterial3" }
androidx-media3-ui = { group = "androidx.media3", name = "media3-ui", version.ref = "media3Ui" }

[plugins]
android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }
Expand Down
2 changes: 2 additions & 0 deletions wear/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ android {

dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.media3.exoplayer)
implementation(libs.androidx.media3.ui)
val composeBom = platform(libs.androidx.compose.bom)
implementation(composeBom)
androidTestImplementation(composeBom)
Expand Down
5 changes: 5 additions & 0 deletions wear/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@
</intent-filter>
</activity>

<activity android:name=".snippets.audio.AudioActivity"
android:exported="true"
android:taskAffinity=""
android:theme="@android:style/Theme.DeviceDefault"/>

<!-- [START android_wear_tile_manifest] -->
<service
android:name=".snippets.tile.MyTileService"
Expand Down
104 changes: 104 additions & 0 deletions wear/src/main/java/com/example/wear/snippets/audio/AudioActivity.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
/*
* Copyright 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example.wear.snippets.audio

import android.content.Context
import android.content.pm.PackageManager
import android.media.AudioDeviceCallback
import android.media.AudioDeviceInfo
import android.media.AudioManager
import androidx.activity.ComponentActivity
import androidx.annotation.OptIn
import androidx.media3.common.AudioAttributes
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.ui.WearUnsuitableOutputPlaybackSuppressionResolverListener

class AudioActivity : ComponentActivity() {

// [START android_wear_audio_detect_devices]
private val audioManager: AudioManager by lazy {
getSystemService(AUDIO_SERVICE) as AudioManager
}

fun audioOutputAvailable(type: Int): Boolean {
if (!packageManager.hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
return false
}
return audioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS).any { it.type == type }
}
// [END android_wear_audio_detect_devices]

@OptIn(UnstableApi::class)
fun buildExoPlayer(context: Context): ExoPlayer {
// [START android_wear_exoplayer_audio_output_suppression]
val exoPlayer = ExoPlayer.Builder(context)
.setAudioAttributes(AudioAttributes.DEFAULT, true)
.setSuppressPlaybackOnUnsuitableOutput(true)
.build()
// [END android_wear_exoplayer_audio_output_suppression]
// [START android_wear_exoplayer_audio_output_suppression_listener]
exoPlayer.addListener(WearUnsuitableOutputPlaybackSuppressionResolverListener(context))
// [END android_wear_exoplayer_audio_output_suppression_listener]
return exoPlayer
}

override fun onCreate(savedInstanceState: android.os.Bundle?) {
super.onCreate(savedInstanceState)

// [START android_wear_audio_detect_devices_sample]
val hasSpeaker = audioOutputAvailable(AudioDeviceInfo.TYPE_BUILTIN_SPEAKER)
val hasBluetoothHeadset = audioOutputAvailable(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP)
val hasBLEBroadcast = audioOutputAvailable(AudioDeviceInfo.TYPE_BLE_BROADCAST)
val hasBLEHeadset = audioOutputAvailable(AudioDeviceInfo.TYPE_BLE_HEADSET)
val hasBLESpeaker = audioOutputAvailable(AudioDeviceInfo.TYPE_BLE_SPEAKER)
// [END android_wear_audio_detect_devices_sample]
println("Has speaker: $hasSpeaker")
println("Has Bluetooth headset: $hasBluetoothHeadset")
println("Has BLE broadcast: $hasBLEBroadcast")
println("Has BLE headset: $hasBLEHeadset")
println("Has BLE speaker: $hasBLESpeaker")

// [START android_wear_audio_register_callback]
val audioDeviceCallback =
object : AudioDeviceCallback() {
override fun onAudioDevicesAdded(addedDevices: Array<out AudioDeviceInfo>?) {
super.onAudioDevicesAdded(addedDevices)
if (audioOutputAvailable(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP) ||
audioOutputAvailable(AudioDeviceInfo.TYPE_BLE_BROADCAST) ||
audioOutputAvailable(AudioDeviceInfo.TYPE_BLE_HEADSET) ||
audioOutputAvailable(AudioDeviceInfo.TYPE_BLE_SPEAKER)
) {
// A Bluetooth or BLE device is connected and available for playback.
}
}
override fun onAudioDevicesRemoved(removedDevices: Array<out AudioDeviceInfo>?) {
super.onAudioDevicesRemoved(removedDevices)
if (!(audioOutputAvailable(AudioDeviceInfo.TYPE_BLUETOOTH_A2DP)) &&
!(audioOutputAvailable(AudioDeviceInfo.TYPE_BLE_BROADCAST)) &&
!(audioOutputAvailable(AudioDeviceInfo.TYPE_BLE_HEADSET)) &&
!(audioOutputAvailable(AudioDeviceInfo.TYPE_BLE_SPEAKER))
) {
// No Bluetooth or BLE devices are connected anymore.
}
}
}

audioManager.registerAudioDeviceCallback(audioDeviceCallback, /*handler=*/ null)
// [END android_wear_audio_register_callback]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* Copyright 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.example.wear.snippets.audio

import android.content.Context
import android.content.Intent
import android.provider.Settings

object BluetoothSettings {
// [START android_wear_bluetooth_settings]
fun Context.launchBluetoothSettings(closeOnConnect: Boolean = true) {
val intent = with(Intent(Settings.ACTION_BLUETOOTH_SETTINGS)) {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
putExtra("EXTRA_CONNECTION_ONLY", true)
if (closeOnConnect) {
putExtra("EXTRA_CLOSE_ON_CONNECT", true)
}
putExtra("android.bluetooth.devicepicker.extra.FILTER_TYPE", FILTER_TYPE_AUDIO)
}
startActivity(intent)
}

internal const val FILTER_TYPE_AUDIO = 1
// [END android_wear_bluetooth_settings]
}