/*
* Copyright (c) Huawei Technologies Co., Ltd. 2024-2025. All rights reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { taskpool } from '@kit.ArkTS';
import i18n from '@ohos.i18n';
import emitter from '@ohos.events.emitter';
import bundle from '@ohos.bundle.bundleManager';
import bundleManager from '@ohos.bundle.bundleManager';
import abilityAccessCtrl, { Permissions } from '@ohos.abilityAccessCtrl';
import launcherBundleResource from '@ohos.bundle.launcherBundleManager';
import { LogUtil } from '@ohos/settings.common/src/main/ets/utils/LogUtil';
import { EventBus } from '@ohos/settings.common/src/main/ets/framework/common/EventBus';
import {
EVENT_ID_BUNDLE_LIST_CHANGED,
EVENT_ID_BUNDLE_REMOVE,
EVENT_ID_MORE_APP_LIST_LOADER,
EVENT_ID_BUNDLE_RESOURCES_CHANGED
} from '@ohos/settings.common/src/main/ets/event/types';
import {
BundleStatusChangeListener,
BundleStatusChangeManager,
} from '@ohos/settings.common/src/main/ets/bundle/BundleStatusChangeManager';
import { AppEntry } from './AppModel';
import { sortByName } from './util/AppManager';
import { AppUtils } from './AppUtils';
import { ThemeManager, PageList } from './util/ThemeManager';
import { HdsDrawableTools } from './AppIconProcessing';
import { AppListLoaderManager, createAndCacheApp } from './AppListLoaderManagerTaskPool';
const TAG: string = 'MoreAppListLoader : ';
const TASK_NUMBER: number = 10;
const CONCURRENCY_THRESHOLD: number = 20;
const BLOCK_PERMISSION_LIST: Set<string> = new Set([
'ohos.permission.ANSWER_CALL',
'ohos.permission.MANAGE_VOICEMAIL',
'ohos.permission.READ_CALL_LOG',
'ohos.permission.WRITE_CALL_LOG',
'ohos.permission.GET_INSTALLED_BUNDLE_LIST',
'ohos.permission.READ_CELL_MESSAGES',
'ohos.permission.READ_MESSAGES',
'ohos.permission.RECEIVE_MMS',
'ohos.permission.RECEIVE_SMS',
'ohos.permission.RECEIVE_WAP_MESSAGES',
'ohos.permission.SEND_MESSAGES'
]);
export class MoreAppListLoader {
public lastSystemLocale: string = '';
public isRegister: boolean = false;
public listenerKey: string = 'more_app_list_loader';
private readonly pageName: PageList = PageList.MORE_APP_LIST_PAGE;
private static instance: MoreAppListLoader;
private isFirstLoad: boolean = true;
private entriesCache: Map<string, AppEntry> = new Map();
private isRefreshing: boolean = false;
private lastRemoveBundleName: string = '';
/**
* 获取应用列表加载类单例
*/
static getInstance(): MoreAppListLoader {
if (!MoreAppListLoader.instance) {
MoreAppListLoader.instance = new MoreAppListLoader();
}
return MoreAppListLoader.instance;
}
/**
* 加载更多应用列表
*/
async loadAppList(): Promise<void> {
let isLocaleChanged: boolean = false;
if (this.lastSystemLocale !== i18n.getSystemLocale() ||
ThemeManager.getInstance().checkIsNeedToRefresh(this.pageName)) {
isLocaleChanged = true;
LogUtil.info(`${TAG} loadAppList clear cache`);
}
if (this.isFirstLoad || isLocaleChanged) {
this.refreshAppList();
if (this.isFirstLoad) {
this.isFirstLoad = false;
}
} else {
await this.updateAppList(this.getAppListCache());
}
}
/**
* 获取更多应用列表缓存信息
*
* @return 应用列表缓存信息
*/
getAppListCache(): AppEntry[] {
return Array.from(this.entriesCache.values());
}
/**
* 订阅包资源管理变化事件
*/
public registerStatusChange(): void {
if (!this.isRegister) {
BundleStatusChangeManager.getInstance().registerBundleChangedListener(this.bundleStatusCallback);
this.subscribeBundleResourcesChangeEvents();
this.isRegister = true;
}
}
/**
* 取消订阅包资源管理变化事件
*/
public unRegisterStatusChange(): void {
if (this.isRegister) {
BundleStatusChangeManager.getInstance().unRegisterBundleChangedListener(this.bundleStatusCallback);
this.unSubscribeBundleResourcesChangeEvents();
this.isRegister = false;
}
}
public isRefreshAppList(): boolean {
return this.isRefreshing;
}
public notifyMoreAppListLoader(isDone: boolean) {
emitter.emit({
eventId: EVENT_ID_MORE_APP_LIST_LOADER,
}, {
data: {
isLoadingDone: isDone
}
});
}
private notifyMoreAppListRemoveAll() {
EventBus.getInstance().emit('EVENT_ID_MORE_APP_LIST_REMOVE_ALL');
}
/**
* 清理更多应用列表缓存信息
*
*/
public clearAppListCache(): void {
this.clearEntriesCache();
this.isFirstLoad = true;
}
private async refreshAppList(): Promise<void> {
if (this.isRefreshing) {
LogUtil.warn(`${TAG} refreshAppList is refreshing...`);
return;
}
this.isRefreshing = true;
LogUtil.info(`${TAG} refreshAppList start`);
ThemeManager.getInstance().resetRefreshStatus(this.pageName);
this.clearEntriesCache();
this.notifyMoreAppListLoader(false);
let moreAppList: AppEntry[] = [];
let allBundleShowList: bundle.BundleInfo[] = [];
try {
allBundleShowList = await this.getMoreAppBundleList();
} catch (error) {
LogUtil.info(`${TAG} getMoreAppList faile: ${error?.message}`);
}
try {
moreAppList = await AppListLoaderManager.getSpecAppListWithoutHds(allBundleShowList) as Array<AppEntry>;
} catch (error) {
LogUtil.error(`${TAG} get appList fail, errmsg: ${error?.message}`);
}
this.updateSystemLocale();
this.isRefreshing = false;
this.updateAppList(moreAppList);
}
private updateSystemLocale(): void {
let currentSystemLocale = i18n.System.getSystemLocale();
LogUtil.info(`${TAG} currentSystemLocale: ${currentSystemLocale}`);
if (this.lastSystemLocale !== currentSystemLocale) {
// 语言变化了,清空缓存
LogUtil.info(`${TAG} clear cache`);
this.clearEntriesCache();
this.lastSystemLocale = currentSystemLocale;
}
}
private async updateAppList(appEntryList: AppEntry[]): Promise<void> {
LogUtil.info(`${TAG} sort start`);
try {
appEntryList = sortByName(appEntryList);
} catch (error) {
LogUtil.error(`${TAG} catch exception when sorting, errmsg: ${error?.message}`);
}
LogUtil.info(`${TAG} sort end`);
await Promise.resolve(this.dispatchAppListChanged(appEntryList));
LogUtil.info(`${TAG} refreshAppList dispatchAppListChanged end`);
await Promise.resolve(this.refreshCache(appEntryList));
}
private refreshCache(appList: Array<AppEntry>): void {
this.entriesCache = new Map<string, AppEntry>();
appList.map(entry => this.entriesCache.set(entry.name, entry));
}
private dispatchAppListChanged(appList: Array<AppEntry>): void {
emitter.emit({ eventId: EVENT_ID_BUNDLE_LIST_CHANGED });
}
private dispatchAppRemove(bundleName: string): void {
emitter.emit({
eventId: EVENT_ID_BUNDLE_REMOVE,
}, {
data: {
bundleName: bundleName,
}
});
}
private removeAppEntry(bundleName: string): void {
if (!bundleName) {
LogUtil.error(`${TAG} removeAppEntry bundleName invalid`);
return;
}
LogUtil.info(`${TAG} removeAppEntry enter, bundleName: ${bundleName}`);
let entry = this.entriesCache.get(bundleName);
if (entry) {
HdsDrawableTools.releaseIcon(entry.icon)
this.entriesCache.delete(bundleName);
this.lastRemoveBundleName = bundleName;
LogUtil.info(`${TAG} removeAppEntry func run`);
}
this.dispatchAppRemove(bundleName);
}
private clearEntriesCache() {
if (this.entriesCache.size === 0) {
return;
}
this.entriesCache.forEach((value: AppEntry) => {
// 判断是否为pixelMap,如果是则需要主动释放
HdsDrawableTools.releaseIcon(value.icon);
})
this.entriesCache.clear();
this.notifyMoreAppListRemoveAll();
}
private async addAppEntry(bundleName: string): Promise<void> {
if (!bundleName) {
LogUtil.error(`${TAG} addAppEntry bundleName invalid`);
return;
}
LogUtil.info(`${TAG} addAppEntry enter, bundleName: ${bundleName}`);
this.isFirstLoad = true;
let task: taskpool.Task = new taskpool.Task(createAndCacheApp, bundleName, 0);
let entry: AppEntry = await taskpool.execute(task, taskpool.Priority.MEDIUM) as AppEntry;
let copyEntry: AppEntry = AppEntry.copy(entry);
if (this.lastRemoveBundleName === bundleName) {
this.entriesCache.set(copyEntry.name, copyEntry);
EventBus.getInstance().emit('EVENT_ID_BUNDLE_ADD', copyEntry);
this.lastRemoveBundleName === '';
}
LogUtil.info(`${TAG} addAppEntry : ${copyEntry}`);
}
private readonly bundleStatusCallback: BundleStatusChangeListener = {
getListenerName: (): string => {
return this.listenerKey ?? ' ';
},
onBundleAdd: (bundleName, userId) => {
LogUtil.info(`${TAG} bundleStatusCallback add, bundleName: ${bundleName} userId: ${userId}`);
this.addAppEntry(bundleName);
},
onBundleRemove: (bundleName, userId) => {
LogUtil.info(`${TAG} bundleStatusCallback remove, bundleName: ${bundleName} userId: ${userId}`);
this.removeAppEntry(bundleName);
},
onBundleUpdate: (bundleName, userId) => {
LogUtil.info(`${TAG} bundleStatusCallback update, bundleName: ${bundleName} userId: ${userId}`);
}
};
private subscribeBundleResourcesChangeEvents(): void {
EventBus.getInstance().on(EVENT_ID_BUNDLE_RESOURCES_CHANGED, this.onBundleResourcesChangedCallback);
}
private onBundleResourcesChangedCallback: (isChanged: boolean) => void = (isChanged: boolean) => {
LogUtil.info(`${TAG} bundle resources changed: ${isChanged}`);
if (isChanged) {
this.refreshAppList();
}
};
public unSubscribeBundleResourcesChangeEvents(): void {
EventBus.getInstance().detach(EVENT_ID_BUNDLE_RESOURCES_CHANGED, this.onBundleResourcesChangedCallback);
}
private async getMoreAppBundleList(): Promise<bundleManager.BundleInfo[]> {
LogUtil.info(`${TAG} getAllBundleList`);
let allBundleInfoList: bundleManager.BundleInfo[] = [];
// Step 1: 获取全应用的信息
let result = await this.getAllBundleList();
if (result) {
allBundleInfoList = result[0];
LogUtil.info(`${TAG} getAllBundleList: ${allBundleInfoList.length}`);
}
// Step 2: 过滤缓存中已有桌面应用,更多应用中不展示桌面应用
let launchBundleList = await this.getAllLauncherAbilityInfo();
let bundleInfoList: bundleManager.BundleInfo[] = allBundleInfoList.filter((allBundleObj) => {
if (allBundleObj.appInfo?.bundleType === 1) {
return false;
}
return launchBundleList.every((appInfoObj) => {
return allBundleObj.name !== appInfoObj?.applicationInfo?.name;
})
});
// Step 3: 获取申请权限的应用列表
let reqPermissionsList = await this.getReqPermissionsAppList(bundleInfoList);
// Step 4: 获取可卸载应用列表
let removableList = this.getRemovableAppList(bundleInfoList);
// Step 5: 获取完整展示应用列表
let allBundleShowList = await this.getAllBundleShowList(reqPermissionsList, removableList);
return allBundleShowList;
}
private async getAllLauncherAbilityInfo(): Promise<launcherBundleResource.LauncherAbilityInfo[]> {
const userID: number = await AppUtils.getActivatedAccountLocalId();
LogUtil.info(`${TAG} now userid is: ${userID}`);
let allLauncherBundleLabelList: launcherBundleResource.LauncherAbilityInfo[] = [];
try {
allLauncherBundleLabelList = await launcherBundleResource.getAllLauncherAbilityInfo(userID);
} catch (error) {
LogUtil.info(`${TAG} allLauncherBundleLabelList catch error: ${error?.message}`);
}
return allLauncherBundleLabelList;
}
async getReqPermissionsAppList(bundleInfoList: bundle.BundleInfo[]) {
let reqPermissionsList: bundleManager.BundleInfo[] = [];
//遍历应用权限,根据应用数量决策是否并发遍历
if (bundleInfoList.length > CONCURRENCY_THRESHOLD) {
const bundlesNumberPerTask = bundleInfoList.length / TASK_NUMBER;
let tasks: taskpool.TaskGroup = new taskpool.TaskGroup();
for (let index = 0; index < TASK_NUMBER; index++) {
tasks.addTask(new taskpool.Task(getMoreAppListShowBundleInfo, BLOCK_PERMISSION_LIST, index < TASK_NUMBER - 1
? bundleInfoList.slice(index * bundlesNumberPerTask, (index + 1) * bundlesNumberPerTask)
: bundleInfoList.slice(index * bundlesNumberPerTask)));
}
try {
await taskpool.execute(tasks).then((res: Object[]) => {
for (let index = 0; index < res.length; index++) {
reqPermissionsList.push(...(res[index] as bundle.BundleInfo[]));
}
});
} catch (error) {
LogUtil.error(`${TAG} taskpool error`);
}
} else {
reqPermissionsList = await getMoreAppListShowBundleInfo(BLOCK_PERMISSION_LIST, bundleInfoList);
}
LogUtil.info(`${TAG} reqPermissionsList length :${reqPermissionsList.length}`);
return reqPermissionsList;
}
getAllBundleList(): Promise<[bundleManager.BundleInfo[]]> {
return Promise.all([
bundleManager.getAllBundleInfo(
bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION |
bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_HAP_MODULE |
bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_REQUESTED_PERMISSION |
bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_DISABLE)
])
}
getRemovableAppList(allBundleResourceList: bundle.BundleInfo[]): bundle.BundleInfo[] {
let bundleInfo: bundle.BundleInfo[] = [];
for (const resourceList of allBundleResourceList) {
if (resourceList?.appInfo?.removable) {
bundleInfo.push(resourceList);
}
}
return bundleInfo;
}
async getAllBundleShowList(reqPermissionsList: bundle.BundleInfo[], removableList: bundle.BundleInfo[]):
Promise<bundle.BundleInfo[]> {
let appInfoList: bundle.ApplicationInfo[] = [];
let flag = bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION |
bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_HAP_MODULE |
bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_REQUESTED_PERMISSION |
bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_DISABLE;
try {
appInfoList = await bundle.getAllApplicationInfo(flag, 0);
} catch (error) {
LogUtil.info(`${TAG} getUserAppList failed: ${error?.message}`);
}
let allBundleShowList = Array.from(new Set(reqPermissionsList.concat(removableList)));
return allBundleShowList.filter((allBundleObj) =>
appInfoList.every((appInfoObj) => allBundleObj.name !== appInfoObj.name));
}
}
@Concurrent
async function getMoreAppListShowBundleInfo(bolockPermissionList: Set<string>,
allBundleResourceList: bundle.BundleInfo[]): Promise<bundle.BundleInfo[]> {
let bundleInfo: bundle.BundleInfo[] = [];
let atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager();
for (const resourceList of allBundleResourceList) {
let reqPermissionDetails = resourceList.reqPermissionDetails;
for (const permissionDetails of reqPermissionDetails) {
//非敏感权限无需展示,grantMode为0且permissionname不在bolockPermissionList里的为敏感权限
if (bolockPermissionList.has(permissionDetails.name)) {
continue;
}
let grantMode: number = 1;
try {
let permissionDef = await bundleManager.getPermissionDef(permissionDetails.name);
grantMode = permissionDef.grantMode;
} catch (err) {
LogUtil.info(`getPermissionDef failed: ${err?.message}`);
}
if (grantMode !== 0) {
continue;
}
//!isPermissionSystemFixed代表为可修改权限,需要展示
let isPermissionSystemFixed: boolean = false;
try {
let flags: number = await atManager
.getPermissionFlags(resourceList.appInfo.accessTokenId, permissionDetails.name as Permissions);
if (flags === 1 << 2) {
isPermissionSystemFixed = true;
}
} catch (err) {
LogUtil.info(`getPermissionFlags failed: ${err?.message}`);
}
if (!isPermissionSystemFixed) {
bundleInfo.push(resourceList);
break;
}
}
}
return bundleInfo;
}