Skip to content

Commit 3fbcdcb

Browse files
1 parent d0ef8d0 commit 3fbcdcb

File tree

4 files changed

+551
-0
lines changed

4 files changed

+551
-0
lines changed

wear/src/main/AndroidManifest.xml

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,25 @@
3535
</intent-filter>
3636
</activity>
3737

38+
<activity
39+
android:name=".snippets.m3.tile.TileActivity"
40+
android:exported="true"
41+
android:taskAffinity=""
42+
android:theme="@android:style/Theme.DeviceDefault">
43+
<intent-filter>
44+
<action android:name="android.intent.action.MAIN" />
45+
46+
<category android:name="android.intent.category.LAUNCHER" />
47+
</intent-filter>
48+
49+
<intent-filter android:label="Deep Link to Message Detail">
50+
<action android:name="android.intent.action.VIEW" />
51+
<category android:name="android.intent.category.DEFAULT" />
52+
<category android:name="android.intent.category.BROWSABLE" />
53+
<data android:scheme="googleandroidsnippets" android:host="app" />
54+
</intent-filter>
55+
</activity>
56+
3857
<!-- [START android_wear_tile_manifest] -->
3958
<service
4059
android:name=".snippets.tile.MyTileService"
@@ -69,6 +88,81 @@
6988
</service>
7089
<!-- [END android_wear_m3_tile_manifest] -->
7190

91+
<service
92+
android:name=".snippets.m3.tile.HelloTileService"
93+
android:label="@string/tile_label"
94+
android:description="@string/tile_description"
95+
android:icon="@mipmap/ic_launcher"
96+
android:exported="true"
97+
android:permission="com.google.android.wearable.permission.BIND_TILE_PROVIDER">
98+
<intent-filter>
99+
<action android:name="androidx.wear.tiles.action.BIND_TILE_PROVIDER" />
100+
</intent-filter>
101+
102+
<meta-data android:name="androidx.wear.tiles.PREVIEW"
103+
android:resource="@drawable/tile_preview" />
104+
</service>
105+
106+
<service
107+
android:name=".snippets.m3.tile.InteractionLaunchAction"
108+
android:label="@string/tile_label"
109+
android:description="@string/tile_description"
110+
android:icon="@mipmap/ic_launcher"
111+
android:exported="true"
112+
android:permission="com.google.android.wearable.permission.BIND_TILE_PROVIDER">
113+
<intent-filter>
114+
<action android:name="androidx.wear.tiles.action.BIND_TILE_PROVIDER" />
115+
</intent-filter>
116+
117+
<meta-data android:name="androidx.wear.tiles.PREVIEW"
118+
android:resource="@drawable/tile_preview" />
119+
</service>
120+
121+
<service
122+
android:name=".snippets.m3.tile.InteractionDeepLink"
123+
android:label="@string/tile_label"
124+
android:description="@string/tile_description"
125+
android:icon="@mipmap/ic_launcher"
126+
android:exported="true"
127+
android:permission="com.google.android.wearable.permission.BIND_TILE_PROVIDER">
128+
<intent-filter>
129+
<action android:name="androidx.wear.tiles.action.BIND_TILE_PROVIDER" />
130+
</intent-filter>
131+
132+
<meta-data android:name="androidx.wear.tiles.PREVIEW"
133+
android:resource="@drawable/tile_preview" />
134+
</service>
135+
136+
<service
137+
android:name=".snippets.m3.tile.InteractionRefresh"
138+
android:label="@string/tile_label"
139+
android:description="@string/tile_description"
140+
android:icon="@mipmap/ic_launcher"
141+
android:exported="true"
142+
android:permission="com.google.android.wearable.permission.BIND_TILE_PROVIDER">
143+
<intent-filter>
144+
<action android:name="androidx.wear.tiles.action.BIND_TILE_PROVIDER" />
145+
</intent-filter>
146+
147+
<meta-data android:name="androidx.wear.tiles.PREVIEW"
148+
android:resource="@drawable/tile_preview" />
149+
</service>
150+
151+
<service
152+
android:name=".snippets.m3.tile.InteractionLoadAction"
153+
android:label="@string/tile_label"
154+
android:description="@string/tile_description"
155+
android:icon="@mipmap/ic_launcher"
156+
android:exported="true"
157+
android:permission="com.google.android.wearable.permission.BIND_TILE_PROVIDER">
158+
<intent-filter>
159+
<action android:name="androidx.wear.tiles.action.BIND_TILE_PROVIDER" />
160+
</intent-filter>
161+
162+
<meta-data android:name="androidx.wear.tiles.PREVIEW"
163+
android:resource="@drawable/tile_preview" />
164+
</service>
165+
72166
</application>
73167

