EntryAbility.ets 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  1. /**
  2. * 应用主Ability入口文件
  3. * 功能:
  4. * 1. 管理应用生命周期
  5. * 2. 处理窗口创建和尺寸变化
  6. * 3. 响应外部调用请求
  7. * 4. 全局状态管理
  8. */
  9. import UIAbility from '@ohos.app.ability.UIAbility';
  10. import hilog from '@ohos.hilog';
  11. import window from '@ohos.window';
  12. import { AbilityConstant, Want } from '@kit.AbilityKit';
  13. import { BusinessError, emitter } from '@kit.BasicServicesKit';
  14. import { AppUtil, ArrayUtil, GlobalContext, LogUtil, StrUtil } from '@pura/harmony-utils';
  15. import { rpc } from '@kit.IPCKit';
  16. import { Utility } from '../common/util/Utility';
  17. import { WXApi, WXEventHandler } from '../common/util/WXApiWrap';
  18. import { UnifiedPlayerService } from '../common/service/UnifiedPlayerService';
  19. import { WidgetRegistrationFix } from '../common/widget/WidgetRegistrationFix';
  20. import { systemShare } from '@kit.ShareKit';
  21. import { SpiderMan } from '@simplepeng/spider-man';
  22. import { smartMobilityCommon } from '@kit.CarKit';
  23. /**
  24. * RPC通信返回类型的实现,用于RPC通信数据序列化和反序列化
  25. */
  26. class MyParcelable implements rpc.Parcelable {
  27. num: number;
  28. str: string;
  29. constructor(num: number, str: string) {
  30. this.num = num;
  31. this.str = str;
  32. }
  33. marshalling(messageSequence: rpc.MessageSequence): boolean {
  34. messageSequence.writeInt(this.num);
  35. messageSequence.writeString(this.str);
  36. return true;
  37. }
  38. unmarshalling(messageSequence: rpc.MessageSequence): boolean {
  39. this.num = messageSequence.readInt();
  40. this.str = messageSequence.readString();
  41. return true;
  42. }
  43. }
  44. /**
  45. * 主Ability类,继承自UIAbility
  46. * 负责:
  47. * - 应用初始化
  48. * - 窗口管理
  49. * - 事件分发
  50. */
  51. export default class EntryAbility extends UIAbility {
  52. // UI上下文对象,用于获取窗口信息
  53. private uiContext?: UIContext;
  54. private awareness: smartMobilityCommon.SmartMobilityAwareness = smartMobilityCommon.getSmartMobilityAwareness();
  55. /**
  56. * 窗口尺寸变化回调函数
  57. * @param windowSize 新的窗口尺寸对象
  58. * 功能:
  59. * 1. 获取最新的窗口断点尺寸
  60. * 2. 更新AppStorage中的尺寸状态
  61. * 3. 记录尺寸变化日志
  62. */
  63. private onWindowSizeChange: (windowSize: window.Size) => void = async (windowSize: window.Size) => {
  64. // 获取宽度断点并更新全局状态
  65. let widthBp = this.uiContext!.getWindowWidthBreakpoint();
  66. AppStorage.setOrCreate('currentWidthBreakpoint', widthBp);
  67. // 获取高度断点并更新全局状态
  68. let heightBp = this.uiContext!.getWindowHeightBreakpoint();
  69. AppStorage.setOrCreate('currentHeightBreakpoint', heightBp);
  70. // 记录尺寸变化日志
  71. // LogUtil.info('pura onWindowSizeChange currentHeightBreakpoint= '+heightBp);
  72. // LogUtil.info( 'pura onWindowSizeChange currentWidthBreakpoint= '+widthBp);
  73. AppStorage.setOrCreate('windowWidth', windowSize.width);
  74. AppStorage.setOrCreate('windowHeight', windowSize.height);
  75. if(windowSize.width > windowSize.height){
  76. AppStorage.setOrCreate('isLandscape', true);
  77. }else{
  78. AppStorage.setOrCreate('isLandscape', false);
  79. }
  80. // LogUtil.info('hicar onWindowSizeChange width= '+windowSize.width);
  81. // LogUtil.info( 'hicar onWindowSizeChange height= '+windowSize.height);
  82. };
  83. onAvoidAreaChange = (data: window.AvoidAreaOptions) => {
  84. if (data.type === window.AvoidAreaType.TYPE_SYSTEM) {
  85. let topRectHeight = px2vp(data.area.topRect.height);
  86. AppStorage.setOrCreate('topRectHeight', topRectHeight);
  87. } else if (data.type === window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR) {
  88. let bottomRectHeight = px2vp(data.area.bottomRect.height);
  89. AppStorage.setOrCreate('bottomRectHeight', bottomRectHeight);
  90. }
  91. }
  92. async onCreate(want:Want, launchParam:AbilityConstant.LaunchParam) {
  93. AppUtil.init(this.context);
  94. AppStorage.setOrCreate('context', this.context);
  95. hilog.info(0x0000, 'Heanup2', '%{public}s', 'Ability onCreate');
  96. // 注册卡片call事件监听器
  97. this.registerWidgetCallListeners();
  98. // 初始化统一播放器服务(与主应用共享)
  99. try {
  100. await UnifiedPlayerService.getInstance().initialize(this.context);
  101. hilog.info(0x0000, 'Heanup2', '✅ UnifiedPlayerService initialized successfully');
  102. } catch (error) {
  103. hilog.error(0x0000, 'Heanup2', `❌ Failed to initialize UnifiedPlayerService: ${error}`);
  104. }
  105. // 执行卡片注册修复(异步执行,不阻塞启动)
  106. this.fixWidgetRegistrationAsync();
  107. setTimeout(async ()=>{
  108. this.loadDoWant(want)
  109. await this.handleParam(want)
  110. },2000)
  111. this.handleWeChatCallIfNeed(want)
  112. this.getHiCarStatus()
  113. }
  114. async onNewWant(want: Want, launchParam: AbilityConstant.LaunchParam): Promise<void> {
  115. hilog.info(0x0000, 'Heanup2', `onNewWant, want=${JSON.stringify(want)}`);
  116. super.onNewWant(want, launchParam);
  117. this.loadDoWant(want)
  118. await this.handleParam(want)
  119. this.handleWeChatCallIfNeed(want)
  120. }
  121. private handleWeChatCallIfNeed(want: Want) {
  122. WXApi.handleWant(want, WXEventHandler)
  123. }
  124. //处理其他app点击其他应用打开播放器播放视频或者音频
  125. loadDoWant(want: Want){
  126. // console.info('onecold KnockController 碰一碰 want ='+JSON.stringify(want));
  127. let uri = want.uri;
  128. if (uri == null || uri == undefined|| StrUtil.isEmpty(uri)) {
  129. console.info('uri is invalid');
  130. return;
  131. }
  132. hilog.info(0x0000, 'Heanup2', `onCreate or onNewWant, uri=${uri}`);
  133. this.doSendEmit(uri)
  134. }
  135. //广播通知打开播放器播放视频或者音频
  136. doSendEmit(uri:string){
  137. setTimeout(async ()=>{
  138. let eventData: emitter.EventData = {
  139. data: {
  140. message: uri
  141. }
  142. };
  143. if(Utility.isMeidaByExtension(uri)){
  144. emitter.emit({ eventId: 2 }, eventData); // 发送音频打开广播事件
  145. }else{
  146. emitter.emit({ eventId: 1 }, eventData); // 发送视频打开广播事件
  147. }
  148. },300)
  149. }
  150. // 华为分享拉起接收 处理分享数据
  151. // 1. 改造 handleParam 为异步函数,让其返回 Promise
  152. async handleParam(want: Want) {
  153. try {
  154. // 通过 await 等待异步操作完成
  155. const data = await systemShare.getSharedData(want);
  156. const records = data.getRecords();
  157. let uri = want.uri;
  158. for (const record of records) {
  159. if (record.uri) {
  160. uri = record.uri;
  161. this.doSendEmit(uri)
  162. break;
  163. }
  164. }
  165. } catch (error) {
  166. const businessError = error as BusinessError;
  167. console.error(`Failed: Code ${businessError.code}, ${businessError.message}`);
  168. }
  169. }
  170. onDestroy() {
  171. hilog.info(0x0000, 'Heanup2', '%{public}s', 'Ability onDestroy');
  172. // 注销卡片call事件监听器
  173. this.unregisterWidgetCallListeners();
  174. let types: smartMobilityCommon.SmartMobilityType[] = [smartMobilityCommon.SmartMobilityType.CAR_HOP];
  175. // 出行连接状态回调函数
  176. const callBack = (info: smartMobilityCommon.SmartMobilityInfo) => {
  177. hilog.info(0x0000, 'Received smart mobility info: ', JSON.stringify(info));
  178. };
  179. // 解注册智慧出行连接状态的监听 示例2
  180. this.awareness.off('smartMobilityStatus', types, callBack);
  181. }
  182. onWindowStageCreate(windowStage: window.WindowStage) {
  183. // Main window is created, set main page for this ability
  184. hilog.info(0x0000, 'Heanup2', '%{public}s', 'Ability onWindowStageCreate');
  185. SpiderMan.init();
  186. AppUtil.init(this.context);
  187. // 初始化 DirectFormUpdateService 的上下文
  188. try {
  189. import('../common/widget/DirectFormUpdateService').then((module) => {
  190. const directFormService = module.DirectFormUpdateService.getInstance();
  191. directFormService.setAppContext(this.context);
  192. hilog.info(0x0000, 'Heanup2', 'DirectFormUpdateService context initialized');
  193. }).catch((error: Error) => {
  194. hilog.error(0x0000, 'Heanup2', `Failed to initialize DirectFormUpdateService: ${error.message}`);
  195. });
  196. } catch (error) {
  197. hilog.error(0x0000, 'Heanup2', `Error initializing DirectFormUpdateService: ${error}`);
  198. }
  199. //1.获取应用主窗口。
  200. let windowClass: window.Window | null = null;
  201. windowStage.getMainWindow((err: BusinessError, data) => {
  202. windowClass = data;
  203. // LogUtil.info( 'getMainWindow = ');
  204. GlobalContext.getContext().setObject('windowClass', data); // 使用GlobalContext替代globalThis
  205. windowClass.setWindowLayoutFullScreen(true).then(() => {
  206. console.info('Succeeded in setting the window layout to full-screen mode.');
  207. }).catch((e: BusinessError) => {
  208. console.error('Failed to set the window layout to full-screen mode. Cause:' + JSON.stringify(e));
  209. })
  210. // let avoidArea = windowClass.getWindowAvoidArea(window.AvoidAreaType.TYPE_SYSTEM)
  211. // let topRectHeight = px2vp(avoidArea.topRect.height);
  212. // AppStorage.setOrCreate('topRectHeight', topRectHeight);
  213. let type = window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR; // 以导航条避让为例
  214. let avoidArea = windowClass.getWindowAvoidArea(type);
  215. let bottomRectHeight = px2vp(avoidArea.bottomRect.height); // 获取到导航条区域的高度
  216. AppStorage.setOrCreate('bottomRectHeight', bottomRectHeight);
  217. type = window.AvoidAreaType.TYPE_SYSTEM; // 以状态栏避让为例
  218. avoidArea = windowClass.getWindowAvoidArea(type);
  219. let topRectHeight = px2vp(avoidArea.topRect.height) // 获取状态栏区域高度
  220. AppStorage.setOrCreate('topRectHeight', topRectHeight);
  221. LogUtil.info('onecold topRectHeight = '+topRectHeight);
  222. windowClass.on('avoidAreaChange', this.onAvoidAreaChange)
  223. })
  224. AppStorage.setOrCreate('windowStage',windowStage);
  225. hilog.info(0x0000, 'Heanup2', '%{public}s', 'Ability onWindowStageCreate');
  226. windowStage.loadContent('pages/SplashIndex', (err, data) => {
  227. if (err.code) {
  228. hilog.error(0x0000, 'Heanup2', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
  229. return;
  230. }
  231. //一多断点开发
  232. windowStage.getMainWindow().then((data: window.Window) => {
  233. this.uiContext = data.getUIContext();
  234. let widthBp = this.uiContext.getWindowWidthBreakpoint();
  235. let heightBp = this.uiContext.getWindowHeightBreakpoint();
  236. AppStorage.setOrCreate('currentWidthBreakpoint', widthBp);
  237. AppStorage.setOrCreate('currentHeightBreakpoint', heightBp);
  238. LogUtil.info( 'getMainWindow currentHeightBreakpoint= '+heightBp);
  239. LogUtil.info( 'getMainWindow currentWidthBreakpoint= '+widthBp);
  240. data.on('windowSizeChange', this.onWindowSizeChange);
  241. AppStorage.setOrCreate('windowWidth', data.getWindowProperties().windowRect.width);
  242. AppStorage.setOrCreate('windowHeight', data.getWindowProperties().windowRect.height);
  243. if(data.getWindowProperties().windowRect.width > data.getWindowProperties().windowRect.height){
  244. AppStorage.setOrCreate('isLandscape', true);
  245. }else{
  246. AppStorage.setOrCreate('isLandscape', false);
  247. }
  248. // LogUtil.info('hicar getMainWindow width= '+data.getWindowProperties().windowRect.width);
  249. // LogUtil.info( 'hicar getMainWindow height= '+data.getWindowProperties().windowRect.height);
  250. }).catch((err: BusinessError) => {
  251. console.error(`Failed to obtain the main window. Cause code: ${err.code}, message: ${err.message}`);
  252. });
  253. hilog.info(0x0000, 'Heanup2', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');
  254. });
  255. }
  256. onWindowStageDestroy() {
  257. // Main window is destroyed, release UI related resources
  258. hilog.info(0x0000, 'Heanup2', '%{public}s', 'Ability onWindowStageDestroy');
  259. }
  260. onForeground() {
  261. // Ability has brought to foreground
  262. hilog.info(0x0000, 'Heanup2', '%{public}s', 'Ability onForeground');
  263. }
  264. onBackground() {
  265. // Ability has back to background
  266. hilog.info(0x0000, 'Heanup2', '%{public}s', 'Ability onBackground');
  267. }
  268. getHiCarStatus(){
  269. this.awareness = smartMobilityCommon.getSmartMobilityAwareness();
  270. // 业务类型
  271. let types: smartMobilityCommon.SmartMobilityType[] = [smartMobilityCommon.SmartMobilityType.HICAR];
  272. // 获取出行业务连接状态
  273. let info = this.awareness.getSmartMobilityStatus(types[0]);
  274. hilog.info(0x0000, 'getHiCarStatus info: ', JSON.stringify(info));
  275. if(info&&info.status==1){
  276. AppStorage.setOrCreate('isHiCarStatus', true);
  277. }else{
  278. AppStorage.setOrCreate('isHiCarStatus', false);
  279. }
  280. // 出行连接状态回调函数
  281. const callBack = (info: smartMobilityCommon.SmartMobilityInfo) => {
  282. hilog.info(0x0000, 'getHiCarStatus Received smart mobility info: ', JSON.stringify(info));
  283. if(info&&info.status==1){
  284. AppStorage.setOrCreate('isHiCarStatus', true);
  285. }else{
  286. AppStorage.setOrCreate('isHiCarStatus', false);
  287. }
  288. this.sendChangeEvent()
  289. };
  290. // 注册智慧出行连接状态的监听
  291. this.awareness.on('smartMobilityStatus', types, callBack);
  292. }
  293. //发送广播通知更新UI
  294. sendChangeEvent() {
  295. const eventData: emitter.EventData = {};
  296. emitter.emit({ eventId: 333 }, eventData); // 发送广播通知更新doSwipBack
  297. }
  298. /**
  299. * 注册卡片call事件监听器
  300. */
  301. private registerWidgetCallListeners(): void {
  302. try {
  303. // 监听播放/暂停事件
  304. this.callee.on('playPause', (data: rpc.MessageSequence) => {
  305. hilog.info(0x0000, 'Heanup2', `Widget call: playPause received`);
  306. const params: Record<string, Object> = JSON.parse(data.readString()) as Record<string, Object>;
  307. hilog.info(0x0000, 'Heanup2', `Widget playPause params: ${JSON.stringify(params)}`);
  308. // 发送播放/暂停事件到主应用
  309. this.sendWidgetControlEvent('PLAY_PAUSE', params);
  310. return new MyParcelable(1, 'playPause_success');
  311. });
  312. // 监听下一首事件
  313. this.callee.on('nextSong', (data: rpc.MessageSequence) => {
  314. hilog.info(0x0000, 'Heanup2', `Widget call: nextSong received`);
  315. const params: Record<string, Object> = JSON.parse(data.readString()) as Record<string, Object>;
  316. hilog.info(0x0000, 'Heanup2', `Widget nextSong params: ${JSON.stringify(params)}`);
  317. // 发送下一首事件到主应用
  318. this.sendWidgetControlEvent('NEXT_SONG', params);
  319. return new MyParcelable(2, 'nextSong_success');
  320. });
  321. // 监听上一首事件
  322. this.callee.on('prevSong', (data: rpc.MessageSequence) => {
  323. hilog.info(0x0000, 'Heanup2', `Widget call: prevSong received`);
  324. const params: Record<string, Object> = JSON.parse(data.readString()) as Record<string, Object>;
  325. hilog.info(0x0000, 'Heanup2', `Widget prevSong params: ${JSON.stringify(params)}`);
  326. // 发送上一首事件到主应用
  327. this.sendWidgetControlEvent('PREV_SONG', params);
  328. return new MyParcelable(3, 'prevSong_success');
  329. });
  330. // 监听打开应用事件
  331. this.callee.on('openApp', (data: rpc.MessageSequence) => {
  332. hilog.info(0x0000, 'Heanup2', `Widget call: openApp received`);
  333. const params: Record<string, Object> = JSON.parse(data.readString()) as Record<string, Object>;
  334. hilog.info(0x0000, 'Heanup2', `Widget openApp params: ${JSON.stringify(params)}`);
  335. // 发送打开应用事件到主应用
  336. this.sendWidgetControlEvent('OPEN_APP', params);
  337. return new MyParcelable(4, 'openApp_success');
  338. });
  339. hilog.info(0x0000, 'Heanup2', 'Widget call listeners registered successfully');
  340. } catch (err) {
  341. hilog.error(0x0000, 'Heanup2', `Failed to register widget call listeners: ${JSON.stringify(err as BusinessError)}`);
  342. }
  343. }
  344. /**
  345. * 注销卡片call事件监听器
  346. */
  347. private unregisterWidgetCallListeners(): void {
  348. try {
  349. this.callee.off('playPause');
  350. this.callee.off('nextSong');
  351. this.callee.off('prevSong');
  352. // this.callee.off('openApp');
  353. // this.callee.off('playByAction');
  354. // this.callee.off('collectAction');
  355. // this.callee.off('requestUpdatePlayCard');
  356. hilog.info(0x0000, 'Heanup2', 'Widget call listeners unregistered successfully');
  357. } catch (err) {
  358. hilog.error(0x0000, 'Heanup2', `Failed to unregister widget call listeners: ${JSON.stringify(err as BusinessError)}`);
  359. }
  360. }
  361. /**
  362. * 发送卡片控制事件到主应用
  363. */
  364. private async sendWidgetControlEvent(command: string, params: Record<string, Object>): Promise<void> {
  365. try {
  366. hilog.info(0x0000, 'Heanup2', `🎵 Processing widget control command: ${command}`);
  367. // 获取UnifiedPlayerService实例
  368. const unifiedService = UnifiedPlayerService.getInstance();
  369. // 确保服务已经初始化
  370. if (!unifiedService) {
  371. hilog.error(0x0000, 'Heanup2', '❌ UnifiedPlayerService not available');
  372. return;
  373. }
  374. // 在处理卡片控制前,确保服务完全初始化
  375. try {
  376. await unifiedService.initialize(this.context);
  377. hilog.info(0x0000, 'Heanup2', '🔄 UnifiedPlayerService re-initialized for widget command');
  378. } catch (error) {
  379. hilog.error(0x0000, 'Heanup2', `❌ Failed to re-initialize UnifiedPlayerService: ${error}`);
  380. }
  381. // 检查初始化状态
  382. const currentState = unifiedService.getCurrentState();
  383. hilog.info(0x0000, 'Heanup2', `🎵 Widget command - Service state: isPlaying=${currentState.isPlaying}, currentIndex=${currentState.currentIndex}`);
  384. // 根据命令执行相应的播放控制
  385. switch (command) {
  386. case 'PLAY_PAUSE':
  387. if (currentState.isPlaying) {
  388. unifiedService.pause();
  389. hilog.info(0x0000, 'Heanup2', '✅ Widget command: Paused playback');
  390. } else {
  391. unifiedService.startPlayOrResumePlay();
  392. hilog.info(0x0000, 'Heanup2', '✅ Widget command: Started/resumed playback');
  393. }
  394. break;
  395. case 'NEXT_SONG':
  396. await unifiedService.playNext();
  397. hilog.info(0x0000, 'Heanup2', '✅ Widget command: Next song');
  398. break;
  399. case 'PREV_SONG':
  400. await unifiedService.playPrevious();
  401. hilog.info(0x0000, 'Heanup2', '✅ Widget command: Previous song');
  402. break;
  403. case 'OPEN_APP':
  404. hilog.info(0x0000, 'Heanup2', '✅ Widget command: Open app (handled by UI)');
  405. break;
  406. default:
  407. hilog.warn(0x0000, 'Heanup2', `❓ Unknown widget command: ${command}`);
  408. break;
  409. }
  410. hilog.info(0x0000, 'Heanup2', `✅ Widget control command processed: ${command}`);
  411. } catch (error) {
  412. hilog.error(0x0000, 'Heanup2', `❌ Failed to process widget control command: ${error}`);
  413. }
  414. }
  415. /**
  416. * 异步执行卡片注册修复
  417. */
  418. private async fixWidgetRegistrationAsync(): Promise<void> {
  419. // 延迟3秒执行,确保应用完全启动
  420. setTimeout(async () => {
  421. try {
  422. const widgetFix = WidgetRegistrationFix.getInstance();
  423. await widgetFix.fixWidgetRegistration(this.context);
  424. } catch (error) {
  425. hilog.error(0x0000, 'Heanup2', `❌ 卡片注册修复失败: ${error}`);
  426. }
  427. }, 3000);
  428. }
  429. }