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.
Files changed (176) hide show
  1. package/dist/index.js +143 -0
  2. package/package.json +27 -0
  3. package/skills/Android Ecosystem/Baseline Profile Generator/SKILL.md +277 -0
  4. package/skills/Android Ecosystem/Glance/SKILL.md +315 -0
  5. package/skills/Android Platform/Configuration/SKILL.md +201 -0
  6. package/skills/Android Platform/Filesystem/SKILL.md +216 -0
  7. package/skills/Android Platform/Lifecycle/SKILL.md +233 -0
  8. package/skills/Android Platform/Manifest/SKILL.md +226 -0
  9. package/skills/Android Platform/Process Death Recovery/SKILL.md +214 -0
  10. package/skills/Android Platform/Resources/SKILL.md +234 -0
  11. package/skills/Android Platform/SavedStateHandle/SKILL.md +217 -0
  12. package/skills/Android Platform/State Restoration/SKILL.md +210 -0
  13. package/skills/Architecture/Bounded Context/SKILL.md +207 -0
  14. package/skills/Architecture/Clean Architecture/SKILL.md +229 -0
  15. package/skills/Architecture/Domain Modeling/SKILL.md +236 -0
  16. package/skills/Architecture/Entity Design/SKILL.md +243 -0
  17. package/skills/Architecture/Feature Isolation/SKILL.md +216 -0
  18. package/skills/Architecture/MVI/SKILL.md +224 -0
  19. package/skills/Architecture/MVVM/SKILL.md +198 -0
  20. package/skills/Architecture/Modularization/SKILL.md +194 -0
  21. package/skills/Architecture/Offline First/SKILL.md +249 -0
  22. package/skills/Architecture/Repository Pattern/SKILL.md +216 -0
  23. package/skills/Architecture/Side Effect Management/SKILL.md +278 -0
  24. package/skills/Architecture/State Management/SKILL.md +229 -0
  25. package/skills/Architecture/Unidirectional Data Flow/SKILL.md +196 -0
  26. package/skills/Architecture/Use Case Design/SKILL.md +244 -0
  27. package/skills/Architecture/Value Object/SKILL.md +226 -0
  28. package/skills/Build Infrastructure/Build Orchestration/SKILL.md +257 -0
  29. package/skills/Build Infrastructure/Dependency Compatibility Resolver/SKILL.md +259 -0
  30. package/skills/Build Infrastructure/Environment Validator/SKILL.md +311 -0
  31. package/skills/Build System/Build Cache/SKILL.md +233 -0
  32. package/skills/Build System/Build Flavor Strategy/SKILL.md +171 -0
  33. package/skills/Build System/Build Variant/SKILL.md +215 -0
  34. package/skills/Build System/Convention Plugin/SKILL.md +288 -0
  35. package/skills/Build System/Dependency Management/SKILL.md +261 -0
  36. package/skills/Build System/Gradle/SKILL.md +284 -0
  37. package/skills/Build System/Incremental Build/SKILL.md +199 -0
  38. package/skills/Build System/KAPT/SKILL.md +198 -0
  39. package/skills/Build System/KSP/SKILL.md +263 -0
  40. package/skills/Build System/Module Dependency Graph Validation/SKILL.md +223 -0
  41. package/skills/Build System/Specialized/C++/SKILL.md +308 -0
  42. package/skills/Build System/Specialized/JNI/SKILL.md +306 -0
  43. package/skills/Build System/Specialized/NDK/SKILL.md +264 -0
  44. package/skills/Build System/Version Catalog/SKILL.md +304 -0
  45. package/skills/Concurrency/Background Processing/SKILL.md +185 -0
  46. package/skills/Concurrency/Channel/SKILL.md +207 -0
  47. package/skills/Concurrency/Coroutine/SKILL.md +200 -0
  48. package/skills/Concurrency/Flow/SKILL.md +179 -0
  49. package/skills/Concurrency/Mutex Strategy/SKILL.md +185 -0
  50. package/skills/Concurrency/SharedFlow/SKILL.md +171 -0
  51. package/skills/Concurrency/StateFlow/SKILL.md +175 -0
  52. package/skills/Concurrency/Structured Concurrency/SKILL.md +197 -0
  53. package/skills/Concurrency/Synchronization Policy/SKILL.md +192 -0
  54. package/skills/Core Language/Annotation Processing/SKILL.md +224 -0
  55. package/skills/Core Language/DSL/SKILL.md +186 -0
  56. package/skills/Core Language/Extension Functions Design/SKILL.md +191 -0
  57. package/skills/Core Language/Immutability/SKILL.md +156 -0
  58. package/skills/Core Language/KMP/SKILL.md +182 -0
  59. package/skills/Core Language/Kotlin/SKILL.md +187 -0
  60. package/skills/Core Language/Reactive State Management/SKILL.md +228 -0
  61. package/skills/Core Language/Reactive Streams/SKILL.md +235 -0
  62. package/skills/Core Language/Serialization/SKILL.md +191 -0
  63. package/skills/Data Layer/Cache Strategy/SKILL.md +261 -0
  64. package/skills/Data Layer/Conflict Resolution/SKILL.md +248 -0
  65. package/skills/Data Layer/DAO/SKILL.md +225 -0
  66. package/skills/Data Layer/DTO Mapping/SKILL.md +269 -0
  67. package/skills/Data Layer/DataStore/SKILL.md +264 -0
  68. package/skills/Data Layer/Database Versioning Strategy/SKILL.md +215 -0
  69. package/skills/Data Layer/Encrypted Database/SKILL.md +212 -0
  70. package/skills/Data Layer/File Storage/SKILL.md +247 -0
  71. package/skills/Data Layer/Indexing/SKILL.md +184 -0
  72. package/skills/Data Layer/Key-Value Store Strategy/SKILL.md +185 -0
  73. package/skills/Data Layer/Merge Strategy/SKILL.md +240 -0
  74. package/skills/Data Layer/Migration/SKILL.md +243 -0
  75. package/skills/Data Layer/Paging/SKILL.md +264 -0
  76. package/skills/Data Layer/Proto DataStore/SKILL.md +250 -0
  77. package/skills/Data Layer/Room/SKILL.md +244 -0
  78. package/skills/Data Layer/SQLite/SKILL.md +255 -0
  79. package/skills/Data Layer/Sync Engine/SKILL.md +268 -0
  80. package/skills/Dependency Injection/Dagger/SKILL.md +283 -0
  81. package/skills/Dependency Injection/Hilt/SKILL.md +345 -0
  82. package/skills/Dependency Injection/Koin/SKILL.md +282 -0
  83. package/skills/Developer Experience/Detekt/SKILL.md +272 -0
  84. package/skills/Developer Experience/Lint Rule/SKILL.md +281 -0
  85. package/skills/Google Ecosystem/Analytics/SKILL.md +281 -0
  86. package/skills/Google Ecosystem/Crashlytics/SKILL.md +234 -0
  87. package/skills/Google Ecosystem/Firebase/SKILL.md +200 -0
  88. package/skills/Google Ecosystem/Firebase Messaging/SKILL.md +266 -0
  89. package/skills/Media/Audio/SKILL.md +257 -0
  90. package/skills/Media/Camera/SKILL.md +229 -0
  91. package/skills/Media/CameraX/SKILL.md +295 -0
  92. package/skills/Media/ExoPlayer/SKILL.md +258 -0
  93. package/skills/Media/Video/SKILL.md +228 -0
  94. package/skills/Meta Skills/Domain Error Model/SKILL.md +238 -0
  95. package/skills/Meta Skills/Error Handling/SKILL.md +255 -0
  96. package/skills/Meta Skills/Error Mapping/SKILL.md +232 -0
  97. package/skills/Meta Skills/Failure Strategy/SKILL.md +294 -0
  98. package/skills/Meta Skills/Migration Strategy/SKILL.md +305 -0
  99. package/skills/Meta Skills/User Friendly Errors/SKILL.md +334 -0
  100. package/skills/Navigation/Deep Navigation/SKILL.md +209 -0
  101. package/skills/Navigation/Navigation/SKILL.md +215 -0
  102. package/skills/Navigation/Nested Navigation/SKILL.md +214 -0
  103. package/skills/Networking/API Contract/SKILL.md +220 -0
  104. package/skills/Networking/Authentication/SKILL.md +210 -0
  105. package/skills/Networking/Certificate Pinning/SKILL.md +167 -0
  106. package/skills/Networking/Fallback Strategy/SKILL.md +182 -0
  107. package/skills/Networking/Ktor/SKILL.md +219 -0
  108. package/skills/Networking/Multipart Upload/SKILL.md +213 -0
  109. package/skills/Networking/OkHttp/SKILL.md +193 -0
  110. package/skills/Networking/REST/SKILL.md +178 -0
  111. package/skills/Networking/Rate Limiting/SKILL.md +170 -0
  112. package/skills/Networking/Retrofit/SKILL.md +241 -0
  113. package/skills/Networking/Retry-Backoff/SKILL.md +181 -0
  114. package/skills/Networking/Server-Sent Events (SSE)/SKILL.md +196 -0
  115. package/skills/Networking/WebSocket/SKILL.md +224 -0
  116. package/skills/Observability/Crash Reporting/SKILL.md +219 -0
  117. package/skills/Observability/Logging/SKILL.md +168 -0
  118. package/skills/Observability/Metrics/SKILL.md +227 -0
  119. package/skills/Observability/Structured Logging/SKILL.md +234 -0
  120. package/skills/Performance/ANR Prevention/SKILL.md +192 -0
  121. package/skills/Performance/Allocation Optimization/SKILL.md +179 -0
  122. package/skills/Performance/App Startup/SKILL.md +183 -0
  123. package/skills/Performance/Baseline Profile/SKILL.md +205 -0
  124. package/skills/Performance/Battery Optimization/SKILL.md +192 -0
  125. package/skills/Performance/Benchmark/SKILL.md +182 -0
  126. package/skills/Performance/Bitmap Optimization/SKILL.md +178 -0
  127. package/skills/Performance/Compose Optimization/SKILL.md +187 -0
  128. package/skills/Performance/Heap Management/SKILL.md +184 -0
  129. package/skills/Performance/Macrobenchmark/SKILL.md +214 -0
  130. package/skills/Performance/Memory Leak Prevention/SKILL.md +218 -0
  131. package/skills/Performance/Rendering Performance/SKILL.md +205 -0
  132. package/skills/Performance/Startup Optimization/SKILL.md +219 -0
  133. package/skills/Security/Biometric/SKILL.md +224 -0
  134. package/skills/Security/Certificate Transparency/SKILL.md +158 -0
  135. package/skills/Security/Cryptography/SKILL.md +244 -0
  136. package/skills/Security/Encrypted Storage/SKILL.md +273 -0
  137. package/skills/Security/Frida Detection/SKILL.md +230 -0
  138. package/skills/Security/Hook Detection/SKILL.md +197 -0
  139. package/skills/Security/Keystore/SKILL.md +272 -0
  140. package/skills/Security/Network Security Config/SKILL.md +186 -0
  141. package/skills/Security/Obfuscation/SKILL.md +226 -0
  142. package/skills/Security/Proguard/SKILL.md +202 -0
  143. package/skills/Security/R8/SKILL.md +234 -0
  144. package/skills/Security/Reverse Engineering Resistance/SKILL.md +267 -0
  145. package/skills/Security/Root Detection/SKILL.md +220 -0
  146. package/skills/Security/Secure Networking/SKILL.md +220 -0
  147. package/skills/System Integration/AlarmManager/SKILL.md +182 -0
  148. package/skills/System Integration/App Widget/SKILL.md +182 -0
  149. package/skills/System Integration/Deep Link/SKILL.md +187 -0
  150. package/skills/System Integration/Foreground Service/SKILL.md +212 -0
  151. package/skills/System Integration/Notification/SKILL.md +237 -0
  152. package/skills/System Integration/WorkManager/SKILL.md +256 -0
  153. package/skills/System Integration/clipboard/SKILL.md +155 -0
  154. package/skills/System Integration/share-intent/SKILL.md +182 -0
  155. package/skills/Testing/Compose Testing/SKILL.md +296 -0
  156. package/skills/Testing/Espresso/SKILL.md +292 -0
  157. package/skills/Testing/Fake Data/SKILL.md +245 -0
  158. package/skills/Testing/Integration Testing/SKILL.md +288 -0
  159. package/skills/Testing/Mocking/SKILL.md +229 -0
  160. package/skills/Testing/Snapshot Testing/SKILL.md +259 -0
  161. package/skills/Testing/UI Testing/SKILL.md +293 -0
  162. package/skills/Testing/Unit Testing/SKILL.md +309 -0
  163. package/skills/UI System/Bottom Sheet Patterns/SKILL.md +279 -0
  164. package/skills/UI System/Compose/SKILL.md +296 -0
  165. package/skills/UI System/Compose Animation/SKILL.md +281 -0
  166. package/skills/UI System/Compose Multiplatform/SKILL.md +261 -0
  167. package/skills/UI System/Compose Navigation/SKILL.md +255 -0
  168. package/skills/UI System/Compose Performance/SKILL.md +274 -0
  169. package/skills/UI System/Design System/SKILL.md +217 -0
  170. package/skills/UI System/Empty State Strategy/SKILL.md +208 -0
  171. package/skills/UI System/Keyboard Navigation/SKILL.md +214 -0
  172. package/skills/UI System/Loading Strategy/SKILL.md +254 -0
  173. package/skills/UI System/Material 3/SKILL.md +279 -0
  174. package/skills/UI System/RTL/SKILL.md +179 -0
  175. package/src/index.ts +182 -0
  176. package/tsconfig.json +19 -0
