From 4bbd268e160fb58565b13c1e8b6f4165de724c2e Mon Sep 17 00:00:00 2001 From: Gustavo Pagani Date: Wed, 22 Nov 2023 13:46:58 +0000 Subject: [PATCH] Improve AppHelperNodeStatus to make nodeType only available when app is installed (#1827) --- datalayer/core/api/current.api | 46 +++++++++++++------ .../data/apphelper/AppHelperNodeStatus.kt | 23 ++++++++-- .../data/apphelper/DataLayerAppHelper.kt | 18 +++++--- .../sample/AppHelperNodeStatusCard.kt | 24 ++++++---- .../datalayer/sample/MainActivity.kt | 11 +++-- .../phone/src/main/res/values/strings.xml | 3 +- docs/datalayer-helpers-guide.md | 3 +- 7 files changed, 88 insertions(+), 40 deletions(-) diff --git a/datalayer/core/api/current.api b/datalayer/core/api/current.api index 81a4e298ec..537bf8ec8c 100644 --- a/datalayer/core/api/current.api +++ b/datalayer/core/api/current.api @@ -93,31 +93,47 @@ package com.google.android.horologist.data { package com.google.android.horologist.data.apphelper { public final class AppHelperNodeStatus { - ctor public AppHelperNodeStatus(String id, String displayName, boolean isAppInstalled, com.google.android.horologist.data.apphelper.AppHelperNodeType nodeType, optional error.NonExistentClass surfacesInfo); + ctor public AppHelperNodeStatus(String id, String displayName, com.google.android.horologist.data.apphelper.AppInstallationStatus appInstallationStatus, optional error.NonExistentClass surfacesInfo); method public String component1(); method public String component2(); - method public boolean component3(); - method public com.google.android.horologist.data.apphelper.AppHelperNodeType component4(); - method public error.NonExistentClass! component5(); - method public com.google.android.horologist.data.apphelper.AppHelperNodeStatus copy(String id, String displayName, boolean isAppInstalled, com.google.android.horologist.data.apphelper.AppHelperNodeType nodeType, error.NonExistentClass! surfacesInfo); + method public com.google.android.horologist.data.apphelper.AppInstallationStatus component3(); + method public error.NonExistentClass! component4(); + method public com.google.android.horologist.data.apphelper.AppHelperNodeStatus copy(String id, String displayName, com.google.android.horologist.data.apphelper.AppInstallationStatus appInstallationStatus, error.NonExistentClass! surfacesInfo); + method public com.google.android.horologist.data.apphelper.AppInstallationStatus getAppInstallationStatus(); method public String getDisplayName(); method public String getId(); - method public com.google.android.horologist.data.apphelper.AppHelperNodeType getNodeType(); method public error.NonExistentClass! getSurfacesInfo(); - method public boolean isAppInstalled(); + property public final com.google.android.horologist.data.apphelper.AppInstallationStatus appInstallationStatus; property public final String displayName; property public final String id; - property public final boolean isAppInstalled; - property public final com.google.android.horologist.data.apphelper.AppHelperNodeType nodeType; property public final error.NonExistentClass! surfacesInfo; } - public enum AppHelperNodeType { - method public static com.google.android.horologist.data.apphelper.AppHelperNodeType valueOf(String name) throws java.lang.IllegalArgumentException; - method public static com.google.android.horologist.data.apphelper.AppHelperNodeType[] values(); - enum_constant public static final com.google.android.horologist.data.apphelper.AppHelperNodeType PHONE; - enum_constant public static final com.google.android.horologist.data.apphelper.AppHelperNodeType UNKNOWN; - enum_constant public static final com.google.android.horologist.data.apphelper.AppHelperNodeType WATCH; + public final class AppHelperNodeStatusKt { + method public static boolean getAppInstalled(com.google.android.horologist.data.apphelper.AppHelperNodeStatus); + } + + public abstract sealed class AppInstallationStatus { + } + + public static final class AppInstallationStatus.Installed extends com.google.android.horologist.data.apphelper.AppInstallationStatus { + ctor public AppInstallationStatus.Installed(com.google.android.horologist.data.apphelper.AppInstallationStatusNodeType nodeType); + method public com.google.android.horologist.data.apphelper.AppInstallationStatusNodeType component1(); + method public com.google.android.horologist.data.apphelper.AppInstallationStatus.Installed copy(com.google.android.horologist.data.apphelper.AppInstallationStatusNodeType nodeType); + method public com.google.android.horologist.data.apphelper.AppInstallationStatusNodeType getNodeType(); + property public final com.google.android.horologist.data.apphelper.AppInstallationStatusNodeType nodeType; + } + + public static final class AppInstallationStatus.NotInstalled extends com.google.android.horologist.data.apphelper.AppInstallationStatus { + field public static final com.google.android.horologist.data.apphelper.AppInstallationStatus.NotInstalled INSTANCE; + } + + public enum AppInstallationStatusNodeType { + method public static com.google.android.horologist.data.apphelper.AppInstallationStatusNodeType valueOf(String name) throws java.lang.IllegalArgumentException; + method public static com.google.android.horologist.data.apphelper.AppInstallationStatusNodeType[] values(); + enum_constant public static final com.google.android.horologist.data.apphelper.AppInstallationStatusNodeType PHONE; + enum_constant public static final com.google.android.horologist.data.apphelper.AppInstallationStatusNodeType UNKNOWN; + enum_constant public static final com.google.android.horologist.data.apphelper.AppInstallationStatusNodeType WATCH; } public abstract class DataLayerAppHelper { diff --git a/datalayer/core/src/main/java/com/google/android/horologist/data/apphelper/AppHelperNodeStatus.kt b/datalayer/core/src/main/java/com/google/android/horologist/data/apphelper/AppHelperNodeStatus.kt index 0dbbc54037..ea419f771f 100644 --- a/datalayer/core/src/main/java/com/google/android/horologist/data/apphelper/AppHelperNodeStatus.kt +++ b/datalayer/core/src/main/java/com/google/android/horologist/data/apphelper/AppHelperNodeStatus.kt @@ -25,13 +25,28 @@ import com.google.android.horologist.data.SurfacesInfo public data class AppHelperNodeStatus( val id: String, val displayName: String, - val isAppInstalled: Boolean, - val nodeType: AppHelperNodeType, + val appInstallationStatus: AppInstallationStatus, val surfacesInfo: SurfacesInfo = SurfacesInfo.getDefaultInstance(), ) -public enum class AppHelperNodeType { - UNKNOWN, +public sealed class AppInstallationStatus { + data object NotInstalled : AppInstallationStatus() + + data class Installed( + val nodeType: AppInstallationStatusNodeType, + ) : AppInstallationStatus() +} + +public enum class AppInstallationStatusNodeType { WATCH, PHONE, + + /** + * This case should not happen, but it's here in order to keep the node listed even in a + * scenario where there were issues retrieving the capability of the node. + */ + UNKNOWN, } + +public val AppHelperNodeStatus.appInstalled: Boolean + get() = this.appInstallationStatus is AppInstallationStatus.Installed diff --git a/datalayer/core/src/main/java/com/google/android/horologist/data/apphelper/DataLayerAppHelper.kt b/datalayer/core/src/main/java/com/google/android/horologist/data/apphelper/DataLayerAppHelper.kt index 119639682f..bedde32afd 100644 --- a/datalayer/core/src/main/java/com/google/android/horologist/data/apphelper/DataLayerAppHelper.kt +++ b/datalayer/core/src/main/java/com/google/android/horologist/data/apphelper/DataLayerAppHelper.kt @@ -76,16 +76,22 @@ abstract class DataLayerAppHelper( val allInstalledNodes = installedPhoneNodes + installedWatchNodes return nearbyNodes.map { + val appInstallationStatus = if (allInstalledNodes.contains(it.id)) { + val nodeType = when (it.id) { + in installedPhoneNodes -> AppInstallationStatusNodeType.PHONE + in installedWatchNodes -> AppInstallationStatusNodeType.WATCH + else -> AppInstallationStatusNodeType.UNKNOWN + } + AppInstallationStatus.Installed(nodeType = nodeType) + } else { + AppInstallationStatus.NotInstalled + } + AppHelperNodeStatus( id = it.id, displayName = it.displayName, - isAppInstalled = allInstalledNodes.contains(it.id), + appInstallationStatus = appInstallationStatus, surfacesInfo = getSurfaceStatus(it.id), - nodeType = when (it.id) { - in installedPhoneNodes -> AppHelperNodeType.PHONE - in installedWatchNodes -> AppHelperNodeType.WATCH - else -> AppHelperNodeType.UNKNOWN - }, ) } } diff --git a/datalayer/sample/phone/src/main/java/com/google/android/horologist/datalayer/sample/AppHelperNodeStatusCard.kt b/datalayer/sample/phone/src/main/java/com/google/android/horologist/datalayer/sample/AppHelperNodeStatusCard.kt index 7608333a13..4181f6aef8 100644 --- a/datalayer/sample/phone/src/main/java/com/google/android/horologist/datalayer/sample/AppHelperNodeStatusCard.kt +++ b/datalayer/sample/phone/src/main/java/com/google/android/horologist/datalayer/sample/AppHelperNodeStatusCard.kt @@ -35,7 +35,9 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.google.android.horologist.data.UsageStatus import com.google.android.horologist.data.apphelper.AppHelperNodeStatus -import com.google.android.horologist.data.apphelper.AppHelperNodeType +import com.google.android.horologist.data.apphelper.AppInstallationStatus +import com.google.android.horologist.data.apphelper.AppInstallationStatusNodeType +import com.google.android.horologist.data.apphelper.appInstalled import com.google.android.horologist.data.complicationInfo import com.google.android.horologist.data.surfacesInfo import com.google.android.horologist.data.tileInfo @@ -68,17 +70,22 @@ fun AppHelperNodeStatusCard( style = MaterialTheme.typography.labelMedium, text = stringResource(R.string.app_helper_node_id_label, nodeStatus.id), ) - Text( - style = MaterialTheme.typography.labelMedium, - text = stringResource(R.string.app_helper_node_type_label, nodeStatus.nodeType), - ) Text( style = MaterialTheme.typography.labelMedium, text = stringResource( R.string.app_helper_is_app_installed_label, - nodeStatus.isAppInstalled, + nodeStatus.appInstalled, ), ) + val nodeType = if (nodeStatus.appInstalled) { + (nodeStatus.appInstallationStatus as AppInstallationStatus.Installed).nodeType + } else { + stringResource(id = R.string.app_helper_node_type_unknown_label) + } + Text( + style = MaterialTheme.typography.labelMedium, + text = stringResource(R.string.app_helper_node_type_label, nodeType), + ) if (nodeStatus.surfacesInfo.complicationsList.isNotEmpty()) { Text( style = MaterialTheme.typography.labelMedium, @@ -140,8 +147,9 @@ fun NodeCardPreview() { val nodeStatus = AppHelperNodeStatus( displayName = "Pixel Watch", id = "a1b2c3", - isAppInstalled = true, - nodeType = AppHelperNodeType.WATCH, + appInstallationStatus = AppInstallationStatus.Installed( + nodeType = AppInstallationStatusNodeType.WATCH, + ), surfacesInfo = surfacesInfo { tiles.add( tileInfo { diff --git a/datalayer/sample/phone/src/main/java/com/google/android/horologist/datalayer/sample/MainActivity.kt b/datalayer/sample/phone/src/main/java/com/google/android/horologist/datalayer/sample/MainActivity.kt index 31de83258b..f3dd8950b6 100644 --- a/datalayer/sample/phone/src/main/java/com/google/android/horologist/datalayer/sample/MainActivity.kt +++ b/datalayer/sample/phone/src/main/java/com/google/android/horologist/datalayer/sample/MainActivity.kt @@ -51,7 +51,8 @@ import com.google.android.horologist.data.ProtoDataStoreHelper.protoDataStore import com.google.android.horologist.data.WearDataLayerRegistry import com.google.android.horologist.data.WearableApiAvailability import com.google.android.horologist.data.apphelper.AppHelperNodeStatus -import com.google.android.horologist.data.apphelper.AppHelperNodeType +import com.google.android.horologist.data.apphelper.AppInstallationStatus +import com.google.android.horologist.data.apphelper.AppInstallationStatusNodeType import com.google.android.horologist.data.complicationInfo import com.google.android.horologist.data.surfacesInfo import com.google.android.horologist.data.tileInfo @@ -96,7 +97,8 @@ class MainActivity : ComponentActivity() { LaunchedEffect(Unit) { coroutineScope.launch { apiAvailable = WearableApiAvailability.isAvailable(registry.dataClient) - nodeList = if (apiAvailable) phoneDataLayerAppHelper.connectedNodes() else listOf() + nodeList = + if (apiAvailable) phoneDataLayerAppHelper.connectedNodes() else listOf() } } @@ -202,8 +204,9 @@ fun MainPreview() { AppHelperNodeStatus( id = "a1b2c3d4", displayName = "Pixel Watch", - isAppInstalled = true, - nodeType = AppHelperNodeType.WATCH, + appInstallationStatus = AppInstallationStatus.Installed( + nodeType = AppInstallationStatusNodeType.WATCH, + ), surfacesInfo = surfacesInfo { tiles.add( tileInfo { diff --git a/datalayer/sample/phone/src/main/res/values/strings.xml b/datalayer/sample/phone/src/main/res/values/strings.xml index ef9e8a2f14..3c44acc461 100644 --- a/datalayer/sample/phone/src/main/res/values/strings.xml +++ b/datalayer/sample/phone/src/main/res/values/strings.xml @@ -19,8 +19,9 @@ This device does not have capability to communicate to Wearable Data Layer API Node: %1$s ID: %1$s - Type: %1$s App installed: %1$s + Type: %1$s + UNKNOWN (app not installed) Complications: %1$s Tiles: %1$s Usage: %1$s diff --git a/docs/datalayer-helpers-guide.md b/docs/datalayer-helpers-guide.md index 11be599bce..d7164518c1 100644 --- a/docs/datalayer-helpers-guide.md +++ b/docs/datalayer-helpers-guide.md @@ -50,8 +50,7 @@ phone. AppHelperNodeStatus( id=7cd1c38a, displayName=Google Pixel Watch, - isAppInstalled=true, - nodeType=WATCH, + appInstallationStatus=Installed(nodeType=WATCH), surfacesInfo=# SurfacesInfo@125fcbff complications { instance_id: 1234