Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve kdocs and samples of datalayer functions #1985

Merged
merged 2 commits into from
Jan 17, 2024
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
10 changes: 5 additions & 5 deletions datalayer/core/api/current.api
Original file line number Diff line number Diff line change
Expand Up @@ -145,12 +145,12 @@ package com.google.android.horologist.data.apphelper {
method protected final String getPlayStoreUri();
method protected final com.google.android.horologist.data.WearDataLayerRegistry getRegistry();
method protected final androidx.wear.remote.interactions.RemoteActivityHelper getRemoteActivityHelper();
method public abstract suspend Object? installOnNode(String node, kotlin.coroutines.Continuation<? super kotlin.Unit>);
method public abstract suspend Object? installOnNode(String nodeId, kotlin.coroutines.Continuation<? super kotlin.Unit>);
method public final suspend Object? isAvailable(kotlin.coroutines.Continuation<? super java.lang.Boolean>);
method @CheckResult protected final suspend Object? sendRequestWithTimeout(String node, String path, byte[] data, optional long timeoutMs, optional kotlin.coroutines.Continuation<? super error.NonExistentClass>);
method @CheckResult public abstract suspend Object? startCompanion(String node, kotlin.coroutines.Continuation<? super error.NonExistentClass>);
method @CheckResult public final suspend Object? startRemoteActivity(String node, error.NonExistentClass config, kotlin.coroutines.Continuation<? super error.NonExistentClass>);
method @CheckResult public final suspend Object? startRemoteOwnApp(String node, kotlin.coroutines.Continuation<? super error.NonExistentClass>);
method @CheckResult protected final suspend Object? sendRequestWithTimeout(String nodeId, String path, byte[] data, optional long timeoutMs, optional kotlin.coroutines.Continuation<? super error.NonExistentClass>);
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 protected final android.content.Context context;
property protected final String playStoreUri;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,45 +136,57 @@ abstract class DataLayerAppHelper(
/**
* Launches to the appropriate store on the specified node to allow installation of the app.
*
* @param node The node to launch on.
* @param nodeId The node to launch on.
*/

abstract suspend fun installOnNode(node: String)
abstract suspend fun installOnNode(nodeId: String)

/**
* Starts the companion that relates to the specified node. This will start on the phone,
* irrespective of whether the specified node is a phone or a watch.
*
* @param node The node to launch on.
* When called from a watch node, it is required that the same app is installed on the specified
* node, otherwise a [timeout](AppHelperResultCode.APP_HELPER_RESULT_TIMEOUT) is expected.
* See [AppHelperNodeStatus.appInstallationStatus] in order to check the installation status.
*
* @param nodeId The node to launch on.
* @return Whether launch was successful or not.
*/
@CheckResult
abstract suspend fun startCompanion(node: String): AppHelperResultCode
abstract suspend fun startCompanion(nodeId: String): AppHelperResultCode

/**
* Launch an activity, which belongs to the same app (same package name), on the specified node.
*
* [Class name][ActivityConfig.getClassFullName] should be a fully qualified class name, such
* as, "com.example.project.SampleActivity".
*
* This call requires that the same app is installed on the specified node, otherwise a
* [timeout](AppHelperResultCode.APP_HELPER_RESULT_TIMEOUT) is expected.
* See [AppHelperNodeStatus.appInstallationStatus] in order to check the installation status.
*/
@CheckResult
public suspend fun startRemoteActivity(
node: String,
nodeId: String,
config: ActivityConfig,
): AppHelperResultCode {
checkIsForegroundOrThrow()
val request = launchRequest { activity = config }
return sendRequestWithTimeout(node, LAUNCH_APP, request.toByteArray())
return sendRequestWithTimeout(nodeId, LAUNCH_APP, request.toByteArray())
}

/**
* Launch own app on the specified node.
*
* This call requires that the same app is installed on the specified node, otherwise a
* [timeout](AppHelperResultCode.APP_HELPER_RESULT_TIMEOUT) is expected.
* See [AppHelperNodeStatus.appInstallationStatus] in order to check the installation status.
*/
@CheckResult
public suspend fun startRemoteOwnApp(node: String): AppHelperResultCode {
public suspend fun startRemoteOwnApp(nodeId: String): AppHelperResultCode {
checkIsForegroundOrThrow()
val request = launchRequest { ownApp = ownAppConfig { } }
return sendRequestWithTimeout(node, LAUNCH_APP, request.toByteArray())
return sendRequestWithTimeout(nodeId, LAUNCH_APP, request.toByteArray())
}

/**
Expand All @@ -184,15 +196,15 @@ abstract class DataLayerAppHelper(
*/
@CheckResult
protected suspend fun sendRequestWithTimeout(
node: String,
nodeId: String,
path: String,
data: ByteArray,
timeoutMs: Long = MESSAGE_REQUEST_TIMEOUT_MS,
): AppHelperResultCode {
val response = try {
withTimeout(timeoutMs) {
// Cancellation will not lead to the GMS Task itself being cancelled.
registry.messageClient.sendRequest(node, path, data).await()
registry.messageClient.sendRequest(nodeId, path, data).await()
}
} catch (timeoutException: TimeoutCancellationException) {
return AppHelperResultCode.APP_HELPER_RESULT_TIMEOUT
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import kotlinx.coroutines.runBlocking
public abstract class DataLayerAppHelperService : WearableListenerService() {
public abstract val appHelper: DataLayerAppHelper

override fun onRequest(node: String, path: String, byteArray: ByteArray): Task<ByteArray> {
override fun onRequest(nodeId: String, path: String, byteArray: ByteArray): Task<ByteArray> {
if (path != DataLayerAppHelper.LAUNCH_APP) {
return Tasks.forResult(byteArrayForResultCode(AppHelperResultCode.APP_HELPER_RESULT_UNKNOWN_REQUEST))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public abstract class BaseGrpcDataService<T : BindableService> : WearDataService
)
}

override fun onRequest(node: String, path: String, data: ByteArray): Task<ByteArray>? {
override fun onRequest(nodeId: String, path: String, data: ByteArray): Task<ByteArray>? {
return rpcServer.handleIncomingMessage(data).asTask()
}

Expand Down
4 changes: 2 additions & 2 deletions datalayer/phone/api/current.api
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ 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 suspend Object? installOnNode(String node, kotlin.coroutines.Continuation<? super kotlin.Unit>);
method @CheckResult public suspend Object? startCompanion(String node, kotlin.coroutines.Continuation<? super com.google.android.horologist.data.AppHelperResultCode>);
method public suspend Object? installOnNode(String nodeId, kotlin.coroutines.Continuation<? super kotlin.Unit>);
method @CheckResult public suspend Object? startCompanion(String nodeId, kotlin.coroutines.Continuation<? super com.google.android.horologist.data.AppHelperResultCode>);
}

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 @@ -38,18 +38,18 @@ public class PhoneDataLayerAppHelper(
) : DataLayerAppHelper(context, registry) {
private val SAMSUNG_COMPANION_PKG = "com.samsung.android.app.watchmanager"

override suspend fun installOnNode(node: String) {
override suspend fun installOnNode(nodeId: String) {
checkIsForegroundOrThrow()
val intent = Intent(Intent.ACTION_VIEW)
.addCategory(Intent.CATEGORY_BROWSABLE)
.setData(Uri.parse(playStoreUri))
remoteActivityHelper.startRemoteActivity(intent, node).await()
remoteActivityHelper.startRemoteActivity(intent, nodeId).await()
}

@CheckResult
override suspend fun startCompanion(node: String): AppHelperResultCode {
override suspend fun startCompanion(nodeId: String): AppHelperResultCode {
checkIsForegroundOrThrow()
val companionPackage = registry.nodeClient.getCompanionPackageForNode(node).await()
val companionPackage = registry.nodeClient.getCompanionPackageForNode(nodeId).await()

/**
* Some devices report the wrong companion for actually launching the Companion app: For
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,10 @@ fun AppHelperNodeStatusCard(
onStartCompanionClick: (String) -> Unit,
onStartRemoteOwnAppClick: (String) -> Unit,
onStartRemoteActivityClick: (nodeId: String) -> Unit,
modifier: Modifier = Modifier,
) {
Box(
modifier = Modifier
modifier = modifier
.padding(16.dp)
.fillMaxWidth(),
contentAlignment = Alignment.Center,
Expand Down Expand Up @@ -121,17 +122,17 @@ fun AppHelperNodeStatusCard(
horizontalArrangement = Arrangement.SpaceBetween,
) {
Button(
modifier = Modifier.wrapContentHeight(),
onClick = { onStartCompanionClick(nodeStatus.id) },
modifier = Modifier.wrapContentHeight(),
) {
Text(
stringResource(id = R.string.node_status_start_companion_button_label),
textAlign = TextAlign.Center,
)
}
Button(
modifier = Modifier.wrapContentHeight().padding(start = 10.dp),
onClick = { onInstallOnNodeClick(nodeStatus.id) },
modifier = Modifier.wrapContentHeight().padding(start = 10.dp),
) {
Text(
stringResource(id = R.string.node_status_install_on_node_button_label),
Expand All @@ -146,19 +147,21 @@ fun AppHelperNodeStatusCard(
horizontalArrangement = Arrangement.SpaceBetween,
) {
Button(
modifier = Modifier.wrapContentHeight(),
onClick = { onStartRemoteOwnAppClick(nodeStatus.id) },
modifier = Modifier.wrapContentHeight(),
enabled = nodeStatus.appInstalled,
) {
Text(
stringResource(id = R.string.node_status_start_own_app_button_label),
textAlign = TextAlign.Center,
)
}
Button(
onClick = { onStartRemoteActivityClick(nodeStatus.id) },
modifier = Modifier
.wrapContentHeight()
.padding(start = 10.dp),
onClick = { onStartRemoteActivityClick(nodeStatus.id) },
enabled = nodeStatus.appInstalled,
) {
Text(
stringResource(id = R.string.node_status_start_remote_activity_button_label),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,15 @@ class NodesActionViewModel

fun onStartCompanionClick(nodeId: String) {
runActionAndHandleAppHelperResult {
phoneDataLayerAppHelper.startCompanion(node = nodeId)
phoneDataLayerAppHelper.startCompanion(nodeId = nodeId)
}
}

fun onInstallOnNodeClick(nodeId: String) {
_uiState.value = NodesScreenState.ActionRunning
viewModelScope.launch {
try {
phoneDataLayerAppHelper.installOnNode(node = nodeId)
phoneDataLayerAppHelper.installOnNode(nodeId = nodeId)

_uiState.value = NodesScreenState.ActionSucceeded
} catch (e: Exception) {
Expand All @@ -92,7 +92,7 @@ class NodesActionViewModel

fun onStartRemoteOwnAppClick(nodeId: String) {
runActionAndHandleAppHelperResult {
phoneDataLayerAppHelper.startRemoteOwnApp(node = nodeId)
phoneDataLayerAppHelper.startRemoteOwnApp(nodeId = nodeId)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ fun NodeDetailsScreen(

NodeDetailsScreen(
nodeId = viewModel.nodeId,
appInstalled = viewModel.appInstalled,
state = state,
onStartCompanionClick = viewModel::onStartCompanionClick,
onInstallOnNodeClick = viewModel::onInstallOnNodeClick,
Expand All @@ -73,6 +74,7 @@ fun NodeDetailsScreen(
@Composable
fun NodeDetailsScreen(
nodeId: String,
appInstalled: Boolean,
state: NodeDetailsScreenState,
onStartCompanionClick: () -> Unit,
onInstallOnNodeClick: () -> Unit,
Expand Down Expand Up @@ -108,6 +110,7 @@ fun NodeDetailsScreen(
Chip(
label = stringResource(id = R.string.node_details_start_companion_chip_label),
onClick = onStartCompanionClick,
enabled = appInstalled,
)
}
item {
Expand All @@ -120,12 +123,14 @@ fun NodeDetailsScreen(
Chip(
label = stringResource(id = R.string.node_details_start_remote_own_app_chip_label),
onClick = onStartRemoteOwnAppClick,
enabled = appInstalled,
)
}
item {
Chip(
label = stringResource(id = R.string.node_details_start_remote_activity_chip_label),
onClick = onStartRemoteActivityClick,
enabled = appInstalled,
)
}
}
Expand Down Expand Up @@ -213,6 +218,7 @@ fun NodeDetailsScreen(
fun NodeDetailsScreenPreview() {
NodeDetailsScreen(
nodeId = "12345",
appInstalled = true,
state = NodeDetailsScreenState.Idle,
onStartCompanionClick = { },
onInstallOnNodeClick = { },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,27 +29,34 @@ import java.net.URLDecoder
import java.net.URLEncoder

private const val nodeIdArg = "nodeId"
private const val appInstalledArg = "appInstalled"
private const val routePrefix = "appHelperNodeDetailsScreen"

private val URL_CHARACTER_ENCODING = Charsets.UTF_8.name()

const val nodeDetailsScreenRoute = "$routePrefix/{$nodeIdArg}"
const val nodeDetailsScreenRoute = "$routePrefix/$nodeIdArg={$nodeIdArg}&$appInstalledArg={$appInstalledArg}"

internal class NodeDetailsScreenArgs(val nodeId: String) {
constructor(savedStateHandle: SavedStateHandle) :
this(URLDecoder.decode(checkNotNull(savedStateHandle[nodeIdArg]), URL_CHARACTER_ENCODING))
internal class NodeDetailsScreenArgs(
val nodeId: String,
val appInstalled: Boolean,
) {
constructor(savedStateHandle: SavedStateHandle) : this(
URLDecoder.decode(checkNotNull(savedStateHandle[nodeIdArg]), URL_CHARACTER_ENCODING),
checkNotNull(savedStateHandle[appInstalledArg]),
)
}

fun NavController.navigateToNodeDetailsScreen(message: String) {
val encodedMessage = URLEncoder.encode(message, URL_CHARACTER_ENCODING)
this.navigate("$routePrefix/$encodedMessage")
fun NavController.navigateToNodeDetailsScreen(nodeId: String, appInstalled: Boolean) {
val encodedNodeId = URLEncoder.encode(nodeId, URL_CHARACTER_ENCODING)
this.navigate("$routePrefix/$nodeIdArg=$encodedNodeId&$appInstalledArg=$appInstalled")
}

fun NavGraphBuilder.nodeDetailsScreen() {
composable(
route = Screen.AppHelperNodeDetailsScreen.route,
arguments = listOf(
navArgument(nodeIdArg) { type = NavType.StringType },
navArgument(appInstalledArg) { type = NavType.BoolType },
),
) {
val columnState = rememberColumnState()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,20 +43,23 @@ class NodeDetailsViewModel
val nodeId: String
get() = nodeDetailsScreenArgs.nodeId

val appInstalled: Boolean
get() = nodeDetailsScreenArgs.appInstalled

private val _uiState = MutableStateFlow<NodeDetailsScreenState>(NodeDetailsScreenState.Idle)
public val uiState: StateFlow<NodeDetailsScreenState> = _uiState

fun onStartCompanionClick() {
runActionAndHandleAppHelperResult {
wearDataLayerAppHelper.startCompanion(node = nodeId)
wearDataLayerAppHelper.startCompanion(nodeId = nodeId)
}
}

fun onInstallOnNodeClick() {
_uiState.value = NodeDetailsScreenState.ActionRunning
viewModelScope.launch {
try {
wearDataLayerAppHelper.installOnNode(node = nodeId)
wearDataLayerAppHelper.installOnNode(nodeId = nodeId)

_uiState.value = NodeDetailsScreenState.ActionSucceeded
} catch (e: Exception) {
Expand All @@ -70,7 +73,7 @@ class NodeDetailsViewModel

fun onStartRemoteOwnAppClick() {
runActionAndHandleAppHelperResult {
wearDataLayerAppHelper.startRemoteOwnApp(node = nodeId)
wearDataLayerAppHelper.startRemoteOwnApp(nodeId = nodeId)
}
}

Expand Down
Loading