From f325b24666b200a5bc34d41c9b4e72148d9a2600 Mon Sep 17 00:00:00 2001 From: Paul Lammertsma Date: Fri, 31 Jul 2026 14:42:12 +0000 Subject: [PATCH] feat(ui): add form-factor detection utility and layout contract interfaces - Add FormFactorUtils to detect TV, Mobile, Wear, Auto, and XR devices. - Define compile-time layout contract interfaces (AgentDemoScreenLayout, DebuggingScreenLayout, SettingsScreenLayout, ConnectedAppsScreenLayout) to decouple screen routers from form-factor presentation. TAG=agy CONV=93e51fa6-7028-4707-8a32-f7af65eb2041 --- .../ui/contracts/AgentDemoScreenLayout.kt | 35 ++++++++++++++ .../ui/contracts/ConnectedAppsScreenLayout.kt | 34 ++++++++++++++ .../ui/contracts/DebuggingScreenLayout.kt | 44 +++++++++++++++++ .../ui/contracts/SettingsScreenLayout.kt | 37 +++++++++++++++ .../agent/ui/layout/FormFactorUtils.kt | 47 +++++++++++++++++++ 5 files changed, 197 insertions(+) create mode 100644 agent/app/src/main/java/com/example/appfunctions/agent/ui/contracts/AgentDemoScreenLayout.kt create mode 100644 agent/app/src/main/java/com/example/appfunctions/agent/ui/contracts/ConnectedAppsScreenLayout.kt create mode 100644 agent/app/src/main/java/com/example/appfunctions/agent/ui/contracts/DebuggingScreenLayout.kt create mode 100644 agent/app/src/main/java/com/example/appfunctions/agent/ui/contracts/SettingsScreenLayout.kt create mode 100644 agent/app/src/main/java/com/example/appfunctions/agent/ui/layout/FormFactorUtils.kt diff --git a/agent/app/src/main/java/com/example/appfunctions/agent/ui/contracts/AgentDemoScreenLayout.kt b/agent/app/src/main/java/com/example/appfunctions/agent/ui/contracts/AgentDemoScreenLayout.kt new file mode 100644 index 0000000..e222594 --- /dev/null +++ b/agent/app/src/main/java/com/example/appfunctions/agent/ui/contracts/AgentDemoScreenLayout.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2026 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.appfunctions.agent.ui.contracts + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.example.appfunctions.agent.ui.screens.agentdemo.AgentUiEvent +import com.example.appfunctions.agent.ui.screens.agentdemo.AgentUiState + +/** + * Mandatory interface contract for Agent Demo screen layouts. + * Both Mobile and TV implementations MUST conform to this contract. + */ +interface AgentDemoScreenLayout { + @Composable + fun Content( + uiState: AgentUiState, + onEvent: (AgentUiEvent) -> Unit, + initialSidePanelVisible: Boolean, + modifier: Modifier, + ) +} diff --git a/agent/app/src/main/java/com/example/appfunctions/agent/ui/contracts/ConnectedAppsScreenLayout.kt b/agent/app/src/main/java/com/example/appfunctions/agent/ui/contracts/ConnectedAppsScreenLayout.kt new file mode 100644 index 0000000..5f8b52d --- /dev/null +++ b/agent/app/src/main/java/com/example/appfunctions/agent/ui/contracts/ConnectedAppsScreenLayout.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2026 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.appfunctions.agent.ui.contracts + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.example.appfunctions.agent.ui.screens.agentdemo.ConnectedAppsUiState + +/** + * Mandatory interface contract for Connected Apps screen layouts. + * Both Mobile and TV implementations MUST conform to this contract. + */ +interface ConnectedAppsScreenLayout { + @Composable + fun Content( + uiState: ConnectedAppsUiState, + onBack: () -> Unit, + onToggleApp: (String, Boolean) -> Unit, + modifier: Modifier, + ) +} diff --git a/agent/app/src/main/java/com/example/appfunctions/agent/ui/contracts/DebuggingScreenLayout.kt b/agent/app/src/main/java/com/example/appfunctions/agent/ui/contracts/DebuggingScreenLayout.kt new file mode 100644 index 0000000..a21894b --- /dev/null +++ b/agent/app/src/main/java/com/example/appfunctions/agent/ui/contracts/DebuggingScreenLayout.kt @@ -0,0 +1,44 @@ +/* + * Copyright 2026 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.appfunctions.agent.ui.contracts + +import android.app.PendingIntent +import androidx.appfunctions.metadata.AppFunctionMetadata +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.example.appfunctions.agent.domain.appfunction.AppInfo +import com.example.appfunctions.agent.ui.screens.debugging.DebuggingUiState + +/** + * Mandatory interface contract for Debugging screen layouts. + * Both Mobile and TV implementations MUST conform to this contract. + */ +interface DebuggingScreenLayout { + @Composable + fun Content( + uiState: DebuggingUiState, + onSearchQueryChanged: (String) -> Unit, + onAppSelected: (AppInfo) -> Unit, + onClearSelectedApp: () -> Unit, + onFunctionInputsChange: (String, Map) -> Unit, + onInvoke: (AppFunctionMetadata) -> Unit, + onClearResult: () -> Unit, + onFunctionExpandedChange: (String, Boolean) -> Unit, + onLaunchPendingIntent: (PendingIntent) -> Unit, + onTogglePin: (AppInfo) -> Unit, + modifier: Modifier, + ) +} diff --git a/agent/app/src/main/java/com/example/appfunctions/agent/ui/contracts/SettingsScreenLayout.kt b/agent/app/src/main/java/com/example/appfunctions/agent/ui/contracts/SettingsScreenLayout.kt new file mode 100644 index 0000000..103c772 --- /dev/null +++ b/agent/app/src/main/java/com/example/appfunctions/agent/ui/contracts/SettingsScreenLayout.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2026 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.appfunctions.agent.ui.contracts + +import androidx.compose.foundation.text.input.TextFieldState +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.example.appfunctions.agent.data.ServiceTier + +/** + * Mandatory interface contract for Settings screen layouts. + * Both Mobile and TV implementations MUST conform to this contract. + */ +interface SettingsScreenLayout { + @Composable + fun Content( + geminiApiKeyState: TextFieldState, + serviceTier: ServiceTier, + onServiceTierSelected: (ServiceTier) -> Unit, + onOpenLicenses: () -> Unit, + onNavigateToConnectedApps: () -> Unit, + modifier: Modifier, + ) +} diff --git a/agent/app/src/main/java/com/example/appfunctions/agent/ui/layout/FormFactorUtils.kt b/agent/app/src/main/java/com/example/appfunctions/agent/ui/layout/FormFactorUtils.kt new file mode 100644 index 0000000..6f4cd21 --- /dev/null +++ b/agent/app/src/main/java/com/example/appfunctions/agent/ui/layout/FormFactorUtils.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2026 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.appfunctions.agent.ui.layout + +import android.content.pm.PackageManager +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalContext + +enum class FormFactor { + MOBILE, + TV, + WEAR, + AUTO, + XR, +} + +@Composable +fun rememberFormFactor(): FormFactor { + val context = LocalContext.current + return remember(context) { + val pm = context.packageManager + when { + pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK) -> FormFactor.TV + pm.hasSystemFeature(PackageManager.FEATURE_WATCH) -> FormFactor.WEAR + pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) -> FormFactor.AUTO + pm.hasSystemFeature("android.hardware.xr.display") -> FormFactor.XR + else -> FormFactor.MOBILE + } + } +} + +@Composable +fun isTvFormFactor(): Boolean = rememberFormFactor() == FormFactor.TV