1/**
2 * Copyright (c) 2021-2023 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 common from '@ohos.app.ability.common';
17import dataStorage from '@ohos.data.preferences';
18import deviceInfo from '@ohos.deviceInfo';
19import relationalStore from '@ohos.data.relationalStore';
20import fs from '@ohos.file.fs';
21import i18n from '@ohos.i18n';
22import settings from '@ohos.settings';
23import systemParameter from '@ohos.systemparameter';
24import SettingsDataConfig from './SettingsDataConfig';
25import { Log } from '../Utils/Log';
26import { GlobalContext }  from './GlobalContext';
27import contextConstant from '@ohos.app.ability.contextConstant';
28import { TableType } from '../common/Common';
29
30const DEFAULT_JSON_FILE_NAME : string = 'default_settings.json';
31const SETTINGSDATA_PREFERENCE : string = 'SettingsDataPreference';
32const SETTINGSDATA_PREFERENCE_USER : string = 'SettingsDataPreferenceUser';
33const EL2_DB_PATH: string = '/data/storage/el2/database/entry/rdb/settingsdata.db'
34const EMULATOR_TYPE: string = 'emulator'
35interface  TIME_FORMAT_DATA {
36  TIME_FORMAT_24: string ;
37  TIME_FORMAT_12: string ;
38}
39const TIME_FORMAT: TIME_FORMAT_DATA = {
40  TIME_FORMAT_24: '24',
41  TIME_FORMAT_12: '12',
42}
43interface IContent {
44  settings: Array<Map<string,string>> ;
45  user: Array<Map<string,string>> ;
46  userSecure: Array<Map<string,string>> ;
47}
48
49const INITIAL_KEY: string = '_CreatedTime';
50const VALID_DB_LENGTH: number = 48;
51const SETTINGS_CLONED_STATUS: string = 'settingsClonedStatus';
52
53class SettingsDBHelper {
54  public static readonly SHARED_TABLE_CREATE_PREFIX: string =
55    `CREATE TABLE IF NOT EXISTS ${SettingsDataConfig.TABLE_NAME}`;
56  // 需要在在表名后拼接当前的userid
57  public static readonly CURRENT_USER_TABLE_CREATE_PREFIX: string =
58    `CREATE TABLE IF NOT EXISTS ${SettingsDataConfig.USER_TABLE_NAME}_`;
59  public static readonly CURRENT_SECURE_TABLE_CREATE_PREFIX: string =
60    `CREATE TABLE IF NOT EXISTS ${SettingsDataConfig.SECURE_TABLE_NAME}_`;
61  public static readonly TABLE_CREATE_SUFFIX = ` (${SettingsDataConfig.FIELD_ID} INTEGER PRIMARY KEY AUTOINCREMENT, ` +
62    `${SettingsDataConfig.FIELD_KEYWORD} TEXT, `  +
63    `${SettingsDataConfig.FIELD_VALUE} TEXT CHECK (LENGTH(VALUE)<=1000))`;
64
65  private rdbStore?: relationalStore.RdbStore;
66  private context: Context;
67  private readonly DEFAULT_USER_ID: number = 100;
68  private area: contextConstant.AreaMode | undefined = undefined;
69  public isFirstStartup: dataStorage.ValueType = true;
70  public maxUserNO: dataStorage.ValueType = 100;
71  private faultOccured: boolean = false;
72
73  private constructor() {
74    this.rdbStore = undefined;
75    this.context = GlobalContext.getContext().getObject('abilityContext') as Context;
76    Log.info('context start'+ JSON.stringify(this.context));
77  }
78
79  private async emulatorParamInit(): Promise<void> {
80    if (this.getProductModel() !== EMULATOR_TYPE) {
81      Log.info('currently not a emulator');
82      return;
83    }
84
85    let tableName: string = this.getTableName(TableType.SETTINGS, this.DEFAULT_USER_ID);
86    await this.loadTableSettings('device_provisioned', '1', tableName);
87    tableName = this.getTableName(TableType.USER_SECURE, this.DEFAULT_USER_ID);
88    await this.loadTableSettings('basic_statement_agreed', '1', tableName);
89    await this.loadTableSettings('user_setup_complete', '1', tableName);
90    await this.loadTableSettings('is_ota_finished', '1', tableName);
91  }
92
93  public getProductModel(): string {
94    return deviceInfo.productModel;
95  }
96
97  public getArea() {
98    const dbFile = EL2_DB_PATH;
99    if (this.area === undefined) {
100      try {
101        let stat = fs.statSync(dbFile);
102        if (stat.size > VALID_DB_LENGTH) {
103          this.area = contextConstant.AreaMode.EL2;
104        } else {
105          this.area = contextConstant.AreaMode.EL1;
106        }
107      } catch {
108        this.area = contextConstant.AreaMode.EL1;
109      }
110    }
111    Log.info(`Area ${this.area}`);
112    return this.area;
113  }
114
115  public async initialInsert(tableName: string): Promise<void> {
116    try {
117      Log.info(`insert ${tableName} with key: ${tableName + INITIAL_KEY} `);
118      if (this.rdbStore) {
119        let ret = await this.rdbStore.insert(tableName,
120          { 'KEYWORD': tableName + INITIAL_KEY, 'VALUE': new Date().toString() });
121        if (ret <= 0) {
122          Log.error(`insert initial key-value failed for ${tableName}`);
123        }
124      } else {
125        Log.error(`insert initial key-value failed for ${tableName}, no rdbStore`);
126        this.faultOccured = true;
127      }
128    } catch (e) {
129      Log.error(`insert initial key-value failed for ${tableName}`);
130    }
131  }
132
133  private async firstStartupConfig() : Promise<void> {
134    Log.info('firstStartupConfig start');
135    let storage = await dataStorage.getPreferences(this.context as Context, SETTINGSDATA_PREFERENCE);
136    this.isFirstStartup = await storage.get('isFirstStartup', true);
137    storage = await dataStorage.getPreferences(this.context as Context, SETTINGSDATA_PREFERENCE_USER);
138    this.maxUserNO = await storage.get('MAXUSERNO', 100);
139    Log.info(`firstStartupConfig isFirstStartUp = ${this.isFirstStartup} max user no: ${this.maxUserNO}`);
140    // 总是创建以下三张表 if not exists
141    // 创建公共数据表
142    await this.rdbStore?.executeSql(SettingsDBHelper.SHARED_TABLE_CREATE_PREFIX +
143      SettingsDBHelper.TABLE_CREATE_SUFFIX, []);
144    // 创建默认用户数据表
145    await this.rdbStore?.executeSql(SettingsDBHelper.CURRENT_USER_TABLE_CREATE_PREFIX +
146      this.DEFAULT_USER_ID + SettingsDBHelper.TABLE_CREATE_SUFFIX, []);
147    // 创建默认用户 secure 数据表
148    await this.rdbStore?.executeSql(SettingsDBHelper.CURRENT_SECURE_TABLE_CREATE_PREFIX +
149      this.DEFAULT_USER_ID + SettingsDBHelper.TABLE_CREATE_SUFFIX, []);
150    if (this.isFirstStartup) {
151      Log.info('loadDefaultSettingsData begin');
152      this.loadDefaultSettingsData();
153      Log.info('loadDefaultSettingsData finish');
154      await this.initialInsert(SettingsDataConfig.TABLE_NAME);
155      await this.initialInsert(SettingsDataConfig.USER_TABLE_NAME + '_' + this.DEFAULT_USER_ID);
156      await this.initialInsert(SettingsDataConfig.SECURE_TABLE_NAME + '_' + this.DEFAULT_USER_ID);
157    }
158    Log.info('firstStartupConfig end');
159    return;
160  }
161
162  public async initRdbStore() {
163    Log.info('call initRdbStore start');
164    let  rdbStore = await relationalStore.getRdbStore(this.context as Context, {
165      name: SettingsDataConfig.DB_NAME,
166      securityLevel:1
167    });
168    if(rdbStore){
169      this.rdbStore = rdbStore;
170    }
171    await this.firstStartupConfig();
172    Log.info('call initRdbStore end');
173    return this.rdbStore;
174  }
175
176  public static getInstance(): SettingsDBHelper {
177    GlobalContext.getContext().getObject('settingsDBHelper') as SettingsDBHelper;
178    if(!GlobalContext.getContext().getObject('settingsDBHelper')){
179      GlobalContext.getContext().setObject('settingsDBHelper', new SettingsDBHelper());
180    }
181    return GlobalContext.getContext().getObject('settingsDBHelper') as SettingsDBHelper;
182  }
183
184  public async getRdbStore() {
185    Log.info('call getRdbStore start');
186    if (!this.rdbStore) {
187      return  await (GlobalContext.getContext().getObject('settingsDBHelper') as SettingsDBHelper).initRdbStore();
188      // return await globalThis.settingsDBHelper.initRdbStore();
189    }
190    return this.rdbStore
191  }
192
193  public async loadTableData(content: IContent, tableType: TableType, userId: number): Promise<void> {
194    if (!content) {
195      Log.error('content is empty');
196      return;
197    }
198    switch (tableType) {
199      case TableType.SETTINGS:
200        this.loadDefaultTaleData(content.settings, TableType.SETTINGS, userId);
201        return;
202      case TableType.USER:
203        this.loadDefaultTaleData(content.user, TableType.USER, userId);
204        return;
205      case TableType.SETTINGS:
206        this.loadDefaultTaleData(content.userSecure, TableType.USER_SECURE, userId);
207        return;
208      default:
209        Log.error('invalid type');
210    }
211  }
212
213  private  getTableName(tableType: TableType, userId: number): string {
214    if (tableType === TableType.SETTINGS) {
215      return SettingsDataConfig.TABLE_NAME;
216    }
217    if (tableType === TableType.USER) {
218      return `${SettingsDataConfig.USER_TABLE_NAME}_${userId}`;
219    }
220    return `${SettingsDataConfig.SECURE_TABLE_NAME}_${userId}`;
221  }
222
223  private async loadTableSettings(key: string, value: string, tableName: string): Promise<void> {
224    if (!this.rdbStore) {
225      Log.error('rdbStore is null!');
226      return
227    }
228    Log.info(`tableName: ${tableName}, key: ${key}, value: ${value}`);
229    try {
230      let ret = await this.rdbStore.insert(tableName, { 'KEYWORD': key, 'VALUE': value });
231      if (ret >= 0) {
232        Log.info(`insert into DB success; ${ret}`);
233      } else {
234        this.faultOccured = true;
235        Log.error(`insert into DB faild; ${ret}`);
236      }
237    } catch (err) {
238      Log.warn(`insert key ${key} failed`);
239    }
240  }
241
242  public async loadUserSettings(key: string, value: string, userId: number|undefined): Promise<void> {
243    if (!this.rdbStore) {
244      Log.error('rdbStore is null!');
245      return
246    }
247    Log.info('key=' + key + ' value ' + value + ' userid ' + userId);
248    await this.rdbStore.insert(SettingsDataConfig.USER_TABLE_NAME + '_' + userId,
249      { 'KEYWORD': key, 'VALUE': value }, (err, ret) => {
250      if (err) {
251        Log.error('loadGlobalSettings insert error:' + JSON.stringify(err));
252      }
253      Log.info('loadGlobalSettings insert ret = ' + ret);
254    });
255  }
256
257  public async readDefaultFile(): Promise<Object> {
258    let rawStr: string = '';
259    try {
260      let content: number[] = Array.from(await this.context?.resourceManager.getRawFile(DEFAULT_JSON_FILE_NAME));
261      rawStr = String.fromCharCode(...Array.from(content));
262    } catch (err) {
263      Log.error('readDefaultFile readRawFile err' + err);
264    }
265
266    if (rawStr) {
267      Log.info('readDefaultFile success');
268      return JSON.parse(rawStr);
269    }
270    return rawStr;
271  }
272
273  private async loadDefaultSettingsData(): Promise<void> {
274    if (!this.isFirstStartup) {
275      Log.info('loadDefaultSettingsData exists');
276      return;
277    }
278    Log.info('loadDefaultSettingsData start');
279    try {
280      let content = await this.readDefaultFile() as IContent;
281      if (!content) {
282        Log.error('readDefaultFile is failed!');
283        return;
284      }
285      // 同时加载三张表,主要用于首次加载场景
286      await this.loadTableData(content, TableType.SETTINGS, this.DEFAULT_USER_ID);
287      await this.loadTableData(content, TableType.USER, this.DEFAULT_USER_ID);
288      await this.loadTableData(content, TableType.USER_SECURE, this.DEFAULT_USER_ID);
289    } catch (err) {
290      Log.error('loadDefaultSettingsData catch error! err = ' + err);
291    }
292
293    let tableName: string = this.getTableName(TableType.SETTINGS, this.DEFAULT_USER_ID);
294    // 初始化设备名称
295    let deviceName: string = deviceInfo.marketName;
296    if (deviceName.startsWith('"') && deviceName.endsWith('"')) {
297      deviceName = JSON.parse(deviceName);
298    }
299    await this.loadTableSettings(settings.general.DEVICE_NAME, deviceName, tableName);
300
301    // 初始化亮度值
302    let defaultBrightness = systemParameter.getSync('const.display.brightness.default');
303    if (defaultBrightness) {
304      await this.loadTableSettings(settings.display.SCREEN_BRIGHTNESS_STATUS, defaultBrightness, tableName);
305    }
306
307    // 初始化克隆标识
308    await this.loadTableSettings(SETTINGS_CLONED_STATUS, '0', tableName);
309
310    // 适配模拟器开机不走OOBE
311    await this.emulatorParamInit();
312
313    //make sure no faultoccured, then write isFirstStartup false;
314    if (this.faultOccured === false) {
315      let storage = await dataStorage.getPreferences(this.context as Context, SETTINGSDATA_PREFERENCE);
316      await storage.put('isFirstStartUp', false);
317      await storage.flush();
318      Log.info('settingsdata initial DB success. ')
319    } else {
320      Log.warn('settingsdata initial DB failed! Will retry possible during next startup!!!');
321    }
322    Log.info('loadDefaultSettingsData end');
323  }
324  private async loadDefaultTaleData(tableData: Array<Map<string, string>>, tableType: TableType,
325                                    userID: number): Promise<void> {
326    if (tableData?.length <= 0) {
327      Log.error(`${tableType} table data is empty`);
328      return;
329    }
330    let tableName: string = this.getTableName(tableType, userID);
331    for (let index = 0; index < tableData.length; index++) {
332      await this.loadTableSettings(tableData[index]['name'], tableData[index]['value'], tableName);
333    }
334  }
335}
336
337export default SettingsDBHelper;
338