Skip to content

依 OPass Push Gateway v1 契約整合 FCM topic 推播 #128

Description

@denny0223

OPass 已採納中央 Push Gateway v1 與 Firebase Cloud Messaging(FCM)topic 推播契約。本 issue 追蹤 CCIP-Android 的實作範圍與驗收;未勾選的項目仍待完成並附上對應 revision 的驗證結果。架構採納、實作完成與目標環境驗收分別記錄;部署、雲端設定與真實派送須另行取得授權。

實作與驗收狀態(2026-09-21)

已合入 dev:23416de。保留 純契約與狀態轉換、FCM 整合 及 登入頁 layout 修正 的獨立提交;沒有合入發行用 master。

  • 基本通知、各活動訂閱、身分 revision 保護、可恢復 pending 狀態與點擊導頁均已實作。使用 Messaging 25.1.3 與既有 Analytics 23.2.0;App 與系統語系透過 LocaleManagerCompat 取得,不依賴 Activity 已建立。
  • 本次 push 的 CI Build 已通過 assembleDebug。同一來源 tree 的本機 testDebugUnitTest lintDebug lintRelease assembleDebug assembleRelease 通過:6 個 JVM tests、lint 0 errors;Release 產物未簽署,尚未發布商店版本。
  • 最新來源 tree 為 e8e18ef1e5f61643227c9cc1653c113b11aa5a2e;本機實測 debug APK SHA-256 為 a8b0e906450b3a722e9659789ad978f5a61eb20acfd0e3beb2e77b1487e43699。已驗證同簽章原地更新、兩活動英文/繁中/系統預設切換,以及無 Activity 的冷啟動訂閱收斂。先前一次冷啟動等待曾逾時,原因未定位;最終診斷重測通過,不能據此保證固定收斂時間。
  • 先前受控實機紀錄覆蓋通知前景/背景/未執行、跨活動與 HTTPS 點擊、角色替換、晚到登入回應、離線、失效回應 fixture、SDK 成功後落盤前中斷、舊版原地升級及振動。這些對應較早 APK,不把它們改標為本次雙語版本的完整實機 E2E;下方未勾選矩陣保留補驗。
  • 統計選擇、delivery export 與成效驗收維持暫緩;系統備份還原及 OS reboot 亦尚未執行。本 issue 保持開啟,iOS 不在本輪開發或驗收範圍。

架構摘要

CCIP-Admin-Bueno -> OPass Push Gateway -> FCM topic -> CCIP-Android / CCIP-iOS
  • Gateway 由 OPass 團隊維運;活動由驗證成功的 Gateway key 決定,呼叫端不能指定 EVENT_ID 或完整 topic。
  • CCIP-Server 不取得 Gateway key,也不參與推播發送。
  • 推播內容一定是公開資訊;不建立 device registry,也不逐一向裝置發送。
  • App 只有在活動登入成功後才訂閱;每個已登入活動各保留一個角色/推播語系 topic,切換目前活動不會取消其他活動的訂閱。

實作依據

  • 已採納的共同契約:ADR 0001、OpenAPI。
  • 內容保存與部署約束:ADR 0002;Gateway 在中央 D1 保存公開推播內容與已知派送結果,活動 key 對應仍由中央 secret 管理。運算、內容保存及原生成效交付均不得要求綁定有效付款方式。
  • 跨專案驗收與環境輸入:測試與發布驗收;中央設定及 CSV 交付:操作文件。
  • 原始程式碼檢視基準:36ef95b17cfcc2ad6ca192b891045298cb5e4b4b;本次功能分支起點為 4638791e4440d90ff9de3b354cee0c31aef3d1a6。

若實作需要改變 topic、payload 或跨 repository 責任,應先更新 Gateway 契約,再調整本 issue。

