<!-- task=mattermost_push_plugin_extraction/02+01_android_native plan=0 tag=REFACTOR -->

# Android Native Plugin Move Plan

## 이 파일을 읽는 구현 에이전트에게

구현을 끝내기 전 `CODE_REVIEW-cloud-G08.md`의 구현 에이전트 소유 섹션을 반드시 채운다. 검증 명령을 실제로 실행하고 stdout/stderr를 붙여 넣은 뒤 active 파일을 그대로 둔 채 review ready를 보고한다. 최종 판정, log rename, `complete.log`, archive 이동은 code-review-skill 전용이다.

## 배경

Mattermost push Android 구현은 현재 host app package `com.tokilabs.mattermost`에 직접 들어 있다. Milestone은 FCM service, notification builder, reply/dismiss receiver, ACK, Room DB helper, network helper, notification resources를 plugin 소유로 이동하라고 요구한다. 이 작업은 `01_plugin_api`가 만든 plugin scaffold에 native implementation을 추가하되, host app call-site cleanup은 다음 subtask로 남긴다.

## 분석 결과

### 읽은 파일

- `agent-ops/roadmap/current.md`
- `agent-ops/roadmap/milestones/mattermost-push-plugin-extraction.md`
- `agent-ops/rules/project/domain/mobile/rules.md`
- `apps/mobile/android/app/build.gradle.kts`
- `apps/mobile/android/app/src/main/AndroidManifest.xml`
- `apps/mobile/android/app/src/main/java/com/tokilabs/mattermost/AppLifecycleTracker.kt`
- `apps/mobile/android/app/src/main/java/com/tokilabs/mattermost/MainActivity.kt`
- `apps/mobile/android/app/src/main/java/com/tokilabs/mattermost/MainApplication.kt`
- `apps/mobile/android/app/src/main/java/com/tokilabs/mattermost/MattermostFirebaseMessagingService.kt`
- `apps/mobile/android/app/src/main/java/com/tokilabs/mattermost/NotificationDismissService.java`
- `apps/mobile/android/app/src/main/java/com/tokilabs/mattermost/NotificationReplyBroadcastReceiver.java`
- `apps/mobile/android/app/src/main/java/com/tokilabs/mattermost/ReceiptDelivery.java`
- `apps/mobile/android/app/src/main/java/com/tokilabs/mattermost/helpers/BitmapCache.kt`
- `apps/mobile/android/app/src/main/java/com/tokilabs/mattermost/helpers/CustomPushNotificationHelper.java`
- `apps/mobile/android/app/src/main/java/com/tokilabs/mattermost/helpers/DatabaseHelper.kt`
- `apps/mobile/android/app/src/main/java/com/tokilabs/mattermost/helpers/Network.kt`
- `apps/mobile/android/app/src/main/java/com/tokilabs/mattermost/helpers/NotificationHelper.kt`
- `apps/mobile/android/app/src/main/java/com/tokilabs/mattermost/helpers/PushNotificationDataHelper.kt`
- `apps/mobile/android/app/src/main/java/com/tokilabs/mattermost/helpers/db/GlobalDatabase.kt`
- `apps/mobile/android/app/src/main/java/com/tokilabs/mattermost/helpers/db/GlobalEntities.kt`
- `apps/mobile/android/app/src/main/java/com/tokilabs/mattermost/helpers/db/MattermostDatabase.kt`
- `apps/mobile/android/app/src/main/java/com/tokilabs/mattermost/helpers/db/ServerEntities.kt`
- `apps/mobile/lib/services/push_notification_service.dart`
- `apps/mobile/test/widget_test.dart`

### 테스트 커버리지 공백

- Android FCM service behavior: no JVM/instrumentation tests exist; verify with plugin Android compile and later host debug build.
- Inline reply/ACK/auth token paths: no tests exist; compile plus smoke criteria are required.
- Room DB helper behavior: existing stubs are not covered and are owned by `04+02_risk_hardening`.