@@ -0,0 +1,266 @@
1
+ ---
2
+ name: firebase-messaging
3
+ description: >
4
+ Firebase Cloud Messaging (FCM) setup and handling for Android.
5
+ Load this skill when implementing push notifications, handling FCM tokens,
6
+ processing foreground/background messages, or sending data payloads.
7
+ ---
8
+
9
+ # Firebase Messaging (FCM)
10
+
11
+ ## Overview
12
+ Firebase Cloud Messaging (FCM) is Google's cross-platform messaging solution for push notifications. It delivers notification messages (shown by system) and data messages (handled by app code) to Android devices. FCM tokens identify each device/app installation.
13
+
14
+ ---
15
+
16
+ ## Core Principles
17
+
18
+ - **Notification messages** — shown automatically by system when app is in background
19
+ - **Data messages** — always delivered to `onMessageReceived()` for app to handle
20
+ - FCM token can change — always update the server when token refreshes
21
+ - Notification permission is required on **API 33+** — `POST_NOTIFICATIONS`
22
+ - Handle messages in a **short time** — `onMessageReceived()` has ~10 second limit
23
+
24
+ ---
25
+
26
+ ## Setup
27
+
28
+ ```kotlin
29
+ // build.gradle.kts
30
+ dependencies {
31
+ implementation(platform(libs.firebase.bom))
32
+ implementation(libs.firebase.messaging)
33
+ }
34
+ ```
35
+
36
+ ```xml
37
+ <!-- AndroidManifest.xml -->
38
+ <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
39
+
40
+ <service
41
+ android:name=".messaging.MyFirebaseMessagingService"
42
+ android:exported="false">
43
+ <intent-filter>
44
+ <action android:name="com.google.firebase.MESSAGING_EVENT" />
45
+ </intent-filter>
46
+ </service>
47
+ ```
48
+
49
+ ---
50
+
51
+ ## MessagingService Implementation
52
+
53
+ ```kotlin
54
+ // ✅ Handle incoming messages and token refresh
55
+ class MyFirebaseMessagingService : FirebaseMessagingService() {
56
+
57
+ @Inject
58
+ lateinit var notificationManager: AppNotificationManager
59
+
60
+ @Inject
61
+ lateinit var tokenRepository: FcmTokenRepository
62
+
63
+ // ✅ Called when a new FCM token is generated
64
+ override fun onNewToken(token: String) {
65
+ super.onNewToken(token)
66
+ // Upload new token to your server
67
+ CoroutineScope(SupervisorJob() + Dispatchers.IO).launch {
68
+ tokenRepository.updateToken(token)
69
+ }
70
+ }
71
+
72
+ // ✅ Called for all messages when app is in foreground
73
+ // Called for data-only messages when app is in background
74
+ override fun onMessageReceived(message: RemoteMessage) {
75
+ super.onMessageReceived(message)
76
+
77
+ val title = message.notification?.title ?: message.data["title"] ?: return
78
+ val body = message.notification?.body ?: message.data["body"] ?: return
79
+ val type = message.data["type"]
80
+ val id = message.data["id"]
81
+
82
+ notificationManager.showNotification(
83
+ title = title,
84
+ body = body,
85
+ data = message.data
86
+ )
87
+ }
88
+ }
89
+ ```
90
+
91
+ ---
92
+
93
+ ## FCM Token Management
94
+
95
+ ```kotlin
96
+ // ✅ Get current token
97
+ class FcmTokenRepository @Inject constructor(
98
+ private val api: UserApi,
99
+ private val prefs: DataStore<Preferences>
100
+ ) {
101
+ suspend fun getToken(): String? = suspendCoroutine { cont ->
102
+ FirebaseMessaging.getInstance().token
103
+ .addOnSuccessListener { cont.resume(it) }
104
+ .addOnFailureListener { cont.resume(null) }
105
+ }
106
+
107
+ suspend fun updateToken(token: String) {
108
+ val saved = prefs.data.first()[FCM_TOKEN_KEY]
109
+ if (saved != token) {
110
+ api.updateFcmToken(token)
111
+ prefs.edit { it[FCM_TOKEN_KEY] = token }
112
+ }
113
+ }
114
+
115
+ suspend fun deleteToken() {
116
+ FirebaseMessaging.getInstance().deleteToken().await()
117
+ api.deleteFcmToken()
118
+ prefs.edit { it.remove(FCM_TOKEN_KEY) }
119
+ }
120
+
121
+ companion object {
122
+ val FCM_TOKEN_KEY = stringPreferencesKey("fcm_token")
123
+ }
124
+ }
125
+ ```
126
+
127
+ ---
128
+
129
+ ## Notification Permission (API 33+)
130
+
131
+ ```kotlin
132
+ // ✅ Request POST_NOTIFICATIONS permission on Android 13+
133
+ @Composable
134
+ fun NotificationPermissionHandler(onGranted: () -> Unit) {
135
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
136
+ val permissionState = rememberPermissionState(
137
+ Manifest.permission.POST_NOTIFICATIONS
138
+ )
139
+ LaunchedEffect(Unit) {
140
+ if (!permissionState.status.isGranted) {
141
+ permissionState.launchPermissionRequest()
142
+ } else {
143
+ onGranted()
144
+ }
145
+ }
146
+ } else {
147
+ LaunchedEffect(Unit) { onGranted() }
148
+ }
149
+ }
150
+ ```
151
+
152
+ ---
153
+
154
+ ## Showing Notifications
155
+
156
+ ```kotlin
157
+ // ✅ NotificationManager wrapper
158
+ class AppNotificationManager @Inject constructor(
159
+ private val context: Context
160
+ ) {
161
+ private val notificationManager =
162
+ context.getSystemService(NotificationManager::class.java)
163
+
164
+ init {
165
+ createChannels()
166
+ }
167
+
168
+ private fun createChannels() {
169
+ val channel = NotificationChannel(
170
+ CHANNEL_ID_GENERAL,
171
+ "General",
172
+ NotificationManager.IMPORTANCE_DEFAULT
173
+ ).apply {
174
+ description = "General app notifications"
175
+ }
176
+ notificationManager.createNotificationChannel(channel)
177
+ }
178
+
179
+ fun showNotification(title: String, body: String, data: Map<String, String>) {
180
+ val intent = Intent(context, MainActivity::class.java).apply {
181
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
182
+ data.forEach { (key, value) -> putExtra(key, value) }
183
+ }
184
+
185
+ val pendingIntent = PendingIntent.getActivity(
186
+ context, 0, intent,
187
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
188
+ )
189
+
190
+ val notification = NotificationCompat.Builder(context, CHANNEL_ID_GENERAL)
191
+ .setSmallIcon(R.drawable.ic_notification)
192
+ .setContentTitle(title)
193
+ .setContentText(body)
194
+ .setAutoCancel(true)
195
+ .setContentIntent(pendingIntent)
196
+ .setPriority(NotificationCompat.PRIORITY_DEFAULT)
197
+ .build()
198
+
199
+ notificationManager.notify(System.currentTimeMillis().toInt(), notification)
200
+ }
201
+
202
+ companion object {
203
+ const val CHANNEL_ID_GENERAL = "general"
204
+ }
205
+ }
206
+ ```
207
+
208
+ ---
209
+
210
+ ## Message Types
211
+
212
+ ```kotlin
213
+ // Notification message (server-sent):
214
+ // {
215
+ // "notification": { "title": "Hello", "body": "World" },
216
+ // "to": "<fcm_token>"
217
+ // }
218
+ // → System shows notification automatically when app in background
219
+ // → onMessageReceived() called when app in foreground
220
+
221
+ // Data message (server-sent):
222
+ // {
223
+ // "data": { "title": "Hello", "body": "World", "type": "order_update" },
224
+ // "to": "<fcm_token>"
225
+ // }
226
+ // → onMessageReceived() always called regardless of app state
227
+
228
+ // ✅ Prefer data messages for full control over display
229
+ ```
230
+
231
+ ---
232
+
233
+ ## Topic Subscription
234
+
235
+ ```kotlin
236
+ // ✅ Subscribe to topics for broadcast messages
237
+ suspend fun subscribeToTopic(topic: String) {
238
+ FirebaseMessaging.getInstance().subscribeToTopic(topic).await()
239
+ }
240
+
241
+ suspend fun unsubscribeFromTopic(topic: String) {
242
+ FirebaseMessaging.getInstance().unsubscribeFromTopic(topic).await()
243
+ }
244
+
245
+ // Usage
246
+ subscribeToTopic("promotions")
247
+ subscribeToTopic("user_${userId}")
248
+ ```
249
+
250
+ ---
251
+
252
+ ## Anti-Patterns
253
+
254
+ - Not updating token on `onNewToken()` — server sends to stale token, messages lost
255
+ - Long-running work in `onMessageReceived()` — only 10 seconds available; use WorkManager
256
+ - Notification messages for data that needs app processing — use data messages
257
+ - Not creating notification channels — notifications silently dropped on API 26+
258
+ - Not handling `POST_NOTIFICATIONS` permission on API 33+ — no notifications shown
259
+
260
+ ---
261
+
262
+ ## Related Skills
263
+ - `firebase` — Firebase core setup
264
+ - `notification` — notification channels and display
265
+ - `workmanager` — background work triggered by FCM
266
+ - `deep-link` — navigating to specific screen from notification tap
@@ -0,0 +1,257 @@
1
+ ---
2
+ name: audio
3
+ description: >
4
+ Android audio playback and recording patterns.
5
+ Load this skill when playing audio files, recording from microphone,
6
+ managing audio focus, or handling audio streams in Android.
7
+ ---
8
+
9
+ # Audio
10
+
11
+ ## Overview
12
+ Android provides multiple APIs for audio — `MediaPlayer` for simple playback, `AudioTrack` for low-latency PCM, `MediaRecorder` for recording, and `AudioRecord` for raw PCM capture. Choosing the right API depends on the use case: streaming, local file, or real-time processing.
13
+
14
+ ---
15
+
16
+ ## Core Principles
17
+
18
+ - Always **request audio focus** before playback — respect other apps
19
+ - **Release resources** (`MediaPlayer`, `MediaRecorder`) when done — they hold hardware
20
+ - Audio operations that block belong on **Dispatchers.IO**
21
+ - Handle **audio output changes** (headphone unplug, Bluetooth) — pause when needed
22
+ - Declare `RECORD_AUDIO` permission for recording — runtime permission required
23
+
24
+ ---
25
+
26
+ ## Permissions
27
+
28
+ ```xml
29
+ <!-- AndroidManifest.xml -->
30
+ <uses-permission android:name="android.permission.RECORD_AUDIO" />
31
+ ```
32
+
33
+ ---
34
+
35
+ ## MediaPlayer — Simple Playback
36
+
37
+ ```kotlin
38
+ // ✅ Play from resource
39
+ class AudioPlayer(private val context: Context) {
40
+
41
+ private var mediaPlayer: MediaPlayer? = null
42
+
43
+ fun playFromResource(@RawRes resId: Int) {
44
+ release()
45
+ mediaPlayer = MediaPlayer.create(context, resId).apply {
46
+ setOnCompletionListener { release() }
47
+ start()
48
+ }
49
+ }
50
+
51
+ // ✅ Play from URL (streaming)
52
+ fun playFromUrl(url: String) {
53
+ release()
54
+ mediaPlayer = MediaPlayer().apply {
55
+ setAudioAttributes(
56
+ AudioAttributes.Builder()
57
+ .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
58
+ .setUsage(AudioAttributes.USAGE_MEDIA)
59
+ .build()
60
+ )
61
+ setDataSource(url)
62
+ setOnPreparedListener { start() }
63
+ setOnErrorListener { _, what, extra ->
64
+ Log.e("AudioPlayer", "Error: what=$what extra=$extra")
65
+ true
66
+ }
67
+ prepareAsync() // ✅ Non-blocking prepare for streams
68
+ }
69
+ }
70
+
71
+ fun pause() { mediaPlayer?.pause() }
72
+ fun resume() { mediaPlayer?.start() }
73
+ fun stop() { mediaPlayer?.stop() }
74
+
75
+ // ✅ Always release — frees hardware resource
76
+ fun release() {
77
+ mediaPlayer?.release()
78
+ mediaPlayer = null
79
+ }
80
+ }
81
+ ```
82
+
83
+ ---
84
+
85
+ ## Audio Focus
86
+
87
+ ```kotlin
88
+ // ✅ Request audio focus before playback
89
+ class AudioFocusManager(private val context: Context) {
90
+
91
+ private val audioManager = context.getSystemService(AudioManager::class.java)
92
+ private var focusRequest: AudioFocusRequest? = null
93
+
94
+ fun requestFocus(onGranted: () -> Unit, onLost: () -> Unit): Boolean {
95
+ val request = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN)
96
+ .setAudioAttributes(
97
+ AudioAttributes.Builder()
98
+ .setUsage(AudioAttributes.USAGE_MEDIA)
99
+ .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
100
+ .build()
101
+ )
102
+ .setOnAudioFocusChangeListener { focusChange ->
103
+ when (focusChange) {
104
+ AudioManager.AUDIOFOCUS_LOSS -> onLost()
105
+ AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> onLost() // pause temporarily
106
+ AudioManager.AUDIOFOCUS_GAIN -> onGranted() // resume
107
+ }
108
+ }
109
+ .build()
110
+
111
+ focusRequest = request
112
+ val result = audioManager.requestAudioFocus(request)
113
+ return result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED
114
+ }
115
+
116
+ fun abandonFocus() {
117
+ focusRequest?.let { audioManager.abandonAudioFocusRequest(it) }
118
+ focusRequest = null
119
+ }
120
+ }
121
+ ```
122
+
123
+ ---
124
+
125
+ ## Handle Headphone Unplug
126
+
127
+ ```kotlin
128
+ // ✅ Pause when headphones are disconnected
129
+ class NoisyReceiver(private val onBecomingNoisy: () -> Unit) : BroadcastReceiver() {
130
+ override fun onReceive(context: Context, intent: Intent) {
131
+ if (intent.action == AudioManager.ACTION_AUDIO_BECOMING_NOISY) {
132
+ onBecomingNoisy() // pause playback
133
+ }
134
+ }
135
+ }
136
+
137
+ // Register/unregister with lifecycle
138
+ override fun onStart() {
139
+ super.onStart()
140
+ val filter = IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY)
141
+ registerReceiver(noisyReceiver, filter)
142
+ }
143
+
144
+ override fun onStop() {
145
+ unregisterReceiver(noisyReceiver)
146
+ super.onStop()
147
+ }
148
+ ```
149
+
150
+ ---
151
+
152
+ ## MediaRecorder — Simple Recording
153
+
154
+ ```kotlin
155
+ // ✅ Record audio to file
156
+ class AudioRecorder(private val context: Context) {
157
+
158
+ private var recorder: MediaRecorder? = null
159
+
160
+ fun startRecording(outputFile: File) {
161
+ recorder = MediaRecorder(context).apply {
162
+ setAudioSource(MediaRecorder.AudioSource.MIC)
163
+ setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
164
+ setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
165
+ setAudioSamplingRate(44100)
166
+ setAudioEncodingBitRate(128_000)
167
+ setOutputFile(outputFile.absolutePath)
168
+ prepare()
169
+ start()
170
+ }
171
+ }
172
+
173
+ fun stopRecording(): File? {
174
+ return try {
175
+ recorder?.apply {
176
+ stop()
177
+ release()
178
+ }
179
+ recorder = null
180
+ outputFile
181
+ } catch (e: Exception) {
182
+ null
183
+ }
184
+ }
185
+
186
+ fun release() {
187
+ recorder?.release()
188
+ recorder = null
189
+ }
190
+ }
191
+ ```
192
+
193
+ ---
194
+
195
+ ## AudioRecord — Raw PCM Capture
196
+
197
+ ```kotlin
198
+ // ✅ For real-time audio processing (speech recognition, audio analysis)
199
+ class RawAudioCapture {
200
+
201
+ private val sampleRate = 44100
202
+ private val bufferSize = AudioRecord.getMinBufferSize(
203
+ sampleRate,
204
+ AudioFormat.CHANNEL_IN_MONO,
205
+ AudioFormat.ENCODING_PCM_16BIT
206
+ )
207
+
208
+ private var audioRecord: AudioRecord? = null
209
+ private var isRecording = false
210
+
211
+ fun startCapture(onAudioData: (ShortArray) -> Unit) {
212
+ audioRecord = AudioRecord(
213
+ MediaRecorder.AudioSource.MIC,
214
+ sampleRate,
215
+ AudioFormat.CHANNEL_IN_MONO,
216
+ AudioFormat.ENCODING_PCM_16BIT,
217
+ bufferSize
218
+ )
219
+
220
+ isRecording = true
221
+ audioRecord?.startRecording()
222
+
223
+ Thread {
224
+ val buffer = ShortArray(bufferSize)
225
+ while (isRecording) {
226
+ val read = audioRecord?.read(buffer, 0, buffer.size) ?: 0
227
+ if (read > 0) onAudioData(buffer.copyOf(read))
228
+ }
229
+ }.start()
230
+ }
231
+
232
+ fun stopCapture() {
233
+ isRecording = false
234
+ audioRecord?.stop()
235
+ audioRecord?.release()
236
+ audioRecord = null
237
+ }
238
+ }
239
+ ```
240
+
241
+ ---
242
+
243
+ ## Anti-Patterns
244
+
245
+ - Not releasing `MediaPlayer` or `MediaRecorder` — holds hardware, other apps can't use it
246
+ - Playing audio without requesting audio focus — overlaps with other apps' audio
247
+ - `prepare()` instead of `prepareAsync()` for network streams — blocks main thread
248
+ - Not handling `AudioManager.ACTION_AUDIO_BECOMING_NOISY` — audio leaks from speaker
249
+ - Recording without checking/requesting `RECORD_AUDIO` permission
250
+
251
+ ---
252
+
253
+ ## Related Skills
254
+ - `exoplayer` — advanced media playback
255
+ - `foreground-service` — background audio playback
256
+ - `lifecycle` — releasing audio resources with lifecycle
257
+ - `manifest` — audio permission declaration