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 */
15
16import abilityAccessCtrl, { Permissions } from '@ohos.abilityAccessCtrl';
17import { BusinessError } from '@ohos.base';
18import audio from '@ohos.multimedia.audio';
19import camera from '@ohos.multimedia.camera';
20import { CustomContentDialog } from '@ohos.arkui.advanced.Dialog';
21import { Log, PermissionDialogException, PermissionDialogReturn } from '../common/utils/utils';
22import { GroupInfo, WantInfo } from '../common/model/typedef';
23import { GlobalContext } from '../common/utils/globalContext';
24import Constants from '../common/utils/constant';
25import { globalGroup, groups } from '../common/model/permissionGroup';
26import bundleManager from '@ohos.bundle.bundleManager';
27import { getCallerBundleInfo } from './PermissionStateSheetDialog';
28import UIExtensionContentSession from '@ohos.app.ability.UIExtensionContentSession';
29import common from '@ohos.app.ability.common';
30
31
32let globalIsOn: boolean;
33let session: UIExtensionContentSession;
34let storage = LocalStorage.getShared();
35let bundleName = '';
36
37@Entry(storage)
38@Component
39struct GlobalSwitchSheetDialog {
40  @LocalStorageLink('want') want: Want | null = storage.get<Want>('want') || null;
41  @LocalStorageLink('session') session: UIExtensionContentSession =
42    storage.get<UIExtensionContentSession>('session') as UIExtensionContentSession;
43  private context = getContext(this) as common.ServiceExtensionContext;
44  private muteSuppoted = false;
45  private groupName: string = '';
46  dialogController: CustomDialogController | null = new CustomDialogController({
47    builder: CustomContentDialog({
48      contentBuilder: () => {
49        this.buildDialog();
50      },
51      contentAreaPadding: {left: 0, right: 0}
52    }),
53    offset: { dx: 0, dy: `-${GlobalContext.load('avoidAreaHeight') as string}` }, // unit included in globalContext
54    alignment: DialogAlignment.Bottom,
55    customStyle: false,
56    isModal: true,
57    width: '100%',
58    autoCancel: false,
59    cornerRadius: {
60      topLeft: 32,
61      topRight: 32,
62      bottomLeft: 0,
63      bottomRight: 0
64    },
65    cancel: () => {
66      PermissionDialogReturn([Constants.ERR_GLOBAL_SWITCH_EXCEPTION], session);
67      this.context.terminateSelf();
68      this.dialogController?.close();
69    }
70  });
71
72  @Builder
73  buildDialog() {
74    applicationItem({
75      isMuteSupported: this.muteSuppoted,
76      currentGroup: this.groupName
77    })
78  }
79
80  async aboutToAppear() {
81    session = this.session;
82    Log.info('GlobalSwitchSheetDialog aboutToAppear');
83    Log.info('GlobalSwitchSheetDialog getWant' + JSON.stringify(this.want));
84    let callerBundle = getCallerBundleInfo(this.want as Object as WantInfo);
85    Log.info('GlobalSwitchSheetDialog bundleName ' + callerBundle.bundleName);
86    let globalSwitch = callerBundle.globSwitch;
87    bundleName = callerBundle.bundleName;
88
89    if (globalSwitch === Constants.GLOBAL_SWITCH_CAMERA) {
90      this.groupName = 'CAMERA';
91    } else if (globalSwitch === Constants.GLOBAL_SWITCH_MICROPHONE) {
92      this.groupName = 'MICROPHONE';
93    } else {
94      Log.error('global switch not suppoted');
95      PermissionDialogException(Constants.ERR_GLOBAL_SWITCH_NOT_SUPPORTED, session);
96      this.context.terminateSelf();
97      return;
98    }
99
100    if (this.groupName == 'MICROPHONE') {
101      let audioManager = audio.getAudioManager();
102      let audioVolumeManager = audioManager.getVolumeManager();
103      let groupid = audio.DEFAULT_VOLUME_GROUP_ID;
104      let audioVolumeGroupManager = await audioVolumeManager.getVolumeGroupManager(groupid);
105      this.muteSuppoted = true;
106      globalIsOn = !audioVolumeGroupManager.isPersistentMicMute();
107    } else {
108      let cameraManager = camera.getCameraManager(GlobalContext.load('context'));
109      this.muteSuppoted = cameraManager.isCameraMuteSupported();
110      globalIsOn = !cameraManager.isCameraMuted();
111    }
112
113    if (this.muteSuppoted == false) {
114      Log.error('global switch muted');
115      PermissionDialogException(Constants.ERR_GLOBAL_SWITCH_NOT_SUPPORTED, session);
116      this.context.terminateSelf();
117      return;
118    }
119
120    if (globalIsOn) {
121      Log.error('global switch is on');
122      PermissionDialogException(Constants.ERR_GLOBAL_SWITCH_IS_ON, session);
123      this.context.terminateSelf();
124      return;
125    }
126
127    Log.info('isMuted ' + globalIsOn);
128    this.dialogController?.open();
129  }
130
131  onPageShow(): void {
132    Log.info('GlobalSwitchSheetDialog onPageShow' );
133  }
134
135  build() {
136  }
137}
138
139@CustomDialog
140struct applicationItem {
141  private context = getContext(this) as common.ServiceExtensionContext;
142  @State globalIsOn: boolean = false;
143  @State backTitle: string = '';
144  @State groupInfo: GroupInfo = new GroupInfo('', '', '', '', [], '', [], false);
145  @State currentGroup: string = '';
146  @State isMuteSupported: boolean = false;
147  @State appName: ResourceStr = '';
148  private controller: CustomDialogController;
149
150  dialogController: CustomDialogController | null = new CustomDialogController({
151    builder: CustomContentDialog({
152      contentBuilder: () => {
153        this.buildContent();
154      },
155      contentAreaPadding: { left: Constants.PADDING_24, right: Constants.PADDING_24 },
156      buttons: [
157        {
158          value: $r('app.string.cancel'),
159          buttonStyle: ButtonStyleMode.TEXTUAL,
160          action: () => {
161            Log.info('global cancel');
162            if (this.dialogController !== null) {
163              this.dialogController.close();
164            }
165          }
166        },
167        {
168          value: $r('app.string.close'),
169          buttonStyle: ButtonStyleMode.TEXTUAL,
170          action: () => {
171            Log.info('global accept');
172            if (this.currentGroup == 'MICROPHONE') {
173              let audioManager = audio.getAudioManager();
174              let audioVolumeManager = audioManager.getVolumeManager();
175              audioVolumeManager.getVolumeGroupManager(audio.DEFAULT_VOLUME_GROUP_ID).then(audioVolumeGroupManager => {
176                audioVolumeGroupManager.setMicMutePersistent(true, audio.PolicyType.PRIVACY).then(() => {
177                  Log.info('microphone muted');
178                  if (this.dialogController !== null) {
179                    this.dialogController.close();
180                  }
181                })
182              })
183            } else {
184              let cameraManager = camera.getCameraManager(this.context);
185              cameraManager.muteCamera(true);
186              if (this.dialogController !== null) {
187                this.dialogController.close();
188              }
189            }
190          }
191        }
192      ],
193    }),
194    autoCancel: false
195  });
196
197  globalListen() {
198    if (this.currentGroup == 'CAMERA') {
199      let cameraManager = camera.getCameraManager(GlobalContext.load('context'));
200      cameraManager.on('cameraMute', (err, curMuted) => {
201        Log.info('curMuted: ' + JSON.stringify(curMuted) + ' err: ' + JSON.stringify(err));
202        this.globalIsOn = !curMuted;
203      })
204    } else {
205      let audioManager = audio.getAudioManager();
206      let audioVolumeManager = audioManager.getVolumeManager();
207      let groupId = audio.DEFAULT_VOLUME_GROUP_ID;
208      audioVolumeManager.getVolumeGroupManager(groupId).then(audioVolumeGroupManager => {
209        audioVolumeGroupManager.on('micStateChange', micStateChange => {
210          Log.info('micStateChange: ' + JSON.stringify(micStateChange));
211          this.globalIsOn = !micStateChange.mute;
212        })
213      })
214    }
215  }
216
217  @Builder
218  buildContent(): void {
219    Flex({ justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
220      Column() {
221        Text(this.currentGroup == 'CAMERA' ? $r('app.string.close_camera') : $r('app.string.close_microphone'))
222          .fontSize(Constants.TEXT_BIG_FONT_SIZE)
223          .fontColor($r('sys.color.font_primary'))
224          .fontWeight(FontWeight.Medium)
225          .lineHeight(Constants.TEXT_BIG_LINE_HEIGHT)
226          .width(Constants.FULL_WIDTH)
227          .padding({ top: Constants.PADDING_14, bottom: Constants.PADDING_14 })
228        Text(
229          this.currentGroup == 'CAMERA' ?
230          $r('app.string.close_camera_desc') :
231          $r('app.string.close_microphone_desc')
232        )
233          .fontSize(Constants.TEXT_MIDDLE_FONT_SIZE)
234          .fontColor($r('sys.color.font_primary'))
235          .lineHeight(Constants.TEXT_LINE_HEIGHT)
236      }
237      .clip(true)
238    }
239  }
240
241  /**
242   * Grant permissions to the app
243   * @param {Number} accessTokenId
244   * @param {String} permission permission name
245   * @param {Number} index Array index to modify permission status
246   */
247  grantUserGrantedPermission(accessTokenId: number, permission: Permissions, resolve: (value: number) => void) {
248    abilityAccessCtrl.createAtManager().grantUserGrantedPermission(accessTokenId, permission, Constants.PERMISSION_FLAG)
249      .then(() => {
250        resolve(0);
251      }).catch((error: BusinessError) => {
252      resolve(-1);
253      Log.error('grantUserGrantedPermission failed. Cause: ' + JSON.stringify(error));
254    })
255  }
256
257  /**
258   * Deauthorize the app
259   * @param {Number} accessTokenId
260   * @param {String} permission permission name
261   * @param {Number} index Array index to modify permission status
262   */
263  revokeUserGrantedPermission(accessTokenId: number, permission: Permissions, resolve: (value: number) => void) {
264    abilityAccessCtrl.createAtManager().revokeUserGrantedPermission(
265      accessTokenId, permission, Constants.PERMISSION_FLAG
266    ).then(() => {
267      resolve(0);
268    }).catch((error: BusinessError) => {
269      resolve(-1);
270      Log.error('revokeUserGrantedPermission failed. Cause: ' + JSON.stringify(error));
271    })
272  }
273
274  /**
275   * Lifecycle function, executed when the page is initialized
276   */
277  async aboutToAppear() {
278    try {
279      let bundleInfo = await bundleManager.getBundleInfo(
280        bundleName,
281        bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION);
282      let manager = this.context.createBundleContext(bundleName).resourceManager;
283      this.appName = await manager.getStringValue(bundleInfo.appInfo.labelId);
284    } catch (e) {
285      Log.error('get appName failed ' + JSON.stringify(e));
286    }
287    groups.forEach(group => {
288      if (group.name === this.currentGroup) {
289        this.groupInfo = group;
290      }
291    })
292    this.backTitle = this.groupInfo.label;
293    this.globalIsOn = globalIsOn;
294    if (globalGroup.indexOf(this.currentGroup) !== -1) {
295      this.globalListen();
296    }
297  }
298
299  aboutToDisappear() {
300    this.dialogController = null;
301  }
302
303  build() {
304    Column() {
305      Row() {
306        Flex({ alignItems: ItemAlign.Start, justifyContent: FlexAlign.Start }) {
307          Column() {
308            Column() {
309              Text($r(this.backTitle, this.appName))
310                .align(Alignment.Start)
311                .fontColor($r('sys.color.font_primary'))
312                .maxLines(Constants.MAXIMUM_HEADER_LINES)
313                .textOverflow({ overflow: TextOverflow.Ellipsis })
314                .fontSize(Constants.TEXT_BIG_FONT_SIZE)
315                .flexGrow(Constants.FLEX_GROW)
316                .fontWeight(FontWeight.Bold)
317                .padding({left: Constants.PADDING_10, top: Constants.PADDING_20})
318            }
319            .width(Constants.FULL_WIDTH)
320            .alignItems(HorizontalAlign.Start)
321            if (globalGroup.indexOf(this.currentGroup) !== -1 && this.isMuteSupported === true) {
322              Row() {
323                Flex({ justifyContent: FlexAlign.SpaceBetween, alignItems: ItemAlign.Center }) {
324                  Text(this.currentGroup == 'CAMERA' ? $r('app.string.camera') : $r('app.string.microphone'))
325                    .fontSize(Constants.TEXT_MIDDLE_FONT_SIZE).fontColor($r('sys.color.font_primary'))
326                    .fontWeight(FontWeight.Medium)
327                  Row() {
328                    Toggle({ type: ToggleType.Switch, isOn: this.globalIsOn })
329                      .selectedColor($r('sys.color.icon_emphasize'))
330                      .switchPointColor($r('sys.color.comp_background_primary_contrary'))
331                      .onChange((isOn: boolean) => {
332                        if (isOn) {
333                          if (this.currentGroup == 'CAMERA') {
334                            let cameraManager = camera.getCameraManager(GlobalContext.load('context'));
335                            cameraManager.muteCamera(false);
336                            PermissionDialogReturn([Constants.PERMISSION_DIALOG_SUCCESS], session);
337                            this.context.terminateSelf();
338                          } else {
339                            let audioManager = audio.getAudioManager();
340                            let audioVolumeManager = audioManager.getVolumeManager();
341                            let groupId = audio.DEFAULT_VOLUME_GROUP_ID;
342                            audioVolumeManager.getVolumeGroupManager(groupId).then(audioVolumeGroupManager => {
343                              audioVolumeGroupManager.setMicMutePersistent(false, audio.PolicyType.PRIVACY);
344                              PermissionDialogReturn([Constants.PERMISSION_DIALOG_SUCCESS], session);
345                              this.context.terminateSelf();
346                            })
347                          }
348                        }
349                      })
350                    Row().onClick(() => {
351                      if (this.dialogController !== null) {
352                        this.dialogController.open();
353                      }
354                    })
355                      .width(Constants.DEFAULT_SLIDER_WIDTH).height(Constants.DEFAULT_SLIDER_HEIGHT)
356                      .position({ x: this.globalIsOn ? 0 : Constants.OFFSET, y: 0 })
357                  }.clip(true)
358                }.height(Constants.LISTITEM_ROW_HEIGHT)
359                .padding({ left: Constants.DEFAULT_PADDING_START, right: Constants.DEFAULT_PADDING_END })
360              }.padding({ top: Constants.LIST_PADDING_TOP, bottom: Constants.LIST_PADDING_BOTTOM })
361              .backgroundColor($r('sys.color.comp_background_list_card'))
362              .borderRadius($r('sys.float.ohos_id_corner_radius_card'))
363              .margin({ top: Constants.MARGIN_16 })
364            }
365          }.padding({ left: Constants.AUTHORITY_LISTITEM_PADDING_LEFT })
366        }.flexGrow(Constants.FLEX_GROW)
367      }
368    }
369  }
370}