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 image from '@ohos.multimedia.image'; 20import wantAgent from '@ohos.app.ability.wantAgent'; 21import { WantAgent } from '@ohos.wantAgent'; 22import { BusinessError } from '@ohos.base'; 23import { DrawableDescriptor } from '@ohos.arkui.drawableDescriptor'; 24import opp from '@ohos.bluetooth.opp'; 25import commonEventManager from '@ohos.commonEventManager'; 26import backgroundTaskManager from '@ohos.backgroundTaskManager'; 27import fs from '@ohos.file.fs'; 28 29const TAG: string = '[BT_SEND_SERVICE]==>' 30 31export default class BluetoothSendUIAbility extends UIAbility { 32 private oppProfile = opp.createOppServerProfile(); 33 private timeInterval: number = 0; 34 private capsuleNotificationID: number = 200; 35 private cancelTransEvent: string = 'ohos.event.notification.BT.TAP_CANCEL'; 36 private subscriber: commonEventManager.CommonEventSubscriber | null = null; 37 private subscribeinfo: commonEventManager.CommonEventSubscribeInfo = { 38 events: [ 39 'ohos.event.notification.BT.LIVEVIEW_REMOVE' 40 ] 41 }; 42 private timerIdCreate: number; 43 private timerIdCreated: boolean = false; 44 45 onCreate(want: Want): void { 46 console.info(TAG, 'BluetoothSendUIAbility onCreate'); 47 this.timerIdCreate = setTimeout(() => { 48 console.info(TAG, 'onCreate 10 seconds but nothing received'); 49 this.handleTerminate(); 50 }, 10000); 51 this.timerIdCreated = true; 52 this.subscribeTransferState(); 53 this.readyReceiveEvent(); 54 this.startContinuousTask(); 55 } 56 57 onDestroy(): void { 58 console.info(TAG, 'BluetoothSendUIAbility onDestroy'); 59 } 60 61 onForeground(): void { 62 console.info(TAG, 'BluetoothSendUIAbility onForeground'); 63 } 64 65 handleTerminate() { 66 this.stopContinuousTask(); 67 this.context.terminateSelf(); 68 } 69 70 readyReceiveEvent() { 71 try { 72 console.info(TAG, 'readyReceiveEvent'); 73 this.subscriber = commonEventManager.createSubscriberSync(this.subscribeinfo); 74 try { 75 commonEventManager.subscribe(this.subscriber, 76 (err: BusinessError, data: commonEventManager.CommonEventData) => { 77 if (err) { 78 console.error(TAG, `subscribe failed, code is ${err.code}, message is ${err.message}`); 79 this.handleTerminate(); 80 } else { 81 this.handleReceivedEvent(data); 82 } 83 }); 84 } catch (error) { 85 let err: BusinessError = error as BusinessError; 86 console.error(TAG, `subscribe failed, code is ${err.code}, message is ${err.message}`); 87 this.handleTerminate(); 88 } 89 } catch (error) { 90 let err: BusinessError = error as BusinessError; 91 console.error(TAG, `createSubscriber failed, code is ${err.code}, message is ${err.message}`); 92 this.handleTerminate(); 93 } 94 } 95 96 handleReceivedEvent(data: commonEventManager.CommonEventData) { 97 console.info(TAG, 'handleReceivedEvent: ' + data.event); 98 switch (data.event) { 99 case 'ohos.event.notification.BT.LIVEVIEW_REMOVE': { 100 this.oppProfile.cancelTransfer(); 101 this.handleTerminate(); 102 break; 103 } 104 default: { 105 break; 106 } 107 } 108 } 109 110 subscribeTransferState() { 111 try { 112 this.subscriberLiveViewNotification(); 113 this.oppProfile.on('transferStateChange', (data: opp.OppTransferInformation) => { 114 if (this.timerIdCreated) { 115 this.timerIdCreated = false; 116 clearTimeout(this.timerIdCreate); 117 } 118 if (data.status == 1) { 119 this.pullUpSendProgressNotification(data.currentBytes / data.totalBytes * 100, 120 this.getFileName(data.filePath)); 121 } else if (data.status == 2) { 122 if (data.result != 0) { 123 console.info(TAG, 'send fail'); 124 this.cancelSendProgressNotification(0); 125 this.pullUpReceiveResultNotification(0, 1); 126 } 127 console.info(TAG, 'transfer finished'); 128 this.oppProfile.off('transferStateChange'); 129 let dirPath = this.context.filesDir; 130 console.log(TAG, 'delete dirPath is ' + dirPath); 131 fs.rmdirSync(dirPath); 132 } 133 }); 134 console.log(TAG, 'oppProfile.transferStateChange'); 135 } catch (err) { 136 console.error(TAG, 'subscribeTransferState err'); 137 this.handleTerminate(); 138 } 139 } 140 141 async getNotificationWantAgent(info: string): Promise<WantAgent> { 142 let wantAgentObjUse: WantAgent; 143 let wantAgentInfo: wantAgent.WantAgentInfo = { 144 wants: [ 145 { 146 action: info, 147 } 148 ], 149 actionType: wantAgent.OperationType.SEND_COMMON_EVENT, 150 requestCode: 0, 151 wantAgentFlags: [wantAgent.WantAgentFlags.CONSTANT_FLAG], 152 }; 153 wantAgentObjUse = await wantAgent.getWantAgent(wantAgentInfo); 154 console.info(TAG, 'getNotificationWantAgent success for ' + info); 155 return wantAgentObjUse; 156 } 157 158 async publishFinishNotification(wantAgentObj: WantAgent, successNum: number, failNum: number) { 159 console.info(TAG, 'publishFinishNotification'); 160 let notificationRequest: notificationManager.NotificationRequest = { 161 content: { 162 notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT, 163 normal: { 164 title: this.context.resourceManager.getStringSync($r('app.string.bluetooth_send_finish_title').id), 165 text: this.getFormatString($r('app.string.bluetooth_send_finish_text'), 166 this.getFormatPlural($r('app.plural.bluetooth_send_finish_success_text', successNum, successNum), successNum), 167 this.getFormatPlural($r('app.plural.bluetooth_send_finish_fail_text', failNum, failNum), failNum)) 168 } 169 }, 170 id: 2, 171 notificationSlotType: notificationManager.SlotType.SERVICE_INFORMATION, 172 wantAgent: wantAgentObj, 173 tapDismissed: true, 174 }; 175 notificationManager.publish(notificationRequest).then(() => { 176 console.info(TAG, 'publishFinishNotification success'); 177 }).catch((err: BusinessError) => { 178 console.error(TAG, 'publishFinishNotification fail'); 179 this.handleTerminate(); 180 }); 181 } 182 183 async pullUpReceiveResultNotification(successNum: number, failNum: number) { 184 console.info(TAG, 'pullUpReceiveResultNotification'); 185 let wantAgentObj: WantAgent; 186 wantAgentObj = await this.getNotificationWantAgent('ohos.event.notification.BT.FINISH_SEND'); 187 await this.publishFinishNotification(wantAgentObj, successNum, failNum); 188 } 189 190 async publishTransProgessNotification(imagePixelMapButton: image.PixelMap, imagePixelMapCapsule: image.PixelMap, 191 wantAgentObjRemove: WantAgent, percent: number, name: string) { 192 console.info(TAG, 'publishTransProgessNotification'); 193 let notificationRequest: notificationManager.NotificationRequest = { 194 notificationSlotType: notificationManager.SlotType.LIVE_VIEW, 195 id: this.capsuleNotificationID, 196 content: { 197 notificationContentType: notificationManager.ContentType.NOTIFICATION_CONTENT_SYSTEM_LIVE_VIEW, 198 systemLiveView: { 199 title: this.context.resourceManager.getStringSync($r('app.string.bluetooth_transfer_notification_title').id), 200 text: this.context.resourceManager.getStringSync($r('app.string.bluetooth_transfer_send_text').id) + name, 201 typeCode: 8, 202 button: { 203 names: [this.cancelTransEvent], 204 icons: [imagePixelMapButton], 205 }, 206 capsule: { 207 title: 'bluetooth', 208 icon: imagePixelMapCapsule, 209 backgroundColor: '#0A59F7', 210 }, 211 progress: { 212 maxValue: 100, 213 currentValue: percent, 214 isPercentage: true, 215 }, 216 } 217 }, 218 tapDismissed: false, 219 removalWantAgent: wantAgentObjRemove 220 }; 221 222 notificationManager.publish(notificationRequest).then(() => { 223 console.info(TAG, 'publishTransProgessNotification success'); 224 if (percent === 100) { 225 this.cancelSendProgressNotification(1); 226 } 227 }).catch((err: BusinessError) => { 228 console.error(TAG, 'publishTransProgessNotification fail'); 229 }); 230 } 231 232 async pullUpSendProgressNotification(percent: number, name: string) { 233 const currentDate: Date = new Date(); 234 const currentTimeInMsUsingGetTime: number = currentDate.getTime(); 235 if (percent !== 100 && (currentTimeInMsUsingGetTime - this.timeInterval) < 1000) { 236 return; 237 } 238 this.timeInterval = currentTimeInMsUsingGetTime; 239 240 console.info(TAG, 'ready to pullUpSendProgressNotification'); 241 let imagePixelMapButton: image.PixelMap | undefined = undefined; 242 let imagePixelMapCapsule: image.PixelMap | undefined = undefined; 243 try { 244 let drawableDescriptor1: DrawableDescriptor = this.context.resourceManager.getDrawableDescriptor($r('app.media.public_cancel_filled').id); 245 imagePixelMapButton = drawableDescriptor1.getPixelMap(); 246 let drawableDescriptor2: DrawableDescriptor = this.context.resourceManager.getDrawableDescriptor($r('app.media.foreground').id); 247 imagePixelMapCapsule = drawableDescriptor2.getPixelMap(); 248 } catch (error) { 249 let code = (error as BusinessError).code; 250 let message = (error as BusinessError).message; 251 console.error(TAG, `getDrawableDescriptor failed, error code is ${code}, message is ${message}`); 252 return; 253 } 254 255 let wantAgentObjRemove: WantAgent; 256 wantAgentObjRemove = await this.getNotificationWantAgent('ohos.event.notification.BT.LIVEVIEW_REMOVE'); 257 await this.publishTransProgessNotification(imagePixelMapButton, imagePixelMapCapsule, wantAgentObjRemove, 258 percent, name); 259 } 260 261 cancelSendProgressNotification(successOrNot: number) { 262 console.info(TAG, 'cancelSendProgressNotification ready to cancel.'); 263 notificationManager.cancel(this.capsuleNotificationID).then(() => { 264 console.info(TAG, 'Succeeded in canceling notification.'); 265 if (successOrNot === 1) { 266 this.pullUpReceiveResultNotification(1, 0); 267 } 268 this.handleTerminate(); 269 }).catch((err: BusinessError) => { 270 console.error(TAG, `failed to cancel notification. Code is ${err.code}, message is ${err.message}`) 271 this.handleTerminate(); 272 }); 273 } 274 275 subscriberLiveViewNotification(): void { 276 let subscriber: notificationManager.SystemLiveViewSubscriber = { 277 onResponse: (id: number, option: notificationManager.ButtonOptions) => { 278 switch (option.buttonName) { 279 case this.cancelTransEvent: { 280 console.info(TAG, 'cancel transfer.'); 281 this.oppProfile.cancelTransfer(); 282 this.cancelSendProgressNotification(0); 283 break; 284 } 285 default: { 286 break; 287 } 288 } 289 } 290 }; 291 try { 292 notificationManager.subscribeSystemLiveView(subscriber); 293 } catch(e) { 294 console.error(TAG, 'subscriberLiveViewNotification fail'); 295 } 296 } 297 298 getFileName(filePath: string): string { 299 let extension = filePath.substring(filePath.lastIndexOf('/') + 1); 300 return extension; 301 } 302 303 getFormatString(resource: Resource, value1: string, value2: string): string { 304 let result = this.context.resourceManager.getStringSync(resource.id); 305 result = result.replace('%1$s', value1); 306 result = result.replace('%2$s', value2); 307 return result; 308 } 309 310 getFormatPlural(resource: Resource, value: number): string { 311 let result = this.context.resourceManager.getPluralStringValueSync(resource.id, value); 312 result = result.replace('%d', value.toString()); 313 return result; 314 } 315 316 async startContinuousTask() { 317 let wantAgentObj: WantAgent; 318 wantAgentObj = await this.getNotificationWantAgent('ohos.event.notification.BT.BACK_RUNNING'); 319 backgroundTaskManager.startBackgroundRunning(this.context, 320 backgroundTaskManager.BackgroundMode.BLUETOOTH_INTERACTION, wantAgentObj).then(() => { 321 console.info(TAG, `Succeeded in operationing startBackgroundRunning.`); 322 }).catch((err: BusinessError) => { 323 console.error(TAG, `Failed to operation startBackgroundRunning. Code is ${err.code}, message is ${err.message}`); 324 }); 325 } 326 327 stopContinuousTask() { 328 backgroundTaskManager.stopBackgroundRunning(this.context).then(() => { 329 console.info(TAG, `Succeeded in operationing stopBackgroundRunning.`); 330 }).catch((err: BusinessError) => { 331 console.error(TAG, `Failed to operation stopBackgroundRunning. Code is ${err.code}, message is ${err.message}`); 332 }); 333 } 334}