<!-- task=mattermost_push_plugin_extraction/02+01_android_native plan=1 tag=REVIEW_REFACTOR -->

# Android Plugin Lifecycle Detach Follow-up Plan

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

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

## 배경

이전 구현은 Android native push source를 plugin repo로 옮기고 원격 Android debug build까지 통과했다. 다만 `MattermostPushPlugin`이 `AppLifecycleTracker`를 application lifecycle callback으로 등록한 뒤 detach 시 unregister하지 않는다. Plugin teardown 경계가 닫히지 않으면 engine 재생성 또는 test host 재attach에서 callback 소유권이 누적될 수 있다.

## 분석 결과

### 읽은 파일

- `agent-task/mattermost_push_plugin_extraction/02+01_android_native/plan_cloud_G08_0.log`
- `agent-task/mattermost_push_plugin_extraction/02+01_android_native/code_review_cloud_G08_0.log`
- `agent-ops/rules/project/domain/mobile/rules.md`
- `agent-ops/roadmap/milestones/mattermost-push-plugin-extraction.md`
- `../mattermost-push-plugin/android/src/main/kotlin/com/tokilabs/mattermost_push_plugin/MattermostPushPlugin.kt`
- `../mattermost-push-plugin/android/src/main/kotlin/com/tokilabs/mattermost_push_plugin/AppLifecycleTracker.kt`
- `../mattermost-push-plugin/android/src/main/kotlin/com/tokilabs/mattermost_push_plugin/MattermostFirebaseMessagingService.kt`
- `../mattermost-push-plugin/android/src/main/AndroidManifest.xml`
- `../mattermost-push-plugin/android/build.gradle.kts`
- `../mattermost-push-plugin/lib/src/mattermost_push_plugin.dart`
- `../mattermost-push-plugin/test/mattermost_push_plugin_test.dart`

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

- Activity lifecycle callback unregister는 Android plugin lifecycle 경계라 기존 Dart tests가 직접 검증하지 않는다.
- Android instrumentation/unit harness는 아직 없으므로 remote `flutter build apk --debug`로 compile/manifest merge를 검증한다.

### 심볼 참조

- `AppLifecycleTracker`: `MattermostPushPlugin.kt:42`, `MattermostFirebaseMessagingService.kt:205`, `AppLifecycleTracker.kt:7`.
- `registerActivityLifecycleCallbacks`: `MattermostPushPlugin.kt:42`.
- `unregisterActivityLifecycleCallbacks`: 현재 plugin source에 없음.

### 분할 판단

- Split decision policy was evaluated before choosing plan files.
- 이 후속 작업은 기존 split subtask `02+01_android_native`의 Required 1건만 수정한다.
- 새 subtask로 분리하지 않는다. 변경 파일이 plugin lifecycle 파일 1개에 한정되고, 검증도 기존 Android native plugin 검증 흐름을 재사용한다.

### 범위 결정 근거

- FCM service, manifest, resource, Room/Network helper 동작은 이 후속 범위에서 변경하지 않는다.
- `apps/mobile` host cleanup은 sibling `03+01,02_host_consumer` 범위로 유지한다.
- Firebase Messaging 기본 component removal 정책은 이전 구현 범위 그대로 두고, 이번 작업은 lifecycle callback 소유권만 닫는다.

### 빌드 등급

- Build lane: `cloud-G08`; review lane: `cloud-G08`. Android plugin lifecycle과 remote Android build 검증이 필요하고, 이전 리뷰의 Required follow-up이다.

## 구현 체크리스트

- [ ] [REVIEW_REFACTOR-1] `AppLifecycleTracker` application callback 등록과 해제를 대칭으로 만든다.
- [ ] 중간 검증과 최종 검증 명령을 모두 실행하고 실제 출력을 `CODE_REVIEW-cloud-G08.md`에 붙여 넣는다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 의존 관계 및 구현 순서

- Directory dependency remains `02+01_android_native` depends on `01_plugin_api`.
- This is a review follow-up inside `02+01_android_native`; do not edit sibling active tasks.

