• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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}