### 심볼 참조

- `MattermostFirebaseMessagingService`: manifest `AndroidManifest.xml:58`, `MainActivity.kt:31`, service definition `MattermostFirebaseMessagingService.kt:29`.
- `MainActivity`: definition `MainActivity.kt:19`, notification launch intent `MattermostFirebaseMessagingService.kt:221`, reply recreate intent `NotificationReplyBroadcastReceiver.java:122`.
- `MainApplication`: manifest `AndroidManifest.xml:10`, definition `MainApplication.kt:13`.
- `NotificationReplyBroadcastReceiver`: manifest `AndroidManifest.xml:73`, helper import `CustomPushNotificationHelper.java:33`.
- `NotificationDismissService`: manifest `AndroidManifest.xml:67`, helper import `CustomPushNotificationHelper.java:32`.
- `ReceiptDelivery`: called by `MattermostFirebaseMessagingService.kt:143`.
- `DatabaseHelper`, `Network`, `NotificationHelper`, `PushNotificationDataHelper`, DB classes are referenced throughout native push files and must move together.

### 분할 판단

- Split decision policy was evaluated before choosing plan files.
- This subtask depends on sibling `01_plugin_api`; `agent-task/mattermost_push_plugin_extraction/01_plugin_api/complete.log` must exist before implementation starts.
- Native move is split from host consumer cleanup because plugin package can compile independently before deleting app-owned native files.
- Risk-hardening is split into `04+02_risk_hardening` because DB schema/stub work has different storage risk and review surface.

### 범위 결정 근거

- Do not delete host app native files in this subtask; `03+01,02_host_consumer` owns app cleanup after plugin native code compiles.
- Do not change Dart app imports in this subtask; `01_plugin_api` owns plugin API and `03+01,02_host_consumer` owns consumer migration.
- Do not complete DAO stubs or DB singleton close fixes here; copy current behavior first, then harden in `04+02_risk_hardening`.

### 빌드 등급

- Build lane: `cloud-G08`; review lane: `cloud-G08`. This touches Android service registration, auth token use, network ACK/reply, notification resources, and Room/manifest dependencies with weak tests.

## 구현 체크리스트

- [ ] [REFACTOR-1] Android plugin class, lifecycle registration, EventChannel/MethodChannel handling을 plugin으로 구현한다.
- [ ] [REFACTOR-2] FCM service, reply/dismiss receiver, ACK, helper, DB source를 plugin Android package로 이동한다.
- [ ] [REFACTOR-3] Android manifest, resources, Gradle dependencies를 plugin 소유로 구성하고 plugin native compile을 검증한다.
- [ ] 중간 검증과 최종 검증 명령을 모두 실행하고 실제 출력을 `CODE_REVIEW-cloud-G08.md`에 붙여 넣는다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 의존 관계 및 구현 순서

- Directory dependency: `02+01_android_native` depends on `01_plugin_api`.
- Start only after `agent-task/mattermost_push_plugin_extraction/01_plugin_api/complete.log` exists.

### [REFACTOR-1] Plugin Registration Boundary

#### 문제

`MainActivity.kt:26` registers channels directly and stores the Flutter engine in `FlutterEngineCache`, while `MainApplication.kt:18` initializes lifecycle, DB, and notification channels. A plugin must own this boundary.

Before (`apps/mobile/android/app/src/main/java/com/tokilabs/mattermost/MainActivity.kt:26`):

```kotlin
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
    super.configureFlutterEngine(flutterEngine)
    FlutterEngineCache.getInstance()
        .put(MattermostFirebaseMessagingService.FLUTTER_ENGINE_ID, flutterEngine)
    EventChannel(flutterEngine.dartExecutor.binaryMessenger, NOTIFICATION_EVENT_CHANNEL)
```

After (`../mattermost-push-plugin/android/src/main/kotlin/com/tokilabs/mattermost_push_plugin/MattermostPushPlugin.kt`):

