Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
7b8ee69
Moves watch device handling code to the Customize screen
garanj Jul 18, 2025
f752870
Moves watch device handling code to the Customize screen
garanj Jul 18, 2025
ca1d47a
Merge branch 'android:main' into main
garanj Aug 5, 2025
5c13c97
Adds watch face generation
garanj Aug 13, 2025
e50c375
Merge remote-tracking branch 'upstream/main'
garanj Aug 13, 2025
5b99f9d
Updates for changes in Androidify, plus some refactoring
garanj Aug 14, 2025
98fb2ba
Adds watch face generation test
garanj Aug 18, 2025
d743027
Merge remote-tracking branch 'upstream/main'
garanj Aug 18, 2025
2df44b7
Moves logo to PNG format
garanj Aug 18, 2025
d15ef05
Remove debug statements
garanj Aug 19, 2025
eb860dc
Adds comments
garanj Aug 19, 2025
6f9c9bb
Updates strings
garanj Aug 19, 2025
fdf0469
Remove unused assets
garanj Aug 19, 2025
d8670d4
Update standalone flag
garanj Aug 19, 2025
ab8e198
Refactors watch face module
garanj Aug 20, 2025
643b89c
Moves Wear activity launch to earlier in receive process
garanj Aug 20, 2025
6091986
Add multiple watch faces
garanj Aug 21, 2025
6fcd924
Updates watch face README
garanj Aug 21, 2025
314c61f
Adds check for timed-out messages
garanj Aug 22, 2025
78725ee
Updates watch face transfer UI
garanj Aug 26, 2025
42f20a5
Update previews
garanj Aug 26, 2025
04f45cd
Removes unnecessary text in UI
garanj Aug 27, 2025
6f990ac
Adds watch face to remote config
garanj Aug 27, 2025
ea1cca4
Sets watch face default config to false
garanj Aug 27, 2025
be424b9
Moves to using Android KeyStore for storing secrets
garanj Aug 28, 2025
d57e39e
Addresses comments
garanj Aug 28, 2025
01caf6b
Updates test RemoteConfigDataSource
garanj Aug 28, 2025
1240948
Addresses further comments
garanj Aug 28, 2025
b6129ed
Fix tests and address comments
garanj Aug 28, 2025
2dedaec
Addresses comments, fixes tests
garanj Aug 28, 2025
96c559d
Fix tests
garanj Aug 28, 2025
b2275c6
Addresses comments
garanj Aug 28, 2025
aefdbd6
Adds x86 Pack lib
garanj Aug 28, 2025
3ab4afc
Sets .so compatibility to API 26
garanj Aug 28, 2025
2ca4699
Update instrumented tests for watch face creation
garanj Aug 29, 2025
206a42a
Update R8 rules
garanj Aug 29, 2025
6ee6f14
Fix dependencies
garanj Aug 29, 2025
f9aaa5f
Merge remote-tracking branch 'upstream/main'
garanj Sep 2, 2025
a2ee7e7
Move bot image to WebP to reduce watch face size
garanj Sep 2, 2025
97a3fa0
Apply spotless
garanj Sep 2, 2025
77c9e5a
Addresses comments
garanj Sep 2, 2025
cf4dc2e
Update to stable Wear Compose
garanj Sep 2, 2025
cf8ff70
Updates padding for watch face screens
garanj Sep 3, 2025
348fba7
Merge branch 'android:main' into main
garanj Sep 4, 2025
96c333d
Merge remote-tracking branch 'upstream/main'
garanj Sep 4, 2025
45844d5
Apply spotless
garanj Sep 4, 2025
a786e88
Fixes test in CustomizeViewModelTest
garanj Sep 4, 2025
efe5c56
Sync repo and fix tests
garanj Sep 5, 2025
28f5269
Merge remote-tracking branch 'upstream/main'
garanj Sep 8, 2025
b3f773a
Merge remote-tracking branch 'upstream/main'
garanj Sep 8, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
10 changes: 8 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ android {
applicationId = "com.android.developers.androidify"
minSdk = libs.versions.minSdk.get().toInt()
targetSdk = 36
versionCode = 5
versionName = "1.1.3"
versionCode = libs.versions.appVersionCode.get().toInt()
versionName = libs.versions.appVersionName.get()

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
Expand Down Expand Up @@ -96,6 +96,12 @@ android {
isIncludeAndroidResources = true
}
}
// To avoid packaging conflicts when using bouncycastle
packaging {
resources {
excludes.add("META-INF/versions/9/OSGI-INF/MANIFEST.MF")
}
}
}

