• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (c) 2025 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 UIAbility from '@ohos.app.ability.UIAbility';
17import Want from '@ohos.app.ability.Want';
18import notificationManager from '@ohos.notificationManager';
19import Notification from '@ohos.notification';
20import Base from '@ohos.base';
21import image from '@ohos.multimedia.image';
22import fs from '@ohos.file.fs';
23import wantAgent from '@ohos.app.ability.wantAgent';
24import { WantAgent } from '@ohos.app.ability.wantAgent';
25import commonEventManager from '@ohos.commonEventManager';
26import resourceManager from '@ohos.resourceManager';
27import opp from '@ohos.bluetooth.opp';
28import type { BusinessError } from '@ohos.base';
29import fileUri from '@ohos.file.fileuri';
30import rpc from '@ohos.rpc';
31import { DrawableDescriptor } from '@ohos.arkui.drawableDescriptor';
32import backgroundTaskManager from '@ohos.backgroundTaskManager';
33import systemParameterEnhance from '@ohos.systemParameterEnhance';
34
35const TAG: string = '[BT_RECEIVE_SERVICE]==>'
36const EVENT_TYPE_TRANSACTION: number = 6;
37const BT_TRANSACTION_EVENT: string = 'usual.event.bluetooth.REPORT.TRANSACTION';
38
39const RESULT_SUCCESS: number = 0;
40const RESULT_ERROR_UNSUPPORTED_TYPE: number = 1;
41const RESULT_ERROR_BAD_REQUEST: number = 2;
42const RESULT_ERROR_NOT_ACCEPTABLE: number = 3;
43const RESULT_ERROR_CANCELED: number = 4;
44const RESULT_ERROR_CONNECTION_FAILED: number = 5;
45const RESULT_ERROR_TRANSFER_FAILED: number = 6;
46const RESULT_ERROR_UNKNOWN: number = 7;
47const RESULT_SUCCESS_DISCONNECT: number = 9;
48
49const STATUS_PENDING: number = 0;
50const STATUS_RUNNING: number = 1;
51const STATUS_FINISH: number = 2;
52const OPP_TRANSFER_USER_ACCEPT: number = 0;
53const OPP_TRANSFER_AUTO_ACCEPT: number = 1;
54const UNKNOWN_VALUE: number = -1;
55
56const btTransactionStatisticsType = {
57    TRANSACTION_TYPE_OPP_SEND : 1,
58    TRANSACTION_TYPE_OPP_RECEIVE : 2,
59};
60
61const btTransactionStatisticsResult = {
62    TRANSACTION_RESULT_TOTAL : 1,
63    TRANSACTION_RESULT_SUCCESS : 2,
64    TRANSACTION_RESULT_FAIL : 3,
65};
66
67const btTransactionStatisticsSceneCode = {
68    TRANSACTION_SCENECODE_NA : 0,
69    TRANSACTION_SCENECODE_1 : 1,
70    TRANSACTION_SCENECODE_2 : 2,
71    TRANSACTION_SCENECODE_3 : 3,
72    TRANSACTION_SCENECODE_4 : 4,
73    TRANSACTION_SCENECODE_5 : 5,
74    TRANSACTION_SCENECODE_6 : 6,
75    TRANSACTION_SCENECODE_7 : 7,
76    TRANSACTION_SCENECODE_8 : 8,
77    TRANSACTION_SCENECODE_9 : 9,
78    TRANSACTION_SCENECODE_10 : 10,
79    TRANSACTION_SCENECODE_11 : 11,
80    TRANSACTION_SCENECODE_12 : 12,
81    TRANSACTION_SCENECODE_13 : 13,
82    TRANSACTION_SCENECODE_14 : 14,
83    TRANSACTION_SCENECODE_15 : 15,
84    TRANSACTION_SCENECODE_16 : 16,
85};
86
87export default class BluetoothReceiveUIAbility extends UIAbility {
88    private subscriber: commonEventManager.CommonEventSubscriber | null = null;
89    private subscribeinfo: commonEventManager.CommonEventSubscribeInfo = {
90        events: [
91            'usual.event.bluetooth.OPP.TAP.ACCEPT',
92            'usual.event.bluetooth.OPP.TAP.REJECT',
93            'usual.event.bluetooth.OPP.TAP.REMOVE',
94            'ohos.event.notification.BT.LIVEVIEW_REMOVE',
95            'usual.event.bluetooth.OPP.FINISH.NOTIFICATION',
96            'usual.event.bluetooth.OPP.FINISH.REMOVE',
97            'ohos.event.notification.BT.GET_URI',
98            'usual.event.bluetooth.OPP.RECEIVE'
99        ]
100    }
101    private cancelTransEvent: string = 'ohos.event.notification.BT.TAP_CANCEL';
102    private capsuleNotificationID: number = 100;
103    private timeInterval: number = 0;
104    private receiveDirectory: string = '';
105    private fileName: string = '';
106    private sandBoxUri: string = '';
107    private oppProfile = opp.createOppServerProfile();
108    private tapEventIsAllowed = true;
109    private transfering: boolean = false;
110    private timerIdCreate: number;
111    private timerIdReceive: number;
112    private fileNameToReceiveMap = new Map();
113    private totalCount: number = 0;
114    private currentCount: number = 0;
115    private successCount: number = 0;
116    private failedCount: number = 0;
117    private isEnabledLive2: boolean = false;
118
119    onCreate(want: Want): void {
120        console.info(TAG, 'BluetoothReceiveUIAbility onCreate');
121        this.readyReceiveEvent();
122        this.startContinuousTask();
123        this.timerIdCreate = setTimeout(() => {
124            console.info(TAG, 'onCreate 1 second but nothing received');
125            this.reportBtTransactionChr(btTransactionStatisticsResult.TRANSACTION_RESULT_FAIL,
126                1, btTransactionStatisticsSceneCode.TRANSACTION_SCENECODE_1, 1);
127            this.handleTerminate();
128        }, 1000);
129    }
130
131    onDestroy(): void {
132        console.info(TAG, 'BluetoothReceiveUIAbility onDestroy');
133    }
134
135    onForeground(): void {
136        console.info(TAG, 'BluetoothReceiveUIAbility onForeground');
137    }
138
139    handleTerminate() {
140        console.info(TAG, 'handleTerminate');
141        this.stopContinuousTask();
142        this.context.terminateSelf();
143    }
144
145    reportBtChrDataByResult(dataResult: number) {
146        console.info(TAG, 'reportBtChrDataByResult dataResult ' + dataResult +
147            ' totalCount' + this.totalCount + ' successCount' + this.successCount);
148        if (dataResult == RESULT_SUCCESS) {
149            this.reportBtTransactionChr(btTransactionStatisticsResult.TRANSACTION_RESULT_SUCCESS,
150                1, btTransactionStatisticsSceneCode.TRANSACTION_SCENECODE_NA, 0);
151        } else if ((dataResult == RESULT_ERROR_NOT_ACCEPTABLE ||
152            dataResult == RESULT_ERROR_BAD_REQUEST) && this.totalCount > 0) {
153            this.reportBtTransactionChr(btTransactionStatisticsResult.TRANSACTION_RESULT_FAIL,
154                this.totalCount, dataResult, this.totalCount);
155            this.reportBtTransactionChr(btTransactionStatisticsResult.TRANSACTION_RESULT_TOTAL,
156                this.totalCount - 1, btTransactionStatisticsSceneCode.TRANSACTION_SCENECODE_NA, 0);
157        } else if ((dataResult == RESULT_ERROR_CANCELED || dataResult == RESULT_ERROR_TRANSFER_FAILED) &&
158            this.totalCount - this.successCount >= 1) {
159            this.reportBtTransactionChr(btTransactionStatisticsResult.TRANSACTION_RESULT_FAIL,
160                this.totalCount - this.successCount, dataResult, this.totalCount - this.successCount);
161            this.reportBtTransactionChr(btTransactionStatisticsResult.TRANSACTION_RESULT_TOTAL,
162                this.totalCount - this.successCount -1, btTransactionStatisticsSceneCode.TRANSACTION_SCENECODE_NA, 0);
163        } else if (dataResult == RESULT_ERROR_CONNECTION_FAILED) {
164            this.reportBtTransactionChr(btTransactionStatisticsResult.TRANSACTION_RESULT_FAIL, 1, dataResult, 1);
165        }
166        this.reportBtTransactionChr(btTransactionStatisticsResult.TRANSACTION_RESULT_TOTAL,
167            1, btTransactionStatisticsSceneCode.TRANSACTION_SCENECODE_NA, 0);
168    }
169
170    reportBtTransactionChr(result: number, resultCount: number, sceneCode: number, sceneCodeCount: number) {
171        const options: commonEventManager.CommonEventPublishData = {
172            code: 0,
173            data: 'message',
174            subscriberPermissions: [],
175            isOrdered: true,
176            isSticky: false,
177            parameters: { 'transactionType': btTransactionStatisticsType.TRANSACTION_TYPE_OPP_RECEIVE, 'result': result,
178                'resultCount': resultCount, 'sceneCode': sceneCode, 'sceneCodeCount': sceneCodeCount}
179        }
180        commonEventManager.publish(BT_TRANSACTION_EVENT, options, (err) => {
181            if (err) {
182            console.info(TAG, 'get bt transaction event publish failed.' + JSON.stringify(err));
183            } else {
184            console.info(TAG, 'get bt transaction event publish success.');
185            }
186        })
187    }
188
189    async readyReceiveEvent() {
190        try {
191            console.info(TAG, 'readyReceiveEvent');
192            clearTimeout(this.timerIdCreate);
193            this.subscriber = commonEventManager.createSubscriberSync(this.subscribeinfo);
194            try {
195                await commonEventManager.subscribe(this.subscriber,
196                (err: BusinessError, data: commonEventManager.CommonEventData) => {
197                    if (err) {
198                        console.error(TAG, `subscribe failed, code is ${err.code}, message is ${err.message}`);
199                        this.handleTerminate();
200                    } else {
201                        this.handleReceivedEvent(data);
202                    }
203                });
204            } catch (error) {
205                let err: BusinessError = error as BusinessError;
206                console.error(TAG, `subscribe failed, code is ${err.code}, message is ${err.message}`);
207                this.handleTerminate();
208            }
209        } catch (error) {
210            let err: BusinessError = error as BusinessError;
211            console.error(TAG, `createSubscriber failed, code is ${err.code}, message is ${err.message}`);
212            this.handleTerminate();
213        }
214    }
215
216    handleReceivedEvent(data: commonEventManager.CommonEventData) {
217        console.info(TAG, 'handleReceivedEvent: ' + data.event);
218        switch (data.event) {
219          case 'usual.event.bluetooth.OPP.TAP.ACCEPT': {
220              console.info(TAG, 'OPP.TAP.ACCEPT tapEventIsAllowed is ' + this.tapEventIsAllowed);
221              if (data.parameters == undefined) {
222                  console.error(TAG, `data.parameters undefined`);
223                  break;
224              }
225              let acceptType = data.parameters?.['acceptType'];
226              console.info(TAG, 'acceptType is ' + acceptType);
227              if (acceptType == OPP_TRANSFER_USER_ACCEPT && this.tapEventIsAllowed) {
228                  clearTimeout(this.timerIdReceive);
229                  this.currentCount = 1;
230                  this.subscriberLiveViewNotification();
231                  this.startOPPReceiveServiceUIAbility();
232                  this.tapEventIsAllowed = false;
233              } else if (acceptType == OPP_TRANSFER_AUTO_ACCEPT) {
234                  this.fileName = data.parameters?.['fileName'];
235                  this.currentCount = data.parameters?.['currentCount'];
236                  this.totalCount = data.parameters?.['totalCount'];
237                  this.startOPPReceiveServiceUIAbility();
238              }
239              break;
240          }
241          case 'usual.event.bluetooth.OPP.TAP.REJECT': {
242              console.info(TAG, 'tapEventIsAllowed is ' + this.tapEventIsAllowed);
243              if (!this.tapEventIsAllowed) {
244                  break;
245              }
246              clearTimeout(this.timerIdReceive);
247              this.transfering = false;
248              this.oppProfile.setIncomingFileConfirmation(false, -1);
249              this.reportBtTransactionChr(btTransactionStatisticsResult.TRANSACTION_RESULT_FAIL,
250                  this.totalCount, btTransactionStatisticsSceneCode.TRANSACTION_SCENECODE_2, this.totalCount);
251              this.reportBtTransactionChr(btTransactionStatisticsResult.TRANSACTION_RESULT_TOTAL,
252                  this.totalCount, btTransactionStatisticsSceneCode.TRANSACTION_SCENECODE_NA, 0);
253              this.pullUpReceiveResultNotification();
254              this.handleTerminate();
255              this.tapEventIsAllowed = false;
256              break;
257          }
258          case 'usual.event.bluetooth.OPP.TAP.REMOVE': {
259              console.info(TAG, 'tapEventIsAllowed is ' + this.tapEventIsAllowed);
260              clearTimeout(this.timerIdReceive);
261              this.transfering = false;
262              this.oppProfile.setIncomingFileConfirmation(false, -1);
263              this.reportBtTransactionChr(btTransactionStatisticsResult.TRANSACTION_RESULT_FAIL,
264                  this.totalCount, btTransactionStatisticsSceneCode.TRANSACTION_SCENECODE_5, this.totalCount);
265              this.reportBtTransactionChr(btTransactionStatisticsResult.TRANSACTION_RESULT_TOTAL,
266                  this.totalCount, btTransactionStatisticsSceneCode.TRANSACTION_SCENECODE_NA, 0);
267              this.pullUpReceiveResultNotification();
268              this.handleTerminate();
269              this.tapEventIsAllowed = true;
270              break;
271          }
272          case 'ohos.event.notification.BT.LIVEVIEW_REMOVE': {
273              this.tapEventIsAllowed = true;
274              this.oppProfile.cancelTransfer();
275              this.pullUpReceiveResultNotification();
276              break;
277          }
278          case 'usual.event.bluetooth.OPP.FINISH.NOTIFICATION': {
279              this.oppProfile.setLastReceivedFileUri(this.receiveDirectory);
280              setTimeout(() => {
281                  if (this.transfering) {
282                      console.info(TAG, 'still transfering, do not terminate');
283                      return;
284                  }
285                  console.info(TAG, 'transfer finished, terminateSelf');
286                  this.handleTerminate();
287              }, 100);
288              break;
289          }
290          case 'usual.event.bluetooth.OPP.FINISH.REMOVE': {
291              setTimeout(() => {
292                  if (this.transfering) {
293                      console.info(TAG, 'still transfering, do not terminate');
294                      return;
295                  }
296                  console.info(TAG, 'transfer finished, terminateSelf');
297                  this.handleTerminate();
298              }, 100);
299              break;
300          }
301          case 'ohos.event.notification.BT.GET_URI': {
302              this.handleBtUri(data);
303              break;
304          }
305          case 'usual.event.bluetooth.OPP.RECEIVE': {
306              clearTimeout(this.timerIdCreate);
307              if (data.parameters == undefined) {
308                  console.error(TAG, 'data.parameters undefined');
309                  break;
310              }
311              this.fileName = data.parameters?.['fileName'];
312              this.totalCount = data.parameters?.['totalCount'];
313              if (this.fileNameToReceiveMap.has(this.fileName)) {
314                  console.error(TAG, 'fileName is repeat, discard this OPP.RECEIVE');
315                  break;
316              }
317              this.fileNameToReceiveMap.set(this.fileName, true);
318              this.transfering = true;
319              this.pullUpNotification(this.fileName);
320              break;
321          }
322          default: {
323              break;
324          }
325        }
326    }
327
328    async handleBtUri(data: commonEventManager.CommonEventData) {
329        if (data.parameters == undefined) {
330            console.error(TAG, 'data.parameters undefined');
331            return;
332        }
333
334        this.oppProfile.off('transferStateChange');
335        this.oppProfile.on('transferStateChange', (data: opp.OppTransferInformation) => {
336            this.totalCount = data.totalCount;
337            this.currentCount = data.currentCount;
338            console.info(TAG, 'transferStateChange status' + data.status + ' result ' + data.result);
339            if (data.status == STATUS_RUNNING) {
340                this.pullUpSendProgressNotification(data.currentBytes / data.totalBytes * 100, this.fileName);
341            } else if (data.status == STATUS_FINISH) {
342                if (data.result != RESULT_SUCCESS_DISCONNECT) {
343                    data.result == RESULT_SUCCESS ? this.successCount++ : this.failedCount++;
344                    this.reportBtChrDataByResult(data.result);
345                }
346                if (data.result != RESULT_SUCCESS && data.filePath != undefined && data.filePath.trim().length > 0) {
347                    this.tapEventIsAllowed = true;
348                    console.info(TAG, `unlink file begin`);
349                    const uri = new fileUri.FileUri(this.receiveDirectory);
350                    let deleteSandBoxUri = uri.path;
351                    fs.unlinkSync(deleteSandBoxUri);
352                }
353                if (data.currentCount == data.totalCount || data.result == RESULT_ERROR_CANCELED ||
354                    data.result == RESULT_ERROR_TRANSFER_FAILED) {
355                    this.tapEventIsAllowed = true;
356                    this.cancelSendProgressNotification();
357                    this.pullUpReceiveResultNotification();
358                    this.oppProfile.off('transferStateChange');
359                    console.info(TAG, 'transferStateChange' + data.result);
360                }
361            }
362        });
363
364        if ((data.parameters.uri == undefined) || (data.parameters.uri == null) ||
365            (data.parameters.uri.length == 0)) {
366            console.error(TAG, 'uri not right');
367            return;
368        }
369        this.receiveDirectory = data.parameters.uri;
370        try {
371            const uri = new fileUri.FileUri(data.parameters.uri);
372            if (!fs.accessSync(uri.path)) {
373                console.error(TAG, 'uri path is not access');
374                return;
375            }
376            if (data.parameters.fileType == 'files') {
377                this.sandBoxUri = uri.path + '/' + this.fileName;
378                this.receiveDirectory = this.receiveDirectory + '/' + this.fileName;
379            } else {
380                this.sandBoxUri = uri.path;
381            }
382            let file = fs.openSync(this.sandBoxUri, fs.OpenMode.CREATE | fs.OpenMode.READ_WRITE);
383            await this.oppProfile.setIncomingFileConfirmation(true, file.fd);
384            fs.close(file.fd);
385        } catch (err) {
386            console.error(TAG, `openFd failed, code: ${err?.code} message:${err?.message}`);
387            return;
388        }
389    }
390
391    handleReceive(data: commonEventManager.CommonEventData) {
392        this.pullUpNotification(this.fileName);
393    }
394
395    async pullUpNotification(fileName: string) {
396        console.info(TAG, 'pullUpNotification');
397        let wantAgentObjAccept: WantAgent;
398        let wantAgentObjReject: WantAgent;
399        let wantAgentObjRemove: WantAgent;
400        wantAgentObjAccept = await this.getNotificationWantAgent('ohos.event.notification.BT.TAP_ACCEPT');
401        wantAgentObjReject = await this.getNotificationWantAgent('ohos.event.notification.BT.TAP_REJECT');
402        wantAgentObjRemove = await this.getNotificationWantAgent('ohos.event.notification.BT.TAP_REMOVE');
403        await this.publishReceiveNotification(wantAgentObjReject, wantAgentObjAccept, wantAgentObjRemove, fileName);
404    }
405
406    async getNotificationWantAgent(info: string): Promise<WantAgent> {
407        let wantAgentObjUse: WantAgent;
408        let wantAgentInfo: wantAgent.WantAgentInfo = {
409            wants: [
410                {
411                    action: info,
412                }
413            ],
414            actionType: wantAgent.OperationType.SEND_COMMON_EVENT,
415            requestCode: 0,
416            wantAgentFlags: [wantAgent.WantAgentFlags.CONSTANT_FLAG],
417        };
418        wantAgentObjUse = await wantAgent.getWantAgent(wantAgentInfo);
419        console.info(TAG, 'getNotificationWantAgent success for ' + info);
420        return wantAgentObjUse;
421    }
422
423    async publishReceiveNotification(wantAgentObjReject: WantAgent, wantAgentObjAccept: WantAgent,
424        wantAgentObjRemove: WantAgent, fileName: string) {
425        let waitTime: number = 50 * 1000;
426        let timeout: number = new Date().getTime() + waitTime;
427        let notificationRequest: notificationManager.NotificationRequest = {
428            content: {
429                notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
430                normal: {
431                    title: this.context.resourceManager.getStringSync($r('app.string.bluetooth_receive_notification_title').id),
432                    text: fileName,
433                }
434            },
435            id: 1,
436            notificationSlotType: notificationManager.SlotType.SERVICE_INFORMATION,
437            actionButtons: [
438                {
439                    title: this.context.resourceManager.getStringSync($r('app.string.bluetooth_receive_notification_button_reject').id),
440                    wantAgent: wantAgentObjReject
441                },
442                {
443                    title: this.context.resourceManager.getStringSync($r('app.string.bluetooth_receive_notification_button_accept').id),
444                    wantAgent: wantAgentObjAccept
445                }
446            ],
447            autoDeletedTime: timeout,
448            removalWantAgent: wantAgentObjRemove
449        };
450        notificationManager.publish(notificationRequest).then(() => {
451            console.info(TAG, 'publishReceiveNotification success');
452            this.timerIdReceive = setTimeout(() => {
453                console.info(TAG, 'receive notification 50 seconds but no tap');
454                this.transfering = false;
455                this.oppProfile.setIncomingFileConfirmation(false, -1);
456                this.reportBtChrDataByResult(RESULT_ERROR_NOT_ACCEPTABLE);
457                this.pullUpReceiveResultNotification();
458                this.handleTerminate();
459            }, waitTime);
460        }).catch((err: Base.BusinessError) => {
461            console.error(TAG, 'publishReceiveNotification fail');
462            this.handleTerminate();
463        });
464    }
465
466    resetCount() {
467        console.info(TAG, 'resetCount');
468        this.totalCount = 0;
469        this.currentCount = 0;
470        this.successCount = 0;
471        this.failedCount = 0;
472    }
473
474    async publishFinishNotification(wantAgentObj: WantAgent, wantAgentObjRemove: WantAgent,
475        successNum: number, failNum: number) {
476        console.info(TAG, 'publishFinishNotification');
477        let notificationRequest: notificationManager.NotificationRequest = {
478            content: {
479                notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
480                normal: {
481                    title: this.context.resourceManager.getStringSync($r('app.string.bluetooth_receive_finish_title').id),
482                    text: this.getFormatString($r('app.string.bluetooth_receive_finish_text'),
483                        this.getFormatPlural($r('app.plural.bluetooth_receive_finish_success_text', successNum, successNum), successNum),
484                        this.getFormatPlural($r('app.plural.bluetooth_receive_finish_fail_text', failNum, failNum), failNum))
485                }
486            },
487            id: 2,
488            notificationSlotType: notificationManager.SlotType.SERVICE_INFORMATION,
489            wantAgent: wantAgentObj,
490            tapDismissed: true,
491            removalWantAgent: wantAgentObjRemove
492        };
493        notificationManager.publish(notificationRequest).then(() => {
494            console.info(TAG, 'publishFinishNotification show success');
495            this.resetCount();
496        }).catch((err: Base.BusinessError) => {
497            console.error(TAG, 'publishFinishNotification fail');
498            this.handleTerminate();
499        });
500    }
501
502    startOPPReceiveServiceUIAbility() {
503        console.info(TAG, 'startOPPReceiveServiceUIAbility');
504        AppStorage.setOrCreate('oppProfile', this.oppProfile);
505        this.oppProfile.setIncomingFileConfirmation(true, -1);
506    }
507
508    async pullUpReceiveResultNotification() {
509        console.info(TAG, 'pullUpNotification successCount' + this.successCount +
510            'failedCount' + this.failedCount + 'totalCount' + this.totalCount);
511        if (this.successCount + this.failedCount != this.totalCount && this.totalCount >= this.successCount) {
512            this.failedCount = this.totalCount - this.successCount;
513        }
514        let wantAgentObj: WantAgent;
515        let wantAgentObjRemove: WantAgent;
516        wantAgentObj = await this.getNotificationWantAgent('ohos.event.notification.BT.FINISH_NOTIFICATION');
517        wantAgentObjRemove = await this.getNotificationWantAgent('ohos.event.notification.BT.FINISH_REMOVE');
518        await this.publishFinishNotification(wantAgentObj, wantAgentObjRemove, this.successCount, this.failedCount);
519    }
520
521    async publishTransProgessNotification(imagePixelMapButton: image.PixelMap, imagePixelMapCapsule: image.PixelMap,
522        wantAgentObjRemove: WantAgent, percent: number, name: string, currentCount: number, totalCount: number) {
523        console.info(TAG, 'publishTransProgessNotification');
524        let progressTitle = this.getFormatNum($r('app.string.bluetooth_receive_count_text'), currentCount, totalCount);
525        let progressName = name;
526        if (this.totalCount == UNKNOWN_VALUE) {
527            progressTitle =
528                this.context.resourceManager.getStringSync($r('app.string.bluetooth_transfer_notification_title').id);
529            progressName =
530                this.context.resourceManager.getStringSync($r('app.string.bluetooth_transfer_receive_text').id) + name;
531        }
532        let notificationRequest: notificationManager.NotificationRequest = {
533            notificationSlotType: notificationManager.SlotType.LIVE_VIEW,
534            id: this.capsuleNotificationID,
535            content: {
536                notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_SYSTEM_LIVE_VIEW,
537                systemLiveView: {
538                    title: progressTitle,
539                    text: progressName,
540                    typeCode: 8,
541                    button: {
542                        names: [this.cancelTransEvent],
543                        iconsResource: [$r('app.media.public_cancel_filled')],
544                    },
545                    capsule: {
546                        title: 'bluetooth',
547                        icon: imagePixelMapCapsule,
548                        backgroundColor: '#0A59F7',
549                    },
550                    progress: {
551                        maxValue: 100,
552                        currentValue: percent,
553                        isPercentage: true,
554                    },
555                }
556            },
557            tapDismissed: false,
558            removalWantAgent: wantAgentObjRemove
559        };
560
561        notificationManager.publish(notificationRequest).then(() => {
562            console.info(TAG, 'publishTransProgessNotification success');
563            if (percent == 100 && this.currentCount == this.totalCount) {
564                this.cancelSendProgressNotification();
565            }
566        }).catch((err: Base.BusinessError) => {
567            console.error(TAG, 'publishTransProgessNotification fail');
568        });
569    }
570
571    async pullUpSendProgressNotification(percent: number, name: string) {
572        const currentDate: Date = new Date();
573        const currentTimeInMsUsingGetTime: number = currentDate.getTime();
574        if (percent !== 100 && (currentTimeInMsUsingGetTime - this.timeInterval) < 1000) {
575            return;
576        }
577        this.timeInterval = currentTimeInMsUsingGetTime;
578
579        console.info(TAG, 'ready to pullUpSendProgressNotification');
580        this.isEnabledLive2 = systemParameterEnhance.getSync('persist.systemui.live2', 'false') == 'true';
581        console.info(TAG, 'this.isEnabledLive2 = ' + this.isEnabledLive2);
582
583        let imagePixelMapButton: image.PixelMap | undefined = undefined;
584        let imagePixelMapCapsule: image.PixelMap | undefined = undefined;
585        try {
586            let drawableDescriptor1: DrawableDescriptor = this.context.resourceManager.getDrawableDescriptor($r('app.media.public_cancel_filled').id);
587            imagePixelMapButton = drawableDescriptor1.getPixelMap();
588            let drawableDescriptor2: DrawableDescriptor;
589            if (!this.isEnabledLive2) {
590                drawableDescriptor2 =
591                    this.context.resourceManager.getDrawableDescriptor($r('app.media.foreground').id);
592            } else {
593                drawableDescriptor2 =
594                    this.context.resourceManager.getDrawableDescriptor($r('app.media.foregroundSmall').id);
595            }
596            imagePixelMapCapsule = drawableDescriptor2.getPixelMap();
597        } catch (error) {
598            let code = (error as BusinessError).code;
599            let message = (error as BusinessError).message;
600            console.error(TAG, `getDrawableDescriptor failed, error code is ${code}, message is ${message}`);
601            return;
602        }
603
604        let wantAgentObjRemove: WantAgent;
605        wantAgentObjRemove = await this.getNotificationWantAgent('ohos.event.notification.BT.LIVEVIEW_REMOVE');
606        await this.publishTransProgessNotification(imagePixelMapButton, imagePixelMapCapsule,
607            wantAgentObjRemove, percent, name, this.currentCount, this.totalCount);
608    }
609
610    cancelSendProgressNotification() {
611        console.info(TAG, 'cancelSendProgressNotification ready to cancel.');
612        notificationManager.cancel(this.capsuleNotificationID).then(() => {
613            console.info(TAG, 'Succeeded in canceling notification');
614        }).catch((err: BusinessError) => {
615            console.error(TAG, `failed to cancel notification. Code is ${err.code}, message is ${err.message}`)
616        });
617        this.transfering = false;
618        if (this.successCount == 0) {
619            console.info(TAG, 'no success file, terminate it.');
620            this.handleTerminate();
621        }
622    }
623
624    subscriberLiveViewNotification(): void {
625        let subscriber: notificationManager.SystemLiveViewSubscriber = {
626            onResponse: (id: number, option: notificationManager.ButtonOptions) => {
627                switch (option.buttonName) {
628                    case this.cancelTransEvent: {
629                        console.info(TAG, 'cancel transfer.');
630                        this.tapEventIsAllowed = true;
631                        this.oppProfile.cancelTransfer();
632                        this.cancelSendProgressNotification();
633                        this.pullUpReceiveResultNotification();
634                        break;
635                    }
636                    default: {
637                        break;
638                    }
639                }
640            }
641        };
642        try {
643            notificationManager.subscribeSystemLiveView(subscriber);
644        } catch(e) {
645            console.error(TAG, 'subscriberLiveViewNotification fail');
646        }
647    }
648
649    getFormatString(resource: Resource, value1: string, value2: string): string {
650        let result = this.context.resourceManager.getStringSync(resource.id);
651        result = result.replace('%1$s', value1);
652        result = result.replace('%2$s', value2);
653        return result;
654    }
655
656    getFormatNum(resource: Resource, value1: number, value2: number): string {
657        let result = this.context.resourceManager.getStringSync(resource.id);
658        result = result.replace('%1$d', value1.toString());
659        result = result.replace('%2$d', value2.toString());
660        return result;
661    }
662
663    getFormatPlural(resource: Resource, value: number): string {
664        let result = this.context.resourceManager.getPluralStringValueSync(resource.id, value);
665        result = result.replace('%d', value.toString());
666        return result;
667    }
668
669    async startContinuousTask() {
670        let wantAgentObj: WantAgent;
671        wantAgentObj = await this.getNotificationWantAgent('ohos.event.notification.BT.BACK_RUNNING');
672        backgroundTaskManager.startBackgroundRunning(this.context,
673        backgroundTaskManager.BackgroundMode.BLUETOOTH_INTERACTION, wantAgentObj).then(() => {
674            console.info(TAG, `Succeeded in operationing startBackgroundRunning.`);
675        }).catch((err: BusinessError) => {
676            console.error(TAG, `Failed to operation startBackgroundRunning. Code is ${err.code}, message is ${err.message}`);
677        });
678    }
679
680    stopContinuousTask() {
681        backgroundTaskManager.stopBackgroundRunning(this.context).then(() => {
682            console.info(TAG, `Succeeded in operationing stopBackgroundRunning.`);
683        }).catch((err: BusinessError) => {
684            console.error(TAG, `Failed to operation stopBackgroundRunning. Code is ${err.code}, message is ${err.message}`);
685        });
686    }
687}
688