diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1 @@ + diff --git a/README.md b/README.md index 3fb7ba43..1ed10772 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ - -## Android Key Attestation - -Android Key Attestation is a security feature that allows you to verify that a -cryptographic key was generated in a hardware-protected environment on an -Android device. This can be used to ensure that the key is not vulnerable to -attack (since it is not possible to extract a key) from malicious software -running on the device. - -To use Key Attestation, you first need to generate a key pair in the Android -Keystore. The key pair must be generated in the Trusted Environment, which is a -secure hardware module that is isolated from the rest of the device. - -While generating a keypair, you can request an attestation certificate from the -Android Keystore. The attestation certificate contains information about the key -pair, including the key identifier, the key algorithm, and the security level of -the Trusted Environment. - -You can then send the attestation certificate to a server that you trust. The -server can use the attestation certificate to verify that the key pair was -generated in the Trusted Environment of a certified Android device and that the -key is not vulnerable to attack. - -Here are the steps involved in key attestation: - -1. The app obtains a unique attestation challenge from the server (to prevent - replay attack) -2. The app requests a key pair (with attestation) from the Android Keystore. -3. The Android Keystore generates the key pair and attestation cert in the - Trusted Environment. -4. The app extracts the attestation certificate from the Android Keystore. -5. The app sends the attestation certificate to a trusted server. -6. The trusted server verifies the attestation certificate and confirms that - the key pair was generated in the Trusted Environment. - -Private Compute Services makes use of key attestation to guarantee security -before performing sensitive operations, such as uploading aggregated metrics or -downloading sensitive models. A trusted Google server is used in validating -attestation records from Private Compute Services. This server makes use of the -https://github.com/google/android-key-attestation library, for validating the -records, and during the validation process, Private Compute Services does not -upload any PII or device identifier besides the unique key generated by the -device. No data is persisted by the Google servers for longer than the period -required to verify the attestation record. - -For more information about key attestation, take a look at -https://developer.android.com/training/articles/security-key-attestation diff --git a/src/com/google/android/as/oss/attestation/api/BUILD b/src/com/google/android/as/oss/attestation/api/BUILD index f9913cc8..d917c2fb 100644 --- a/src/com/google/android/as/oss/attestation/api/BUILD +++ b/src/com/google/android/as/oss/attestation/api/BUILD @@ -1,4 +1,4 @@ -# Copyright 2024 Google LLC +# Copyright 2025 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/com/google/android/as/oss/attestation/api/attestation.proto b/src/com/google/android/as/oss/attestation/api/attestation.proto index 8eebf815..cf4529d3 100644 --- a/src/com/google/android/as/oss/attestation/api/attestation.proto +++ b/src/com/google/android/as/oss/attestation/api/attestation.proto @@ -1,4 +1,4 @@ -// Copyright 2024 Google LLC +// Copyright 2025 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/src/com/google/android/as/oss/attestation/config/BUILD b/src/com/google/android/as/oss/attestation/config/BUILD index 072a5d5d..74e75b6a 100644 --- a/src/com/google/android/as/oss/attestation/config/BUILD +++ b/src/com/google/android/as/oss/attestation/config/BUILD @@ -1,4 +1,4 @@ -# Copyright 2024 Google LLC +# Copyright 2025 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/src/com/google/android/as/oss/attestation/config/PcsAttestationMeasurementConfig.java b/src/com/google/android/as/oss/attestation/config/PcsAttestationMeasurementConfig.java index c54c55f3..3dc9adf5 100644 --- a/src/com/google/android/as/oss/attestation/config/PcsAttestationMeasurementConfig.java +++ b/src/com/google/android/as/oss/attestation/config/PcsAttestationMeasurementConfig.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 Google LLC + * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/com/google/android/as/oss/attestation/config/impl/BUILD b/src/com/google/android/as/oss/attestation/config/impl/BUILD index 988c44c4..b256a9dc 100644 --- a/src/com/google/android/as/oss/attestation/config/impl/BUILD +++ b/src/com/google/android/as/oss/attestation/config/impl/BUILD @@ -1,4 +1,4 @@ -# Copyright 2024 Google LLC +# Copyright 2025 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -25,7 +25,7 @@ android_library( "//src/com/google/android/as/oss/attestation/config", "//src/com/google/android/as/oss/common:annotation", "//src/com/google/android/as/oss/common/config", - "//third_party/java/hilt:hilt-android", "@maven//:com_google_dagger_dagger", + "@maven//:com_google_dagger_hilt-android", ], ) diff --git a/src/com/google/android/as/oss/attestation/config/impl/PcsAttestationMeasurementConfigModule.java b/src/com/google/android/as/oss/attestation/config/impl/PcsAttestationMeasurementConfigModule.java index 4f2b5b93..b7d1bd5f 100644 --- a/src/com/google/android/as/oss/attestation/config/impl/PcsAttestationMeasurementConfigModule.java +++ b/src/com/google/android/as/oss/attestation/config/impl/PcsAttestationMeasurementConfigModule.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 Google LLC + * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/com/google/android/as/oss/attestation/config/impl/PcsAttestationMeasurementConfigReader.java b/src/com/google/android/as/oss/attestation/config/impl/PcsAttestationMeasurementConfigReader.java index 6d3868e2..715b534f 100644 --- a/src/com/google/android/as/oss/attestation/config/impl/PcsAttestationMeasurementConfigReader.java +++ b/src/com/google/android/as/oss/attestation/config/impl/PcsAttestationMeasurementConfigReader.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 Google LLC + * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/com/google/android/as/oss/attestation/impl/BUILD b/src/com/google/android/as/oss/attestation/impl/BUILD new file mode 100644 index 00000000..4d62c193 --- /dev/null +++ b/src/com/google/android/as/oss/attestation/impl/BUILD @@ -0,0 +1,68 @@ +# Copyright 2025 Google LLC +# +# 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 +# +# http://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. + +load("@bazel_rules_android//android:rules.bzl", "android_library") + +package(default_visibility = [ + "//visibility:public", +]) + +android_library( + name = "impl", + srcs = [ + "PccAttestationMeasurementClientImpl.java", + ], + deps = [ + "//google/internal/android/keyattestation/v1:key_attestation_service_java_proto_lite", + "//google/internal/android/keyattestation/v1:key_attestation_service_lite_grpc", + "//java/com/google/common/time:time-android", + "//java/com/google/protobuf/util:javatime_lite", + "//src/com/google/android/as/oss/attestation", + "//src/com/google/android/as/oss/attestation/api:attestation_java_proto_lite", + "//src/com/google/android/as/oss/common/time", + "//src/com/google/android/as/oss/logging:api", + "//src/com/google/android/as/oss/logging:atoms_java_proto_lite", + "//src/com/google/android/as/oss/logging:enums_java_proto_lite", + "//src/com/google/android/as/oss/networkusage/db", + "//src/com/google/android/as/oss/networkusage/db:repository", + "@maven//:com_google_flogger_google_extensions", + "@maven//:com_google_guava_guava", + "@maven//:com_google_protobuf_protobuf_javalite", + "@maven//:io_grpc_grpc_api", + ], +) + +android_library( + name = "impl_module", + srcs = [ + "PccAttestationMeasurementClientModule.java", + ], + deps = [ + ":impl", + "//src/com/google/android/as/oss/attestation", + "//src/com/google/android/as/oss/attestation/config", + "//src/com/google/android/as/oss/common", + "//src/com/google/android/as/oss/common:annotation", + "//src/com/google/android/as/oss/common/config", + "//src/com/google/android/as/oss/common/time", + "//src/com/google/android/as/oss/logging:api", + "//src/com/google/android/as/oss/networkusage/db:repository", + "//third_party/java/grpc:okhttp_android", + "@maven//:com_google_dagger_dagger", + "@maven//:com_google_dagger_hilt-android", + "@maven//:io_grpc_grpc_api", + "@maven//:io_grpc_grpc_stub", + "@maven//:javax_inject_javax_inject", + ], +) diff --git a/src/com/google/android/as/oss/attestation/impl/PccAttestationMeasurementClientImpl.java b/src/com/google/android/as/oss/attestation/impl/PccAttestationMeasurementClientImpl.java index 0c4e0a2c..037326fb 100644 --- a/src/com/google/android/as/oss/attestation/impl/PccAttestationMeasurementClientImpl.java +++ b/src/com/google/android/as/oss/attestation/impl/PccAttestationMeasurementClientImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 Google LLC + * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/com/google/android/as/oss/attestation/impl/PccAttestationMeasurementClientModule.java b/src/com/google/android/as/oss/attestation/impl/PccAttestationMeasurementClientModule.java index b93097c5..59d67ff1 100644 --- a/src/com/google/android/as/oss/attestation/impl/PccAttestationMeasurementClientModule.java +++ b/src/com/google/android/as/oss/attestation/impl/PccAttestationMeasurementClientModule.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 Google LLC + * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/com/google/android/as/oss/attestation/jobs/AttestationJobSchedulerModule.java b/src/com/google/android/as/oss/attestation/jobs/AttestationJobSchedulerModule.java index c132cbb1..7bf04285 100644 --- a/src/com/google/android/as/oss/attestation/jobs/AttestationJobSchedulerModule.java +++ b/src/com/google/android/as/oss/attestation/jobs/AttestationJobSchedulerModule.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 Google LLC + * Copyright 2025 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/com/google/android/as/oss/attestation/jobs/BUILD b/src/com/google/android/as/oss/attestation/jobs/BUILD new file mode 100644 index 00000000..168f1eb0 --- /dev/null +++ b/src/com/google/android/as/oss/attestation/jobs/BUILD @@ -0,0 +1,34 @@ +# Copyright 2025 Google LLC +# +# 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 +# +# http://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. + +load("@bazel_rules_android//android:rules.bzl", "android_library") + +package(default_visibility = ["//visibility:public"]) + +android_library( + name = "jobs", + srcs = [ + "AttestationJobSchedulerModule.java", + ], + deps = [ + "//src/com/google/android/as/oss/attestation/config", + "//src/com/google/android/as/oss/common/config", + "//src/com/google/android/as/oss/common/flavor", + "//src/com/google/android/as/oss/fl/api:training_java_proto_lite", + "//src/com/google/android/as/oss/fl/federatedcompute/training", + "//src/com/google/android/as/oss/fl/populations", + "@maven//:com_google_dagger_dagger", + "@maven//:com_google_dagger_hilt-android", + ], +) diff --git a/src/com/google/android/as/oss/common/AndroidManifest.xml b/src/com/google/android/as/oss/common/AndroidManifest.xml index 8cd04453..9c0b5f33 100644 --- a/src/com/google/android/as/oss/common/AndroidManifest.xml +++ b/src/com/google/android/as/oss/common/AndroidManifest.xml @@ -1,6 +1,6 @@ + + + + + + + + + + + + + + + diff --git a/src/com/google/android/as/oss/assets/README.md b/src/com/google/android/as/oss/dataattribution/AndroidManifest_api.xml similarity index 64% rename from src/com/google/android/as/oss/assets/README.md rename to src/com/google/android/as/oss/dataattribution/AndroidManifest_api.xml index 4cd9bb77..9b614398 100644 --- a/src/com/google/android/as/oss/assets/README.md +++ b/src/com/google/android/as/oss/dataattribution/AndroidManifest_api.xml @@ -1,5 +1,6 @@ + -The proto files in this directory correspond to policies enforced on federated -computation facilitated by Private Compute Services. These policies follow the -proto structure defined at -https://github.com/PolymerLabs/arcs/blob/master/java/arcs/core/data/proto/manifest.proto. + + + + + + diff --git a/src/com/google/android/as/oss/dataattribution/BUILD b/src/com/google/android/as/oss/dataattribution/BUILD new file mode 100644 index 00000000..6a8bc628 --- /dev/null +++ b/src/com/google/android/as/oss/dataattribution/BUILD @@ -0,0 +1,104 @@ +# Copyright 2025 Google LLC +# +# 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 +# +# http://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. + +load("@bazel_rules_android//android:rules.bzl", "android_library") + +package(default_visibility = ["//visibility:public"]) + +package_group( + name = "attribution_data_visibility", + includes = ["//visibility:public"], +) + +android_library( + name = "dataattribution", + srcs = [ + "DataAttributionApi.kt", + ], + exports_manifest = True, + manifest = "AndroidManifest_api.xml", + visibility = [":attribution_data_visibility"], + deps = [ + "//src/com/google/android/as/oss/dataattribution/proto:attribution_chip_header_kt_proto_lite", + "//src/com/google/android/as/oss/dataattribution/proto:attribution_dialog_kt_proto_lite", + "//third_party/java/androidx/appcompat", + ], +) + +android_library( + name = "prod_modules", + exports_manifest = True, + manifest = "AndroidManifest.xml", + tags = [ + "keep_dep", + ], + exports = [ + ":data_attribution_activity", + ], +) + +android_library( + name = "data_attribution_activity", + srcs = [ + "DataAttributionActivity.kt", + "DataAttributionActivityViewModel.kt", + "DialogAnchor.kt", + ], + exports_manifest = True, + manifest = "AndroidManifest.xml", + resource_files = glob(["res/**"]), + deps = [ + ":data_attribution_components", + ":dataattribution", + "//src/com/google/android/as/oss/dataattribution/proto:attribution_chip_header_kt_proto_lite", + "//src/com/google/android/as/oss/dataattribution/proto:attribution_dialog_kt_proto_lite", + "//third_party/java/android_libs/material_components:dialog", + "//third_party/java/androidx/activity/compose", + "//third_party/java/androidx/appcompat", + "//third_party/java/androidx/compose/foundation/layout", + "//third_party/java/androidx/compose/runtime", + "@maven//:com_google_dagger_dagger", + "@maven//:com_google_flogger_google_extensions", + ], +) + +android_library( + name = "data_attribution_components", + srcs = ["DataAttributionDialog.kt"], + exports_manifest = True, + manifest = "AndroidManifest.xml", + resource_files = glob(["res/**"]), + deps = [ + "//src/com/google/android/as/oss/dataattribution/proto:attribution_chip_header_kt_proto_lite", + "//src/com/google/android/as/oss/dataattribution/proto:attribution_dialog_kt_proto_lite", + "//src/com/google/android/as/oss/delegatedui/service/templates/fonts:flex_fonts", + "//src/com/google/android/as/oss/delegatedui/utils:serializable_bitmap", + "//src/com/google/android/as/oss/delegatedui/utils:tintable_icon", + "//src/com/google/android/as/oss/feedback", + "//src/com/google/android/as/oss/feedback/proto:entity_feedback_dialog_data_kt_proto_lite", + "//src/com/google/android/as/oss/feedback/proto:feedback_tag_data_kt_proto_lite", + "//third_party/java/android/android_sdk_linux/platforms/stable:android_platform_feature_flags", + "//third_party/java/android_libs/material_components:dialog", + "//third_party/java/androidx/activity/compose", + "//third_party/java/androidx/appcompat", + "//third_party/java/androidx/compose/foundation", + "//third_party/java/androidx/compose/foundation/layout", + "//third_party/java/androidx/compose/material3", + "//third_party/java/androidx/compose/runtime", + "//third_party/java/androidx/compose/ui", + "//third_party/java/androidx/compose/ui/graphics", + "//third_party/java/androidx/compose/ui/text", + "//third_party/java/androidx/compose/ui/unit", + ], +) diff --git a/src/com/google/android/as/oss/dataattribution/DataAttributionActivity.kt b/src/com/google/android/as/oss/dataattribution/DataAttributionActivity.kt new file mode 100644 index 00000000..222ebb3d --- /dev/null +++ b/src/com/google/android/as/oss/dataattribution/DataAttributionActivity.kt @@ -0,0 +1,111 @@ +/* + * Copyright 2025 Google LLC + * + * 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 + * + * http://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.google.android.`as`.oss.dataattribution + +import android.app.PendingIntent +import android.os.Bundle +import android.view.Gravity +import android.view.View +import androidx.activity.ComponentActivity +import androidx.activity.viewModels +import androidx.compose.ui.platform.ComposeView +import com.google.android.`as`.oss.dataattribution.DataAttributionApi.EXTRA_ATTRIBUTION_CHIP_DATA_PROTO +import com.google.android.`as`.oss.dataattribution.DataAttributionApi.EXTRA_ATTRIBUTION_DIALOG_DATA_PROTO +import com.google.android.`as`.oss.dataattribution.DataAttributionApi.EXTRA_ATTRIBUTION_SOURCE_DEEP_LINKS +import com.google.android.`as`.oss.dataattribution.proto.AttributionChipData +import com.google.android.`as`.oss.dataattribution.proto.AttributionDialogData +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.google.common.flogger.GoogleLogger +import com.google.common.flogger.StackSize +import dagger.hilt.android.AndroidEntryPoint + +/** An activity to host UI components when a user long clicks Delegated UI. */ +@AndroidEntryPoint(ComponentActivity::class) +class DataAttributionActivity : Hilt_DataAttributionActivity() { + + private val viewModel: DataAttributionActivityViewModel by viewModels() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + try { + val attributionDialogData = + intent.getByteArrayExtra(EXTRA_ATTRIBUTION_DIALOG_DATA_PROTO)!!.let { + AttributionDialogData.parseFrom(it) + } + val attributionChipData = + intent.getByteArrayExtra(EXTRA_ATTRIBUTION_CHIP_DATA_PROTO)?.let { + AttributionChipData.parseFrom(it) + } + val sourceDeepLinks = + intent.getParcelableArrayExtra( + EXTRA_ATTRIBUTION_SOURCE_DEEP_LINKS, + PendingIntent::class.java, + ) as Array? + + val view = createView(attributionDialogData, attributionChipData, sourceDeepLinks) + showDialog(view) + } catch (e: Exception) { + logger + .atSevere() + .withCause(e) + .withStackTrace(StackSize.SMALL) + .log("DataAttributionActivity.onCreate() failed. Finishing activity.") + finish() + return + } + } + + private fun createView( + attributionDialogData: AttributionDialogData, + attributionChipData: AttributionChipData?, + sourceDeepLinks: Array?, + ) = + ComposeView(this).apply { + setContent { + DataAttributionDialog( + attributionDialogData = attributionDialogData, + attributionChipData = attributionChipData, + sourceDeepLinks = sourceDeepLinks, + onDismissRequest = { finish() }, + ) + } + } + + private fun showDialog(view: View) { + val dialogGravity = viewModel.getDialogGravity(window) + val dialog = + // Use an AlertDialog to keep the keyboard visible when the activity is launched. + MaterialAlertDialogBuilder(this, R.style.Theme_DataAttribution_Dialog) + .apply { + setView(view) + setOnDismissListener { finish() } + if (dialogGravity is DialogGravity.AboveIme) { + setBackgroundInsetTop(0) + setBackgroundInsetBottom(dialogGravity.backgroundInsetBottomPx) + } + } + .create() + val gravity = if (dialogGravity is DialogGravity.AboveIme) Gravity.BOTTOM else Gravity.CENTER + dialog.window?.setGravity(gravity) + dialog.show() + } + + companion object { + private val logger = GoogleLogger.forEnclosingClass() + } +} diff --git a/src/com/google/android/as/oss/dataattribution/DataAttributionActivityViewModel.kt b/src/com/google/android/as/oss/dataattribution/DataAttributionActivityViewModel.kt new file mode 100644 index 00000000..22ffb65d --- /dev/null +++ b/src/com/google/android/as/oss/dataattribution/DataAttributionActivityViewModel.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2025 Google LLC + * + * 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 + * + * http://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.google.android.`as`.oss.dataattribution + +import android.view.Window +import androidx.lifecycle.ViewModel + +/** + * A [ViewModel] for the DataAttributionActivity. + * + * This class provides a single point of access to the [DialogAnchor] for determining the + * appropriate gravity for a dialog. + */ +class DataAttributionActivityViewModel : ViewModel() { + private val dialogAnchor = DialogAnchor() + + /** + * Determines the appropriate gravity for a dialog, attempting to position it above the Input + * Method Editor (IME) if applicable and visible in portrait mode. + * + * @param window The [Window] to use for determining the dialog gravity. + * @return [DialogGravity] indicating the suggested placement. + */ + fun getDialogGravity(window: Window) = dialogAnchor.getDialogGravity(window) +} diff --git a/src/com/google/android/as/oss/dataattribution/DataAttributionApi.kt b/src/com/google/android/as/oss/dataattribution/DataAttributionApi.kt new file mode 100644 index 00000000..13c359cf --- /dev/null +++ b/src/com/google/android/as/oss/dataattribution/DataAttributionApi.kt @@ -0,0 +1,84 @@ +/* + * Copyright 2025 Google LLC + * + * 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 + * + * http://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.google.android.`as`.oss.dataattribution + +import android.app.PendingIntent +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK +import android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS +import android.content.Intent.FLAG_ACTIVITY_NEW_TASK +import com.google.android.`as`.oss.dataattribution.proto.AttributionChipData +import com.google.android.`as`.oss.dataattribution.proto.AttributionDialogData + +/** The public API for showing the DataAttributionActivity. */ +object DataAttributionApi { + + /** Key for the AttributionDialogData proto extra in the Intent bundle. */ + internal const val EXTRA_ATTRIBUTION_DIALOG_DATA_PROTO = "attribution_dialog_data_proto" + /** Key for the AttributionChipData proto extra in the Intent bundle. */ + internal const val EXTRA_ATTRIBUTION_CHIP_DATA_PROTO = "attribution_chip_data_proto" + /** Key for the source deep links in the Intent bundle. */ + internal const val EXTRA_ATTRIBUTION_SOURCE_DEEP_LINKS = "attribution_source_deep_links" + + private const val PCS_PKG_NAME: String = "com.google.android.as.oss" + + private const val DATA_ATTRIBUTION_ACTIVITY_NAME: String = + "com.google.android.as.oss.dataattribution.DataAttributionActivity" + + /** + * Creates an [Intent] that starts [DataAttributionActivity] with the correct parameters. + * + * @param attributionDialogData The [AttributionDialogData] used to render the attribution dialog. + * @param attributionChipData An optional [AttributionChipData] that, if provided, will be used to + * render a chip that represents the data that will be egressed on click. Providing this field + * also enables the feedback entry-point from attributions. + * @param sourceDeepLinks An optional array of deep links for each AttributionCardData in + * [attributionDialogData]. If provided, each non-null deep link will be used to make the + * [AttributionCard] clickable. + */ + fun createDataAttributionIntent( + context: Context, + attributionDialogData: AttributionDialogData, + attributionChipData: AttributionChipData?, + sourceDeepLinks: Array?, + ): Intent { + require( + sourceDeepLinks == null || sourceDeepLinks.size == attributionDialogData.attributionsCount + ) { + "You must provide an array whose size equals [attributionDialogData.attributionsCount]." + } + + val intent = + Intent(Intent.ACTION_MAIN).apply { + setComponent(ComponentName(PCS_PKG_NAME, DATA_ATTRIBUTION_ACTIVITY_NAME)) + // FLAG_ACTIVITY_NEW_TASK: Needed to start an Activity outside of an Activity context. + // FLAG_ACTIVITY_CLEAR_TASK: Fix for old Activity being reused. + // FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS: Ensure the Activity launched doesn't show up as a + // separate task when a user opens recents. + setFlags( + FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK or FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS + ) + putExtra(EXTRA_ATTRIBUTION_DIALOG_DATA_PROTO, attributionDialogData.toByteArray()) + attributionChipData?.let { putExtra(EXTRA_ATTRIBUTION_CHIP_DATA_PROTO, it.toByteArray()) } + sourceDeepLinks?.let { putExtra(EXTRA_ATTRIBUTION_SOURCE_DEEP_LINKS, it) } + } + + return intent + } +} diff --git a/src/com/google/android/as/oss/dataattribution/DataAttributionDialog.kt b/src/com/google/android/as/oss/dataattribution/DataAttributionDialog.kt new file mode 100644 index 00000000..d213c1ab --- /dev/null +++ b/src/com/google/android/as/oss/dataattribution/DataAttributionDialog.kt @@ -0,0 +1,458 @@ +/* + * Copyright 2025 Google LLC + * + * 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 + * + * http://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. + */ + +@file:OptIn(ExperimentalMaterial3ExpressiveApi::class) + +package com.google.android.`as`.oss.dataattribution + +import android.app.ActivityOptions +import android.app.PendingIntent +import android.content.Intent +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.sizeIn +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyItemScope +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.Typography +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.role +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.semantics.traversalIndex +import androidx.compose.ui.unit.dp +import com.android.window.flags.ExportedFlags.balAdditionalStartModes +import com.google.android.`as`.oss.dataattribution.proto.AttributionCardData +import com.google.android.`as`.oss.dataattribution.proto.AttributionChipData +import com.google.android.`as`.oss.dataattribution.proto.AttributionDialogData +import com.google.android.`as`.oss.delegatedui.service.templates.fonts.FlexFontUtils.withFlexFont +import com.google.android.`as`.oss.delegatedui.utils.IconOrImage +import com.google.android.`as`.oss.delegatedui.utils.SerializableBitmap.deserializeToBitmap +import com.google.android.`as`.oss.delegatedui.utils.asTintableIcon +import com.google.android.`as`.oss.feedback.FeedbackApi +import com.google.android.`as`.oss.feedback.api.FeedbackRatingSentiment +import com.google.android.`as`.oss.feedback.api.entityFeedbackDialogData + +/** Dialog displayed when a user long clicks Delegated UI. */ +@Composable +fun DataAttributionDialog( + attributionDialogData: AttributionDialogData, + attributionChipData: AttributionChipData?, + sourceDeepLinks: Array?, + onDismissRequest: () -> Unit, +) { + DataAttributionScaffold( + data = attributionDialogData, + chipContent = { if (attributionChipData != null) AttributionChip(attributionChipData) }, + cardContent = { i, cardData -> + AttributionCard( + data = cardData, + deepLink = sourceDeepLinks?.getOrNull(i), + onDismissRequest = onDismissRequest, + ) + }, + footerContent = { + DataAttributionDialogFooter( + data = attributionDialogData, + feedbackSessionId = attributionDialogData.sessionId, + feedbackEntityContent = attributionChipData?.chipLabel, + onDismissRequest = onDismissRequest, + ) + }, + onDismissRequest = onDismissRequest, + ) +} + +@Composable +private fun DataAttributionScaffold( + data: AttributionDialogData, + chipContent: @Composable LazyItemScope.() -> Unit, + cardContent: @Composable LazyItemScope.(Int, AttributionCardData) -> Unit, + footerContent: @Composable () -> Unit, + onDismissRequest: () -> Unit, +) { + MainTheme { + Surface(color = MaterialTheme.colorScheme.surfaceContainerLow) { + Column(modifier = Modifier.fillMaxWidth().wrapContentHeight()) { + // Virtual dismiss button for TalkBack. + Box( + modifier = + Modifier.fillMaxWidth() + .semantics { + role = Role.Button + contentDescription = data.dismissButtonText + traversalIndex = 1f // Don't initially focus on this element. + } + .clickable { onDismissRequest() } + ) + + // Title and logo + Row( + modifier = Modifier.fillMaxWidth().padding(start = 16.dp, top = 16.dp, end = 16.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + ) { + Text( + text = data.title, + style = MaterialTheme.typography.titleMediumEmphasized, + color = MaterialTheme.colorScheme.onSurface, + ) + data.logo?.deserializeToBitmap()?.let { logoBitmap -> + Spacer(Modifier.width(16.dp)) + Box(modifier = Modifier.padding(vertical = 5.dp), contentAlignment = Alignment.Center) { + Icon( + modifier = Modifier.size(24.dp), + bitmap = logoBitmap.asImageBitmap(), + tint = MaterialTheme.colorScheme.onSurface, + contentDescription = null, + ) + } + } + } + + LazyColumn( + modifier = + Modifier.fillMaxWidth() + .weight(1f, fill = false) + .padding(start = 16.dp, top = 16.dp, end = 16.dp), + verticalArrangement = Arrangement.spacedBy(16.dp), + ) { + // Message in header. + item { + Text( + text = data.message, + modifier = Modifier.fillMaxWidth(), + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface, + ) + } + + // Chip in header. + item { chipContent() } + + if (data.attributionsList.isNotEmpty()) { + // Header of attributions list. + item { + Text( + text = data.attributionHeader, + modifier = Modifier.fillMaxWidth(), + style = MaterialTheme.typography.titleSmall, + color = MaterialTheme.colorScheme.onSurface, + ) + } + + // Cards of attributions list. + item { + Column { + for (i in data.attributionsList.indices) { + Box { cardContent(i, data.attributionsList[i]) } + } + } + } + } + } + + footerContent() + } + } + } +} + +@Composable +private fun AttributionChip(data: AttributionChipData) { + Row( + modifier = + Modifier.border( + width = 1.dp, + color = MaterialTheme.colorScheme.outline, + shape = RoundedCornerShape(size = 20.dp), + ) + .fillMaxWidth() + .heightIn(min = 72.dp) + .padding(16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + val chipIcon = + data.chipIcon.deserializeToBitmap()?.asTintableIcon(tintable = true) + ?: data.chipImage.deserializeToBitmap()?.asTintableIcon(tintable = false) + + chipIcon?.let { + Box( + modifier = + Modifier.width(40.dp) + .height(40.dp) + .clip(CircleShape) + .background(MaterialTheme.colorScheme.secondaryContainer), + contentAlignment = Alignment.Center, + ) { + IconOrImage( + icon = chipIcon, + modifier = Modifier.sizeIn(minWidth = 24.dp, minHeight = 24.dp), + tint = MaterialTheme.colorScheme.onSurface, + ) + } + } + Spacer(modifier = Modifier.width(16.dp)) + Text( + text = data.chipLabel, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurface, + ) + } +} + +/** Nested cards which display attribution data in the attribution dialog. */ +@Composable +fun AttributionCard( + data: AttributionCardData, + deepLink: PendingIntent?, + onDismissRequest: () -> Unit, +) { + Row( + modifier = + Modifier.fillMaxWidth() + .clip(RoundedCornerShape(20.dp)) + .clickable(pendingIntent = deepLink) { onDismissRequest() } + .padding(horizontal = 8.dp, vertical = 12.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(12.dp), + ) { + data.bitmap.deserializeToBitmap()?.let { cardBitmap -> + Image( + modifier = Modifier.size(40.dp), + bitmap = cardBitmap.asImageBitmap(), + contentDescription = null, + ) + } + + Column(modifier = Modifier.weight(1f)) { + Text( + text = data.title, + style = MaterialTheme.typography.titleMediumEmphasized, + modifier = Modifier.fillMaxWidth(), + color = MaterialTheme.colorScheme.onSurface, + ) + data.subtitle + ?.takeIf { it.isNotBlank() } + ?.let { subtitleText -> + Text( + text = subtitleText, + style = MaterialTheme.typography.bodyMedium, + color = MaterialTheme.colorScheme.onSurfaceVariant, + modifier = Modifier.fillMaxWidth(), + ) + } + } + + if (deepLink != null) { + Box(modifier = Modifier.size(40.dp), contentAlignment = Alignment.Center) { + Icon( + modifier = Modifier.size(24.dp), + painter = painterResource(R.drawable.gs_keyboard_arrow_right_24dp), + contentDescription = null, + tint = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + } + } +} + +@Composable +fun DataAttributionDialogFooter( + data: AttributionDialogData, + feedbackSessionId: String, + feedbackEntityContent: String?, + onDismissRequest: () -> Unit, +) { + val context = LocalContext.current + Row( + modifier = Modifier.padding(8.dp).fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween, + ) { + // Settings + Row( + modifier = + Modifier.clip(shape = RoundedCornerShape(percent = 100)) + .clickable( + onClick = { + context.startActivity( + Intent().apply { + setClassName( + "com.google.android.apps.pixel.psi", + "com.google.android.apps.pixel.psi.app.settings.SettingsActivity", + ) + } + ) + onDismissRequest() + } + ) + .padding(horizontal = 12.dp, vertical = 6.dp), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(4.dp), + ) { + Icon( + modifier = Modifier.size(20.dp), + painter = painterResource(R.drawable.gs_settings_24dp), + contentDescription = null, + tint = MaterialTheme.colorScheme.primary, + ) + Text( + text = data.settingsButtonText, + style = MaterialTheme.typography.labelLarge, + color = MaterialTheme.colorScheme.primary, + ) + } + + Spacer(modifier = Modifier.width(6.dp)) + + // Feedback. Only shown when feedbackEntityContent is provided. + if (feedbackEntityContent != null) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(6.dp), + ) { + IconButton( + onClick = { + context.startActivity( + FeedbackApi.createEntityFeedbackIntent( + context = context, + data = + entityFeedbackDialogData { + this.entityContent = feedbackEntityContent + this.clientSessionId = feedbackSessionId + this.ratingSentiment = FeedbackRatingSentiment.RATING_SENTIMENT_THUMBS_UP + this.dialogCommonData = data.feedbackDialogCommonData + }, + ) + ) + onDismissRequest() + }, + modifier = Modifier.size(48.dp), + ) { + Icon( + modifier = Modifier.size(20.dp), + painter = painterResource(R.drawable.gs_thumb_up_24dp), + contentDescription = data.thumbsUpButtonContentDescription, + tint = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + IconButton( + onClick = { + context.startActivity( + FeedbackApi.createEntityFeedbackIntent( + context = context, + data = + entityFeedbackDialogData { + this.entityContent = feedbackEntityContent + this.clientSessionId = feedbackSessionId + this.ratingSentiment = FeedbackRatingSentiment.RATING_SENTIMENT_THUMBS_DOWN + this.dialogCommonData = data.feedbackDialogCommonData + }, + ) + ) + onDismissRequest() + }, + modifier = Modifier.size(48.dp), + ) { + Icon( + modifier = Modifier.size(20.dp), + painter = painterResource(R.drawable.gs_thumb_down_24dp), + contentDescription = data.thumbsDownButtonContentDescription, + tint = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + } + } + } +} + +@Composable +fun MainTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + // Dynamic color is available on Android 12+ + dynamicColor: Boolean = true, + content: @Composable () -> Unit, +) { + val colorScheme = + when { + dynamicColor -> { + val context = LocalContext.current + if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) + } + + darkTheme -> darkColorScheme() + else -> lightColorScheme() + } + + MaterialTheme(colorScheme = colorScheme, typography = Typography().withFlexFont()) { content() } +} + +@Composable +private fun Modifier.clickable(pendingIntent: PendingIntent?, onClick: () -> Unit): Modifier { + return if (pendingIntent == null) { + this + } else { + this.clickable { + pendingIntent.send( + ActivityOptions.makeBasic() + .apply { + if (balAdditionalStartModes()) { + setPendingIntentBackgroundActivityStartMode( + // TODO: Update to MODE_BACKGROUND_ACTIVITY_START_ALLOW_IF_VISIBLE + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED + ) + } + } + .toBundle() + ) + onClick() + } + } +} diff --git a/src/com/google/android/as/oss/dataattribution/DialogAnchor.kt b/src/com/google/android/as/oss/dataattribution/DialogAnchor.kt new file mode 100644 index 00000000..00240e0f --- /dev/null +++ b/src/com/google/android/as/oss/dataattribution/DialogAnchor.kt @@ -0,0 +1,123 @@ +/* + * Copyright 2025 Google LLC + * + * 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 + * + * http://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.google.android.`as`.oss.dataattribution + +import android.content.Context +import android.content.res.Configuration +import android.os.Build +import android.view.Window +import android.view.WindowInsets +import android.view.WindowMetrics +import com.google.android.`as`.oss.dataattribution.DialogGravity.Center + +/** The gravity of the dialog relative to the screen. */ +sealed interface DialogGravity { + + /** The dialog is centered on the screen. */ + data object Center : DialogGravity + + /** The dialog is positioned above the IME. */ + data class AboveIme(val backgroundInsetBottomPx: Int) : DialogGravity +} + +/** + * A helper class for positioning a dialog relative to the ime. + * + * This class provides methods to determine the appropriate gravity for a dialog, taking into + * account the current state of the IME (Input Method Editor) and screen orientation. + */ +class DialogAnchor { + + /** + * Cached value of the dialog gravity for portrait mode. + * + * This is used to avoid recalculating the dialog gravity multiple times when the activity is + * recreated. + * + * Note: After configuration change, the ime window metrics not updated immediately, and not found + * a way to get the updated value, so we need to cache the first time result. + */ + private var cachedDialogGravityForPortrait: DialogGravity? = null + + /** + * Determines the appropriate gravity for a dialog, attempting to position it above the Input + * Method Editor (IME) if applicable and visible in portrait mode. + * + * @param window The window to position the dialog relative to. + * @return [DialogGravity] indicating the suggested placement. + */ + fun getDialogGravity(window: Window): DialogGravity { + // IME-aware positioning is typically desired only in portrait mode. + if (!isScreenPortraitMode(window.context)) return Center + + return cachedDialogGravityForPortrait + ?: calculateDialogGravity(window).also { cachedDialogGravityForPortrait = it } + } + + private fun calculateDialogGravity(window: Window): DialogGravity { + val windowMetrics = window.windowManager.currentWindowMetrics + val currentInsets = windowMetrics.windowInsets + + // Check if the IME's reported state and position are suitable for anchoring. + if (!isImeStateSuitableForAnchoring(windowMetrics, currentInsets)) return Center + + val imeBottomInset = currentInsets.getInsets(WindowInsets.Type.ime()).bottom + // Only anchor above IME if it's actually visible and has a positive inset. + // A zero inset means the IME isn't currently affecting the layout at the bottom. + if (imeBottomInset > 0) return DialogGravity.AboveIme(backgroundInsetBottomPx = imeBottomInset) + return Center // Fallback to center if IME inset is not positive. + } + + /** + * Checks if the IME's current state and reported position are suitable for anchoring a dialog + * above it. + * + * On API 35 (VanillaIceCream) and above, this verifies that the IME has a valid bounding + * rectangle that has a positive height and is contained within the window bounds. This provides a + * more accurate assessment of the IME's spatial impact. + * + * On older APIs (below API 35), it falls back to checking if the IME is reported as visible via + * [WindowInsets.isVisible]. + * + * @param windowMetrics The current [WindowMetrics] for the window. + * @param currentInsets The current [WindowInsets] for the window. + * @return `true` if the IME state is suitable for anchoring, `false` otherwise. + */ + private fun isImeStateSuitableForAnchoring( + windowMetrics: WindowMetrics, + currentInsets: WindowInsets, + ): Boolean { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { + // For V+ (API 35+), get the IME bounding rectangles. + // We expect a single, valid IME rectangle for standard keyboard behavior. + val imeBoundingRect = + currentInsets.getBoundingRects(WindowInsets.Type.ime()).singleOrNull() + ?: return false // No IME bounding rect found or multiple (unexpected). + + // The IME is suitable if its rect has a positive height (is actually laid out) + // and is fully contained within the current window bounds. + imeBoundingRect.height() > 0 && windowMetrics.bounds.contains(imeBoundingRect) + } else { + // For older APIs, a simpler check: is the IME currently visible? + currentInsets.isVisible(WindowInsets.Type.ime()) + } + } + + /** Checks if the device screen is currently in portrait orientation. */ + private fun isScreenPortraitMode(context: Context): Boolean = + context.resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT +} diff --git a/src/com/google/android/as/oss/dataattribution/proto/BUILD b/src/com/google/android/as/oss/dataattribution/proto/BUILD new file mode 100644 index 00000000..af848f39 --- /dev/null +++ b/src/com/google/android/as/oss/dataattribution/proto/BUILD @@ -0,0 +1,62 @@ +# Copyright 2025 Google LLC +# +# 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 +# +# http://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. + +load("//third_party/protobuf/bazel:java_lite_proto_library.bzl", "java_lite_proto_library") +load("//third_party/protobuf/bazel:proto_library.bzl", "proto_library") +load("//third_party/protobuf/build_defs:kt_jvm_proto_library.bzl", "kt_jvm_lite_proto_library") + +package( + default_visibility = [ + "//visibility:public", + ], +) + +proto_library( + name = "attribution_dialog_proto", + srcs = ["attribution_dialog.proto"], + deps = [ + "", + "//src/com/google/android/as/oss/feedback/proto:feedback_dialog_common_data_proto", + "//third_party/protobuf:cpp_features_proto", + ], +) + +java_lite_proto_library( + name = "attribution_dialog_java_proto_lite", + deps = [":attribution_dialog_proto"], +) + +kt_jvm_lite_proto_library( + name = "attribution_dialog_kt_proto_lite", + deps = [":attribution_dialog_proto"], +) + +proto_library( + name = "attribution_chip_header_proto", + srcs = ["attribution_chip_header.proto"], + deps = [ + "", + "//third_party/protobuf:cpp_features_proto", + ], +) + +java_lite_proto_library( + name = "attribution_chip_header_java_proto_lite", + deps = [":attribution_chip_header_proto"], +) + +kt_jvm_lite_proto_library( + name = "attribution_chip_header_kt_proto_lite", + deps = [":attribution_chip_header_proto"], +) diff --git a/src/com/google/android/as/oss/dataattribution/proto/attribution_chip_header.proto b/src/com/google/android/as/oss/dataattribution/proto/attribution_chip_header.proto new file mode 100644 index 00000000..d46e1a92 --- /dev/null +++ b/src/com/google/android/as/oss/dataattribution/proto/attribution_chip_header.proto @@ -0,0 +1,41 @@ +// Copyright 2025 Google LLC +// +// 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 +// +// http://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. + +edition = "2023"; + +package com.google.android.as.oss.dataattribution.proto; + +import "third_party/protobuf/cpp_features.proto"; + +option features.(pb.cpp).string_type = VIEW; +option java_package = "com.google.android.as.oss.dataattribution.proto"; +option java_multiple_files = true; + +// The data required to populate [AttributionChipData]. +// Next ID: 4 +message AttributionChipData { + // A bitmap that should match the icon/image the user sees on the long-pressed + // entity. + oneof chip_bitmap { + // Chip icon, will be tinted. + bytes chip_icon = 3; + + // Chip image, will not be tinted. + bytes chip_image = 1; + } + + // the label that should match the full egress data if the user clicks on the + // long-pressed entity. + string chip_label = 2; +} diff --git a/src/com/google/android/as/oss/dataattribution/proto/attribution_dialog.proto b/src/com/google/android/as/oss/dataattribution/proto/attribution_dialog.proto new file mode 100644 index 00000000..e5b48f2a --- /dev/null +++ b/src/com/google/android/as/oss/dataattribution/proto/attribution_dialog.proto @@ -0,0 +1,79 @@ +// Copyright 2025 Google LLC +// +// 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 +// +// http://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. + +edition = "2023"; + +package com.google.android.as.oss.dataattribution.proto; + +import "src/com/google/android/as/oss/feedback/proto/feedback_dialog_common_data.proto"; + +import "third_party/protobuf/cpp_features.proto"; + +option features.(pb.cpp).string_type = VIEW; +option java_package = "com.google.android.as.oss.dataattribution.proto"; +option java_multiple_files = true; + +// The data required to populate [DataAttributionDialog]. +message AttributionDialogData { + // the header of the dialog. + string title = 1; + + // an optional value proposition logo to display at the top of the dialog, + // next to the title. + bytes logo = 2; + + // a sub-header providing information regarding the dialog. This property is + // used for the row present below the title. + string message = 3; + + // the header of the attribution card list. This property is used for the row + // present below the message row. + string attribution_header = 4; + + // consists of the attribution data. This data is used for the row present + // below the attributionHeader. + repeated AttributionCardData attributions = 5 + ; + + // an identifier for the current suggestion session, used for logging or + // feedback purposes. + string session_id = 6; + + // the TalkBack text of the dismiss button. + string dismiss_button_text = 7; + + // The label of the Settings button. + string settings_button_text = 8; + + // The content description of the Thumbs Up button. + string thumbs_up_button_content_description = 9; + + // The content description of the Thumbs Down button. + string thumbs_down_button_content_description = 10; + + // The common dialog data needed to show the Feedback dialog. + feedback.api.FeedbackDialogCommonData feedback_dialog_common_data = 11; +} + +// The attribution data used to populate [AttributionCard]. +message AttributionCardData { + // the image that represents the source of the attribution. + bytes bitmap = 1; + + // the title of the attribution. + string title = 2; + + // the subtitle of the attribution. + string subtitle = 3; +} diff --git a/src/com/google/android/as/oss/dataattribution/res/drawable/gs_keyboard_arrow_right_24dp.xml b/src/com/google/android/as/oss/dataattribution/res/drawable/gs_keyboard_arrow_right_24dp.xml new file mode 100644 index 00000000..a0d1bcec --- /dev/null +++ b/src/com/google/android/as/oss/dataattribution/res/drawable/gs_keyboard_arrow_right_24dp.xml @@ -0,0 +1,29 @@ + + + + + + + + diff --git a/src/com/google/android/as/oss/dataattribution/res/drawable/gs_settings_24dp.xml b/src/com/google/android/as/oss/dataattribution/res/drawable/gs_settings_24dp.xml new file mode 100644 index 00000000..480c7903 --- /dev/null +++ b/src/com/google/android/as/oss/dataattribution/res/drawable/gs_settings_24dp.xml @@ -0,0 +1,29 @@ + + + + + + + + diff --git a/src/com/google/android/as/oss/dataattribution/res/drawable/gs_thumb_down_24dp.xml b/src/com/google/android/as/oss/dataattribution/res/drawable/gs_thumb_down_24dp.xml new file mode 100644 index 00000000..73e502ed --- /dev/null +++ b/src/com/google/android/as/oss/dataattribution/res/drawable/gs_thumb_down_24dp.xml @@ -0,0 +1,29 @@ + + + + + + + + diff --git a/src/com/google/android/as/oss/dataattribution/res/drawable/gs_thumb_up_24dp.xml b/src/com/google/android/as/oss/dataattribution/res/drawable/gs_thumb_up_24dp.xml new file mode 100644 index 00000000..fecb6759 --- /dev/null +++ b/src/com/google/android/as/oss/dataattribution/res/drawable/gs_thumb_up_24dp.xml @@ -0,0 +1,29 @@ + + + + + + + + diff --git a/src/com/google/android/as/oss/dataattribution/res/values/themes.xml b/src/com/google/android/as/oss/dataattribution/res/values/themes.xml new file mode 100644 index 00000000..041e1d00 --- /dev/null +++ b/src/com/google/android/as/oss/dataattribution/res/values/themes.xml @@ -0,0 +1,34 @@ + + + + + + + + + + diff --git a/src/com/google/android/as/oss/delegatedui/BUILD b/src/com/google/android/as/oss/delegatedui/BUILD new file mode 100644 index 00000000..150bbaa8 --- /dev/null +++ b/src/com/google/android/as/oss/delegatedui/BUILD @@ -0,0 +1,30 @@ +# Copyright 2025 Google LLC +# +# 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 +# +# http://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. + +load("@bazel_rules_android//android:rules.bzl", "android_library") +load("//devtools/deps/check:deps_check.bzl", "check_dependencies") + +package(default_visibility = ["//visibility:public"]) + +android_library( + name = "prod_modules", + tags = [ + "keep_dep", + ], + exports = [ + "//src/com/google/android/as/oss/delegatedui/config:module", + "//src/com/google/android/as/oss/delegatedui/service/data/serviceconnection:module", + "//src/com/google/android/as/oss/delegatedui/service/module:grpc_module", + ], +) diff --git a/src/com/google/android/as/oss/delegatedui/api/common/BUILD b/src/com/google/android/as/oss/delegatedui/api/common/BUILD new file mode 100644 index 00000000..1983eb13 --- /dev/null +++ b/src/com/google/android/as/oss/delegatedui/api/common/BUILD @@ -0,0 +1,54 @@ +# Copyright 2025 Google LLC +# +# 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 +# +# http://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. + +load("@bazel_rules_android//android:rules.bzl", "android_library") +load("//third_party/bazel_rules/rules_python/python:proto.bzl", "py_proto_library") +load("//third_party/protobuf/bazel:java_lite_proto_library.bzl", "java_lite_proto_library") +load("//third_party/protobuf/bazel:proto_library.bzl", "proto_library") +load("//third_party/protobuf/build_defs:kt_jvm_proto_library.bzl", "kt_jvm_lite_proto_library") + +package( + default_visibility = [ + "//visibility:public", + ], +) + +proto_library( + name = "common_proto", + srcs = ["common.proto"], + deps = [], +) + +java_lite_proto_library( + name = "common_java_proto_lite", + deps = [":common_proto"], +) + +kt_jvm_lite_proto_library( + name = "common_kt_proto_lite", + deps = [":common_proto"], +) + +android_library( + name = "nested_scroll", + srcs = ["DelegatedUiNestedScrollEvent.kt"], + deps = [ + "@maven//:androidx_core_core", + ], +) + +py_proto_library( + name = "common_py_pb2", + deps = [":common_proto"], +) diff --git a/src/com/google/android/as/oss/delegatedui/api/common/DelegatedUiNestedScrollEvent.kt b/src/com/google/android/as/oss/delegatedui/api/common/DelegatedUiNestedScrollEvent.kt new file mode 100644 index 00000000..08b1ca29 --- /dev/null +++ b/src/com/google/android/as/oss/delegatedui/api/common/DelegatedUiNestedScrollEvent.kt @@ -0,0 +1,52 @@ +/* + * Copyright 2025 Google LLC + * + * 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 + * + * http://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.google.android.`as`.oss.delegatedui.api.common + +import androidx.core.view.ViewCompat +import androidx.core.view.ViewCompat.ScrollAxis + +/** Nested scrolling event. */ +sealed interface DelegatedUiNestedScrollEvent { + + /** Nested scrolling control signal: start. */ + data class NestedScrollStartEvent(@ScrollAxis val axes: Int) : DelegatedUiNestedScrollEvent { + override fun toString() = "NestedScrollStartEvent(axes=${axes.toScrollAxesString()})" + } + + /** Nested scrolling delta. */ + data class NestedScrollDelta(val scrollX: Float, val scrollY: Float) : + DelegatedUiNestedScrollEvent + + /** Nested scrolling control signal: stop. May include nested fling velocity. */ + data class NestedScrollStopEvent(val flingX: Float, val flingY: Float) : + DelegatedUiNestedScrollEvent +} + +@ScrollAxis +private fun Int.toScrollAxesString(): String { + val axes = this + + if (axes == ViewCompat.SCROLL_AXIS_NONE) { + return "[NONE]" + } + + return buildList { + if ((axes and ViewCompat.SCROLL_AXIS_HORIZONTAL) != 0) add("HORIZONTAL") + if ((axes and ViewCompat.SCROLL_AXIS_VERTICAL) != 0) add("VERTICAL") + } + .joinToString(", ", prefix = "[", postfix = "]") +} diff --git a/src/com/google/android/as/oss/delegatedui/api/common/common.proto b/src/com/google/android/as/oss/delegatedui/api/common/common.proto new file mode 100644 index 00000000..2e993c5c --- /dev/null +++ b/src/com/google/android/as/oss/delegatedui/api/common/common.proto @@ -0,0 +1,75 @@ +// Copyright 2025 Google LLC +// +// 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 +// +// http://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. + +edition = "2023"; + +package com.google.android.as.oss.delegatedui.api.common; + +option java_package = "com.google.android.as.oss.delegatedui.api.common"; +option java_multiple_files = true; + +message DelegatedUiClientId { + // The package name of the client app. + string client_package_name = 1; + DelegatedUiUseCase use_case = 2; +} + +// Next ID: 7 +message DelegatedUiClientInputs { + // Client data to use to generate remote content. + common.DelegatedUiIngressData ingress_data = 1; + + // The data provider requested by the client. + common.DelegatedUiDataProviderInfo data_provider_info = 6; + + // The android.view.View.MeasureSpec of the delegated UI that the client's + // local UI can support. + int32 measure_spec_width = 2; + int32 measure_spec_height = 3; + + // The client can pass in an argb int value representing the background color + // for the delegated UI to draw. This is useful for when the client sets the + // SurfaceView z-order to Behind, which would normally render black pixels + // wherever the delegated UI is not drawing. + int32 background_color = 4; + + // The client can pass in a bitmasked flag value representing the nested + // scroll axes that are supported. This impacts the way the remote UI handles + // its internal nested scrolling, and the nested scroll events that the DUI + // session sends back to the client. + int32 client_nested_scroll_axes = 5; +} + +message DelegatedUiDataProviderInfo { + enum DelegatedUiDataProvider { + DATA_PROVIDER_UNSPECIFIED = 0; + // Device Intelligence (PSI) + DATA_PROVIDER_PSI = 1; + // Android System Intelligence (ASI) + DATA_PROVIDER_ASI = 2; + } + DelegatedUiDataProvider data_provider = 1; +} + +// Client extensions + +// Ingress data from clients to the delegated UI service. +message DelegatedUiIngressData { + extensions 16 to 100; +} + +// Describes the client's delegated UI use-case. +message DelegatedUiUseCase { + extensions 16 to 100; +} diff --git a/src/com/google/android/as/oss/delegatedui/api/config/BUILD b/src/com/google/android/as/oss/delegatedui/api/config/BUILD new file mode 100644 index 00000000..4b9d428f --- /dev/null +++ b/src/com/google/android/as/oss/delegatedui/api/config/BUILD @@ -0,0 +1,39 @@ +# Copyright 2025 Google LLC +# +# 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 +# +# http://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. + +load("//third_party/protobuf/bazel:java_lite_proto_library.bzl", "java_lite_proto_library") +load("//third_party/protobuf/bazel:proto_library.bzl", "proto_library") +load("//third_party/protobuf/build_defs:kt_jvm_proto_library.bzl", "kt_jvm_lite_proto_library") + +package( + default_visibility = [ + "//visibility:public", + ], +) + +proto_library( + name = "data_service_config_proto", + srcs = ["data_service_config.proto"], + deps = ["//src/com/google/android/as/oss/delegatedui/api/common:common_proto"], +) + +java_lite_proto_library( + name = "data_service_config_java_proto_lite", + deps = [":data_service_config_proto"], +) + +kt_jvm_lite_proto_library( + name = "data_service_config_kt_proto_lite", + deps = [":data_service_config_proto"], +) diff --git a/src/com/google/android/as/oss/delegatedui/api/config/data_service_config.proto b/src/com/google/android/as/oss/delegatedui/api/config/data_service_config.proto new file mode 100644 index 00000000..01a54026 --- /dev/null +++ b/src/com/google/android/as/oss/delegatedui/api/config/data_service_config.proto @@ -0,0 +1,33 @@ +// Copyright 2025 Google LLC +// +// 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 +// +// http://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. + +edition = "2023"; + +package com.google.android.as.oss.delegatedui.api.config; + +import "src/com/google/android/as/oss/delegatedui/api/common/common.proto"; + +option java_package = "com.google.android.as.oss.delegatedui.api.config"; +option java_multiple_files = true; + +// Configuration for a DelegatedUiDataService. +message DelegatedUiDataServiceConfig { + common.DelegatedUiDataProviderInfo provider_info = 1; + int64 connection_idle_timeout_seconds = 2; +} + +// A list of DelegatedUiDataServiceConfig. +message DelegatedUiDataServiceConfigList { + repeated DelegatedUiDataServiceConfig configs = 1; +} diff --git a/src/com/google/android/as/oss/delegatedui/api/infra/dataservice/BUILD b/src/com/google/android/as/oss/delegatedui/api/infra/dataservice/BUILD new file mode 100644 index 00000000..e71022de --- /dev/null +++ b/src/com/google/android/as/oss/delegatedui/api/infra/dataservice/BUILD @@ -0,0 +1,84 @@ +# Copyright 2025 Google LLC +# +# 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 +# +# http://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. + +load("@bazel_rules_android//android:rules.bzl", "android_library") +load("@rules_proto_grpc//java:defs.bzl", "java_grpc_library") +load("//third_party/bazel_rules/rules_python/python:proto.bzl", "py_proto_library") +load("//third_party/protobuf/bazel:java_lite_proto_library.bzl", "java_lite_proto_library") +load("//third_party/protobuf/bazel:proto_library.bzl", "proto_library") +load("//third_party/protobuf/build_defs:kt_jvm_proto_library.bzl", "kt_jvm_lite_proto_library") +load("//tools/build_defs/kotlin:rules.bzl", "kt_jvm_grpc_library") + +package( + default_visibility = [ + "//visibility:public", + ], +) + +proto_library( + name = "data_service_proto", + srcs = [ + "data_service.proto", + "data_service_get_additional_data.proto", + "data_service_get_template_data.proto", + "data_service_log_usage_data.proto", + ], + has_services = True, + deps = [ + "", + "//src/com/google/android/as/oss/delegatedui/api/common:common_proto", + "//src/com/google/android/as/oss/delegatedui/api/integration/templates:templates_proto", + "//src/com/google/android/as/oss/delegatedui/api/integration/templates:ui_id_token_proto", + ], +) + +java_lite_proto_library( + name = "data_service_java_proto_lite", + deps = [":data_service_proto"], +) + +kt_jvm_lite_proto_library( + name = "data_service_kt_proto_lite", + deps = [":data_service_proto"], +) + +java_grpc_library( + name = "data_service_java_grpc", + srcs = [":data_service_proto"], + constraints = ["android"], + flavor = "lite", + deps = [":data_service_java_proto_lite"], +) + +kt_jvm_grpc_library( + name = "data_service_kt_grpc", + srcs = [":data_service_proto"], + flavor = "lite", + deps = [":data_service_kt_proto_lite"], +) + +py_proto_library( + name = "data_service_py_pb2", + deps = [":data_service_proto"], +) + +android_library( + name = "parcelable_keys", + srcs = ["DelegatedUiDataServiceParcelableKeys.kt"], + manifest = "//src/com/google/android/as/oss/common:AndroidManifest.xml", + deps = [ + "//src/com/google/android/as/oss/delegatedui/utils:parcelable_key", + "//src/com/google/android/as/oss/delegatedui/utils:server_interceptor", + ], +) diff --git a/src/com/google/android/as/oss/delegatedui/api/infra/dataservice/DelegatedUiDataServiceParcelableKeys.kt b/src/com/google/android/as/oss/delegatedui/api/infra/dataservice/DelegatedUiDataServiceParcelableKeys.kt new file mode 100644 index 00000000..0e44e3ee --- /dev/null +++ b/src/com/google/android/as/oss/delegatedui/api/infra/dataservice/DelegatedUiDataServiceParcelableKeys.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2025 Google LLC + * + * 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 + * + * http://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.google.android.`as`.oss.delegatedui.api.infra.dataservice + +import android.app.PendingIntent +import android.app.RemoteAction +import android.content.res.Configuration +import android.graphics.Bitmap +import com.google.android.`as`.oss.delegatedui.utils.ParcelableOverMetadataServerInterceptor +import com.google.android.`as`.oss.delegatedui.utils.RepeatedParcelableKey +import com.google.android.`as`.oss.delegatedui.utils.SingleParcelableKey + +/** Parcelable keys for the Delegated UI Service. */ +object DelegatedUiDataServiceParcelableKeys { + + /** Key for the configuration. */ + val CONFIGURATION_KEY = SingleParcelableKey("configuration", Configuration.CREATOR) + + /** Key for the image, this onlt supports a single image to be sent/received. */ + val IMAGE_KEY = SingleParcelableKey("image", Bitmap.CREATOR) + + /** Key for the pending intent list, allows for multiple pending intents to be sent/received. */ + val PENDING_INTENT_LIST_KEY = RepeatedParcelableKey("pending_intent_list", PendingIntent.CREATOR) + + /** Key for the remote action list, allows for multiple remote actions to be sent/received. */ + val REMOTE_ACTION_LIST_KEY = RepeatedParcelableKey("remote_action_list", RemoteAction.CREATOR) + + /** + * Interceptor to be set on the Delegated UI data service server for receiving and sending + * Parcelables over headers. + */ + val SERVER_INTERCEPTOR = + ParcelableOverMetadataServerInterceptor( + CONFIGURATION_KEY, + IMAGE_KEY, + PENDING_INTENT_LIST_KEY, + REMOTE_ACTION_LIST_KEY, + ) +} diff --git a/src/com/google/android/as/oss/delegatedui/api/infra/dataservice/data_service.proto b/src/com/google/android/as/oss/delegatedui/api/infra/dataservice/data_service.proto new file mode 100644 index 00000000..9672a8f8 --- /dev/null +++ b/src/com/google/android/as/oss/delegatedui/api/infra/dataservice/data_service.proto @@ -0,0 +1,57 @@ +// Copyright 2025 Google LLC +// +// 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 +// +// http://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. + +edition = "2023"; + +package com.google.android.as.oss.delegatedui.api.infra.dataservice; + +import "src/com/google/android/as/oss/delegatedui/api/infra/dataservice/data_service_get_additional_data.proto"; +import "src/com/google/android/as/oss/delegatedui/api/infra/dataservice/data_service_get_template_data.proto"; +import "src/com/google/android/as/oss/delegatedui/api/infra/dataservice/data_service_log_usage_data.proto"; + +option java_package = "com.google.android.as.oss.delegatedui.api.infra.dataservice"; +option java_multiple_files = true; + +// Service to provide data for a delegated UI renderer. +// +// This service proto is defined inside PCS, but the implementation will be +// offered by another app. +service DelegatedUiDataService { + // Get the delegated ui template data used for rendering the remote template + // and for data egress. + // + // Request headers contain Parcelable Configuration "configuration". Response + // headers may contain Parcelable Bitmap "image", RemoteViews "remote_views", + // PendingIntentList "pending_intent_list", or RemoteActionList + // "remote_action_list". + rpc GetDelegatedUiTemplateData(DelegatedUiGetTemplateDataRequest) + returns (DelegatedUiGetTemplateDataResponse) { + option deadline = 5.0; + } + + // Get additional data for handling clicks. + // + // Response headers may contain Parcelable PendingIntentList + // "pending_intent_list" or RemoteActionList "remote_action_list". + rpc GetDelegatedUiAdditionalData(DelegatedUiGetAdditionalDataRequest) + returns (DelegatedUiGetAdditionalDataResponse) { + option deadline = 5.0; + } + + // Log usage data for user interactions with the remote UI. + rpc LogDelegatedUiUsageData(DelegatedUiLogUsageDataRequest) + returns (DelegatedUiLogUsageDataResponse) { + option deadline = 5.0; + } +} diff --git a/src/com/google/android/as/oss/delegatedui/api/infra/dataservice/data_service_get_additional_data.proto b/src/com/google/android/as/oss/delegatedui/api/infra/dataservice/data_service_get_additional_data.proto new file mode 100644 index 00000000..0cd4fa27 --- /dev/null +++ b/src/com/google/android/as/oss/delegatedui/api/infra/dataservice/data_service_get_additional_data.proto @@ -0,0 +1,36 @@ +// Copyright 2025 Google LLC +// +// 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 +// +// http://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. + +edition = "2023"; + +package com.google.android.as.oss.delegatedui.api.infra.dataservice; + +import "src/com/google/android/as/oss/delegatedui/api/common/common.proto"; +import "src/com/google/android/as/oss/delegatedui/api/integration/templates/template_integration.proto"; + +option java_package = "com.google.android.as.oss.delegatedui.api.infra.dataservice"; +option java_multiple_files = true; + +// Get additional data for the given ingress data +message DelegatedUiGetAdditionalDataRequest { + // UUID of the active delegated UI session. + string session_uuid = 1; + common.DelegatedUiClientId client_id = 2; + // Ingress data used to generate the delegated ui data. + common.DelegatedUiIngressData client_ingress_data = 3; +} + +message DelegatedUiGetAdditionalDataResponse { + integration.templates.DelegatedUiAdditionalData additional_data = 1; +} diff --git a/src/com/google/android/as/oss/delegatedui/api/infra/dataservice/data_service_get_template_data.proto b/src/com/google/android/as/oss/delegatedui/api/infra/dataservice/data_service_get_template_data.proto new file mode 100644 index 00000000..f84cda7d --- /dev/null +++ b/src/com/google/android/as/oss/delegatedui/api/infra/dataservice/data_service_get_template_data.proto @@ -0,0 +1,68 @@ +// Copyright 2025 Google LLC +// +// 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 +// +// http://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. + +edition = "2023"; + +package com.google.android.as.oss.delegatedui.api.infra.dataservice; + +import "src/com/google/android/as/oss/delegatedui/api/common/common.proto"; +import "src/com/google/android/as/oss/delegatedui/api/integration/templates/template_integration.proto"; + +option java_package = "com.google.android.as.oss.delegatedui.api.infra.dataservice"; +option java_multiple_files = true; + +// Get the delegated ui template data for the given ingress data +message DelegatedUiGetTemplateDataRequest { + // Configuration from the local Context. Used to construct a remote Context. + // This will be sent through RPC metadata headers, and is only documented here + // for the record. + // android.content.res.Configuration configuration = 0; + + // UUID of the active delegated UI session. + string session_uuid = 1; + + common.DelegatedUiClientId client_id = 2; + // Ingress data used to generate the delegated ui data. + common.DelegatedUiIngressData client_ingress_data = 3; + + // A list of templates supported by the renderer. + repeated integration.templates.DelegatedUiTemplateType supported_templates = + 4; + + // The android.view.View.MeasureSpec of the delegated UI that the client's + // local UI can support - for the RemoteViews template use case. + int32 measure_spec_width = 5; + int32 measure_spec_height = 6; +} + +message DelegatedUiGetTemplateDataResponse { + // The template to use for the delegated UI content. + integration.templates.DelegatedUiTemplateType template_type = 1; + + // The data to use to populate the template. + integration.templates.DelegatedUiTemplateData template_data = 2 + ; + + // Whether there is additional data available for a follow-up + // GetDelegatedUiAdditionalData call. + bool has_additional_data = 3; + + // The title to set on the delegated UI pane / window, for a11y. + CommonTemplateData common_data = 4; +} + +message CommonTemplateData { + // The title to set on the delegated UI pane / window, for a11y. + string accessibility_pane_title = 1; +} diff --git a/src/com/google/android/as/oss/delegatedui/api/infra/dataservice/data_service_log_usage_data.proto b/src/com/google/android/as/oss/delegatedui/api/infra/dataservice/data_service_log_usage_data.proto new file mode 100644 index 00000000..3f0f24b7 --- /dev/null +++ b/src/com/google/android/as/oss/delegatedui/api/infra/dataservice/data_service_log_usage_data.proto @@ -0,0 +1,78 @@ +// Copyright 2025 Google LLC +// +// 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 +// +// http://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. + +edition = "2023"; + +package com.google.android.as.oss.delegatedui.api.infra.dataservice; + +import "src/com/google/android/as/oss/delegatedui/api/common/common.proto"; +import "src/com/google/android/as/oss/delegatedui/api/integration/templates/ui_id_token.proto"; + +option java_package = "com.google.android.as.oss.delegatedui.api.infra.dataservice"; +option java_multiple_files = true; + +message DelegatedUiLogUsageDataRequest { + string session_uuid = 1; + common.DelegatedUiClientId client_id = 2; + + DelegatedUiUsageData usage_data = 3; +} + +message DelegatedUiLogUsageDataResponse {} + +message DelegatedUiUsageData { + // The user interaction that triggered this event. + InteractionType interaction = 1; + // The semantic meaning of this event. + SemanticsType semantics = 2; + // The UI elements to be logged. The list can be larger than one only when + // InteractionType is INTERACTION_TYPE_VIEW. + repeated integration.templates.UiIdToken ui_id_token = 3; + + // Possible user interactions that can trigger an event. + enum InteractionType { + INTERACTION_TYPE_UNSPECIFIED = 0; + // User click. + INTERACTION_TYPE_CLICK = 1; + // User long-click. + INTERACTION_TYPE_LONG_CLICK = 2; + // Additional data load. + INTERACTION_TYPE_LOAD = 3; + // Interop interaction. + INTERACTION_TYPE_INTEROP = 4; + // Impression of a UI element. + INTERACTION_TYPE_VIEW = 5; + // User drag. + INTERACTION_TYPE_DRAG = 6; + } + + // Possible semantic meanings of an event. + enum SemanticsType { + SEMANTICS_TYPE_UNSPECIFIED = 0; + // We sent egress data out of PCC. + SEMANTICS_TYPE_SEND_EGRESS_DATA = 1; + // We executed an action, possibly sending data out of PCC. + SEMANTICS_TYPE_EXECUTE_ACTION = 2; + // We displayed a data attribution dialog in PCS. + SEMANTICS_TYPE_SHOW_DATA_ATTRIBUTION = 3; + // We just log that the interaction was invoked. + SEMANTICS_TYPE_LOG_USAGE = 4; + // We displayed an entity feedback dialog in PCS. + SEMANTICS_TYPE_SHOW_ENTITY_FEEDBACK = 5; + // We displayed an multi feedback dialog in PCS. + SEMANTICS_TYPE_SHOW_MULTI_FEEDBACK = 6; + // We log that the suggestion was dismissed. + SEMANTICS_TYPE_DISMISS_SUGGESTION = 7; + } +} diff --git a/src/com/google/android/as/oss/delegatedui/api/infra/uiservice/BUILD b/src/com/google/android/as/oss/delegatedui/api/infra/uiservice/BUILD new file mode 100644 index 00000000..428f1d9a --- /dev/null +++ b/src/com/google/android/as/oss/delegatedui/api/infra/uiservice/BUILD @@ -0,0 +1,81 @@ +# Copyright 2025 Google LLC +# +# 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 +# +# http://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. + +load("@bazel_rules_android//android:rules.bzl", "android_library") +load("@rules_proto_grpc//java:defs.bzl", "java_grpc_library") +load("//third_party/protobuf/bazel:java_lite_proto_library.bzl", "java_lite_proto_library") +load("//third_party/protobuf/bazel:proto_library.bzl", "proto_library") +load("//third_party/protobuf/build_defs:kt_jvm_proto_library.bzl", "kt_jvm_lite_proto_library") +load("//tools/build_defs/kotlin:rules.bzl", "kt_jvm_grpc_library") + +package( + default_visibility = [ + "//visibility:public", + ], +) + +proto_library( + name = "service_proto", + srcs = [ + "ui_service.proto", + "ui_service_create.proto", + "ui_service_data_egress.proto", + "ui_service_invalidate.proto", + "ui_service_nested_scroll.proto", + "ui_service_prepare.proto", + "ui_service_size_change.proto", + "ui_service_update.proto", + ], + has_services = True, + deps = [ + "", + "//src/com/google/android/as/oss/delegatedui/api/common:common_proto", + "//src/com/google/android/as/oss/delegatedui/api/integration/egress:egress_proto", + ], +) + +java_lite_proto_library( + name = "service_java_proto_lite", + deps = [":service_proto"], +) + +kt_jvm_lite_proto_library( + name = "service_kt_proto_lite", + deps = [":service_proto"], +) + +java_grpc_library( + name = "service_java_grpc", + srcs = [":service_proto"], + constraints = ["android"], + flavor = "lite", + deps = [":service_java_proto_lite"], +) + +kt_jvm_grpc_library( + name = "service_kt_grpc", + srcs = [":service_proto"], + flavor = "lite", + deps = [":service_kt_proto_lite"], +) + +android_library( + name = "parcelable_keys", + srcs = ["DelegatedUiServiceParcelableKeys.kt"], + manifest = "//src/com/google/android/as/oss/common:AndroidManifest.xml", + deps = [ + "//src/com/google/android/as/oss/delegatedui/utils:parcelable_key", + "//src/com/google/android/as/oss/delegatedui/utils:server_interceptor", + ], +) diff --git a/src/com/google/android/as/oss/delegatedui/api/infra/uiservice/DelegatedUiServiceParcelableKeys.kt b/src/com/google/android/as/oss/delegatedui/api/infra/uiservice/DelegatedUiServiceParcelableKeys.kt new file mode 100644 index 00000000..ea4c4c57 --- /dev/null +++ b/src/com/google/android/as/oss/delegatedui/api/infra/uiservice/DelegatedUiServiceParcelableKeys.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2025 Google LLC + * + * 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 + * + * http://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.google.android.`as`.oss.delegatedui.api.infra.uiservice + +import android.content.res.Configuration +import android.view.SurfaceControlViewHost.SurfacePackage +import android.window.InputTransferToken +import com.google.android.`as`.oss.delegatedui.utils.ParcelableOverMetadataServerInterceptor +import com.google.android.`as`.oss.delegatedui.utils.SingleParcelableKey + +/** Parcelable keys for the Delegated UI Service. */ +object DelegatedUiServiceParcelableKeys { + + /** Key for the input transfer token. */ + val INPUT_TRANSFER_TOKEN_KEY = + SingleParcelableKey("input_transfer_token", InputTransferToken.CREATOR) + + /** Key for the configuration. */ + val CONFIGURATION_KEY = SingleParcelableKey("configuration", Configuration.CREATOR) + + /** Key for the surface package. */ + val SURFACE_PACKAGE_KEY = SingleParcelableKey("surface_package", SurfacePackage.CREATOR) + + /** + * Interceptor to be set on the Delegated UI service server for receiving and sending Parcelables + * over headers. + */ + val SERVER_INTERCEPTOR = + ParcelableOverMetadataServerInterceptor( + INPUT_TRANSFER_TOKEN_KEY, + SURFACE_PACKAGE_KEY, + CONFIGURATION_KEY, + ) +} diff --git a/src/com/google/android/as/oss/delegatedui/api/infra/uiservice/ui_service.proto b/src/com/google/android/as/oss/delegatedui/api/infra/uiservice/ui_service.proto new file mode 100644 index 00000000..be77242c --- /dev/null +++ b/src/com/google/android/as/oss/delegatedui/api/infra/uiservice/ui_service.proto @@ -0,0 +1,93 @@ +// Copyright 2025 Google LLC +// +// 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 +// +// http://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. + +edition = "2023"; + +package com.google.android.as.oss.delegatedui.api.infra.uiservice; + +import "src/com/google/android/as/oss/delegatedui/api/infra/uiservice/ui_service_create.proto"; +import "src/com/google/android/as/oss/delegatedui/api/infra/uiservice/ui_service_data_egress.proto"; +import "src/com/google/android/as/oss/delegatedui/api/infra/uiservice/ui_service_invalidate.proto"; +import "src/com/google/android/as/oss/delegatedui/api/infra/uiservice/ui_service_nested_scroll.proto"; +import "src/com/google/android/as/oss/delegatedui/api/infra/uiservice/ui_service_prepare.proto"; +import "src/com/google/android/as/oss/delegatedui/api/infra/uiservice/ui_service_size_change.proto"; +import "src/com/google/android/as/oss/delegatedui/api/infra/uiservice/ui_service_update.proto"; + +option java_package = "com.google.android.as.oss.delegatedui.api.infra.uiservice"; +option java_multiple_files = true; + +// Service to render protected content using a delegated UI. +// +// This service will host a delegated UI containing protected content, that a +// client app can embed in their UI. The client app will not be able to access +// the actual rendered content. +// +// Upon user consent, protected data may be egressed back to the client. +service DelegatedUiService { + // Prepares for a delegated UI session. Client apps should query this before + // trying to connect to a delegated UI session. + // + // Request headers contain Parcelable "configuration". + rpc PrepareDelegatedUiSession(DelegatedUiPrepareRequest) + returns (DelegatedUiPrepareResponse) { + option deadline = 5.0; + } + + // Establishes a bi-directional stream for a client to communicate with the + // delegated UI service. + // + // Request headers contain Parcelable "host_token" and "configuration". + // Response headers contain Parcelable "surface_package". + rpc ConnectDelegatedUiSession(stream DelegatedUiRequest) + returns (stream DelegatedUiResponse) { + option deadline = 5.0; + } + + // Invalidates an active delegated UI session from an external component. + // Clients should not call this, and should instead pass an update request + // into a connected session. + rpc InvalidateDelegatedUiSession(DelegatedUiInvalidateRequest) + returns (DelegatedUiInvalidateResponse) { + option deadline = 5.0; + } +} + +// Request from the calling client to the delegated UI service. +message DelegatedUiRequest { + oneof request { + // Create - initialization call to set up remote rendering via delegated UI + DelegatedUiCreateRequest create_request = 1; + // Update - send updates from client that that may impact the delegated UI + DelegatedUiUpdateRequest update_request = 2; + } +} + +// Response from the delegated UI service to the calling client. +// Next id: 7 +message DelegatedUiResponse { + oneof response { + // Response to create request. + DelegatedUiCreateResponse create_response = 1; + // Response to update request. + DelegatedUiUpdateResponse update_response = 6; + // Asynchronous event - notifies on size change event from delegated UI. + DelegatedUiSizeChangeResponse size_change_response = 2; + // Asynchronous event - notifies on consented data egress from delegated UI. + DelegatedUiDataEgressResponse data_egress_response = 3; + // Asynchronous event - notifies on nested scroll event from delegated UI. + DelegatedUiNestedScrollResponse nested_scroll_response = 4; + // Asynchronous event - notifies on nested fling event from delegated UI. + DelegatedUiNestedFlingResponse nested_fling_response = 5; + } +} diff --git a/src/com/google/android/as/oss/delegatedui/api/infra/uiservice/ui_service_create.proto b/src/com/google/android/as/oss/delegatedui/api/infra/uiservice/ui_service_create.proto new file mode 100644 index 00000000..bcc66da4 --- /dev/null +++ b/src/com/google/android/as/oss/delegatedui/api/infra/uiservice/ui_service_create.proto @@ -0,0 +1,60 @@ +// Copyright 2025 Google LLC +// +// 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 +// +// http://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. + +edition = "2023"; + +package com.google.android.as.oss.delegatedui.api.infra.uiservice; + +import "src/com/google/android/as/oss/delegatedui/api/common/common.proto"; + +option java_package = "com.google.android.as.oss.delegatedui.api.infra.uiservice"; +option java_multiple_files = true; + +// Create - initialization call to set up remote rendering via delegated UI +message DelegatedUiCreateRequest { + // Host token from the local SurfaceView. Used to construct a remote + // SurfaceControlViewHost. This will be sent through RPC metadata headers, and + // is only documented here for the record. + // android.window.InputTransferToken input_transfer_token = 0; + + // Configuration from the local Context. Used to construct a remote Context. + // This will be sent through RPC metadata headers, and is only documented here + // for the record. + // android.content.resConfiguration configuration = 0; + + // The display id of the display to render the delegated UI on. + int32 display_id = 1; + + common.DelegatedUiClientId client_id = 2; + + common.DelegatedUiClientInputs client_inputs = 3; + + // Unique session uuid that was created by the prepare call. + // If prepare was not called beforehand, this field can be left empty and + // a new uuid will be generated by the server + string session_uuid = 1; +} + +message DelegatedUiCreateResponse { + // Surface package of the remote SurfaceControlViewHost. The caller needs to + // pass this into SurfaceView#setChildSurfacePackage to render the delegated + // UI. This will be sent through RPC metadata headers, and is only documented + // here for the record. + // android.view.SurfaceControlViewHost.SurfacePackage surface_package = 0; + + // Unique session uuid that will be constant over the course of this session. + // This is the same uuid that was passed in the request, or a new uuid if + // none was passed in. + string session_uuid = 1; +} diff --git a/src/com/google/android/as/oss/delegatedui/api/infra/uiservice/ui_service_data_egress.proto b/src/com/google/android/as/oss/delegatedui/api/infra/uiservice/ui_service_data_egress.proto new file mode 100644 index 00000000..9c6fc928 --- /dev/null +++ b/src/com/google/android/as/oss/delegatedui/api/infra/uiservice/ui_service_data_egress.proto @@ -0,0 +1,28 @@ +// Copyright 2025 Google LLC +// +// 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 +// +// http://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. + +edition = "2023"; + +package com.google.android.as.oss.delegatedui.api.infra.uiservice; + +import "src/com/google/android/as/oss/delegatedui/api/integration/egress/egress_integration.proto"; + +option java_package = "com.google.android.as.oss.delegatedui.api.infra.uiservice"; +option java_multiple_files = true; + +// Asynchronous event - notifies on consented data egress from delegated UI. +message DelegatedUiDataEgressResponse { + // The data item that was consented to be egressed. + integration.egress.DelegatedUiEgressData egress_data = 1; +} diff --git a/src/com/google/android/as/oss/delegatedui/api/infra/uiservice/ui_service_invalidate.proto b/src/com/google/android/as/oss/delegatedui/api/infra/uiservice/ui_service_invalidate.proto new file mode 100644 index 00000000..932cdcd6 --- /dev/null +++ b/src/com/google/android/as/oss/delegatedui/api/infra/uiservice/ui_service_invalidate.proto @@ -0,0 +1,32 @@ +// Copyright 2025 Google LLC +// +// 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 +// +// http://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. + +edition = "2023"; + +package com.google.android.as.oss.delegatedui.api.infra.uiservice; + +option java_package = "com.google.android.as.oss.delegatedui.api.infra.uiservice"; +option java_multiple_files = true; + +// Invalidates an active delegated UI session, causing it to re-render itself. +message DelegatedUiInvalidateRequest { + // The delegated UI session to invalidate. + string session_uuid = 1; +} + +message DelegatedUiInvalidateResponse { + // True if the active delegated UI session was successfully invalidated. False + // if the session was not found. + bool success = 1; +} diff --git a/src/com/google/android/as/oss/delegatedui/api/infra/uiservice/ui_service_nested_scroll.proto b/src/com/google/android/as/oss/delegatedui/api/infra/uiservice/ui_service_nested_scroll.proto new file mode 100644 index 00000000..6d20e162 --- /dev/null +++ b/src/com/google/android/as/oss/delegatedui/api/infra/uiservice/ui_service_nested_scroll.proto @@ -0,0 +1,55 @@ +// Copyright 2025 Google LLC +// +// 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 +// +// http://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. + +edition = "2023"; + +package com.google.android.as.oss.delegatedui.api.infra.uiservice; + +option java_package = "com.google.android.as.oss.delegatedui.api.infra.uiservice"; +option java_multiple_files = true; + +// Remote UI reports a nested scroll event. +message DelegatedUiNestedScrollResponse { + float scroll_x = 1; // Deprecated. + float scroll_y = 2; // Deprecated. + + oneof event { + NestedScrollStart nested_scroll_start = 3; + NestedScrollDelta nested_scroll_delta = 4; + NestedScrollStop nested_scroll_stop = 5; + } +} + +// Nested scrolling control signal: start. +message NestedScrollStart { + int32 axes = 1; +} + +// Nested scrolling delta. +message NestedScrollDelta { + float scroll_x = 1; + float scroll_y = 2; +} + +// Nested scrolling control signal: stop. May include nested fling velocity. +message NestedScrollStop { + float fling_x = 1; + float fling_y = 2; +} + +// Remote UI reports a nested fling event. Deprecated. +message DelegatedUiNestedFlingResponse { + float velocity_x = 1; + float velocity_y = 2; +} diff --git a/src/com/google/android/as/oss/delegatedui/api/infra/uiservice/ui_service_prepare.proto b/src/com/google/android/as/oss/delegatedui/api/infra/uiservice/ui_service_prepare.proto new file mode 100644 index 00000000..43efa869 --- /dev/null +++ b/src/com/google/android/as/oss/delegatedui/api/infra/uiservice/ui_service_prepare.proto @@ -0,0 +1,53 @@ +// Copyright 2025 Google LLC +// +// 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 +// +// http://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. + +edition = "2023"; + +package com.google.android.as.oss.delegatedui.api.infra.uiservice; + +import "src/com/google/android/as/oss/delegatedui/api/common/common.proto"; + +option java_package = "com.google.android.as.oss.delegatedui.api.infra.uiservice"; +option java_multiple_files = true; + +// Prepares for a delegated UI session for these inputs. +// Next ID: 6 +message DelegatedUiPrepareRequest { + // Configuration from the local Context. Used to construct a remote Context. + // This will be sent through RPC metadata headers, and is only documented here + // for the record. + // android.content.res.Configuration configuration = 0; + + common.DelegatedUiClientId client_id = 1; + common.DelegatedUiIngressData ingress_data = 2; + common.DelegatedUiDataProviderInfo data_provider_info = 5; + + // The android.view.View.MeasureSpec of the delegated UI that the client's + // local UI can support. + int32 measure_spec_width = 3; + int32 measure_spec_height = 4; +} + +message DelegatedUiPrepareResponse { + // True if remote content is available for the given delegated UI request. + bool is_content_available = 1; + + // If remote content is available, remote UI reports the size it will render + // at. Respects the MeasureSpec given in the request. + int32 desired_width = 2; + int32 desired_height = 3; + + // Unique session uuid that will be constant over the course of this session. + string session_uuid = 1; +} diff --git a/src/com/google/android/as/oss/delegatedui/api/infra/uiservice/ui_service_size_change.proto b/src/com/google/android/as/oss/delegatedui/api/infra/uiservice/ui_service_size_change.proto new file mode 100644 index 00000000..8b004bff --- /dev/null +++ b/src/com/google/android/as/oss/delegatedui/api/infra/uiservice/ui_service_size_change.proto @@ -0,0 +1,27 @@ +// Copyright 2025 Google LLC +// +// 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 +// +// http://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. + +edition = "2023"; + +package com.google.android.as.oss.delegatedui.api.infra.uiservice; + +option java_package = "com.google.android.as.oss.delegatedui.api.infra.uiservice"; +option java_multiple_files = true; + +// Remote UI reports a change in rendered size. Respects the MeasureSpec given +// in the most recent create/update request. +message DelegatedUiSizeChangeResponse { + int32 updated_width = 1; + int32 updated_height = 2; +} diff --git a/src/com/google/android/as/oss/delegatedui/api/infra/uiservice/ui_service_update.proto b/src/com/google/android/as/oss/delegatedui/api/infra/uiservice/ui_service_update.proto new file mode 100644 index 00000000..8d104040 --- /dev/null +++ b/src/com/google/android/as/oss/delegatedui/api/infra/uiservice/ui_service_update.proto @@ -0,0 +1,31 @@ +// Copyright 2025 Google LLC +// +// 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 +// +// http://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. + +edition = "2023"; + +package com.google.android.as.oss.delegatedui.api.infra.uiservice; + +import "src/com/google/android/as/oss/delegatedui/api/common/common.proto"; + +option java_package = "com.google.android.as.oss.delegatedui.api.infra.uiservice"; +option java_multiple_files = true; + +// Update - send updates from client that that may impact the delegated UI +message DelegatedUiUpdateRequest { + common.DelegatedUiClientInputs client_inputs = 1; +} + +// Client needs to know when the update call has been handled, so send back an +// empty message. +message DelegatedUiUpdateResponse {} diff --git a/src/com/google/android/as/oss/delegatedui/api/integration/egress/BUILD b/src/com/google/android/as/oss/delegatedui/api/integration/egress/BUILD new file mode 100644 index 00000000..4ce0c38d --- /dev/null +++ b/src/com/google/android/as/oss/delegatedui/api/integration/egress/BUILD @@ -0,0 +1,44 @@ +# Copyright 2025 Google LLC +# +# 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 +# +# http://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. + +load("//third_party/protobuf/bazel:java_lite_proto_library.bzl", "java_lite_proto_library") +load("//third_party/protobuf/bazel:proto_library.bzl", "proto_library") +load("//third_party/protobuf/build_defs:kt_jvm_proto_library.bzl", "kt_jvm_lite_proto_library") + +package( + default_visibility = [ + "//visibility:public", + ], +) + +proto_library( + name = "egress_proto", + srcs = ["egress_integration.proto"], + deps = [ + "//src/com/google/android/as/oss/delegatedui/api/integration/egress/airflow:airflow_egress_integration_proto", + "//src/com/google/android/as/oss/delegatedui/api/integration/egress/bugle:bugle_egress_proto", + "//src/com/google/android/as/oss/delegatedui/api/integration/egress/example:example_egress_proto", + "//src/com/google/android/as/oss/delegatedui/api/integration/egress/sundog:sundog_egress_proto", + ], +) + +java_lite_proto_library( + name = "egress_java_proto_lite", + deps = [":egress_proto"], +) + +kt_jvm_lite_proto_library( + name = "egress_kt_proto_lite", + deps = [":egress_proto"], +) diff --git a/src/com/google/android/as/oss/delegatedui/api/integration/egress/airflow/BUILD b/src/com/google/android/as/oss/delegatedui/api/integration/egress/airflow/BUILD new file mode 100644 index 00000000..940e33e1 --- /dev/null +++ b/src/com/google/android/as/oss/delegatedui/api/integration/egress/airflow/BUILD @@ -0,0 +1,39 @@ +# Copyright 2025 Google LLC +# +# 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 +# +# http://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. + +load("//third_party/protobuf/bazel:java_proto_library.bzl", "java_proto_library") +load("//third_party/protobuf/bazel:proto_library.bzl", "proto_library") +load("//third_party/protobuf/build_defs:kt_jvm_proto_library.bzl", "kt_jvm_lite_proto_library") + +package( + default_visibility = [ + "//visibility:public", + ], +) + +proto_library( + name = "airflow_egress_integration_proto", + srcs = ["airflow_egress_integration.proto"], + deps = [""], +) + +java_proto_library( + name = "airflow_egress_integration_java_proto", + deps = [":airflow_egress_integration_proto"], +) + +kt_jvm_lite_proto_library( + name = "airflow_egress_integration_kt_proto_lite", + deps = [":airflow_egress_integration_proto"], +) diff --git a/src/com/google/android/as/oss/delegatedui/api/integration/egress/airflow/airflow_egress_integration.proto b/src/com/google/android/as/oss/delegatedui/api/integration/egress/airflow/airflow_egress_integration.proto new file mode 100644 index 00000000..887ae6ea --- /dev/null +++ b/src/com/google/android/as/oss/delegatedui/api/integration/egress/airflow/airflow_egress_integration.proto @@ -0,0 +1,44 @@ +// Copyright 2025 Google LLC +// +// 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 +// +// http://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. + +edition = "2023"; + +package com.google.android.as.oss.delegatedui.api.integration.egress.airflow; + +option java_multiple_files = true; +option java_package = "com.google.android.as.oss.delegatedui.api.integration.egress.airflow"; + +message AirflowEgressData { + oneof events { + PreparingDataEvent preparing_data_event = 1; + DataReadyEvent data_ready_event = 2; + ErrorEvent error_event = 3; + } +} + +// Event to notify the UI that data is coming. +message PreparingDataEvent {} + +message DataReadyEvent { + string data = 1; +} + +message ErrorEvent { + enum StatusCode { + STATUS_CODE_UNSPECIFIED = 0; + INTERNAL = 1; + DEADLINE_EXCEEDED = 2; + } + StatusCode status_code = 1; +} diff --git a/src/com/google/android/as/oss/delegatedui/api/integration/egress/bugle/BUILD b/src/com/google/android/as/oss/delegatedui/api/integration/egress/bugle/BUILD new file mode 100644 index 00000000..731a1596 --- /dev/null +++ b/src/com/google/android/as/oss/delegatedui/api/integration/egress/bugle/BUILD @@ -0,0 +1,41 @@ +# Copyright 2025 Google LLC +# +# 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 +# +# http://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. + +load("//third_party/protobuf/bazel:java_lite_proto_library.bzl", "java_lite_proto_library") +load("//third_party/protobuf/bazel:proto_library.bzl", "proto_library") +load("//third_party/protobuf/build_defs:kt_jvm_proto_library.bzl", "kt_jvm_lite_proto_library") + +package( + default_visibility = [ + "//visibility:public", + ], +) + +proto_library( + name = "bugle_egress_proto", + srcs = ["bugle_egress_integration.proto"], + deps = [ + "", + ], +) + +java_lite_proto_library( + name = "bugle_egress_java_proto_lite", + deps = [":bugle_egress_proto"], +) + +kt_jvm_lite_proto_library( + name = "bugle_egress_kt_proto_lite", + deps = [":bugle_egress_proto"], +) diff --git a/src/com/google/android/as/oss/delegatedui/api/integration/egress/bugle/bugle_egress_integration.proto b/src/com/google/android/as/oss/delegatedui/api/integration/egress/bugle/bugle_egress_integration.proto new file mode 100644 index 00000000..8959b99f --- /dev/null +++ b/src/com/google/android/as/oss/delegatedui/api/integration/egress/bugle/bugle_egress_integration.proto @@ -0,0 +1,31 @@ +// Copyright 2025 Google LLC +// +// 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 +// +// http://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. + +edition = "2023"; + +package com.google.android.as.oss.delegatedui.api.integration.egress.bugle; + + +option java_package = "com.google.android.as.oss.delegatedui.api.integration.egress.bugle"; +option java_multiple_files = true; + +// This is for search term data as well as bugle suggestion data. +message BugleEgressData { + oneof data { + string suggestion_text = 1 + ; // bugle suggestion text + string photo_search_query = 2 + ; // bugle photo search query + } +} diff --git a/src/com/google/android/as/oss/delegatedui/api/integration/egress/egress_integration.proto b/src/com/google/android/as/oss/delegatedui/api/integration/egress/egress_integration.proto new file mode 100644 index 00000000..fc4bdf52 --- /dev/null +++ b/src/com/google/android/as/oss/delegatedui/api/integration/egress/egress_integration.proto @@ -0,0 +1,39 @@ +// Copyright 2025 Google LLC +// +// 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 +// +// http://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. + +edition = "2023"; + +package com.google.android.as.oss.delegatedui.api.integration.egress; + +import "src/com/google/android/as/oss/delegatedui/api/integration/egress/airflow/airflow_egress_integration.proto"; +import "src/com/google/android/as/oss/delegatedui/api/integration/egress/bugle/bugle_egress_integration.proto"; +import "src/com/google/android/as/oss/delegatedui/api/integration/egress/example/example_egress_integration.proto"; +import "src/com/google/android/as/oss/delegatedui/api/integration/egress/sundog/sundog_egress_integration.proto"; + +option java_package = "com.google.android.as.oss.delegatedui.api.integration.egress"; +option java_multiple_files = true; + +// Approved egress data formats from the delegated UI service. + +message DelegatedUiEgressData { + oneof egress_data { + example.ExampleSourceClickEgressData example_source_click_egress_data = 1; + bugle.BugleEgressData bugle_egress_data = 2; + sundog.SundogEventClickEgressData sundog_event_click_egress_data = 3; + sundog.SundogLocationClickEgressData sundog_location_click_egress_data = 4; + airflow.AirflowEgressData airflow_egress_data = 6; + // New CUJ data egress types should be added above. + } + reserved 5; +} diff --git a/src/com/google/android/as/oss/delegatedui/api/integration/egress/example/BUILD b/src/com/google/android/as/oss/delegatedui/api/integration/egress/example/BUILD new file mode 100644 index 00000000..5daf0379 --- /dev/null +++ b/src/com/google/android/as/oss/delegatedui/api/integration/egress/example/BUILD @@ -0,0 +1,40 @@ +# Copyright 2025 Google LLC +# +# 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 +# +# http://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. + +load("//third_party/protobuf/bazel:java_lite_proto_library.bzl", "java_lite_proto_library") +load("//third_party/protobuf/bazel:proto_library.bzl", "proto_library") +load("//third_party/protobuf/build_defs:kt_jvm_proto_library.bzl", "kt_jvm_lite_proto_library") + +package( + default_visibility = [ + "//visibility:public", + ], +) + +proto_library( + name = "example_egress_proto", + srcs = ["example_egress_integration.proto"], + deps = [ + ], +) + +java_lite_proto_library( + name = "example_egress_java_proto_lite", + deps = [":example_egress_proto"], +) + +kt_jvm_lite_proto_library( + name = "example_egress_kt_proto_lite", + deps = [":example_egress_proto"], +) diff --git a/src/com/google/android/as/oss/delegatedui/api/integration/egress/example/example_egress_integration.proto b/src/com/google/android/as/oss/delegatedui/api/integration/egress/example/example_egress_integration.proto new file mode 100644 index 00000000..3958a368 --- /dev/null +++ b/src/com/google/android/as/oss/delegatedui/api/integration/egress/example/example_egress_integration.proto @@ -0,0 +1,32 @@ +// Copyright 2025 Google LLC +// +// 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 +// +// http://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. + +edition = "2023"; + +package com.google.android.as.oss.delegatedui.api.integration.egress.example; + +option java_package = "com.google.android.as.oss.delegatedui.api.integration.egress.example"; +option java_multiple_files = true; + +// When user clicks on the source chip. +message ExampleSourceClickEgressData { + string label = 1; + string message = 2; + string icon_type = 3; +} + +// When user clicks on the action button. +message ExampleActionClickEgressData { + string action_deeplink = 1; +} diff --git a/src/com/google/android/as/oss/delegatedui/api/integration/egress/sundog/BUILD b/src/com/google/android/as/oss/delegatedui/api/integration/egress/sundog/BUILD new file mode 100644 index 00000000..37b6c27c --- /dev/null +++ b/src/com/google/android/as/oss/delegatedui/api/integration/egress/sundog/BUILD @@ -0,0 +1,42 @@ +# Copyright 2025 Google LLC +# +# 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 +# +# http://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. + +load("//third_party/protobuf/bazel:java_lite_proto_library.bzl", "java_lite_proto_library") +load("//third_party/protobuf/bazel:proto_library.bzl", "proto_library") +load("//third_party/protobuf/build_defs:kt_jvm_proto_library.bzl", "kt_jvm_lite_proto_library") + +package( + default_visibility = [ + "//visibility:public", + ], +) + +proto_library( + name = "sundog_egress_proto", + srcs = ["sundog_egress_integration.proto"], + deps = [ + "", + "@com_google_protobuf//:timestamp_proto", + ], +) + +java_lite_proto_library( + name = "sundog_egress_java_proto_lite", + deps = [":sundog_egress_proto"], +) + +kt_jvm_lite_proto_library( + name = "sundog_egress_kt_proto_lite", + deps = [":sundog_egress_proto"], +) diff --git a/src/com/google/android/as/oss/delegatedui/api/integration/egress/sundog/sundog_egress_integration.proto b/src/com/google/android/as/oss/delegatedui/api/integration/egress/sundog/sundog_egress_integration.proto new file mode 100644 index 00000000..fd2494ae --- /dev/null +++ b/src/com/google/android/as/oss/delegatedui/api/integration/egress/sundog/sundog_egress_integration.proto @@ -0,0 +1,40 @@ +// Copyright 2025 Google LLC +// +// 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 +// +// http://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. + +edition = "2023"; + +package com.google.android.as.oss.delegatedui.api.integration.egress.sundog; + +import "google/protobuf/timestamp.proto"; + + +option java_package = "com.google.android.as.oss.delegatedui.api.integration.egress.sundog"; +option java_multiple_files = true; + +// When user clicks on the chip. +// Next Id: 5 +message SundogEventClickEgressData { + string event_title = 1; + string location_name = 2; + .google.protobuf.Timestamp start_time = 3; + .google.protobuf.Timestamp end_time = 4; +} + +// When user clicks on the chip. +// Next Id: 4 +message SundogLocationClickEgressData { + string location_name = 1; + double latitude = 2; + double longitude = 3; +} diff --git a/src/com/google/android/as/oss/delegatedui/api/integration/templates/BUILD b/src/com/google/android/as/oss/delegatedui/api/integration/templates/BUILD new file mode 100644 index 00000000..11bb0666 --- /dev/null +++ b/src/com/google/android/as/oss/delegatedui/api/integration/templates/BUILD @@ -0,0 +1,67 @@ +# Copyright 2025 Google LLC +# +# 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 +# +# http://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. + +load("//third_party/bazel_rules/rules_python/python:proto.bzl", "py_proto_library") +load("//third_party/protobuf/bazel:java_lite_proto_library.bzl", "java_lite_proto_library") +load("//third_party/protobuf/bazel:proto_library.bzl", "proto_library") +load("//third_party/protobuf/build_defs:kt_jvm_proto_library.bzl", "kt_jvm_lite_proto_library") + +package( + default_visibility = [ + "//visibility:public", + ], +) + +proto_library( + name = "templates_proto", + srcs = ["template_integration.proto"], + deps = [ + "//src/com/google/android/as/oss/delegatedui/api/integration/templates/airflow:airflow_template_proto", + "//src/com/google/android/as/oss/delegatedui/api/integration/templates/beacon:beacon_template_proto", + "//src/com/google/android/as/oss/delegatedui/api/integration/templates/bugle:bugle_template_proto", + "//src/com/google/android/as/oss/delegatedui/api/integration/templates/example:example_template_proto", + "//src/com/google/android/as/oss/delegatedui/api/integration/templates/subzero:subzero_template_proto", + "//src/com/google/android/as/oss/delegatedui/api/integration/templates/sundog:sundog_template_proto", + ], +) + +java_lite_proto_library( + name = "templates_java_proto_lite", + deps = [":templates_proto"], +) + +kt_jvm_lite_proto_library( + name = "templates_kt_proto_lite", + deps = [":templates_proto"], +) + +py_proto_library( + name = "templates_py_pb2", + deps = [":templates_proto"], +) + +proto_library( + name = "ui_id_token_proto", + srcs = ["ui_id_token.proto"], +) + +java_lite_proto_library( + name = "ui_id_token_java_proto_lite", + deps = [":ui_id_token_proto"], +) + +kt_jvm_lite_proto_library( + name = "ui_id_token_kt_proto_lite", + deps = [":ui_id_token_proto"], +) diff --git a/src/com/google/android/as/oss/delegatedui/api/integration/templates/airflow/BUILD b/src/com/google/android/as/oss/delegatedui/api/integration/templates/airflow/BUILD new file mode 100644 index 00000000..631653d5 --- /dev/null +++ b/src/com/google/android/as/oss/delegatedui/api/integration/templates/airflow/BUILD @@ -0,0 +1,42 @@ +# Copyright 2025 Google LLC +# +# 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 +# +# http://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. + +load("//third_party/protobuf/bazel:java_lite_proto_library.bzl", "java_lite_proto_library") +load("//third_party/protobuf/bazel:proto_library.bzl", "proto_library") +load("//third_party/protobuf/build_defs:kt_jvm_proto_library.bzl", "kt_jvm_lite_proto_library") + +package( + default_visibility = [ + "//visibility:public", + ], +) + +proto_library( + name = "airflow_template_proto", + srcs = ["airflow_template_integration.proto"], + deps = [ + "", + "//src/com/google/android/as/oss/delegatedui/api/integration/templates:ui_id_token_proto", + ], +) + +java_lite_proto_library( + name = "airflow_template_java_proto_lite", + deps = [":airflow_template_proto"], +) + +kt_jvm_lite_proto_library( + name = "airflow_template_kt_proto_lite", + deps = [":airflow_template_proto"], +) diff --git a/src/com/google/android/as/oss/delegatedui/api/integration/templates/airflow/airflow_template_integration.proto b/src/com/google/android/as/oss/delegatedui/api/integration/templates/airflow/airflow_template_integration.proto new file mode 100644 index 00000000..34d0c27e --- /dev/null +++ b/src/com/google/android/as/oss/delegatedui/api/integration/templates/airflow/airflow_template_integration.proto @@ -0,0 +1,36 @@ +// Copyright 2025 Google LLC +// +// 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 +// +// http://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. + +edition = "2023"; + +package com.google.android.as.oss.delegatedui.api.integration.templates.airflow; + +import "src/com/google/android/as/oss/delegatedui/api/integration/templates/ui_id_token.proto"; + +option java_package = "com.google.android.as.oss.delegatedui.api.integration.templates.airflow"; +option java_multiple_files = true; + +message AirflowTemplateData { + // Decorative icon to be displayed in the UI. + // + // This will be sent through RPC metadata headers, and is only documented here + // for the record. + // Bitmap image = 0; + + // The text to display in the UI. + string label = 1; + + // Token that uniquely identifies the UI element. + UiIdToken ui_id_token = 2; +} diff --git a/src/com/google/android/as/oss/delegatedui/api/integration/templates/beacon/BUILD b/src/com/google/android/as/oss/delegatedui/api/integration/templates/beacon/BUILD new file mode 100644 index 00000000..0da840ee --- /dev/null +++ b/src/com/google/android/as/oss/delegatedui/api/integration/templates/beacon/BUILD @@ -0,0 +1,50 @@ +# Copyright 2025 Google LLC +# +# 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 +# +# http://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. + +load("//third_party/bazel_rules/rules_python/python:proto.bzl", "py_proto_library") +load("//third_party/protobuf/bazel:java_lite_proto_library.bzl", "java_lite_proto_library") +load("//third_party/protobuf/bazel:proto_library.bzl", "proto_library") +load("//third_party/protobuf/build_defs:kt_jvm_proto_library.bzl", "kt_jvm_lite_proto_library") + +package( + default_visibility = [ + "//visibility:public", + ], +) + +proto_library( + name = "beacon_template_proto", + srcs = ["beacon_template_integration.proto"], + deps = [ + "", + "//src/com/google/android/as/oss/delegatedui/api/integration/templates:ui_id_token_proto", + "//src/com/google/android/as/oss/feedback/proto:entity_feedback_dialog_data_proto", + "//src/com/google/android/as/oss/feedback/proto:feedback_dialog_common_data_proto", + ], +) + +java_lite_proto_library( + name = "beacon_template_java_proto_lite", + deps = [":beacon_template_proto"], +) + +kt_jvm_lite_proto_library( + name = "beacon_template_kt_proto_lite", + deps = [":beacon_template_proto"], +) + +py_proto_library( + name = "beacon_template_py_pb2", + deps = [":beacon_template_proto"], +) diff --git a/src/com/google/android/as/oss/delegatedui/api/integration/templates/beacon/beacon_template_integration.proto b/src/com/google/android/as/oss/delegatedui/api/integration/templates/beacon/beacon_template_integration.proto new file mode 100644 index 00000000..758b033a --- /dev/null +++ b/src/com/google/android/as/oss/delegatedui/api/integration/templates/beacon/beacon_template_integration.proto @@ -0,0 +1,259 @@ +// Copyright 2025 Google LLC +// +// 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 +// +// http://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. + +edition = "2023"; + +package com.google.android.as.oss.delegatedui.api.integration.templates.beacon; + +import "src/com/google/android/as/oss/delegatedui/api/integration/templates/ui_id_token.proto"; +import "src/com/google/android/as/oss/feedback/proto/entity_feedback_dialog_data.proto"; +import "src/com/google/android/as/oss/feedback/proto/feedback_dialog_common_data.proto"; + + +option features.field_presence = IMPLICIT; +option java_package = "com.google.android.as.oss.delegatedui.api.integration.templates.beacon"; +option java_multiple_files = true; + +// Beacon template data. +message BeaconTemplateData { + BeaconWidget widget = 1 [features.field_presence = EXPLICIT]; + // The unique ID of the remote UI session. + string session_uuid = 2; +} + +// Represents the entire widget, the entry point to to all beacon template data +// Next ID: 16 +message BeaconWidget { + repeated BeaconGeneralCard general_cards = 1 [deprecated = true]; + repeated BeaconGeneralCard general_cards_emails = 10; + repeated BeaconGeneralCard general_cards_messages = 11; + repeated BeaconDetailedCard detailed_cards = 2; + BeaconAiDisclaimer ai_disclaimer_with_link = 13; + // A static string call to action button. When clicked, the widget will expand + // to show more results + string cta_display_more_results = 4; + // A static string call to action button. When clicked, the widget will + // collapse to show fewer results + string cta_display_fewer_results = 12; + // The background color of the widget + int64 background_color = 5 [features.field_presence = EXPLICIT]; + + // The UiIdTokens for the widget's UI elements. + // TODO: populate these UiIdTokens. + UiIdToken general_card_ui_id = 6; + UiIdToken detailed_card_ui_id = 7; + UiIdToken disclaimer_button_ui_id = 8; + UiIdToken show_more_results_button_ui_id = 9; + + BeaconGenericContentDescriptions generic_content_descriptions = 14; + + // Deprecated fields + string ai_disclaimer = 3 [deprecated = true]; + feedback.api.FeedbackDialogCommonData feedback_dialog_common_data = 15 + [deprecated = true]; +} + + // Deprecated fields + string good_feedback_button_accessibility_content_description = 5 + [deprecated = true]; + string bad_feedback_button_accessibility_content_description = 6 + [deprecated = true]; + string card_expanded_accessibility_content_description = 7 + [ deprecated = true]; + string card_collapsed_accessibility_content_description = 8 + [ deprecated = true]; + string card_expanded_accessibility_state_description = 18 [deprecated = true]; + string card_collapsed_accessibility_state_description = 19 + [deprecated = true]; + string card_collapsed_accessibility_click_label = 20 [deprecated = true]; + string card_expanded_accessibility_click_label = 21 [deprecated = true]; + string feedback_submitted_state_description = 15 [deprecated = true]; + string feedback_not_submitted_state_description = 16 [deprecated = true]; +} + +// Represents a grid-based layout in a more "detailed" view +// Next ID: 17 +message BeaconDetailedCard { + // An icon to be displayed for each card. Helps the user easily understand the + // type of data in the card + enum Icon { + UNKNOWN = 0; + RECEIPT = 1; + EVENT = 2; + ORDERS = 3; + TRANSPORT = 4; + LODGING = 5; + } + // The title of the card + string title = 1; + Icon icon = 2 [deprecated = true]; + repeated BeaconRow rows = 3; + BeaconResponseSource data_source = 4 [features.field_presence = EXPLICIT]; + + // The UiIdTokens for the card's UI elements. + UiIdToken good_feedback_button_ui_id = 7; + UiIdToken bad_feedback_button_ui_id = 8; + UiIdToken source_navigation_button_ui_id = 9; + + // The entity feedback data for the card + feedback.api.EntityFeedbackDialogData thumbs_up_entity_feedback_dialog_data = + 15; + feedback.api.EntityFeedbackDialogData + thumbs_down_entity_feedback_dialog_data = 16; + + // The unique identifier of the card within the list. This is used to uniquely + // identify the source data when rendered in a list + string list_uuid = 10; + + // Deprecated fields + string good_feedback_button_accessibility_content_description = 5 + [deprecated = true]; + string bad_feedback_button_accessibility_content_description = 6 + [deprecated = true]; + string feedback_submitted_state_description = 12 [deprecated = true]; + string feedback_not_submitted_state_description = 13 [deprecated = true]; + string card_sentiment_label = 11 [deprecated = true]; + string entity_content = 14 [deprecated = true]; +} + +// Represents a row in the widget's grid. +message BeaconRow { + oneof row_layout { + BeaconFullLengthRow full_length = 1; + BeaconTwoItemRow half_half_split = 2; + BeaconTwoItemRow seven_three_split = 3; + } +} + +// Single item in the row, always takes up the full width of a card +message BeaconFullLengthRow { + BeaconRowItem item = 1; +} + +// Two items in the row, could be split evenly or unevently in size, but the two +// items together will always take up the full width of a card +message BeaconTwoItemRow { + BeaconRowItem item_one = 1; + BeaconRowItem item_two = 2; +} + +// The content inside a row item +message BeaconRowItem { + // Informs the user what the item represents + BeaconRowItemText label = 1; + // The main content of the item + BeaconRowItemText content = 2; + // A quick summary of the content + BeaconRowItemText content_summary = 3 [features.field_presence = EXPLICIT]; + + // The content description for the row item. This is used by screen readers + // to announce the row item to the user. It will be a combination of the + // label, content, and content summary, in the format "