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 installer from '@ohos.bundle.installer';
17import bundleMonitor from '@ohos.bundle.bundleMonitor';
18import osAccount from '@ohos.account.osAccount';
19import hiSysEvent from '@ohos.hiSysEvent';
20import launcherBundleManager from '@ohos.bundle.launcherBundleManager';
21import { Log } from '../utils/Log';
22import { CheckEmptyUtils } from '../utils/CheckEmptyUtils';
23import { AppItemInfo } from '../bean/AppItemInfo';
24import { CommonConstants } from '../constants/CommonConstants';
25import { ResourceManager } from './ResourceManager';
26import { EventConstants } from '../constants/EventConstants';
27import { BadgeManager } from '../manager/BadgeManager';
28import { PreferencesHelper } from './PreferencesHelper';
29import { BusinessError } from '@ohos.base';
30import performanceMonitor from '@ohos.arkui.performanceMonitor';
31
32const TAG = 'LauncherAbilityManager';
33
34interface BundleStatusCallback {
35  add: (bundleName: string, userId: number) => void;
36  remove: (bundleName: string, userId: number) => void;
37  update: (bundleName: string, userId: number) => void;
38}
39
40/**
41 * Wrapper class for innerBundleManager and formManager interfaces.
42 */
43class LauncherAbilityManager {
44  private static readonly CURRENT_USER_ID = -2;
45  private readonly mAppMap = new Map<string, AppItemInfo>();
46  private mUserId: number = 100;
47  private mIsNeedRegisterBundleCallback = false;
48  private readonly mBundleStatusCallback: BundleStatusCallback = {
49    add: (bundleName, userId) => {
50      Log.showDebug(TAG, `PACKAGE_ADDED bundleName: ${bundleName}, userId: ${userId}, mUserId ${this.mUserId}`);
51      this.mUserId === userId && this.notifyLauncherAbilityChange(EventConstants.EVENT_PACKAGE_ADDED, bundleName, userId);
52    },
53    remove: (bundleName, userId) => {
54      Log.showDebug(TAG, `PACKAGE_REMOVED bundleName: ${bundleName}, userId: ${userId}, mUserId ${this.mUserId}`);
55      this.mUserId === userId && this.notifyLauncherAbilityChange(EventConstants.EVENT_PACKAGE_REMOVED, bundleName, userId);
56    },
57    update: (bundleName, userId) => {
58      Log.showDebug(TAG, `PACKAGE_CHANGED bundleName: ${bundleName}, userId: ${userId}, mUserId ${this.mUserId}`);
59      this.mUserId === userId && this.notifyLauncherAbilityChange(EventConstants.EVENT_PACKAGE_CHANGED, bundleName, userId);
60    }
61  };
62
63  private readonly mLauncherAbilityChangeListeners: any[] = [];
64
65  /**
66   * Get desktop application information management object
67   *
68   * @return Desktop application information management object instance
69   */
70  static getInstance(): LauncherAbilityManager {
71    if (globalThis.LauncherAbilityManagerInstance == null) {
72      globalThis.LauncherAbilityManagerInstance = new LauncherAbilityManager();
73    }
74    return globalThis.LauncherAbilityManagerInstance;
75  }
76
77  private constructor() {
78    const osAccountManager = osAccount.getAccountManager();
79    osAccountManager.getOsAccountLocalId((err, localId) => {
80      Log.showDebug(TAG, `getOsAccountLocalId localId ${localId}`);
81      this.mUserId = localId;
82    });
83  }
84
85  getUserId(): number {
86    return this.mUserId;
87  }
88
89  checkBundleMonitor(): void {
90    if (!this.mIsNeedRegisterBundleCallback) {
91      return;
92    }
93    Log.showInfo(TAG, 'checkBundleMonitor need to register bundle callback');
94    this.bundleMonitorOn();
95  }
96
97  bundleMonitorOn(): void  {
98    this.mIsNeedRegisterBundleCallback = false;
99    bundleMonitor.on('add', (bundleChangeInfo) => {
100      Log.showInfo(TAG, `add bundleName: ${bundleChangeInfo.bundleName} userId: ${bundleChangeInfo.userId}`);
101      this.mBundleStatusCallback.add(bundleChangeInfo.bundleName, bundleChangeInfo.userId);
102    });
103    bundleMonitor.on('update', (bundleChangeInfo) => {
104      Log.showInfo(TAG, `update bundleName: ${bundleChangeInfo.bundleName} userId: ${bundleChangeInfo.userId}`);
105      this.mBundleStatusCallback.update(bundleChangeInfo.bundleName, bundleChangeInfo.userId);
106    });
107    bundleMonitor.on('remove', (bundleChangeInfo) => {
108      Log.showInfo(TAG, `remove bundleName: ${bundleChangeInfo.bundleName} userId: ${bundleChangeInfo.userId}`);
109      this.mBundleStatusCallback.remove(bundleChangeInfo.bundleName, bundleChangeInfo.userId);
110    });
111    Log.showInfo(TAG, 'bundleMonitorOn register bundle callback finished');
112  }
113
114  /**
115   * Monitor system application status.
116   *
117   * @params listener: listening object
118   */
119  async registerLauncherAbilityChangeListener(listener: any):Promise<void> {
120    if (listener != null) {
121      if (this.mLauncherAbilityChangeListeners.length == 0) {
122        try {
123          let isBmsHasInit = await PreferencesHelper.getInstance().get('isBmsHasInit', false);
124          Log.showInfo(TAG, `registerCallback success, isBmsHasInit:${isBmsHasInit}`);
125          if (isBmsHasInit) {
126            this.bundleMonitorOn();
127          } else {
128            PreferencesHelper.getInstance().put('isBmsHasInit', true);
129            this.mIsNeedRegisterBundleCallback = true;
130          }
131        } catch (errData) {
132          let message = (errData as BusinessError).message;
133          let errCode = (errData as BusinessError).code;
134          Log.showError(TAG, `registerCallback fail errCode:${errCode}, message:${message}`);
135        }
136      }
137      const index = this.mLauncherAbilityChangeListeners.indexOf(listener);
138      if (index == CommonConstants.INVALID_VALUE) {
139        this.mLauncherAbilityChangeListeners.push(listener);
140      }
141    }
142  }
143
144  /**
145   * Cancel monitoring system application status.
146   *
147   * @params listener: listening object
148   */
149  unregisterLauncherAbilityChangeListener(listener: any): void {
150    if (listener != null) {
151      const index = this.mLauncherAbilityChangeListeners.indexOf(listener);
152      if (index != CommonConstants.INVALID_VALUE) {
153        this.mLauncherAbilityChangeListeners.splice(index, 1);
154      }
155      if (this.mLauncherAbilityChangeListeners.length == 0) {
156        try {
157          bundleMonitor.off('add');
158          bundleMonitor.off('update');
159          bundleMonitor.off('remove');
160          Log.showInfo(TAG, 'unregisterCallback success');
161        } catch (errData) {
162          let message = (errData as BusinessError).message;
163          let errCode = (errData as BusinessError).code;
164          Log.showError(TAG, `unregisterCallback fail errCode:${errCode}, message:${message}`);
165        }
166      }
167    }
168  }
169
170  private notifyLauncherAbilityChange(event, bundleName: string, userId): void {
171    for (let index = 0; index < this.mLauncherAbilityChangeListeners.length; index++) {
172      this.mLauncherAbilityChangeListeners[index](event, bundleName, userId);
173    }
174  }
175
176  /**
177   * get all app List info from BMS
178   */
179  async getLauncherAbilityList(): Promise<AppItemInfo[]> {
180    let abilityList = null;
181    await launcherBundleManager.getAllLauncherAbilityInfo(LauncherAbilityManager.CURRENT_USER_ID)
182      .then((res) => {
183        abilityList = res;
184      })
185      .catch((err) => {
186        Log.showError(TAG, `getLauncherAbilityList error: ${JSON.stringify(err)}`);
187      });
188    const appItemInfoList = new Array<AppItemInfo>();
189    if (CheckEmptyUtils.isEmpty(abilityList)) {
190      Log.showDebug(TAG, 'getLauncherAbilityList Empty');
191      return appItemInfoList;
192    }
193    for (let i = 0; i < abilityList.length; i++) {
194      let appItem = await this.convertToAppItemInfo(abilityList[i]);
195      appItemInfoList.push(appItem);
196    }
197    return appItemInfoList;
198  }
199
200  /**
201   * get AbilityInfos by bundleName from BMS
202   *
203   * @params bundleName Application package name
204   * @return List of entry capabilities information of the target application
205   */
206  async getLauncherAbilityInfo(bundleName: string): Promise<AppItemInfo[]> {
207    let abilityInfos: launcherBundleManager.LauncherAbilityInfo[];
208    await launcherBundleManager.getLauncherAbilityInfo(bundleName, this.mUserId)
209      .then((res) => {
210        abilityInfos = res;
211      })
212      .catch((err) => {
213        Log.showError(TAG, `getLauncherAbilityInfo error: ${JSON.stringify(err)}`);
214      });
215    const appItemInfoList = new Array<AppItemInfo>();
216    if (CheckEmptyUtils.isEmpty(abilityInfos) || abilityInfos.length === 0) {
217      Log.showDebug(TAG, 'getLauncherAbilityInfo Empty');
218      return appItemInfoList;
219    }
220    for (let i = 0; i < abilityInfos.length; i++) {
221      let appItem = await this.convertToAppItemInfo(abilityInfos[i]);
222      appItemInfoList.push(appItem);
223    }
224    return appItemInfoList;
225  }
226
227  /**
228   * get AppItemInfo from BMS with bundleName
229   * @params bundleName
230   * @return AppItemInfo
231   */
232  async getAppInfoByBundleName(bundleName: string, abilityName?: string): Promise<AppItemInfo | undefined> {
233    let appItemInfo: AppItemInfo | undefined = undefined;
234    // get from cache
235    if (this.mAppMap && this.mAppMap.has(bundleName)) {
236      appItemInfo = this.mAppMap.get(bundleName);
237    }
238    if (appItemInfo && abilityName && appItemInfo.abilityName === abilityName) {
239      return appItemInfo;
240    }
241    // get from system
242    let abilityInfos = new Array<launcherBundleManager.LauncherAbilityInfo>();
243    await launcherBundleManager.getLauncherAbilityInfo(bundleName, LauncherAbilityManager.CURRENT_USER_ID)
244      .then((res)=>{
245        if (res && res.length) {
246          abilityInfos = res;
247        }
248      })
249      .catch((err)=>{
250        Log.showError(TAG, `getAppInfoByBundleName getLauncherAbilityInfo error: ${JSON.stringify(err)}`);
251      });
252    if (!abilityInfos || abilityInfos.length === 0) {
253      Log.showDebug(TAG, `${bundleName} has no launcher ability`);
254      return undefined;
255    }
256    let appInfo = abilityInfos[0];
257    if (abilityName) {
258      appInfo = abilityInfos.find(item => {
259        return item.elementName.abilityName === abilityName;
260      });
261    }
262    if (!appInfo) {
263      appInfo = abilityInfos[0];
264    }
265    const data = await this.convertToAppItemInfo(appInfo);
266    return data;
267  }
268
269  private async convertToAppItemInfo(info): Promise<AppItemInfo> {
270    const appItemInfo = new AppItemInfo();
271    appItemInfo.appName = await ResourceManager.getInstance().getAppNameSync(
272      info.labelId, info.elementName.bundleName, info.elementName.moduleName, info.applicationInfo.label
273    );
274    appItemInfo.isSystemApp = info.applicationInfo.systemApp;
275    appItemInfo.isUninstallAble = info.applicationInfo.removable;
276    appItemInfo.appIconId = info.iconId;
277    appItemInfo.appLabelId = info.labelId;
278    appItemInfo.bundleName = info.elementName.bundleName;
279    appItemInfo.abilityName = info.elementName.abilityName;
280    appItemInfo.moduleName = info.elementName.moduleName;
281    appItemInfo.keyName = info.elementName.bundleName + info.elementName.abilityName + info.elementName.moduleName;
282    appItemInfo.typeId = CommonConstants.TYPE_APP;
283    appItemInfo.installTime = String(new Date());
284    appItemInfo.badgeNumber = await BadgeManager.getInstance().getBadgeByBundleSync(info.elementName.bundleName);
285    await ResourceManager.getInstance().updateIconCache(appItemInfo.appIconId, appItemInfo.bundleName, appItemInfo.moduleName);
286    this.mAppMap.set(appItemInfo.bundleName, appItemInfo);
287    return appItemInfo;
288  }
289
290  /**
291   * uninstall application, notice the userId need to be the login user
292   *
293   * @params bundleName application bundleName
294   * @params callback to get result
295   */
296  async uninstallLauncherAbility(bundleName: string, callback: (resultCode: number) => void): Promise<void> {
297    Log.showInfo(TAG, `uninstallLauncherAbility bundleName: ${bundleName}`);
298    try {
299      const bundlerInstaller = await installer.getBundleInstaller();
300      bundlerInstaller.uninstall(bundleName, {
301        userId: this.mUserId,
302        installFlag: 0,
303        isKeepData: false
304      }, (err: BusinessError) => {
305        if (err) {
306          callback(CommonConstants.INVALID_VALUE);
307          Log.showError(TAG, `uninstallLauncherAbility failed: ${JSON.stringify(err)}`);
308        } else {
309          callback(CommonConstants.UNINSTALL_SUCCESS);
310          Log.showDebug(TAG, `uninstallLauncherAbility successfully: ${JSON.stringify(err)}`);
311        }
312      });
313    } catch (err) {
314      let errCode = (err as BusinessError).code;
315      let errMsg = (err as BusinessError).message;
316      Log.showError(TAG, `uninstallLauncherAbility errCode: ${errCode}, errMsg: ${errMsg}`);
317    }
318  }
319
320  /**
321   * start the app
322   *
323   * @params paramAbilityName: Ability name
324   * @params paramBundleName: Application package name
325   */
326  startLauncherAbility(paramAbilityName: string, paramBundleName: string, paramModuleName: string) {
327    Log.showDebug(TAG, `startApplication abilityName: ${paramAbilityName}, bundleName: ${paramBundleName}, moduleName ${paramModuleName}`);
328    globalThis.desktopContext.startAbility({
329      bundleName: paramBundleName,
330      abilityName: paramAbilityName,
331      moduleName: paramModuleName
332    }).then(() => {
333      Log.showDebug(TAG, 'startApplication promise success');
334      performanceMonitor.end('LAUNCHER_APP_LAUNCH_FROM_ICON');
335      Log.showDebug(TAG, 'performanceMonitor end');
336    }, (err) => {
337      Log.showError(TAG, `startApplication promise error: ${JSON.stringify(err)}`);
338    });
339
340    const sysEventInfo = {
341      domain: 'LAUNCHER_APP',
342      name: 'START_ABILITY',
343      eventType: hiSysEvent.EventType.BEHAVIOR,
344      params: {
345        'BUNDLE_NAME': paramBundleName,
346        'ABILITY_NAME': paramAbilityName,
347        'MODULE_NAME': paramModuleName
348      }
349    };
350    hiSysEvent.write(sysEventInfo,
351      (err, value) => {
352        if (err) {
353          Log.showError(TAG, `startApplication hiSysEvent write error: ${err.code}`);
354        } else {
355          Log.showDebug(TAG, `startApplication hiSysEvent write success: ${value}`);
356        }
357      });
358    Log.showDebug(TAG, 'performanceMonitor begin');
359    performanceMonitor.begin('LAUNCHER_APP_LAUNCH_FROM_ICON', performanceMonitor.ActionType.LAST_UP,
360      paramBundleName);
361  }
362
363  /**
364   * start form config ability
365   *
366   * @params paramAbilityName
367   * @params paramBundleName
368   */
369  startAbilityFormEdit(paramAbilityName: string, paramBundleName: string, paramModuleName: string, paramCardId: number) {
370    Log.showDebug(TAG, `startAbility abilityName: ${paramAbilityName},bundleName: ${paramBundleName}, moduleName: ${paramModuleName} ,paramCardId: ${paramCardId}`);
371    globalThis.desktopContext.startAbility({
372      bundleName: paramBundleName,
373      abilityName: paramAbilityName,
374      moduleName: paramModuleName,
375      parameters:
376      {
377        formId: paramCardId.toString()
378      }
379    }).then((ret) => {
380      Log.showDebug(TAG, `startAbility ret: ${JSON.stringify(ret)}`);
381    }, (err) => {
382      Log.showError(TAG, `startAbility catch error: ${JSON.stringify(err)}`);
383    });
384  }
385
386  async getShortcutInfo(paramBundleName: string, callback) {
387    Log.showDebug(TAG, `getShortcutInfo bundleName: ${paramBundleName}`);
388    await launcherBundleManager.getShortcutInfo(paramBundleName)
389      .then(shortcutInfo => {
390        callback(paramBundleName, shortcutInfo);
391      })
392      .catch(err => {
393      });
394  }
395
396  /**
397   * start application by uri
398   *
399   * @params paramBundleName application bundle name
400   * @params paramAbilityName application abilit uri
401   */
402  startLauncherAbilityByUri(paramBundleName: string, abilityUri) {
403    Log.showInfo(TAG, `startLauncherAbilityByUri bundleName:${paramBundleName} abilityUri:${abilityUri}`);
404    const result = globalThis.desktopContext.startAbility({
405      bundleName: paramBundleName,
406      uri: abilityUri
407    }).then(() => {
408      Log.showDebug(TAG, 'startLauncherAbilityByUri promise success');
409    }, (err) => {
410      Log.showError(TAG, `startLauncherAbilityByUri promise error: ${JSON.stringify(err)}`);
411    });
412    Log.showDebug(TAG, `startLauncherAbilityByUri AceApplication : startAbility : ${result}`);
413  }
414
415  cleanAppMapCache() {
416    this.mAppMap.clear();
417  }
418}
419
420export const launcherAbilityManager = LauncherAbilityManager.getInstance();
421