1/**
2 * Copyright (c) 2021-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 { Log } from '../utils/Log';
17import { CheckEmptyUtils } from '../utils/CheckEmptyUtils';
18import { EventConstants } from '../constants/EventConstants';
19import { CommonConstants } from '../constants/CommonConstants';
20import { FormModel } from './FormModel';
21import { AppItemInfo } from '../bean/AppItemInfo';
22import { localEventManager } from '../manager/LocalEventManager';
23import { launcherAbilityManager } from '../manager/LauncherAbilityManager';
24import SystemApplication from '../configs/SystemApplication';
25import { AtomicServiceAppModel } from './AtomicServiceAppModel';
26import launcherBundleManager from '@ohos.bundle.launcherBundleManager';
27
28const TAG = 'AppModel';
29
30/**
31 * Desktop application information data model.
32 */
33export class AppModel {
34  private mBundleInfoList: AppItemInfo[] = [];
35  private readonly mSystemApplicationName = [];
36  private readonly mAppStateChangeListener = [];
37  private readonly mShortcutInfoMap = new Map<string, launcherBundleManager.ShortcutInfo[]>();
38  private readonly mFormModel: FormModel;
39  private readonly mInstallationListener;
40  private readonly mAtomicServiceAppModel: AtomicServiceAppModel;
41
42  private constructor() {
43    Log.showInfo(TAG, 'constructor start');
44    this.mSystemApplicationName = SystemApplication.SystemApplicationName.split(',');
45    this.mFormModel = FormModel.getInstance();
46    this.mAtomicServiceAppModel = AtomicServiceAppModel.getInstance();
47    this.mInstallationListener = this.installationSubscriberCallBack.bind(this);
48  }
49
50  /**
51   * Get the application data model object.
52   *
53   * @return {object} application data model singleton
54   */
55  static getInstance(): AppModel {
56    if (globalThis.AppModel == null) {
57      globalThis.AppModel = new AppModel();
58    }
59    return globalThis.AppModel;
60  }
61
62  /**
63   * Get the list of apps displayed on the desktop.
64   * (public function, reduce the frequency of method call)
65   *
66   * @return {array} bundleInfoList
67   */
68  async getAppList() {
69    Log.showInfo(TAG, 'getAppList start');
70    if (!CheckEmptyUtils.isEmptyArr(this.mBundleInfoList)) {
71      Log.showInfo(TAG, `getAppList bundleInfoList length: ${this.mBundleInfoList.length}`);
72      return this.mBundleInfoList;
73    }
74    const bundleInfoList: AppItemInfo[] = await this.getAppListAsync();
75    return bundleInfoList;
76  }
77
78  /**
79   * Get the list of apps displayed on the desktop (private function).
80   *
81   * @return {array} bundleInfoList, excluding system applications
82   */
83  async getAppListAsync(): Promise<AppItemInfo[]> {
84    let allAbilityList: AppItemInfo[] = await launcherAbilityManager.getLauncherAbilityList();
85    Log.showInfo(TAG, `getAppListAsync--->allAbilityList length: ${allAbilityList.length}`);
86    let launcherAbilityList: AppItemInfo[] = [];
87    for (const ability of allAbilityList) {
88      if (this.mSystemApplicationName.indexOf(ability.bundleName) === CommonConstants.INVALID_VALUE) {
89        launcherAbilityList.push(ability);
90        this.updateShortcutInfo(ability.bundleName);
91        this.mFormModel.updateAppItemFormInfo(ability.bundleName);
92      }
93    }
94    this.mBundleInfoList = launcherAbilityList;
95    Log.showInfo(TAG, `getAppListAsync--->allAbilityList length after filtration: ${launcherAbilityList.length}`);
96    return launcherAbilityList;
97  }
98
99  /**
100   * Register application list change event listener.
101   *
102   * @param listener
103   */
104  registerStateChangeListener(listener): void {
105    if (this.mAppStateChangeListener.indexOf(listener) === CommonConstants.INVALID_VALUE) {
106      this.mAppStateChangeListener.push(listener);
107    }
108  }
109
110  /**
111   * Unregister application list change event listener.
112   *
113   * @param listener
114   */
115  unregisterAppStateChangeListener(listener): void {
116    let index: number = this.mAppStateChangeListener.indexOf(listener);
117    if (index != CommonConstants.INVALID_VALUE) {
118      this.mAppStateChangeListener.splice(index, 1);
119    }
120  }
121
122  getUserId(): number {
123    return launcherAbilityManager.getUserId();
124  }
125
126  /**
127   * Start listening to the system application status.
128   */
129  registerAppListEvent(): void {
130    launcherAbilityManager.registerLauncherAbilityChangeListener(this.mInstallationListener);
131  }
132
133  /**
134   * Stop listening for system application status.
135   */
136  unregisterAppListEvent(): void {
137    launcherAbilityManager.unregisterLauncherAbilityChangeListener(this.mInstallationListener);
138  }
139
140  /**
141   * The callback function of the application installation event.
142   *
143   * @param {Object} event
144   * @param {string} bundleName
145   * @param {number} userId
146   */
147  private async installationSubscriberCallBack(event, bundleName, userId) {
148    Log.showInfo(TAG, `installationSubscriberCallBack event: ${event}`);
149    this.closePopup();
150    this.updateShortcutInfo(bundleName, event);
151    this.mFormModel.updateAppItemFormInfo(bundleName, event);
152    // initial mBundleInfoList
153    if (CheckEmptyUtils.isEmptyArr(this.mBundleInfoList)) {
154      await this.getAppListAsync();
155    }
156    if (event === EventConstants.EVENT_PACKAGE_REMOVED) {
157      this.removeItem(bundleName);
158      this.mAtomicServiceAppModel.removeAtomicServiceItem(bundleName);
159      this.mFormModel.deleteFormByBundleName(bundleName);
160
161      // delete app from folder
162      localEventManager.sendLocalEventSticky(EventConstants.EVENT_FOLDER_PACKAGE_REMOVED, bundleName);
163
164      // delete app form dock
165      localEventManager.sendLocalEventSticky(EventConstants.EVENT_REQUEST_RESIDENT_DOCK_ITEM_DELETE, {
166        bundleName: bundleName,
167        keyName: undefined
168      });
169      localEventManager.sendLocalEventSticky(EventConstants.EVENT_REQUEST_RECENT_DOCK_ITEM_DELETE, {
170        bundleName: bundleName,
171        keyName: undefined
172      });
173
174      // delete app from pageDesktop
175      localEventManager.sendLocalEventSticky(EventConstants.EVENT_REQUEST_PAGEDESK_ITEM_DELETE, {
176        bundleName: bundleName,
177        keyName: undefined
178      });
179
180      localEventManager.sendLocalEventSticky(EventConstants.EVENT_REQUEST_RECOMMEND_FORM_DELETE, bundleName);
181    } else {
182      let appItemInfo: AppItemInfo = await this.getAndReplaceLauncherAbility(bundleName);
183
184      if (CheckEmptyUtils.isEmpty(appItemInfo)) {
185        appItemInfo = await this.mAtomicServiceAppModel.getAndReplaceAtomicAbility(bundleName);
186      }
187
188      if (CheckEmptyUtils.isEmpty(appItemInfo)) {
189        Log.showError(TAG, `installationSubscriberCallBack neither launcher nor atomic app, bundleName:${bundleName} `);
190        return;
191      }
192      if (event === EventConstants.EVENT_PACKAGE_CHANGED) {
193        Log.showInfo(TAG, `installationSubscriber, PACKAGE_CHANGED, bundleName is ${bundleName}`);
194        AppStorage.setOrCreate('formRefresh', String(new Date()));
195
196        localEventManager.sendLocalEventSticky(EventConstants.EVENT_REQUEST_RECOMMEND_FORM_UPDATE, bundleName);
197        Log.showError(TAG, `installationSubscriberCallBack unKnow bundleType:${appItemInfo.bundleType}`);
198      } else {
199        await this.mFormModel.updateAppItemFormInfo(bundleName);
200        await this.mFormModel.updateAtomicServiceAppItemFormInfo(bundleName);
201        localEventManager.sendLocalEventSticky(EventConstants.EVENT_REQUEST_RECOMMEND_FORM_ADD, bundleName);
202      }
203    }
204    this.notifyAppStateChangeEvent();
205  }
206
207  private async getAndReplaceLauncherAbility(bundleName: string): Promise<AppItemInfo> {
208    const abilityInfos: AppItemInfo[] = await launcherAbilityManager.getLauncherAbilityInfo(bundleName);
209    if (CheckEmptyUtils.isEmptyArr(abilityInfos)) {
210      return undefined;
211    }
212    Log.showDebug(TAG, `launcher abilityInfos: ${JSON.stringify(abilityInfos)}`);
213    this.replaceItem(bundleName, abilityInfos);
214    return abilityInfos[0];
215  }
216
217  /**
218   * Send event about application state change.
219   */
220  private notifyAppStateChangeEvent() {
221    for (let i = 0; i < this.mAppStateChangeListener.length; i++) {
222      this.mAppStateChangeListener[i](this.mBundleInfoList);
223    }
224  }
225
226  /**
227   * Get the app index in bundleInfoList.
228   *
229   * @param {string} bundleName
230   * @return {number} index
231   */
232  private getItemIndex(bundleName): number {
233    for (const listItem of this.mBundleInfoList) {
234      if (listItem.bundleName === bundleName) {
235        const index = this.mBundleInfoList.indexOf(listItem);
236        return index;
237      }
238    }
239    return CommonConstants.INVALID_VALUE;
240  }
241
242  /**
243   * Append app items into the bundleInfoList.
244   *
245   * @param {array} abilityInfos
246   */
247  private appendItem(abilityInfos): void {
248    for (let index = 0; index < abilityInfos.length; index++) {
249      this.mBundleInfoList.push(abilityInfos[index]);
250    }
251  }
252
253  /**
254   * Remove app item from the bundleInfoList.
255   *
256   * @param {string} bundleName
257   */
258  private removeItem(bundleName: string): void {
259    Log.showDebug(TAG, `removeItem bundleName: ${bundleName}`);
260    let originItemIndex = this.getItemIndex(bundleName);
261    while (originItemIndex != CommonConstants.INVALID_VALUE) {
262      this.removeItemCache(this.mBundleInfoList[originItemIndex]);
263      this.mBundleInfoList.splice(originItemIndex, 1);
264      originItemIndex = this.getItemIndex(bundleName);
265    }
266  }
267
268  /**
269 * Remove app item from the cache.
270 *
271 * @param {string} bundleName
272 */
273  private removeItemCache(appItemInfo: AppItemInfo): void {
274    Log.showDebug(TAG, `removeItemCache bundleName: ${(appItemInfo.bundleName)}`);
275    let cacheKey = appItemInfo.appLabelId + appItemInfo.bundleName + appItemInfo.moduleName;
276    globalThis.ResourceManager.deleteAppResourceCache(cacheKey, 'name');
277    cacheKey = appItemInfo.appIconId + appItemInfo.bundleName + appItemInfo.moduleName;
278    globalThis.ResourceManager.deleteAppResourceCache(cacheKey, 'icon');
279  }
280
281  /**
282   * Replace app items in the bundleInfoList.
283   *
284   * @param {string} bundleName
285   * @param {array} abilityInfos
286   */
287  private replaceItem(bundleName: string, abilityInfos): void {
288    Log.showDebug(TAG, `replaceItem bundleName: ${bundleName}`);
289    this.removeItem(bundleName);
290    this.appendItem(abilityInfos);
291  }
292
293  /**
294   * Put shortcut info into map.
295   *
296   * @param {string} bundleName
297   * @param {array} shortcutInfo
298   */
299  setShortcutInfo(bundleName: string, shortcutInfo: launcherBundleManager.ShortcutInfo[]): void {
300    this.mShortcutInfoMap.set(bundleName, shortcutInfo);
301  }
302
303  /**
304   * Get shortcut info from map.
305   *
306   * @param {string} bundleName
307   * @return {array | undefined} shortcutInfo
308   */
309  getShortcutInfo(bundleName: string): launcherBundleManager.ShortcutInfo[] | undefined {
310    return this.mShortcutInfoMap.get(bundleName);
311  }
312
313  /**
314   * Update shortcut info of map.
315   *
316   * @param {string} bundleName
317   * @param {string | undefined} eventType
318   */
319  private updateShortcutInfo(bundleName, eventType?): void {
320    if (eventType && eventType === EventConstants.EVENT_PACKAGE_REMOVED) {
321      this.mShortcutInfoMap.delete(bundleName);
322      return;
323    }
324    launcherAbilityManager.getShortcutInfo(bundleName, this.setShortcutInfo.bind(this));
325  }
326
327  /**
328   * Close popup.
329   */
330  private closePopup(): void {
331    ContextMenu.close();
332  }
333}