baselineProfile() {
Expand Down
10 changes: 10 additions & 0 deletions app/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,13 @@
-keepattributes Signature
-keepattributes *Annotation*
-keepattributes InnerClasses

# Ignore missing Java SE image classes from TwelveMonkeys ImageIO
-dontwarn javax.imageio.**

# Ignore missing Java SE XML classes from Xerces and other XML processors
-dontwarn org.apache.xml.resolver.**
-dontwarn org.eclipse.wst.xml.xpath2.processor.**

# Ignore missing Java SE annotation processing classes, often from libraries like AutoValue
-dontwarn javax.lang.model.**
8 changes: 8 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,14 @@

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- Required deeplink to make the app launchable from the watch -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="androidify"
android:host="launch" />
</intent-filter>
</activity>
<!-- need to use Theme.AppCompat -->
<activity
Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,4 @@ subprojects {
licenseHeaderFile(rootProject.file("spotless/copyright.xml"), "(<[^!?])")
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ interface RemoteConfigDataSource {
fun getImageGenerationEditsModelName(): String

fun getBotBackgroundInstructionPrompt(): String

fun watchfaceFeatureEnabled(): Boolean
}

@Singleton
Expand Down Expand Up @@ -111,4 +113,8 @@ class RemoteConfigDataSourceImpl @Inject constructor() : RemoteConfigDataSource
override fun getBotBackgroundInstructionPrompt(): String {
return remoteConfig.getString("bot_background_instruction_prompt")
}

override fun watchfaceFeatureEnabled(): Boolean {
return remoteConfig.getBoolean("watchface_feature_enabled")
}
}
4 changes: 4 additions & 0 deletions core/network/src/main/res/xml/remote_config_defaults.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
limitations under the License.
-->
<defaults>
<entry>
<key>watchface_feature_enabled</key>
<value>false</value>
</entry>
<entry>
<key>background_vibes_feature_enabled</key>
<value>false</value>
Expand Down
2 changes: 2 additions & 0 deletions core/testing/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ dependencies {
implementation(projects.core.network)
implementation(projects.core.util)
implementation(projects.feature.results)
implementation(projects.watchface)
implementation(projects.wear.common)

ksp(libs.hilt.compiler)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,8 @@ class TestRemoteConfigDataSource(private val useGeminiNano: Boolean) : RemoteCon
override fun getBotBackgroundInstructionPrompt(): String {
return "bot_background_instruction_prompt"
}

override fun watchfaceFeatureEnabled(): Boolean {
return true
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.android.developers.testing.repository

import android.graphics.Bitmap
import com.android.developers.androidify.watchface.WatchFaceAsset
import com.android.developers.androidify.watchface.transfer.WatchFaceInstallationRepository
import com.android.developers.androidify.wear.common.ConnectedWatch
import com.android.developers.androidify.wear.common.WatchFaceActivationStrategy
import com.android.developers.androidify.wear.common.WatchFaceInstallError
import com.android.developers.androidify.wear.common.WatchFaceInstallationStatus
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import java.util.UUID

class FakeWatchFaceInstallationRepository : WatchFaceInstallationRepository {
private val watch = ConnectedWatch(
nodeId = "1234",
displayName = "Pixel Watch",
hasAndroidify = true,
)

private val watchFaceAsset = WatchFaceAsset(
id = "watch_face_1",
previewPath = com.android.developers.androidify.results.R.drawable.watch_face_preview,
)

private var transferId = generateTransferId()

private val _connectedWatch = MutableStateFlow<ConnectedWatch?>(null)
override val connectedWatch = _connectedWatch.asStateFlow()

private val _watchFaceInstallationStatus =
MutableStateFlow<WatchFaceInstallationStatus>(WatchFaceInstallationStatus.NotStarted)
override val watchFaceInstallationUpdates = _watchFaceInstallationStatus.asStateFlow()

override suspend fun createAndTransferWatchFace(
connectedWatch: ConnectedWatch,
watchFace: WatchFaceAsset,
bitmap: Bitmap,
): WatchFaceInstallError {
transferId = generateTransferId()
delay(5_000)
_watchFaceInstallationStatus.value = WatchFaceInstallationStatus.Complete(
success = true,
otherNodeId = "5678",
transferId = transferId,
activationStrategy = WatchFaceActivationStrategy.NO_ACTION_NEEDED,
validationToken = "1234abcd",
installError = WatchFaceInstallError.NO_ERROR,
)
return WatchFaceInstallError.NO_ERROR
}

override suspend fun getAvailableWatchFaces(): Result<List<WatchFaceAsset>> {
return Result.success(listOf(watchFaceAsset))
}

override suspend fun resetInstallationStatus() {
transferId = generateTransferId()
_watchFaceInstallationStatus.value = WatchFaceInstallationStatus.NotStarted
}

private fun generateTransferId() = UUID.randomUUID().toString().take(8)

public fun setWatchAsConnected() {
_connectedWatch.value = watch
}
}
8 changes: 7 additions & 1 deletion core/theme/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,12 @@ android {
kotlinOptions {
jvmTarget = libs.versions.jvmTarget.get()
}

// To avoid packaging conflicts when using bouncycastle
packaging {
resources {
excludes.add("META-INF/versions/9/OSGI-INF/MANIFEST.MF")
}
}
}

dependencies {
Expand All @@ -57,6 +62,7 @@ dependencies {
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
implementation(projects.core.util)
implementation(libs.guava)

implementation(libs.androidx.adaptive)
implementation(libs.androidx.adaptive.layout)
Expand Down
2 changes: 1 addition & 1 deletion data/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.serialization)
alias(libs.plugins.kotlin.ksp)
alias(libs.plugins.hilt)
}
Expand Down Expand Up @@ -59,5 +58,6 @@ dependencies {
implementation(libs.ai.edge) {
exclude(group = "com.google.guava")
}

ksp(libs.hilt.compiler)
}
7 changes: 7 additions & 0 deletions feature/camera/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ android {
testOptions {
targetSdk = 36
}
// To avoid packaging conflicts when using bouncycastle
packaging {
resources {
excludes.add("META-INF/versions/9/OSGI-INF/MANIFEST.MF")
}
}
}

dependencies {
Expand All @@ -66,6 +72,7 @@ dependencies {
implementation(libs.coil.compose)
implementation(libs.kotlinx.coroutines.play.services)
implementation(libs.mlkit.pose.detection)
implementation(libs.guava)
ksp(libs.hilt.compiler)

implementation(libs.androidx.ui.tooling)
Expand Down
6 changes: 6 additions & 0 deletions feature/creation/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,12 @@ android {
}
targetSdk = 36
}
// To avoid packaging conflicts when using bouncycastle
packaging {
resources {
excludes.add("META-INF/versions/9/OSGI-INF/MANIFEST.MF")
}
}
}

dependencies {
Expand Down
6 changes: 6 additions & 0 deletions feature/home/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ android {
testOptions {
targetSdk = 36
}
// To avoid packaging conflicts when using bouncycastle
packaging {
resources {
excludes.add("META-INF/versions/9/OSGI-INF/MANIFEST.MF")
}
}
}

dependencies {
Expand Down
8 changes: 8 additions & 0 deletions feature/results/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ android {
testOptions {
targetSdk = 36
}
// To avoid packaging conflicts when using bouncycastle
packaging {
resources {
excludes.add("META-INF/versions/9/OSGI-INF/MANIFEST.MF")
}
}
}

dependencies {
Expand All @@ -72,6 +78,8 @@ dependencies {
implementation(projects.core.theme)
implementation(projects.core.util)
implementation(projects.data)
implementation(projects.wear.common)
implementation(projects.watchface)
testImplementation(kotlin("test"))

// Android Instrumented Tests
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* Copyright 2025 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.android.developers.androidify.customize

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.android.developers.androidify.results.R
import com.android.developers.androidify.theme.AndroidifyTheme
import com.android.developers.androidify.watchface.WatchFaceAsset

@Composable
fun AllDoneWatchFacePanel(
modifier: Modifier = Modifier,
selectedWatchFace: WatchFaceAsset?,
onAllDoneClick: () -> Unit = { },
) {
Column(
modifier = modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.weight(1f),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
) {
WatchFacePreviewItem(
watchFace = selectedWatchFace,
isSelected = true,
onClick = { },
)
}
Spacer(modifier = Modifier.height(24.dp))
WatchFacePanelButton(
modifier = modifier.padding(horizontal = 16.dp),
buttonText = stringResource(R.string.complete_all_done),
iconResId = R.drawable.check_24,
onClick = onAllDoneClick,
)
}
}

@OptIn(ExperimentalMaterial3Api::class)
@Preview(showBackground = true)
@Composable
private fun AllDoneWatchFacePanelPreview() {
val watchFace1 = WatchFaceAsset(
id = "watch_face_1",
previewPath = R.drawable.watch_face_preview,
)
AndroidifyTheme {
AllDoneWatchFacePanel(
selectedWatchFace = watchFace1,
)
}
}
Loading