1/* 2 * Copyright (c) 2022 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 16import prompt from '@ohos.prompt' 17import router from '@ohos.router' 18import { 19 AppItemInfo, 20 CheckEmptyUtils, 21 EventConstants, 22 CommonConstants, 23 GridLayoutItemInfo, 24 FormManager, 25 FormModel, 26 Logger, 27 LauncherAbilityManager, 28 MenuInfo, 29 RdbManager, 30 ResourceManager 31} from '@ohos/base' 32 33const TAG: string = 'LayoutInfoModel' 34const SYSTEM_APPLICATIONS: string = 'com.ohos.launcher,ohos.samples.launcher,com.ohos.systemui,com.ohos.devicemanagerui,com.ohos.callui,com.example.kikakeyboard,com.ohos.contactdataability,com.ohos.telephonydataability,com.ohos.medialibrary.MediaLibraryDataA,com.ohos.medialibrary.MediaScannerAbilityA' 35const KEY_NAME = 'name' 36 37export class DesktopLayoutModel { 38 private static layoutInfoModel: DesktopLayoutModel = undefined 39 private layoutInfo: Array<Array<GridLayoutItemInfo>> = [] 40 private readonly mSystemApplicationName = SYSTEM_APPLICATIONS.split(',') 41 private mLauncherAbilityManager: LauncherAbilityManager = undefined 42 private context: any = undefined 43 44 constructor(context) { 45 this.context = context 46 this.mLauncherAbilityManager = LauncherAbilityManager.getInstance(context) 47 this.mLauncherAbilityManager.registerLauncherAbilityChangeListener(this.appChangeListener) 48 } 49 50 appChangeListener = (event, bundleName: string, userId) => { 51 Logger.info(TAG, `appChangeListener event = ${event},bundle = ${bundleName}`) 52 FormModel.updateAppItemFormInfo(bundleName) 53 if (event === EventConstants.EVENT_PACKAGE_REMOVED) { 54 this.removeItemByBundle(bundleName) 55 } else if (event === EventConstants.EVENT_PACKAGE_ADDED) { 56 this.mLauncherAbilityManager.getAppInfoByBundleName(bundleName).then(appInfo => { 57 Logger.debug(TAG, `appChangeListener EVENT_PACKAGE_ADDED,info = ${JSON.stringify(appInfo)}`) 58 this.addAppToDesktop(appInfo, true) 59 }) 60 } 61 } 62 63 64 /** 65 * Get the application data model object. 66 * 67 * @return {object} application data model singleton 68 */ 69 static getInstance(context): DesktopLayoutModel { 70 if (this.layoutInfoModel == null || this.layoutInfoModel === undefined) { 71 this.layoutInfoModel = new DesktopLayoutModel(context) 72 } 73 return this.layoutInfoModel 74 } 75 76 private removeItemByBundle(bundleName: string) { 77 let page = this.layoutInfo.length 78 for (let i = 0;i < page; i++) { 79 for (let j = 0;j < this.layoutInfo[i].length; j++) { 80 if (this.layoutInfo[i][j].bundleName === bundleName) { 81 this.removeItemFromDeskTop(this.layoutInfo[i][j]) 82 } 83 } 84 } 85 } 86 87 /** 88 * getAppItemFormInfo 89 * 90 * @param bundleName 91 */ 92 getAppItemFormInfo(bundleName: string) { 93 return FormModel.getAppItemFormInfo(bundleName) 94 } 95 96 /** 97 * buildMenuInfoList 98 * 99 * @param appInfo: GridLayoutItemInfo 100 */ 101 buildMenuInfoList(appInfo: GridLayoutItemInfo, dialog: any) { 102 if (CheckEmptyUtils.isEmpty(appInfo)) { 103 return undefined 104 } 105 let menuInfoList = new Array<MenuInfo>() 106 let open = new MenuInfo() 107 open.menuImgSrc = $r('app.media.ic_public_add_norm') 108 open.menuText = $r('app.string.app_menu_open') 109 open.onMenuClick = () => { 110 this.jumpTo(appInfo.abilityName, appInfo.bundleName) 111 } 112 menuInfoList.push(open) 113 114 Logger.info(TAG, `buildMenuInfoList getAppItemFormInfo,bundleName = ${appInfo.bundleName}`) 115 const formInfoList = FormModel.getAppItemFormInfo(appInfo.bundleName) 116 Logger.info(TAG, `buildMenuInfoList formInfoList = ${JSON.stringify(formInfoList)}`) 117 if (!CheckEmptyUtils.isEmptyArr(formInfoList)) { 118 let addFormToDeskTopMenu = new MenuInfo() 119 addFormToDeskTopMenu.menuImgSrc = $r('app.media.ic_public_app') 120 addFormToDeskTopMenu.menuText = $r('app.string.add_form_to_desktop') 121 addFormToDeskTopMenu.onMenuClick = () => { 122 Logger.info(TAG, 'Launcher click menu item into add form to desktop view') 123 if (!CheckEmptyUtils.isEmpty(appInfo)) { 124 AppStorage.SetOrCreate('formAppInfo', appInfo) 125 Logger.info(TAG, 'Launcher AppStorage.SetOrCreate formAppInfo') 126 this.jumpToFormManagerView(appInfo) 127 } 128 } 129 menuInfoList.push(addFormToDeskTopMenu) 130 } 131 132 const uninstallMenu = new MenuInfo() 133 uninstallMenu.menuImgSrc = $r('app.media.ic_public_delete') 134 uninstallMenu.menuText = $r('app.string.uninstall') 135 uninstallMenu.onMenuClick = () => { 136 Logger.info(TAG, 'Launcher click menu item uninstall') 137 if (!CheckEmptyUtils.isEmpty(dialog)) { 138 dialog.open() 139 } 140 } 141 menuInfoList.push(uninstallMenu) 142 return menuInfoList 143 } 144 145 /** 146 * buildCardInfoList 147 * 148 * @param dialog 149 */ 150 buildCardInfoList(dialog: any) { 151 let menuInfoList = new Array<MenuInfo>() 152 const uninstallMenu = new MenuInfo() 153 uninstallMenu.menuImgSrc = $r('app.media.ic_public_delete') 154 uninstallMenu.menuText = $r('app.string.remove') 155 uninstallMenu.onMenuClick = () => { 156 Logger.info(TAG, 'Launcher click menu item uninstall') 157 if (!CheckEmptyUtils.isEmpty(dialog)) { 158 dialog.open() 159 } 160 } 161 menuInfoList.push(uninstallMenu) 162 return menuInfoList 163 } 164 165 /** 166 * getAppName 167 * 168 * @param cacheKey 169 */ 170 getAppName(cacheKey: string) { 171 return ResourceManager.getInstance(this.context).getAppResourceCache(cacheKey, KEY_NAME) 172 } 173 174 /** 175 * jump to form manager 176 * @param formInfo 177 178 * */ 179 jumpToFormManagerView(formInfo: GridLayoutItemInfo) { 180 router.push({ 181 url: 'pages/FormPage', 182 params: { 183 formInfo: formInfo 184 } 185 }) 186 } 187 188 /** 189 * Start target ability 190 * 191 * @param bundleName target bundle name 192 * @param abilityName target ability name 193 */ 194 jumpTo(abilityName: string, bundleName: string): void { 195 this.mLauncherAbilityManager.startLauncherAbility(abilityName, bundleName) 196 } 197 198 /** 199 * getLayoutInfoCache 200 */ 201 getLayoutInfoCache() { 202 return this.layoutInfo 203 } 204 205 /** 206 * Get the list of apps displayed on the desktop (private function). 207 * 208 * @return {array} bundleInfoList, excluding system applications 209 */ 210 async getAppListAsync(): Promise<AppItemInfo[]> { 211 let allAbilityList: AppItemInfo[] = await this.mLauncherAbilityManager.getLauncherAbilityList() 212 Logger.info(TAG, `getAppListAsync allAbilityList length: ${allAbilityList.length}`) 213 let launcherAbilityList: AppItemInfo[] = [] 214 for (let i in allAbilityList) { 215 if (this.mSystemApplicationName.indexOf(allAbilityList[i].bundleName) === CommonConstants.INVALID_VALUE) { 216 launcherAbilityList.push(allAbilityList[i]) 217 FormModel.updateAppItemFormInfo(allAbilityList[i].bundleName) 218 } 219 } 220 Logger.debug(TAG, `getAppListAsync launcherAbiltyList length: ${launcherAbilityList.length}`) 221 return launcherAbilityList 222 } 223 224 /** 225 * getLayoutInfo 226 */ 227 async getLayoutInfo() { 228 await RdbManager.initRdbConfig(this.context) 229 let infos = await this.getAppListAsync() 230 let gridLayoutItemInfos = await RdbManager.queryLayoutInfo() 231 Logger.info(TAG, `queryLayoutInfo,gridLayoutItemInfos = ${gridLayoutItemInfos.length}`) 232 let result: Array<Array<GridLayoutItemInfo>> = [] 233 let plusApps = [] 234 // 如果查询到的数据长度是0,说明之前没有过数据,此时初始化数据并插入 235 if (gridLayoutItemInfos.length === 0) { 236 let result = this.initPositionInfos(infos) 237 await RdbManager.insertData(result) 238 Logger.info(TAG, `getLayoutInfo result0,${JSON.stringify(result)}`) 239 this.layoutInfo = result 240 return result 241 } 242 // 数据库中查询到了数据,则优先加载数据库中的应用和卡片,剩余的应用图标在最后一个图标的位置后面添加 243 else { 244 for (let i = 0;i < infos.length; i++) { 245 Logger.info(TAG, `getLayoutInfo infos,i = ${i}`) 246 let find = false 247 for (let j = 0;j < gridLayoutItemInfos.length; j++) { 248 if (infos[i].bundleName === gridLayoutItemInfos[j].bundleName) { 249 Logger.info(TAG, `getLayoutInfo find,j = ${j}`) 250 if (gridLayoutItemInfos[j].page >= result.length) { 251 result.push([]) 252 } 253 result[gridLayoutItemInfos[j].page].push(gridLayoutItemInfos[j]) 254 find = true 255 } 256 } 257 if (!find) { 258 plusApps.push(infos[i]) 259 } 260 } 261 Logger.info(TAG, `getLayoutInfo result1,${JSON.stringify(result[0].length)}`) 262 this.layoutInfo = result 263 // 加载完数据库中的后,剩余的app 264 if (plusApps.length > 0) { 265 Logger.info(TAG, `加载完数据库中的后,剩余的app`) 266 for (let k = 0;k < plusApps.length; k++) { 267 let item = plusApps[k] 268 if (item) { 269 this.addAppToDesktop(item, false) 270 } 271 } 272 } 273 Logger.info(TAG, `getLayoutInfo result2,${JSON.stringify(result[0].length)}`) 274 return this.layoutInfo 275 } 276 } 277 278 private async addAppToDesktop(appInfo: AppItemInfo, isRefresh: boolean) { 279 if (CheckEmptyUtils.isEmpty(appInfo)) { 280 return 281 } 282 let pageInfos = this.layoutInfo 283 for (let i = 0;i < pageInfos.length; i++) { 284 Logger.info(TAG, `removeCardFromDeskTop pageInfos${i}`) 285 for (let j = 0;j < pageInfos[i].length; j++) { 286 if (pageInfos[i][j].bundleName === appInfo.bundleName && pageInfos[i][j].abilityName === appInfo.abilityName) { 287 return 288 } 289 } 290 } 291 let gridItem: GridLayoutItemInfo = this.covertAppItemToGridItem(appInfo, 0, 0, 0) 292 let page = this.layoutInfo.length 293 gridItem = this.updateItemLayoutInfo(gridItem) 294 if (gridItem.page >= page) { 295 this.layoutInfo.push([]) 296 } 297 this.layoutInfo[gridItem.page].push(gridItem) 298 Logger.debug(TAG, `addAppToDesktop item ${JSON.stringify(gridItem)}`) 299 await RdbManager.initRdbConfig(this.context) 300 await RdbManager.insertItem(gridItem) 301 if (isRefresh) { 302 AppStorage.SetOrCreate('isRefresh', true) 303 } 304 } 305 306 /** 307 * uninstallAppItem 308 * 309 * @param itemInfo: GridLayoutItemInfo 310 */ 311 async uninstallAppItem(itemInfo: GridLayoutItemInfo) { 312 if (CheckEmptyUtils.isEmpty(itemInfo)) { 313 return 314 } 315 let appInfo = await this.mLauncherAbilityManager.getAppInfoByBundleName(itemInfo.bundleName) 316 if (CheckEmptyUtils.isEmpty(appInfo)) { 317 return 318 } 319 if (appInfo.isUninstallAble) { 320 this.mLauncherAbilityManager.uninstallLauncherAbility(itemInfo.bundleName, (result) => { 321 if (result.code == CommonConstants.UNINSTALL_SUCCESS) { 322 } 323 this.informUninstallResult(result.code) 324 }) 325 } else { 326 this.informUninstallResult(CommonConstants.UNINSTALL_FORBID) 327 } 328 } 329 330 private informUninstallResult(resultCode: number) { 331 let uninstallMessage: string = '' 332 if (resultCode === CommonConstants.UNINSTALL_FORBID) { 333 uninstallMessage = this.context.resourceManager.getStringSync($r('app.string.disable_uninstall').id) 334 } else if (resultCode === CommonConstants.UNINSTALL_SUCCESS) { 335 uninstallMessage = this.context.resourceManager.getStringSync($r('app.string.uninstall_success').id) 336 } else { 337 uninstallMessage = this.context.resourceManager.getStringSync($r('app.string.uninstall_failed').id) 338 } 339 prompt.showToast({ 340 message: uninstallMessage 341 }) 342 } 343 344 /** 345 * initPositionInfos 346 * 347 * @param appInfos 348 */ 349 initPositionInfos(appInfos: Array<AppItemInfo>) { 350 if (CheckEmptyUtils.isEmptyArr(appInfos)) { 351 return [] 352 } 353 Logger.info(TAG, `initPositionInfos, appInfos size = ${appInfos.length}`) 354 let countsOnePage = CommonConstants.DEFAULT_COLUMN_COUNT * CommonConstants.DEFAULT_ROW_COUNT 355 let result: Array<Array<GridLayoutItemInfo>> = [] 356 let page = Math.floor(appInfos.length / countsOnePage) + 1 357 for (let i = 0;i < page; i++) { 358 let item: Array<GridLayoutItemInfo> = [] 359 result.push(item) 360 } 361 Logger.debug(TAG, `result0 = ${JSON.stringify(result)}`) 362 for (let j = 0;j < appInfos.length; j++) { 363 let item = appInfos[j] 364 Logger.debug(TAG, `infos[${j}], item = ${JSON.stringify(item)}`) 365 let page = Math.floor(j / countsOnePage) 366 let column = Math.floor(j % CommonConstants.DEFAULT_COLUMN_COUNT) 367 let row = Math.floor(j / CommonConstants.DEFAULT_COLUMN_COUNT) % countsOnePage 368 let gridItem: GridLayoutItemInfo = this.covertAppItemToGridItem(item, page, column, row) 369 if (!CheckEmptyUtils.isEmpty(gridItem)) { 370 result[page].push(gridItem) 371 } 372 Logger.debug(TAG, `infos[${j}], page = ${page},row = ${row},column = ${column}`) 373 } 374 Logger.debug(TAG, `result1 = ${JSON.stringify(result)}`) 375 return result 376 } 377 378 private covertAppItemToGridItem(item: AppItemInfo, page: number, column: number, row: number) { 379 if (CheckEmptyUtils.isEmpty(item)) { 380 return undefined 381 } 382 let gridItem: GridLayoutItemInfo = new GridLayoutItemInfo() 383 gridItem.appName = item.appName 384 gridItem.appIconId = item.appIconId 385 gridItem.bundleName = item.bundleName 386 gridItem.moduleName = item.moduleName 387 gridItem.abilityName = item.abilityName 388 gridItem.container = -100 389 gridItem.page = page 390 gridItem.column = column 391 gridItem.row = row 392 gridItem.area = [1, 1] 393 gridItem.typeId = 0 394 return gridItem 395 } 396 397 /** 398 * createCardToDeskTop 399 * 400 * @param formCardItem 401 */ 402 async createCardToDeskTop(formCardItem: any) { 403 if (CheckEmptyUtils.isEmpty(formCardItem)) { 404 return 405 } 406 Logger.info(TAG, `createCardToDeskTop formCardItem ${JSON.stringify(formCardItem)}`) 407 let gridItem = this.createNewCardItemInfo(formCardItem) 408 let page = this.layoutInfo.length 409 gridItem = this.updateItemLayoutInfo(gridItem) 410 if (gridItem.page >= page) { 411 this.layoutInfo.push([]) 412 } 413 this.layoutInfo[gridItem.page].push(gridItem) 414 await RdbManager.initRdbConfig(this.context) 415 await RdbManager.insertItem(gridItem) 416 Logger.info(TAG, `createCardToDeskTop gridItem2 = ${JSON.stringify(gridItem)}`) 417 AppStorage.SetOrCreate('isRefresh', true) 418 } 419 420 /** 421 * remove item from desktop 422 * 423 * @param item 424 */ 425 async removeItemFromDeskTop(item: GridLayoutItemInfo) { 426 if (CheckEmptyUtils.isEmpty(item)) { 427 return 428 } 429 Logger.info(TAG, 'removeCardFromDeskTop start') 430 let pageInfos = this.layoutInfo 431 searchCircle:for (let i = 0;i < pageInfos.length; i++) { 432 Logger.info(TAG, `removeCardFromDeskTop pageInfos${i}`) 433 for (let j = 0;j < pageInfos[i].length; j++) { 434 if (pageInfos[i][j].bundleName === item.bundleName && pageInfos[i][j].page === item.page 435 && pageInfos[i][j].row === item.row && pageInfos[i][j].column === item.column) { 436 Logger.debug(TAG, `removeCardFromDeskTop pageInfos${i}${j} is find,remove`) 437 pageInfos[i].splice(j, 1) 438 // 移除后是空白屏幕,移除屏幕 439 if (pageInfos[i].length === 0) { 440 pageInfos.splice(i, 1) 441 } 442 break searchCircle 443 } 444 } 445 } 446 this.layoutInfo = pageInfos 447 await RdbManager.deleteItemByPosition(item.page, item.row, item.column) 448 Logger.info(TAG, `removeCardFromDeskTop item= ${JSON.stringify(item)}`) 449 AppStorage.SetOrCreate('isRefresh', true) 450 } 451 452 private updateItemLayoutInfo(item: GridLayoutItemInfo) { 453 let page = this.layoutInfo.length 454 const row = CommonConstants.DEFAULT_ROW_COUNT 455 const column = CommonConstants.DEFAULT_COLUMN_COUNT 456 let isNeedNewPage = true 457 pageCycle: for (let i = 0; i < page; i++) { 458 for (let y = 0; y < row; y++) { 459 for (let x = 0; x < column; x++) { 460 if (this.isPositionValid(item, i, x, y)) { 461 isNeedNewPage = false 462 item.page = i 463 item.column = x 464 item.row = y 465 break pageCycle 466 } 467 } 468 } 469 } 470 471 if (isNeedNewPage) { 472 item.page = page 473 item.column = 0 474 item.row = 0 475 } 476 return item 477 } 478 479 private isPositionValid(item: GridLayoutItemInfo, page: number, startColumn: number, startRow: number) { 480 const row = CommonConstants.DEFAULT_ROW_COUNT 481 const column = CommonConstants.DEFAULT_COLUMN_COUNT 482 if ((startRow + item.area[0]) > row || (startColumn + item.area[1]) > column) { 483 Logger.info(TAG, 'isPositionValid return false 1') 484 return false 485 } 486 let isValid = true 487 for (let x = startColumn; x < startColumn + item.area[1]; x++) { 488 for (let y = startRow; y < startRow + item.area[0]; y++) { 489 if (this.isPositionOccupied(page, x, y)) { 490 Logger.info(TAG, 'isPositionValid return false 2') 491 isValid = false 492 break 493 } 494 } 495 } 496 return isValid 497 } 498 499 private isPositionOccupied(page: number, column: number, row: number) { 500 const layoutInfo = this.layoutInfo[page] 501 // current page has space 502 for (const item of layoutInfo) { 503 const xMatch = (column >= item.column) && (column < item.column + item.area[1]) 504 const yMatch = (row >= item.row) && (row < item.row + item.area[0]) 505 if (xMatch && yMatch) { 506 return true 507 } 508 } 509 return false 510 } 511 512 private createNewCardItemInfo(formCardItem: any): GridLayoutItemInfo { 513 if (CheckEmptyUtils.isEmpty(formCardItem)) { 514 return undefined 515 } 516 let gridItem: GridLayoutItemInfo = new GridLayoutItemInfo() 517 gridItem.appName = formCardItem.appName 518 gridItem.typeId = CommonConstants.TYPE_CARD 519 gridItem.cardId = formCardItem.cardId 520 gridItem.cardName = formCardItem.cardName 521 gridItem.bundleName = formCardItem.bundleName 522 gridItem.moduleName = formCardItem.moduleName 523 gridItem.abilityName = formCardItem.abilityName 524 gridItem.container = -100 525 gridItem.page = 0 526 gridItem.column = 0 527 gridItem.row = 0 528 gridItem.area = FormManager.getCardSize(formCardItem.dimension) 529 return gridItem 530 } 531}