1/** 2 * Copyright (c) 2023-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 bundleMonitor from '@ohos.bundle.bundleMonitor'; 17import router from '@ohos.router'; 18import deviceInfo from '@ohos.deviceInfo'; 19import accessibility from '@ohos.accessibility'; 20import config from '@ohos.accessibility.config'; 21import { ResourceUtils } from '../model/accessibilityImpl/resourceUtils'; 22import LogUtil from '../../../../../../common/utils/src/main/ets/default/baseUtil/LogUtil'; 23import ConfigData from '../../../../../../common/utils/src/main/ets/default/baseUtil/ConfigData'; 24import LongHeadComponent from '../../../../../../common/component/src/main/ets/default/longHeadComponent'; 25import ComponentConfig from '../../../../../../common/component/src/main/ets/default/ComponentConfig'; 26 27const MODULE_TAG = ConfigData.TAG + 'service-> '; 28const DEVICE_TYPE = deviceInfo.deviceType; 29 30class ServiceInfoData { 31 serviceIcon: string = ''; 32 serviceTitle: string = ''; 33 serviceArrow: string = ''; 34 serviceSummary: string = ''; 35 serviceEndText: string = ''; 36 serviceState: boolean = false; 37 serviceId: string = ''; 38 serviceBundleName: string = ''; 39 serviceUri: string = ''; 40} 41 42/** 43 * AccessibilityService Info 44 */ 45@Entry 46@Component 47struct AccessibilityServiceInfo { 48 tag: string = 'AccessibilityServiceInfoSettings'; 49 isPhoneOrRK: boolean = true; 50 bundleName: string = ''; 51 serviceName: string = ''; 52 capabilities: Array<accessibility.Capability> = ['retrieve', 'keyEventObserver', 'gesture', 'zoom', 'touchGuide']; 53 private scroller: Scroller = new Scroller(); 54 @State serviceIsOn: boolean = false; 55 @State pageChanged: boolean = false; 56 @State serviceIsOnClick: boolean = false; 57 @StorageLink('accessibilityServiceInfo') serviceInfo: ServiceInfoData = { 58 serviceIcon: '', 59 serviceTitle: '', 60 serviceArrow: '', 61 serviceSummary: '', 62 serviceEndText: '', 63 serviceState: false, 64 serviceId: '', 65 serviceBundleName: '', 66 serviceUri: '', 67 }; 68 showDialog: CustomDialogController | null = new CustomDialogController({ 69 builder: ShowDialog({ 70 firstClickAction: () => { 71 this.openServiceButton(this.serviceInfo.serviceId); 72 }, 73 secondClickAction: () => { 74 this.cancelButton(); 75 }, 76 }), 77 alignment: this.isPhoneOrRK ? DialogAlignment.Bottom : DialogAlignment.Center, 78 autoCancel: true, 79 offset: ({ dx: 0, dy: this.isPhoneOrRK ? '-12dp' : 0 }), 80 cancel: this.cancelButton, 81 }); 82 83 build() { 84 Column() { 85 GridContainer({ gutter: ConfigData.GRID_CONTAINER_GUTTER_24, margin: ConfigData.GRID_CONTAINER_MARGIN_24 }) { 86 Column() { 87 LongHeadComponent({ longHeadName: this.serviceInfo.serviceTitle, isActive: true }); 88 Scroll(this.scroller) { 89 Column() { 90 Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) { 91 Row() { 92 Text(this.serviceInfo.serviceTitle) 93 .fontSize($r('sys.float.ohos_id_text_size_body1')) 94 .fontColor($r('sys.color.ohos_id_color_text_primary')) 95 .fontWeight(FontWeight.Medium) 96 .maxLines(ComponentConfig.MAX_LINES_3) 97 .textOverflow({ overflow: TextOverflow.Ellipsis }) 98 .width('90%') 99 .textAlign(TextAlign.Start); 100 101 Blank(); 102 103 if (this.pageChanged) { 104 Toggle({ type: ToggleType.Switch, isOn: this.serviceIsOn }) 105 .width('36vp') 106 .height('20vp') 107 .selectedColor('#007DFF') 108 .onClick(() => { 109 this.serviceIsOnClick = true; 110 }) 111 .onChange((isOn: boolean) => { 112 this.setExtensionServiceState(isOn); 113 }); 114 } else { 115 Toggle({ type: ToggleType.Switch, isOn: this.serviceIsOn }) 116 .width('36vp') 117 .height('20vp') 118 .selectedColor('#007DFF') 119 .onClick(() => { 120 this.serviceIsOnClick = true; 121 }) 122 .onChange((isOn: boolean) => { 123 this.setExtensionServiceState(isOn); 124 }); 125 } 126 } 127 .height($r('app.float.wh_value_56')) 128 .width(ConfigData.WH_100_100) 129 .alignItems(VerticalAlign.Center) 130 .padding({ left: $r('app.float.wh_value_12'), right: $r('app.float.wh_value_6') }) 131 .backgroundColor($r('app.color.white_bg_color')) 132 .borderRadius($r('app.float.radius_24')); 133 } 134 135 Blank(4); 136 137 Row() { 138 Text($r('app.string.more_settings')) 139 .fontSize($r('sys.float.ohos_id_text_size_body1')) 140 .fontColor($r('sys.color.ohos_id_color_text_primary')) 141 .fontWeight(FontWeight.Medium) 142 .textAlign(TextAlign.Start); 143 144 Blank(); 145 Image('/res/image/ic_settings_arrow.svg') 146 .width($r('app.float.wh_value_12')) 147 .height($r('app.float.wh_value_24')) 148 .margin({ right: $r('app.float.distance_8') }) 149 .fillColor($r('sys.color.ohos_id_color_primary')) 150 .opacity($r('sys.float.ohos_fa_alpha_content_fourth')); 151 } 152 .height($r('app.float.wh_value_56')) 153 .width(ConfigData.WH_100_100) 154 .alignItems(VerticalAlign.Center) 155 .padding({ left: $r('app.float.wh_value_12'), right: $r('app.float.wh_value_6') }) 156 .enabled(false) 157 .backgroundColor($r('app.color.white_bg_color')) 158 .borderRadius($r('app.float.radius_24')); 159 160 Blank(20); 161 162 Text($r('app.string.help')) 163 .width(ConfigData.WH_100_100) 164 .fontSize($r('app.float.font_14')) 165 .fontColor($r('sys.color.ohos_id_color_text_secondary')) 166 .height($r('app.float.wh_value_52')) 167 .lineHeight($r('app.float.wh_value_20')) 168 .padding({ 169 left: $r('app.float.wh_value_12'), 170 top: $r('app.float.distance_4'), 171 bottom: $r('app.float.distance_24'), 172 }); 173 Text(this.serviceInfo.serviceSummary) 174 .width(ConfigData.WH_100_100) 175 .fontSize($r('app.float.font_14')) 176 .fontColor($r('sys.color.ohos_id_color_text_secondary')) 177 .height('auto') 178 .lineHeight($r('app.float.wh_value_20')) 179 .padding({ 180 left: $r('app.float.wh_value_12'), 181 top: $r('app.float.distance_4'), 182 bottom: $r('app.float.distance_80'), 183 }); 184 } 185 .useSizeType({ 186 sm: { span: 4, offset: 0 }, 187 md: { span: 6, offset: 1 }, 188 lg: { span: 8, offset: 2 }, 189 }); 190 } 191 .width(ConfigData.WH_100_100) 192 .height(ConfigData.WH_100_100) 193 .align(Alignment.TopStart) 194 .scrollBar(BarState.Auto) 195 .edgeEffect(EdgeEffect.Spring); 196 } 197 } 198 .width(ConfigData.WH_100_100) 199 .height(ConfigData.WH_100_100); 200 } 201 .backgroundColor($r("sys.color.ohos_id_color_sub_background")) 202 .width(ConfigData.WH_100_100) 203 .height(ConfigData.WH_100_100); 204 } 205 206 aboutToAppear(): void { 207 LogUtil.info(`${this.tag} aboutToAppear start`); 208 if (DEVICE_TYPE === 'phone' || DEVICE_TYPE === 'default') { 209 this.isPhoneOrRK = true; 210 } else { 211 this.isPhoneOrRK = false; 212 } 213 this.openExtensionServiceManagementDetailsStatusListener(); 214 this.getServiceName(); 215 LogUtil.info(`${this.tag} aboutToAppear end `); 216 } 217 218 aboutToDisappear(): void { 219 LogUtil.info(`${this.tag} aboutToDisappear start`); 220 this.closeExtensionServiceManagementDetailsStatusListener(); 221 this.showDialog = null; 222 } 223 224 onPageShow(): void { 225 this.openExtensionServiceManagementDetailsApplicationListener(); 226 this.setExtensionServiceManagementApplicationData(); 227 } 228 229 getServiceName(): void { 230 let param = router.getParams(); 231 if (param) { 232 LogUtil.info(`${this.tag} param is valid`); 233 this.serviceInfo = param as ServiceInfoData; 234 this.bundleName = this.serviceInfo.serviceBundleName; 235 this.serviceName = this.serviceInfo.serviceId; 236 this.serviceIsOn = this.serviceInfo.serviceState; 237 AppStorage.SetOrCreate('ServiceInformation', param); 238 } 239 } 240 241 setExtensionServiceState(isOn: boolean): void { 242 LogUtil.info(`${this.tag} setExtensionServiceState ${isOn}`); 243 LogUtil.info(`${this.tag} serviceState: ${JSON.stringify(this.serviceIsOn)}`); 244 if (isOn) { 245 if (this.serviceIsOnClick) { 246 this.showDialog?.open(); 247 this.serviceIsOnClick = false; 248 } 249 } else { 250 this.disableAbility(this.serviceInfo.serviceId); 251 } 252 } 253 254 enableAbility(abilityName: string): void { 255 config.enableAbility(abilityName, this.capabilities).then(() => { 256 LogUtil.info(`${this.tag} enable accessibilityService success`); 257 }).catch((err: object) => { 258 this.serviceIsOn = false; 259 this.pageChanged = !this.pageChanged; 260 LogUtil.error(`${this.tag} failed to enable accessibilityService, because ${JSON.stringify(err)}`); 261 }) 262 } 263 264 disableAbility(abilityName: string): void { 265 config.disableAbility(abilityName).then(() => { 266 LogUtil.info(`${MODULE_TAG} disable accessibilityService success`); 267 }).catch((err: object) => { 268 LogUtil.error(`${MODULE_TAG} disable accessibilityService failed, error: ${JSON.stringify(err)}`); 269 }); 270 } 271 272 async openServiceButton(abilityName: string): Promise<void> { 273 let enabledServiceList: Array<accessibility.AccessibilityAbilityInfo> = await accessibility.getAccessibilityExtensionList('all', 'enable') 274 if (enabledServiceList && enabledServiceList.length > 0) { 275 for (let enabledService of enabledServiceList) { 276 if (enabledService.id && enabledService.id === abilityName) { 277 this.serviceIsOn = true; 278 return; 279 } 280 } 281 } 282 this.enableAbility(abilityName) 283 } 284 285 cancelButton(): void { 286 this.disableAbility(this.serviceInfo.serviceId); 287 this.serviceIsOn = false; 288 this.pageChanged = !this.pageChanged; 289 LogUtil.info(`${MODULE_TAG} cancel: serviceStatus ${JSON.stringify(this.serviceIsOn)}`); 290 } 291 292 async setExtensionServiceManagementStatusData(): Promise<void> { 293 this.serviceIsOn = false; 294 let enabledServiceList: Array<accessibility.AccessibilityAbilityInfo> = await accessibility.getAccessibilityExtensionList('all', 'enable'); 295 if (!enabledServiceList || enabledServiceList.length < 1) { 296 return; 297 } 298 for (let enabledService of enabledServiceList) { 299 if (enabledService.id && enabledService.id === this.serviceInfo.serviceId) { 300 this.serviceIsOn = true; 301 break; 302 } 303 } 304 } 305 306 async setExtensionServiceManagementApplicationData(): Promise<void> { 307 let installServiceList: Array<accessibility.AccessibilityAbilityInfo> = await accessibility.getAccessibilityExtensionList('all', 'install'); 308 if (!installServiceList || installServiceList.length < 1) { 309 router.back(); 310 return; 311 } 312 let flag: boolean = true; 313 for (let enabledService of installServiceList) { 314 if (enabledService.id && this.serviceInfo.serviceId === enabledService.id) { 315 flag = false; 316 break; 317 } 318 } 319 if (flag) { 320 router.back(); 321 } 322 } 323 324 openExtensionServiceManagementDetailsStatusListener(): void { 325 try { 326 config.on('enabledAccessibilityExtensionListChange', () => { 327 LogUtil.info(`subscribe enabled accessibility extension list change state success`); 328 this.setExtensionServiceManagementStatusData(); 329 }); 330 } catch (exception) { 331 LogUtil.info(`failed to subscribe enabled accessibility extension list change state, because ${JSON.stringify(exception)}`); 332 } 333 } 334 335 closeExtensionServiceManagementDetailsStatusListener(): void { 336 try { 337 config.off('enabledAccessibilityExtensionListChange', () => { 338 LogUtil.info(`Unsubscribe enabled accessibility extension list change state success`); 339 }); 340 } catch (exception) { 341 LogUtil.info(`failed to Unsubscribe enabled accessibility extension list change state, because ${JSON.stringify(exception)}`); 342 } 343 } 344 345 openExtensionServiceManagementDetailsApplicationListener(): void { 346 try { 347 bundleMonitor.on('update', (bundleChangeInfo) => { 348 LogUtil.info(`${this.tag} Update bundleName: ${bundleChangeInfo.bundleName} userId: ${bundleChangeInfo.userId}`); 349 LogUtil.info(`${this.tag} Update localBundleName: ${this.bundleName} `); 350 if (this.bundleName === bundleChangeInfo.bundleName) { 351 router.back(); 352 } 353 354 }); 355 } catch (exception) { 356 LogUtil.info(`${this.tag} failed subscribe bundleMonitor, result: ${JSON.stringify(exception)}`); 357 } 358 try { 359 bundleMonitor.on('remove', (bundleChangeInfo) => { 360 LogUtil.info(`${this.tag} Remove bundleName: ${bundleChangeInfo.bundleName} userId: ${bundleChangeInfo.userId}`); 361 this.setExtensionServiceManagementApplicationData(); 362 }); 363 } catch (exception) { 364 LogUtil.info(`${this.tag} failed subscribe bundleMonitor, result: ${JSON.stringify(exception)}`); 365 } 366 } 367 368 closeExtensionServiceManagementDetailsApplicationListener(): void { 369 try { 370 bundleMonitor.off('update'); 371 } catch (exception) { 372 LogUtil.info(`${this.tag} failed subscribe bundleMonitor, result: ${JSON.stringify(exception)}`); 373 } 374 try { 375 bundleMonitor.off('remove'); 376 } catch (exception) { 377 LogUtil.info(`${this.tag} failed subscribe bundleMonitor, result: ${JSON.stringify(exception)}`); 378 } 379 } 380 381 onPageHide(): void { 382 LogUtil.info(`${this.tag} onPageHide start`); 383 this.closeExtensionServiceManagementDetailsApplicationListener(); 384 } 385} 386 387/** 388 * Show dialog 389 */ 390@CustomDialog 391struct ShowDialog { 392 dialogController?: CustomDialogController; 393 firstClickAction?: () => void; 394 secondClickAction?: () => void; 395 @State @Watch("freezeView") 396 private countDownTime: number = 10000; 397 @State freezingTimeForView: number = -1; 398 @State userApproval: boolean = false; 399 @State title: string = ResourceUtils.getPluralStringValueSync($r('app.plural.user_approval_remain_times'), 10); 400 @State enableState: boolean = false; 401 402 build() { 403 Column() { 404 405 Blank(2); 406 407 Text($r('app.string.accessibility_security_notification')) 408 .width(ConfigData.WH_100_100) 409 .fontSize($r('app.float.font_24')) 410 .fontColor($r('sys.color.ohos_id_color_text_secondary')) 411 .padding({ 412 left: $r('app.float.wh_value_12'), 413 top: $r('app.float.distance_4'), 414 bottom: $r('app.float.distance_24'), 415 }); 416 417 Text($r('app.string.accessibility_notify_content')) 418 .width(ConfigData.WH_100_100) 419 .fontSize($r('app.float.font_18')) 420 .fontColor($r('sys.color.ohos_id_color_text_secondary')) 421 .padding({ 422 left: $r('app.float.wh_value_12'), 423 top: $r('app.float.distance_4'), 424 bottom: $r('app.float.distance_24'), 425 }); 426 427 Text($r('app.string.accessibility_risk_detail')) 428 .width(ConfigData.WH_100_100) 429 .fontSize($r('app.float.font_20')) 430 .fontColor($r('sys.color.ohos_id_color_text_secondary')) 431 .padding({ 432 left: $r('app.float.wh_value_12'), 433 top: $r('app.float.distance_4'), 434 bottom: $r('app.float.distance_24'), 435 }); 436 437 Text($r('app.string.accessibility_risk_content')) 438 .width(ConfigData.WH_100_100) 439 .fontSize($r('app.float.font_18')) 440 .fontColor($r('sys.color.ohos_id_color_text_secondary')) 441 .padding({ 442 left: $r('app.float.wh_value_12'), 443 top: $r('app.float.distance_4'), 444 bottom: $r('app.float.distance_24'), 445 }); 446 447 // checkbox 448 Flex({ justifyContent: FlexAlign.Start, alignItems: ItemAlign.Center }) { 449 Checkbox() 450 .select(false) 451 .selectedColor('#4C89F0') 452 .onChange((value: boolean) => { 453 this.updateUserApproval(value); 454 }); 455 Text($r('app.string.accessibility_user_approval')) 456 .fontSize($r('app.float.font_18')) 457 .baselineOffset(-20) 458 .margin({ bottom: $r('app.float.item_common_vertical_margin') }); 459 } 460 461 Button($r('app.string.accessibility_disable')) 462 .backgroundColor($r('app.color.font_color_007DFF')) 463 .fontSize($r("app.float.font_20")) 464 .fontColor(Color.White) 465 .fontWeight(FontWeight.Medium) 466 .width(ComponentConfig.WH_90_100) 467 .onClick(() => this.cancelEvent()); 468 Divider() 469 .color($r('sys.color.ohos_id_color_list_separator')) 470 .vertical(true) 471 .height($r('app.float.wh_value_10')) 472 .opacity($r('app.float.opacity_0_2')); 473 Button(this.title) 474 .backgroundColor(Color.White) 475 .fontSize($r("app.float.font_20")) 476 .fontColor(Color.Red) 477 .fontWeight(FontWeight.Medium) 478 .width(ComponentConfig.WH_90_100) 479 .enabled(this.enableState) 480 .opacity(this.enableState ? 1 : 0.5) 481 .onClick(() => this.confirmEvent()); 482 } 483 .alignItems(HorizontalAlign.Center) 484 .width(ConfigData.WH_100_100); 485 } 486 487 updateUserApproval(value: boolean): void { 488 this.userApproval = value; 489 this.getEnableButtonState(); 490 } 491 492 getUserApproval(): boolean { 493 return this.userApproval; 494 } 495 496 getEnableButtonState(): void { 497 if (this.freezingTimeForView <= 0 && this.userApproval) { 498 this.enableState = true; 499 } else { 500 this.enableState = false; 501 } 502 } 503 504 updateButtonTitle(): void { 505 this.getEnableButtonState(); 506 if (this.freezingTimeForView > 0) { 507 this.title = ResourceUtils.getPluralStringValueSync($r('app.plural.user_approval_remain_times'), this.freezingTimeForView); 508 } else { 509 this.title = ResourceUtils.getCapitalStringSync($r('app.string.accessibility_enable')); 510 } 511 } 512 513 cancelEvent(): void { 514 LogUtil.info(`${MODULE_TAG} Cancel enable accessibility service`); 515 if (this.secondClickAction) { 516 this.secondClickAction(); 517 } 518 this.freezingTimeForView = -1; 519 if (this.dialogController) { 520 this.dialogController.close(); 521 } 522 } 523 524 confirmEvent(): void { 525 LogUtil.info(`${MODULE_TAG} Cancel enable accessibility service`); 526 if (this.firstClickAction) { 527 this.firstClickAction(); 528 } 529 if (this.dialogController) { 530 this.dialogController.close(); 531 } 532 } 533 534 async freezeView() { 535 if (this.countDownTime <= 0) { 536 return; 537 } 538 this.freezingTimeForView = this.countDownTime / 1000; 539 while (this.freezingTimeForView > 0) { 540 await Sleep(1000); 541 LogUtil.info(`${MODULE_TAG} time left: ${JSON.stringify(this.freezingTimeForView)}`); 542 this.freezingTimeForView -= 1; 543 this.updateButtonTitle(); 544 } 545 } 546 547 aboutToAppear(): void { 548 this.freezeView(); 549 } 550 551 aboutToDisappear(): void { 552 this.freezingTimeForView = -1; 553 } 554} 555 556export const Sleep = (ms: number) => { 557 return new Promise<PromiseConstructor>(resolve => setTimeout(resolve, ms)); 558}