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.insertItem(gridItem) 300 if (isRefresh) { 301 AppStorage.SetOrCreate('isRefresh', true) 302 } 303 } 304 305 /** 306 * uninstallAppItem 307 * 308 * @param itemInfo: GridLayoutItemInfo 309 */ 310 async uninstallAppItem(itemInfo: GridLayoutItemInfo) { 311 if (CheckEmptyUtils.isEmpty(itemInfo)) { 312 return 313 } 314 let appInfo = await this.mLauncherAbilityManager.getAppInfoByBundleName(itemInfo.bundleName) 315 if (CheckEmptyUtils.isEmpty(appInfo)) { 316 return 317 } 318 if (appInfo.isUninstallAble) { 319 this.mLauncherAbilityManager.uninstallLauncherAbility(itemInfo.bundleName, (result) => { 320 if (result.code == CommonConstants.UNINSTALL_SUCCESS) { 321 } 322 this.informUninstallResult(result.code) 323 }) 324 } else { 325 this.informUninstallResult(CommonConstants.UNINSTALL_FORBID) 326 } 327 } 328 329 private informUninstallResult(resultCode: number) { 330 let uninstallMessage: string = '' 331 if (resultCode === CommonConstants.UNINSTALL_FORBID) { 332 uninstallMessage = this.context.resourceManager.getStringSync($r('app.string.disable_uninstall').id) 333 } else if (resultCode === CommonConstants.UNINSTALL_SUCCESS) { 334 uninstallMessage = this.context.resourceManager.getStringSync($r('app.string.uninstall_success').id) 335 } else { 336 uninstallMessage = this.context.resourceManager.getStringSync($r('app.string.uninstall_failed').id) 337 } 338 prompt.showToast({ 339 message: uninstallMessage 340 }) 341 } 342 343 /** 344 * initPositionInfos 345 * 346 * @param appInfos 347 */ 348 initPositionInfos(appInfos: Array<AppItemInfo>) { 349 if (CheckEmptyUtils.isEmptyArr(appInfos)) { 350 return [] 351 } 352 Logger.info(TAG, `initPositionInfos, appInfos size = ${appInfos.length}`) 353 let countsOnePage = CommonConstants.DEFAULT_COLUMN_COUNT * CommonConstants.DEFAULT_ROW_COUNT 354 let result: Array<Array<GridLayoutItemInfo>> = [] 355 let page = Math.floor(appInfos.length / countsOnePage) + 1 356 for (let i = 0;i < page; i++) { 357 let item: Array<GridLayoutItemInfo> = [] 358 result.push(item) 359 } 360 Logger.debug(TAG, `result0 = ${JSON.stringify(result)}`) 361 for (let j = 0;j < appInfos.length; j++) { 362 let item = appInfos[j] 363 Logger.debug(TAG, `infos[${j}], item = ${JSON.stringify(item)}`) 364 let page = Math.floor(j / countsOnePage) 365 let column = Math.floor(j % CommonConstants.DEFAULT_COLUMN_COUNT) 366 let row = Math.floor(j / CommonConstants.DEFAULT_COLUMN_COUNT) % countsOnePage 367 let gridItem: GridLayoutItemInfo = this.covertAppItemToGridItem(item, page, column, row) 368 if (!CheckEmptyUtils.isEmpty(gridItem)) { 369 result[page].push(gridItem) 370 } 371 Logger.debug(TAG, `infos[${j}], page = ${page},row = ${row},column = ${column}`) 372 } 373 Logger.debug(TAG, `result1 = ${JSON.stringify(result)}`) 374 return result 375 } 376 377 private covertAppItemToGridItem(item: AppItemInfo, page: number, column: number, row: number) { 378 if (CheckEmptyUtils.isEmpty(item)) { 379 return undefined 380 } 381 let gridItem: GridLayoutItemInfo = new GridLayoutItemInfo() 382 gridItem.appName = item.appName 383 gridItem.appIconId = item.appIconId 384 gridItem.bundleName = item.bundleName 385 gridItem.moduleName = item.moduleName 386 gridItem.abilityName = item.abilityName 387 gridItem.container = -100 388 gridItem.page = page 389 gridItem.column = column 390 gridItem.row = row 391 gridItem.area = [1, 1] 392 gridItem.typeId = 0 393 return gridItem 394 } 395 396 /** 397 * createCardToDeskTop 398 * 399 * @param formCardItem 400 */ 401 async createCardToDeskTop(formCardItem: any) { 402 if (CheckEmptyUtils.isEmpty(formCardItem)) { 403 return 404 } 405 Logger.info(TAG, `createCardToDeskTop formCardItem ${JSON.stringify(formCardItem)}`) 406 let gridItem = this.createNewCardItemInfo(formCardItem) 407 let page = this.layoutInfo.length 408 gridItem = this.updateItemLayoutInfo(gridItem) 409 if (gridItem.page >= page) { 410 this.layoutInfo.push([]) 411 } 412 this.layoutInfo[gridItem.page].push(gridItem) 413 await RdbManager.insertItem(gridItem) 414 Logger.info(TAG, `createCardToDeskTop gridItem2 = ${JSON.stringify(gridItem)}`) 415 AppStorage.SetOrCreate('isRefresh', true) 416 } 417 418 /** 419 * remove item from desktop 420 * 421 * @param item 422 */ 423 async removeItemFromDeskTop(item: GridLayoutItemInfo) { 424 if (CheckEmptyUtils.isEmpty(item)) { 425 return 426 } 427 Logger.info(TAG, 'removeCardFromDeskTop start') 428 let pageInfos = this.layoutInfo 429 searchCircle:for (let i = 0;i < pageInfos.length; i++) { 430 Logger.info(TAG, `removeCardFromDeskTop pageInfos${i}`) 431 for (let j = 0;j < pageInfos[i].length; j++) { 432 if (pageInfos[i][j].bundleName === item.bundleName && pageInfos[i][j].page === item.page 433 && pageInfos[i][j].row === item.row && pageInfos[i][j].column === item.column) { 434 Logger.debug(TAG, `removeCardFromDeskTop pageInfos${i}${j} is find,remove`) 435 pageInfos[i].splice(j, 1) 436 // 移除后是空白屏幕,移除屏幕 437 if (pageInfos[i].length === 0) { 438 pageInfos.splice(i, 1) 439 } 440 break searchCircle 441 } 442 } 443 } 444 this.layoutInfo = pageInfos 445 await RdbManager.deleteItemByPosition(item.page, item.row, item.column) 446 Logger.info(TAG, `removeCardFromDeskTop item= ${JSON.stringify(item)}`) 447 AppStorage.SetOrCreate('isRefresh', true) 448 } 449 450 private updateItemLayoutInfo(item: GridLayoutItemInfo) { 451 let page = this.layoutInfo.length 452 const row = CommonConstants.DEFAULT_ROW_COUNT 453 const column = CommonConstants.DEFAULT_COLUMN_COUNT 454 let isNeedNewPage = true 455 pageCycle: for (let i = 0; i < page; i++) { 456 for (let y = 0; y < row; y++) { 457 for (let x = 0; x < column; x++) { 458 if (this.isPositionValid(item, i, x, y)) { 459 isNeedNewPage = false 460 item.page = i 461 item.column = x 462 item.row = y 463 break pageCycle 464 } 465 } 466 } 467 } 468 469 if (isNeedNewPage) { 470 item.page = page 471 item.column = 0 472 item.row = 0 473 } 474 return item 475 } 476 477 private isPositionValid(item: GridLayoutItemInfo, page: number, startColumn: number, startRow: number) { 478 const row = CommonConstants.DEFAULT_ROW_COUNT 479 const column = CommonConstants.DEFAULT_COLUMN_COUNT 480 if ((startRow + item.area[0]) > row || (startColumn + item.area[1]) > column) { 481 Logger.info(TAG, 'isPositionValid return false 1') 482 return false 483 } 484 let isValid = true 485 for (let x = startColumn; x < startColumn + item.area[1]; x++) { 486 for (let y = startRow; y < startRow + item.area[0]; y++) { 487 if (this.isPositionOccupied(page, x, y)) { 488 Logger.info(TAG, 'isPositionValid return false 2') 489 isValid = false 490 break 491 } 492 } 493 } 494 return isValid 495 } 496 497 private isPositionOccupied(page: number, column: number, row: number) { 498 const layoutInfo = this.layoutInfo[page] 499 // current page has space 500 for (const item of layoutInfo) { 501 const xMatch = (column >= item.column) && (column < item.column + item.area[1]) 502 const yMatch = (row >= item.row) && (row < item.row + item.area[0]) 503 if (xMatch && yMatch) { 504 return true 505 } 506 } 507 return false 508 } 509 510 private createNewCardItemInfo(formCardItem: any): GridLayoutItemInfo { 511 if (CheckEmptyUtils.isEmpty(formCardItem)) { 512 return undefined 513 } 514 let gridItem: GridLayoutItemInfo = new GridLayoutItemInfo() 515 gridItem.appName = formCardItem.appName 516 gridItem.typeId = CommonConstants.TYPE_CARD 517 gridItem.cardId = formCardItem.cardId 518 gridItem.cardName = formCardItem.cardName 519 gridItem.bundleName = formCardItem.bundleName 520 gridItem.moduleName = formCardItem.moduleName 521 gridItem.abilityName = formCardItem.abilityName 522 gridItem.container = -100 523 gridItem.page = 0 524 gridItem.column = 0 525 gridItem.row = 0 526 gridItem.area = FormManager.getCardSize(formCardItem.dimension) 527 return gridItem 528 } 529}