应用在首次打开或运行中,在用户未主动点击权限对应的相关功能或服务时,提前向用户弹窗申请开启【读取全部日程】权限,不符合相关法律法规要求。该问题已被修复。
Signed-off-by: laoyang <laoyang@example.com>
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
import { router } from '@kit.ArkUI';
|
||||
import { common } from '@kit.AbilityKit';
|
||||
import { BusinessError } from '@kit.BasicServicesKit';
|
||||
import { notificationManager } from '@kit.NotificationKit';
|
||||
import { DavAccount, AccountStore, CalSource, BookPalette } from '../common/AccountStore';
|
||||
import { EventDb, LocalEvent } from '../common/EventDb';
|
||||
import { AppSettings } from '../common/AppSettings';
|
||||
@@ -272,6 +273,11 @@ struct EventEditPage {
|
||||
}
|
||||
// 日志仅记录 uid 与提醒/重复设置,不落盘日程标题(避免隐私内容进入可备份的 sync.log)
|
||||
LogUtil.write(`本地保存日程(标题已脱敏)uid=${e.uid} 提醒=${e.reminders.join('/')}分钟 重复=${e.rrule === '' ? '否' : e.rrule}`);
|
||||
// 设置了提醒 → 此刻才申请通知权限(用户刚主动使用了依赖通知的功能,
|
||||
// 符合《审核指南》7.17"在用户主动点击对应功能时申请",不随 App 启动自动弹窗)
|
||||
if (e.reminders.length > 0) {
|
||||
await this.ensureNotifyPermission();
|
||||
}
|
||||
// 立即刷新提醒(不等下一轮同步),保证刚保存的提醒马上生效
|
||||
try {
|
||||
await ReminderService.refreshReminders(context as common.UIAbilityContext);
|
||||
@@ -290,6 +296,27 @@ struct EventEditPage {
|
||||
this.isSaving = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认通知权限已开启 —— 仅"用户保存了带提醒的日程"这条路径会调用。
|
||||
* 日程提醒依赖系统通知,用户刚主动设置了提醒,此时申请权限与其使用目的完全一致。
|
||||
* ⚠️ 严禁在 App 启动 / 首次打开时调用(《审核指南》7.17:不得提前弹窗申请权限)。
|
||||
*/
|
||||
private async ensureNotifyPermission(): Promise<void> {
|
||||
try {
|
||||
const enabled: boolean = await notificationManager.isNotificationEnabled();
|
||||
if (enabled) {
|
||||
return;
|
||||
}
|
||||
const context = this.getUIContext().getHostContext();
|
||||
if (context === undefined) {
|
||||
return;
|
||||
}
|
||||
await notificationManager.requestEnableNotification(context as common.UIAbilityContext);
|
||||
} catch (err) {
|
||||
// 用户拒绝或未弹窗:日程照常保存,只是提醒可能不弹出(设置页可再开启)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除前二次确认。
|
||||
* 删除日程不可撤销,且会同时从本地库与服务器(DAV)移除,故破坏性操作前必须显式确认,
|
||||
|
||||
@@ -5,7 +5,6 @@ import { mediaquery, router } from '@kit.ArkUI';
|
||||
import { common, abilityAccessCtrl, bundleManager } from '@kit.AbilityKit';
|
||||
import { geoLocationManager } from '@kit.LocationKit';
|
||||
import { BusinessError, pasteboard } from '@kit.BasicServicesKit';
|
||||
import { notificationManager } from '@kit.NotificationKit';
|
||||
import { DavAccount, AccountStore, CalSource, TYPE_CALDAV } from '../common/AccountStore';
|
||||
import { DisplayEvent, CalendarDataService } from '../common/CalendarDataService';
|
||||
import { SyncEngine } from '../common/SyncEngine';
|
||||
@@ -84,7 +83,6 @@ struct Index {
|
||||
// ---- 隐私政策与用户协议首启同意(未同意前不申请任何权限、不加载数据) ----
|
||||
@State policyChecked: boolean = false; // 是否已读取过同意状态(避免首帧闪烁)
|
||||
@State policyAgreed: boolean = false; // 是否已同意《隐私政策》与《用户协议》
|
||||
private notifAsked: boolean = false; // 通知权限是否已申请(同意后才申请)
|
||||
@State showTips: boolean = false; // 首启功能引导(整页覆盖层:遮罩 + 底部卡片 + Swiper 三页)
|
||||
// ---- 内置文档浏览器(《隐私政策》/《用户服务协议》,整页 Web 覆盖层,不跳出应用) ----
|
||||
@State showDoc: boolean = false;
|
||||
@@ -211,40 +209,17 @@ struct Index {
|
||||
}
|
||||
}
|
||||
|
||||
private initPermissionAndLoad(): void {
|
||||
const context = this.getUIContext().getHostContext();
|
||||
if (context === undefined) {
|
||||
return;
|
||||
}
|
||||
this.reloadAll();
|
||||
this.ensureNotificationPermission(context as common.UIAbilityContext);
|
||||
if (!this.permissionAsked) {
|
||||
this.permissionAsked = true;
|
||||
// 申请读取全部日历权限(用于混合展示系统日程)
|
||||
CalendarDataService.ensureSystemCalendarPermission(context as common.UIAbilityContext)
|
||||
.then((): void => {
|
||||
this.reloadAll();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 申请通知权限(日程提醒、后台同步常驻通知都依赖它)。
|
||||
* 必须在用户同意《隐私政策》之后才调用 —— 同意前不得申请任何权限。
|
||||
* 启动/同意隐私政策后的初始化:**只加载本地库与 DAV 数据**。
|
||||
*
|
||||
* ⚠️ 合规约束(《审核指南》7.17):这里**不申请任何权限**。
|
||||
* 权限必须由用户主动点击对应功能时申请,否则会被判定为"未主动点击即提前弹窗申请":
|
||||
* - 读取全部日程(日历)→ 设置页「混合显示系统日历」开关 / 「备份到 CalDAV 日历本」
|
||||
* - 通知 → 保存带提醒的日程 / 开启「后台持续同步」/ 设置页「去开启通知」
|
||||
* 本方法只负责加载数据;系统日历来源由 CalendarDataService.loadSources 按开关与授权状态自行决定是否读取。
|
||||
*/
|
||||
private ensureNotificationPermission(context: common.UIAbilityContext): void {
|
||||
if (this.notifAsked) {
|
||||
return;
|
||||
}
|
||||
this.notifAsked = true;
|
||||
notificationManager.isNotificationEnabled()
|
||||
.then((enabled: boolean): Promise<void> => {
|
||||
if (!enabled) {
|
||||
return notificationManager.requestEnableNotification(context).catch((): void => {});
|
||||
}
|
||||
return Promise.resolve();
|
||||
})
|
||||
.catch((): void => {});
|
||||
private initPermissionAndLoad(): void {
|
||||
this.reloadAll();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5,6 +5,7 @@ import { common } from '@kit.AbilityKit';
|
||||
import { notificationManager } from '@kit.NotificationKit';
|
||||
import { AppSettings } from '../common/AppSettings';
|
||||
import { CardDataService } from '../common/CardDataService';
|
||||
import { CalendarDataService } from '../common/CalendarDataService';
|
||||
import { BackgroundSyncService } from '../common/BackgroundSyncService';
|
||||
import { AccountStore, DavAccount } from '../common/AccountStore';
|
||||
import { ReminderService } from '../common/ReminderService';
|
||||
@@ -49,6 +50,15 @@ struct SettingsPage {
|
||||
this.context = ctx;
|
||||
AppSettings.getShowSystemCalendar(ctx).then((v: boolean): void => {
|
||||
this.showSystem = v;
|
||||
// 状态自洽:若记录为"开",但权限已被用户在系统设置里回收 → 自动回落为"关",避免开关与实际不符
|
||||
if (v) {
|
||||
CalendarDataService.hasSystemCalendarPermission().then((granted: boolean): void => {
|
||||
if (!granted) {
|
||||
this.showSystem = false;
|
||||
AppSettings.setShowSystemCalendar(ctx, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
AppSettings.getSyncIntervalMinutes(ctx).then((v: number): void => {
|
||||
this.intervalMinutes = v;
|
||||
@@ -87,6 +97,27 @@ struct SettingsPage {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 确认通知权限已开启 —— **只在用户主动使用依赖通知的功能时调用**
|
||||
* (保存带提醒的日程 / 开启后台持续同步 / 点击设置页"去开启通知")。
|
||||
* ⚠️ 严禁在 App 启动、首次打开等用户未操作的时机调用(《审核指南》7.17)。
|
||||
*/
|
||||
private async ensureNotifyPermission(): Promise<void> {
|
||||
if (this.context === undefined) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const enabled: boolean = await notificationManager.isNotificationEnabled();
|
||||
if (!enabled) {
|
||||
await notificationManager.requestEnableNotification(
|
||||
this.context as common.UIAbilityContext);
|
||||
}
|
||||
} catch (err) {
|
||||
// 用户拒绝或系统未弹窗:不阻断功能,仅状态仍显示为未开启
|
||||
}
|
||||
this.refreshNotifyState();
|
||||
}
|
||||
|
||||
/** 加载全部 DAV 日历本(含探测到的写权限,只读/静音标记管理用) */
|
||||
private async loadAllBooks(): Promise<void> {
|
||||
if (this.context === undefined) {
|
||||
@@ -198,6 +229,18 @@ struct SettingsPage {
|
||||
if (this.context === undefined) {
|
||||
return;
|
||||
}
|
||||
// "备份到 CalDAV" 需要读取手机系统(本地)日历 → 只在用户主动选择该模式时申请权限
|
||||
if (mode === 'backup') {
|
||||
const granted: boolean = await CalendarDataService.requestSystemCalendarPermission(
|
||||
this.context as common.UIAbilityContext);
|
||||
if (!granted) {
|
||||
this.sysMode = 'display'; // 回弹选择,保持与实际授权状态一致
|
||||
this.getUIContext().getPromptAction().showToast({
|
||||
message: '未获得"读取全部日程"权限,无法开启系统日历备份'
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.sysMode = mode;
|
||||
await AppSettings.setSysCalMode(this.context, mode);
|
||||
if (mode === 'backup' && this.backupKey === '') {
|
||||
@@ -223,9 +266,26 @@ struct SettingsPage {
|
||||
.showToast({ message: '备份目标已更新,下次同步生效' });
|
||||
}
|
||||
|
||||
private async saveShowSystem(value: boolean): Promise<void> { if (this.context === undefined) {
|
||||
/**
|
||||
* 保存「混合显示系统日历」开关。
|
||||
* ⚠️ 合规约束(《审核指南》7.17):读取系统日历依赖"读取全部日程"敏感权限,
|
||||
* **只有在用户主动打开该开关时才申请**;未授权则开关回弹、不保存设置。
|
||||
*/
|
||||
private async saveShowSystem(value: boolean): Promise<void> {
|
||||
if (this.context === undefined) {
|
||||
return;
|
||||
}
|
||||
if (value) {
|
||||
const granted: boolean = await CalendarDataService.requestSystemCalendarPermission(
|
||||
this.context as common.UIAbilityContext);
|
||||
if (!granted) {
|
||||
this.showSystem = false; // 回弹开关,保持与实际授权状态一致
|
||||
this.getUIContext().getPromptAction().showToast({
|
||||
message: '未获得"读取全部日程"权限,无法混合显示系统日历'
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
await AppSettings.setShowSystemCalendar(this.context, value);
|
||||
this.getUIContext().getPromptAction()
|
||||
.showToast({ message: value ? '已开启系统日历混合显示,返回首页生效' : '已关闭系统日历混合显示,返回首页生效' });
|
||||
@@ -245,6 +305,10 @@ struct SettingsPage {
|
||||
if (this.context === undefined) {
|
||||
return;
|
||||
}
|
||||
// 后台持续同步依赖常驻通知 → 在用户主动开启时才申请通知权限(不随启动自动弹窗)
|
||||
if (value) {
|
||||
await this.ensureNotifyPermission();
|
||||
}
|
||||
try {
|
||||
await BackgroundSyncService.setBackgroundSync(
|
||||
this.context as common.UIAbilityContext, value);
|
||||
@@ -447,7 +511,7 @@ struct SettingsPage {
|
||||
.fontSize(15)
|
||||
.fontWeight(FontWeight.Medium)
|
||||
.fontColor($r('app.color.text_primary'))
|
||||
Text('选择手机系统日历日程的处理方式')
|
||||
Text('选择手机系统日历日程的处理方式(需授权读取系统日历)')
|
||||
.fontSize(12)
|
||||
.fontColor($r('app.color.text_secondary'))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user