import { formBindingData, FormExtensionAbility, formInfo, formProvider } from '@kit.FormKit'; import { Configuration, Want } from '@kit.AbilityKit'; import { BusinessError } from '@kit.BasicServicesKit'; import { hilog } from '@kit.PerformanceAnalysisKit'; import { WidgetDataManager } from '../common/widget/WidgetDataManager'; import { PlayerControlService } from '../common/widget/PlayerControlService'; import { WidgetCommand, WidgetControlParams, WidgetData, WidgetSize } from '../common/widget/WidgetTypes'; import { FormLayoutManager } from '../common/widget/FormLayoutManager'; import { WidgetSizeAdapter, SizeChangeListener } from '../common/widget/WidgetSizeAdapter'; import { WidgetConfigManager, UserPreferences } from '../common/widget/WidgetConfigManager'; import { ThemeSyncService, ThemeChangeListener, ThemeChangeEvent, ThemeInfo } from '../common/widget/ThemeSyncService'; import { UserPreferencesService, PreferencesChangeListener, PreferencesChangeEvent } from '../common/widget/UserPreferencesService'; import { ObjectUtils } from '../common/widget/ObjectUtils'; import { PLAY_PAUSE_EVENT, NEXT_SONG_EVENT, PREV_SONG_EVENT, SEEK_TO_EVENT, OPEN_APP_EVENT, OPEN_PLAYER_EVENT, LONG_PRESS_MENU_EVENT, WIDGET_SETTINGS_EVENT, WIDGET_DELETE_EVENT, WIDGET_CONFIG_PAGE_EVENT, PAGE_MAIN, PAGE_PLAYER } from '../common/widget/WidgetEventConstants'; const TAG = 'Heanup'; /** * 卡片设置参数接口 */ interface WidgetSettingsParams extends Record { formId: string; currentSize: string; currentConfig: string; } /** * 卡片配置参数接口 */ interface WidgetConfigParams extends Record { formId: string; configType: string; configValue: string; } /** * 桌面播放器卡片扩展能力 * 负责处理卡片的生命周期管理和用户交互事件 * 支持多尺寸适配和UI重构 */ export default class EntryFormAbility extends FormExtensionAbility implements SizeChangeListener, ThemeChangeListener, PreferencesChangeListener { private widgetDataManager: WidgetDataManager | null = null; private playerControlService: PlayerControlService = new PlayerControlService(); private layoutManager: FormLayoutManager = FormLayoutManager.getInstance(); private sizeAdapter: WidgetSizeAdapter = WidgetSizeAdapter.getInstance(); private configManager: WidgetConfigManager = WidgetConfigManager.getInstance(); private themeSyncService: ThemeSyncService = ThemeSyncService.getInstance(); private userPreferencesService: UserPreferencesService = UserPreferencesService.getInstance(); private formSizeMap: Map = new Map(); /** * 初始化服务 */ private initializeServices(): void { if (!this.widgetDataManager) { this.widgetDataManager = new WidgetDataManager(); hilog.info(0x0000, TAG, 'WidgetDataManager initialized'); } } /** * 卡片创建时调用 */ onAddForm(want: Want): formBindingData.FormBindingData { hilog.info(0x0000, TAG, 'onAddForm called'); // 初始化服务 this.initializeServices(); const formId = want.parameters?.['ohos.extra.param.key.form_identity'] as string; const formName = want.parameters?.['ohos.extra.param.key.form_name'] as string; const tempFlag = want.parameters?.['ohos.extra.param.key.form_temporary'] as boolean; hilog.info(0x0000, TAG, `Form added: ${formId}, name: ${formName}, temp: ${tempFlag}`); // 检测卡片尺寸 const widgetSize = this.sizeAdapter.detectSizeFromWant(want); this.formSizeMap.set(formId, widgetSize); hilog.info(0x0000, TAG, `Detected widget size: ${widgetSize} for form: ${formId}`); // 注册各种监听器 this.sizeAdapter.registerSizeChangeListener(formId, this); this.themeSyncService.addThemeChangeListener(this); this.userPreferencesService.addPreferencesChangeListener(this); // 初始化卡片数据 this.initializeWidget(formId, widgetSize); // 添加数据流测试 - 仅在开发环境中 setTimeout(() => { this.testWidgetDataFlow(formId); }, 3000); // 获取当前播放状态而不是初始数据 this.playerControlService.getCurrentPlayState().then((currentState: WidgetData) => { const adaptedData = this.layoutManager.adaptDataForSize(currentState, widgetSize); const formData = formBindingData.createFormBindingData(adaptedData); // 立即更新卡片以显示当前状态 formProvider.updateForm(formId, formData); hilog.info(0x0000, TAG, `Widget ${formId} initialized with current state`); }); // 返回初始数据作为临时显示 const initialData = this.widgetDataManager?.getInitialWidgetData() || this.getDefaultWidgetData(); const adaptedData = this.layoutManager.adaptDataForSize(initialData, widgetSize); return formBindingData.createFormBindingData(adaptedData); } /** * 获取默认卡片数据 */ private getDefaultWidgetData(): WidgetData { return { playState: { isPlaying: false, isPaused: true, isLoading: false }, currentSong: { id: '', title: '暂无播放', artist: '未知艺术家', album: '未知专辑', coverImagePath: '', duration: 0 }, progress: { currentPosition: 0, duration: 0, percentage: 0, currentTimeText: '00:00', totalTimeText: '00:00' }, playlist: { hasNext: false, hasPrevious: false, currentIndex: 0, totalCount: 0 }, config: { size: 'medium' as WidgetSize, theme: 'auto' as string, showProgress: true, showCover: true } }; } /** * 卡片更新时调用 */ onUpdateForm(formId: string): void { hilog.info(0x0000, TAG, `onUpdateForm called: ${formId}`); // 获取最新的播放状态数据 this.updateWidgetData(formId); // 延迟再次更新,确保数据同步到所有卡片 setTimeout((): void => { this.updateAllWidgetsData(); }, 200); } /** * 卡片删除时调用 */ onRemoveForm(formId: string): void { hilog.info(0x0000, TAG, `onRemoveForm called: ${formId}`); // 注销各种监听器 this.sizeAdapter.unregisterSizeChangeListener(formId); this.themeSyncService.removeThemeChangeListener(this); this.userPreferencesService.removePreferencesChangeListener(this); // 清理卡片尺寸记录 this.formSizeMap.delete(formId); // 清理卡片相关数据和配置 this.widgetDataManager?.removeWidgetData(formId); this.configManager.removeWidgetConfig(formId); } /** * 卡片可见性变化时调用 */ onVisibilityChange(newStatus: Record): void { hilog.info(0x0000, TAG, 'onVisibilityChange called'); const formIds = Object.keys(newStatus); for (let i = 0; i < formIds.length; i++) { const formId = formIds[i]; const isVisible = newStatus[formId] === 1; hilog.info(0x0000, TAG, `Form ${formId} visibility: ${isVisible}`); if (isVisible) { // 卡片变为可见时,更新数据 this.updateWidgetData(formId); } } } /** * 处理卡片事件(用户交互) */ onFormEvent(formId: string, message: string): void { hilog.info(0x0000, TAG, `onFormEvent called: ${formId}, message: ${message}`); try { const eventData = JSON.parse(message) as Object; this.handleWidgetEvent(formId, eventData); } catch (error) { hilog.error(0x0000, TAG, `Failed to parse form event: ${error}`); } } /** * 卡片配置更新时调用 */ onConfigurationUpdate(newConfig: Object): void { hilog.info(0x0000, TAG, 'onConfigurationUpdate called'); // 启动主题同步服务 this.themeSyncService.startThemeSync(); // 更新所有卡片以适应新配置 this.widgetDataManager?.updateAllWidgets(); } /** * 实现SizeChangeListener接口 * 处理卡片尺寸变化事件 */ onSizeChanged(formId: string, oldSize: WidgetSize, newSize: WidgetSize): void { hilog.info(0x0000, TAG, `Size changed for form ${formId}: ${oldSize} -> ${newSize}`); // 更新尺寸记录 this.formSizeMap.set(formId, newSize); // 立即更新卡片数据以适应新尺寸 this.updateWidgetData(formId); } /** * 处理卡片尺寸变化(系统调用) * @param newStatus 新的尺寸状态 */ onAcquireFormState(want: Want): number { hilog.info(0x0000, TAG, 'onAcquireFormState called'); const formId = want.parameters?.['ohos.extra.param.key.form_identity'] as string; if (formId) { // 检测新的尺寸 const newSize = this.sizeAdapter.detectSizeFromWant(want); const oldSize = this.formSizeMap.get(formId); if (oldSize && this.sizeAdapter.validateSizeChange(oldSize, newSize)) { // 处理尺寸变化 this.handleFormSizeChange(formId, oldSize, newSize); } } // 返回卡片状态 - 使用数字常量代替枚举 return 1; // READY状态 } /** * 实现ThemeChangeListener接口 * 处理主题变化事件 */ onThemeChanged(event: ThemeChangeEvent): void { hilog.info(0x0000, TAG, `Theme changed: ${event.themeInfo.theme}`); // 更新所有卡片的主题 this.updateAllWidgetsTheme(event.themeInfo); } /** * 实现PreferencesChangeListener接口 * 处理用户偏好设置变化事件 */ onPreferencesChanged(event: PreferencesChangeEvent): void { hilog.info(0x0000, TAG, `Preferences changed: ${event.changedKeys.join(', ')}`); // 根据变化的设置更新卡片 this.handlePreferencesChange(event.changedKeys, event.newPreferences); } /** * 初始化卡片 */ private async initializeWidget(formId: string, widgetSize: WidgetSize): Promise { try { // 注册播放状态监听 this.playerControlService.registerStateListener((data: WidgetData) => { hilog.info(0x0000, TAG, `Widget ${formId} received state update: isPlaying=${data.playState.isPlaying}, title=${data.currentSong.title}, hasNext=${data.playlist.hasNext}, hasPrevious=${data.playlist.hasPrevious}`); // 根据卡片尺寸适配数据 const currentSize = this.formSizeMap.get(formId) || WidgetSize.MEDIUM; const adaptedData = this.layoutManager.adaptDataForSize(data, currentSize); this.widgetDataManager?.updateWidget(formId, adaptedData); hilog.info(0x0000, TAG, `Widget ${formId} updated successfully`); }); // 获取当前播放状态 const currentState = await this.playerControlService.getCurrentPlayState(); // 获取卡片个性化配置 const personalConfig = await this.configManager.getWidgetConfig(formId, widgetSize); // 应用尺寸和个性化配置 currentState.config = ObjectUtils.assign(currentState.config); currentState.config.showProgress = personalConfig.showProgress; currentState.config.showCover = personalConfig.showCover; currentState.config.theme = personalConfig.theme; // 适配数据到指定尺寸 const adaptedData = this.layoutManager.adaptDataForSize(currentState, widgetSize); await this.widgetDataManager?.saveWidgetData(formId, adaptedData); // 立即请求一次最新状态,并多次重试确保获取到真实状态 setTimeout(() => { this.updateWidgetData(formId); }, 500); setTimeout(() => { this.updateWidgetData(formId); }, 2000); setTimeout(() => { this.updateWidgetData(formId); }, 5000); hilog.info(0x0000, TAG, `Widget ${formId} initialized successfully with size: ${widgetSize}, listeners registered: PlayerControl + AvSession`); } catch (error) { hilog.error(0x0000, TAG, `Failed to initialize widget ${formId}: ${error}`); } } /** * 更新卡片数据 */ private async updateWidgetData(formId: string): Promise { try { const currentState = await this.playerControlService.getCurrentPlayState(); // 获取卡片当前尺寸 const currentSize = this.formSizeMap.get(formId) || WidgetSize.MEDIUM; // 适配数据到当前尺寸 const adaptedData = this.layoutManager.adaptDataForSize(currentState, currentSize); await this.widgetDataManager?.updateWidget(formId, adaptedData); hilog.info(0x0000, TAG, `Widget ${formId} updated successfully with size: ${currentSize}`); } catch (error) { hilog.error(0x0000, TAG, `Failed to update widget ${formId}: ${error}`); } } /** * 更新所有卡片数据 */ private async updateAllWidgetsData(): Promise { try { const currentState = await this.playerControlService.getCurrentPlayState(); // 遍历所有卡片ID并更新 this.formSizeMap.forEach(async (size: WidgetSize, formId: string) => { try { const adaptedData = this.layoutManager.adaptDataForSize(currentState, size); await this.widgetDataManager?.updateWidget(formId, adaptedData); hilog.info(0x0000, TAG, `Widget ${formId} updated in batch with size: ${size}`); } catch (error) { hilog.error(0x0000, TAG, `Failed to update widget ${formId} in batch: ${error}`); } }); hilog.info(0x0000, TAG, `Batch update completed for ${this.formSizeMap.size} widgets`); } catch (error) { hilog.error(0x0000, TAG, `Failed to batch update widgets: ${error}`); } } /** * 处理卡片事件 */ private async handleWidgetEvent(formId: string, eventData: Object): Promise { try { // 解析事件数据 const actionData: Record = eventData as Record; const action: string = (actionData['action'] as string) || 'message'; hilog.info(0x0000, TAG, `Handling widget event,action ${action}`); hilog.info(0x0000, TAG, `Handling widget event,actionData ${JSON.stringify(actionData)}`); // 获取事件类型,优先从顶层的func字段获取,如果没有则从params中获取 let eventType: string = (actionData['func'] as string) || ''; let eventParams: Record = {}; if (!eventType) { // 如果顶层没有func,尝试从params中获取 const params: Record = (actionData['params'] as Record) || {}; eventType = (params['func'] as string) || ''; eventParams = params; } else { // 如果顶层有func,则整个actionData就是参数 eventParams = actionData; } if (!eventType) { hilog.warn(0x0000, TAG, `No event type found in widget event data`); return; } hilog.info(0x0000, TAG, `Processing widget event type: ${eventType}`); switch (eventType) { case PLAY_PAUSE_EVENT: await this.playerControlService.sendControlCommand(WidgetCommand.PLAY_PAUSE); break; case NEXT_SONG_EVENT: await this.playerControlService.sendControlCommand(WidgetCommand.NEXT_SONG); break; case PREV_SONG_EVENT: await this.playerControlService.sendControlCommand(WidgetCommand.PREV_SONG); break; case SEEK_TO_EVENT: const percentage = (eventParams['percentage'] as number) || 0; const controlParams: WidgetControlParams = { percentage: percentage, position: 0 }; hilog.info(0x0000, TAG, `Seeking to percentage: ${percentage}`); await this.playerControlService.sendControlCommand(WidgetCommand.SEEK_TO, controlParams); break; case OPEN_APP_EVENT: // 启动主应用到首页 await this.playerControlService.launchMainApp(PAGE_MAIN); break; case OPEN_PLAYER_EVENT: // 启动主应用到播放器页面 await this.playerControlService.launchMainApp(PAGE_PLAYER); break; case LONG_PRESS_MENU_EVENT: // 处理长按菜单显示(由系统处理,这里记录日志) hilog.info(0x0000, TAG, 'Long press menu event triggered'); break; case WIDGET_SETTINGS_EVENT: // 打开卡片设置页面 await this.handleWidgetSettings(formId); break; case WIDGET_DELETE_EVENT: // 处理卡片删除 await this.handleWidgetDelete(formId); break; case WIDGET_CONFIG_PAGE_EVENT: // 跳转到卡片配置界面 await this.handleWidgetConfigPage(formId); break; default: hilog.warn(0x0000, TAG, `Unknown widget event type: ${eventType}`); return; } // 处理完命令后立即更新当前卡片状态 this.updateWidgetData(formId); // 延迟更新所有卡片,确保状态同步 setTimeout((): void => { this.updateAllWidgetsData(); }, 300); } catch (error) { hilog.error(0x0000, TAG, `Failed to handle widget event: ${error}`); } } /** * 处理表单尺寸变化 */ private async handleFormSizeChange(formId: string, oldSize: WidgetSize, newSize: WidgetSize): Promise { try { hilog.info(0x0000, TAG, `Handling form size change: ${formId} from ${oldSize} to ${newSize}`); // 获取当前播放状态 const currentState = await this.playerControlService.getCurrentPlayState(); // 使用适配器处理尺寸变化 const adaptedData = this.sizeAdapter.handleSizeChange(formId, oldSize, newSize, currentState); // 更新卡片数据 await this.widgetDataManager?.updateWidget(formId, adaptedData); hilog.info(0x0000, TAG, `Form size change handled successfully for: ${formId}`); } catch (error) { hilog.error(0x0000, TAG, `Failed to handle form size change: ${error}`); } } /** * 更新所有卡片的主题 */ private async updateAllWidgetsTheme(themeInfo: ThemeInfo): Promise { try { // 更新所有卡片配置中的主题信息 await this.configManager.updateAllWidgetConfigs((config) => { const updatedConfig = ObjectUtils.assign(config); updatedConfig.theme = themeInfo.theme; updatedConfig.backgroundColor = themeInfo.backgroundColor; updatedConfig.textColor = themeInfo.textColor; updatedConfig.accentColor = themeInfo.accentColor; return updatedConfig; }); // 刷新所有卡片显示 await this.widgetDataManager?.updateAllWidgets(); hilog.info(0x0000, TAG, 'All widgets theme updated'); } catch (error) { hilog.error(0x0000, TAG, `Failed to update all widgets theme: ${error}`); } } /** * 处理偏好设置变化 */ private async handlePreferencesChange(changedKeys: string[], newPreferences: UserPreferences): Promise { try { // 检查是否需要重新同步主题 if (changedKeys.includes('syncThemeColor') && newPreferences.syncThemeColor) { await this.themeSyncService.syncTheme(); } // 检查是否需要更新缓存设置 if (changedKeys.includes('enableCache') || changedKeys.includes('cacheExpiry')) { // 这里可以更新缓存相关设置 hilog.info(0x0000, TAG, 'Cache settings updated'); } // 检查隐私设置变化 const privacyKeys = ['showSongInfo', 'showArtistInfo', 'showAlbumCover']; const hasPrivacyChange = changedKeys.some(key => privacyKeys.includes(key)); if (hasPrivacyChange) { // 更新所有卡片的显示配置 await this.configManager.updateAllWidgetConfigs((config) => { const updatedConfig = ObjectUtils.assign(config); updatedConfig.showCover = (newPreferences.showAlbumCover as boolean) && config.showCover; return updatedConfig; }); // 刷新所有卡片显示 await this.widgetDataManager?.updateAllWidgets(); } hilog.info(0x0000, TAG, 'Preferences change handled successfully'); } catch (error) { hilog.error(0x0000, TAG, `Failed to handle preferences change: ${error}`); } } /** * 处理卡片设置 */ private async handleWidgetSettings(formId: string): Promise { try { hilog.info(0x0000, TAG, `Opening widget settings for form: ${formId}`); // 获取当前卡片配置 const currentSize = this.formSizeMap.get(formId) || WidgetSize.MEDIUM; const currentConfig = await this.configManager.getWidgetConfig(formId, currentSize); // 启动主应用到卡片设置页面,传递卡片ID和当前配置 const settingsParams: WidgetSettingsParams = { formId: formId, currentSize: currentSize, currentConfig: JSON.stringify(currentConfig) }; await this.playerControlService.launchMainApp('widget_settings', settingsParams); hilog.info(0x0000, TAG, `Widget settings opened for form: ${formId}`); } catch (error) { hilog.error(0x0000, TAG, `Failed to open widget settings: ${error}`); } } /** * 处理卡片删除 */ private async handleWidgetDelete(formId: string): Promise { try { hilog.info(0x0000, TAG, `Processing widget delete for form: ${formId}`); // 显示删除确认对话框(通过主应用) const deleteParams: WidgetConfigParams = { formId: formId, configType: 'delete', configValue: 'confirm_delete' }; await this.playerControlService.launchMainApp('widget_delete_confirm', deleteParams); hilog.info(0x0000, TAG, `Widget delete confirmation shown for form: ${formId}`); } catch (error) { hilog.error(0x0000, TAG, `Failed to process widget delete: ${error}`); } } /** * 处理卡片配置界面跳转 */ private async handleWidgetConfigPage(formId: string): Promise { try { hilog.info(0x0000, TAG, `Opening widget config page for form: ${formId}`); // 获取当前卡片信息 const currentSize = this.formSizeMap.get(formId) || WidgetSize.MEDIUM; const currentConfig = await this.configManager.getWidgetConfig(formId, currentSize); // 启动主应用到卡片配置页面 const configParams: WidgetConfigParams = { formId: formId, configType: 'full_config', configValue: JSON.stringify(currentConfig) }; await this.playerControlService.launchMainApp('widget_config', configParams); hilog.info(0x0000, TAG, `Widget config page opened for form: ${formId}`); } catch (error) { hilog.error(0x0000, TAG, `Failed to open widget config page: ${error}`); } } /** * 测试卡片数据流(开发调试用) */ private testWidgetDataFlow(formId: string): void { hilog.info(0x0000, TAG, `🔍 Testing widget data flow for form: ${formId}`); // 测试1: 检查监听器状态 hilog.info(0x0000, TAG, `📊 Current listener count: PlayerControlService has listeners registered`); // 测试2: 手动触发一次数据更新 setTimeout(() => { hilog.info(0x0000, TAG, `🔄 Manually triggering data update for form: ${formId}`); this.updateWidgetData(formId); }, 1000); // 测试3: 获取AvSession当前数据 setTimeout(() => { try { const currentData = this.playerControlService.getCurrentPlayState(); currentData.then((data) => { hilog.info(0x0000, TAG, `📊 AvSession current data: isPlaying=${data.playState.isPlaying}, hasNext=${data.playlist.hasNext}, hasPrevious=${data.playlist.hasPrevious}, title=${data.currentSong.title}`); }); } catch (error) { hilog.error(0x0000, TAG, `❌ Failed to get current data: ${error}`); } }, 2000); } }