74168
</manifest>
Lines changed: 272 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,272 @@
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+
17+
package com.example.wear.snippets.m3.tile
18+
19+
import android.content.ComponentName
20+
import android.content.Intent
21+
import androidx.core.app.TaskStackBuilder
22+
import androidx.core.net.toUri
23+
import androidx.wear.protolayout.ActionBuilders
24+
import androidx.wear.protolayout.ActionBuilders.launchAction
25+
import androidx.wear.protolayout.LayoutElementBuilders
26+
import androidx.wear.protolayout.ResourceBuilders.Resources
27+
import androidx.wear.protolayout.TimelineBuilders.Timeline
28+
import androidx.wear.protolayout.expression.dynamicDataMapOf
29+
import androidx.wear.protolayout.expression.intAppDataKey
30+
import androidx.wear.protolayout.expression.mapTo
31+
import androidx.wear.protolayout.expression.stringAppDataKey
32+
import androidx.wear.protolayout.material3.MaterialScope
33+
import androidx.wear.protolayout.material3.Typography.BODY_LARGE
34+
import androidx.wear.protolayout.material3.materialScope
35+
import androidx.wear.protolayout.material3.primaryLayout
36+
import androidx.wear.protolayout.material3.text
37+
import androidx.wear.protolayout.material3.textButton
38+
import androidx.wear.protolayout.modifiers.clickable
39+
import androidx.wear.protolayout.modifiers.loadAction
40+
import androidx.wear.protolayout.types.layoutString
41+
import androidx.wear.tiles.RequestBuilders
42+
import androidx.wear.tiles.RequestBuilders.ResourcesRequest
43+
import androidx.wear.tiles.TileBuilders.Tile
44+
import androidx.wear.tiles.TileService
45+
import com.google.common.util.concurrent.Futures
46+
import com.google.common.util.concurrent.ListenableFuture
47+
import java.util.Locale
48+
import kotlin.random.Random
49+
50+
private const val RESOURCES_VERSION = "1"
51+
52+
abstract class BaseTileService : TileService() {
53+
54+
override fun onTileRequest(
55+
requestParams: RequestBuilders.TileRequest
56+
): ListenableFuture<Tile> =
57+
Futures.immediateFuture(
58+
Tile.Builder()
59+
.setResourcesVersion(RESOURCES_VERSION)
60+
.setTileTimeline(
61+
Timeline.fromLayoutElement(
62+
materialScope(this, requestParams.deviceConfiguration) {
63+
tileLayout(requestParams)
64+
}
65+
)
66+
)
67+
.build()
68+
)
69+
70+
override fun onTileResourcesRequest(
71+
requestParams: ResourcesRequest
72+
): ListenableFuture<Resources> =
73+
Futures.immediateFuture(
74+
Resources.Builder().setVersion(requestParams.version).build()
75+
)
76+
77+
abstract fun MaterialScope.tileLayout(
78+
requestParams: RequestBuilders.TileRequest
79+
): LayoutElementBuilders.LayoutElement
80+
}
81+
82+
class HelloTileService : BaseTileService() {
83+
override fun MaterialScope.tileLayout(
84+
requestParams: RequestBuilders.TileRequest
85+
) = primaryLayout(mainSlot = { text("Hello, World!".layoutString) })
86+
}
87+
88+
class InteractionRefresh : BaseTileService() {
89+
override fun MaterialScope.tileLayout(
90+
requestParams: RequestBuilders.TileRequest
91+
) =
92+
primaryLayout(
93+
// Output a debug code so we can see the layout changing
94+
titleSlot = {
95+
text(
96+
String.format(
97+
Locale.ENGLISH,
98+
"Debug %06d",
99+
Random.nextInt(0, 1_000_000),
100+
)
101+
.layoutString
102+
)
103+
},
104+
mainSlot = {
105+
// [START android_wear_m3_interaction_refresh]
106+
textButton(
107+
onClick = clickable(loadAction()),
108+
labelContent = { text("Refresh".layoutString) },
109+
)
110+
// [END android_wear_m3_interaction_refresh]
111+
},
112+
)
113+
}
114+
115+
class InteractionDeepLink : TileService() {
116+
117+
// [START android_wear_m3_interaction_deeplink_tile]
118+
override fun onTileRequest(
119+
requestParams: RequestBuilders.TileRequest
120+
): ListenableFuture<Tile?> {
121+
val lastClickableId = requestParams.currentState.lastClickableId
122+
if (lastClickableId == "foo") {
123+
TaskStackBuilder.create(this)
124+
.addNextIntentWithParentStack(
125+
Intent(
126+
Intent.ACTION_VIEW,
127+
"googleandroidsnippets://app/message_detail/1".toUri(),
128+
this,
129+
TileActivity::class.java,
130+
)
131+
)
132+
.startActivities()
133+
}
134+
// ... User didn't tap a button (either first load or tapped somewhere else)
135+
// [START_EXCLUDE]
136+
return Futures.immediateFuture(
137+
Tile.Builder()
138+
.setResourcesVersion(RESOURCES_VERSION)
139+
.setTileTimeline(
140+
Timeline.fromLayoutElement(
141+
materialScope(this, requestParams.deviceConfiguration) {
142+
tileLayout(requestParams)
143+
}
144+
)
145+
)
146+
.build()
147+
)
148+
// [END_EXCLUDE]
149+
}
150+
151+
// [END android_wear_m3_interaction_deeplink_tile]
152+
153+
override fun onTileResourcesRequest(
154+
requestParams: ResourcesRequest
155+
): ListenableFuture<Resources?> =
156+
Futures.immediateFuture(
157+
Resources.Builder().setVersion(requestParams.version).build()
158+
)
159+
160+
fun MaterialScope.tileLayout(requestParams: RequestBuilders.TileRequest) =
161+
primaryLayout(
162+
mainSlot = {
163+
// [START android_wear_m3_interaction_deeplink_layout]
164+
textButton(
165+
labelContent = {
166+
text("Deep Link me!".layoutString, typography = BODY_LARGE)
167+
},
168+
onClick = clickable(id = "foo", action = loadAction()),
169+
)
170+
// [END android_wear_m3_interaction_deeplink_layout]
171+
}
172+
)
173+
}
174+
175+
class InteractionLoadAction : BaseTileService() {
176+
177+
override fun onTileRequest(
178+
requestParams: RequestBuilders.TileRequest
179+
): ListenableFuture<Tile> {
180+
181+
val name: String?
182+
val age: Int?
183+
184+
// When triggered by loadAction(), "name" will be "Javier", and "age" will
185+
// be 37.
186+
with(requestParams.currentState.stateMap) {
187+
name = this[stringAppDataKey("name")]
188+
age = this[intAppDataKey("age")]
189+
}
190+
191+
return Futures.immediateFuture(
192+
Tile.Builder()
193+
.setResourcesVersion(RESOURCES_VERSION)
194+
.setTileTimeline(
195+
Timeline.fromLayoutElement(
196+
materialScope(this, requestParams.deviceConfiguration) {
197+
tileLayout(requestParams)
198+
}
199+
)
200+
)
201+
.build()
202+
)
203+
}
204+
205+
override fun MaterialScope.tileLayout(
206+
requestParams: RequestBuilders.TileRequest
207+
) =
208+
primaryLayout(
209+
// Output a debug code so we can verify that the reload happens
210+
titleSlot = {
211+
text(
212+
String.format(
213+
Locale.ENGLISH,
214+
"Debug %06d",
215+
Random.nextInt(0, 1_000_000),
216+
)
217+
.layoutString
218+
)
219+
},
220+
mainSlot = {
221+
// [START android_wear_m3_interaction_loadaction_layout]
222+
textButton(
223+
labelContent = {
224+
text("loadAction()".layoutString, typography = BODY_LARGE)
225+
},
226+
onClick =
227+
clickable(
228+
action =
229+
loadAction(
230+
dynamicDataMapOf(
231+
stringAppDataKey("name") mapTo "Javier",
232+
intAppDataKey("age") mapTo 37,
233+
)
234+
)
235+
),
236+
)
237+
// [END android_wear_m3_interaction_loadaction_layout]
238+
},
239+
)
240+
}
241+
242+
class InteractionLaunchAction : BaseTileService() {
243+
244+
override fun MaterialScope.tileLayout(
245+
requestParams: RequestBuilders.TileRequest
246+
) =
247+
primaryLayout(
248+
mainSlot = {
249+
// [START android_wear_m3_interactions_launchaction]
250+
textButton(
251+
labelContent = {
252+
text("launchAction()".layoutString, typography = BODY_LARGE)
253+
},
254+
onClick =
255+
clickable(
256+
action =
257+
launchAction(
258+
ComponentName(
259+
"com.example.wear",
260+
"com.example.wear.snippets.m3.tile.TileActivity",
261+
),
262+
mapOf(
263+
"name" to ActionBuilders.stringExtra("Bartholomew"),
264+
"age" to ActionBuilders.intExtra(21),
265+
),
266+
)
267+
),
268+
)
269+
// [END android_wear_m3_interactions_launchaction]
270+
}
271+
)
272+
}

0 commit comments

Comments
 (0)