1/* 2 * Copyright (c) 2021-2023 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16/** 17 * [系统全局globalThis挂载的对象类型定义] 18 */ 19import window from '@ohos.window' 20import display from '@ohos.display' 21import deviceInfo from '@ohos.deviceInfo' 22import type context from '@ohos.app.ability.common' 23import i18n from '@ohos.i18n' 24import pointer from '@ohos.multimodalInput.pointer'; 25import type { BusinessError } from '@ohos.base' 26 27import { Logger } from '../../util/HiLogger' 28import { appStorageKeys, setOrCreateAppStorage } from '../AppStorageHelper' 29import { createOrGet, globalKeys } from '../GlobalThisHelper' 30import { Util } from '../../util/Util' 31 32const logger: Logger = new Logger("GlobalModel") 33// PC的头部间距,16 / 160 34const PC_TOP_PADDING: number = 0.1 35// 分屏比例 36const SPLIT_RATIO_MINI = 5 / 12 37const SPLIT_RATIO_MAX = 7 / 12 38// 白色字符串 39const WHITE: string = "#ffffffff" 40// 黑色字符串 41const BLACK: string = "#ff000000" 42// 透明 43const TRANS: string = "#00ffffff" 44 45/** 46 * 窗口对象 47 */ 48export class AppScreen { 49 // abilityContext 50 context: context.UIAbilityContext 51 // stage模型的窗口对象 52 windowStage: window.WindowStage 53 // 屏幕宽度 54 width: number = 0 55 // 屏幕高度 56 height: number = 0 57 // 窗口x坐标 58 windowPosX: number = 0 59 // 窗口y坐标 60 windowPosY: number = 0 61 // 窗口宽度 62 windowWidth: number = 0 63 // 窗口高度 64 windowHeight: number = 0 65 // 状态栏高度 66 topHeight: number = 0 67 // 导航栏高度 68 bottomHeight: number = 0 69 // 屏幕密度类型,用来计算lpx真实物理大小 70 densityType: DensityTypes = DensityTypes.SM 71 // 窗口配置变化监听 72 configChangeListener: Array<(densityType: DensityTypes) => void> = [] 73 // 接收窗口变化后一系列配置完成后的promise 74 lastWindowSizeResult: Promise<boolean> = Promise.resolve(true) 75 // 横竖屏模式设定 76 orientationSetting: window.Orientation = window.Orientation?.AUTO_ROTATION_LANDSCAPE_RESTRICTED 77 // 当前是横屏1还是竖屏0 78 orientation: number = 0 79 // 是否已经设置过沉浸式 80 isImmersiveSet: boolean = false 81 // 应用启动后是否设置过densityType 82 isDensityTypeSet: boolean = false 83 // DPI mate40pro 560 84 densityDPI: number = 0 85 // 是否全屏 86 isFullScreen: boolean = false 87 // 沉浸式,可以显示状态栏文字 88 isLayoutFullScreen: boolean = false 89 // 沉浸式,不显示状态栏文字 90 isFullScreenWindow: boolean = false 91 // 天枢wgr是否分屏, 高度为设备高度,且宽度小于设备宽度 92 isPCMultiWindow: boolean = false 93 // 状态栏背景是否是深色 94 isTopBgDark: boolean = false 95 // 状态栏背景颜色 96 statusBarBgColor: string = "" 97 // 导航栏背景是否是深色 98 isBottomBgDark: boolean = false 99 // 屏幕是否常亮 100 isKeepScreenOn: boolean = false 101 // 判断当前设备是否可折叠 102 isFoldScreen: boolean = false 103 // 获取当前屏幕折叠状态 展开态: 1 折叠态: 2 半折态: 3 104 displayStatus: number = 1 105 // 获取当前屏幕显示模式0:折叠状态未知1:折叠状态为完全展开2:折叠状态为折叠3:折叠状态为半折叠。半折叠指完全展开和折叠之间的状态。 106 displayMode: number = 0 107 // 分屏比例 108 appSplitRatio: AppSplitRatios = AppSplitRatios.NO 109 // 当前窗口模式 110 windowStatusType: window.WindowStatusType = window.WindowStatusType.UNDEFINED 111 // 应用信息 112 app: MusicApp 113 114 // 构造函数 115 constructor(context: context.UIAbilityContext, windowStage: window.WindowStage) { 116 logger.info('new Screen') 117 this.context = context 118 this.windowStage = windowStage 119 this.app = createOrGet(MusicApp, globalKeys.app) 120 this.init() 121 } 122 123 /** 124 * 初始化 125 */ 126 async init(): Promise<void> { 127 this.enterImmersion() 128 this.getDisplayStatus() 129 this.bindWindowSizeListener() 130 this.bindFoldStatusChange() 131 this.bindFoldDisplayModeChange() 132 this.setOrientationSetting() 133 this.lastWindowSizeResult = this.windowResize() 134 this.bindWindowEventListener() 135 } 136 137 /** 138 * 监听应用窗口焦点变化 139 */ 140 bindWindowEventListener(): void { 141 window.getLastWindow(this.context).then((windowClass) => { 142 logger.info('Succeeded in obtaining the top window. Data: ' + windowClass); 143 if (!windowClass) { 144 return 145 } 146 try { 147 windowClass.on('windowEvent', (value) => { 148 if (value === window.WindowEventType.WINDOW_ACTIVE) { 149 logger.info('Window event happened. Event:' + value); 150 this.app?.setActiveNo() 151 } 152 }); 153 } catch (err) { 154 logger.error('Failed to register callback. Cause: ' + JSON.stringify(err)); 155 } 156 }).catch((err: BusinessError) => { 157 logger.error('Failed to obtain the top window. Cause: ' + JSON.stringify(err)); 158 }); 159 } 160 161 getDisplayStatus(): void { 162 this.isFoldScreen = display.isFoldable(); 163 164 this.displayStatus = display.getFoldStatus(); 165 166 this.displayMode = display.getFoldDisplayMode(); 167 } 168 169 setOrientationSetting(): void { 170 let orientation: window.Orientation = window.Orientation.AUTO_ROTATION_RESTRICTED 171 let globalDeviceInfo: DeviceInfo = createOrGet(DeviceInfo, globalKeys.deviceInfo) 172 let isPhone: boolean = globalDeviceInfo.deviceType === DeviceTypes.PHONE 173 logger.info('this.isFoldScreen' + this.isFoldScreen + 'this.displayMode' + this.displayMode) 174 if (this.isFoldScreen) { 175 if (this.displayMode === 1) { 176 logger.info('this.isFoldScreen' + this.isFoldScreen + 'this.displayMode' + this.displayMode) 177 orientation = window.Orientation.AUTO_ROTATION_RESTRICTED 178 } else if (this.displayMode === 2) { 179 logger.info('this.isFoldScreen' + this.isFoldScreen + 'this.displayMode' + this.displayMode) 180 orientation = window.Orientation.PORTRAIT 181 } else if (this.displayMode === 3 || this.displayMode === 0) { 182 return 183 } 184 } else if (isPhone) { 185 // 手机横屏MD修改判断设备类型 186 orientation = window.Orientation.PORTRAIT 187 } else { 188 orientation = window.Orientation.AUTO_ROTATION_RESTRICTED 189 } 190 this.setOrientation(orientation) 191 } 192 193 /** 194 * 销毁 195 */ 196 destroy(): void { 197 this.unbindWindowSizeListener() 198 this.unbindFoldDisplayModeChange() 199 this.unbindFoldStatusChange() 200 this.unbindWindowEventListener() 201 } 202 203 /** 204 * 取消监听应用窗口焦点变化 205 */ 206 unbindWindowEventListener(): void { 207 // todo: onDestroy的时候窗口已经销毁了,目前没有合适的元能力生命周期用于解注册监听,窗口已知的一个现象,后续解决再放开 208 // try { 209 // windowClass.off('windowEvent'); 210 // } catch (exception) { 211 // logger.error('Failed to unregister callback. Cause: ' + JSON.stringify(exception)); 212 // } 213 } 214 215 /** 216 * 绑定窗口变化监听 217 */ 218 async bindWindowSizeListener(): Promise<void> { 219 let w = await this.windowStage.getMainWindow() 220 if (!w) { 221 return 222 } 223 // 绑定窗口变化监听 224 w.on('windowSizeChange', (data: window.Size) => { 225 logger.info('windowSizeChange' + JSON.stringify(data)) 226 this.windowResizeHapenned(data) 227 }) 228 w.on('avoidAreaChange', (data) => { 229 if (data.type === window.AvoidAreaType.TYPE_SYSTEM) { 230 logger.info('avoidAreaChange' + JSON.stringify(data.area)) 231 let isPC = isWideService() 232 this.topHeight = data.area.topRect.height 233 234 this.bottomHeight = data.area.bottomRect.height 235 if (isPC && this.topHeight === 0) { 236 this.topHeight = PC_TOP_PADDING * this.densityDPI 237 } 238 setOrCreateAppStorage<number>(appStorageKeys.statusBarHeight, this.topHeight) 239 setOrCreateAppStorage<number>(appStorageKeys.navigatorBarHeight, this.bottomHeight) 240 241 } 242 }) 243 // 绑定窗口模式变化监听 244 w.on('windowStatusChange', (windowStatusChange: window.WindowStatusType) => { 245 logger.info('windowStatusChange: ' + JSON.stringify(windowStatusChange)) 246 this.windowStatusType = windowStatusChange 247 if (windowStatusChange === window.WindowStatusType.MINIMIZE) { 248 return 249 } 250 if (windowStatusChange === window.WindowStatusType.SPLIT_SCREEN) { 251 this.setSplitRatio() 252 } else { 253 this.setAppSplitRatio(AppSplitRatios.NO) 254 } 255 }) 256 } 257 258 /** 259 * 注销窗口变化监听 260 */ 261 async unbindWindowSizeListener(): Promise<void> { 262 let w = await this.windowStage.getMainWindow() 263 if (!w) { 264 return 265 } 266 // 和zhengjiangliang OFF不传callback整体注销 267 await w.off('windowSizeChange') 268 await w.off('avoidAreaChange') 269 await w.off('windowStatusChange') 270 } 271 272 /** 273 * 绑定折叠屏折叠状态变化监听 274 */ 275 bindFoldStatusChange(): void { 276 277 try { 278 display.on('foldStatusChange', (foldStatus: display.FoldStatus) => { 279 this.displayStatus = foldStatus; 280 logger.info('displayMode' + foldStatus) 281 }); 282 } catch (exception) { 283 logger.error('Failed code' + JSON.stringify(exception)); 284 } 285 } 286 287 /** 288 * 注销绑定折叠屏折叠状态变化监听 289 */ 290 unbindFoldStatusChange(): void { 291 display.off('foldStatusChange') 292 } 293 294 /** 295 * 绑定折叠屏折叠模式变化监听 296 */ 297 bindFoldDisplayModeChange(): void { 298 try { 299 display.on('foldDisplayModeChange', (foldDisplayMode: display.FoldDisplayMode) => { 300 logger.info('displayMode' + foldDisplayMode + this.displayMode + this.orientationSetting) 301 this.displayMode = foldDisplayMode; 302 this.setOrientationSetting() 303 }); 304 } catch (exception) { 305 logger.error('Failed code' + JSON.stringify(exception)); 306 } 307 } 308 309 unbindFoldDisplayModeChange(): void { 310 display.off('foldDisplayModeChange') 311 } 312 313 /** 314 * 设置全局ability上下文 315 */ 316 setContext(context: context.UIAbilityContext): void { 317 this.context = context 318 } 319 320 /** 321 * 获取屏幕大小 322 */ 323 async getScreenSize(): Promise<boolean> { 324 logger.info('windows getScreenSize') 325 let p1 = display.getDefaultDisplay().then((disp) => { 326 logger.info('getDefaultDisplay:' + JSON.stringify(disp)); 327 this.width = disp.width 328 this.height = disp.height 329 this.densityDPI = disp.densityDPI 330 }) 331 let w = await this.windowStage.getMainWindow() 332 if (!w) { 333 return new Promise((resolve) => { 334 p1.then(() => { 335 resolve(true) 336 }) 337 }) 338 } 339 let p2 = w.getProperties().then((prop: window.WindowProperties) => { 340 logger.info('getProperties:' + JSON.stringify(prop)); 341 this.windowWidth = prop.windowRect.width 342 this.windowHeight = prop.windowRect.height 343 this.windowPosX = prop.windowRect.left 344 this.windowPosY = prop.windowRect.top 345 this.isLayoutFullScreen = prop.isLayoutFullScreen || false 346 this.isFullScreenWindow = prop.isFullScreen 347 }) 348 349 let isPC = isWideService() 350 return isPC ? new Promise((resolve) => { 351 Promise.all([p1, p2]).then(() => { 352 // PC模式下,有可能设置为手机全屏,此时模式如下判断,需要设置窗口高度减去topHeight 353 if (!this.isFullScreen && this.windowWidth === this.width && this.windowHeight === this.height && this.isLayoutFullScreen && !this.isFullScreenWindow) { 354 logger.info('pc topHeight:' + this.topHeight); 355 this.windowHeight -= this.topHeight 356 logger.info('pc windowHeight:' + this.windowHeight); 357 } 358 this.isPCMultiWindow = this.windowHeight === this.height && this.windowWidth !== this.width 359 this.topHeight = PC_TOP_PADDING * this.densityDPI 360 setOrCreateAppStorage<number>(appStorageKeys.statusBarHeight, this.topHeight) 361 if (!this.isLayoutFullScreen && !this.isPCMultiWindow) { 362 // 天枢窗口非全屏模式非分屏模式,顶部38vp,底部和左右各5vp 363 this.windowWidth = this.windowWidth - 10 * this.densityDPI / 160 364 this.windowHeight = this.windowHeight - 43 * this.densityDPI / 160 365 } 366 this.setSplitRatio() 367 resolve(true) 368 }) 369 }) : Promise.all([p1, p2]).then(() => { 370 this.setSplitRatio() 371 return true 372 }).catch(() => { 373 return false 374 }) 375 } 376 377 /** 378 * 设置全屏和状态栏颜色 379 */ 380 async enterImmersion(): Promise<void> { 381 logger.info('windows enterImmersion') 382 // 手机默认设置全屏,平板PC默认设置不全屏。 如果全屏模式发生切换,则置设置沉浸模式标记位为否,重新进入设置 383 // 除PC之外,其他默认设置全屏 384 let isFullScreen = !isWideService() 385 this.isImmersiveSet && (this.isImmersiveSet = isFullScreen === this.isFullScreen) 386 this.isFullScreen = isFullScreen 387 if (this.isImmersiveSet) { 388 return 389 } 390 this.isImmersiveSet = true 391 let w = await this.windowStage.getMainWindow() 392 if (!w) { 393 return 394 } 395 if (this.isFullScreen) { 396 logger.info('this.isFullScreen' + this.isFullScreen) 397 // await w.setFullScreen(this.isFullScreen) 398 await w.setWindowLayoutFullScreen(this.isFullScreen) 399 } 400 // await w.setSystemBarEnable(["status", "navigation"]) 401 this.setSystemBarColor(this.isTopBgDark, this.isBottomBgDark, w) 402 logger.info('windows enterImmersion finish') 403 } 404 405 /** 406 * 设置横竖屏模式 407 * 408 * @param isTopBgDark 顶部背景是否深色 409 * @param isBottomBgDark 底部背景是否深色 410 * @param w topWindow 411 */ 412 setSystemBarColor(isTopBgDark: boolean = false, isBottomBgDark: boolean = false, w?: window.Window,): void { 413 this.isTopBgDark = isTopBgDark 414 this.setSystemBarColorWithColor(isTopBgDark ? WHITE : BLACK, isBottomBgDark, w) 415 } 416 417 /** 418 * 设置系统bar颜色 419 * 420 * @param isTopBgDark 顶部背景是否深色 421 * @param isBottomBgDark 底部背景是否深色 422 * @param w topWindow 423 */ 424 setSystemBarColorWithColor(statusBarBgColor: string, isBottomBgDark: boolean = false, w?: window.Window): void { 425 if (Util.isEmpty(statusBarBgColor)) { 426 logger.error("setSystemBarColorWithColor :: statusBarBgColor is Empty") 427 return 428 } 429 430 if (statusBarBgColor === this.statusBarBgColor && isBottomBgDark === this.isBottomBgDark) { 431 return 432 } 433 if (!w) { 434 try { 435 w = this.windowStage.getMainWindowSync() 436 } catch (error) { 437 logger.info(`setSystemStatusBarColor, getMainWindowSync error: ${JSON.stringify(error, [`code`, `message`])}`); 438 } 439 if (!w) { 440 return 441 } 442 } 443 444 try { 445 this.statusBarBgColor = statusBarBgColor 446 this.isBottomBgDark = isBottomBgDark 447 w.setWindowSystemBarProperties({ 448 navigationBarColor: TRANS, 449 statusBarColor: TRANS, 450 navigationBarContentColor: isBottomBgDark ? WHITE : BLACK, 451 // todo - 如果是深色模式,只需此处逻辑改为 statusBarContentColor: isDark ? ColorUtil.WHITE : statusBarBgColor 452 statusBarContentColor: statusBarBgColor 453 }, null) 454 } catch (error) { 455 logger.error(`setSystemBarColor, setWindowSystemBarProperties error: ${JSON.stringify(error, [`code`, `message`])}`); 456 } 457 } 458 459 /** 460 * 设置横竖屏模式 461 * 462 * @param orientation 横竖屏模式 463 */ 464 async setOrientation(orientation: window.Orientation = window.Orientation.AUTO_ROTATION_RESTRICTED): Promise<void> { 465 let w = await this.windowStage.getMainWindow() 466 if (!w) { 467 return 468 } 469 logger.info('this.densityType' + this.densityType + 'this.isFoldScreen' + this.isFoldScreen + 'this.displayMode' + this.displayMode) 470 logger.info('windowResize densityType' + this.orientationSetting + 'orientation' + orientation) 471 if (this.orientationSetting === orientation) { 472 return 473 } 474 this.orientationSetting = orientation 475 logger.info('windowResize densityType350' + this.orientationSetting + 'orientation' + orientation) 476 await w.setPreferredOrientation(orientation) 477 } 478 479 /** 480 * 获取状态栏和导航栏高度 481 */ 482 async getAvoidArea(): Promise<void> { 483 logger.info('windows getAvoidArea') 484 let w = await this.windowStage.getMainWindow() 485 if (!w) { 486 return 487 } 488 let area = await w.getAvoidArea(window.AvoidAreaType.TYPE_SYSTEM) 489 logger.info('windows getAvoidArea' + JSON.stringify(area)) 490 let isPC = isWideService() 491 // PC模式下,如果头部没有空隙,留一定空隙 492 if (isPC && area.topRect.height === 0) { 493 this.topHeight = PC_TOP_PADDING * this.densityDPI 494 // fix systemUI bug 每次下拉系统通知栏,状态栏高度会为0 需要屏蔽掉 495 } else if (area.topRect.height !== 0) { 496 this.topHeight = area.topRect.height 497 } 498 this.bottomHeight = area?.bottomRect?.height || 0 499 logger.info('windows getAvoidArea finish') 500 } 501 502 /** 503 * 窗口变化发生 504 * 505 * @param data 窗口数据 506 */ 507 windowResizeHapenned(data: window.Size): void { 508 logger.info('Succeeded in enabling the listener for window size changes. Data: ' + JSON.stringify(data)); 509 this.lastWindowSizeResult = this.windowResize() 510 } 511 512 /** 513 * 处理窗口变化修改成员变量属性值 514 * 515 * @return Promise<boolean> 窗口变化结果,成功失败 516 */ 517 windowResize(): Promise<boolean> { 518 return new Promise(async (resolve, reject) => { 519 // todo 当前设置全屏后会触发resize,然后又走到这个方法,所以设置全屏和获取屏幕属性方法的时序不需要控制。反正resize之后还有重新获取屏幕属性 520 let p2 = this.getAvoidArea() 521 let p3 = this.getScreenSize() 522 Promise.all([p2, p3]).then(() => { 523 this.windowResizeDetail() 524 resolve(true) 525 }).catch(() => { 526 resolve(false) 527 }) 528 }) 529 } 530 531 /** 532 * 处理窗口变化修改成员变量属性值具体方法 533 */ 534 windowResizeDetail: () => void = () => { 535 if (this.densityDPI === 0) { 536 logger.info('densityDPI zero') 537 return 538 } 539 // 说明计算公式pixels = dips * (density / 160) 540 let vpWidth = this.windowWidth / (this.densityDPI / 160) 541 let densityType: DensityTypes = DensityTypes.SM 542 if (vpWidth < 320) { 543 densityType = DensityTypes.XS 544 } else if (vpWidth < 600) { 545 densityType = DensityTypes.SM 546 } else if (vpWidth < 840) { 547 densityType = DensityTypes.MD 548 } else { 549 densityType = DensityTypes.LG 550 } 551 logger.info('windowResize densityType' + densityType + 'vpWidth' + vpWidth) 552 this.setDensityType(densityType) 553 this.setOrientationSetting() 554 } 555 /** 556 * 计算分屏模式下比例 557 * 558 * @param listener 窗口变化监听器 559 */ 560 setSplitRatio: () => void = (): void => { 561 let appSplitRatio: AppSplitRatios 562 if (this.windowStatusType !== window.WindowStatusType.SPLIT_SCREEN) { 563 // 应用宽高与窗口宽高未分屏 564 logger.info('windowResize appSplitRatio none') 565 appSplitRatio = AppSplitRatios.NO 566 return 567 } else if (this.windowWidth === this.width) { 568 // 应用宽度 = 窗口宽度:上下分屏,通过计算 应用高度与窗口高度比值 判断 音乐分屏比例 569 if (this.windowHeight <= this.height * SPLIT_RATIO_MINI) { 570 appSplitRatio = AppSplitRatios.PORTRAIT_S 571 } else if (this.windowHeight > this.height * SPLIT_RATIO_MAX) { 572 appSplitRatio = AppSplitRatios.PORTRAIT_L 573 } else { 574 appSplitRatio = AppSplitRatios.PORTRAIT_M 575 } 576 } else if (this.windowHeight === this.height) { 577 // 应用高度 = 窗口高度:左右分屏,通过计算 应用宽度与窗口宽度比值 判断 音乐分屏比例 578 if (this.windowWidth <= this.width * SPLIT_RATIO_MINI) { 579 appSplitRatio = AppSplitRatios.LANDSCAPE_S 580 } else if (this.windowWidth > this.width * SPLIT_RATIO_MAX) { 581 appSplitRatio = AppSplitRatios.LANDSCAPE_L 582 } else { 583 appSplitRatio = AppSplitRatios.LANDSCAPE_M 584 } 585 } else { 586 appSplitRatio = AppSplitRatios.NO 587 } 588 logger.info(`windowResize appSplitRatio ${appSplitRatio} ${this.windowWidth} ${this.width} ${this.windowHeight} ${this.height}`) 589 this.setAppSplitRatio(appSplitRatio) 590 } 591 /** 592 * 设置屏幕密度类型 593 * 594 * @param type 屏幕密度类型 595 */ 596 setDensityType: (type: DensityTypes) => void = (type: DensityTypes): void => { 597 this.densityType = type 598 let windowArea: WindowArea = { windowWidth: this.windowWidth, windowHeight: this.windowHeight } 599 setOrCreateAppStorage<DensityTypes>(appStorageKeys.densityType, type) 600 setOrCreateAppStorage<number>(appStorageKeys.screenWidth, this.width) 601 setOrCreateAppStorage<number>(appStorageKeys.screenHeight, this.height) 602 setOrCreateAppStorage<number>(appStorageKeys.windowWidth, this.windowWidth) 603 setOrCreateAppStorage<number>(appStorageKeys.windowHeight, this.windowHeight) 604 // 保证横竖屏切换两个同时更新 605 setOrCreateAppStorage<WindowArea>(appStorageKeys.windowArea, windowArea) 606 setOrCreateAppStorage<number>(appStorageKeys.statusBarHeight, this.topHeight) 607 setOrCreateAppStorage<number>(appStorageKeys.navigatorBarHeight, this.bottomHeight) 608 if (this.configChangeListener.length > 0) { 609 this.configChangeListener.forEach((listener) => { 610 try { 611 listener(type) 612 } catch (e) { 613 logger.info('setDensityType forEach error = ' + e); 614 } 615 }) 616 } 617 } 618 /** 619 * 设置分屏比例 620 * 621 * @param ratio 分屏比例 622 */ 623 setAppSplitRatio: (ratio: AppSplitRatios) => void = (ratio: AppSplitRatios): void => { 624 this.appSplitRatio = ratio 625 setOrCreateAppStorage<AppSplitRatios>(appStorageKeys.appSplitRatio, ratio) 626 } 627 628 /** 629 * 设置鼠标形状 630 * @param pointerStyle 鼠标形状 631 */ 632 async setPointerStyle(pointerTypes: pointer.PointerStyle): Promise<void> { 633 let w = await this.windowStage.getMainWindow() 634 w.getProperties().then((prop: window.WindowProperties) => { 635 let windowId = prop.id; 636 if (windowId < 0) { 637 logger.info(`Invalid windowId`); 638 return; 639 } 640 try { 641 pointer.setPointerStyle(windowId, pointerTypes).then(() => { 642 logger.info(`Set pointer style success`); 643 }); 644 } catch (error) { 645 logger.info(`Set pointer style failed, error: ${JSON.stringify(error, [`code`, `message`])}`); 646 } 647 }) 648 } 649 650 /** 651 * 设置/取消屏幕常亮 652 * @param screenOn 653 */ 654 async setIsKeepScreenOn(screenOn: boolean): Promise<void> { 655 logger.info('setIsKeepScreenOn ' + screenOn + ' this.isKeepScreenOn ' + this.isKeepScreenOn) 656 if (screenOn === this.isKeepScreenOn) { 657 return 658 } 659 660 let w = await this.windowStage.getMainWindow() 661 if (!w) { 662 logger.info('setIsKeepScreenOn return') 663 return 664 } 665 try { 666 w.setWindowKeepScreenOn(screenOn, (err) => { 667 if (err.code) { 668 logger.error('Failed to set the screen to be always on. Cause: ' + JSON.stringify(err)); 669 return; 670 } 671 this.isKeepScreenOn = screenOn 672 logger.info('Succeeded in setting the screen to be always on.' + screenOn); 673 }); 674 } catch (exception) { 675 logger.error('Error to set the screen to be always on. Cause: ' + JSON.stringify(exception)); 676 } 677 } 678} 679 680export function isWideService(): boolean { 681 const globalDeviceInfo: DeviceInfo = createOrGet(DeviceInfo, globalKeys.deviceInfo) 682 // return globalDeviceInfo.deviceType === DeviceTypes.TABLET || globalDeviceInfo.deviceType === DeviceTypes.PC 683 return globalDeviceInfo.deviceType === DeviceTypes.PC 684} 685 686/** 687 * 设备类型 688 */ 689export enum DeviceTypes { 690 PHONE = 0, 691 TABLET = 1, 692 PC = 2 693} 694 695 696/** 697 * 渠道类型 698 */ 699export enum EnvironmentType { 700 // 联调环境 701 DEV = 0, 702 // 镜像环境 703 MIRROR = 1, 704 // 现网环境 705 SECURITY = 3 706} 707 708/** 709 * 屏幕密度类型 710 */ 711export enum DensityTypes { 712 // 手表 713 XS = 0, 714 // 手机竖屏和折叠屏不展开 715 SM = 1, 716 // 折叠屏展开和pad竖屏 717 MD = 2, 718 // pad横屏 719 LG = 3 720} 721 722/** 723 * 分屏模式下音乐与其他应用所占屏幕比例 724 */ 725export enum AppSplitRatios { 726 // 不分屏 727 NO = 0, 728 // 上下分屏 音乐与其他应用屏幕占比 <= 1:2 729 PORTRAIT_S = 1, 730 // 上下分屏 音乐与其他应用屏幕占比 1:2 ~ 2:1 731 PORTRAIT_M = 2, 732 // 上下分屏 音乐与其他应用屏幕占比 >= 2:1 733 PORTRAIT_L = 3, 734 // 左右分屏 音乐与其他应用屏幕占比 <= 1:2 735 LANDSCAPE_S = 4, 736 // 左右分屏 音乐与其他应用屏幕占比 1:2 ~ 2:1 737 LANDSCAPE_M = 5, 738 // 左右分屏 音乐与其他应用屏幕占比 >= 2:1 739 LANDSCAPE_L = 6, 740} 741 742 743/** 744 * 设备信息 745 */ 746export class DeviceInfo { 747 // 设备类型 748 deviceType: number = DeviceTypes.PHONE 749 /** 750 * Obtains the product model represented by a string. 751 * 752 * @syscap SystemCapability.Startup.SystemInfo 753 * @since 6 754 */ 755 productModel: string 756 /** 757 * Obtains the OS version represented by a string. 758 * 759 * @syscap SystemCapability.Startup.SystemInfo 760 * @since 6 761 */ 762 osFullName: string 763 /** 764 * Obtains the SDK API version number. 765 * 766 * @syscap SystemCapability.Startup.SystemInfo 767 * @since 6 768 */ 769 sdkApiVersion: number 770 /** 771 * Obtains the device udid. 772 * 773 * @syscap SystemCapability.Startup.SystemInfo 774 * @since 7 775 */ 776 udid: string 777 /** 778 * Obtains the device manufacturer represented by a string. 779 * 780 * @syscap SystemCapability.Startup.SystemInfo 781 * @since 6 782 */ 783 manufacture: string 784 /** 785 * Obtains the device brand represented by a string. 786 * 787 * @syscap SystemCapability.Startup.SystemInfo 788 * @since 6 789 */ 790 brand: string 791 /** 792 * User-Agent 793 * 794 */ 795 ua: string = '' 796 /** 797 * Obtains the major (M) version number, which increases with any updates to the overall architecture. 798 * <p>The M version number monotonically increases from 1 to 99. 799 * 800 * @syscap SystemCapability.Startup.SystemInfo 801 * @since 6 802 */ 803 majorVersion: number 804 // 系统语言 805 lang: string = MusicApp.DEFAULT_LANG 806 /** 807 * 获取CPU的核数 todo 808 * 809 * @return CPU的核数,异常情况下CPU核数为-1 810 */ 811 cpucores: string = '8' 812 memory: string = '8.0G' 813 814 constructor() { 815 // * which can be {@code phone} (or {@code default} for phones), {@code wearable}, {@code liteWearable}, 816 // * {@code tablet}, {@code tv}, {@code car}, or {@code smartVision}. 817 switch (deviceInfo.deviceType) { 818 case 'phone': 819 this.deviceType = DeviceTypes.PHONE 820 break; 821 case 'default': 822 this.deviceType = DeviceTypes.PHONE 823 break; 824 case 'tablet': 825 this.deviceType = DeviceTypes.TABLET 826 break; 827 case '2in1': 828 this.deviceType = DeviceTypes.PC 829 break; 830 case 'pc': 831 this.deviceType = DeviceTypes.PC 832 break; 833 default: 834 this.deviceType = DeviceTypes.PHONE 835 break; 836 } 837 this.osFullName = deviceInfo.osFullName 838 this.sdkApiVersion = deviceInfo.sdkApiVersion 839 // this.udid = deviceInfo.udid 权限不满足获取不到 840 this.udid = '' 841 this.manufacture = deviceInfo.manufacture 842 this.majorVersion = deviceInfo.majorVersion 843 this.brand = (deviceInfo.brand).toUpperCase() 844 this.productModel = deviceInfo.productModel 845 display.getDefaultDisplay().then((disp) => { 846 logger.info('getDefaultDisplay in device:' + JSON.stringify(disp)); 847 this.ua = `model=${this.productModel},brand=${this.brand},rom=${this.osFullName},emui=${this.osFullName},os=${this.majorVersion},apilevel=${this.sdkApiVersion},manufacturer=${this.brand},useBrandCust=0,extChannel=,cpucore=8,memory=8.0G,srceenHeight=${disp.width},screenWidth=${disp.height},harmonyApiLevel=${this.sdkApiVersion},huaweiOsBrand=harmony` 848 logger.debug('deviceType ua:' + this.ua) 849 }) 850 // todo 需要确认udid是否可以在同意协议之前获取。 851 logger.debug('deviceType: ' + deviceInfo.deviceType + ';osFullName: ' + deviceInfo.osFullName + ';sdkApiVersion: ' + deviceInfo.sdkApiVersion + ';udid: ' + (deviceInfo.udid !== undefined) + ';manufacture: ' + deviceInfo.manufacture + ';brand: ' + deviceInfo.brand + ';hardwareProfile:' + deviceInfo.hardwareProfile) 852 this.getLang() 853 } 854 855 /** 856 * 获取系统语言 857 * 858 * @return 语言是否发生切换 859 */ 860 getLang(): boolean { 861 let lang = i18n.getSystemLocale() 862 let langRes = '' 863 // en-Latn-US 去除Latn 864 if (lang) { 865 let langArray = lang.split('-') 866 if (langArray.length === 3) { 867 langRes = langArray[0] + '-' + langArray[2] 868 } else { 869 langRes = lang 870 } 871 } else { 872 langRes = MusicApp.DEFAULT_LANG 873 } 874 let res = langRes !== this.lang 875 this.lang = langRes 876 // 语言发生变化时更新全局变量 877 if (res) { 878 setOrCreateAppStorage<string>(appStorageKeys.lang, this.lang) 879 } 880 logger.info("i18n getSystemLanguage: " + this.lang) 881 return res 882 } 883} 884 885/** 886 * BEARER_CELLULAR 0 蜂窝网络。 887 * BEARER_WIFI 1 Wi-Fi网络。 888 * BEARER_ETHERNET 3 以太网网络。 889 */ 890enum NetworkType { 891 // 蜂窝网络 892 BEARER_CELLULAR = 0, 893 // Wi-Fi网络 894 BEARER_WIFI = 1, 895 // 以太网网络。 896 BEARER_ETHERNET = 3 897} 898 899/** 900 * 应用信息 901 */ 902export class MusicApp { 903 // 客户端traceId 904 xClientTraceId: string 905 static readonly DEFAULT_LANG: string = 'zh_CN' 906 // abilityStage上下文 907 abilityStageContext: context.AbilityStageContext | undefined = undefined 908 // ability上下文 909 abilityContext: context.UIAbilityContext | null = null 910 // UIExtensionContext上下文 911 UIExtensionContext: context.UIExtensionContext | null = null 912 // 产品类型名称 phone、watch 913 productName: string 914 // 應用是否在前台 915 active: boolean = true 916 activeNo: number = 0 917 activeChangeListeners: Array<(active: boolean) => void> = [] 918 919 constructor(productName: string = 'phone') { 920 this.productName = productName 921 this.xClientTraceId = Util.systemUUid() 922 logger.info("Music app init productName:" + this.productName) 923 } 924 925 /** 926 * 注册應用是否在前台 927 * @param listener listener 928 */ 929 addActiveChangeListener(listener: (active: boolean) => void): void { 930 this.activeChangeListeners.push(listener) 931 } 932 933 /** 934 * 取消注册應用是否在前台 935 * @param listener listener 936 */ 937 removeActiveChangeListener(listener: (active: boolean) => void): void { 938 let idx = this.activeChangeListeners.findIndex((item) => { 939 return item === listener 940 }) 941 if (idx > -1) { 942 this.activeChangeListeners.splice(idx, 1) 943 } 944 } 945 946 /** 947 * 修改應用是否在前台 948 * @param active active 949 */ 950 setActive(active: boolean): void { 951 this.active = active 952 if (this.activeChangeListeners.length) { 953 this.activeChangeListeners.forEach((listener: (active: boolean) => void) => { 954 listener(active) 955 }) 956 } 957 } 958 959 /** 960 * 修改重复退出回到应用次数 961 * @param active active 962 */ 963 setActiveNo(): void { 964 this.activeNo++ 965 logger.info("activeNo " + this.activeNo) 966 } 967} 968 969/** 970 * 宽高同时更新 971 */ 972export class WindowArea { 973 windowWidth: number = 0 974 windowHeight: number = 0 975} 976 977