```kotlin
class MattermostPushPlugin : FlutterPlugin, ActivityAware, EventChannel.StreamHandler, MethodCallHandler {
    override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {
        DatabaseHelper.init(binding.applicationContext)
        CustomPushNotificationHelper.createNotificationChannels(binding.applicationContext)
        EventChannel(binding.binaryMessenger, CHANNEL_EVENTS).setStreamHandler(this)
        MethodChannel(binding.binaryMessenger, CHANNEL_ACTIONS).setMethodCallHandler(this)
    }
}
```

#### 해결 방법

Implement `MattermostPushPlugin` as the only Flutter channel registrar. Move channel constants into plugin-owned code. Register lifecycle callbacks through `ActivityAware` when an activity is available, and make services call `DatabaseHelper.init(applicationContext)` defensively when started before plugin attachment.

#### 수정 파일 및 체크리스트

- [ ] `../mattermost-push-plugin/android/src/main/kotlin/com/tokilabs/mattermost_push_plugin/MattermostPushPlugin.kt` 작성
- [ ] Event sink ownership moves from service static field to plugin-owned dispatcher or companion object
- [ ] Method calls `saveDeviceToken`, `getDeviceToken`, `setAuthToken`, `setSigningKey`, `clearAuthToken` preserve argument names
- [ ] Activity lifecycle callback registration/unregistration handles detach

#### 테스트 작성

Skip Android unit tests here because the generated plugin has no Android test harness. Verification is compile/build based.

#### 중간 검증

```bash
cd /config/workspace/mattermost-push-plugin && flutter analyze --no-fatal-infos
```

Expected: Dart side and generated plugin metadata analyze cleanly.

### [REFACTOR-2] Move Native Push Source

#### 문제

`MattermostFirebaseMessagingService.kt:29` and related helper classes are app-owned. They refer to app `MainActivity` at `MattermostFirebaseMessagingService.kt:221` and `NotificationReplyBroadcastReceiver.java:122`, so a direct copy would keep a host dependency.

Before (`apps/mobile/android/app/src/main/java/com/tokilabs/mattermost/MattermostFirebaseMessagingService.kt:220`):

```kotlin
private fun buildAndPostNotification(notificationId: Int, createSummary: Boolean, bundle: Bundle) {
    val intent = Intent(this, MainActivity::class.java).apply {
        flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
        putExtras(bundle)
    }
```

After (`../mattermost-push-plugin/android/src/main/kotlin/com/tokilabs/mattermost_push_plugin/MattermostFirebaseMessagingService.kt`):

```kotlin
private fun launchIntent(context: Context, bundle: Bundle): Intent {
    return context.packageManager.getLaunchIntentForPackage(context.packageName)!!.apply {
        flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
        putExtras(bundle)
    }
}
```

#### 해결 방법

Move Kotlin/Java files under plugin package paths and update package/import statements. Replace `MainActivity::class.java` references with package launch intents so the plugin does not know the host activity class. Keep behavior-preserving code changes only; stub hardening waits for `04+02_risk_hardening`.

#### 수정 파일 및 체크리스트

- [ ] Move `AppLifecycleTracker.kt`
- [ ] Move `MattermostFirebaseMessagingService.kt`
- [ ] Move `NotificationDismissService.java`
- [ ] Move `NotificationReplyBroadcastReceiver.java`
- [ ] Move `ReceiptDelivery.java`
- [ ] Move `helpers/**`
- [ ] Move `helpers/db/**`
- [ ] Update package/imports from `com.tokilabs.mattermost` to plugin package
- [ ] Replace host `MainActivity` references with launch-intent helper

#### 테스트 작성

Skip direct tests. The code path requires Android runtime, FCM, NotificationManager, and Room. Compile and debug build are required.

#### 중간 검증

```bash
cd /config/workspace/mattermost-push-plugin/example && flutter build apk --debug
```

Expected: generated plugin example compiles with moved Android native code.

### [REFACTOR-3] Manifest, Resources, Dependencies

#### 문제

Host app owns notification manifest entries at `AndroidManifest.xml:56`, reply/dismiss registrations at `67` and `73`, and Android dependencies at `app/build.gradle.kts:52-71`.

Before (`apps/mobile/android/app/build.gradle.kts:49`):

```kotlin
dependencies {
    coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4")
    implementation(platform("com.google.firebase:firebase-bom:33.7.0"))
    implementation("com.google.firebase:firebase-messaging")
```

After (`../mattermost-push-plugin/android/build.gradle` or generated Kotlin DSL equivalent):

```kotlin
dependencies {
    implementation(platform("com.google.firebase:firebase-bom:33.7.0"))
    implementation("com.google.firebase:firebase-messaging")
    implementation("io.jsonwebtoken:jjwt-api:0.12.5")
    runtimeOnly("io.jsonwebtoken:jjwt-impl:0.12.5")
}
```

#### 해결 방법

Declare plugin service/receiver entries in plugin Android manifest. Move notification reply and status icon resources into plugin resource directories and update `R` references. Add Firebase Messaging, JJWT, OkHttp, coroutines, Room, and kapt/ksp configuration to plugin Android Gradle. Do not remove host app dependencies until `03+01,02_host_consumer`.

#### 수정 파일 및 체크리스트

- [ ] `../mattermost-push-plugin/android/src/main/AndroidManifest.xml` includes FCM service and receivers
- [ ] `../mattermost-push-plugin/android/src/main/res/drawable*/ic_notif_action_reply.png` copied
- [ ] `../mattermost-push-plugin/android/src/main/res/mipmap*/ic_notification.png` copied
- [ ] Plugin Android Gradle includes Firebase Messaging, JJWT, OkHttp, coroutines, Room runtime/ktx/compiler
- [ ] Package `R` imports resolve to plugin package

#### 테스트 작성

Skip separate tests; manifest/resource correctness is verified by Android build.

#### 중간 검증

```bash
cd /config/workspace/mattermost-push-plugin/example && flutter build apk --debug
```

Expected: Android build succeeds and resource references resolve.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `../mattermost-push-plugin/android/src/main/kotlin/com/tokilabs/mattermost_push_plugin/MattermostPushPlugin.kt` | REFACTOR-1 |
| `../mattermost-push-plugin/android/src/main/kotlin/com/tokilabs/mattermost_push_plugin/MattermostFirebaseMessagingService.kt` | REFACTOR-2 |
| `../mattermost-push-plugin/android/src/main/java/com/tokilabs/mattermost_push_plugin/NotificationReplyBroadcastReceiver.java` | REFACTOR-2 |
| `../mattermost-push-plugin/android/src/main/java/com/tokilabs/mattermost_push_plugin/NotificationDismissService.java` | REFACTOR-2 |
| `../mattermost-push-plugin/android/src/main/java/com/tokilabs/mattermost_push_plugin/ReceiptDelivery.java` | REFACTOR-2 |
| `../mattermost-push-plugin/android/src/main/kotlin/com/tokilabs/mattermost_push_plugin/helpers/**` | REFACTOR-2 |
| `../mattermost-push-plugin/android/src/main/kotlin/com/tokilabs/mattermost_push_plugin/helpers/db/**` | REFACTOR-2 |
| `../mattermost-push-plugin/android/src/main/AndroidManifest.xml` | REFACTOR-3 |
| `../mattermost-push-plugin/android/src/main/res/**` | REFACTOR-3 |
| `../mattermost-push-plugin/android/build.gradle` | REFACTOR-3 |

## 최종 검증

```bash
cd /config/workspace/mattermost-push-plugin && dart format --output=none --set-exit-if-changed lib test
cd /config/workspace/mattermost-push-plugin && flutter test
cd /config/workspace/mattermost-push-plugin/example && flutter build apk --debug
```

Expected: all commands succeed. Cached Flutter test output is acceptable; Android build must be freshly run.

모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
