Skip to content

Commit

Permalink
Improve connectedAndInstalledNodes to only query/listen to a single c…
Browse files Browse the repository at this point in the history
…apability (#2000)
  • Loading branch information
luizgrp authored Jan 23, 2024
1 parent 4409a2c commit 28d3d56
Show file tree
Hide file tree
Showing 7 changed files with 263 additions and 240 deletions.
5 changes: 3 additions & 2 deletions datalayer/core/api/current.api
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,9 @@ package com.google.android.horologist.data.apphelper {
public abstract class DataLayerAppHelper {
ctor public DataLayerAppHelper(android.content.Context context, com.google.android.horologist.data.WearDataLayerRegistry registry);
method protected final void checkIsForegroundOrThrow();
method protected final kotlinx.coroutines.flow.Flow<java.util.Set<com.google.android.gms.wearable.Node>> connectedAndInstalledNodes(String capability);
method public final suspend Object? connectedNodes(kotlin.coroutines.Continuation<? super java.util.List<? extends com.google.android.horologist.data.apphelper.AppHelperNodeStatus>>);
method public final kotlinx.coroutines.flow.Flow<java.util.Set<com.google.android.gms.wearable.Node>> getConnectedAndInstalledNodes();
method public abstract kotlinx.coroutines.flow.Flow<java.util.Set<com.google.android.gms.wearable.Node>> getConnectedAndInstalledNodes();
method protected final android.content.Context getContext();
method protected final String getPlayStoreUri();
method protected final com.google.android.horologist.data.WearDataLayerRegistry getRegistry();
Expand All @@ -151,7 +152,7 @@ package com.google.android.horologist.data.apphelper {
method @CheckResult public abstract suspend Object? startCompanion(String nodeId, kotlin.coroutines.Continuation<? super error.NonExistentClass>);
method @CheckResult public final suspend Object? startRemoteActivity(String nodeId, error.NonExistentClass config, kotlin.coroutines.Continuation<? super error.NonExistentClass>);
method @CheckResult public final suspend Object? startRemoteOwnApp(String nodeId, kotlin.coroutines.Continuation<? super error.NonExistentClass>);
property public final kotlinx.coroutines.flow.Flow<java.util.Set<com.google.android.gms.wearable.Node>> connectedAndInstalledNodes;
property public abstract kotlinx.coroutines.flow.Flow<java.util.Set<com.google.android.gms.wearable.Node>> connectedAndInstalledNodes;
property protected final android.content.Context context;
property protected final String playStoreUri;
property protected final com.google.android.horologist.data.WearDataLayerRegistry registry;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ package com.google.android.horologist.data.apphelper
import android.app.ActivityManager
import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
import android.content.Context
import android.net.Uri
import android.os.Process
import androidx.annotation.CheckResult
import androidx.wear.remote.interactions.RemoteActivityHelper
Expand All @@ -39,6 +38,7 @@ import com.google.android.horologist.data.launchRequest
import com.google.android.horologist.data.ownAppConfig
import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.tasks.await
Expand All @@ -56,8 +56,6 @@ abstract class DataLayerAppHelper(
protected val context: Context,
protected val registry: WearDataLayerRegistry,
) {
private val installedDeviceCapabilityUri: String = "wear://*/$CAPABILITY_DEVICE_PREFIX"

private val activityManager: ActivityManager by lazy { context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager }

protected val playStoreUri: String = "market://details?id=${context.packageName}"
Expand Down Expand Up @@ -106,32 +104,42 @@ abstract class DataLayerAppHelper(
/**
* Creates a flow to keep the client updated with the set of connected devices with the app
* installed.
*
* When called from a phone device, multiple watches can be connected to it.
*
* When called from a watch device, usually only a single phone device will be connected to it.
*/
public val connectedAndInstalledNodes = callbackFlow<Set<Node>> {
val listener: CapabilityClient.OnCapabilityChangedListener =
CapabilityClient.OnCapabilityChangedListener { capability ->
@Suppress("UNUSED_VARIABLE")
val unused =
trySend(capability.nodes.filter { it.isNearby }.toSet())
}
public abstract val connectedAndInstalledNodes: Flow<Set<Node>>

protected fun connectedAndInstalledNodes(capability: String) = callbackFlow<Set<Node>> {
suspend fun sendNearbyNodes() {
val capabilityInfo = registry.capabilityClient.getCapability(
capability,
CapabilityClient.FILTER_REACHABLE,
).await()

val nearbyNodes = capabilityInfo.nodes.filter { it.isNearby }.toSet()

val allCaps = registry.capabilityClient.getAllCapabilities(
CapabilityClient.FILTER_REACHABLE,
).await()
val installedCaps =
allCaps.filter { it.key.startsWith(CAPABILITY_DEVICE_PREFIX) }.values.flatMap { it.nodes }
.filter { it.isNearby }.toSet()
@Suppress("UNUSED_VARIABLE")
val unused = trySend(nearbyNodes)
}

suspend fun listenAndSendChanges() {
val listener: CapabilityClient.OnCapabilityChangedListener =
CapabilityClient.OnCapabilityChangedListener { capability ->
@Suppress("UNUSED_VARIABLE")
val unused = trySend(capability.nodes.filter { it.isNearby }.toSet())
}

@Suppress("UNUSED_VARIABLE")
val unused = trySend(installedCaps)
registry.capabilityClient.addListener(
listener,
Uri.parse(installedDeviceCapabilityUri),
CapabilityClient.FILTER_PREFIX,
)
awaitClose {
registry.capabilityClient.removeListener(listener)
registry.capabilityClient.addListener(listener, capability)
awaitClose {
registry.capabilityClient.removeListener(listener)
}
}

sendNearbyNodes()

listenAndSendChanges()
}

/**
Expand Down
2 changes: 2 additions & 0 deletions datalayer/phone/api/current.api
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ package com.google.android.horologist.datalayer.phone {

@com.google.android.horologist.annotations.ExperimentalHorologistApi public final class PhoneDataLayerAppHelper extends com.google.android.horologist.data.apphelper.DataLayerAppHelper {
ctor public PhoneDataLayerAppHelper(android.content.Context context, com.google.android.horologist.data.WearDataLayerRegistry registry);
method public kotlinx.coroutines.flow.Flow<java.util.Set<com.google.android.gms.wearable.Node>> getConnectedAndInstalledNodes();
method public suspend Object? installOnNode(String nodeId, kotlin.coroutines.Continuation<? super com.google.android.horologist.data.AppHelperResultCode>);
method @CheckResult public suspend Object? startCompanion(String nodeId, kotlin.coroutines.Continuation<? super com.google.android.horologist.data.AppHelperResultCode>);
property public kotlinx.coroutines.flow.Flow<java.util.Set<com.google.android.gms.wearable.Node>> connectedAndInstalledNodes;
}

public final class PhoneDataLayerListenerService extends com.google.android.horologist.data.apphelper.DataLayerAppHelperService {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,16 @@ import android.net.Uri
import androidx.annotation.CheckResult
import androidx.concurrent.futures.await
import androidx.wear.remote.interactions.RemoteActivityHelper
import com.google.android.gms.wearable.Node
import com.google.android.horologist.annotations.ExperimentalHorologistApi
import com.google.android.horologist.data.AppHelperResultCode
import com.google.android.horologist.data.WearDataLayerRegistry
import com.google.android.horologist.data.apphelper.DataLayerAppHelper
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.tasks.await

private const val SAMSUNG_COMPANION_PKG = "com.samsung.android.app.watchmanager"

/**
* Subclass of [DataLayerAppHelper] for use on phones.
*/
Expand All @@ -37,7 +41,9 @@ public class PhoneDataLayerAppHelper(
context: Context,
registry: WearDataLayerRegistry,
) : DataLayerAppHelper(context, registry) {
private val SAMSUNG_COMPANION_PKG = "com.samsung.android.app.watchmanager"

override val connectedAndInstalledNodes: Flow<Set<Node>>
get() = connectedAndInstalledNodes(WATCH_CAPABILITY)

override suspend fun installOnNode(nodeId: String): AppHelperResultCode {
checkIsForegroundOrThrow()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,11 @@ fun NodesListenerScreen(
}

is NodesListenerScreenState.Loaded -> {
item {
Text(stringResource(id = R.string.nodes_listener_screen_message))
}

if (state.nodeList.isNotEmpty()) {
item {
Text(stringResource(id = R.string.nodes_listener_screen_message))
}
items(state.nodeList.toList()) { node ->
Chip(
label = node.displayName,
Expand Down
2 changes: 2 additions & 0 deletions datalayer/watch/api/current.api
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.google.android.horologist.datalayer.watch {

@com.google.android.horologist.annotations.ExperimentalHorologistApi public final class WearDataLayerAppHelper extends com.google.android.horologist.data.apphelper.DataLayerAppHelper {
ctor public WearDataLayerAppHelper(android.content.Context context, com.google.android.horologist.data.WearDataLayerRegistry registry, kotlinx.coroutines.CoroutineScope scope, optional String? appStoreUri);
method public kotlinx.coroutines.flow.Flow<java.util.Set<com.google.android.gms.wearable.Node>> getConnectedAndInstalledNodes();
method public kotlinx.coroutines.flow.Flow<com.google.android.horologist.data.SurfacesInfo> getSurfacesInfo();
method public suspend Object? installOnNode(String nodeId, kotlin.coroutines.Continuation<? super com.google.android.horologist.data.AppHelperResultCode>);
method public suspend Object? markActivityLaunchedOnce(kotlin.coroutines.Continuation<? super kotlin.Unit>);
Expand All @@ -13,6 +14,7 @@ package com.google.android.horologist.datalayer.watch {
method public suspend Object? markTileAsInstalled(String tileName, kotlin.coroutines.Continuation<? super kotlin.Unit>);
method public suspend Object? markTileAsRemoved(String tileName, kotlin.coroutines.Continuation<? super kotlin.Unit>);
method @CheckResult public suspend Object? startCompanion(String nodeId, kotlin.coroutines.Continuation<? super com.google.android.horologist.data.AppHelperResultCode>);
property public kotlinx.coroutines.flow.Flow<java.util.Set<com.google.android.gms.wearable.Node>> connectedAndInstalledNodes;
property public final kotlinx.coroutines.flow.Flow<com.google.android.horologist.data.SurfacesInfo> surfacesInfo;
}

Expand Down
Loading

0 comments on commit 28d3d56

Please sign in to comment.