From 92b2c31b760d16f272f40ee84174071912805919 Mon Sep 17 00:00:00 2001 From: garan Date: Tue, 16 Sep 2025 23:27:01 +0100 Subject: [PATCH 1/3] Adds Wearable API check --- .../transfer/WearAssetTransmitter.kt | 30 +++++++++----- .../transfer/WearDeviceRepository.kt | 40 ++++++++++++++----- .../transfer/WearableApiAvailability.kt | 30 ++++++++++++++ 3 files changed, 79 insertions(+), 21 deletions(-) create mode 100644 watchface/src/main/java/com/android/developers/androidify/watchface/transfer/WearableApiAvailability.kt diff --git a/watchface/src/main/java/com/android/developers/androidify/watchface/transfer/WearAssetTransmitter.kt b/watchface/src/main/java/com/android/developers/androidify/watchface/transfer/WearAssetTransmitter.kt index b2816d12..31ce2a77 100644 --- a/watchface/src/main/java/com/android/developers/androidify/watchface/transfer/WearAssetTransmitter.kt +++ b/watchface/src/main/java/com/android/developers/androidify/watchface/transfer/WearAssetTransmitter.kt @@ -82,20 +82,30 @@ class WearAssetTransmitterImpl @Inject constructor( */ override val watchFaceInstallationUpdates = callbackFlow { trySend(WatchFaceInstallationStatus.NotStarted) - val listener = MessageClient.OnMessageReceivedListener { event -> - if (event.path.contains(transferId)) { - val response = - ProtoBuf.decodeFromByteArray(event.data) - if (response.transferId == transferId) { - trySend(response) + + var listener : MessageClient.OnMessageReceivedListener? = null + + /** + * Some devices don't have access to Wearable API via Play Services, so it is necessary to + * check for this scenario first before trying to use the API. + */ + val apiAvailable = WearableApiAvailability.isAvailable(messageClient) + if (apiAvailable) { + listener = MessageClient.OnMessageReceivedListener { event -> + if (event.path.contains(transferId)) { + val response = + ProtoBuf.decodeFromByteArray(event.data) + if (response.transferId == transferId) { + trySend(response) + } } } + messageClient.addListener(listener).await() } - - messageClient.addListener(listener).await() - awaitClose { - messageClient.removeListener(listener).addOnSuccessListener { } + listener?.let { + messageClient.removeListener(it).addOnSuccessListener { } + } } } diff --git a/watchface/src/main/java/com/android/developers/androidify/watchface/transfer/WearDeviceRepository.kt b/watchface/src/main/java/com/android/developers/androidify/watchface/transfer/WearDeviceRepository.kt index 2cb74f5c..34af0219 100644 --- a/watchface/src/main/java/com/android/developers/androidify/watchface/transfer/WearDeviceRepository.kt +++ b/watchface/src/main/java/com/android/developers/androidify/watchface/transfer/WearDeviceRepository.kt @@ -45,23 +45,39 @@ class WearDeviceRepositoryImpl @Inject constructor( private val capabilityClient: CapabilityClient by lazy { Wearable.getCapabilityClient(context) } override val connectedWatch = callbackFlow { - val allDevices = nodeClient.connectedNodes.await().toSet() - val reachableCapability = - capabilityClient.getCapability(ANDROIDIFY_INSTALLED, CapabilityClient.FILTER_REACHABLE) - .await() + var capabilityListener: CapabilityClient.OnCapabilityChangedListener? = null - val installedDevices = reachableCapability.nodes.toSet() + /** + * Some devices don't have access to Wearable API via Play Services, so it is necessary to + * check for this scenario first before trying to use the API. + */ + val apiAvailable = WearableApiAvailability.isAvailable(nodeClient) + if (apiAvailable) { + val allDevices = nodeClient.connectedNodes.await().toSet() + val reachableCapability = + capabilityClient.getCapability( + ANDROIDIFY_INSTALLED, + CapabilityClient.FILTER_REACHABLE + ) + .await() - trySend(selectConnectedDevice(installedDevices, allDevices)) + val installedDevices = reachableCapability.nodes.toSet() + trySend(selectConnectedDevice(installedDevices, allDevices)) - val capabilityListener = CapabilityClient.OnCapabilityChangedListener { capabilityInfo -> - val installedDevicesUpdated = capabilityInfo.nodes.toSet() + capabilityListener = + CapabilityClient.OnCapabilityChangedListener { capabilityInfo -> + val installedDevicesUpdated = capabilityInfo.nodes.toSet() - trySend(selectConnectedDevice(installedDevicesUpdated, allDevices)) + trySend(selectConnectedDevice(installedDevicesUpdated, allDevices)) + } + capabilityClient.addListener(capabilityListener, ANDROIDIFY_INSTALLED) + } else { + trySend(null) } - capabilityClient.addListener(capabilityListener, ANDROIDIFY_INSTALLED) awaitClose { - capabilityClient.removeListener(capabilityListener) + capabilityListener?.let { + capabilityClient.removeListener(it) + } } } @@ -89,4 +105,6 @@ class WearDeviceRepositoryImpl @Inject constructor( null } } + + private val TAG = "WearDeviceRepository" } diff --git a/watchface/src/main/java/com/android/developers/androidify/watchface/transfer/WearableApiAvailability.kt b/watchface/src/main/java/com/android/developers/androidify/watchface/transfer/WearableApiAvailability.kt new file mode 100644 index 00000000..148f382f --- /dev/null +++ b/watchface/src/main/java/com/android/developers/androidify/watchface/transfer/WearableApiAvailability.kt @@ -0,0 +1,30 @@ +package com.android.developers.androidify.watchface.transfer + +import android.util.Log +import com.google.android.gms.common.GoogleApiAvailability +import com.google.android.gms.common.api.AvailabilityException +import com.google.android.gms.common.api.GoogleApi +import kotlinx.coroutines.tasks.await + +/** + * Checks whether a given Wearable Data Layer API is available on this device. + */ +object WearableApiAvailability { + suspend fun isAvailable(api: GoogleApi<*>): Boolean { + return try { + GoogleApiAvailability.getInstance() + .checkApiAvailability(api) + .await() + + true + } catch (e: AvailabilityException) { + Log.d( + TAG, + "${api.javaClass.simpleName} API is not available in this device.", + ) + false + } + } + + val TAG = "WearableApiAvailability" +} \ No newline at end of file From b7de6ead156110db70dfde0ba13e1cfd386197c2 Mon Sep 17 00:00:00 2001 From: garan Date: Tue, 16 Sep 2025 23:27:51 +0100 Subject: [PATCH 2/3] Apply spotless --- .../androidify/watchface/creator/Signer.kt | 2 +- .../watchface/transfer/WearAssetTransmitter.kt | 4 ++-- .../watchface/transfer/WearDeviceRepository.kt | 2 +- .../transfer/WearableApiAvailability.kt | 17 ++++++++++++++++- 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/watchface/src/main/java/com/android/developers/androidify/watchface/creator/Signer.kt b/watchface/src/main/java/com/android/developers/androidify/watchface/creator/Signer.kt index ed4a2168..38e545fc 100644 --- a/watchface/src/main/java/com/android/developers/androidify/watchface/creator/Signer.kt +++ b/watchface/src/main/java/com/android/developers/androidify/watchface/creator/Signer.kt @@ -39,7 +39,7 @@ import java.security.cert.X509Certificate import java.util.Calendar import java.util.Date -private val keyAlias : String +private val keyAlias: String get() = "com.android.developers.androidify.ApkSigningKey-" + BuildConfig.BUILD_TYPE private val certAlias: String get() = "com.android.developers.androidify.Cert-" + BuildConfig.BUILD_TYPE diff --git a/watchface/src/main/java/com/android/developers/androidify/watchface/transfer/WearAssetTransmitter.kt b/watchface/src/main/java/com/android/developers/androidify/watchface/transfer/WearAssetTransmitter.kt index 31ce2a77..305ce3b2 100644 --- a/watchface/src/main/java/com/android/developers/androidify/watchface/transfer/WearAssetTransmitter.kt +++ b/watchface/src/main/java/com/android/developers/androidify/watchface/transfer/WearAssetTransmitter.kt @@ -83,7 +83,7 @@ class WearAssetTransmitterImpl @Inject constructor( override val watchFaceInstallationUpdates = callbackFlow { trySend(WatchFaceInstallationStatus.NotStarted) - var listener : MessageClient.OnMessageReceivedListener? = null + var listener: MessageClient.OnMessageReceivedListener? = null /** * Some devices don't have access to Wearable API via Play Services, so it is necessary to @@ -100,7 +100,7 @@ class WearAssetTransmitterImpl @Inject constructor( } } } - messageClient.addListener(listener).await() + messageClient.addListener(listener).await() } awaitClose { listener?.let { diff --git a/watchface/src/main/java/com/android/developers/androidify/watchface/transfer/WearDeviceRepository.kt b/watchface/src/main/java/com/android/developers/androidify/watchface/transfer/WearDeviceRepository.kt index 34af0219..62af682d 100644 --- a/watchface/src/main/java/com/android/developers/androidify/watchface/transfer/WearDeviceRepository.kt +++ b/watchface/src/main/java/com/android/developers/androidify/watchface/transfer/WearDeviceRepository.kt @@ -57,7 +57,7 @@ class WearDeviceRepositoryImpl @Inject constructor( val reachableCapability = capabilityClient.getCapability( ANDROIDIFY_INSTALLED, - CapabilityClient.FILTER_REACHABLE + CapabilityClient.FILTER_REACHABLE, ) .await() diff --git a/watchface/src/main/java/com/android/developers/androidify/watchface/transfer/WearableApiAvailability.kt b/watchface/src/main/java/com/android/developers/androidify/watchface/transfer/WearableApiAvailability.kt index 148f382f..8fcd7395 100644 --- a/watchface/src/main/java/com/android/developers/androidify/watchface/transfer/WearableApiAvailability.kt +++ b/watchface/src/main/java/com/android/developers/androidify/watchface/transfer/WearableApiAvailability.kt @@ -1,3 +1,18 @@ +/* + * 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.android.developers.androidify.watchface.transfer import android.util.Log @@ -27,4 +42,4 @@ object WearableApiAvailability { } val TAG = "WearableApiAvailability" -} \ No newline at end of file +} From 647898359d889d14499a5a5683ff0d361f38c858 Mon Sep 17 00:00:00 2001 From: garan Date: Tue, 16 Sep 2025 23:55:36 +0100 Subject: [PATCH 3/3] Addresses comments --- .../transfer/WearDeviceRepository.kt | 2 -- .../transfer/WearableApiAvailability.kt | 28 +++++++++---------- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/watchface/src/main/java/com/android/developers/androidify/watchface/transfer/WearDeviceRepository.kt b/watchface/src/main/java/com/android/developers/androidify/watchface/transfer/WearDeviceRepository.kt index 62af682d..fde3e4da 100644 --- a/watchface/src/main/java/com/android/developers/androidify/watchface/transfer/WearDeviceRepository.kt +++ b/watchface/src/main/java/com/android/developers/androidify/watchface/transfer/WearDeviceRepository.kt @@ -105,6 +105,4 @@ class WearDeviceRepositoryImpl @Inject constructor( null } } - - private val TAG = "WearDeviceRepository" } diff --git a/watchface/src/main/java/com/android/developers/androidify/watchface/transfer/WearableApiAvailability.kt b/watchface/src/main/java/com/android/developers/androidify/watchface/transfer/WearableApiAvailability.kt index 8fcd7395..1295b706 100644 --- a/watchface/src/main/java/com/android/developers/androidify/watchface/transfer/WearableApiAvailability.kt +++ b/watchface/src/main/java/com/android/developers/androidify/watchface/transfer/WearableApiAvailability.kt @@ -25,21 +25,19 @@ import kotlinx.coroutines.tasks.await * Checks whether a given Wearable Data Layer API is available on this device. */ object WearableApiAvailability { - suspend fun isAvailable(api: GoogleApi<*>): Boolean { - return try { - GoogleApiAvailability.getInstance() - .checkApiAvailability(api) - .await() + suspend fun isAvailable(api: GoogleApi<*>) = try { + GoogleApiAvailability.getInstance() + .checkApiAvailability(api) + .await() - true - } catch (e: AvailabilityException) { - Log.d( - TAG, - "${api.javaClass.simpleName} API is not available in this device.", - ) - false - } + true + } catch (e: AvailabilityException) { + Log.d( + TAG, + "${api.javaClass.simpleName} API is not available in this device.", + ) + false } - - val TAG = "WearableApiAvailability" } + +private const val TAG = "WearableApiAvailability"