1/*
2 * Copyright (c) 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 type common from '@ohos.app.ability.common';
17import update from '@ohos.update';
18import type Want from '@ohos.app.ability.Want';
19import { ErrorCode } from '@ohos/common/src/main/ets/const/update_const';
20import type { OtaStatus, UpgradeData, } from '@ohos/common/src/main/ets/const/update_const';
21import { UpdateState, UpgradeCallResult, } from '@ohos/common/src/main/ets/const/update_const';
22import type { Message } from '@ohos/common/src/main/ets/manager/UpdateManager';
23import {
24  UpdateManager,
25  MessageQueue,
26  OtaStatusHolder,
27} from '@ohos/common/src/main/ets/manager/UpdateManager';
28import { DeviceUtils } from '@ohos/common/src/main/ets/util/DeviceUtils';
29import { LogUtils } from '@ohos/common/src/main/ets/util/LogUtils';
30import type { BaseState } from '../manager/StateManager';
31import { StateManager } from '../manager/StateManager';
32import { NotificationManager } from '../notify/NotificationManager';
33import VersionUtils from '../util/VersionUtils';
34import { UpgradeAdapter } from '../UpgradeAdapter';
35import { FormatUtils } from '@ohos/common/src/main/ets/util/FormatUtils';
36
37/**
38 * 升级接口管理类
39 *
40 * @since 2022-06-05
41 */
42export class OtaUpdateManager {
43  private static readonly KEY = 'EventInfo';
44  private _updateStatus: number;
45  private _downloadProgress: number;
46  private lastStatus: number;
47  private stateObj: BaseState;
48  private otaStatusHolder: OtaStatusHolder;
49  private updateManager: UpdateManager;
50  private messageQueue: MessageQueue;
51
52  /**
53   * 单例--升级管理类对象实例
54   *
55   * @return 升级管理类对象实例
56   */
57  static getInstance(): OtaUpdateManager {
58    return globalThis.otaUpdateManager ?? new OtaUpdateManager();
59  }
60
61  private constructor() {
62    this.log('OtaUpdateManager init.');
63    globalThis.otaUpdateManager = this;
64    this.otaStatusHolder = new OtaStatusHolder();
65    this.messageQueue = new MessageQueue(this.handleMessage.bind(this));
66
67    this.updateManager = new UpdateManager();
68    this.updateManager.bind(update.BusinessSubType.FIRMWARE, this.notifyUpdateStatusRemote.bind(this));
69  }
70
71  /**
72   * 取升级状态
73   *
74   * @return resolve 状态/reject 错误信息
75   */
76  async getOtaStatus(): Promise<UpgradeData<OtaStatus>> {
77    return new Promise((resolve, reject) => {
78      this.updateManager.getOtaStatus().then((result: UpgradeData<OtaStatus>) => {
79        if (result?.callResult === UpgradeCallResult.OK) {
80          this.refreshState(result?.data);
81        }
82        resolve(result);
83      });
84    });
85  }
86
87  /**
88   * 从due数据库取新版本信息
89   *
90   * @return resolve 新版本信息/reject 错误信息
91   */
92  async getNewVersion(): Promise<UpgradeData<update.NewVersionInfo>> {
93    return new Promise((resolve, reject) => {
94      this.updateManager.getNewVersion().then((result: UpgradeData<update.NewVersionInfo>) => {
95        if (result?.callResult === UpgradeCallResult.OK) {
96          globalThis.cachedNewVersionInfo = result?.data;
97          resolve(result);
98        } else {
99          resolve(result);
100        }
101      });
102    });
103  }
104
105  /**
106   * 获取新版本描述文件
107   *
108   * @return 新版本描述文件
109   */
110  async getNewVersionDescription(): Promise<UpgradeData<Array<update.ComponentDescription>>> {
111    let versionDigest: string = await VersionUtils.getNewVersionDigest();
112    return this.updateManager.getNewVersionDescription(versionDigest, update.DescriptionFormat.STANDARD,
113      DeviceUtils.getSystemLanguage());
114  }
115
116  /**
117   * 获取当前版本升级日志
118   *
119   * @return 当前版本描述文件
120   */
121  async getCurrentVersionDescription(): Promise<UpgradeData<Array<update.ComponentDescription>>> {
122    return this.updateManager.getCurrentVersionDescription(update.DescriptionFormat.STANDARD,
123      DeviceUtils.getSystemLanguage());
124  }
125
126  /**
127   * 从服务器取搜索新版本
128   *
129   * @return resolve 新版本信息/reject 错误信息
130   */
131  async checkNewVersion(): Promise<UpgradeData<update.CheckResult>> {
132    return new Promise((resolve, reject) => {
133      this.updateManager.checkNewVersion().then((result: UpgradeData<update.CheckResult>) => {
134        if (result?.callResult === UpgradeCallResult.OK) {
135          globalThis.cachedNewVersionInfo = result?.data?.newVersionInfo;
136          resolve(result);
137        } else {
138          resolve(result);
139        }
140      });
141    });
142  }
143
144  /**
145   * 升级
146   *
147   * @param order 安装指令
148   */
149  async upgrade(order: update.Order = update.Order.INSTALL_AND_APPLY): Promise<void> {
150    let versionDigest: string = await VersionUtils.getNewVersionDigest();
151    return new Promise((resolve, reject) => {
152      this.updateManager.upgrade(versionDigest, order).then(()=> {
153        resolve();
154      }).catch(err => {
155        let status: OtaStatus = {
156          status: order === update.Order.APPLY ? UpdateState.INSTALL_SUCCESS : UpdateState.DOWNLOAD_SUCCESS,
157          percent: 100,
158          endReason: err?.data?.[0]?.errorCode?.toString() || ErrorCode.DEFAULT_ERROR,
159        };
160        this.notifyUpdateStatus(status, globalThis.abilityContext);
161        this.logError('upgrade err:' + JSON.stringify(err));
162        reject(err);
163      });
164    });
165  }
166
167  /**
168   * 下载
169   *
170   * @param downloadNetwork 下载网络类型,默认为wifi
171   */
172  async download(downloadNetwork: update.NetType = update.NetType.WIFI): Promise<void> {
173    UpgradeAdapter.getInstance().getNotifyInstance()?.cancelAll();
174    let versionDigest: string = await VersionUtils.getNewVersionDigest();
175    this.setDownloadProgress(0);
176    this.updateManager.download(versionDigest, downloadNetwork, update.Order.DOWNLOAD)
177      .catch(err => {
178        let status: OtaStatus = {
179          status: UpdateState.CHECK_SUCCESS,
180          percent: 0,
181          endReason: err?.data?.[0]?.errorCode?.toString() || '',
182        };
183        this.notifyUpdateStatus(status, globalThis.abilityContext);
184      });
185  }
186
187  /**
188   * 继续下载
189   */
190  async resumeDownload(): Promise<void>  {
191    let versionDigest: string = await VersionUtils.getNewVersionDigest();
192    this.setUpdateState(UpdateState.DOWNLOADING);
193    this.updateManager.resumeDownload(versionDigest, update.NetType.WIFI).then(result => {
194      this.log('resumeDownload result:' + JSON.stringify(result));
195    }).catch(err => {
196      let status: OtaStatus = {
197        status: UpdateState.DOWNLOAD_PAUSE,
198        percent: this.getDownloadProgress(),
199        endReason: err?.data?.[0]?.errorCode?.toString() || '',
200      };
201      this.notifyUpdateStatus(status, globalThis.abilityContext);
202    });
203  }
204
205  /**
206   * 取消升级
207   */
208  async cancel(): Promise<void>  {
209    this.setUpdateState(UpdateState.CHECK_SUCCESS);
210    this.setDownloadProgress(0);
211    await UpgradeAdapter.getInstance().getNotifyInstance()?.cancelAll();
212    this.updateManager.cancel();
213  }
214
215  /**
216   * 取当前版本数据
217   *
218   * @return resolve 当前版本信息/reject 错误信息
219   */
220  async getCurrentVersionInfo(): Promise<UpgradeData<update.CurrentVersionInfo>> {
221    return this.updateManager.getCurrentVersionInfo();
222  }
223
224  /**
225   * 取升级状态缓存数据
226   *
227   * @return 升级状态
228   */
229  getUpdateState(): number {
230    return this._updateStatus;
231  }
232
233  /**
234   * 设置升级状态缓存数据
235   *
236   * @param value 状态
237   */
238  setUpdateState(value): void {
239    if (this._updateStatus !== Number(value) && value !== undefined && value !== null) {
240      this._updateStatus = Number(value);
241      AppStorage.Set('updateStatus', this._updateStatus);
242    }
243  }
244
245  /**
246   * 取升级进度
247   *
248   * @return 升级进度
249   */
250  getDownloadProgress(): number {
251    return this._downloadProgress;
252  }
253
254  /**
255   * 设置进度
256   *
257   * @param value 进度
258   */
259  setDownloadProgress(value): void {
260    if (this._downloadProgress !== value && value !== undefined && value !== null) {
261      this._downloadProgress = value;
262      AppStorage.Set('downloadProgress', this._downloadProgress);
263    }
264  }
265
266  /**
267   * 取状态对象
268   *
269   * @param status 状态
270   */
271  getStateObj(status: number): BaseState {
272    if (this.stateObj?.state === status) {
273      return this.stateObj;
274    } else {
275      return StateManager.createInstance(status);
276    }
277  }
278
279  private refreshState(otaStatus: OtaStatus): void {
280    if (!this.stateObj || this.lastStatus !== otaStatus.status) {
281      this.stateObj = StateManager.createInstance(otaStatus);
282    }
283    this.stateObj.refresh(otaStatus);
284    this.lastStatus = otaStatus.status;
285    this.setUpdateState(this.stateObj.state);
286    this.setDownloadProgress(this.stateObj.percent);
287  }
288
289  /**
290   * 状态刷新
291   *
292   * @param otaStatus 状态
293   */
294  private async notifyUpdateStatusRemote(eventInfo: update.EventInfo): Promise<void> {
295    this.log(`notifyUpdateStatusRemote ${JSON.stringify(eventInfo)}`);
296    let message: Message = {
297      context: globalThis.extensionContext || globalThis.abilityContext,
298      eventInfo: eventInfo,
299    };
300
301    this.messageQueue.execute(message);
302  }
303
304  private async handleMessage(context: common.Context, eventInfo: update.EventInfo): Promise<void> {
305    let otaStatus: OtaStatus = this.getFormattedOtaStatus(eventInfo);
306    if (this.isTerminalState(otaStatus)) {
307      globalThis.lastVersionName = await VersionUtils.obtainNewVersionName(eventInfo?.taskBody);
308    }
309    let versionDigest: string = eventInfo?.taskBody?.versionDigestInfo?.versionDigest ?? '';
310    await this.notifyUpdateStatus(otaStatus, context, versionDigest, eventInfo?.eventId);
311  }
312
313  private async notifyUpdateStatus(otaStatus: OtaStatus, context: common.Context, verDigest?: string,
314    eventId?: update.EventId): Promise<void> {
315    this.log('notifyUpdateStatus:' + JSON.stringify(otaStatus));
316    this.refreshState(otaStatus);
317
318    if (!this.otaStatusHolder.isStatusChangedAndRefresh(otaStatus, eventId)) {
319      LogUtils.warn('UpdateManager', 'notifyUpdateStatus is repeating, abandon.');
320      return;
321    }
322    if (!globalThis.cachedNewVersionInfo && !this.isTerminalState(otaStatus)) {
323      await this.getNewVersion();
324    }
325
326    await StateManager.createInstance(otaStatus).notify(context, eventId);
327  }
328
329  private isTerminalState(otaStatus: OtaStatus): boolean {
330    let status = otaStatus?.status ?? UpdateState.INIT;
331    if (status === UpdateState.INIT || status === UpdateState.DOWNLOAD_FAILED ||
332      status === UpdateState.INSTALL_FAILED || status === UpdateState.UPGRADE_SUCCESS ||
333      status === UpdateState.UPGRADE_FAILED) {
334      return true;
335    }
336    return false;
337  }
338
339  /**
340   * 收到推送消息
341   *
342   * @param otaStatus 状态数据
343   */
344  async onReceivedUpdateServiceMessage(eventInfo: update.EventInfo): Promise<void> {
345    this.log('receives from onReceivedUpdateServiceMessage:' + JSON.stringify(eventInfo));
346    let message: Message = {
347      context: globalThis.extensionContext,
348      eventInfo: eventInfo,
349    };
350    await this.messageQueue.execute(message);
351  }
352
353  /**
354   * 收到page推送消息
355   *
356   * @param otaStatus 状态数据
357   */
358  async onReceivedUpdatePageMessage(otaStatus: OtaStatus): Promise<void> {
359    this.log('receives from onReceivedUpdatePageMessage:' + JSON.stringify(otaStatus));
360    this.notifyUpdateStatus(otaStatus, globalThis.abilityContext);
361  }
362
363  /**
364   * 处理推送消息
365   *
366   * @param want 推送数据
367   * @param context 上下文
368   */
369  public async handleWant(want: Want, context: common.Context): Promise<void> {
370    let action: string = want?.action ?? '';
371    if (await NotificationManager.handleAction(action, context)) {
372      this.log('handleWant:' + FormatUtils.stringify(want));
373      return;
374    }
375    let eventInfo = this.wantParser(want);
376    this.log('handleWant: eventInfo is ' + FormatUtils.stringify(eventInfo));
377    if (!eventInfo?.eventId) {
378      this.log('eventInfo?.eventId is null');
379      return;
380    }
381    await this.onReceivedUpdateServiceMessage(eventInfo);
382  }
383
384  /**
385   * 是否升级终止
386   *
387   * @return 是否升级终止
388   */
389  public isTerminal(): boolean {
390    return this.isTerminalState(this.stateObj?.otaStatus);
391  }
392
393  private wantParser(want: Want): update.EventInfo {
394    let eventInfo: update.EventInfo = want?.parameters?.[OtaUpdateManager.KEY] as update.EventInfo;
395    if (typeof eventInfo === 'string') {
396      eventInfo = FormatUtils.parseJson(eventInfo);
397    }
398    return eventInfo;
399  }
400
401  private log(message: string): void {
402    LogUtils.log('UpdateManager', message);
403  }
404
405  private logError(message: string): void {
406    LogUtils.error('UpdateManager', message);
407  }
408
409  /**
410   * 通过eventInfo获取OtaStatus
411   * 同时对status、percent数据进行调整
412   *
413   * @param eventInfo 事件
414   * @return OtaStatus 实例
415   */
416  private getFormattedOtaStatus(eventInfo: update.EventInfo): OtaStatus {
417    let endReason: string = eventInfo.taskBody?.errorMessages?.[0]?.errorCode?.toString();
418    let otaStatus: OtaStatus = {
419      status: eventInfo.taskBody?.status,
420      percent: eventInfo.taskBody?.progress,
421      endReason: !endReason || endReason === '0' ? null : endReason,
422    };
423    if (!otaStatus.status) {
424      otaStatus.status = this.getUpdateStateFromEventId(eventInfo.eventId);
425    }
426    return otaStatus;
427  }
428
429  private getUpdateStateFromEventId(eventId: update.EventId): UpdateState {
430    let status: UpdateState;
431    switch (eventId) {
432      case update.EventId.EVENT_TASK_RECEIVE:
433        status = UpdateState.CHECK_SUCCESS;
434        break;
435      case update.EventId.EVENT_TASK_CANCEL:
436        status = UpdateState.INIT;
437        break;
438      case update.EventId.EVENT_DOWNLOAD_START:
439        status = UpdateState.DOWNLOADING;
440        break;
441      case update.EventId.EVENT_DOWNLOAD_SUCCESS:
442        status = UpdateState.DOWNLOAD_SUCCESS;
443        break;
444      case update.EventId.EVENT_DOWNLOAD_FAIL:
445        status = UpdateState.DOWNLOAD_FAILED;
446        break;
447      case update.EventId.EVENT_UPGRADE_SUCCESS:
448        status = UpdateState.UPGRADE_SUCCESS;
449        break;
450      case update.EventId.EVENT_UPGRADE_FAIL:
451        status = UpdateState.UPGRADE_FAILED;
452        break;
453      default:
454        break;
455    }
456    return status;
457  }
458}