1# Adapting to Camera Changes in Different Folding States (ArkTS)
2
3Before developing a camera application, request permissions by following the instructions provided in [Camera Development Preparations](camera-preparation.md).
4
5Cameras that a foldable device can use vary according to its folding states. To deliver a smooth user experience during transitions between folded and unfolded states, an application can call [CameraManager.on('foldStatusChange')](../../reference/apis-camera-kit/js-apis-camera.md#onfoldstatuschange12) or [display.on('foldStatusChange')](../../reference/apis-arkui/js-apis-display.md#displayonfoldstatuschange10) to listen for folding state changes of the device, call [CameraManager.getSupportedCameras](../../reference/apis-camera-kit/js-apis-camera.md#getsupportedcameras) to obtain the available cameras in the current state, and make adaptations accordingly.
6
7Read [Camera](../../reference/apis-camera-kit/js-apis-camera.md) for the API reference.
8
9## Creating an XComponent
10Use two [XComponents](../../reference/apis-arkui/arkui-ts/ts-basic-components-xcomponent.md) to present the folded and unfolded states, respectively. This prevents the previous camera feed from lingering on the screen if the camera is not properly closed during folding state transition.
11
12   ```ts
13    @Entry
14    @Component
15    struct Index {
16      @State reloadXComponentFlag: boolean = false;
17      @StorageLink('foldStatus') @Watch('reloadXComponent') foldStatus: number = 0;
18      private mXComponentController: XComponentController = new XComponentController();
19      private mXComponentOptions: XComponentOptions = {
20        type: XComponentType.SURFACE,
21        controller: this.mXComponentController
22      }
23
24      reloadXComponent() {
25        this.reloadXComponentFlag = !this.reloadXComponentFlag;
26      }
27
28      async loadXComponent() {
29        // Initialize the XComponent.
30      }
31
32      build() {
33        Stack() {
34          if (this.reloadXComponentFlag) {
35            XComponent(this.mXComponentOptions)
36              .onLoad(async () => {
37                await this.loadXComponent();
38              })
39              .width(px2vp(1080))
40              .height(px2vp(1920))
41          } else {
42            XComponent(this.mXComponentOptions)
43              .onLoad(async () => {
44                await this.loadXComponent();
45              })
46              .width(px2vp(1080))
47              .height(px2vp(1920))
48          }
49        }
50        .size({ width: '100%', height: '100%' })
51        .backgroundColor(Color.Black)
52      }
53    }
54   ```
55## Obtaining the Device Folding State
56
57You can use either of the following solutions.
58
59- Solution 1: Call [CameraManager.on('foldStatusChange')](../../../application-dev/reference/apis-camera-kit/js-apis-camera.md#onfoldstatuschange12) provided by the camera framework to listen for device folding state changes.
60    ```ts
61    import { camera } from '@kit.CameraKit';
62    import { BusinessError } from '@kit.BasicServicesKit';
63
64    let cameraManager = camera.getCameraManager(getContext())
65
66    function registerFoldStatusChanged(err: BusinessError, foldStatusInfo: camera.FoldStatusInfo) {
67      // The foldStatus variable is used to control the display of the XComponent.
68      AppStorage.setOrCreate<number>('foldStatus', foldStatusInfo.foldStatus);
69    }
70
71    cameraManager.on('foldStatusChange', registerFoldStatusChanged);
72    //cameraManager.off('foldStatusChange', registerFoldStatusChanged);
73    ```
74- Solution 2: Call [display.on('foldStatusChange')](../../reference/apis-arkui/js-apis-display.md#displayonfoldstatuschange10) to listen for device folding state changes.
75    ```ts
76    import { display } from '@kit.ArkUI';
77    let preFoldStatus: display.FoldStatus = display.getFoldStatus();
78    display.on('foldStatusChange', (foldStatus: display.FoldStatus) => {
79      // The supported cameras returned by the camera framework are the same when the device is in the FOLD_STATUS_HALF_FOLDED or FOLD_STATUS_EXPANDED state. Therefore, you do not need to reconfigure streams during the transition between these two states.
80      if ((preFoldStatus === display.FoldStatus.FOLD_STATUS_HALF_FOLDED &&
81        foldStatus === display.FoldStatus.FOLD_STATUS_EXPANDED) ||
82        (preFoldStatus === display.FoldStatus.FOLD_STATUS_EXPANDED &&
83          foldStatus === display.FoldStatus.FOLD_STATUS_HALF_FOLDED)) {
84        preFoldStatus = foldStatus;
85        return;
86      }
87      preFoldStatus = foldStatus;
88      // The foldStatus variable is used to control the display of the XComponent.
89      AppStorage.setOrCreate<number>('foldStatus', foldStatus);
90    })
91    ```
92
93## Example
94```ts
95import { camera } from '@kit.CameraKit';
96import { BusinessError } from '@kit.BasicServicesKit';
97import { abilityAccessCtrl } from '@kit.AbilityKit';
98import { display } from '@kit.ArkUI';
99
100let context = getContext(this);
101
102const TAG = 'FoldScreenCameraAdaptationDemo ';
103
104@Entry
105@Component
106struct Index {
107  @State isShow: boolean = false;
108  @State reloadXComponentFlag: boolean = false;
109  @StorageLink('foldStatus') @Watch('reloadXComponent') foldStatus: number = 0;
110  private mXComponentController: XComponentController = new XComponentController();
111  private mXComponentOptions: XComponentOptions = {
112    type: XComponentType.SURFACE,
113    controller: this.mXComponentController
114  }
115  private mSurfaceId: string = '';
116  private mCameraPosition: camera.CameraPosition = camera.CameraPosition.CAMERA_POSITION_BACK;
117  private mCameraManager: camera.CameraManager = camera.getCameraManager(context);
118  // Select the surface width and height as required.
119  private surfaceRect: SurfaceRect = {
120    surfaceWidth: 1080,
121    surfaceHeight: 1920
122  };
123  private curCameraDevice: camera.CameraDevice | undefined = undefined;
124  private mCameraInput: camera.CameraInput | undefined = undefined;
125  private mPreviewOutput: camera.PreviewOutput | undefined = undefined;
126  private mPhotoSession: camera.PhotoSession | undefined = undefined;
127  // One of the recommended preview resolutions
128  private previewProfileObj: camera.Profile = {
129    format: 1003,
130    size: {
131      width: 1920,
132      height: 1080
133    }
134  };
135
136  private preFoldStatus: display.FoldStatus = display.getFoldStatus();
137  // Listen for the foldable screen status. You can use cameraManager.on(type: 'foldStatusChange', callback: AsyncCallback<FoldStatusInfo>): void;
138  // or display.on(type: 'foldStatusChange', callback: Callback<FoldStatus>): void;.
139  private foldStatusCallback =
140    (err: BusinessError, info: camera.FoldStatusInfo): void => this.registerFoldStatusChanged(err, info);
141  private displayFoldStatusCallback =
142    (foldStatus: display.FoldStatus): void => this.onDisplayFoldStatusChange(foldStatus);
143
144
145  registerFoldStatusChanged(err: BusinessError, foldStatusInfo: camera.FoldStatusInfo) {
146    console.info(TAG + 'foldStatusChanged foldStatus: ' + foldStatusInfo.foldStatus);
147    for (let i = 0; i < foldStatusInfo.supportedCameras.length; i++) {
148      console.info(TAG +
149        `foldStatusChanged camera[${i}]: ${foldStatusInfo.supportedCameras[i].cameraId},cameraPosition: ${foldStatusInfo.supportedCameras[i].cameraPosition}`);
150    }
151    AppStorage.setOrCreate<number>('foldStatus', foldStatusInfo.foldStatus);
152  }
153
154  onDisplayFoldStatusChange(foldStatus: display.FoldStatus): void {
155    console.error(TAG + `onDisplayFoldStatusChange foldStatus: ${foldStatus}`);
156    if ((this.preFoldStatus === display.FoldStatus.FOLD_STATUS_HALF_FOLDED &&
157      foldStatus === display.FoldStatus.FOLD_STATUS_EXPANDED) ||
158      (this.preFoldStatus === display.FoldStatus.FOLD_STATUS_EXPANDED &&
159        foldStatus === display.FoldStatus.FOLD_STATUS_HALF_FOLDED)) {
160      this.preFoldStatus = foldStatus;
161      return;
162    }
163    this.preFoldStatus = foldStatus;
164    // Obtain the currently opened camera. If the rear camera is opened, its use is not affected when the device is folded.
165    if (!this.curCameraDevice) {
166      return;
167    }
168    // The foldStatus variable is used to control the display of the XComponent.
169    AppStorage.setOrCreate<number>('foldStatus', foldStatus);
170  }
171
172  requestPermissionsFn(): void {
173    let atManager = abilityAccessCtrl.createAtManager();
174    atManager.requestPermissionsFromUser(context, [
175      'ohos.permission.CAMERA'
176    ]).then((): void => {
177      this.isShow = true;
178    }).catch((error: BusinessError): void => {
179      console.error(TAG + 'ohos.permission.CAMERA no permission.');
180    });
181  }
182
183  aboutToAppear(): void {
184    console.log(TAG + 'aboutToAppear is called');
185    this.requestPermissionsFn();
186    this.onFoldStatusChange();
187  }
188
189  async aboutToDisappear(): Promise<void> {
190    await this.releaseCamera();
191    // Stop the listening.
192    this.offFoldStatusChange();
193  }
194
195  async onPageShow(): Promise<void> {
196    await this.initCamera(this.mSurfaceId, this.mCameraPosition);
197  }
198
199  async releaseCamera(): Promise<void> {
200    // Stop the session.
201    try {
202      await this.mPhotoSession?.stop();
203    } catch (error) {
204      let err = error as BusinessError;
205      console.error(TAG + 'Failed to stop session, errorCode = ' + err.code);
206    }
207
208    // Release the camera input stream.
209    try {
210      await this.mCameraInput?.close();
211    } catch (error) {
212      let err = error as BusinessError;
213      console.error(TAG + 'Failed to close device, errorCode = ' + err.code);
214    }
215
216    // Release the preview output stream.
217    try {
218      await this.mPreviewOutput?.release();
219    } catch (error) {
220      let err = error as BusinessError;
221      console.error(TAG + 'Failed to release previewOutput, errorCode = ' + err.code);
222    }
223
224    this.mPreviewOutput = undefined;
225
226    // Release the session.
227    try {
228      await this.mPhotoSession?.release();
229    } catch (error) {
230      let err = error as BusinessError;
231      console.error(TAG + 'Failed to release photoSession, errorCode = ' + err.code);
232    }
233
234    // Set the session to null.
235    this.mPhotoSession = undefined;
236  }
237
238  onFoldStatusChange(): void {
239    this.mCameraManager.on('foldStatusChange', this.foldStatusCallback);
240    // display.on('foldStatusChange', this.displayFoldStatusCallback);
241  }
242
243  offFoldStatusChange(): void {
244    this.mCameraManager.off('foldStatusChange', this.foldStatusCallback);
245    // display.off('foldStatusChange', this.displayFoldStatusCallback);
246  }
247
248  reloadXComponent(): void {
249    this.reloadXComponentFlag = !this.reloadXComponentFlag;
250  }
251
252  async loadXComponent(): Promise<void> {
253    this.mSurfaceId = this.mXComponentController.getXComponentSurfaceId();
254    this.mXComponentController.setXComponentSurfaceRect(this.surfaceRect);
255    console.info(TAG + `mCameraPosition: ${this.mCameraPosition}`)
256    await this.initCamera(this.mSurfaceId, this.mCameraPosition);
257  }
258
259  getPreviewProfile(cameraOutputCapability: camera.CameraOutputCapability): camera.Profile | undefined {
260    let previewProfiles = cameraOutputCapability.previewProfiles;
261    if (previewProfiles.length < 1) {
262      return undefined;
263    }
264    let index = previewProfiles.findIndex((previewProfile: camera.Profile) => {
265      return previewProfile.size.width === this.previewProfileObj.size.width &&
266        previewProfile.size.height === this.previewProfileObj.size.height &&
267        previewProfile.format === this.previewProfileObj.format;
268    })
269    if (index === -1) {
270      return undefined;
271    }
272    return previewProfiles[index];
273  }
274
275  async initCamera(surfaceId: string, cameraPosition: camera.CameraPosition): Promise<void> {
276    await this.releaseCamera();
277    // Create a CameraManager object.
278    if (!this.mCameraManager) {
279      console.error(TAG + 'camera.getCameraManager error');
280      return;
281    }
282
283    // Obtain the camera list.
284    let cameraArray: Array<camera.CameraDevice> = this.mCameraManager.getSupportedCameras();
285    if (cameraArray.length <= 0) {
286      console.error(TAG + 'cameraManager.getSupportedCameras error');
287      return;
288    }
289
290    for (let index = 0; index < cameraArray.length; index++) {
291      console.info(TAG + 'cameraId : ' + cameraArray[index].cameraId); // Obtain the camera ID.
292      console.info(TAG + 'cameraPosition : ' + cameraArray[index].cameraPosition); // Obtain the camera position.
293      console.info(TAG + 'cameraType : ' + cameraArray[index].cameraType); // Obtain the camera type.
294      console.info(TAG + 'connectionType : ' + cameraArray[index].connectionType); // Obtain the camera connection type.
295    }
296
297    let deviceIndex = cameraArray.findIndex((cameraDevice: camera.CameraDevice) => {
298      return cameraDevice.cameraPosition === cameraPosition;
299    })
300    if (deviceIndex === -1) {
301      deviceIndex = 0;
302      console.error(TAG + 'not found camera');
303    }
304    this.curCameraDevice = cameraArray[deviceIndex];
305
306    // Create a camera input stream.
307    try {
308      this.mCameraInput = this.mCameraManager.createCameraInput(this.curCameraDevice);
309    } catch (error) {
310      let err = error as BusinessError;
311      console.error(TAG + 'Failed to createCameraInput errorCode = ' + err.code);
312    }
313    if (this.mCameraInput === undefined) {
314      return;
315    }
316
317    // Open a camera.
318    try {
319      await this.mCameraInput.open();
320    } catch (error) {
321      let err = error as BusinessError;
322      console.error(TAG + 'Failed to open device, errorCode = ' + err.code);
323    }
324
325    // Obtain the supported scene modes.
326    let sceneModes: Array<camera.SceneMode> = this.mCameraManager.getSupportedSceneModes(this.curCameraDevice);
327    let isSupportPhotoMode: boolean = sceneModes.indexOf(camera.SceneMode.NORMAL_PHOTO) >= 0;
328    if (!isSupportPhotoMode) {
329      console.error(TAG + 'photo mode not support');
330      return;
331    }
332
333    // Obtain the output streams supported by the camera device.
334    let cameraOutputCapability: camera.CameraOutputCapability =
335      this.mCameraManager.getSupportedOutputCapability(this.curCameraDevice, camera.SceneMode.NORMAL_PHOTO);
336    if (!cameraOutputCapability) {
337      console.error(TAG + 'cameraManager.getSupportedOutputCapability error');
338      return;
339    }
340    console.info(TAG + 'outputCapability: ' + JSON.stringify(cameraOutputCapability));
341    let previewProfile = this.getPreviewProfile(cameraOutputCapability);
342    if (previewProfile === undefined) {
343      console.error(TAG + 'The resolution of the current preview stream is not supported.');
344      return;
345    }
346    this.previewProfileObj = previewProfile;
347
348    // Create a preview output stream. For details about the surfaceId parameter, see the XComponent. The preview stream uses the surface provided by the XComponent.
349    try {
350      this.mPreviewOutput = this.mCameraManager.createPreviewOutput(this.previewProfileObj, surfaceId);
351    } catch (error) {
352      let err = error as BusinessError;
353      console.error(TAG + `Failed to create the PreviewOutput instance. error code: ${err.code}`);
354    }
355    if (this.mPreviewOutput === undefined) {
356      return;
357    }
358
359    // Create a session.
360    try {
361      this.mPhotoSession = this.mCameraManager.createSession(camera.SceneMode.NORMAL_PHOTO) as camera.PhotoSession;
362    } catch (error) {
363      let err = error as BusinessError;
364      console.error(TAG + 'Failed to create the session instance. errorCode = ' + err.code);
365    }
366    if (this.mPhotoSession === undefined) {
367      return;
368    }
369
370    // Start configuration for the session.
371    try {
372      this.mPhotoSession.beginConfig();
373    } catch (error) {
374      let err = error as BusinessError;
375      console.error(TAG + 'Failed to beginConfig. errorCode = ' + err.code);
376    }
377
378    // Add the camera input stream to the session.
379    try {
380      this.mPhotoSession.addInput(this.mCameraInput);
381    } catch (error) {
382      let err = error as BusinessError;
383      console.error(TAG + 'Failed to addInput. errorCode = ' + err.code);
384    }
385
386    // Add the preview output stream to the session.
387    try {
388      this.mPhotoSession.addOutput(this.mPreviewOutput);
389    } catch (error) {
390      let err = error as BusinessError;
391      console.error(TAG + 'Failed to addOutput(previewOutput). errorCode = ' + err.code);
392    }
393
394    // Commit the session configuration.
395    try {
396      await this.mPhotoSession.commitConfig();
397    } catch (error) {
398      let err = error as BusinessError;
399      console.error(TAG + 'Failed to commit session configuration, errorCode = ' + err.code);
400    }
401
402    // Start the session.
403    try {
404      await this.mPhotoSession.start()
405    } catch (error) {
406      let err = error as BusinessError;
407      console.error(TAG + 'Failed to start session. errorCode = ' + err.code);
408    }
409  }
410
411  build() {
412    if (this.isShow) {
413      Stack() {
414        if (this.reloadXComponentFlag) {
415          XComponent(this.mXComponentOptions)
416            .onLoad(async () => {
417              await this.loadXComponent();
418            })
419            .width(px2vp(1080))
420            .height(px2vp(1920))
421        } else {
422          XComponent(this.mXComponentOptions)
423            .onLoad(async () => {
424              await this.loadXComponent();
425            })
426            .width(px2vp(1080))
427            .height(px2vp(1920))
428        }
429        Text('Switch camera')
430          .size({ width: 80, height: 48 })
431          .position({ x: 1, y: 1 })
432          .backgroundColor(Color.White)
433          .textAlign(TextAlign.Center)
434          .borderRadius(24)
435          .onClick(async () => {
436            this.mCameraPosition = this.mCameraPosition === camera.CameraPosition.CAMERA_POSITION_BACK ?
437              camera.CameraPosition.CAMERA_POSITION_FRONT : camera.CameraPosition.CAMERA_POSITION_BACK;
438            this.reloadXComponentFlag = !this.reloadXComponentFlag;
439          })
440      }
441      .size({ width: '100%', height: '100%' })
442      .backgroundColor(Color.Black)
443    }
444  }
445}
446```
447