1/** 2 * Copyright (c) 2024-2024 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 Logger from '../utils/Logger'; 17import dataShare from '@ohos.data.dataShare'; 18import { MenuConfig } from '../bean/MenuConfig'; 19import DataShareResultSet from '@ohos.data.DataShareResultSet'; 20import { StringUtil } from './StringUtil'; 21import resourceUtil from './ResourceUtil'; 22import { RawFileUtil } from './RawFileUtil'; 23import DefaultMenuConfig from '../bean/DefaultMenuConfig'; 24import HashMap from '@ohos.util.HashMap'; 25import common from '@ohos.app.ability.common'; 26import bundleManager from '@ohos.bundle.bundleManager'; 27import BundleInfoModel from '../../model/bundleInfo/BundleInfoModel'; 28import abilityAccessCtrl, { Permissions } from '@ohos.abilityAccessCtrl'; 29import resourceManager from '@ohos.resourceManager'; 30import dataSharePredicates from '@ohos.data.dataSharePredicates'; 31 32const TAG = 'AutoMenu'; 33const DST_ABILITY_MODE = 1; 34// UIAbility access method 35const DST_UIABILITY_MODE = 0; 36 37// Database unique identifier 38const DISPLAY_ONLY_PRIMARY_USER = 'ONLY_PRIMARY_USER'; 39const DISPLAY_ONLY_SUB_USER = 'ONLY_SUB_USER'; 40const MAIN_USER_ID = 100; 41 42const CHECK_ACCESS_TOKEN_PERMISSION: Permissions = 'ohos.permission.ACCESS_SECURITY_PRIVACY_CENTER'; 43const PRIVACY_CENTER = 'metadata.access.privacy.center'; 44const ORDERING_RULE_FILE = 'menu_list_rule.json'; 45const DISPLAY_MODEL_CARD = 'card'; 46const DISPLAY_MODEL_LIST = 'list'; 47 48export class AutoMenuManager { 49 // Default configuration information,key is bundle name, value is config 50 private _defaultMenuConfigMap: HashMap<string, DefaultMenuConfig> = new HashMap(); 51 52 // key is bundle name, value is id. 53 private _defaultBusinessIdMap: HashMap<string, string> = new HashMap(); 54 55 private _predicates: dataSharePredicates.DataSharePredicates = new dataSharePredicates.DataSharePredicates(); 56 57 private _shareArray = ['*']; 58 59 private static _INSTANCE: AutoMenuManager; 60 61 /** 62 * To get single instance 63 * @returns The single instance 64 */ 65 public static getInstance(): AutoMenuManager { 66 if (!AutoMenuManager._INSTANCE) { 67 AutoMenuManager._INSTANCE = new AutoMenuManager(); 68 } 69 return AutoMenuManager._INSTANCE; 70 } 71 72 /** 73 * To get all config in system 74 * @param context The context 75 * @param userId The user id 76 * @returns The all menu config 77 */ 78 async getMenuConfigFromBms(context: Context, userId: number): Promise<MenuConfig[]> { 79 // Obtain all configuration information and internally check permissions 80 let allPkgConfigList: MenuConfig[] = await this._readConfigFromBms(context); 81 // Adapt to businessId 82 for (let index = 0; index < allPkgConfigList.length; index++) { 83 const item = allPkgConfigList[index]; 84 if (item.businessId === null || item.businessId === undefined || item.businessId.length === 0) { 85 item.businessId = await this._getDefaultBusinessId(context, item.bundleName); 86 } 87 } 88 Logger.info(TAG, 'getMenuConfigFromBms allPkgConfigList: ' + JSON.stringify(allPkgConfigList)); 89 // Filter illegal configuration information 90 let verifiedPkgConfigList: MenuConfig[] = []; 91 for (let index = 0; index < allPkgConfigList.length; index++) { 92 const menuConfig = allPkgConfigList[index]; 93 let isVerifyPass: boolean = await this._verifyMenuConfig(context, menuConfig, userId); 94 if (isVerifyPass) { 95 verifiedPkgConfigList.push(menuConfig); 96 } else { 97 Logger.error(TAG, 'Not verify pass. ' + menuConfig.businessId); 98 } 99 } 100 Logger.info(TAG, 'getMenuConfigFromBms result: ' + JSON.stringify(verifiedPkgConfigList)); 101 return verifiedPkgConfigList; 102 } 103 104 /** 105 * To judge this menu is support or not 106 * @param context The context 107 * @param item The menu id 108 * @returns True is support 109 */ 110 async isSupportMenu(context: Context, item: MenuConfig): Promise<number> { 111 let isSupport: number = 0; 112 const uri = item.showControlAbilityUri; 113 if (uri === undefined) { 114 isSupport = 1; 115 } else { 116 let value: string = await this._verificationUri(context, item.showControlAbilityUri); 117 if (value === '0x55') { 118 isSupport = 1; 119 } else if (value === '0xaa') { 120 isSupport = 0; 121 } 122 } 123 return isSupport; 124 } 125 126 /** 127 * To get menu priority 128 * @param context The context 129 * @param menuConfig The menu config 130 * @returns The menu priority 131 */ 132 async getMenuPriority(context: Context, menuConfig: MenuConfig): Promise<number> { 133 if (this._defaultBusinessIdMap.length === 0) { 134 await this._initDefaultMenuConfig(context); 135 } 136 try { 137 let defaultMenuConfig: DefaultMenuConfig = this._defaultMenuConfigMap.get(menuConfig.businessId); 138 if (defaultMenuConfig === undefined) { 139 return 0; 140 } 141 return defaultMenuConfig.priority; 142 } catch (err) { 143 Logger.info(TAG, 'get menu priority error.') 144 } 145 return 0; 146 } 147 148 private async _getDefaultBusinessId(context: Context, bundleName: string): Promise<string> { 149 if (this._defaultBusinessIdMap.length === 0) { 150 await this._initDefaultMenuConfig(context); 151 } 152 try { 153 return this._defaultBusinessIdMap.get(bundleName) 154 } catch (err) { 155 Logger.info(TAG, 'get menu priority error.') 156 } 157 return ''; 158 } 159 160 private async _initDefaultMenuConfig(context: Context) { 161 try { 162 let jsonStr: string = await RawFileUtil.getRawFileByContext(context, ORDERING_RULE_FILE); 163 let defaultMenuConfigList: DefaultMenuConfig[] = JSON.parse(jsonStr); 164 for (let index = 0; index < defaultMenuConfigList.length; index++) { 165 const item = defaultMenuConfigList[index]; 166 this._defaultMenuConfigMap.set(item.businessId, item); 167 this._defaultBusinessIdMap.set(item.bundleName, item.businessId); 168 } 169 } catch (e) { 170 Logger.error(TAG, 'error when parse advice config.') 171 } 172 } 173 174 // Query Ability information with functional access 175 private async _readConfigFromBms(context: Context): Promise<MenuConfig[]> { 176 let abilityInfoList: bundleManager.ExtensionAbilityInfo[] = []; 177 let uiAbilityInfoList: bundleManager.AbilityInfo[] = []; 178 try { 179 abilityInfoList = await BundleInfoModel.queryExtensionAbilityInfoOther() 180 } catch (err) { 181 Logger.error(TAG, 'queryExtensionAbilityInfoOther error.') 182 } 183 // Query information accessed through uiAbility 184 try { 185 uiAbilityInfoList = await BundleInfoModel.queryAbilityInfoOther() 186 } catch (err) { 187 Logger.error(TAG, 'queryAbilityInfoOther error.') 188 } 189 if (abilityInfoList.length === 0 && uiAbilityInfoList.length === 0) { 190 return []; 191 } 192 // Check the Ability permissions for feature access 193 let atManage: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager(); 194 let pkgConfigList: MenuConfig[] = []; 195 for (let index = 0; index < abilityInfoList.length; index++) { 196 const abilityInfo = abilityInfoList[index]; 197 try { 198 let data: abilityAccessCtrl.GrantStatus = await atManage.checkAccessToken( 199 abilityInfo.applicationInfo.accessTokenId, CHECK_ACCESS_TOKEN_PERMISSION); 200 if (data !== abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) { 201 continue; 202 } 203 let singlePkgConfigList: MenuConfig[] = await this._readConfigFromSingleAbilityInfo(context, abilityInfo); 204 pkgConfigList = pkgConfigList.concat(singlePkgConfigList); 205 } catch (err) { 206 Logger.info(TAG, 'checkAccessToken error: ' + abilityInfo.bundleName); 207 } 208 } 209 // Check the Ability permissions of the function accessed through uiAbility method 210 for (let index = 0; index < uiAbilityInfoList.length; index++) { 211 const uiAbilityInfo = uiAbilityInfoList[index]; 212 try { 213 let data: abilityAccessCtrl.GrantStatus = await atManage.checkAccessToken( 214 uiAbilityInfo.applicationInfo.accessTokenId, CHECK_ACCESS_TOKEN_PERMISSION); 215 if (data !== abilityAccessCtrl.GrantStatus.PERMISSION_GRANTED) { 216 continue; 217 } 218 let singlePkgConfigList: MenuConfig[] = await this._readConfigFromSingleAbilityInfo(context, uiAbilityInfo); 219 pkgConfigList = pkgConfigList.concat(singlePkgConfigList); 220 } catch (err) { 221 Logger.info(TAG, 'uiAbilityInfoList checkAccessToken error: ' + uiAbilityInfo.bundleName); 222 } 223 } 224 return pkgConfigList; 225 } 226 227 // Read access method configuration information and convert it to the corresponding data class 228 private async _readConfigFromSingleAbilityInfo(context: Context, 229 abilityInfo: bundleManager.ExtensionAbilityInfo | bundleManager.AbilityInfo): Promise<MenuConfig[]> { 230 // Get MetaData data 231 let singleAbilityMenuConfigList: MenuConfig[] = []; 232 for (let index = 0; index < abilityInfo.metadata.length; index++) { 233 const metaData = abilityInfo.metadata[index]; 234 if (metaData.name !== PRIVACY_CENTER) { 235 continue; 236 } 237 let bundleName = abilityInfo.bundleName; 238 let fileName = metaData.value; 239 // Retrieve JSON files based on metaData 240 let content: string = await this._getFileByMetaData(context, bundleName, fileName); 241 if (StringUtil.isEmpty(content)) { 242 continue; 243 } 244 let menuConfig: MenuConfig = JSON.parse(content); 245 menuConfig.bundleName = bundleName; 246 singleAbilityMenuConfigList.push(menuConfig); 247 } 248 Logger.info(TAG, 'readConfigFromSingleAbilityInfo: ' + JSON.stringify(singleAbilityMenuConfigList)); 249 return singleAbilityMenuConfigList; 250 } 251 252 // Query the JSON data of metaData based on the package name 253 private async _getFileByMetaData(context: Context, bundleName: string, fileName: string): Promise<string> { 254 let resourceManager = resourceUtil.getBundleResourceManager(bundleName, context) as resourceManager.ResourceManager; 255 return await RawFileUtil.getStringByFile(resourceManager, fileName); 256 } 257 258 private async _verifyMenuConfig(context: Context, menuConfig: MenuConfig, userId: number): Promise<boolean> { 259 // Classification verification based on card or list mode, currently only list mode is supported 260 try { 261 if (menuConfig.displayedMode === DISPLAY_MODEL_CARD || menuConfig.displayedMode === DISPLAY_MODEL_LIST) { 262 return await this._verificationListContent(context, userId, menuConfig); 263 } 264 } catch (err) { 265 Logger.error(TAG, 'verifyMenuConfig error.'); 266 } 267 return false; 268 } 269 270 // Verify showControlAbilityUri 271 private async _verificationUri(context: Context, uri: string): Promise<string> { 272 try { 273 let data: dataShare.DataShareHelper = await dataShare.createDataShareHelper(context, uri, { isProxy: false }); 274 if (data !== undefined) { 275 let resultSet: DataShareResultSet = await data.query(uri, this._predicates, this._shareArray); 276 resultSet.goToFirstRow(); 277 let showResult = resultSet.getString(resultSet.getColumnIndex('SHOW_RESULT')); 278 if (StringUtil.isEmpty(showResult)) { 279 return ''; 280 } 281 if (showResult === '0x55' || showResult === '0xaa') { 282 Logger.info(TAG, 'verificationUri showResult: ' + showResult); 283 return showResult; 284 } else { 285 Logger.info(TAG, 'showResult: ' + showResult); 286 return ''; 287 } 288 } 289 } catch (err) { 290 Logger.error(TAG, 'verificationUri createDataShareHelper error: ' + JSON.stringify(err)); 291 } 292 return ''; 293 } 294 295 private async _verificationListContent(context: common.Context, userId: number, 296 menuConfig: MenuConfig): Promise<boolean> { 297 if (menuConfig === undefined || menuConfig === null) { 298 return false; 299 } 300 if (this._isValidAccessMode(menuConfig.dstAbilityMode, menuConfig.dstBundleName)) { 301 Logger.error(TAG, 'verificationListContent dstAbilityMode error.') 302 return false; 303 } 304 if (StringUtil.isEmpty(menuConfig.dstBundleName)) { 305 Logger.error(TAG, 'verificationListContent dstBundleName error.'); 306 return false; 307 } 308 if (StringUtil.isEmpty(menuConfig.dstAbilityName)) { 309 Logger.error(TAG, 'verificationListContent dstAbilityName error.'); 310 return false; 311 } 312 let _isValidResourceWithList: boolean = await this._isValidResourceWithList(context, menuConfig); 313 if (!_isValidResourceWithList) { 314 return false; 315 } 316 // Verify the configuration of primary and secondary users, optional fields 317 if (menuConfig.displayUserConfig !== undefined) { 318 let displayUserConfigRes = this._verificationPrimaryUser(menuConfig.displayUserConfig, userId); 319 Logger.info(TAG, `displayUserConfigRes value is: ${displayUserConfigRes}`); 320 if (displayUserConfigRes === false) { 321 return displayUserConfigRes; 322 } 323 } 324 // Verify uri, optional fields 325 let showControlAbilityUri = menuConfig.showControlAbilityUri; 326 if (showControlAbilityUri !== undefined) { 327 let _verificationUriResult: string = await this._verificationUri(context, showControlAbilityUri); 328 Logger.info(TAG, 'verificationListContent verificationUri: ' + showControlAbilityUri); 329 Logger.info(TAG, 'verificationListContent verificationUri value: ' + _verificationUriResult); 330 if ('' === _verificationUriResult) { 331 Logger.error(TAG, 'verificationListContent verificationUri error.') 332 return false; 333 } else { 334 return true; 335 } 336 } 337 return true; 338 } 339 340 private async _isValidResourceWithList(context: common.Context, menuConfig: MenuConfig): Promise<boolean> { 341 let resourceManager = resourceUtil.getBundleResourceManager(menuConfig.bundleName, context); 342 if (resourceManager === null || resourceManager === undefined) { 343 return false; 344 } 345 if (StringUtil.isEmpty(menuConfig.mainTitleResource)) { 346 return false; 347 } 348 // title 349 let titleResList = menuConfig.mainTitleResource.split(':'); 350 if (titleResList.length < 2) { 351 Logger.error(TAG, 'titleResList length is error.') 352 return false; 353 } 354 // Verify title 355 let value: string = await resourceManager?.getStringByName(titleResList[1]); 356 if (value === '') { 357 Logger.error(TAG, 'titlePromise is error.') 358 return false; 359 } 360 return true; 361 } 362 363 // Whether the access mode is valid 364 private _isValidAccessMode(dstAbilityMode: number, dstBundleName: string): boolean { 365 return dstAbilityMode !== DST_ABILITY_MODE && dstAbilityMode !== DST_UIABILITY_MODE 366 } 367 368 // Verify sub users 369 private _verificationPrimaryUser(displayUserConfig: string, userId: number): boolean { 370 let isPrimaryUser: boolean = userId === MAIN_USER_ID; 371 Logger.info(TAG, `verificationSubUser, displayUser: ${displayUserConfig}, isPrimaryUser: ${isPrimaryUser}`); 372 if ((isPrimaryUser && displayUserConfig === DISPLAY_ONLY_PRIMARY_USER) || 373 (!isPrimaryUser && displayUserConfig === DISPLAY_ONLY_SUB_USER)) { 374 return true; 375 } else { 376 return false; 377 } 378 } 379} 380