修改前基準與風險

  • CCIPApplication 在 App 啟動時初始化 OneSignal,並註冊 NotificationClickListener。
  • TokenCheckFragment 只有在 /status 驗證成功後才儲存 token、role 並加入 <EVENT_ID><ROLE> tag,符合「登入成功後才訂閱」的產品規則。
  • 成功登入新身分時不會移除舊 OneSignal tag。過去向「全體」發送時,OneSignal 可能將多個符合條件的 tag 合併成同一位收件者;改成每個 FCM role topic 各送一則後,殘留訂閱可能造成重複通知。
  • token 與 role 已依活動分開儲存,符合每個已登入活動各自訂閱的需求。FCM topic 狀態也必須依 EVENT_ID 儲存;同一活動重新登入不同身分時,只替換該活動的舊 topic。
  • App 透過 AppCompat 的 per-app locale 切換語言;LocaleUtil 可取得套用後的實際 locale。
  • App 的 AndroidManifest 尚未直接宣告 POST_NOTIFICATIONS;FCM SDK 本身會透過 manifest merge 提供此權限。
  • FastPassFragment.updateStatus 的部分失敗路徑會清除 token/role,403 則是活動 Wi-Fi 限制;不能只接成功登入,或把所有服務錯誤當成登入失效。
  • AndroidManifest 目前 allowBackup=true;本安裝的 topic 套用狀態與待完成轉換須排除備份還原。
  • 現有 session_bookmark channel 是議程提醒,不應拿來顯示活動公告推播。

需求與限制

  • 只支援採用新契約的 App 版本,不做 OneSignal 雙送或舊版推播相容;既有安裝升級仍須保留登入並重新驗證、建立 FCM 訂閱。
  • 使用者只有成功登入後才訂閱 topic;通知權限不影響訂閱資格。
  • Android 目前沒有登出功能。再次成功登入同一活動的不同身分時,新身分只取代該活動的舊身分。
  • 每個 App 安裝實例可同時訂閱多個活動,但同一個 EVENT_ID 至多保留一個 OPass topic。
  • topic 格式為 opass-v1.<EVENT_ID>.<ROLE>.<PUSH_LOCALE>,不建立 .all topic。
  • App 不儲存 Gateway key、Firebase service-account credential,也不把 FID 或 registration token 上傳到 Gateway。

1. SDK 與既有 OneSignal 清理

  • 以現有 version catalog 加入 Firebase Messaging,選擇支援 FID registration(25.1.0 起)的穩定相容版本,核對與既有 Analytics 的依賴相容性並提交實際使用的版本。沿用既有 google-services.json、Google Services plugin 與 Firebase Analytics;API 及版本選擇以官方 release notes 和建置結果為準。
  • 移除 OneSignal dependency、版本宣告與 import。
  • 移除 CCIPApplication 的 OneSignal 初始化與 click listener 註冊。
  • 移除 AndroidManifest 的 OneSignal metadata。
  • 刪除 OneSignal 專用的 NotificationClickListener。
  • 移除 OneSignal 後,確認合併後的 AndroidManifest 仍包含 POST_NOTIFICATIONS,並驗證既有通知權限請求流程正常;不要求 App 重複宣告 SDK 已提供的權限。
  • 在 AndroidManifest.xml 設定 firebase_messaging_installation_id_enabled=true,採用 Firebase Messaging 目前的 FID registration 流程,不沿用已棄用的 registration token API。
  • 確認 Firebase Messaging 與 Analytics 都連到現有 opass-8b7db 專案,不建立第二套 Firebase 設定。

2. Topic 推導與訂閱同步

  • 建立單一、小型的 topic manager,集中處理 topic 推導、訂閱與取消訂閱;不建立自製 queue 或 device registry。
  • 驗證 EVENT_ID 與 ROLE 均符合 [A-Za-z0-9_-]{1,64},並拒絕角色 all。
  • 保留既有 App 介面翻譯與語言選項;推播使用英文 en 與正體中文 zh-Hant,依下列規則對應 App locale:
    • 中文 zh 及其延伸標籤 → zh-Hant;正體與簡體介面共用正體中文推播內容
    • nan-Hant-*、nan-Latn-* → zh-Hant
    • x-default → 先解析目前系統 locale,再套用相同規則
    • 其他語言 → en
  • 依活動分開保存已驗證的登入/角色與本安裝的 topic 套用狀態、待完成轉換;不得只保存單一 topic。訂閱套用狀態排除雲端備份與裝置轉移還原,新安裝不能沿用「已訂閱」標記。
  • 根據所有已驗證登入活動的角色推導目標 topic 集合;token/role 從另一裝置或備份帶入時,先向對應活動驗證成功並確認活動 ID,不能直接訂閱。僅在本安裝已確認套用且沒有待完成轉換時,才可略過相同目標。
  • 驗證請求綁定發起時的 EVENT_ID 與登入身分版本;更新 token/role、清除憑證或觸發同步前,確認回應仍對應該活動的目前身分。過期的成功或失效回應均忽略;狀態寫入使用請求所屬活動,不以回應時 getCurrentEvent() 的結果決定。檢查與更新須連續完成,不能只序列化 SDK 訂閱操作。
  • 序列化同一活動的 SDK 操作;呼叫 SDK 前先持久化待完成轉換,取消舊 topic 後才訂閱新 topic,成功後更新已套用狀態。
  • App 中斷、重新啟動或目標再次變更時,能清理可能已訂閱但尚未記錄成功的目標 topic,再收斂到當前目標;使用本機待完成狀態與 Firebase Messaging SDK 的既有重試,不新增自製 queue。

