diff --git a/cars/build.gradle.kts b/cars/build.gradle.kts index 74cd8b44d..6c839271c 100644 --- a/cars/build.gradle.kts +++ b/cars/build.gradle.kts @@ -33,9 +33,12 @@ android { } dependencies { + implementation(libs.androidx.biometric.ktx) implementation(libs.androidx.core.ktx) implementation(libs.androidx.appcompat) implementation(libs.androidx.car) + implementation(libs.androidx.media3.common.ktx) + implementation(libs.androidx.media3.exoplayer) implementation(libs.google.android.material) testImplementation(libs.junit) androidTestImplementation(libs.androidx.test.ext.junit) diff --git a/cars/src/main/java/com/example/cars/parked/Browser.kt b/cars/src/main/java/com/example/cars/parked/Browser.kt index 2bcab1c51..25bf71f4b 100644 --- a/cars/src/main/java/com/example/cars/parked/Browser.kt +++ b/cars/src/main/java/com/example/cars/parked/Browser.kt @@ -20,7 +20,10 @@ import android.app.KeyguardManager import android.content.Context import android.content.Intent import android.provider.Settings +import androidx.biometric.BiometricManager +import androidx.biometric.BiometricPrompt import androidx.core.content.getSystemService +import com.example.cars.R fun isDeviceCredentialSet(context: Context) { // [START android_cars_parked_browser_device_credential] @@ -37,3 +40,24 @@ fun openSecurityScreen(context: Context) { ) // [END android_cars_parked_browser_security_settings] } + +fun fullFlow(context: Context) { + // [START android_cars_parked_browser_protect_sensitive_data] + val keyguardManager = context.getSystemService() + val isDeviceSecure = keyguardManager?.isDeviceSecure == true + lateinit var biometricPrompt: BiometricPrompt + + if (!isDeviceSecure) { + context.startActivity( + Intent(Settings.ACTION_SECURITY_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + ) + } else { + val promptInfo = BiometricPrompt.PromptInfo.Builder() + .setTitle(context.getString(R.string.auth_title)) + .setSubtitle(context.getString(R.string.sync_data_to_car_notice)) + .setAllowedAuthenticators(BiometricManager.Authenticators.DEVICE_CREDENTIAL) + .build() + biometricPrompt.authenticate(promptInfo) + } + // [END android_cars_parked_browser_protect_sensitive_data] +} diff --git a/cars/src/main/java/com/example/cars/parked/CarAwarePlayer.kt b/cars/src/main/java/com/example/cars/parked/CarAwarePlayer.kt new file mode 100644 index 000000000..333996d51 --- /dev/null +++ b/cars/src/main/java/com/example/cars/parked/CarAwarePlayer.kt @@ -0,0 +1,78 @@ +/* + * Copyright 2026 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.cars.parked + +import android.car.Car +import android.car.drivingstate.CarUxRestrictions +import android.car.drivingstate.CarUxRestrictionsManager +import android.content.Context +import android.content.pm.PackageManager +import androidx.media3.common.ForwardingSimpleBasePlayer +import androidx.media3.common.util.UnstableApi +import androidx.media3.exoplayer.ExoPlayer +import com.google.common.util.concurrent.ListenableFuture + +// [START android_cars_parked_car_aware_player] +@UnstableApi +class CarAwarePlayer(context: Context) : + ForwardingSimpleBasePlayer(ExoPlayer.Builder(context).build()) { + private var shouldPreventPlay = false + private var pausedByUxRestrictions = false + private lateinit var carUxRestrictionsManager: CarUxRestrictionsManager + + init { + if (context.packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) { + val car = Car.createCar(context) + carUxRestrictionsManager = + car.getCarManager(Car.CAR_UX_RESTRICTION_SERVICE) as CarUxRestrictionsManager + shouldPreventPlay = + carUxRestrictionsManager.currentCarUxRestrictions.isRequiresDistractionOptimization + invalidateState() + + carUxRestrictionsManager.registerListener { restrictions: CarUxRestrictions -> + shouldPreventPlay = restrictions.isRequiresDistractionOptimization + if (!shouldPreventPlay && pausedByUxRestrictions) { + handleSetPlayWhenReady(true) + invalidateState() + } else if (shouldPreventPlay && isPlaying) { + pausedByUxRestrictions = true + handleSetPlayWhenReady(false) + invalidateState() + } + } + } + } + + override fun getState(): State { + val state = super.getState() + return state.buildUpon() + .setAvailableCommands( + state.availableCommands.buildUpon() + .removeIf(COMMAND_PLAY_PAUSE, shouldPreventPlay) + .build() + ) + .build() + } + + override fun handleRelease(): ListenableFuture<*> { + if (::carUxRestrictionsManager.isInitialized) { + carUxRestrictionsManager.unregisterListener() + } + return super.handleRelease() + } +} +// [END android_cars_parked_car_aware_player] diff --git a/cars/src/main/res/values-car-v30/integers.xml b/cars/src/main/res/values-car-v30/integers.xml new file mode 100644 index 000000000..a617cf551 --- /dev/null +++ b/cars/src/main/res/values-car-v30/integers.xml @@ -0,0 +1,21 @@ + + + + // [START android_cars_res_values_car_v30_integers] + 3 + // [END android_cars_res_values_car_v30_integers] + diff --git a/cars/src/main/res/values/integers.xml b/cars/src/main/res/values/integers.xml new file mode 100644 index 000000000..21e5374e7 --- /dev/null +++ b/cars/src/main/res/values/integers.xml @@ -0,0 +1,19 @@ + + + + 0 + diff --git a/cars/src/main/res/values/strings.xml b/cars/src/main/res/values/strings.xml index 20822c168..cfbce795a 100644 --- a/cars/src/main/res/values/strings.xml +++ b/cars/src/main/res/values/strings.xml @@ -16,4 +16,6 @@ --> Cars Snippets + Authenticate using device credentials + Authenticate to allow your data to be synced to the car diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 80ac3bada..39a34593d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -130,6 +130,8 @@ wearRemoteInteractions = "1.2.0" wearToolingPreview = "1.0.0" webkit = "1.16.0" wfp = "1.0.0" +media3CommonKtx = "1.11.1" +biometricKtx = "1.1.0" [libraries] accompanist-adaptive = "com.google.accompanist:accompanist-adaptive:0.37.3" @@ -345,6 +347,8 @@ validator-push = { module = "com.google.android.wearable.watchface.validator:val wear-compose-material = { module = "androidx.wear.compose:compose-material", version.ref = "wearComposeMaterial" } wear-compose-material3 = { module = "androidx.wear.compose:compose-material3", version.ref = "wearComposeMaterial3" } wear-compose-navigation3 = { module = "androidx.wear.compose:compose-navigation3", version.ref = "wearComposeNavigation3" } +androidx-media3-common-ktx = { group = "androidx.media3", name = "media3-common-ktx", version.ref = "media3CommonKtx" } +androidx-biometric-ktx = { group = "androidx.biometric", name = "biometric", version.ref = "biometricKtx" } [plugins] android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }