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 */ 15import hilog from '@ohos.hilog'; 16import abilityManager from '@ohos.app.ability.abilityManager'; 17import common from '@ohos.app.ability.common'; 18import { ErrorCallback, Callback, BusinessError } from '@ohos.base'; 19import AtomicServiceOptions from '@ohos.app.ability.AtomicServiceOptions'; 20import commonEventManager from '@ohos.commonEventManager'; 21import Base from '@ohos.base'; 22 23const EMBEDDED_HALF_MODE = 2; 24const atomicServiceDataTag: string = 'ohos.atomicService.window'; 25const ERR_CODE_ABNORMAL: number = 100014; 26const ERR_CODE_CAPABILITY_NOT_SUPPORT: number = 801; 27const LOG_TAG: string = 'HalfScreenLaunchComponent'; 28@Component 29export struct HalfScreenLaunchComponent { 30 @BuilderParam content: Callback<void> = this.doNothingBuilder; 31 context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext; 32 appId: string = ''; 33 options?: AtomicServiceOptions; 34 @State private isShow: boolean = false; 35 private subscriber: commonEventManager.CommonEventSubscriber | null = null; 36 onError?: ErrorCallback; 37 onTerminated?: Callback<TerminationInfo>; 38 onReceive?: Callback<Record<string, Object>>; 39 40 aboutToAppear(): void { 41 let subscribeInfo: commonEventManager.CommonEventSubscribeInfo = { 42 events: [commonEventManager.Support.COMMON_EVENT_DISTRIBUTED_ACCOUNT_LOGOUT], 43 }; 44 45 commonEventManager.createSubscriber(subscribeInfo, 46 (err: Base.BusinessError, data: commonEventManager.CommonEventSubscriber) => { 47 if (err) { 48 hilog.error(0x3900, LOG_TAG, 49 'Failed to create subscriber, err: %{public}s.', JSON.stringify(err)); 50 return; 51 } 52 53 if (data === null || data === undefined) { 54 hilog.error(0x3900, LOG_TAG, 'Failed to create subscriber, data is null.'); 55 return; 56 } 57 58 this.subscriber = data; 59 commonEventManager.subscribe(this.subscriber, 60 (err: Base.BusinessError, data: commonEventManager.CommonEventData) => { 61 if (err) { 62 hilog.error(0x3900, LOG_TAG, 63 'Failed to subscribe common event, err: %{public}s.', JSON.stringify(err)); 64 return; 65 } 66 this.isShow = false; 67 }) 68 }) 69 } 70 71 aboutToDisappear(): void { 72 if (this.subscriber !== null) { 73 commonEventManager.unsubscribe(this.subscriber, (err) => { 74 if (err) { 75 hilog.error(0x3900, LOG_TAG, 76 'UnsubscribeCallBack, err: %{public}s.', JSON.stringify(err)); 77 } else { 78 this.subscriber = null; 79 } 80 }) 81 } 82 } 83 84 @Builder 85 doNothingBuilder() { 86 }; 87 88 resetOptions(): void { 89 if (this.options?.parameters) { 90 this.options.parameters['ohos.extra.param.key.showMode'] = EMBEDDED_HALF_MODE; 91 this.options.parameters['ability.want.params.IsNotifyOccupiedAreaChange'] = true; 92 this.options.parameters['ability.want.params.IsModal'] = true; 93 hilog.info(0x3900, LOG_TAG, 'replaced options is %{public}s !', JSON.stringify(this.options)); 94 } else { 95 this.options = { 96 parameters: { 97 'ohos.extra.param.key.showMode': EMBEDDED_HALF_MODE, 98 'ability.want.params.IsNotifyOccupiedAreaChange': true, 99 'ability.want.params.IsModal': true 100 } 101 } 102 } 103 } 104 105 async checkAbility(): Promise<void> { 106 if (this.isShow) { 107 hilog.error(0x3900, LOG_TAG, 'EmbeddedAbility already shows'); 108 this.isShow = false; 109 return; 110 } 111 this.resetOptions(); 112 try { 113 abilityManager.queryAtomicServiceStartupRule(this.context, this.appId) 114 .then((data: abilityManager.AtomicServiceStartupRule) => { 115 if (data.isOpenAllowed) { 116 if (data.isEmbeddedAllowed) { 117 this.isShow = true; 118 hilog.info(0x3900, LOG_TAG, 'EmbeddedOpen is Allowed!'); 119 } else { 120 this.popUp(); 121 } 122 } else { 123 hilog.info(0x3900, LOG_TAG, 'is not allowed open!'); 124 } 125 }).catch((err: BusinessError) => { 126 hilog.error(0x3900, LOG_TAG, 'queryAtomicServiceStartupRule called error!%{public}s', err.message); 127 if (ERR_CODE_CAPABILITY_NOT_SUPPORT === err.code) { 128 this.popUp(); 129 } 130 }); 131 } catch (err: BusinessError) { 132 hilog.error(0x3900, LOG_TAG, 'AtomicServiceStartupRule failed: %{public}s', err.message); 133 this.popUp(); 134 } 135 } 136 137 async popUp(): Promise<void> { 138 this.isShow = false; 139 try { 140 const ability = await this.context.openAtomicService(this.appId, this.options); 141 hilog.info(0x3900, LOG_TAG, '%{public}s open service success!', ability.want); 142 } catch (e) { 143 hilog.error(0x3900, LOG_TAG, '%{public}s open service error!', e.message); 144 } 145 } 146 147 private handleOnReceiveEvent(data: Object): void { 148 if (data === undefined || data === null) { 149 return; 150 } 151 if (this.onReceive !== undefined) { 152 const sourceKeys = Object.keys(data); 153 let atomicServiceData: Record<string, Object> = {}; 154 for (let i = 0; i < sourceKeys.length; i++) { 155 if (sourceKeys[i].includes(atomicServiceDataTag)) { 156 atomicServiceData[sourceKeys[i]] = data[sourceKeys[i]]; 157 } 158 } 159 this.onReceive(atomicServiceData); 160 } 161 } 162 163 build() { 164 Row() { 165 this.content(); 166 }.justifyContent(FlexAlign.Center) 167 .onClick( 168 () => { 169 hilog.info(0x3900, LOG_TAG, 'on start atomicservice'); 170 this.checkAbility(); 171 } 172 ).bindContentCover($$this.isShow, this.uiExtensionBuilder(), 173 { 174 modalTransition: ModalTransition.NONE, 175 enableSafeArea: true 176 }); 177 } 178 179 @Builder 180 uiExtensionBuilder() { 181 Column() { 182 UIExtensionComponent({ 183 bundleName: `com.atomicservice.${this.appId}`, 184 flags: this.options?.flags, 185 parameters: this.options?.parameters 186 }, 187 { 188 windowModeFollowStrategy: WindowModeFollowStrategy.FOLLOW_HOST_WINDOW_MODE 189 }) 190 .height('100%') 191 .width('100%') 192 .backgroundColor(Color.Transparent) 193 .onError( 194 err => { 195 if (this.onError) { 196 this.onError(err); 197 } 198 this.isShow = false; 199 hilog.error(0x3900, LOG_TAG, 'call up UIExtension error!%{public}s', err.message); 200 if (err.code !== ERR_CODE_ABNORMAL) { 201 this.getUIContext().showAlertDialog({ 202 message: err.message 203 }); 204 } 205 } 206 ) 207 .onTerminated(info => { 208 this.isShow = false; 209 if (this.onTerminated) { 210 this.onTerminated(info); 211 } 212 }) 213 .onReceive(data => { 214 this.handleOnReceiveEvent(data); 215 }) 216 } 217 .height(LayoutPolicy.matchParent) 218 .width(LayoutPolicy.matchParent) 219 .ignoreLayoutSafeArea([LayoutSafeAreaType.SYSTEM], [LayoutSafeAreaEdge.TOP, LayoutSafeAreaEdge.BOTTOM]) 220 } 221}