Skip to content

Commit 531643f

Browse files
authored
Merge branch 'main' into jetpack-september
2 parents d740be3 + f194cd3 commit 531643f

File tree

8 files changed

+159
-9
lines changed

8 files changed

+159
-9
lines changed

feature/creation/src/main/java/com/android/developers/androidify/creation/EditScreen.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ fun EditScreenScaffoldWithAppBar(
151151
},
152152
actions = {
153153
AboutButton { onAboutPressed() }
154-
if (couldRequestFullSpace()) {
154+
if (uiState.xrEnabled && couldRequestFullSpace()) {
155155
RequestFullSpaceIconButton()
156156
}
157157
},

feature/results/src/main/java/com/android/developers/androidify/results/ResultsScreen.kt

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -138,11 +138,13 @@ fun ResultsScreenContents(
138138
},
139139
actions = {
140140
AboutButton { onAboutPress() }
141-
if (couldRequestFullSpace()) {
142-
RequestFullSpaceIconButton()
143-
}
144-
if (couldRequestHomeSpace()) {
145-
RequestHomeSpaceIconButton()
141+
if (state.xrEnabled) {
142+
if (couldRequestFullSpace()) {
143+
RequestFullSpaceIconButton()
144+
}
145+
if (couldRequestHomeSpace()) {
146+
RequestHomeSpaceIconButton()
147+
}
146148
}
147149
},
148150
)

gradle/libs.versions.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# build
33
appVersionCode = "10"
44
appVersionName = "1.3.0"
5+
appVersionWearOffset = "60000000"
56
agp = "8.11.1"
67
bcpkixJdk18on = "1.81"
78
compileSdk = "36"
@@ -83,6 +84,7 @@ mlkitCommon = "18.11.0"
8384
mlkitSegmentation = "16.0.0-beta1"
8485
playServicesBase = "18.7.2"
8586
timber = "5.0.1"
87+
workRuntimeKtx = "2.10.4"
8688
xr-compose = "1.0.0-alpha07"
8789

8890
[libraries]
@@ -132,6 +134,7 @@ androidx-wear-compose-ui-tooling = { group = "androidx.wear.compose", name = "co
132134
androidx-wear-remote-interactions = { module = "androidx.wear:wear-remote-interactions", version.ref = "wearRemoteInteractions" }
133135
androidx-window = { module = "androidx.window:window", version.ref = "window" }
134136
androidx-window-core = { module = "androidx.window:window-core", version.ref = "window" }
137+
androidx-work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "workRuntimeKtx" }
135138
apksig = { module = "com.android.tools.build:apksig", version.ref = "apksig" }
136139
bcpkix-jdk18on = { module = "org.bouncycastle:bcpkix-jdk18on", version.ref = "bcpkixJdk18on" }
137140
coil-compose = { group = "io.coil-kt.coil3", name = "coil-compose", version.ref = "coilCompose" }

wear/build.gradle.kts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ android {
3535
applicationId = "com.android.developers.androidify"
3636
targetSdk = 36
3737
// Ensure Wear OS app has its own version space
38-
versionCode = 60_000_000 + libs.versions.appVersionCode.get().toInt()
38+
versionCode = libs.versions.appVersionWearOffset.get().toInt() + libs.versions.appVersionCode.get().toInt()
3939
versionName = libs.versions.appVersionName.get()
4040
}
4141

@@ -85,6 +85,7 @@ dependencies {
8585
implementation(libs.androidx.wear.remote.interactions)
8686
implementation(libs.horologist.compose.layout)
8787
implementation(libs.accompanist.permissions)
88+
implementation(libs.androidx.work.runtime.ktx)
8889

8990
"cliToolConfiguration"(libs.validator.push.cli)
9091
}

wear/src/main/AndroidManifest.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,5 +77,14 @@
7777
<data android:scheme="wear" android:host="*" android:pathPrefix="/transfer_apk" />
7878
</intent-filter>
7979
</service>
80+
81+
<receiver
82+
android:name=".updater.UpdateReceiver"
83+
android:enabled="true"
84+
android:exported="false">
85+
<intent-filter>
86+
<action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
87+
</intent-filter>
88+
</receiver>
8089
</application>
8190
</manifest>
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright 2025 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.android.developers.androidify.updater
17+
18+
import android.content.BroadcastReceiver
19+
import android.content.Context
20+
import android.content.Intent
21+
import androidx.work.OneTimeWorkRequestBuilder
22+
import androidx.work.WorkManager
23+
24+
/**
25+
* Updates the watch face, if necessary, when the overall app is updated, if the app contains a
26+
* newer default watch face within it.
27+
*
28+
* Uses a WorkManager job to avoid trying to complete this within the time allowed for the
29+
* onReceive.
30+
*/
31+
class UpdateReceiver : BroadcastReceiver() {
32+
override fun onReceive(context: Context, intent: Intent) {
33+
if (Intent.ACTION_MY_PACKAGE_REPLACED == intent.action) {
34+
val updateRequest = OneTimeWorkRequestBuilder<UpdateWorker>().build()
35+
val workManager = WorkManager.getInstance(context)
36+
workManager.enqueue(updateRequest)
37+
}
38+
}
39+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Copyright 2025 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.android.developers.androidify.updater
17+
18+
import android.content.Context
19+
import android.content.pm.PackageManager
20+
import android.os.ParcelFileDescriptor
21+
import android.util.Log
22+
import androidx.wear.watchfacepush.WatchFacePushManagerFactory
23+
import androidx.work.CoroutineWorker
24+
import androidx.work.WorkerParameters
25+
import java.io.File
26+
import java.io.FileOutputStream
27+
import java.io.IOException
28+
29+
private const val defaultWatchFaceName = "default_watchface.apk"
30+
private const val manifestTokenKey = "com.google.android.wearable.marketplace.DEFAULT_WATCHFACE_VALIDATION_TOKEN"
31+
32+
private const val TAG = "UpdateWorker"
33+
34+
/**
35+
* WorkManager worker that tries to update the default watch face, if installed.
36+
*
37+
* Checks which watch faces the package already has installed, and if there is a default watch face
38+
* in the assets bundle. Compares the versions of these to determine whether an update is necessary
39+
* and if so, updates the default watch face, taking also the new watch face validation token from
40+
* the manifest file.
41+
*/
42+
class UpdateWorker(appContext: Context, workerParams: WorkerParameters) :
43+
CoroutineWorker(appContext, workerParams) {
44+
45+
override suspend fun doWork(): Result {
46+
val watchFacePushManager = WatchFacePushManagerFactory.createWatchFacePushManager(applicationContext)
47+
48+
val watchFaces = watchFacePushManager.listWatchFaces().installedWatchFaceDetails
49+
.associateBy { it.packageName }
50+
51+
val copiedFile = File.createTempFile("tmp", ".apk", applicationContext.cacheDir)
52+
try {
53+
applicationContext.assets.open(defaultWatchFaceName).use { inputStream ->
54+
FileOutputStream(copiedFile).use { outputStream -> inputStream.copyTo(outputStream) }
55+
}
56+
val packageInfo =
57+
applicationContext.packageManager.getPackageArchiveInfo(copiedFile.absolutePath, 0)
58+
59+
packageInfo?.let { newPkg ->
60+
// Check if the default watch face is currently installed and should therefore be
61+
// updated if the one in the assets folder has a higher version code.
62+
watchFaces[newPkg.packageName]?.let { curPkg ->
63+
if (newPkg.longVersionCode > curPkg.versionCode) {
64+
ParcelFileDescriptor.open(
65+
copiedFile,
66+
ParcelFileDescriptor.MODE_READ_ONLY,
67+
).use { pfd ->
68+
val token = getDefaultWatchFaceToken()
69+
if (token != null) {
70+
watchFacePushManager.updateWatchFace(curPkg.slotId, pfd, token)
71+
Log.d(TAG, "Watch face updated from ${curPkg.versionCode} to ${newPkg.longVersionCode}")
72+
} else {
73+
Log.w(TAG, "Watch face not updated, no token found")
74+
}
75+
}
76+
}
77+
}
78+
}
79+
} catch (e: IOException) {
80+
Log.w(TAG, "Watch face not updated", e)
81+
return Result.failure()
82+
} finally {
83+
copiedFile.delete()
84+
}
85+
return Result.success()
86+
}
87+
88+
private fun getDefaultWatchFaceToken(): String? {
89+
val appInfo = applicationContext.packageManager.getApplicationInfo(
90+
applicationContext.packageName,
91+
PackageManager.GET_META_DATA,
92+
)
93+
return appInfo.metaData?.getString(manifestTokenKey)
94+
}
95+
}

wear/watchface/build.gradle.kts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@ android {
2626
applicationId = "com.android.developers.androidify.watchfacepush.defaultwf"
2727
minSdk = 36
2828
targetSdk = 36
29-
versionCode = 1
30-
versionName = "1.0"
29+
// The default watch face version is kept in lock step with the Wear OS app.
30+
versionCode = libs.versions.appVersionWearOffset.get().toInt() + libs.versions.appVersionCode.get().toInt()
31+
versionName = libs.versions.appVersionName.get()
3132
}
3233

3334
buildTypes {

0 commit comments

Comments
 (0)