/*
* 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 i18n from '@ohos.i18n';
import bundle from '@ohos.bundle.bundleManager';
import configPolicy from '@ohos.configPolicy';
import fs from '@ohos.file.fs';
import deviceInfo from '@ohos.deviceInfo';
// import search from '@hms.ai.search';
import type common from '@ohos.app.ability.common';
import taskpool from '@ohos.taskpool';
import { JSON } from '@kit.ArkTS';
import { bundleResourceManager } from '@kit.AbilityKit';
import {
BundleStatusChangeListener,
BundleStatusChangeManager
} from '@ohos/settings.common/src/main/ets/bundle/BundleStatusChangeManager';
import { LogUtil } from '@ohos/settings.common/src/main/ets/utils/LogUtil';
import SearchItemInfoManager from '@ohos/settings.common/src/main/ets/data/SearchItemInfoManager';
import SearchItemOperationManager from '@ohos/settings.common/src/main/ets/data/SearchItemOperationManager';
import { SearchItemOperation } from '@ohos/settings.common/src/main/ets/data/types';
import { Constants, PackagesConstant } from '@ohos/settings.common/src/main/ets/constant/PackagesConstant';
import { PreferencesUtil } from '@ohos/settings.common/src/main/ets/utils/PreferencesUtil';
import { DeviceUtil } from '@ohos/settings.common/src/main/ets/utils/BaseUtils';
import { HiSysEventUtil } from '@ohos/settings.common/src/main/ets/systemEvent/HiSysEventUtil';
import { CheckEmptyUtils } from '@ohos/settings.common/src/main/ets/utils/CheckEmptyUtils';
import { AbilityUtils } from '@ohos/settings.common/src/main/ets/utils/AbilityUtils';
import { ContextUtils } from '@ohos/settings.common/src/main/ets/utils/baseutils/ContextUtils';
import { NavEntryKey } from '@ohos/settings.common/src/main/ets/utils/Consts';
import { SearchItem, SearchPage, SearchPageConfig } from '../model/SearchPageConfig';
import { ExternalInfo, ExternalInfoConfig } from '../model/ExternalInfoConfig';
import {
HideMenu,
ItemInfo,
RenderItemChildrenInfo,
RenderItemInfo,
ResultItemInfo,
} from '../model/type';
import { SearchConfigFileParser } from '../util/SearchConfigFileParser';
import { PageInfo, PageInfoConfig } from '../model/PageInfoConfig';
import { SettingPageDescController } from './SettingPageDescController';
import { UpgradeConfig, UpgradeInfo } from '../model/UpgradeConfig';
import lazy { CheckItemUtil } from '../util/CheckItemUtil';
import lazy { SearchResourceManager } from './SearchResourceManager';
import lazy { HideSearchItemsConfig } from '../model/HideSearchItemsConfig';
import { initExternalSearchData } from '../util/SearchTaskPoolManager';
import { ResourceManagerUtil } from '@ohos/settings.common/src/main/ets/utils/ResourceManagerUtil';
import {
HighlightIndex,
HIGHLIGHT_DEFAULT_INDEX,
highLightKeyWord,
isMatchKeyWord
} from '@ohos/settings.common/src/main/ets/utils/ApplicationUtil';
import { SearchRDBController } from './SearchRDBController';
import { SearchSecurityUtil } from '../util/SearchSecurityUtil';
const TAG: string = 'SearchController';
const phoneSearchConfigPath: string = 'searchConfig/phone/searchPage.json';
const padSearchConfigPath: string = 'searchConfig/pad/searchPage.json';
const pcSearchConfigPath: string = 'searchConfig/phone/searchPage.json';
const externalConfigPath: string = 'searchConfig/externalInfo.json'
const ccmPageConfigPath: string = 'etc/settings/hide_menu.json';
const pageInfoConfigPath: string = 'searchConfig/pageInfo.json';
const UPGRADE_CONFIG: string = 'searchConfig/upgrade.json';
const BUNDLE_NOT_FOUND: number = 17700001;
const BUNDLE_DISABLED: number = 17700026;
export class SearchDataController {
public static searchController: SearchDataController | undefined = undefined;
private static latestRdbVersion: number = 0;
private static localeChangingSet: Set<string> = new Set();
private externalPageConfigCache: ExternalInfoConfig | undefined;
private readonly bundleStatusCallback: BundleStatusChangeListener = {
onBundleAdd: (bundleName, userId, appIndex) => {
LogUtil.info(`${TAG} bundleStatusCallback add : bundleName:${bundleName} userId:${userId} appIndex:${appIndex}`);
SearchDataController.getInstance().updateSearchItemInfo(bundleName, true);
},
onBundleRemove: (bundleName, userId, appIndex) => {
LogUtil.info(`${TAG} bundleStatusCallback remove : bundleName:${bundleName} userId:${userId} appIndex:${appIndex}`);
SearchDataController.getInstance().updateSearchItemInfo(bundleName, false);
},
onBundleUpdate: (bundleName, userId, appIndex) => {
LogUtil.info(`${TAG} bundleStatusCallback update : bundleName:${bundleName} userId:${userId} appIndex:${appIndex}`);
},
getListenerName: (): string => {
return TAG;
}
};
public static getInstance(): SearchDataController {
if (SearchDataController.searchController) {
return SearchDataController.searchController;
}
SearchDataController.searchController = new SearchDataController();
return SearchDataController.searchController;
}
public static async initSearchTable(): Promise<void> {
LogUtil.showInfo(TAG, 'start initSearchTable');
let rdbStore = await SearchItemInfoManager.getRdbStore();
if (!rdbStore) {
LogUtil.showError(TAG, 'initSearchTable get rdbStore error');
return;
}
// 初始化操作记录表
let rdbStoreOp = await SearchItemOperationManager.getRdbStore();
if (!rdbStoreOp) {
LogUtil.showError(TAG, 'initSearchItemOperationTable get rdbStore error');
return;
}
let property: string = `ItemInfo${i18n.System.getSystemLocale()}${Constants.ITEMINFO_TABLE_VERSION}`;
if (await PreferencesUtil.has(property)) {
LogUtil.showInfo(TAG, 'search table has already init');
return;
}
let versionInDb: number = rdbStore.version;
let upgradeSqlList: string[] = await SearchDataController.getUpgradeSql(versionInDb);
LogUtil.showInfo(TAG, `current dbVersion: ${versionInDb}, latestVersion: ${SearchDataController.latestRdbVersion}`);
/* instrument ignore if*/
if (SearchDataController.latestRdbVersion > versionInDb && upgradeSqlList.length > 0) {
await SearchItemInfoManager.upgradeDb(upgradeSqlList, SearchDataController.latestRdbVersion);
}
}
// instrument ignore next
static async updateSearchData(): Promise<void> {
LogUtil.info(`${TAG} start update SearchData to DataBase. updateSearchData`);
if (await PreferencesUtil.has(`InitUpdateItemInfo${Constants.ITEMINFO_TABLE_VERSION}`)) {
LogUtil.info(`${TAG} InitUpdateItemInfo has done`);
return;
}
await SearchItemInfoManager.updateSearchItemData();
await PreferencesUtil.put(`InitUpdateItemInfo${Constants.ITEMINFO_TABLE_VERSION}`, 'done');
}
/**
* 清空搜索数据
* 版本号更新时会执行:清空表并清除初始化标记,使 initSearchData 能重新写入完整数据
* @returns
*/
static async deleteSearchData(): Promise<void> {
LogUtil.info(`${TAG} start delete SearchData`);
if (await PreferencesUtil.has(`ClearItemInfoTable${Constants.ITEMINFO_TABLE_VERSION}`)) {
LogUtil.info(`${TAG} ClearItemInfoTable has done`);
return;
}
await SearchItemInfoManager.clearSearchData();
// 清空表后必须清除 ItemInfo 的初始化标记,否则 initSearchData 会误判为已完成而跳过重新写入
let curLocale: string = i18n.System.getSystemLocale();
await PreferencesUtil.delete(`ItemInfo${curLocale}${Constants.ITEMINFO_TABLE_VERSION}`);
await PreferencesUtil.put(`ClearItemInfoTable${Constants.ITEMINFO_TABLE_VERSION}`, 'done');
}
/**
* 初始化搜索数据到数据库
* @returns
*/
static async initSearchData(): Promise<void> {
LogUtil.info(`${TAG} start init SearchData to DataBase`);
let curLocale: string = i18n.System.getSystemLocale();
LogUtil.info(`${TAG} start init SearchData to DataBase ${curLocale}`);
// await SearchRDBController.initData()
await SearchRDBController.initialize()
if (await PreferencesUtil.has(`ItemInfo${curLocale}${Constants.ITEMINFO_TABLE_VERSION}`)) {
LogUtil.info(`${TAG} ${curLocale}${Constants.ITEMINFO_TABLE_VERSION} has done`);
return;
}
if (SearchDataController.localeChangingSet.has(curLocale)) {
LogUtil.showInfo(TAG, 'current locale search item is updating');
return;
}
SearchDataController.localeChangingSet.add(curLocale);
await SearchItemInfoManager.deleteSearchItem(curLocale);
let hideSearchConfig: HideSearchItemsConfig = await SearchDataController.getInstance().getHideSearchConfig();
let searchResourceManager: SearchResourceManager = new SearchResourceManager(curLocale);
await SearchDataController.initInternalSearchData(hideSearchConfig, curLocale, searchResourceManager);
await SearchDataController.initExternalSearchData(curLocale);
await SearchDataController.updateOperationSearchItem();
await PreferencesUtil.put(`ItemInfo${curLocale}${Constants.ITEMINFO_TABLE_VERSION}`, 'done');
SearchDataController.localeChangingSet.delete(curLocale);
}
// instrument ignore next
private static async initInternalSearchData(hideSearchConfig: HideSearchItemsConfig,
curLocale: string, resourceManager: SearchResourceManager): Promise<void> {
let searchPageConfig: SearchPageConfig | undefined = await SearchConfigFileParser.getInstance()
.parserSearchPageConfig(DeviceUtil.isDevicePc() ? pcSearchConfigPath : DeviceUtil.isDevicePad() ?
padSearchConfigPath : phoneSearchConfigPath);
LogUtil.info(`${TAG} searchPageConfig content`);
if (!searchPageConfig) {
LogUtil.warn(`${TAG} parse ${phoneSearchConfigPath} not find`);
return;
}
try {
LogUtil.info(`${TAG} getSearchPage size:${searchPageConfig.searchPages.length}`);
for (let searchPage of searchPageConfig.searchPages) {
if (hideSearchConfig.menuEntryList.includes(searchPage.entryKey)) {
LogUtil.warn(`${TAG} searchPage need hide`);
continue;
}
LogUtil.info(`${TAG} internal search page entrykey:${searchPage.entryKey}`);
let itemInfoList = await SearchDataController.getItemInfoList(searchPage, curLocale, resourceManager);
LogUtil.info(`${TAG} itemInfoList: ${itemInfoList?.length}`);
if (itemInfoList?.length > 0) {
await SearchItemInfoManager.batchInsertSearchItemData(itemInfoList, hideSearchConfig.getItemList());
}
}
HiSysEventUtil.searchReportEvent(TAG, 'initSearchData success');
} catch (error) {
LogUtil.error(`${TAG} parse error ${error?.message}`);
HiSysEventUtil.searchReportEvent(TAG, `json file parse error ${error?.message}`);
}
}
private static async initExternalSearchData(curLocale: string): Promise<void> {
LogUtil.info(`${TAG} start init SearchData for external to DataBase. initExternalSearchData`);
let externalConfig: ExternalInfoConfig | undefined = await SearchConfigFileParser.getInstance()
.parserExternalInfoConfig(externalConfigPath);
LogUtil.info(`${TAG} externalConfig content: ${externalConfigPath}`);
if (externalConfig) {
try {
let context: common.Context = ContextUtils.getContext();
let task: taskpool.Task =
new taskpool.Task(initExternalSearchData, externalConfig.externalInfos, curLocale, context);
await taskpool.execute(task, taskpool.Priority.MEDIUM);
} catch (error) {
LogUtil.error(`${TAG} catch exception when initExternalSearchData, errcode: ${error?.code}, errmsg: ${error?.message}`);
}
}
LogUtil.info(`${TAG} init SearchData for external to DataBase end. initExternalSearchData`);
}
/**
* 加载操作历史记录中的搜索项,并更新回数据库中
*/
private static async updateOperationSearchItem(): Promise<void> {
LogUtil.info(`${TAG} updateOperationSearchItem start.`);
try {
// 读取操作历史记录表
let operationData: SearchItemOperation[] = await SearchItemOperationManager.querySearchItemOperationData();
LogUtil.info(`${TAG} Query SearchItemOperation List size: ${operationData.length}`);
// 配置文件中不存在的,需要额外加入到入库项中
for (let itemOperation of operationData) {
let itemInfo = itemOperation;
if (itemOperation.status === 'delete') {
itemInfo.enable = false;
} else {
itemInfo.enable = (itemOperation.content === '1');
}
// 更新搜索项中的值
await SearchItemInfoManager.insertOrUpdateSearchItemData(itemInfo);
}
} catch (error) {
LogUtil.error(`${TAG} updateOperationSearchItem error, errcode: ${error?.code}, errmsg: ${error?.message}`);
}
LogUtil.info(`${TAG} updateOperationSearchItem end.`);
}
private static async getItemInfoList(searchPage: SearchPage, curLocale: string,
resourceManager: SearchResourceManager, bundleName?: string, context?: common.Context): Promise<Array<ItemInfo>> {
let searchItemList: SearchItem[] = searchPage.searchItems;
LogUtil.info(`${TAG} searchItemList: ${searchItemList?.length} ${searchPage.supportDevices}`);
LogUtil.info(`${TAG} hylhly-----searchItemList: ${CheckEmptyUtils.isEmpty(searchItemList)} ${SearchDataController.checkIsDeviceSupport(searchPage.supportDevices)}`);
let itemInfoList: ItemInfo[] = [];
/* instrument ignore if*/
if (CheckEmptyUtils.isEmpty(searchItemList) ||
!SearchDataController.checkIsDeviceSupport(searchPage.supportDevices)) {
LogUtil.warn(`${TAG} searchItemList is not supported or empty`);
return itemInfoList;
}
for (let searchItem of searchItemList) {
LogUtil.info(`${TAG} searchItem.itemTitle: ${searchItem.itemTitle} ${searchItem.supportDevices}`);
/* instrument ignore if*/
if (!SearchDataController.checkIsDeviceSupport(searchItem.supportDevices)) {
LogUtil.warn(`${TAG} searchItem is not supported`);
continue;
}
const resolvedItemTitle: string = searchItem.itemTitle === '$string:time_24_hour_title' ?
resourceManager.getNumberFormatStringSync($r('app.string.time_24_hour_title'), '24') :
bundleName ? resourceManager.getExternalStringByName(bundleName, searchItem.itemTitle) :
resourceManager.getStringByName(searchItem.itemTitle);
let itemInfo: ItemInfo = {
locale: curLocale,
itemName: searchItem.itemName,
itemTitle: resolvedItemTitle,
itemDescription: searchItem.itemDescription,
entryKey: searchPage.entryKey,
pageTitle: bundleName ? resourceManager.getExternalStringByName(bundleName, searchPage.pageTitle) :
resourceManager.getStringByName(searchPage.pageTitle),
params: searchPage.params,
enable: await (context ? CheckItemUtil.checkItemEnable(searchItem, context) :
CheckItemUtil.checkItemEnable(searchItem)),
icon: searchItem.icon,
alias: searchItem.alias ?? '',
path: resourceManager.getPathByName(bundleName as string, searchPage.path, bundleName !== undefined),
childItems: searchItem.childItems,
bundleName: bundleName ?? PackagesConstant.SETTINGS_BUNDLE_NAME,
checkType: searchItem.checkItem?.type ?? '',
checkKey: searchItem.checkItem?.checkKey ?? '',
checkValue: searchItem.checkItem?.value ?? '',
};
LogUtil.debug(`${TAG} itemInfo`);
itemInfoList.push(itemInfo);
}
return itemInfoList;
}
// instrument ignore next
public static async processExternalItemInfoList(externalBundle: ExternalInfo, curLocale: string,
hideSearchConfig: HideSearchItemsConfig, resourceManager: SearchResourceManager,
context?: common.Context): Promise<void> {
let bundleInfo: bundle.BundleInfo;
try {
bundleInfo = bundle.getBundleInfoSync(externalBundle.bundleName, bundle.BundleFlag
.GET_BUNDLE_INFO_WITH_APPLICATION | bundle.BundleFlag.GET_BUNDLE_INFO_WITH_METADATA);
if (!bundleInfo?.appInfo?.systemApp) {
LogUtil.error(`${TAG} processExternalItemInfoList ${externalBundle.bundleName} is not system app`);
return;
}
} catch (err) {
LogUtil.error(`${TAG} processExternalItemInfoList failed, code: ${err?.code}, message: ${err?.message}.`);
return;
}
const findMetaData = bundleInfo?.appInfo?.metadataArray?.find(metaData => metaData.moduleName === externalBundle
.moduleName);
if (!findMetaData) {
LogUtil.error(`${TAG} processExternalItemInfoList failed, not find meta data`);
return;
}
const externalSearchConfig = findMetaData?.metadata?.find(meta => meta.name === 'action.settings.search.path');
if (!externalSearchConfig) {
LogUtil.error(`${TAG} processExternalItemInfoList failed, not find search config`);
return;
}
let externalSearchPageConfig: SearchPageConfig | undefined = await SearchConfigFileParser.getInstance()
.parseExternalSearchPageConfig(externalBundle.bundleName, externalSearchConfig?.value);
LogUtil.info(`${TAG} externalSearchPageConfig content`);
if (!externalSearchPageConfig) {
LogUtil.error(`${TAG} processExternalItemInfoList failed, externalConfig not found`);
return;
}
try {
LogUtil.info(`${TAG} getExternalSearchPage Size:${externalSearchPageConfig.searchPages.length}`);
for (let externalSearchPage of externalSearchPageConfig.searchPages) {
LogUtil.info(`${TAG} externalSearchPage entrykey:${externalSearchPage.entryKey}`);
if (hideSearchConfig.menuEntryList.includes(externalSearchPage.entryKey)) {
LogUtil.info(`${TAG} externalSearchPage ${externalSearchPage.entryKey} need hide`);
continue;
}
let externalItemInfoList = await SearchDataController.getItemInfoList(externalSearchPage, curLocale,
resourceManager, externalBundle.bundleName, context);
LogUtil.info(`${TAG} externalItemInfoList: ${externalItemInfoList?.length}`);
if (externalItemInfoList.length > 0) {
await SearchItemInfoManager.batchInsertSearchItemData(externalItemInfoList, hideSearchConfig.itemList,
context);
}
}
HiSysEventUtil.searchReportEvent(TAG, 'initExternalSearchData success');
} catch (error) {
LogUtil.error(`${TAG} parse error ${error?.message}`);
HiSysEventUtil.searchReportEvent(TAG, `json file parse error ${error?.message}`);
}
}
async getSearchResult(value: string,
queryMetaService?: (value: string) => RenderItemInfo[]): Promise<RenderItemInfo[]> {
try {
const result = await SearchRDBController.queryAllItems(value)
// 3. 处理查询结果
LogUtil.info(TAG + 'getSearchResult' + JSON.stringify(result))
// 高亮使用与 DB 查询相同的过滤后关键词,否则带标点等输入(如 ",蓝牙")无法正确高亮
const sanitizedValue = SearchSecurityUtil.sanitizeSearchInput(value);
let retResult: RenderItemInfo[] = [];
if (result?.length) {
let resList = result.filter((item) => item.locale === i18n.System.getSystemLocale());
retResult = await SearchDataController.getInstance().getSearchItemInfo(resList, sanitizedValue);
}
await SearchDataController.getInstance().initApplicationResult(value, retResult, queryMetaService);
// instrument ignore next
retResult.sort((firstItem, secondItem) => {
if (firstItem.isOuterApp && secondItem.isOuterApp) {
return 0;
}
if (firstItem.isOuterApp) {
return 1;
}
if (secondItem.isOuterApp) {
return -1;
}
return 0;
});
retResult.sort((firstItem, secondItem) => {
if (firstItem.isOuterApp && secondItem.isOuterApp) {
return 0;
}
if (firstItem.isOuterApp) {
return 1;
}
if (secondItem.isOuterApp) {
return -1;
}
return 0;
});
return retResult;
} catch (error) {
LogUtil.error(`${TAG} getSearchResult error: ${error?.message}`);
}
return new Promise((res) => {
let defaultResult: RenderItemInfo[] = [];
res(defaultResult);
});
}
private async initApplicationResult(value: string, retResult: RenderItemInfo[],
queryMetaService?: (value: string) => RenderItemInfo[]): Promise<void> {
/* instrument ignore if*/
if (!queryMetaService) {
LogUtil.showWarn(TAG, 'no queryMetaService');
return;
}
LogUtil.showInfo(TAG, 'start query metaService');
let serviceResults: RenderItemInfo[] = queryMetaService(value);
retResult.push(...serviceResults)
}
// private async initAppSearchResult(value: string, retResult: RenderItemInfo[],
// searchSession: search.SearchSession): Promise<void> {
// try {
// let appQuery: search.Query = DeviceUtil.isDevicePc() ? SearchAppsDataConfig.getPcSearchQuery(value)
// : SearchAppsDataConfig.getSearchQuery(value);
// let appResult: search.SearchResult<ResultItemInfo> = await searchSession.search(appQuery);
// LogUtil.info(`${TAG} appResult: ${JSON.stringify(appResult)}`);
// if (appResult.records?.length) {
// let appSearchRenders: RenderItemInfo[] = SearchDataController.getInstance().appResult2RenderItemInfo(appResult);
// retResult.push(...appSearchRenders)
// }
// } catch (e) {
// LogUtil.error(`${TAG} initAppSearchResult error: ${e?.code}`);
// }
// }
// private appResult2RenderItemInfo(appResult: search.SearchResult<ResultItemInfo>): RenderItemInfo[] {
// let ret: RenderItemInfo[] = [];
// for (let item of appResult.records) {
// /* instrument ignore if*/
// if (!item || this.isRequiredFilter(item)) {
// continue;
// }
// // pc上分身应用的名称需要加上应用id
// let appName: string =
// `${item.appName}${DeviceUtil.isDevicePc() && item.appIndex && item.appIndex > 0 ? item.appIndex : ''}`;
// let renderItem: RenderItemInfo = {
// entryKey: item.bundleName,
// isOuterApp: false,
// children: [{
// title: appName.split(new RegExp(`<|>`, 'gi')),
// entryKey: item.bundleName,
// path: '',
// parentKey: '',
// itemName: '',
// params: '',
// isApp: true,
// appName: appName.split(new RegExp(`<em>|</em>`, 'gi')).join(''),
// appIndex: item.appIndex,
// isOuterApp: false,
// installSource: item.installSource
// }]
// }
// ret.push(renderItem);
// }
// return ret;
// }
// private isRequiredFilter(item: ResultItemInfo): boolean {
// if (item.appStatus !== 0) {
// LogUtil.info(`${TAG} app not installed: ${item.bundleName}`);
// return true;
// }
// if (item.bundleName === 'com.ohos.contacts' && item.typeId === 0 && item.shortcutId === '') {
// LogUtil.debug(`${TAG} filter contacts app`);
// return true;
// }
// if (item.bundleName === 'com.ohos.vassistant') {
// LogUtil.debug(`${TAG} filter xiaoyi app`);
// return true;
// }
// return false;
// }
public registerStatusChange(): void {
BundleStatusChangeManager.getInstance().registerBundleChangedListener(this.bundleStatusCallback);
}
public unRegisterStatusChange(): void {
BundleStatusChangeManager.getInstance().unRegisterBundleChangedListener(this.bundleStatusCallback);
}
public async updateRemovableBundles(): Promise<void> {
LogUtil.info(`${TAG} updateRemovableBundles start`);
if (!this.externalPageConfigCache) {
this.externalPageConfigCache = await SearchConfigFileParser.getInstance()
.parserExternalInfoConfig(externalConfigPath);
}
if (!this.externalPageConfigCache) {
LogUtil.showError(TAG, 'updateRemovableBundles failed, parserExternalInfoConfig fail');
return;
}
this.externalPageConfigCache.externalInfos.forEach(bundleInfo => {
if (bundleInfo && bundleInfo.removable) {
try {
bundle.getBundleInfoSync(bundleInfo.bundleName, bundle.BundleFlag.GET_BUNDLE_INFO_DEFAULT);
this.updateSearchItemInfo(bundleInfo.bundleName, true);
} catch (err) {
if (err?.code === BUNDLE_NOT_FOUND || err?.code === BUNDLE_DISABLED) {
this.updateSearchItemInfo(bundleInfo.bundleName, false);
}
}
}
});
}
public async updateSearchItemStatus(itemName: string, enable: boolean): Promise<void> {
if (CheckEmptyUtils.isEmpty(itemName)) {
LogUtil.showError(TAG, 'updateSearchItemStatus failed, itemName is empty');
return;
}
await SearchItemInfoManager.updateSearchItem(itemName, enable);
}
public async updateSearchEntryStatus(entryKey: string, enable: boolean): Promise<void> {
if (CheckEmptyUtils.isEmpty(entryKey)) {
LogUtil.showError(TAG, 'updateSearchEntryStatus failed, entryKey is empty');
return;
}
await SearchItemInfoManager.updateSearchEntry(entryKey, enable);
}
public async initExternalSearchDataWithBundleName(bundleName: string): Promise<void> {
if (CheckEmptyUtils.isEmpty(bundleName)) {
LogUtil.error(`${TAG} initExternalSearchDataWithBundleName failed, bundleName is empty`);
return;
}
let externalConfig: ExternalInfoConfig | undefined = await SearchConfigFileParser.getInstance()
.parserExternalInfoConfig(externalConfigPath);
LogUtil.info(`${TAG} externalConfig content: ${externalConfigPath}`);
if (externalConfig) {
for (let bundle of externalConfig.externalInfos) {
if (bundle.bundleName !== bundleName) {
continue;
}
LogUtil.info(`${TAG} initExternalSearchDataWithBundleName, bundleName: ${bundleName}`);
let curLocale: string = i18n.System.getSystemLocale();
let hideSearchConfig: HideSearchItemsConfig = await SearchDataController.getInstance().getHideSearchConfig();
let searchResourceManager: SearchResourceManager = new SearchResourceManager(curLocale);
await SearchDataController.processExternalItemInfoList(bundle, curLocale, hideSearchConfig,
searchResourceManager);
}
AbilityUtils.timeoutForceGC(1000, 'SearchController->initExternalSearchDataWithBundleName');
}
}
/**
* 修改子项的搜索使能
*
* @param itemName parent
* @param enable true:可以搜索
* @param checkList 需要重新使能的子项列表
*/
public async updateChildItemSearchStatus(itemName: string, enable: boolean, checkList?: string[]): Promise<void> {
if (CheckEmptyUtils.isEmpty(itemName)) {
LogUtil.showError(TAG, 'updateChildItemSearchStatus failed, itemName is empty');
return;
}
let resultArr = await SearchItemInfoManager.queryChildItems(itemName);
if (CheckEmptyUtils.isEmptyArr(resultArr)) {
LogUtil.showError(TAG, 'updateChildItemSearchStatus failed, childItems is empty');
return;
}
for (let item of resultArr) {
await SearchItemInfoManager.updateSearchItem(item, enable);
}
this.updateChildSearchStatusUseFunction(enable, checkList);
}
private async updateChildSearchStatusUseFunction(enable: boolean, checkList?: string[]): Promise<void> {
if (checkList && enable) {
for (let item of checkList) {
let checkEnable = await CheckItemUtil.checkChildItemEnable(item, enable);
SearchItemInfoManager.updateSearchItem(item, checkEnable);
}
}
}
public async getHideSearchConfig(): Promise<HideSearchItemsConfig> {
let ccmConfigStr = await this.getCcmPageConfigStr();
let hideSearchConfig: HideSearchItemsConfig = new HideSearchItemsConfig();
if (CheckEmptyUtils.checkStrIsEmpty(ccmConfigStr)) {
LogUtil.showInfo(TAG, 'Hide menu ccm config is empty');
return hideSearchConfig;
}
let hideMenuList: HideMenu[] = [];
try {
hideMenuList = JSON.parse(ccmConfigStr) as HideMenu[];
} catch (err) {
LogUtil.showError(TAG, `parse ccm config error: ${err?.message}`);
}
if (CheckEmptyUtils.isEmpty(hideMenuList)) {
LogUtil.showInfo(TAG, 'Hide menu list is empty');
return hideSearchConfig;
}
LogUtil.debug(`${TAG} hideMenus :${hideMenuList?.length}`);
let pageInfoConfig: PageInfoConfig | undefined = await SearchConfigFileParser.getInstance()
.parserPageInfoConfig(pageInfoConfigPath);
return SearchDataController.getInstance().getEntryKeyList(pageInfoConfig?.pageInfos, hideMenuList);
}
private async getSearchItemInfo(resList: ResultItemInfo[], searchValue: string): Promise<RenderItemInfo[]> {
let parentMap = new Map<string, RenderItemInfo>();
for (let item of resList) {
if (CheckEmptyUtils.checkStrIsEmpty(item.title)) {
continue;
}
if (!item.appStatus) {
continue;
}
let newItem: RenderItemChildrenInfo;
let topLevelEntry = '';
let isFromOuter: boolean = (item?.callbackType === 1 && item.wantCallback !== undefined);
LogUtil.showInfo(TAG, `isFromOuter ${isFromOuter}`);
if (isFromOuter) {
let outerNewItem: RenderItemChildrenInfo | undefined = SearchDataController.getInstance()
.getSearchChildrenItemInfoForOuter(item, searchValue);
if (!outerNewItem) {
continue;
}
newItem = outerNewItem;
topLevelEntry = outerNewItem.entryKey;
} else {
newItem = SearchDataController.getInstance()
.getSearchChildrenItemInfo(item, searchValue);
if (item.entryKey === NavEntryKey.OUC_SOFTWARE_UPDATE_SETTINGS) {
topLevelEntry = NavEntryKey.ABOUT_DEVICE_ENTRY;
} else {
topLevelEntry = await SettingPageDescController.getTopLevelEntryKey(newItem.entryKey);
}
}
if (CheckEmptyUtils.checkStrIsEmpty(topLevelEntry)) {
LogUtil.showWarn(TAG, `topLevelEntry is empty, entryKey: ${newItem.entryKey}`);
continue;
}
let parentMapItem: RenderItemInfo | undefined = parentMap.get(topLevelEntry);
if (parentMapItem) {
parentMapItem.children.push(newItem)
} else {
parentMap.set(topLevelEntry, {
entryKey: topLevelEntry,
isOuterApp: isFromOuter,
children: [newItem],
appName: item.appName
})
}
}
return Array.from(parentMap.values());
}
private async getCcmPageConfigStr(): Promise<string> {
try {
let filePath: string = await configPolicy.getOneCfgFile(ccmPageConfigPath);
return fs.readTextSync(filePath);
} catch (e) {
LogUtil.error(`${TAG} loadCcmConfigs exception:${e?.code} ${e?.message}`);
}
return '';
}
private findSubMenus(pageInfoList: PageInfo[] | undefined, entryKey: string): string[] {
let subMenus: PageInfo[] = [];
if (pageInfoList) {
subMenus = pageInfoList.filter(pageInfo => pageInfo.parentKey === entryKey);
}
const menuList = [entryKey];
for (let i = 0; i < subMenus.length; i++) {
const subMenu = subMenus[i];
menuList.push(...this.findSubMenus(pageInfoList, subMenu.entryKey));
}
return menuList;
}
private getEntryKeyList(pageInfoList: PageInfo[] | undefined, menuList: HideMenu[]): HideSearchItemsConfig {
let hideSearchConfig: HideSearchItemsConfig = new HideSearchItemsConfig();
for (let i = 0; i < menuList.length; i++) {
if (CheckEmptyUtils.isEmptyArr(menuList[i].itemName)) {
hideSearchConfig.menuEntryList.push(...this.findSubMenus(pageInfoList, menuList[i].key));
} else {
hideSearchConfig.itemList.push(...menuList[i].itemName);
}
}
return hideSearchConfig;
}
private async updateSearchItemInfo(bundleName: string, isEnable: boolean) {
LogUtil.info(`${TAG} updateSearchItemInfo start ${bundleName}`);
if (!this.externalPageConfigCache) {
this.externalPageConfigCache = await SearchConfigFileParser.getInstance()
.parserExternalInfoConfig(externalConfigPath);
}
LogUtil.debug(`${TAG} externalConfig content: ${externalConfigPath}`);
if (this.externalPageConfigCache) {
const findBundleInfo =
this.externalPageConfigCache.externalInfos.find(bundleInfo => bundleInfo.bundleName === bundleName);
if (findBundleInfo) {
SearchItemInfoManager.updateSearchItemByBundle(bundleName, isEnable);
}
}
}
private getSearchChildrenItemInfo(item: ResultItemInfo, searchValue: string): RenderItemChildrenInfo {
const titleWithHighlight = this.buildTitleWithHighlight(item.title, searchValue);
return {
title: titleWithHighlight,
entryKey: item.entryKey,
path: CheckEmptyUtils.checkStrIsEmpty(item.path) ? item.title.split(new RegExp(`<em>|</em>`, 'gi'))
.join('') : item.path,
parentKey: item.parentKey,
params: item.uriParams,
itemName: item.itemName,
isOuterApp: false
};
}
/**
* 构建带高亮标记的标题数组,用于搜索结果高亮显示
* 支持:中文/英文直接匹配
*/
private buildTitleWithHighlight(title: string, searchValue: string): string[] {
if (CheckEmptyUtils.checkStrIsEmpty(searchValue)) {
return title.split(new RegExp(`<|>`, 'gi'));
}
const highlightIndex: HighlightIndex = { startIndex: HIGHLIGHT_DEFAULT_INDEX, endIndex: HIGHLIGHT_DEFAULT_INDEX };
if (isMatchKeyWord(title, searchValue, highlightIndex)) {
const highlighted = highLightKeyWord(title, searchValue, highlightIndex);
if (highlighted.length > 0) {
return highlighted;
}
}
return title.split(new RegExp(`<|>`, 'gi'));
}
private getSearchChildrenItemInfoForOuter(item: ResultItemInfo, searchValue: string): RenderItemChildrenInfo | undefined {
if (item.wantCallback) {
let wantCallBack = JSON.parse(item.wantCallback) as Want;
if (CheckEmptyUtils.checkStrIsEmpty(item.appName) &&
!CheckEmptyUtils.checkStrIsEmpty(wantCallBack.bundleName)) {
let label = ResourceManagerUtil.getBundleResourceInfo(wantCallBack.bundleName,
bundleResourceManager.ResourceFlag.GET_RESOURCE_INFO_WITH_LABEL)?.label as string;
item.appName = label ?? '';
}
const titleWithHighlight = this.buildTitleWithHighlight(item.title, searchValue);
let renderItemInfo: RenderItemChildrenInfo = {
title: titleWithHighlight,
entryKey: wantCallBack.bundleName ?? '',
path: CheckEmptyUtils.checkStrIsEmpty(item.path) ? item.title.split(new RegExp(`<em>|</em>`, 'gi'))
.join('') : item.path,
parentKey: item.parentKey,
params: item.uriParams,
itemName: item.itemName,
appName: item.appName,
isOuterApp: true,
bundleName: wantCallBack.bundleName,
abilityName: wantCallBack.abilityName,
want: wantCallBack,
isApp: true
};
return renderItemInfo;
}
return undefined;
}
private static async getUpgradeSql(oldVersion: number): Promise<string[]> {
let upgradeSqlList: string[] = [];
try {
LogUtil.showInfo(TAG, 'read upgrade from json');
let upgradeConfig: UpgradeConfig | undefined = await SearchConfigFileParser.getInstance()
.parserUpgradeInfo(UPGRADE_CONFIG);
if (upgradeConfig) {
let upgradeInfoList: UpgradeInfo[] = upgradeConfig.upgradeInfos;
SearchDataController.latestRdbVersion = oldVersion;
upgradeInfoList.forEach(upgradeInfo => {
if (upgradeInfo.version > SearchDataController.latestRdbVersion) {
upgradeSqlList = upgradeSqlList.concat(upgradeInfo.sqlList);
SearchDataController.latestRdbVersion = upgradeInfo.version;
}
});
}
} catch (err) {
LogUtil.showError(TAG, `read upgrade from json error: ${err?.message}`);
}
LogUtil.showInfo(TAG, `read upgrade end, length: ${upgradeSqlList.length}`);
return upgradeSqlList;
}
private static checkIsDeviceSupport(supportDevices: Array<string>): boolean {
if (CheckEmptyUtils.isEmptyArr(supportDevices)) {
return true;
}
let deviceType = deviceInfo.deviceType;
// TODO 临时方案,由于当前设备deviceInfo.deviceType为default,先手动改成phone
if (deviceType === 'default') {
deviceType = 'phone'
}
for (let supportDevice of supportDevices) {
if (supportDevice === deviceType) {
return true;
}
}
return false;
}
}