至少在下列時機同步訂閱:

  • TokenCheckFragment 驗證身分成功並儲存新 token 與 role 後。
  • /status 成功回傳的角色更新,以及 FastPassFragment 等路徑明確判定登入失效、清除 token/role 後。先區分活動 Wi-Fi 限制、暫時性伺服器錯誤與真正失效;離線或暫時失敗保留既有已驗證狀態,不清空其他活動。
  • App 啟動並讀取登入狀態後,核對所有已登入活動;切換目前活動本身不得取消其他活動的 topic。
  • App 語系變更後,更新所有已登入活動的 topic。
  • Firebase Messaging registration/FID 完成或更新時。

3. 通知顯示、權限與點擊

  • 在 App 啟動時建立固定 ID 為 announcements、IMPORTANCE_DEFAULT 且使用預設提示音與振動的公開活動推播 NotificationChannel;宣告 VIBRATE,前景請求預設振動,保留既有頻道的使用者設定。
  • 在 AndroidManifest 將 announcements 設為 FCM default notification channel,並指定符合 Android 規範的 default notification icon,確保背景通知不落入其他 channel。
  • 增加 FirebaseMessagingService 並在 AndroidManifest 宣告 com.google.firebase.MESSAGING_EVENT intent filter,讓 App 在前景時也能顯示 notification message。
  • 在 FirebaseMessagingService.onRegistered(installationId: String) 收到 FID registration 完成或更新時觸發 topic 同步;不得把 FID 上傳到 Gateway 或寫入 log。
  • App 在背景或未執行時使用 FCM notification message 的系統顯示路徑,不改成依賴背景執行的 data-only notification。
  • 使用 FCM normal priority 與 Gateway 的主辦名稱標題,不由推播 payload 管理或累加 badge;不以本機計時器撤除一小時前已顯示的通知。
  • LauncherActivity 讀取背景通知點擊帶入的 data;前景通知建立的 PendingIntent 也走同一條路徑,不維護兩套解析邏輯。
  • 只有 payload 同時包含 push_id 與 event_id 時才視為 Gateway 推播通知。uri 為 HTTPS 時開啟該 URI;沒有 uri 時切換至 event_id 對應的活動並進入公告頁。
  • 將登入成功後的 OneSignal 權限請求改成既有程式已採用的 ActivityResultContracts.RequestPermission。
  • 使用者拒絕通知權限時仍維持正確 topic 訂閱;保留登入提示與議程提醒既有的個別互動,不把兩者混成同一個設定。

4. Analytics 與驗證

