1/* 2 * Copyright (c) 2024 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 */ 15import UIExtensionAbility from '@ohos.app.ability.UIExtensionAbility'; 16import window from '@ohos.window'; 17import { Log, PermissionDialogException, setAvoidArea } from '../common/utils/utils'; 18import UIExtensionContentSession from '@ohos.app.ability.UIExtensionContentSession'; 19import Want from '@ohos.app.ability.Want'; 20import uiExtensionHost from '@ohos.uiExtensionHost'; 21import display from '@ohos.display'; 22import { BusinessError } from '@ohos.base'; 23import Constants from '../common/utils/constant'; 24import AbilityConstant from '@ohos.app.ability.AbilityConstant'; 25import deviceInfo from '@ohos.deviceInfo'; 26 27let terminating: boolean = false; 28let state: boolean = false; 29 30export default class PermissionStateSheetAbility extends UIExtensionAbility { 31 private subWindowForHost: window.Window | null = null; 32 private shouldChangeState: boolean = true; 33 34 onCreate(launchParam: AbilityConstant.LaunchParam): void { 35 Log.info('PermissionStateSheetAbility on create'); 36 if (deviceInfo.deviceType === 'wearable') { 37 this.context.terminateSelf(); 38 Log.info('PermissionStateSheetAbility terminateSelf'); 39 return; 40 } 41 } 42 43 onSessionCreate(want: Want, session: UIExtensionContentSession): void { 44 Log.info('current state:' + state); 45 if (state == true) { 46 this.shouldChangeState = false; 47 Log.error('window state is already set'); 48 PermissionDialogException(Constants.ERR_MODAL_ALREADY_EXIST, session); 49 this.context.terminateSelf(); 50 return; 51 } else { 52 Log.info('set window state to true'); 53 state = true; 54 } 55 56 let param: Record<string, Object> = { 57 'session': session, 'want': want 58 }; 59 60 let storage: LocalStorage = new LocalStorage(param); 61 this.createSubWindow(session, storage).then(() => { 62 // if met error, do not load window, 63 // in case of nested error codes passed to applications 64 if (terminating) { 65 this.context.terminateSelf(); 66 return; 67 } 68 this.loadContentAndSetColor(storage).then((result: boolean) => { 69 if (!result) { 70 return; 71 } 72 this.showWindow(); 73 }) 74 }) 75 } 76 77 onSessionDestroy(): void { 78 Log.info('OnSessionDestroy start'); 79 try { 80 Log.info('try to reshow non-secure windows.'); 81 this.subWindowForHost?.hideNonSystemFloatingWindows(false); 82 } catch (error) { 83 Log.error('failed to reshowing the non-secure windows. Cause: ' + JSON.stringify(error)); 84 } 85 try { 86 this.subWindowForHost?.off('windowEvent'); 87 } catch (exception) { 88 Log.error('fail to call off window event: ' + JSON.stringify(exception)); 89 } 90 this.subWindowForHost?.destroyWindow((err, data) => { 91 if (err && err.code !== 0) { 92 Log.info('destroy window:' + err.message + ' ' + JSON.stringify(data)); 93 } 94 }); 95 Log.info('destroy window end'); 96 } 97 98 onForeground(): void { 99 Log.info('onForegorund'); 100 this.subWindowForHost?.showWindow(); 101 } 102 103 onBackground(): void { 104 Log.info('extension onBackground'); 105 this.subWindowForHost?.hide(); 106 } 107 108 onDestroy(): void | Promise<void> { 109 Log.info('onDestroy'); 110 if (this.shouldChangeState == true) { 111 state = false; 112 Log.info('set window state to false'); 113 } 114 this.subWindowForHost = null; 115 } 116 117 async loadContentAndSetColor(storage: LocalStorage): Promise<boolean> { 118 let promise: Promise<void> | undefined = 119 this.subWindowForHost?.loadContent('PermissionSheet/PermissionStateSheetDialog', storage); 120 if (promise) { 121 await promise.then(() => { 122 Log.info('Succeeded in loading the content'); 123 try { 124 this.subWindowForHost?.setWindowBackgroundColor('#00000000'); 125 } catch (error) { 126 Log.error(`setWindowBackgroundColor failed, error code ${JSON.stringify(error)}`); 127 } 128 }).catch((err: BusinessError) => { 129 Log.error('Failed to load the content. Cause:' + JSON.stringify(err)); 130 return false; 131 }) 132 } 133 return true; 134 } 135 136 async createSubWindow(session: UIExtensionContentSession, storage: LocalStorage): Promise<void> { 137 let extensionWinProxy: uiExtensionHost.UIExtensionHostWindowProxy = session?.getUIExtensionHostWindowProxy(); 138 setAvoidArea(extensionWinProxy); 139 let subWindowOpts: window.SubWindowOptions = { 140 title: '', decorEnabled: false, isModal: true, 141 }; 142 if (!extensionWinProxy) { 143 Log.error('extensionWinProxy is nullptr!'); 144 return; 145 } 146 // try to create subwindow first 147 await extensionWinProxy.createSubWindowWithOptions('permissionSubWindowForHost' + Date(), subWindowOpts) 148 .then((subWindow: window.Window)=>{ 149 this.subWindowForHost = subWindow; 150 }).catch((err: BusinessError) => { 151 Log.error('create sub window error:' + err.message + ' ' + err.code); 152 }); 153 // if create subwindow fails, try create floating window 154 if (!this.subWindowForHost) { 155 Log.error('subwindow is null, failed to create subwindow, tray create float window'); 156 await this.createFloatingWindow(session); 157 if (!this.subWindowForHost) { 158 terminating = true; 159 PermissionDialogException(Constants.ERR_MODAL_ALREADY_EXIST, session); 160 this.context.terminateSelf(); 161 Log.error('floating is null, failed to create float window, terminate self'); 162 return; 163 } 164 } 165 try { 166 let dis = display.getDefaultDisplaySync(); 167 await this.subWindowForHost?.resize(dis.width, dis.height).catch((err: BusinessError) => { 168 Log.error('resize window error: ' + err.code + ' ' + err.message); 169 }); 170 } catch (error) { 171 Log.error('getDefaultDisplaySync error: ' + error.code + ' ' + error.message); 172 } 173 this.subWindowForHost?.on('windowEvent', (eventType: window.WindowEventType) => { 174 Log.info('window event type is: ' + eventType); 175 storage.setOrCreate('permissionWindow', eventType); 176 }) 177 Log.info('create and resize window success'); 178 return; 179 } 180 181 async createFloatingWindow(session: UIExtensionContentSession): Promise<void> { 182 let config: window.Configuration = { 183 name: 'PermissionFloatWindow' + Date(), 184 windowType: window.WindowType.TYPE_FLOAT, 185 ctx: this.context 186 } 187 let promise: Promise<window.Window> | undefined = window.createWindow(config); 188 await promise.then((floatWindow: window.Window) => { 189 this.subWindowForHost = floatWindow; 190 session.loadContent(''); 191 session.setWindowBackgroundColor('#00000000'); 192 }).catch((err: BusinessError) => { 193 Log.error('create floating window error:' + err.message + ' ' + err.code); 194 // if modal already exist , return 195 if (err.code === Constants.CREATE_WINDOW_REPEATED) { 196 Log.error('try to create subwindow repeatedly'); 197 terminating = true; 198 PermissionDialogException(Constants.ERR_MODAL_ALREADY_EXIST, session); 199 this.context.terminateSelf(); 200 } 201 }); 202 if (!this.subWindowForHost) { 203 Log.error('create floating fail'); 204 return; 205 } 206 Log.info('create floating window success'); 207 } 208 209 showWindow(): void { 210 this.subWindowForHost?.showWindow((err) => { 211 if (err && err.code !== 0) { 212 Log.error(`failed to showWindow for subWindow, ${JSON.stringify(err)}`); 213 return; 214 } 215 this.subWindowForHost?.hideNonSystemFloatingWindows(true); 216 Log.info('show window and set non floating success'); 217 }) 218 } 219}