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}