• 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 */
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}