android-sdd 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +143 -0
- package/package.json +27 -0
- package/skills/Android Ecosystem/Baseline Profile Generator/SKILL.md +277 -0
- package/skills/Android Ecosystem/Glance/SKILL.md +315 -0
- package/skills/Android Platform/Configuration/SKILL.md +201 -0
- package/skills/Android Platform/Filesystem/SKILL.md +216 -0
- package/skills/Android Platform/Lifecycle/SKILL.md +233 -0
- package/skills/Android Platform/Manifest/SKILL.md +226 -0
- package/skills/Android Platform/Process Death Recovery/SKILL.md +214 -0
- package/skills/Android Platform/Resources/SKILL.md +234 -0
- package/skills/Android Platform/SavedStateHandle/SKILL.md +217 -0
- package/skills/Android Platform/State Restoration/SKILL.md +210 -0
- package/skills/Architecture/Bounded Context/SKILL.md +207 -0
- package/skills/Architecture/Clean Architecture/SKILL.md +229 -0
- package/skills/Architecture/Domain Modeling/SKILL.md +236 -0
- package/skills/Architecture/Entity Design/SKILL.md +243 -0
- package/skills/Architecture/Feature Isolation/SKILL.md +216 -0
- package/skills/Architecture/MVI/SKILL.md +224 -0
- package/skills/Architecture/MVVM/SKILL.md +198 -0
- package/skills/Architecture/Modularization/SKILL.md +194 -0
- package/skills/Architecture/Offline First/SKILL.md +249 -0
- package/skills/Architecture/Repository Pattern/SKILL.md +216 -0
- package/skills/Architecture/Side Effect Management/SKILL.md +278 -0
- package/skills/Architecture/State Management/SKILL.md +229 -0
- package/skills/Architecture/Unidirectional Data Flow/SKILL.md +196 -0
- package/skills/Architecture/Use Case Design/SKILL.md +244 -0
- package/skills/Architecture/Value Object/SKILL.md +226 -0
- package/skills/Build Infrastructure/Build Orchestration/SKILL.md +257 -0
- package/skills/Build Infrastructure/Dependency Compatibility Resolver/SKILL.md +259 -0
- package/skills/Build Infrastructure/Environment Validator/SKILL.md +311 -0
- package/skills/Build System/Build Cache/SKILL.md +233 -0
- package/skills/Build System/Build Flavor Strategy/SKILL.md +171 -0
- package/skills/Build System/Build Variant/SKILL.md +215 -0
- package/skills/Build System/Convention Plugin/SKILL.md +288 -0
- package/skills/Build System/Dependency Management/SKILL.md +261 -0
- package/skills/Build System/Gradle/SKILL.md +284 -0
- package/skills/Build System/Incremental Build/SKILL.md +199 -0
- package/skills/Build System/KAPT/SKILL.md +198 -0
- package/skills/Build System/KSP/SKILL.md +263 -0
- package/skills/Build System/Module Dependency Graph Validation/SKILL.md +223 -0
- package/skills/Build System/Specialized/C++/SKILL.md +308 -0
- package/skills/Build System/Specialized/JNI/SKILL.md +306 -0
- package/skills/Build System/Specialized/NDK/SKILL.md +264 -0
- package/skills/Build System/Version Catalog/SKILL.md +304 -0
- package/skills/Concurrency/Background Processing/SKILL.md +185 -0
- package/skills/Concurrency/Channel/SKILL.md +207 -0
- package/skills/Concurrency/Coroutine/SKILL.md +200 -0
- package/skills/Concurrency/Flow/SKILL.md +179 -0
- package/skills/Concurrency/Mutex Strategy/SKILL.md +185 -0
- package/skills/Concurrency/SharedFlow/SKILL.md +171 -0
- package/skills/Concurrency/StateFlow/SKILL.md +175 -0
- package/skills/Concurrency/Structured Concurrency/SKILL.md +197 -0
- package/skills/Concurrency/Synchronization Policy/SKILL.md +192 -0
- package/skills/Core Language/Annotation Processing/SKILL.md +224 -0
- package/skills/Core Language/DSL/SKILL.md +186 -0
- package/skills/Core Language/Extension Functions Design/SKILL.md +191 -0
- package/skills/Core Language/Immutability/SKILL.md +156 -0
- package/skills/Core Language/KMP/SKILL.md +182 -0
- package/skills/Core Language/Kotlin/SKILL.md +187 -0
- package/skills/Core Language/Reactive State Management/SKILL.md +228 -0
- package/skills/Core Language/Reactive Streams/SKILL.md +235 -0
- package/skills/Core Language/Serialization/SKILL.md +191 -0
- package/skills/Data Layer/Cache Strategy/SKILL.md +261 -0
- package/skills/Data Layer/Conflict Resolution/SKILL.md +248 -0
- package/skills/Data Layer/DAO/SKILL.md +225 -0
- package/skills/Data Layer/DTO Mapping/SKILL.md +269 -0
- package/skills/Data Layer/DataStore/SKILL.md +264 -0
- package/skills/Data Layer/Database Versioning Strategy/SKILL.md +215 -0
- package/skills/Data Layer/Encrypted Database/SKILL.md +212 -0
- package/skills/Data Layer/File Storage/SKILL.md +247 -0
- package/skills/Data Layer/Indexing/SKILL.md +184 -0
- package/skills/Data Layer/Key-Value Store Strategy/SKILL.md +185 -0
- package/skills/Data Layer/Merge Strategy/SKILL.md +240 -0
- package/skills/Data Layer/Migration/SKILL.md +243 -0
- package/skills/Data Layer/Paging/SKILL.md +264 -0
- package/skills/Data Layer/Proto DataStore/SKILL.md +250 -0
- package/skills/Data Layer/Room/SKILL.md +244 -0
- package/skills/Data Layer/SQLite/SKILL.md +255 -0
- package/skills/Data Layer/Sync Engine/SKILL.md +268 -0
- package/skills/Dependency Injection/Dagger/SKILL.md +283 -0
- package/skills/Dependency Injection/Hilt/SKILL.md +345 -0
- package/skills/Dependency Injection/Koin/SKILL.md +282 -0
- package/skills/Developer Experience/Detekt/SKILL.md +272 -0
- package/skills/Developer Experience/Lint Rule/SKILL.md +281 -0
- package/skills/Google Ecosystem/Analytics/SKILL.md +281 -0
- package/skills/Google Ecosystem/Crashlytics/SKILL.md +234 -0
- package/skills/Google Ecosystem/Firebase/SKILL.md +200 -0
- package/skills/Google Ecosystem/Firebase Messaging/SKILL.md +266 -0
- package/skills/Media/Audio/SKILL.md +257 -0
- package/skills/Media/Camera/SKILL.md +229 -0
- package/skills/Media/CameraX/SKILL.md +295 -0
- package/skills/Media/ExoPlayer/SKILL.md +258 -0
- package/skills/Media/Video/SKILL.md +228 -0
- package/skills/Meta Skills/Domain Error Model/SKILL.md +238 -0
- package/skills/Meta Skills/Error Handling/SKILL.md +255 -0
- package/skills/Meta Skills/Error Mapping/SKILL.md +232 -0
- package/skills/Meta Skills/Failure Strategy/SKILL.md +294 -0
- package/skills/Meta Skills/Migration Strategy/SKILL.md +305 -0
- package/skills/Meta Skills/User Friendly Errors/SKILL.md +334 -0
- package/skills/Navigation/Deep Navigation/SKILL.md +209 -0
- package/skills/Navigation/Navigation/SKILL.md +215 -0
- package/skills/Navigation/Nested Navigation/SKILL.md +214 -0
- package/skills/Networking/API Contract/SKILL.md +220 -0
- package/skills/Networking/Authentication/SKILL.md +210 -0
- package/skills/Networking/Certificate Pinning/SKILL.md +167 -0
- package/skills/Networking/Fallback Strategy/SKILL.md +182 -0
- package/skills/Networking/Ktor/SKILL.md +219 -0
- package/skills/Networking/Multipart Upload/SKILL.md +213 -0
- package/skills/Networking/OkHttp/SKILL.md +193 -0
- package/skills/Networking/REST/SKILL.md +178 -0
- package/skills/Networking/Rate Limiting/SKILL.md +170 -0
- package/skills/Networking/Retrofit/SKILL.md +241 -0
- package/skills/Networking/Retry-Backoff/SKILL.md +181 -0
- package/skills/Networking/Server-Sent Events (SSE)/SKILL.md +196 -0
- package/skills/Networking/WebSocket/SKILL.md +224 -0
- package/skills/Observability/Crash Reporting/SKILL.md +219 -0
- package/skills/Observability/Logging/SKILL.md +168 -0
- package/skills/Observability/Metrics/SKILL.md +227 -0
- package/skills/Observability/Structured Logging/SKILL.md +234 -0
- package/skills/Performance/ANR Prevention/SKILL.md +192 -0
- package/skills/Performance/Allocation Optimization/SKILL.md +179 -0
- package/skills/Performance/App Startup/SKILL.md +183 -0
- package/skills/Performance/Baseline Profile/SKILL.md +205 -0
- package/skills/Performance/Battery Optimization/SKILL.md +192 -0
- package/skills/Performance/Benchmark/SKILL.md +182 -0
- package/skills/Performance/Bitmap Optimization/SKILL.md +178 -0
- package/skills/Performance/Compose Optimization/SKILL.md +187 -0
- package/skills/Performance/Heap Management/SKILL.md +184 -0
- package/skills/Performance/Macrobenchmark/SKILL.md +214 -0
- package/skills/Performance/Memory Leak Prevention/SKILL.md +218 -0
- package/skills/Performance/Rendering Performance/SKILL.md +205 -0
- package/skills/Performance/Startup Optimization/SKILL.md +219 -0
- package/skills/Security/Biometric/SKILL.md +224 -0
- package/skills/Security/Certificate Transparency/SKILL.md +158 -0
- package/skills/Security/Cryptography/SKILL.md +244 -0
- package/skills/Security/Encrypted Storage/SKILL.md +273 -0
- package/skills/Security/Frida Detection/SKILL.md +230 -0
- package/skills/Security/Hook Detection/SKILL.md +197 -0
- package/skills/Security/Keystore/SKILL.md +272 -0
- package/skills/Security/Network Security Config/SKILL.md +186 -0
- package/skills/Security/Obfuscation/SKILL.md +226 -0
- package/skills/Security/Proguard/SKILL.md +202 -0
- package/skills/Security/R8/SKILL.md +234 -0
- package/skills/Security/Reverse Engineering Resistance/SKILL.md +267 -0
- package/skills/Security/Root Detection/SKILL.md +220 -0
- package/skills/Security/Secure Networking/SKILL.md +220 -0
- package/skills/System Integration/AlarmManager/SKILL.md +182 -0
- package/skills/System Integration/App Widget/SKILL.md +182 -0
- package/skills/System Integration/Deep Link/SKILL.md +187 -0
- package/skills/System Integration/Foreground Service/SKILL.md +212 -0
- package/skills/System Integration/Notification/SKILL.md +237 -0
- package/skills/System Integration/WorkManager/SKILL.md +256 -0
- package/skills/System Integration/clipboard/SKILL.md +155 -0
- package/skills/System Integration/share-intent/SKILL.md +182 -0
- package/skills/Testing/Compose Testing/SKILL.md +296 -0
- package/skills/Testing/Espresso/SKILL.md +292 -0
- package/skills/Testing/Fake Data/SKILL.md +245 -0
- package/skills/Testing/Integration Testing/SKILL.md +288 -0
- package/skills/Testing/Mocking/SKILL.md +229 -0
- package/skills/Testing/Snapshot Testing/SKILL.md +259 -0
- package/skills/Testing/UI Testing/SKILL.md +293 -0
- package/skills/Testing/Unit Testing/SKILL.md +309 -0
- package/skills/UI System/Bottom Sheet Patterns/SKILL.md +279 -0
- package/skills/UI System/Compose/SKILL.md +296 -0
- package/skills/UI System/Compose Animation/SKILL.md +281 -0
- package/skills/UI System/Compose Multiplatform/SKILL.md +261 -0
- package/skills/UI System/Compose Navigation/SKILL.md +255 -0
- package/skills/UI System/Compose Performance/SKILL.md +274 -0
- package/skills/UI System/Design System/SKILL.md +217 -0
- package/skills/UI System/Empty State Strategy/SKILL.md +208 -0
- package/skills/UI System/Keyboard Navigation/SKILL.md +214 -0
- package/skills/UI System/Loading Strategy/SKILL.md +254 -0
- package/skills/UI System/Material 3/SKILL.md +279 -0
- package/skills/UI System/RTL/SKILL.md +179 -0
- package/src/index.ts +182 -0
- package/tsconfig.json +19 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: app-widget
|
|
3
|
+
description: >
|
|
4
|
+
Home screen App Widget implementation in Android.
|
|
5
|
+
Load this skill when building home screen widgets, updating widget
|
|
6
|
+
content, handling widget interactions, using Glance for Compose-based
|
|
7
|
+
widgets, or configuring widget metadata.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# App Widget
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
App Widgets are miniature app views displayed on the home screen or lock screen. They update periodically and respond to user interactions. Modern widgets are best built with **Glance** (Jetpack Compose-based API). Legacy widgets use `RemoteViews`.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Core Principles
|
|
18
|
+
|
|
19
|
+
- Use **Glance** for new widgets — it provides a Compose-like API over RemoteViews
|
|
20
|
+
- Widgets run in a separate process — keep updates lightweight and fast
|
|
21
|
+
- Update widgets via `AppWidgetManager` — not direct state mutation
|
|
22
|
+
- Provide a `configure` activity if the widget needs user setup
|
|
23
|
+
- Minimize update frequency — excessive updates drain battery
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Glance Widget (Recommended)
|
|
28
|
+
|
|
29
|
+
```kotlin
|
|
30
|
+
// ✅ Glance widget — Compose-like API
|
|
31
|
+
class StatsWidget : GlanceAppWidget() {
|
|
32
|
+
|
|
33
|
+
override suspend fun provideGlance(context: Context, id: GlanceId) {
|
|
34
|
+
// Load data before composing
|
|
35
|
+
val stats = statsRepository.getStats()
|
|
36
|
+
|
|
37
|
+
provideContent {
|
|
38
|
+
StatsWidgetContent(stats)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@Composable
|
|
44
|
+
fun StatsWidgetContent(stats: Stats) {
|
|
45
|
+
Column(
|
|
46
|
+
modifier = GlanceModifier
|
|
47
|
+
.fillMaxSize()
|
|
48
|
+
.background(GlanceTheme.colors.widgetBackground)
|
|
49
|
+
.padding(16.dp),
|
|
50
|
+
verticalAlignment = Alignment.CenterVertically
|
|
51
|
+
) {
|
|
52
|
+
Text(
|
|
53
|
+
text = stats.title,
|
|
54
|
+
style = TextStyle(
|
|
55
|
+
color = GlanceTheme.colors.onSurface,
|
|
56
|
+
fontSize = 16.sp,
|
|
57
|
+
fontWeight = FontWeight.Bold
|
|
58
|
+
)
|
|
59
|
+
)
|
|
60
|
+
Spacer(modifier = GlanceModifier.height(8.dp))
|
|
61
|
+
Text(
|
|
62
|
+
text = stats.value,
|
|
63
|
+
style = TextStyle(
|
|
64
|
+
color = GlanceTheme.colors.primary,
|
|
65
|
+
fontSize = 32.sp
|
|
66
|
+
)
|
|
67
|
+
)
|
|
68
|
+
Button(
|
|
69
|
+
text = "Refresh",
|
|
70
|
+
onClick = actionRunCallback<RefreshAction>()
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Glance Action
|
|
79
|
+
|
|
80
|
+
```kotlin
|
|
81
|
+
// ✅ Handle widget button tap
|
|
82
|
+
class RefreshAction : ActionCallback {
|
|
83
|
+
override suspend fun onAction(
|
|
84
|
+
context: Context,
|
|
85
|
+
glanceId: GlanceId,
|
|
86
|
+
parameters: ActionParameters
|
|
87
|
+
) {
|
|
88
|
+
// Trigger data refresh then update widget
|
|
89
|
+
statsRepository.refresh()
|
|
90
|
+
StatsWidget().update(context, glanceId)
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Glance Widget Receiver
|
|
98
|
+
|
|
99
|
+
```kotlin
|
|
100
|
+
// ✅ AppWidgetReceiver for Glance widgets
|
|
101
|
+
class StatsWidgetReceiver : GlanceAppWidgetReceiver() {
|
|
102
|
+
override val glanceAppWidget: GlanceAppWidget = StatsWidget()
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## Widget Metadata (XML)
|
|
109
|
+
|
|
110
|
+
```xml
|
|
111
|
+
<!-- res/xml/stats_widget_info.xml -->
|
|
112
|
+
<appwidget-provider
|
|
113
|
+
xmlns:android="http://schemas.android.com/apk/res/android"
|
|
114
|
+
android:minWidth="180dp"
|
|
115
|
+
android:minHeight="110dp"
|
|
116
|
+
android:targetCellWidth="2"
|
|
117
|
+
android:targetCellHeight="2"
|
|
118
|
+
android:updatePeriodMillis="1800000"
|
|
119
|
+
android:initialLayout="@layout/widget_loading"
|
|
120
|
+
android:description="@string/widget_description"
|
|
121
|
+
android:previewImage="@drawable/widget_preview"
|
|
122
|
+
android:widgetCategory="home_screen" />
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Manifest Registration
|
|
128
|
+
|
|
129
|
+
```xml
|
|
130
|
+
<receiver
|
|
131
|
+
android:name=".widget.StatsWidgetReceiver"
|
|
132
|
+
android:exported="true">
|
|
133
|
+
<intent-filter>
|
|
134
|
+
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
|
135
|
+
</intent-filter>
|
|
136
|
+
<meta-data
|
|
137
|
+
android:name="android.appwidget.provider"
|
|
138
|
+
android:resource="@xml/stats_widget_info" />
|
|
139
|
+
</receiver>
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Updating Widget from App
|
|
145
|
+
|
|
146
|
+
```kotlin
|
|
147
|
+
// ✅ Update all instances of a widget
|
|
148
|
+
suspend fun updateAllWidgets(context: Context) {
|
|
149
|
+
val manager = GlanceAppWidgetManager(context)
|
|
150
|
+
val glanceIds = manager.getGlanceIds(StatsWidget::class.java)
|
|
151
|
+
glanceIds.forEach { glanceId ->
|
|
152
|
+
StatsWidget().update(context, glanceId)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// ✅ Trigger update from WorkManager for periodic refresh
|
|
157
|
+
class WidgetUpdateWorker(context: Context, params: WorkerParameters) :
|
|
158
|
+
CoroutineWorker(context, params) {
|
|
159
|
+
override suspend fun doWork(): Result {
|
|
160
|
+
updateAllWidgets(applicationContext)
|
|
161
|
+
return Result.success()
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Anti-Patterns
|
|
169
|
+
|
|
170
|
+
- Doing heavy work directly in `provideGlance` on the main thread — use `withContext(Dispatchers.IO)`
|
|
171
|
+
- Setting `updatePeriodMillis` too low — minimum enforced is 30 minutes
|
|
172
|
+
- Using `RemoteViews` for new widgets when Glance is available — harder to maintain
|
|
173
|
+
- Not providing a preview image — widget looks broken in the picker
|
|
174
|
+
- Keeping large bitmaps in widget state — causes TransactionTooLargeException
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Related Skills
|
|
179
|
+
- `glance` — detailed Glance API patterns
|
|
180
|
+
- `workmanager` — scheduling periodic widget updates
|
|
181
|
+
- `notification` — alternative for time-sensitive updates
|
|
182
|
+
- `compose` — Compose fundamentals that inform Glance's API
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: deep-link
|
|
3
|
+
description: >
|
|
4
|
+
Deep link handling at the Android system level.
|
|
5
|
+
Load this skill when configuring intent filters, handling incoming URIs
|
|
6
|
+
in the Activity, setting up App Links with digital asset links,
|
|
7
|
+
or processing deep links from notifications and external apps.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Deep Link
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
Deep links allow external sources — browsers, other apps, notifications — to open a specific screen inside your app via a URI. Android supports two types: custom scheme deep links (`myapp://`) and App Links (`https://`). App Links require domain verification and are more secure.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Core Principles
|
|
18
|
+
|
|
19
|
+
- Prefer **App Links** (HTTPS) over custom schemes — they can't be intercepted by other apps
|
|
20
|
+
- Verify App Links with `assetlinks.json` hosted on your domain
|
|
21
|
+
- Parse the URI in the Activity and delegate to the navigation system — don't handle in the manifest
|
|
22
|
+
- Validate all incoming URI parameters — treat them as untrusted input
|
|
23
|
+
- Test deep links with `adb` before release
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Manifest Intent Filters
|
|
28
|
+
|
|
29
|
+
```xml
|
|
30
|
+
<!-- AndroidManifest.xml -->
|
|
31
|
+
<activity
|
|
32
|
+
android:name=".MainActivity"
|
|
33
|
+
android:exported="true"
|
|
34
|
+
android:launchMode="singleTask">
|
|
35
|
+
|
|
36
|
+
<!-- App Link (HTTPS — requires domain verification) -->
|
|
37
|
+
<intent-filter android:autoVerify="true">
|
|
38
|
+
<action android:name="android.intent.action.VIEW" />
|
|
39
|
+
<category android:name="android.intent.category.DEFAULT" />
|
|
40
|
+
<category android:name="android.intent.category.BROWSABLE" />
|
|
41
|
+
<data
|
|
42
|
+
android:scheme="https"
|
|
43
|
+
android:host="example.com"
|
|
44
|
+
android:pathPrefix="/users" />
|
|
45
|
+
</intent-filter>
|
|
46
|
+
|
|
47
|
+
<!-- Custom scheme (no verification required) -->
|
|
48
|
+
<intent-filter>
|
|
49
|
+
<action android:name="android.intent.action.VIEW" />
|
|
50
|
+
<category android:name="android.intent.category.DEFAULT" />
|
|
51
|
+
<category android:name="android.intent.category.BROWSABLE" />
|
|
52
|
+
<data android:scheme="myapp" />
|
|
53
|
+
</intent-filter>
|
|
54
|
+
|
|
55
|
+
</activity>
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## App Links Verification (assetlinks.json)
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
// Hosted at: https://example.com/.well-known/assetlinks.json
|
|
64
|
+
[{
|
|
65
|
+
"relation": ["delegate_permission/common.handle_all_urls"],
|
|
66
|
+
"target": {
|
|
67
|
+
"namespace": "android_app",
|
|
68
|
+
"package_name": "com.example.app",
|
|
69
|
+
"sha256_cert_fingerprints": [
|
|
70
|
+
"AA:BB:CC:DD:..."
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
}]
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
# Get your app's SHA-256 fingerprint
|
|
78
|
+
keytool -list -v -keystore release.keystore -alias my_alias
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Handling the Deep Link in Activity
|
|
84
|
+
|
|
85
|
+
```kotlin
|
|
86
|
+
// ✅ Navigation Compose handles deep links automatically
|
|
87
|
+
// Just ensure your NavHost is set up with deep links on destinations (see deep-navigation skill)
|
|
88
|
+
|
|
89
|
+
// ✅ Manual handling if needed
|
|
90
|
+
class MainActivity : ComponentActivity() {
|
|
91
|
+
override fun onCreate(savedInstanceState: Bundle?) {
|
|
92
|
+
super.onCreate(savedInstanceState)
|
|
93
|
+
handleDeepLinkIntent(intent)
|
|
94
|
+
setContent { AppNavHost() }
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
override fun onNewIntent(intent: Intent) {
|
|
98
|
+
super.onNewIntent(intent)
|
|
99
|
+
handleDeepLinkIntent(intent)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
private fun handleDeepLinkIntent(intent: Intent?) {
|
|
103
|
+
val uri = intent?.data ?: return
|
|
104
|
+
// Navigation Compose picks this up automatically via NavHost
|
|
105
|
+
// Manual parsing only needed outside of Navigation Compose
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## URI Validation
|
|
113
|
+
|
|
114
|
+
```kotlin
|
|
115
|
+
// ✅ Validate and sanitize deep link parameters
|
|
116
|
+
fun parseUserDeepLink(uri: Uri): String? {
|
|
117
|
+
if (uri.host != "example.com") return null
|
|
118
|
+
if (!uri.path.orEmpty().startsWith("/users/")) return null
|
|
119
|
+
|
|
120
|
+
val userId = uri.lastPathSegment ?: return null
|
|
121
|
+
if (userId.isBlank() || userId.length > 64) return null
|
|
122
|
+
if (!userId.matches(Regex("[a-zA-Z0-9_-]+"))) return null
|
|
123
|
+
|
|
124
|
+
return userId
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## Testing Deep Links
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
# Test App Link
|
|
134
|
+
adb shell am start \
|
|
135
|
+
-W -a android.intent.action.VIEW \
|
|
136
|
+
-d "https://example.com/users/123" \
|
|
137
|
+
com.example.app
|
|
138
|
+
|
|
139
|
+
# Test custom scheme
|
|
140
|
+
adb shell am start \
|
|
141
|
+
-W -a android.intent.action.VIEW \
|
|
142
|
+
-d "myapp://users/123" \
|
|
143
|
+
com.example.app
|
|
144
|
+
|
|
145
|
+
# Verify App Link association
|
|
146
|
+
adb shell pm get-app-links com.example.app
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## Deep Link from Notification
|
|
152
|
+
|
|
153
|
+
```kotlin
|
|
154
|
+
// ✅ Build notification with deep link PendingIntent
|
|
155
|
+
fun buildDeepLinkPendingIntent(context: Context, userId: String): PendingIntent {
|
|
156
|
+
val deepLinkUri = Uri.parse("https://example.com/users/$userId")
|
|
157
|
+
val intent = Intent(Intent.ACTION_VIEW, deepLinkUri).apply {
|
|
158
|
+
setPackage(context.packageName)
|
|
159
|
+
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
|
|
160
|
+
}
|
|
161
|
+
return TaskStackBuilder.create(context).run {
|
|
162
|
+
addNextIntentWithParentStack(intent)
|
|
163
|
+
getPendingIntent(
|
|
164
|
+
userId.hashCode(),
|
|
165
|
+
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
|
166
|
+
)!!
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Anti-Patterns
|
|
174
|
+
|
|
175
|
+
- Using only custom scheme deep links for sensitive actions — other apps can register the same scheme
|
|
176
|
+
- Not verifying App Links — browser asks user to choose the app instead of opening directly
|
|
177
|
+
- Not validating URI parameters — path traversal or injection via malicious links
|
|
178
|
+
- Not handling `onNewIntent` — deep links sent to an already-running activity are ignored
|
|
179
|
+
- Trusting all incoming URIs without validation — treat as untrusted user input
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Related Skills
|
|
184
|
+
- `deep-navigation` — wiring deep links into Navigation Compose destinations
|
|
185
|
+
- `notification` — launching deep links from notifications
|
|
186
|
+
- `manifest` — intent filter configuration
|
|
187
|
+
- `nested-navigation` — deep linking into nested navigation graphs
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: foreground-service
|
|
3
|
+
description: >
|
|
4
|
+
Foreground Service implementation in Android.
|
|
5
|
+
Load this skill when running long-running user-visible operations like
|
|
6
|
+
media playback, file upload/download, location tracking, or any task
|
|
7
|
+
that must run while the app is in background with a visible notification.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Foreground Service
|
|
11
|
+
|
|
12
|
+
## Overview
|
|
13
|
+
A Foreground Service runs in the background while showing a persistent notification. It survives app backgrounding and is used for operations the user is actively aware of — music playback, navigation, ongoing uploads. Since Android 14, foreground service types must be declared explicitly.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Core Principles
|
|
18
|
+
|
|
19
|
+
- Always show a **meaningful notification** — it's required and user-facing
|
|
20
|
+
- Declare the correct **foreground service type** in the manifest
|
|
21
|
+
- Stop the service when the work is done — `stopSelf()` or `stopForeground()`
|
|
22
|
+
- Use `CoroutineScope` inside the service for async work
|
|
23
|
+
- Prefer WorkManager for deferrable work — ForegroundService is for user-visible ongoing tasks
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Manifest Declaration
|
|
28
|
+
|
|
29
|
+
```xml
|
|
30
|
+
<!-- AndroidManifest.xml -->
|
|
31
|
+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
|
32
|
+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
|
|
33
|
+
<!-- Add the type matching your use case -->
|
|
34
|
+
|
|
35
|
+
<application>
|
|
36
|
+
<service
|
|
37
|
+
android:name=".service.UploadService"
|
|
38
|
+
android:foregroundServiceType="dataSync"
|
|
39
|
+
android:exported="false" />
|
|
40
|
+
</application>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Service Types Reference
|
|
46
|
+
|
|
47
|
+
| Type | Use Case |
|
|
48
|
+
|------|----------|
|
|
49
|
+
| `dataSync` | Upload / download / sync |
|
|
50
|
+
| `mediaPlayback` | Music / podcast playback |
|
|
51
|
+
| `location` | Navigation, location tracking |
|
|
52
|
+
| `camera` | Video recording |
|
|
53
|
+
| `microphone` | Audio recording |
|
|
54
|
+
| `connectedDevice` | Bluetooth device communication |
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Basic Foreground Service
|
|
59
|
+
|
|
60
|
+
```kotlin
|
|
61
|
+
// ✅ Foreground service with coroutine scope
|
|
62
|
+
class UploadService : Service() {
|
|
63
|
+
|
|
64
|
+
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
|
65
|
+
private var uploadJob: Job? = null
|
|
66
|
+
|
|
67
|
+
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
|
68
|
+
when (intent?.action) {
|
|
69
|
+
ACTION_START -> {
|
|
70
|
+
val fileUri = intent.getStringExtra(EXTRA_FILE_URI) ?: run {
|
|
71
|
+
stopSelf()
|
|
72
|
+
return START_NOT_STICKY
|
|
73
|
+
}
|
|
74
|
+
startForegroundWithNotification()
|
|
75
|
+
startUpload(fileUri)
|
|
76
|
+
}
|
|
77
|
+
ACTION_CANCEL -> cancelUpload()
|
|
78
|
+
}
|
|
79
|
+
return START_NOT_STICKY
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private fun startForegroundWithNotification() {
|
|
83
|
+
val notification = buildNotification("Starting upload…")
|
|
84
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
85
|
+
startForeground(NOTIFICATION_ID, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_DATA_SYNC)
|
|
86
|
+
} else {
|
|
87
|
+
startForeground(NOTIFICATION_ID, notification)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private fun startUpload(fileUri: String) {
|
|
92
|
+
uploadJob = scope.launch {
|
|
93
|
+
try {
|
|
94
|
+
uploadRepository.upload(Uri.parse(fileUri)) { progress ->
|
|
95
|
+
updateNotification("Uploading… $progress%")
|
|
96
|
+
}
|
|
97
|
+
updateNotification("Upload complete")
|
|
98
|
+
delay(2_000)
|
|
99
|
+
} catch (e: CancellationException) {
|
|
100
|
+
updateNotification("Upload cancelled")
|
|
101
|
+
} catch (e: Exception) {
|
|
102
|
+
updateNotification("Upload failed: ${e.message}")
|
|
103
|
+
} finally {
|
|
104
|
+
stopForeground(STOP_FOREGROUND_REMOVE)
|
|
105
|
+
stopSelf()
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private fun cancelUpload() {
|
|
111
|
+
uploadJob?.cancel()
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
override fun onDestroy() {
|
|
115
|
+
scope.cancel()
|
|
116
|
+
super.onDestroy()
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
override fun onBind(intent: Intent?): IBinder? = null
|
|
120
|
+
|
|
121
|
+
private fun buildNotification(text: String): Notification {
|
|
122
|
+
createNotificationChannel()
|
|
123
|
+
return NotificationCompat.Builder(this, CHANNEL_ID)
|
|
124
|
+
.setContentTitle("File Upload")
|
|
125
|
+
.setContentText(text)
|
|
126
|
+
.setSmallIcon(R.drawable.ic_upload)
|
|
127
|
+
.setOngoing(true)
|
|
128
|
+
.build()
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
private fun updateNotification(text: String) {
|
|
132
|
+
val manager = getSystemService(NotificationManager::class.java)
|
|
133
|
+
manager.notify(NOTIFICATION_ID, buildNotification(text))
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private fun createNotificationChannel() {
|
|
137
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
138
|
+
val channel = NotificationChannel(
|
|
139
|
+
CHANNEL_ID, "Upload", NotificationManager.IMPORTANCE_LOW
|
|
140
|
+
)
|
|
141
|
+
getSystemService(NotificationManager::class.java).createNotificationChannel(channel)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
companion object {
|
|
146
|
+
const val ACTION_START = "action_start"
|
|
147
|
+
const val ACTION_CANCEL = "action_cancel"
|
|
148
|
+
const val EXTRA_FILE_URI = "file_uri"
|
|
149
|
+
private const val NOTIFICATION_ID = 1001
|
|
150
|
+
private const val CHANNEL_ID = "upload_channel"
|
|
151
|
+
|
|
152
|
+
fun startIntent(context: Context, fileUri: Uri): Intent =
|
|
153
|
+
Intent(context, UploadService::class.java).apply {
|
|
154
|
+
action = ACTION_START
|
|
155
|
+
putExtra(EXTRA_FILE_URI, fileUri.toString())
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
fun cancelIntent(context: Context): Intent =
|
|
159
|
+
Intent(context, UploadService::class.java).apply {
|
|
160
|
+
action = ACTION_CANCEL
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## Starting the Service
|
|
169
|
+
|
|
170
|
+
```kotlin
|
|
171
|
+
// ✅ Start from ViewModel via event
|
|
172
|
+
class UploadViewModel : ViewModel() {
|
|
173
|
+
private val _events = Channel<UploadEvent>(Channel.BUFFERED)
|
|
174
|
+
val events = _events.receiveAsFlow()
|
|
175
|
+
|
|
176
|
+
fun onUploadClick(fileUri: Uri) {
|
|
177
|
+
viewModelScope.launch {
|
|
178
|
+
_events.send(UploadEvent.StartService(fileUri))
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ✅ Handle in composable / activity
|
|
184
|
+
LaunchedEffect(Unit) {
|
|
185
|
+
viewModel.events.collect { event ->
|
|
186
|
+
when (event) {
|
|
187
|
+
is UploadEvent.StartService -> {
|
|
188
|
+
val intent = UploadService.startIntent(context, event.fileUri)
|
|
189
|
+
context.startForegroundService(intent)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Anti-Patterns
|
|
199
|
+
|
|
200
|
+
- Starting a ForegroundService for short tasks — use coroutines or WorkManager
|
|
201
|
+
- Not calling `stopSelf()` when work completes — service stays alive indefinitely
|
|
202
|
+
- Missing `foregroundServiceType` in manifest on Android 10+ — SecurityException
|
|
203
|
+
- Doing heavy work on the main thread inside `onStartCommand` — use a coroutine scope
|
|
204
|
+
- Not cancelling the coroutine scope in `onDestroy` — coroutine leak
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Related Skills
|
|
209
|
+
- `workmanager` — deferrable background work without a notification requirement
|
|
210
|
+
- `notification` — building and updating notifications
|
|
211
|
+
- `background-processing` — choosing the right background mechanism
|
|
212
|
+
- `coroutine` — coroutine scope inside services
|