• 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.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}