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 清理
2. Topic 推導與訂閱同步
至少在下列時機同步訂閱:
3. 通知顯示、權限與點擊
4. Analytics 與驗證
統計蒐集的預設、詢問時機、撤回方式及與既有 Analytics 設定的關係暫緩定案,先完成不依賴此選擇的功能。Analytics/delivery export 的使用者選擇流程及依該政策進行的成效驗收,待產品決策定案後完成;不得從 SDK 或舊程式的預設推定使用者同意。這項前置決策不阻擋 Gateway、Admin、基本通知、訂閱與導頁的獨立開發;拒絕或撤回統計不得影響通知使用。
人工驗收矩陣(未勾選表示尚未以本次最終來源完成該整組案例;已有的局部及較早版本結果另列,不等同未實作):
其他待補裝置覆蓋:API 32 以下的已儲存 App 語系冷啟動,以及 Android 7 前景/背景提示音與振動。
不在本 issue 範圍
- device registry 或 registration token 上傳 API。
- App 內的 Gateway key、service-account credential 或發布功能。
.all topic、逐裝置發送、OneSignal 相容層。
- 為這次遷移新增登出或逐活動靜音 UI。
- UnifiedPush、補送流程、App 內報表或後續轉化追蹤。
參考資料
OPass 已採納中央 Push Gateway v1 與 Firebase Cloud Messaging(FCM)topic 推播契約。本 issue 追蹤 CCIP-Android 的實作範圍與驗收;未勾選的項目仍待完成並附上對應 revision 的驗證結果。架構採納、實作完成與目標環境驗收分別記錄;部署、雲端設定與真實派送須另行取得授權。
實作與驗收狀態(2026-09-21)
已合入
dev:23416de。保留 純契約與狀態轉換、FCM 整合 及 登入頁 layout 修正 的獨立提交;沒有合入發行用master。25.1.3與既有 Analytics23.2.0;App 與系統語系透過LocaleManagerCompat取得,不依賴 Activity 已建立。assembleDebug。同一來源 tree 的本機testDebugUnitTest lintDebug lintRelease assembleDebug assembleRelease通過:6 個 JVM tests、lint 0 errors;Release 產物未簽署,尚未發布商店版本。e8e18ef1e5f61643227c9cc1653c113b11aa5a2e;本機實測 debug APK SHA-256 為a8b0e906450b3a722e9659789ad978f5a61eb20acfd0e3beb2e77b1487e43699。已驗證同簽章原地更新、兩活動英文/繁中/系統預設切換,以及無 Activity 的冷啟動訂閱收斂。先前一次冷啟動等待曾逾時,原因未定位;最終診斷重測通過,不能據此保證固定收斂時間。架構摘要
EVENT_ID或完整 topic。實作依據
36ef95b17cfcc2ad6ca192b891045298cb5e4b4b;本次功能分支起點為4638791e4440d90ff9de3b354cee0c31aef3d1a6。若實作需要改變 topic、payload 或跨 repository 責任,應先更新 Gateway 契約,再調整本 issue。
修改前基準與風險
CCIPApplication在 App 啟動時初始化 OneSignal,並註冊NotificationClickListener。TokenCheckFragment只有在/status驗證成功後才儲存 token、role 並加入<EVENT_ID><ROLE>tag,符合「登入成功後才訂閱」的產品規則。EVENT_ID儲存;同一活動重新登入不同身分時,只替換該活動的舊 topic。LocaleUtil可取得套用後的實際 locale。POST_NOTIFICATIONS;FCM SDK 本身會透過 manifest merge 提供此權限。FastPassFragment.updateStatus的部分失敗路徑會清除 token/role,403則是活動 Wi-Fi 限制;不能只接成功登入,或把所有服務錯誤當成登入失效。allowBackup=true;本安裝的 topic 套用狀態與待完成轉換須排除備份還原。session_bookmarkchannel 是議程提醒,不應拿來顯示活動公告推播。需求與限制
EVENT_ID至多保留一個 OPass topic。opass-v1.<EVENT_ID>.<ROLE>.<PUSH_LOCALE>,不建立.alltopic。1. SDK 與既有 OneSignal 清理
25.1.0起)的穩定相容版本,核對與既有 Analytics 的依賴相容性並提交實際使用的版本。沿用既有google-services.json、Google Services plugin 與 Firebase Analytics;API 及版本選擇以官方 release notes 和建置結果為準。CCIPApplication的 OneSignal 初始化與 click listener 註冊。NotificationClickListener。POST_NOTIFICATIONS,並驗證既有通知權限請求流程正常;不要求 App 重複宣告 SDK 已提供的權限。AndroidManifest.xml設定firebase_messaging_installation_id_enabled=true,採用 Firebase Messaging 目前的 FID registration 流程,不沿用已棄用的 registration token API。opass-8b7db專案,不建立第二套 Firebase 設定。2. Topic 推導與訂閱同步
EVENT_ID與ROLE均符合[A-Za-z0-9_-]{1,64},並拒絕角色all。en與正體中文zh-Hant,依下列規則對應 App locale:zh及其延伸標籤 →zh-Hant;正體與簡體介面共用正體中文推播內容nan-Hant-*、nan-Latn-*→zh-Hantx-default→ 先解析目前系統 locale,再套用相同規則enEVENT_ID與登入身分版本;更新 token/role、清除憑證或觸發同步前,確認回應仍對應該活動的目前身分。過期的成功或失效回應均忽略;狀態寫入使用請求所屬活動,不以回應時getCurrentEvent()的結果決定。檢查與更新須連續完成,不能只序列化 SDK 訂閱操作。至少在下列時機同步訂閱:
TokenCheckFragment驗證身分成功並儲存新 token 與 role 後。/status成功回傳的角色更新,以及FastPassFragment等路徑明確判定登入失效、清除 token/role 後。先區分活動 Wi-Fi 限制、暫時性伺服器錯誤與真正失效;離線或暫時失敗保留既有已驗證狀態,不清空其他活動。3. 通知顯示、權限與點擊
announcements、IMPORTANCE_DEFAULT且使用預設提示音與振動的公開活動推播NotificationChannel;宣告VIBRATE,前景請求預設振動,保留既有頻道的使用者設定。announcements設為 FCM default notification channel,並指定符合 Android 規範的 default notification icon,確保背景通知不落入其他 channel。FirebaseMessagingService並在 AndroidManifest 宣告com.google.firebase.MESSAGING_EVENTintent filter,讓 App 在前景時也能顯示 notification message。FirebaseMessagingService.onRegistered(installationId: String)收到 FID registration 完成或更新時觸發 topic 同步;不得把 FID 上傳到 Gateway 或寫入 log。LauncherActivity讀取背景通知點擊帶入的 data;前景通知建立的PendingIntent也走同一條路徑,不維護兩套解析邏輯。push_id與event_id時才視為 Gateway 推播通知。uri為 HTTPS 時開啟該 URI;沒有uri時切換至event_id對應的活動並進入公告頁。ActivityResultContracts.RequestPermission。4. Analytics 與驗證
統計蒐集的預設、詢問時機、撤回方式及與既有 Analytics 設定的關係暫緩定案,先完成不依賴此選擇的功能。Analytics/delivery export 的使用者選擇流程及依該政策進行的成效驗收,待產品決策定案後完成;不得從 SDK 或舊程式的預設推定使用者同意。這項前置決策不阻擋 Gateway、Admin、基本通知、訂閱與導頁的獨立開發;拒絕或撤回統計不得影響通知使用。
push_id用作 FCM Analytics label;Android 只將它用於通知點擊辨識,不另造識別碼。push_id可取得的送達及通知點擊數、計數單位與資料限制;缺漏不作零值,不當成不重複人數或後續轉化。CSV 由中央團隊交付,App 不新增報表或上傳裝置名冊。./gradlew testDebugUnitTest lintDebug assembleDebug,並記錄無法在本機執行的驗證。人工驗收矩陣(未勾選表示尚未以本次最終來源完成該整組案例;已有的局部及較早版本結果另列,不等同未實作):
zh-Hant;驗證英文 fallback 與x-default。最新實機已驗證英文、正體與系統預設;簡體介面對應zh-Hant已有單元測試,仍待此版本實機補驗。400 invalid tokenfixture 驗證;真正 Server 撤銷有效憑證尚未實測。uri的通知時,點擊後開啟event_id對應活動的公告頁。uri、無uri、拒絕通知權限三種路徑。其他待補裝置覆蓋:API 32 以下的已儲存 App 語系冷啟動,以及 Android 7 前景/背景提示音與振動。
不在本 issue 範圍
.alltopic、逐裝置發送、OneSignal 相容層。參考資料