diff --git a/apps/mobile-shell/app.json b/apps/mobile-shell/app.json index 5f5d54e6e..3774817df 100644 --- a/apps/mobile-shell/app.json +++ b/apps/mobile-shell/app.json @@ -52,7 +52,10 @@ "allowBackup": false, "softwareKeyboardLayoutMode": "resize", "blockedPermissions": [ - "android.permission.RECORD_AUDIO" + "android.permission.RECORD_AUDIO", + "android.permission.RECEIVE_BOOT_COMPLETED", + "android.permission.SCHEDULE_EXACT_ALARM", + "android.permission.USE_EXACT_ALARM" ], "adaptiveIcon": { "foregroundImage": "./assets/icon.png", diff --git a/apps/mobile-shell/scripts/check-config.mjs b/apps/mobile-shell/scripts/check-config.mjs index d6e7fe30f..9061999be 100644 --- a/apps/mobile-shell/scripts/check-config.mjs +++ b/apps/mobile-shell/scripts/check-config.mjs @@ -38,6 +38,12 @@ const devScaffoldTerms = [ '模' + '拟', '伪' + '造', ]; +const blockedAndroidPermissions = [ + 'android.permission.RECORD_AUDIO', + 'android.permission.RECEIVE_BOOT_COMPLETED', + 'android.permission.SCHEDULE_EXACT_ALARM', + 'android.permission.USE_EXACT_ALARM', +]; function extractStringArrayExport(source, exportName) { const match = source.match( @@ -267,18 +273,16 @@ if (appConfig.android?.softwareKeyboardLayoutMode !== 'resize') { throw new Error('mobile shell Android keyboard layout must resize the WebView'); } -if ( - !appConfig.android?.blockedPermissions?.includes( - 'android.permission.RECORD_AUDIO', - ) -) { - throw new Error('mobile shell Android package must block microphone permission'); +for (const permission of blockedAndroidPermissions) { + if (!appConfig.android?.blockedPermissions?.includes(permission)) { + throw new Error(`mobile shell Android package must block ${permission}`); + } } -if ( - appConfig.android?.permissions?.includes('android.permission.RECORD_AUDIO') -) { - throw new Error('mobile shell Android package must not request microphone'); +for (const permission of blockedAndroidPermissions) { + if (appConfig.android?.permissions?.includes(permission)) { + throw new Error(`mobile shell Android package must not request ${permission}`); + } } if ( @@ -398,6 +402,19 @@ if (Array.isArray(notificationsPlugin)) { } } +for (const forbiddenNotificationSnippet of [ + 'getExpoPushTokenAsync', + 'getDevicePushTokenAsync', + 'addPushTokenListener', + 'addNotificationResponseReceivedListener', +]) { + if (bridgeSource.includes(forbiddenNotificationSnippet)) { + throw new Error( + `mobile shell must not register remote or background notification flow: ${forbiddenNotificationSnippet}`, + ); + } +} + for (const snippet of [ 'file.exportText', 'file.importText', diff --git a/apps/mobile-shell/scripts/check-expo-config.mjs b/apps/mobile-shell/scripts/check-expo-config.mjs index 1e235d36d..27c854e83 100644 --- a/apps/mobile-shell/scripts/check-expo-config.mjs +++ b/apps/mobile-shell/scripts/check-expo-config.mjs @@ -130,6 +130,17 @@ assertIncludes( 'android.permission.RECORD_AUDIO', 'Android blocked permissions', ); +for (const permission of [ + 'android.permission.RECEIVE_BOOT_COMPLETED', + 'android.permission.SCHEDULE_EXACT_ALARM', + 'android.permission.USE_EXACT_ALARM', +]) { + assertIncludes( + expoConfig.android?.blockedPermissions, + permission, + 'Android background notification blocked permissions', + ); +} assertEqual( expoConfig.android?.adaptiveIcon?.foregroundImage, './assets/icon.png', diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index e3dceef33..da640d5ca 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -50,6 +50,7 @@ - 2026-06-18 H5 背景音乐接入宿主生命周期:`useBackgroundMusic` 通过 `useHostLifecycleActive()` 消费 `subscribeHostAppLifecycle()` 的归一结果,宿主进入后台、inactive 或桌面窗口失焦时降低音量并暂停音频循环,同时 `suspend` WebAudio context;回到 `active + focused` 且用户原本开启音乐时再恢复播放,不改变用户音量设置。 - 2026-06-18 固定玩法音频接入宿主生命周期:前端新增 `useHostLifecycleActive()` 统一消费 `subscribeHostAppLifecycle()`,`useBackgroundMusic`、拼图运行态和抓大鹅运行态都只依赖该归一状态判断音频可播放性;宿主 inactive、background 或窗口失焦时暂停 `