統計蒐集的預設、詢問時機、撤回方式及與既有 Analytics 設定的關係暫緩定案,先完成不依賴此選擇的功能。Analytics/delivery export 的使用者選擇流程及依該政策進行的成效驗收,待產品決策定案後完成;不得從 SDK 或舊程式的預設推定使用者同意。這項前置決策不阻擋 Gateway、Admin、基本通知、訂閱與導頁的獨立開發;拒絕或撤回統計不得影響通知使用。

  • 確認 Gateway 將 push_id 用作 FCM Analytics label;Android 只將它用於通知點擊辨識,不另造識別碼。
  • 確認背景 notification message 的送達、顯示與開啟事件會進入 Firebase reporting;配合中央完成 BigQuery 匯出設定並啟用必要的 delivery metrics,遵守隱私告知與使用者選擇。前景自行顯示的通知若沒有官方對應統計,不自製看似相同的指標。
  • 與中央團隊確認按 push_id 可取得的送達及通知點擊數、計數單位與資料限制;缺漏不作零值,不當成不重複人數或後續轉化。CSV 由中央團隊交付,App 不新增報表或上傳裝置名冊。
  • 為語系對應、topic 建構與訂閱同步的狀態轉換加入最小單元測試。
  • 加入最小必要的 unit test 設定;自動化測試不得訂閱正式 topic 或送出真實通知。
  • 執行 ./gradlew testDebugUnitTest lintDebug assembleDebug,並記錄無法在本機執行的驗證。

人工驗收矩陣(未勾選表示尚未以本次最終來源完成該整組案例;已有的局部及較早版本結果另列,不等同未實作):

  • 最新 debug APK 以相同 application ID、簽章與 Firebase client 保留資料更新;登入、身分 revision、UID、首次安裝時間與 registration 保留。這是既有 FCM 安裝的更新,不能取代下列 OneSignal 舊版遷移驗收。
  • 最新 APK 的實際 App 英文選擇 → 系統 per-app 繁中 → App 跟隨系統,兩個已登入活動的 topic 均正確更新;測試後恢復原設定。
  • 最新 APK 無 Activity 的冷啟動:App 繁中、系統英文,兩活動 target/applied 皆正確、pending 清空。最終診斷重測通過;先前逾時原因未定位,保留追蹤。
  • 未登入、首次登入、相同身分再次登入。
  • 同活動切換角色只保留新角色;切換活動或切換回曾登入活動時,其他已登入活動仍可收訊。
  • 正體與簡體中文介面皆訂閱 zh-Hant;驗證英文 fallback 與 x-default。最新實機已驗證英文、正體與系統預設;簡體介面對應 zh-Hant 已有單元測試,仍待此版本實機補驗。
  • 切換語言後更新所有活動;同活動切換身分後不會再收到該活動舊角色 topic 的推播。
  • 離線/暫時驗證失敗不誤登出;明確登入失效只移除該活動。先前已有斷網與 400 invalid token fixture 驗證;真正 Server 撤銷有效憑證尚未實測。
  • 新身分登入後才收到舊身分的驗證成功或失效回應,不得覆蓋角色、清除新 token 或切回舊 topic;切換活動後才抵達的回應,不得改寫目前開啟的另一活動。
  • SDK 訂閱成功、本機狀態寫入前中斷,重新啟動並再換角色後仍會清掉殘留 topic。
  • 備份還原或新裝置帶入 token 時,驗證成功前不新增訂閱;驗證後不因舊套用標記而漏訂閱。純狀態測試已覆蓋資格規則,系統備份/還原實測暫緩,不能互相取代。
  • App 在前景、背景與未執行時均符合預期。
  • 收到非目前活動且沒有 uri 的通知時,點擊後開啟 event_id 對應活動的公告頁。
  • 有 uri、無 uri、拒絕通知權限三種路徑。
  • Firebase Console 的 Android Sends、Received、Impressions 與 Opens 符合文件所述限制。此項待統計政策定案後驗收,不以 SDK 或既有 Analytics 預設推定同意。
  • 同一裝置從 OneSignal 舊版本直接升級後,可為所有已登入活動建立正確的 FCM topic 訂閱;另驗證已有快取 registration 的重啟、FID 更新及語系變更,不只測乾淨安裝。先前版本有保留資料遷移紀錄,本輪最終版尚未重跑整組;實際 FID 更換、OS reboot 尚未驗收。不要求 OneSignal 舊版繼續收訊。

其他待補裝置覆蓋:API 32 以下的已儲存 App 語系冷啟動,以及 Android 7 前景/背景提示音與振動。

不在本 issue 範圍

  • device registry 或 registration token 上傳 API。
  • App 內的 Gateway key、service-account credential 或發布功能。
  • .all topic、逐裝置發送、OneSignal 相容層。
  • 為這次遷移新增登出或逐活動靜音 UI。
  • UnifiedPush、補送流程、App 內報表或後續轉化追蹤。

參考資料

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions