feat(background): implement user consent mechanism for auto-start functionality
Add auto-start consent system that requires explicit user permission before enabling background sync services. Users can now choose between silent mode (no auto-start) or background mode (15-minute sync intervals). - Add consent service with AutoStartMode enum and storage utilities - Create withRemoveAutoStart Expo plugin to disable Android auto-start defaults - Integrate consent checks into JPush service, background task manager, and foreground service - Add auto-start consent UI in notification settings with descriptive dialog - Update privacy policy with section 8 explaining auto-start scenarios and user controls - Change default sync mode from BATTERY_SAVER to DISABLED - Export reinitBackgroundService function for re-initializing after consent changes - Merge OTA Android and iOS workflows into matrix build - Add release signing config in withSigning plugin for Android - Expand .dockerignore with native credential and build artifact exclusions
This commit is contained in:
@@ -29,6 +29,12 @@ import {
|
||||
} from '@/services/notification';
|
||||
import { useResponsive, useResponsiveSpacing } from '../../hooks';
|
||||
import { backgroundSyncManager, BackgroundSyncMode } from '@/services/background';
|
||||
import {
|
||||
loadAutoStartConsent,
|
||||
consentToAutoStart,
|
||||
rejectAutoStart,
|
||||
getAutoStartDescription,
|
||||
} from '@/services/consent';
|
||||
|
||||
// 内容最大宽度
|
||||
const CONTENT_MAX_WIDTH = 720;
|
||||
@@ -49,8 +55,9 @@ export const NotificationSettingsScreen: React.FC = () => {
|
||||
const [vibrationEnabled, setVibrationEnabledState] = useState(true);
|
||||
const [pushEnabled, setPushEnabled] = useState(true);
|
||||
const [soundEnabled, setSoundEnabled] = useState(true);
|
||||
const [syncMode, setSyncMode] = useState<BackgroundSyncMode>(BackgroundSyncMode.BATTERY_SAVER);
|
||||
const [syncMode, setSyncMode] = useState<BackgroundSyncMode>(BackgroundSyncMode.DISABLED);
|
||||
const [systemPushEnabled, setSystemPushEnabled] = useState<boolean | null>(null);
|
||||
const [autoStartConsented, setAutoStartConsented] = useState(false);
|
||||
const { isWideScreen, isMobile } = useResponsive();
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
@@ -67,6 +74,10 @@ export const NotificationSettingsScreen: React.FC = () => {
|
||||
setSoundEnabled(prefs.soundEnabled);
|
||||
setSyncMode(backgroundSyncManager.getMode());
|
||||
|
||||
// 加载自启动同意状态
|
||||
const consent = await loadAutoStartConsent();
|
||||
setAutoStartConsented(consent.consented);
|
||||
|
||||
if (Platform.OS !== 'web') {
|
||||
const enabled = await jpushService.checkNotificationPermission();
|
||||
setSystemPushEnabled(enabled);
|
||||
@@ -106,6 +117,41 @@ export const NotificationSettingsScreen: React.FC = () => {
|
||||
};
|
||||
|
||||
const handleSyncModeChange = async (mode: BackgroundSyncMode) => {
|
||||
// 如果要切换到需要自启动的模式,先请求用户同意
|
||||
if (mode !== BackgroundSyncMode.DISABLED && !autoStartConsented) {
|
||||
Alert.alert(
|
||||
'后台消息接收',
|
||||
getAutoStartDescription() + '\n\n开启后,应用可在后台接收消息推送。',
|
||||
[
|
||||
{ text: '取消', style: 'cancel' },
|
||||
{
|
||||
text: '同意并开启',
|
||||
onPress: async () => {
|
||||
try {
|
||||
await consentToAutoStart('接收实时消息推送');
|
||||
setAutoStartConsented(true);
|
||||
// 用户同意后,继续切换模式
|
||||
await doSetSyncMode(mode);
|
||||
} catch (error) {
|
||||
console.error('同意自启动失败:', error);
|
||||
}
|
||||
},
|
||||
},
|
||||
]
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果切换到禁用模式,撤销自启动同意
|
||||
if (mode === BackgroundSyncMode.DISABLED && autoStartConsented) {
|
||||
await rejectAutoStart();
|
||||
setAutoStartConsented(false);
|
||||
}
|
||||
|
||||
await doSetSyncMode(mode);
|
||||
};
|
||||
|
||||
const doSetSyncMode = async (mode: BackgroundSyncMode) => {
|
||||
if (mode === BackgroundSyncMode.REALTIME) {
|
||||
Alert.alert(
|
||||
'实时模式',
|
||||
@@ -129,25 +175,56 @@ export const NotificationSettingsScreen: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleAutoStartModeChange = async (mode: AutoStartMode) => {
|
||||
if (mode === AutoStartMode.BACKGROUND) {
|
||||
Alert.alert(
|
||||
'后台消息接收',
|
||||
getAutoStartDescription() + '\n\n开启后,应用可在后台接收消息推送。',
|
||||
[
|
||||
{ text: '取消', style: 'cancel' },
|
||||
{
|
||||
text: '同意并开启',
|
||||
onPress: async () => {
|
||||
await setAutoStartMode(AutoStartMode.BACKGROUND);
|
||||
setAutoStartModeState(AutoStartMode.BACKGROUND);
|
||||
setAutoStartConsented(true);
|
||||
// 重新初始化后台服务以应用新设置
|
||||
const { reinitBackgroundService } = await import('@/services/background');
|
||||
await reinitBackgroundService();
|
||||
Alert.alert('已开启', '后台消息接收已开启。您可随时在设置中关闭。');
|
||||
},
|
||||
},
|
||||
]
|
||||
);
|
||||
} else {
|
||||
await setAutoStartMode(AutoStartMode.SILENT);
|
||||
setAutoStartModeState(AutoStartMode.SILENT);
|
||||
setAutoStartConsented(false);
|
||||
// 重新初始化后台服务以应用新设置
|
||||
const { reinitBackgroundService } = await import('@/services/background');
|
||||
await reinitBackgroundService();
|
||||
}
|
||||
};
|
||||
|
||||
const syncModeOptions: { mode: BackgroundSyncMode; title: string; subtitle: string; icon: string }[] = [
|
||||
{
|
||||
mode: BackgroundSyncMode.DISABLED,
|
||||
title: '静默模式',
|
||||
subtitle: '不自启动,仅在使用时接收消息',
|
||||
icon: 'bell-off-outline',
|
||||
},
|
||||
{
|
||||
mode: BackgroundSyncMode.BATTERY_SAVER,
|
||||
title: '省电模式',
|
||||
subtitle: '系统后台任务,每 15 分钟检查一次',
|
||||
title: '后台模式',
|
||||
subtitle: '允许自启动,每 15 分钟检查一次',
|
||||
icon: 'leaf',
|
||||
},
|
||||
{
|
||||
mode: BackgroundSyncMode.REALTIME,
|
||||
title: '实时模式',
|
||||
subtitle: '通知栏常驻保活,即时同步消息',
|
||||
subtitle: '允许自启动,通知栏常驻保活',
|
||||
icon: 'lightning-bolt',
|
||||
},
|
||||
{
|
||||
mode: BackgroundSyncMode.DISABLED,
|
||||
title: '禁用',
|
||||
subtitle: '仅在应用打开时接收消息',
|
||||
icon: 'close-circle-outline',
|
||||
},
|
||||
];
|
||||
|
||||
const handleRequestSystemPermission = async () => {
|
||||
@@ -304,12 +381,30 @@ export const NotificationSettingsScreen: React.FC = () => {
|
||||
))}
|
||||
</View>
|
||||
|
||||
{/* 实时模式说明 */}
|
||||
{/* 模式说明 */}
|
||||
{syncMode === BackgroundSyncMode.DISABLED && (
|
||||
<View style={styles.tipContainer}>
|
||||
<MaterialCommunityIcons name="information-outline" size={16} color={colors.text.hint} />
|
||||
<Text variant="caption" color={colors.text.hint} style={styles.tipText}>
|
||||
静默模式下,应用不会自启动。您仅在打开应用时接收消息。此模式最省电,但可能错过实时消息。
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{syncMode === BackgroundSyncMode.BATTERY_SAVER && (
|
||||
<View style={styles.tipContainer}>
|
||||
<MaterialCommunityIcons name="information-outline" size={16} color={colors.text.hint} />
|
||||
<Text variant="caption" color={colors.text.hint} style={styles.tipText}>
|
||||
后台模式已开启,应用允许自启动以接收消息推送。系统会每 15 分钟检查一次新消息。
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{syncMode === BackgroundSyncMode.REALTIME && (
|
||||
<View style={styles.tipContainer}>
|
||||
<MaterialCommunityIcons name="shield-check-outline" size={16} color={colors.text.hint} />
|
||||
<Text variant="caption" color={colors.text.hint} style={styles.tipText}>
|
||||
实时模式已开启,应用将在通知栏显示常驻通知以保持后台运行。如需更稳定的后台运行,请在系统设置中将本应用加入电池优化白名单。
|
||||
实时模式已开启,应用允许自启动并在通知栏显示常驻通知以保持后台运行。如需更稳定的后台运行,请在系统设置中将本应用加入电池优化白名单。
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
|
||||
@@ -30,7 +30,7 @@ const THEME_COLORS = {
|
||||
};
|
||||
|
||||
// 政策最后更新日期
|
||||
const LAST_UPDATED = '2026年4月28日';
|
||||
const LAST_UPDATED = '2026年6月15日';
|
||||
|
||||
// 隐私政策内容
|
||||
const PRIVACY_SECTIONS = [
|
||||
@@ -170,18 +170,68 @@ const PRIVACY_SECTIONS = [
|
||||
• 使用目的:辅助消息推送通道管理
|
||||
• 收集的个人信息:设备推送令牌(Push Token)、设备标识符
|
||||
|
||||
3. WorkManager(Android系统组件)
|
||||
• 提供方:Google LLC
|
||||
• 使用目的:在用户同意自启动后,执行后台消息同步任务
|
||||
• 收集的个人信息:无额外个人信息收集
|
||||
• 隐私政策:https://policies.google.com/privacy
|
||||
|
||||
4. expo-task-manager / expo-background-task
|
||||
• 提供方:Expo(650 Industries, Inc.)
|
||||
• 使用目的:在用户同意自启动后,管理后台任务调度
|
||||
• 收集的个人信息:无额外个人信息收集
|
||||
|
||||
5. expo-notifications
|
||||
• 提供方:Expo(650 Industries, Inc.)
|
||||
• 使用目的:在用户同意自启动后,恢复通知服务
|
||||
• 收集的个人信息:设备推送令牌
|
||||
|
||||
6. expo-callkit-telecom
|
||||
• 提供方:Expo(650 Industries, Inc.)
|
||||
• 使用目的:通话功能相关服务
|
||||
• 收集的个人信息:无额外个人信息收集
|
||||
|
||||
如您后续接入其他第三方SDK(如微信登录、分享等功能),我们将在本章节更新相关说明,并告知您对应SDK收集的信息类型和用途。更新后的SDK目录将在应用内公布,请以最新版本为准。`,
|
||||
},
|
||||
{
|
||||
title: '八、隐私政策的更新',
|
||||
content: `8.1 我们可能会不时更新本隐私政策。更新后的隐私政策将在应用内公布,并标注更新日期。
|
||||
title: '八、应用自启动与关联启动说明',
|
||||
content: `8.1 自启动/关联启动的目的与场景
|
||||
为了及时向您推送消息通知,本应用可能需要在以下场景进行自启动或关联启动:
|
||||
|
||||
8.2 对于重大变更,我们会在变更生效前通过应用内通知、弹窗等方式告知您。
|
||||
• 设备开机完成后:恢复后台消息推送服务,确保您能及时收到新消息提醒
|
||||
• 应用更新后:恢复后台任务调度,保证消息同步功能正常运作
|
||||
• 系统重启后:恢复通知服务,确保推送通道可用
|
||||
• 关联启动场景:当系统或其他应用触发相关事件时,为保证推送服务连续性而进行关联启动
|
||||
|
||||
8.3 如您不同意变更后的内容,应立即停止使用本服务。如您继续使用本服务,即视为您已阅读并同意受变更后的隐私政策约束。`,
|
||||
8.2 用户同意机制
|
||||
• 自启动/关联启动功能仅在您明确同意后才启用
|
||||
• 首次使用时,我们会在"通知设置"中向您说明自启动的目的、场景、规则及必要性,并征得您的同意
|
||||
• 您可随时在"设置-通知设置-后台同步模式"中更改选择:
|
||||
- 静默模式:不自启动,仅在使用应用时接收消息
|
||||
- 后台模式:同意自启动,系统每15分钟检查一次新消息
|
||||
- 实时模式:同意自启动,通知栏常驻保活,即时同步消息
|
||||
|
||||
8.3 关闭自启动的影响
|
||||
• 选择静默模式后,应用不会自启动
|
||||
• 您仅在打开应用时才能接收消息
|
||||
• 此模式最省电,但可能错过实时消息
|
||||
• 关闭自启动不会影响应用内的其他功能使用
|
||||
|
||||
8.4 我们承诺
|
||||
• 自启动行为仅用于消息推送服务,不会用于收集额外个人信息
|
||||
• 自启动行为不会用于广告推送或其他商业目的
|
||||
• 我们仅在用户同意的范围内使用自启动功能`,
|
||||
},
|
||||
{
|
||||
title: '九、联系我们',
|
||||
title: '九、隐私政策的更新',
|
||||
content: `9.1 我们可能会不时更新本隐私政策。更新后的隐私政策将在应用内公布,并标注更新日期。
|
||||
|
||||
9.2 对于重大变更,我们会在变更生效前通过应用内通知、弹窗等方式告知您。
|
||||
|
||||
9.3 如您不同意变更后的内容,应立即停止使用本服务。如您继续使用本服务,即视为您已阅读并同意受变更后的隐私政策约束。`,
|
||||
},
|
||||
{
|
||||
title: '十、联系我们',
|
||||
content: `如果您对本隐私政策有任何疑问、意见或建议,或者您希望行使您的权利,请通过以下方式与我们联系:
|
||||
|
||||
• 邮箱:system@qczlit.cn
|
||||
|
||||
Reference in New Issue
Block a user