### [REVIEW_REFACTOR-1] Lifecycle Callback Unregister

#### 문제

`MattermostPushPlugin.kt:42`는 `Application.registerActivityLifecycleCallbacks(AppLifecycleTracker)`를 호출한다. 그러나 `onDetachedFromEngine`(`MattermostPushPlugin.kt:55`)은 channel과 event state만 정리하고 lifecycle callback을 해제하지 않는다.

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

```kotlin
if (context is Application) {
    context.registerActivityLifecycleCallbacks(AppLifecycleTracker)
}
```

After:

```kotlin
private var lifecycleApplication: Application? = null

private fun registerLifecycleCallbacks(context: Context) {
    val application = context.applicationContext as? Application ?: context as? Application ?: return
    if (lifecycleApplication === application) return
    lifecycleApplication?.unregisterActivityLifecycleCallbacks(AppLifecycleTracker)
    application.registerActivityLifecycleCallbacks(AppLifecycleTracker)
    lifecycleApplication = application
}

private fun unregisterLifecycleCallbacks() {
    lifecycleApplication?.unregisterActivityLifecycleCallbacks(AppLifecycleTracker)
    lifecycleApplication = null
}
```

#### 해결 방법

Store the `Application` used for registration in `MattermostPushPlugin`. Register through a helper that guards duplicate registration, and call the unregister helper from `onDetachedFromEngine` before clearing plugin state. Keep `AppLifecycleTracker` itself unchanged.

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

- [ ] `../mattermost-push-plugin/android/src/main/kotlin/com/tokilabs/mattermost_push_plugin/MattermostPushPlugin.kt`에 `Application` registration owner field 추가
- [ ] `onAttachedToEngine`에서 helper를 통해 lifecycle callback 등록
- [ ] `onDetachedFromEngine`에서 `unregisterActivityLifecycleCallbacks(AppLifecycleTracker)` 호출 후 field clear
- [ ] 기존 MethodChannel/EventChannel 이름과 method argument 유지

#### 테스트 작성

새 Android lifecycle unit test는 작성하지 않는다. 현재 plugin Android test harness가 application lifecycle callback registration을 검증하지 못하고, 이번 수정은 compile-detectable API 사용과 remote Android build로 확인한다.

#### 중간 검증

```bash
rg --sort path -n "registerActivityLifecycleCallbacks|unregisterActivityLifecycleCallbacks|lifecycleApplication" /config/workspace/mattermost-push-plugin/android/src/main/kotlin/com/tokilabs/mattermost_push_plugin/MattermostPushPlugin.kt
```

Expected: registration owner field, register call, unregister call이 모두 같은 파일에 나타난다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `../mattermost-push-plugin/android/src/main/kotlin/com/tokilabs/mattermost_push_plugin/MattermostPushPlugin.kt` | REVIEW_REFACTOR-1 |

## 최종 검증

```bash
REMOTE_DIR=/tmp/mattermost-push-plugin-verify-$(date +%s)
ssh -o BatchMode=yes -o ConnectTimeout=10 toki@toki-labs.com "mkdir -p '$REMOTE_DIR'"
tar -C /config/workspace/mattermost-push-plugin --exclude=.git --exclude=build --exclude=.dart_tool --exclude=example/build --exclude=example/.dart_tool --exclude=android/build --exclude=example/android/.gradle -czf - . | ssh -o BatchMode=yes -o ConnectTimeout=10 toki@toki-labs.com "tar -xzf - -C '$REMOTE_DIR'"
ssh -o BatchMode=yes -o ConnectTimeout=10 toki@toki-labs.com "export PATH=/Users/toki/docker/services/openvscode/data/volume/home/sdk/flutter/bin:\$PATH; cd '$REMOTE_DIR' && flutter pub get && flutter analyze --no-fatal-infos && dart format --output=none --set-exit-if-changed lib test && flutter test && cd example && flutter pub get && flutter build apk --debug"
```

Expected: all commands succeed. Android build verification must run through `ssh toki@toki-labs.com`; do not replace it with the local container Android SDK result.

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