• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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