diff --git a/DistributedAppDev/hapAppDcameraSample/.gitignore b/DistributedAppDev/hapAppDcameraSample/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..d2ff20141ceed86d87c0ea5d99481973005bab2b
--- /dev/null
+++ b/DistributedAppDev/hapAppDcameraSample/.gitignore
@@ -0,0 +1,12 @@
+/node_modules
+/oh_modules
+/local.properties
+/.idea
+**/build
+/.hvigor
+.cxx
+/.clangd
+/.clang-format
+/.clang-tidy
+**/.test
+/.appanalyzer
\ No newline at end of file
diff --git a/DistributedAppDev/hapAppDcameraSample/AppScope/app.json5 b/DistributedAppDev/hapAppDcameraSample/AppScope/app.json5
new file mode 100644
index 0000000000000000000000000000000000000000..d1020debfad407be0dbe30e96868ec2efe3a8145
--- /dev/null
+++ b/DistributedAppDev/hapAppDcameraSample/AppScope/app.json5
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2025 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+{
+ "app": {
+ "bundleName": "com.ohos.HapAppDcameraSample",
+ "vendor": "HapAppDcameraSample",
+ "versionCode": 1000000,
+ "versionName": "1.0.0",
+ "icon": "$media:app_icon",
+ "label": "$string:app_name"
+ }
+}
diff --git a/DistributedAppDev/hapAppDcameraSample/AppScope/resources/base/element/string.json b/DistributedAppDev/hapAppDcameraSample/AppScope/resources/base/element/string.json
new file mode 100644
index 0000000000000000000000000000000000000000..6bbca97820f2efb52c28c45e89c98f97909825c7
--- /dev/null
+++ b/DistributedAppDev/hapAppDcameraSample/AppScope/resources/base/element/string.json
@@ -0,0 +1,8 @@
+{
+ "string": [
+ {
+ "name": "app_name",
+ "value": "HapAppDcameraSample"
+ }
+ ]
+}
diff --git a/DistributedAppDev/hapAppDcameraSample/AppScope/resources/base/media/app_icon.png b/DistributedAppDev/hapAppDcameraSample/AppScope/resources/base/media/app_icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..a39445dc87828b76fed6d2ec470dd455c45319e3
Binary files /dev/null and b/DistributedAppDev/hapAppDcameraSample/AppScope/resources/base/media/app_icon.png differ
diff --git a/DistributedAppDev/hapAppDcameraSample/Readme.md b/DistributedAppDev/hapAppDcameraSample/Readme.md
new file mode 100644
index 0000000000000000000000000000000000000000..15323e956102c44fa6a7bd5bc34feb16f12f232e
--- /dev/null
+++ b/DistributedAppDev/hapAppDcameraSample/Readme.md
@@ -0,0 +1,92 @@
+# 分布式相机示例代码工程化
+
+### 介绍
+
+本示例为开发指南中 **分布式相机开发** 章节示例代码的完整工程。该工程展示了如何发现并连接一个远端的分布式摄像头设备,并将远端的预览图像显示在本地屏幕上,最后通过本地操作来控制远端摄像头进行拍照。
+
+本工程配套的开发指南文档,详细描述了相关的开发流程与核心代码,可查阅如下链接:
+[分布式相机开发指南](https://gitcode.com/openharmony/docs/blob/master/zh-cn/application-dev/distributedservice/camera-distributed.md)
+
+### 效果预览
+
+| 主界面 |权限申请|
+|---|---|
+|
|
|
+| 预览 |照片文件|
+|
|
+### 使用说明
+
+1. 启动应用,应用会弹出权限申请对话框,请点击同意。
+2. 确保两台设备已在同一个局域网下成功组网。
+3. 点击界面上的 **"Connect Remote"** 按钮,应用将开始初始化并连接远端的分布式摄像头。
+4. 连接成功后,远端摄像头的预览画面将实时显示在应用屏幕上。
+5. 点击屏幕下方的圆形白色拍照按钮,即可控制远端设备进行拍照。
+6. 拍摄的照片会自动保存到应用的沙箱目录中。
+ > 参考路径: /data/app/el2/100/com.ohos.HapAppDcameraSample/haps/entry/files
+7. 退出应用或应用进入后台时,会自动释放相机资源。
+
+### 工程目录
+```
+entry/src/main/ets/
+|---common
+| |---DeviceDialog.ets // 远端设备列表对话框 (可选,当前未直接使用)
+| └---TitleBarComponent.ets // 顶部标题栏组件
+|---entryability
+| └---EntryAbility.ts // 应用主入口与权限申请
+|---entrybackupability
+| └---EntryBackupAbility.ets // 应用备份恢复能力 (可按需保留)
+|---recorder
+| |---RemoteDeviceModel.ets // 远端设备模型
+| └---VideoRecorder.ets // 核心业务页面 (UI与相机逻辑)
+└---utils
+ |---DateTimeUtils.ets // 日期时间工具类
+ |---Logger.ts // 日志工具类
+ └---SaveCameraAsset.ets // 媒体文件保存工具类
+```
+
+### 具体实现
+
+ * 本示例应用核心展示了分布式相机的连接、预览与拍照功能:
+ * **应用权限申请**:
+ 在 `EntryAbility.ts` 中,通过 `abilityAccessCtrl` 模块为应用申请相机、麦克风、媒体读写和分布式数据同步等必要的权限。
+ * **远端相机初始化**:
+ 在 `VideoRecorder.ets` 的 `initCamera` 方法中,通过 `camera.getCameraManager()` 获取相机管理器,调用 `getSupportedCameras()` 遍历查找 `connectionType` 为 `CAMERA_CONNECTION_REMOTE` 的远端相机设备。
+ * **创建输入与输出**:
+ 在 `VideoRecorder.ets` 中,分别为远端相机创建 `CameraInput` 对象;基于界面上的 XComponent 组件创建 `PreviewOutput` 以显示预览流;并创建 `ImageReceiver` 和 `PhotoOutput` 以接收拍照数据流。
+ * **创建并运行会话**:
+ 在 `VideoRecorder.ets` 的 `createCaptureSession` 方法中,创建 `CaptureSession`,将上述输入和输出添加到会话中,配置完成后调用 `start()` 方法开启预览。
+ * **拍照与资源释放**:
+ 在 `VideoRecorder.ets` 中,调用 `photoOutput.capture()` 执行拍照;在 `imageArrival` 回调中处理图像数据并保存到本地文件;在组件销毁时,调用 `releaseCamera()` 方法,依次停止和释放会话、输入、输出等所有占用的资源。
+ * **错误码**:
+ 开发过程中遇到的错误码请参见 [相机服务子系统错误码]
+
+### 相关权限
+
+ - [ohos.permission.CAMERA]
+ - [ohos.permission.MICROPHONE]
+ - [ohos.permission.DISTRIBUTED_DATASYNC]
+ - [ohos.permission.MEDIA_LOCATION]
+ - [ohos.permission.READ_MEDIA]
+ - [ohos.permission.WRITE_MEDIA]
+
+### 依赖
+
+不涉及
+
+### 约束与限制
+
+1. 本示例仅支持标准系统上运行。
+2. 本示例为Stage模型,支持API版本 **API Version 20 Beta5**,SDK版本号:**OpenHarmony SDK Ohos_sdk_public 6.0.0.47**。
+3. 本示例需要使用DevEco Studio **6.0.0 Beta5** 及以上版本才可编译运行。
+
+### 下载
+
+如需单独下载本工程,执行如下命令:
+
+```
+git init
+git config core.sparsecheckout true
+echo code/DocsSample/hapAppDcameraSample/ > .git/info/sparse-checkout // 请根据实际路径修改
+git remote add origin https://gitee.com/openharmony/applications_app_samples.git
+git pull origin master
+```
\ No newline at end of file
diff --git a/DistributedAppDev/hapAppDcameraSample/build-profile.json5 b/DistributedAppDev/hapAppDcameraSample/build-profile.json5
new file mode 100644
index 0000000000000000000000000000000000000000..9c49a21ef342160d7903a9a9f6c4a3a08f6d9c1c
--- /dev/null
+++ b/DistributedAppDev/hapAppDcameraSample/build-profile.json5
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2025 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+{
+ "app": {
+ "signingConfigs": [
+ ],
+ "products": [
+ {
+ "name": "default",
+ "signingConfig": "default",
+ "compatibleSdkVersion": "6.0.0(20)",
+ "runtimeOS": "HarmonyOS",
+ "buildOption": {
+ "strictMode": {
+ "caseSensitiveCheck": true,
+ "useNormalizedOHMUrl": true
+ }
+ },
+ "targetSdkVersion": "6.0.0(20)"
+ }
+ ],
+ "buildModeSet": [
+ {
+ "name": "debug",
+ },
+ {
+ "name": "release"
+ }
+ ]
+ },
+ "modules": [
+ {
+ "name": "entry",
+ "srcPath": "./entry",
+ "targets": [
+ {
+ "name": "default",
+ "applyToProducts": [
+ "default"
+ ]
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/DistributedAppDev/hapAppDcameraSample/code-linter.json5 b/DistributedAppDev/hapAppDcameraSample/code-linter.json5
new file mode 100644
index 0000000000000000000000000000000000000000..44d50304643a42f437232b908c9b44c51cea8f60
--- /dev/null
+++ b/DistributedAppDev/hapAppDcameraSample/code-linter.json5
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2025 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+{
+ "files": [
+ "**/*.ets"
+ ],
+ "ignore": [
+ "**/src/ohosTest/**/*",
+ "**/src/test/**/*",
+ "**/src/mock/**/*",
+ "**/node_modules/**/*",
+ "**/oh_modules/**/*",
+ "**/build/**/*",
+ "**/.preview/**/*"
+ ],
+ "ruleSet": [
+ "plugin:@performance/recommended",
+ "plugin:@typescript-eslint/recommended"
+ ],
+ "rules": {
+ }
+}
\ No newline at end of file
diff --git a/DistributedAppDev/hapAppDcameraSample/entry/.gitignore b/DistributedAppDev/hapAppDcameraSample/entry/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..e2713a2779c5a3e0eb879efe6115455592caeea5
--- /dev/null
+++ b/DistributedAppDev/hapAppDcameraSample/entry/.gitignore
@@ -0,0 +1,6 @@
+/node_modules
+/oh_modules
+/.preview
+/build
+/.cxx
+/.test
\ No newline at end of file
diff --git a/DistributedAppDev/hapAppDcameraSample/entry/build-profile.json5 b/DistributedAppDev/hapAppDcameraSample/entry/build-profile.json5
new file mode 100644
index 0000000000000000000000000000000000000000..384f7363aed3352dbb05f27e16b93245dc32f882
--- /dev/null
+++ b/DistributedAppDev/hapAppDcameraSample/entry/build-profile.json5
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2025 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+{
+ "apiType": "stageMode",
+ "buildOption": {
+ "externalNativeOptions": {
+ "path": "./src/main/cpp2/CMakeLists.txt",
+ "arguments": "",
+ "cppFlags": ""
+ }
+ },
+ "buildOptionSet": [
+ {
+ "name": "release",
+ "arkOptions": {
+ "obfuscation": {
+ "ruleOptions": {
+ "enable": false,
+ "files": [
+ "./obfuscation-rules.txt"
+ ]
+ }
+ }
+ },
+ "nativeLib": {
+ "debugSymbol": {
+ "strip": true,
+ "exclude": []
+ }
+ }
+ },
+ ],
+ "targets": [
+ {
+ "name": "default"
+ },
+ {
+ "name": "ohosTest",
+ }
+ ]
+}
\ No newline at end of file
diff --git a/DistributedAppDev/hapAppDcameraSample/entry/hvigorfile.ts b/DistributedAppDev/hapAppDcameraSample/entry/hvigorfile.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8774588471ede4c1563f09d9a1d22f764bb1fd9e
--- /dev/null
+++ b/DistributedAppDev/hapAppDcameraSample/entry/hvigorfile.ts
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2025 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { hapTasks } from '@ohos/hvigor-ohos-plugin';
+
+export default {
+ system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
+ plugins:[] /* Custom plugin to extend the functionality of Hvigor. */
+}
diff --git a/DistributedAppDev/hapAppDcameraSample/entry/obfuscation-rules.txt b/DistributedAppDev/hapAppDcameraSample/entry/obfuscation-rules.txt
new file mode 100644
index 0000000000000000000000000000000000000000..22e97bf9c7d622cdb6e78a13214d1361a2dc75c2
--- /dev/null
+++ b/DistributedAppDev/hapAppDcameraSample/entry/obfuscation-rules.txt
@@ -0,0 +1,23 @@
+# Define project specific obfuscation rules here.
+# You can include the obfuscation configuration files in the current module's build-profile.json5.
+#
+# For more details, see
+# https://developer.huawei.com/consumer/cn/doc/OpenHarmony-guides-V5/source-obfuscation-V5
+
+# Obfuscation options:
+# -disable-obfuscation: disable all obfuscations
+# -enable-property-obfuscation: obfuscate the property names
+# -enable-toplevel-obfuscation: obfuscate the names in the global scope
+# -compact: remove unnecessary blank spaces and all line feeds
+# -remove-log: remove all console.* statements
+# -print-namecache: print the name cache that contains the mapping from the old names to new names
+# -apply-namecache: reuse the given cache file
+
+# Keep options:
+# -keep-property-name: specifies property names that you want to keep
+# -keep-global-name: specifies names that you want to keep in the global scope
+
+-enable-property-obfuscation
+-enable-toplevel-obfuscation
+-enable-filename-obfuscation
+-enable-export-obfuscation
\ No newline at end of file
diff --git a/DistributedAppDev/hapAppDcameraSample/entry/oh-package.json5 b/DistributedAppDev/hapAppDcameraSample/entry/oh-package.json5
new file mode 100644
index 0000000000000000000000000000000000000000..ca5dba866ade0eea1b1617cd5caacc7b8b0e8a8b
--- /dev/null
+++ b/DistributedAppDev/hapAppDcameraSample/entry/oh-package.json5
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2025 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+{
+ "name": "entry",
+ "version": "1.0.0",
+ "description": "Please describe the basic information.",
+ "main": "",
+ "author": "",
+ "license": "",
+ "dependencies": {
+ }
+}
\ No newline at end of file
diff --git a/DistributedAppDev/hapAppDcameraSample/entry/src/main/ets/common/DeviceDialog.ets b/DistributedAppDev/hapAppDcameraSample/entry/src/main/ets/common/DeviceDialog.ets
new file mode 100644
index 0000000000000000000000000000000000000000..f289354e46b07f1dda5c1b145e4edda258f7e6f6
--- /dev/null
+++ b/DistributedAppDev/hapAppDcameraSample/entry/src/main/ets/common/DeviceDialog.ets
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2022-2025 Huawei Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import deviceManager from '@ohos.distributedDeviceManager';
+import Logger from '../utils/Logger';
+
+const TAG: string = 'Sample_DeviceDialog';
+
+@CustomDialog
+export struct DeviceDialog {
+ controller?: CustomDialogController;
+ // Kept your original AppStorage and selectedIndex logic to ensure compatibility
+ @StorageLink('deviceList') deviceList: Array = AppStorage.get('deviceList')!;
+ private selectedIndex: number | undefined = 0;
+ private onSelectedIndexChange: (selectedIndex: number | undefined) => void = () => {
+ };
+
+ // --- UI Optimization ---
+ // @Builder for list item UI
+ @Builder
+ buildDeviceItem(item: deviceManager.DeviceBasicInfo, index: number | undefined) {
+ // *** FIX: The 'let isSelected' variable declaration was removed from here ***
+ // The logic is now placed directly inside the component attributes below.
+ Row() {
+ // 1. Selection indicator
+ Circle({ width: 20, height: 20 })
+ .fill(this.selectedIndex === index ? '#007DFF' : '#F1F3F5') // Logic is here
+ .margin({ right: 15 })
+
+ // 2. Device name
+ Text(item.deviceName)
+ .fontSize(16)
+ .layoutWeight(1)
+ .fontColor(this.selectedIndex === index ? '#007DFF' : Color.Black) // Logic is here
+ }
+ .width('100%')
+ .padding({ top: 12, bottom: 12, left: 15, right: 15 })
+ .backgroundColor(this.selectedIndex === index ? '#E8F1FF' : Color.Transparent) // Logic is here
+ .borderRadius(10)
+ .clip(true)
+ // 3. Press effect for better interaction
+ .stateStyles({
+ pressed: {
+ .backgroundColor('#F1F3F5')
+ }
+ })
+ .onClick(() => {
+ // 4. Smooth selection animation
+ animateTo({ duration: 200 }, () => {
+ Logger.info(TAG, `select device: ${item.deviceId}`);
+ if (index !== undefined) {
+ Logger.info(TAG, `onSelectedIndexChange: ${index}`);
+ this.selectedIndex = index;
+ this.onSelectedIndexChange(this.selectedIndex);
+ }
+ });
+ });
+ }
+
+ build() {
+ Column() {
+ // Title
+ Text($r('app.string.choiceDevice'))
+ .fontSize(20)
+ .fontWeight(FontWeight.Bold)
+ .width('100%')
+ .textAlign(TextAlign.Start)
+ .margin({ bottom: 15 })
+
+ // List
+ List({ space: 10 }) {
+ ForEach(this.deviceList, (item: deviceManager.DeviceBasicInfo, index: number | undefined) => {
+ ListItem() {
+ // Call the @Builder function to construct the item
+ this.buildDeviceItem(item, index);
+ }
+ }, (item: deviceManager.DeviceBasicInfo) => item.deviceName);
+ }
+ .width('100%')
+ .layoutWeight(1)
+
+ // Button
+ Button() {
+ Text($r('app.string.cancel'))
+ .fontSize(16)
+ .fontWeight(FontWeight.Medium)
+ }
+ .width('100%')
+ .height(44)
+ .type(ButtonType.Capsule)
+ .backgroundColor('#F1F3F5')
+ .fontColor(Color.Black)
+ .margin({ top: 20 })
+ .onClick(() => {
+ if (this.controller !== undefined) {
+ this.controller.close();
+ }
+ });
+ }
+ // Overall dialog styling
+ .width('85%')
+ .padding(20)
+ .backgroundColor(Color.White)
+ .borderRadius(20)
+ .shadow({ radius: 10, color: '#1F000000', offsetX: 0, offsetY: 5 });
+ }
+}
\ No newline at end of file
diff --git a/DistributedAppDev/hapAppDcameraSample/entry/src/main/ets/common/TitleBarComponent.ets b/DistributedAppDev/hapAppDcameraSample/entry/src/main/ets/common/TitleBarComponent.ets
new file mode 100644
index 0000000000000000000000000000000000000000..f57536470a605a458684444e7f7f168eeeae08f4
--- /dev/null
+++ b/DistributedAppDev/hapAppDcameraSample/entry/src/main/ets/common/TitleBarComponent.ets
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2022-2025 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import deviceManager from '@ohos.distributedDeviceManager';
+import Logger from '../utils/Logger';
+import { DeviceDialog } from '../common/DeviceDialog';
+import { RemoteDeviceModel, BUNDLE_NAME } from '../recorder/RemoteDeviceModel';
+import common from '@ohos.app.ability.common';
+import Want from '@ohos.app.ability.Want';
+import { router } from '@kit.ArkUI';
+
+const TAG: string = 'Sample_TitleBarComponent';
+const DATA_CHANGE: string = 'dataChange';
+const EXIT: string = 'exit';
+const DEVICE_DISCOVERY_RANGE: number = 1000;
+
+@Component
+export struct TitleBarComponent {
+ @Prop isLand: boolean | null = null;
+ @State selectedIndex: number | undefined = 0;
+ @StorageLink('deviceList') deviceList: Array = [];
+ private isShow: boolean = false;
+ private startAbilityCallBack: (key: string) => void = () => {
+ };
+ private dialogController: CustomDialogController | null = null;
+ private remoteDeviceModel: RemoteDeviceModel = new RemoteDeviceModel();
+ onSelectedIndexChange = (index: number | undefined) => {
+ Logger.info(TAG, `selectedIndexChange`);
+ this.selectedIndex = index;
+ this.dialogController?.close();
+ this.selectDevice();
+ };
+
+ aboutToAppear() {
+ AppStorage.setOrCreate('deviceList', this.deviceList);
+ }
+
+ clearSelectState() {
+ this.deviceList = [];
+ if (this.dialogController !== null) {
+ this.dialogController.close();
+ }
+ Logger.info(TAG, `cancelDialog`);
+ if (this.remoteDeviceModel === undefined) {
+ return;
+ }
+ this.remoteDeviceModel.unregisterDeviceListCallback();
+ }
+
+ selectDevice() {
+ if (this.selectedIndex !== undefined && (this.remoteDeviceModel === null || this.remoteDeviceModel.discoverList.length <= 0)) {
+ Logger.info(TAG, `continue unauthed device: ${JSON.stringify(this.deviceList)}`);
+ this.clearSelectState();
+ return;
+ }
+ Logger.info(TAG, `needAuth:`);
+ if (this.selectedIndex !== undefined) {
+ console.log(TAG, ',', this.deviceList.length, ',' , this.deviceList[this.selectedIndex].deviceName);
+
+ this.remoteDeviceModel.authenticateDevice(this.deviceList[this.selectedIndex], () => {
+ Logger.info(TAG, `auth and online finished`);
+ if (this.remoteDeviceModel !== null && this.remoteDeviceModel.deviceList !== null && this.selectedIndex !== undefined) {
+ for (let i = 0; i < this.remoteDeviceModel.deviceList!.length; i++) {
+ if (this.remoteDeviceModel.deviceList![i].deviceName === this.deviceList[this.selectedIndex].deviceName) {
+ }
+ }
+ }
+ });
+ }
+ this.clearSelectState();
+ }
+
+ showDiainfo() {
+ this.deviceList = [];
+ // 注册监听回调,发现设备或查找到已认证设备会弹窗显示
+ this.remoteDeviceModel.registerDeviceListCallback(() => {
+ this.deviceList = [];
+ let context: common.UIAbilityContext | undefined = AppStorage.get('UIAbilityContext');
+ if (context !== undefined) {
+ this.deviceList.push({
+ deviceId: '0',
+ deviceName: context.resourceManager.getStringSync($r('app.string.localhost').id),
+ deviceType: '0',
+ networkId: ''
+ });
+ }
+ let deviceTempList = this.remoteDeviceModel.discoverList.length > 0 ? this.remoteDeviceModel.discoverList : this.remoteDeviceModel.deviceList;
+ if (deviceTempList !== null) {
+ for (let i = 0; i < deviceTempList!.length; i++) {
+ Logger.info(TAG, `found device ${i}/${deviceTempList!.length} deviceId= ${deviceTempList![i].deviceId}, deviceName= ${deviceTempList![i].deviceName}, deviceType= ${deviceTempList![i].deviceType}`);
+ if (deviceTempList !== null) {
+ this.deviceList.push({
+ deviceId: deviceTempList![i].deviceId,
+ deviceName: deviceTempList![i].deviceName,
+ deviceType: deviceTempList![i].deviceType,
+ networkId: deviceTempList![i].networkId,
+ });
+ AppStorage.set('deviceList', this.deviceList);
+ }
+ }
+ }
+ });
+ if (this.dialogController === null) {
+ this.dialogController = new CustomDialogController({
+ builder: DeviceDialog({
+ selectedIndex: this.selectedIndex,
+ onSelectedIndexChange: this.onSelectedIndexChange
+ }),
+ cancel: () => {
+ this.clearSelectState();
+ },
+ autoCancel: true,
+ alignment: this.isLand ? DialogAlignment.Center : DialogAlignment.Bottom,
+ customStyle: false
+ });
+ }
+ if (this.dialogController !== null) {
+ this.dialogController.open();
+ }
+ }
+
+ build() {
+ Row() {
+ Blank().layoutWeight(1);
+ if (!this.isShow) {
+ Image($r('app.media.ic_hop_normal1'))
+ .id('selectDevice')
+ .margin({ right: 32 })
+ .width('9%')
+ .margin({ right: '12%' })
+ .objectFit(ImageFit.Contain)
+ .onClick(() => {
+ this.showDiainfo();
+ });
+ }
+ }
+ .width('100%')
+ .height(this.isLand ? '10%' : '6%')
+ .constraintSize({ minHeight: 50 })
+ .alignItems(VerticalAlign.Center);
+ }
+}
\ No newline at end of file
diff --git a/DistributedAppDev/hapAppDcameraSample/entry/src/main/ets/entryability/EntryAbility.ts b/DistributedAppDev/hapAppDcameraSample/entry/src/main/ets/entryability/EntryAbility.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ce7d1ed9719b976e07defe391868462ac2b36172
--- /dev/null
+++ b/DistributedAppDev/hapAppDcameraSample/entry/src/main/ets/entryability/EntryAbility.ts
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2023-2025 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the 'License');
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an 'AS IS' BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { BusinessError } from '@kit.BasicServicesKit';
+import { abilityAccessCtrl, AbilityConstant, Permissions,
+ UIAbility, Want } from '@kit.AbilityKit';
+import { window } from '@kit.ArkUI';
+
+/**
+ * Lift cycle management of Ability.
+ */
+// [Start request_permissions]
+export default class EntryAbility extends UIAbility {
+ onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
+ console.info('Sample_VideoRecorder', 'Ability onCreate,requestPermissionsFromUser');
+ let permissionNames: Array = ['ohos.permission.MEDIA_LOCATION', 'ohos.permission.READ_MEDIA',
+ 'ohos.permission.WRITE_MEDIA', 'ohos.permission.CAMERA', 'ohos.permission.MICROPHONE', 'ohos.permission.DISTRIBUTED_DATASYNC'];
+ abilityAccessCtrl.createAtManager().requestPermissionsFromUser(this.context, permissionNames).then((data)=> {
+ console.info('testTag', data);
+ })
+ .catch((err : BusinessError) => {
+ console.error('testTag', err.message);
+ });
+ }
+ // [StartExclude request_permissions]
+ onDestroy() {
+ console.info('Sample_VideoRecorder', 'Ability onDestroy');
+ }
+
+ async onWindowStageCreate(windowStage: window.WindowStage) {
+ // Main window is created, set main page for this ability
+ console.info('Sample_VideoRecorder', 'Ability onWindowStageCreate');
+ windowStage.loadContent('recorder/VideoRecorder', (err, data) => {
+ if (err.code) {
+ console.error('Sample_VideoRecorder', 'Failed to load the content. Cause: ' + JSON.stringify(err));
+ return;
+ }
+ console.info('Sample_VideoRecorder', 'Succeeded in loading the content. Data: ' + JSON.stringify(data));
+ windowStage.getMainWindow().then((win: window.Window) => {
+ win.setWindowKeepScreenOn(true);
+ })
+ });
+ }
+
+ onWindowStageDestroy() {
+ // Main window is destroyed, release UI related resources
+ console.info('Sample_VideoRecorder', 'Ability onWindowStageDestroy');
+ }
+
+ onForeground() {
+ // Ability has brought to foreground
+ console.info('Sample_VideoRecorder', 'Ability onForeground');
+ }
+
+ onBackground() {
+ // Ability has back to background
+ console.info('Sample_VideoRecorder', 'Ability onBackground');
+ }
+ // [EndExclude request_permissions]
+}
+// [End request_permissions]
\ No newline at end of file
diff --git a/DistributedAppDev/hapAppDcameraSample/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets b/DistributedAppDev/hapAppDcameraSample/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets
new file mode 100644
index 0000000000000000000000000000000000000000..69a47ce6bf1d1e2d0c5a0f0432a8bb83ef1daae4
--- /dev/null
+++ b/DistributedAppDev/hapAppDcameraSample/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets
@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2025 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { hilog } from '@kit.PerformanceAnalysisKit';
+import { BackupExtensionAbility, BundleVersion } from '@kit.CoreFileKit';
+
+export default class EntryBackupAbility extends BackupExtensionAbility {
+ async onBackup() {
+ hilog.info(0x0000, 'testTag', 'onBackup ok');
+ }
+
+ async onRestore(bundleVersion: BundleVersion) {
+ hilog.info(0x0000, 'testTag', 'onRestore ok %{public}s', JSON.stringify(bundleVersion));
+ }
+}
\ No newline at end of file
diff --git a/DistributedAppDev/hapAppDcameraSample/entry/src/main/ets/recorder/RemoteDeviceModel.ets b/DistributedAppDev/hapAppDcameraSample/entry/src/main/ets/recorder/RemoteDeviceModel.ets
new file mode 100644
index 0000000000000000000000000000000000000000..25646d544d3f5c1ff4aca9a4370e5297f80fd6e2
--- /dev/null
+++ b/DistributedAppDev/hapAppDcameraSample/entry/src/main/ets/recorder/RemoteDeviceModel.ets
@@ -0,0 +1,275 @@
+/*
+ * Copyright (c) 2022-2025 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import deviceManager from '@ohos.distributedDeviceManager';
+import Logger from '../utils/Logger';
+import { Callback } from '@ohos.base';
+
+interface deviceData {
+ device: deviceManager.DeviceBasicInfo;
+}
+
+interface extraInfo {
+ bindType: number;
+ targetPkgName: string;
+ appName: string;
+}
+
+const TAG: string = 'Sample_RemoteDeviceModel';
+let SUBSCRIBE_ID: number = 100;
+
+export const BUNDLE_NAME: string = 'com.samples.avrecorder';
+
+export class RemoteDeviceModel {
+ public deviceList: Array | null = [];
+ public discoverList: Array = [];
+ private callback: () => void = () => {
+ };
+ private authCallback: () => void = () => {
+ };
+ private deviceManager: deviceManager.DeviceManager | undefined = undefined;
+
+ registerDeviceListCallback(callback: Callback) {
+ Logger.info(TAG, `deviceManager type =${typeof (this.deviceManager)} ,${JSON.stringify(this.deviceManager)} ,${JSON.stringify(this.deviceManager) === '{}'}`);
+ if (typeof (this.deviceManager) !== 'undefined') {
+ this.registerDeviceListCallbackImplement(callback);
+ return;
+ }
+ Logger.info(TAG, 'deviceManager.createDeviceManager begin');
+ try {
+ let dmInstance = deviceManager.createDeviceManager(BUNDLE_NAME);
+ this.deviceManager = dmInstance;
+ this.registerDeviceListCallbackImplement(callback);
+ Logger.info(TAG, `createDeviceManager callback returned, value= ${JSON.stringify(this.deviceManager)}`);
+ } catch (error) {
+ Logger.error(TAG, `createDeviceManager throw code:${error.code} message:${error.message}`);
+ }
+ Logger.info(TAG, 'deviceManager.createDeviceManager end');
+ }
+
+ changeStateOnline(device: deviceManager.DeviceBasicInfo) {
+ if (this.deviceList !== null && !this.deviceList.some((existDevice) => existDevice.deviceId === device.deviceId)) {
+ this.deviceList![this.deviceList!.length] = device;
+ }
+ Logger.info(TAG, `online, device list= ${JSON.stringify(this.deviceList)}`);
+ this.callback();
+ if (this.authCallback !== null) {
+ this.authCallback();
+ this.authCallback = () => {
+ };
+ }
+ }
+
+ changeStateOffline(device: deviceManager.DeviceBasicInfo) {
+ if (this.deviceList !== null && this.deviceList!.length > 0) {
+ let list: Array = [];
+ for (let j = 0; j < this.deviceList!.length; j++) {
+ if (this.deviceList![j].deviceId !== device.deviceId) {
+ list[j] = device;
+ }
+ }
+ this.deviceList = list;
+ }
+ Logger.info(TAG, `offline, updated device list=${JSON.stringify(device)}`);
+ this.callback();
+ }
+
+ changeState(device: deviceManager.DeviceBasicInfo, state: number) {
+ if (this.deviceList !== null && this.deviceList!.length <= 0) {
+ this.callback();
+ return;
+ }
+ if (this.deviceList !== null && state === deviceManager.DeviceStateChange.AVAILABLE) {
+ let list: Array = new Array();
+ for (let i = 0; i < this.deviceList!.length; i++) {
+ if (this.deviceList![i].deviceId !== device.deviceId) {
+ list[i] = device;
+ }
+ }
+ this.deviceList = list;
+ Logger.info(TAG, `ready, device list= ${JSON.stringify(device)}`);
+ this.callback();
+ } else {
+ if (this.deviceList !== null) {
+ for (let j = 0; j < this.deviceList!.length; j++) {
+ if (this.deviceList![j].deviceId === device.deviceId) {
+ this.deviceList![j] = device;
+ break;
+ }
+ }
+ Logger.info(TAG, `offline, device list= ${JSON.stringify(this.deviceList)}`);
+ this.callback();
+ }
+ }
+ }
+
+ registerDeviceListCallbackImplement(callback: Callback) {
+ Logger.info(TAG, 'registerDeviceListCallback');
+ this.callback = callback;
+ if (this.deviceManager === undefined) {
+ Logger.error(TAG, 'deviceManager has not initialized');
+ this.callback();
+ return;
+ }
+ Logger.info(TAG, 'getTrustedDeviceListSync begin');
+ try {
+ let list = this.deviceManager !== undefined ? this.deviceManager.getAvailableDeviceListSync() : null;
+ Logger.info(TAG, `getTrustedDeviceListSync end, deviceList= ${JSON.stringify(list)}`);
+ if (typeof (list) !== 'undefined' && JSON.stringify(list) !== '[]') {
+ this.deviceList = list!;
+ }
+ Logger.info(TAG, `getTrustedDeviceListSync end, deviceList=${JSON.stringify(list)}`);
+ } catch (error) {
+ Logger.error(TAG, `getTrustedDeviceListSync throw code:${error.code} message:${error.message}`);
+ }
+ this.callback();
+ Logger.info(TAG, 'callback finished');
+ try {
+ if (this.deviceManager !== undefined) {
+ this.deviceManager.on('deviceStateChange', (data) => {
+ if (data === null) {
+ return;
+ }
+ Logger.info(TAG, `deviceStateChange data= ${JSON.stringify(data)}`);
+ switch (data.action) {
+ case deviceManager.DeviceStateChange.AVAILABLE:
+ this.changeState(data.device, deviceManager.DeviceStateChange.AVAILABLE);
+ break;
+ case deviceManager.DeviceStateChange.UNKNOWN:
+ this.changeStateOnline(data.device);
+ break;
+ case deviceManager.DeviceStateChange.UNAVAILABLE:
+ this.changeStateOffline(data.device);
+ break;
+ default:
+ break;
+ }
+ });
+ }
+ if (this.deviceManager !== undefined) {
+ this.deviceManager.on('discoverSuccess', (data) => {
+ if (data === null) {
+ return;
+ }
+ this.discoverList = [];
+ Logger.info(TAG, `discoverSuccess data=${JSON.stringify(data)}`);
+ this.deviceFound(data.device);
+ });
+ this.deviceManager.on('discoverFailure', (data) => {
+ Logger.info(TAG, `discoverFailure data= ${JSON.stringify(data)}`);
+ });
+ this.deviceManager.on('serviceDie', () => {
+ Logger.error(TAG, 'serviceDie');
+ });
+ }
+ } catch (error) {
+ Logger.error(TAG, `on throw code:${error.code} message:${error.message}`);
+ }
+ this.startDeviceDiscovery();
+ }
+
+ deviceFound(data: deviceManager.DeviceBasicInfo) {
+ for (let i = 0; i < this.discoverList.length; i++) {
+ if (this.discoverList[i].deviceId === data.deviceId) {
+ Logger.info(TAG, 'device founded ignored');
+ return;
+ }
+ }
+ this.discoverList[this.discoverList.length] = data;
+ Logger.info(TAG, `deviceFound self.discoverList= ${this.discoverList}`);
+ this.callback();
+ }
+
+ /**
+ * 通过SUBSCRIBE_ID搜索分布式组网内的设备
+ */
+ startDeviceDiscovery() {
+ let discoverParam: Record = {
+ 'discoverTargetType': 1
+ };
+
+ let filterOptions: Record = {
+ 'availableStatus': 0,
+ };
+
+ Logger.info(TAG, `startDeviceDiscovery${SUBSCRIBE_ID}`);
+ try {
+ if (this.deviceManager !== undefined) {
+ this.deviceManager.startDiscovering(discoverParam, filterOptions);
+ }
+ } catch (error) {
+ Logger.error(TAG, `startDeviceDiscovery throw code:${error.code} message:${error.message}`);
+ }
+ }
+
+ unregisterDeviceListCallback() {
+ Logger.info(TAG, `stopDeviceDiscovery ${SUBSCRIBE_ID}`);
+ if (this.deviceManager === undefined) {
+ return;
+ }
+ if (this.deviceManager !== undefined) {
+ try {
+ Logger.info(TAG, `stopDiscovering`);
+ this.deviceManager.stopDiscovering();
+ } catch (error) {
+ Logger.error(TAG, `stopDeviceDiscovery throw code:${JSON.stringify(error.code)} message:${error.message}`);
+ }
+ try {
+ this.deviceManager.off('deviceStateChange');
+ this.deviceManager.off('discoverSuccess');
+ this.deviceManager.off('discoverFailure');
+ this.deviceManager.off('serviceDie');
+ } catch (error) {
+ Logger.error(TAG, `off throw code:${error.code} message:${error.message}`);
+ }
+ }
+ this.deviceList = [];
+ this.discoverList = [];
+ }
+
+ authenticateDevice(device: deviceManager.DeviceBasicInfo, callBack: Callback) {
+ Logger.info(TAG, `authenticateDevice ${JSON.stringify(device)}`);
+ Logger.info(TAG, `authenticateDevice ${device.networkId}`);
+ for (let i = 0; i < this.discoverList.length; i++) {
+ if (this.discoverList[i].deviceId !== device.deviceId) {
+ continue;
+ }
+ if (this.deviceManager === undefined) {
+ return;
+ }
+ try {
+ if (this.deviceManager !== undefined) {
+ this.deviceManager.bindTarget(device.deviceId, {
+ bindLevel: 3,
+ bindType: 1,
+ targetPkgName: BUNDLE_NAME,
+ appName: 'Distributed distributecalc',
+ }, (err, data) => {
+ if (err) {
+ Logger.error(TAG, `authenticateDevice error: ${JSON.stringify(err)}`);
+ this.authCallback = () => {
+ };
+ return;
+ }
+ Logger.info(TAG, `authenticateDevice succeed: ${JSON.stringify(data)}`);
+ this.authCallback = callBack;
+ });
+ }
+ } catch (error) {
+ Logger.error(TAG, `authenticateDevice throw throw code:${error.code} message:${error.message}`);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/DistributedAppDev/hapAppDcameraSample/entry/src/main/ets/recorder/VideoRecorder.ets b/DistributedAppDev/hapAppDcameraSample/entry/src/main/ets/recorder/VideoRecorder.ets
new file mode 100644
index 0000000000000000000000000000000000000000..126c9eca0d9480787d33b41c2f7452e0a5f35a55
--- /dev/null
+++ b/DistributedAppDev/hapAppDcameraSample/entry/src/main/ets/recorder/VideoRecorder.ets
@@ -0,0 +1,376 @@
+/*
+ * Copyright (C) 2023-2025 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { camera } from '@kit.CameraKit';
+import { BusinessError } from '@kit.BasicServicesKit';
+import Logger from '../utils/Logger';
+import SaveCameraAsset from '../utils/SaveCameraAsset';
+import { RemoteDeviceModel } from './RemoteDeviceModel';
+import { TitleBarComponent } from '../common/TitleBarComponent';
+import { fileIo } from '@kit.CoreFileKit';
+import { image } from '@kit.ImageKit';
+
+const TAG: string = 'Sample_PhotoTaker';
+
+@Entry
+@Component
+struct PhotoTaker {
+ // --- UI State ---
+ @State isLand: boolean = false;
+ @State isShow: boolean = false;
+ // --- Core Camera Objects ---
+ private surfaceId: string = '';
+ private xcomponentController: XComponentController = new XComponentController();
+ private cameraManager?: camera.CameraManager;
+ private cameras?: Array;
+ private cameraInput?: camera.CameraInput;
+ private cameraSession?: camera.Session;
+ private previewOutput?: camera.PreviewOutput;
+ private photoReceiver?: image.ImageReceiver;
+ private photoOutput?: camera.PhotoOutput;
+ // --- Helper Objects ---
+ private mFileAssetId?: number = 0;
+ private cameraIndex: number = 0;
+ private cameraOutputCapability?: camera.CameraOutputCapability;
+ private remoteDeviceModel: RemoteDeviceModel = new RemoteDeviceModel();
+
+ // --- Lifecycle & Callbacks ---
+ aboutToAppear(): void {
+ Logger.info(TAG, 'aboutToAppear');
+ }
+
+ aboutToDisappear(): void {
+ Logger.info(TAG, 'aboutToDisappear called');
+ this.releaseCamera();
+ }
+ async failureCallback(error: BusinessError): Promise {
+ Logger.error(TAG, `Operation failed: ${error.code}, ${error.message}`);
+ }
+
+ async catchCallback(error: BusinessError): Promise {
+ Logger.error(TAG, `Operation caught exception: ${error.message}`);
+ }
+
+ // --- Camera Initialization ---
+ // [Start init_camera]
+ async initCamera(): Promise {
+ Logger.info(TAG, 'initCamera called');
+ if (this.cameraManager) {
+ return;
+ }
+ this.cameraManager = camera.getCameraManager(globalThis.abilityContext);
+ this.cameras = this.cameraManager.getSupportedCameras();
+ if (this.cameras) {
+ this.cameraIndex =
+ this.cameras.findIndex(cam => cam.connectionType === camera.ConnectionType.CAMERA_CONNECTION_REMOTE);
+ if (this.cameraIndex !== -1) {
+ this.cameraOutputCapability = this.cameraManager.getSupportedOutputCapability(this.cameras[this.cameraIndex],
+ camera.SceneMode.NORMAL_PHOTO);
+ }
+ }
+ }
+ // [End init_camera]
+
+ // [Start camera_input]
+ async createCameraInput(): Promise {
+ if (!this.cameras || this.cameraIndex === -1 || !this.cameraManager) {
+ Logger.error(TAG, 'createCameraInput failed: prerequisites not met.');
+ return;
+ }
+ const came = this.cameras[this.cameraIndex];
+ this.cameraInput = this.cameraManager.createCameraInput(came);
+
+ if (!this.cameraInput) {
+ Logger.error(TAG, 'createCameraInput failed: cameraManager.createCameraInput returned undefined.');
+ return;
+ }
+ try {
+ await this.cameraInput.open();
+ Logger.info(TAG, 'CameraInput opened successfully.');
+ } catch (error) {
+ const err = error as BusinessError;
+ Logger.error(TAG, `cameraInput.open() failed with code: ${err.code}, message: ${err.message}`);
+ }
+ }
+ // [End camera_input]
+
+ // [Start create_preview]
+ async createPreviewOutput(): Promise {
+ // Use optional chaining for a clean prerequisite check
+ if (this.cameraOutputCapability?.previewProfiles && this.cameraManager && this.surfaceId) {
+ // Select the first available preview profile
+ const previewProfile = this.cameraOutputCapability.previewProfiles[0];
+
+ this.previewOutput = this.cameraManager.createPreviewOutput(previewProfile, this.surfaceId);
+
+ // Add a clear check to validate the result of the API call
+ if (!this.previewOutput) {
+ Logger.error(TAG, 'createPreviewOutput failed: cameraManager.createPreviewOutput returned undefined.');
+ } else {
+ Logger.info(TAG, 'PreviewOutput created successfully.');
+ }
+ } else {
+ Logger.error(TAG, 'createPreviewOutput failed: prerequisites not met (capability, manager, or surfaceId missing).');
+ }
+ }
+ // [End create_preview]
+
+ // [Start create_photo]
+ async createPhotoOutput() {
+ if (!this.cameraManager || this.photoReceiver) {
+ return;
+ }
+
+ const photoProfile: camera.Profile = {
+ format: camera.CameraFormat.CAMERA_FORMAT_JPEG,
+ size: { 'width': 1280, 'height': 720 }
+ };
+
+ try {
+ this.photoReceiver = image.createImageReceiver(photoProfile.size, image.ImageFormat.JPEG, 8);
+ this.photoReceiver.on('imageArrival', () => {
+ (async () => {
+ if (!this.photoReceiver) {
+ Logger.error(TAG, 'photoReceiver is undefined in imageArrival callback.');
+ return;
+ }
+ let receivedImage: image.Image | undefined = undefined;
+ try {
+ receivedImage = await this.photoReceiver.readNextImage();
+ const component = await receivedImage.getComponent(image.ComponentType.JPEG);
+ await this.getImageFileFd();
+ await fileIo.write(this.mFileAssetId, component.byteBuffer);
+ Logger.info(TAG, 'Photo saved successfully!');
+ } catch (e) {
+ Logger.error(TAG, `Error processing image: ${JSON.stringify(e)}`);
+ } finally {
+ if (receivedImage) {
+ await receivedImage.release();
+ }
+ await this.closeFd();
+ }
+ })();
+ });
+
+ const surfaceId = await this.photoReceiver.getReceivingSurfaceId();
+ this.photoOutput = this.cameraManager.createPhotoOutput(photoProfile, surfaceId);
+ } catch (error) {
+ Logger.error(TAG, `createPhotoOutput failed: ${JSON.stringify(error)}`);
+ }
+ }
+ private mSaveCameraAsset: SaveCameraAsset = new SaveCameraAsset(TAG);
+
+ async getImageFileFd(): Promise {
+ this.mFileAssetId = await this.mSaveCameraAsset.createImageFd();
+ }
+
+ async closeFd(): Promise {
+ if (this.mSaveCameraAsset) {
+ await this.mSaveCameraAsset.closeImageFile();
+ }
+ this.mFileAssetId = undefined;
+ }
+ // [End create_photo]
+
+ // [Start create_session]
+ async createSession(): Promise {
+ Logger.info(TAG, 'createSession called');
+ if (!this.cameraManager) {
+ Logger.error(TAG, 'createSession failed: cameraManager is not initialized.');
+ return;
+ }
+ this.cameraSession = this.cameraManager.createSession(camera.SceneMode.NORMAL_PHOTO);
+ if (!this.cameraSession) {
+ Logger.error(TAG, 'createSession failed: cameraManager.createSession returned undefined.');
+ return;
+ }
+
+ try {
+ Logger.info(TAG, 'cameraSession beginConfig');
+ this.cameraSession.beginConfig();
+
+ if (this.cameraInput) {
+ Logger.info(TAG, 'cameraSession addInput: cameraInput');
+ this.cameraSession.addInput(this.cameraInput);
+ }
+ if (this.previewOutput) {
+ Logger.info(TAG, 'cameraSession addOutput: previewOutput');
+ this.cameraSession.addOutput(this.previewOutput);
+ }
+ if (this.photoOutput) {
+ Logger.info(TAG, 'cameraSession addOutput: photoOutput');
+ this.cameraSession.addOutput(this.photoOutput);
+ }
+
+ Logger.info(TAG, 'cameraSession commitConfig');
+ await this.cameraSession.commitConfig();
+ Logger.info(TAG, 'cameraSession commitConfig successfully.');
+
+ } catch (error) {
+ const err = error as BusinessError;
+ Logger.error(TAG, `cameraSession configuration failed with code: ${err.code}, message: ${err.message}`);
+ this.failureCallback(err);
+ }
+ }
+ // [End create_session]
+
+ // [Start start_session]
+ async startSession(): Promise {
+ Logger.info(TAG, 'startSession called');
+ if (!this.cameraSession) {
+ Logger.error(TAG, 'startSession failed: captureSession does not exist!');
+ return;
+ }
+
+ try {
+ await this.cameraSession.start();
+ Logger.info(TAG, 'cameraSession started successfully.');
+ } catch (error) {
+ const err = error as BusinessError;
+ Logger.error(TAG, `Failed to start session! Code: ${err.code}, Msg: ${err.message}`);
+ }
+ }
+ // [End start_session]
+
+ // [Start release_camera]
+ async releaseCamera(): Promise {
+ Logger.info(TAG, '--- STARTING CAMERA RELEASE SEQUENCE ---');
+
+ try {
+ // Step 1: Stop and release the session
+ if (this.cameraSession) {
+ Logger.info(TAG, 'Stopping capture session...');
+ await this.cameraSession.stop();
+ Logger.info(TAG, 'Releasing capture session...');
+ await this.cameraSession.release();
+ this.cameraSession = undefined;
+ }
+
+ // Step 2: Release the preview output
+ if (this.previewOutput) {
+ Logger.info(TAG, 'Releasing preview output...');
+ await this.previewOutput.release();
+ this.previewOutput = undefined;
+ }
+
+ // Step 3: Release the photo output and receiver
+ if (this.photoOutput) {
+ Logger.info(TAG, 'Releasing photo output...');
+ await this.photoOutput.release();
+ this.photoOutput = undefined;
+ }
+ if (this.photoReceiver) {
+ Logger.info(TAG, 'Releasing photo receiver...');
+ await this.photoReceiver.release();
+ this.photoReceiver = undefined;
+ }
+
+ // Step 4: Close the camera input
+ if (this.cameraInput) {
+ Logger.info(TAG, 'Closing camera input...');
+ await this.cameraInput.close();
+ this.cameraInput = undefined;
+ }
+
+ Logger.info(TAG, 'All camera resources released.');
+
+ } catch (error) {
+ const err = error as BusinessError;
+ Logger.error(TAG, `Error during camera release! Code: ${err.code}, Msg: ${err.message}`);
+ } finally {
+ // Ensure core objects are cleared even if an error occurs
+ this.cameraManager = undefined;
+ this.cameras = undefined;
+ Logger.info(TAG, '--- CAMERA RELEASE SEQUENCE ENDED ---');
+ }
+ }
+ // [End release_camera]
+ async enterInit(): Promise {
+ Logger.info(TAG, 'enterInit called');
+ await this.initCamera();
+ await this.createCameraInput();
+ await this.createPreviewOutput();
+ await this.createPhotoOutput();
+ await this.createSession();
+ await this.startSession();
+ Logger.info(TAG, 'end enterInit');
+ }
+
+ // --- User Actions ---
+ async takePicture(): Promise {
+ if (!this.photoOutput) {
+ Logger.error(TAG, 'photoOutput is not ready!');
+ return;
+ }
+ Logger.info(TAG, 'takePicture called');
+ await this.photoOutput.capture();
+ }
+
+ build() {
+ Column() {
+ Row() {
+ Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween }) {
+ TitleBarComponent({
+ isLand: this.isLand,
+ startAbilityCallBack: () => {
+ },
+ remoteDeviceModel: this.remoteDeviceModel,
+ isShow: this.isShow
+ })
+ Button('Connect Remote')
+ .onClick(() => this.enterInit())
+ }
+ }
+
+ Stack({ alignContent: Alignment.Bottom }) {
+ XComponent({
+ id: 'xcomponent1',
+ type: 'surface',
+ controller: this.xcomponentController
+ })
+ .onLoad(() => {
+ this.xcomponentController.setXComponentSurfaceSize({ surfaceWidth: 1280, surfaceHeight: 720 });
+ this.surfaceId = this.xcomponentController.getXComponentSurfaceId();
+ })
+ .width('100%').height('100%')
+
+ // This is the new button, replacing the old one.
+ Row() {
+ Button() {
+ Text()
+ .width('240px')
+ .height('240px')
+ .borderRadius('120px')
+ // Using a fixed color as requested
+ .backgroundColor('#FFFFFF')
+ }
+ .border({ width: 3, color: 0xFFFFFF, radius: 70 })
+ .width('300px')
+ .height('300px')
+ .margin({ left: 30, right: 30, bottom: 50 }) // Added bottom margin for spacing
+ .backgroundColor('rgba(255,255,255,0.20)')
+ .onClick(() => {
+ // Correctly calls the takePicture function
+ this.takePicture();
+ })
+ .id('takePictureButton')
+ }
+ .width('100%')
+ .justifyContent(FlexAlign.Center)
+
+ }.width('100%').layoutWeight(1)
+ }
+ }
+}
\ No newline at end of file
diff --git a/DistributedAppDev/hapAppDcameraSample/entry/src/main/ets/utils/DateTimeUtils.ets b/DistributedAppDev/hapAppDcameraSample/entry/src/main/ets/utils/DateTimeUtils.ets
new file mode 100644
index 0000000000000000000000000000000000000000..dab924e286245354c0b5e52b0ea3bcc8a02dc732
--- /dev/null
+++ b/DistributedAppDev/hapAppDcameraSample/entry/src/main/ets/utils/DateTimeUtils.ets
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023-2025 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+export default class DateTimeUtil {
+ getTime(): string {
+ const DATETIME = new Date();
+ return this.concatTime(DATETIME.getHours(), DATETIME.getMinutes(), DATETIME.getSeconds());
+ }
+
+ getDate(): string {
+ const DATETIME = new Date();
+ return this.concatDate(DATETIME.getFullYear(), DATETIME.getMonth() + 1, DATETIME.getDate());
+ }
+
+ fill(value: number): string {
+ return (value > 9 ? '' : '0') + value;
+ }
+
+ concatDate(year: number, month: number, date: number): string {
+ return `${year}${month}${date}`;
+ }
+
+ concatTime(hour: number, minute: number, second: number): string {
+ return `${this.fill(hour)}${this.fill(minute)}${this.fill(second)}`;
+ }
+}
+
+export function getShownTimer(ms: number): string {
+ let seconds: number = Math.round(ms / 1000);
+ let sec: number = seconds % 60;
+ let min: number = (seconds - sec) / 60;
+ if (sec < 10) {
+ sec = 0 + sec;
+ }
+ if (min < 10) {
+ min = 0 + min;
+ }
+ return min + ':' + sec;
+}
+
+export function dateTime(t: number): string {
+ let minute: number = Math.floor(t / 60) % 60;
+ let m = minute < 10 ? '0' + minute : minute;
+ let second: number = t % 60;
+ let s = second < 10 ? '0' + second : second;
+ return m + ':' + s;
+}
\ No newline at end of file
diff --git a/DistributedAppDev/hapAppDcameraSample/entry/src/main/ets/utils/Logger.ts b/DistributedAppDev/hapAppDcameraSample/entry/src/main/ets/utils/Logger.ts
new file mode 100644
index 0000000000000000000000000000000000000000..eaa9d847c8004f1acdc623ec200b95ffd5f5c245
--- /dev/null
+++ b/DistributedAppDev/hapAppDcameraSample/entry/src/main/ets/utils/Logger.ts
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2023-2025 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import hilog from '@ohos.hilog';
+
+class Logger {
+ private domain: number;
+ private prefix: string;
+ private format: string = '%{public}s, %{public}s';
+
+ constructor(prefix: string) {
+ this.prefix = prefix;
+ this.domain = 0xFF00;
+ }
+
+ debug(...args: string[]): void {
+ hilog.debug(this.domain, this.prefix, this.format, args);
+ }
+
+ info(...args: string[]): void {
+ hilog.info(this.domain, this.prefix, this.format, args);
+ }
+
+ warn(...args: string[]): void {
+ hilog.warn(this.domain, this.prefix, this.format, args);
+ }
+
+ error(...args: string[]): void {
+ hilog.error(this.domain, this.prefix, this.format, args);
+ }
+}
+
+export default new Logger('AVRecorderSample');
\ No newline at end of file
diff --git a/DistributedAppDev/hapAppDcameraSample/entry/src/main/ets/utils/SaveCameraAsset.ets b/DistributedAppDev/hapAppDcameraSample/entry/src/main/ets/utils/SaveCameraAsset.ets
new file mode 100644
index 0000000000000000000000000000000000000000..51c8b9d057cf569136c4c42a0079928d4059c0c7
--- /dev/null
+++ b/DistributedAppDev/hapAppDcameraSample/entry/src/main/ets/utils/SaveCameraAsset.ets
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2023-2025 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { common } from '@kit.AbilityKit';
+import { fileIo as fs } from '@kit.CoreFileKit';
+import DateTimeUtil from './DateTimeUtils';
+import Logger from './Logger';
+import { photoAccessHelper } from '@kit.MediaLibraryKit';
+import { dataSharePredicates } from '@kit.ArkData';
+
+export default class SaveCameraAsset {
+ private tag: string;
+
+ constructor(tag: string) {
+ this.tag = tag;
+ }
+
+ private context = getContext(this) as common.UIAbilityContext;
+ private lastSaveTime: string = '';
+ private saveIndex: number = 0;
+ public VideoPrepareFile?: fs.File;
+ public AudioPrepareFile?: fs.File;
+ public ImagePrepareFile?: fs.File;
+
+ public async createImageFd() {
+ Logger.info(this.tag, 'get Image File Fd');
+ const mDateTimeUtil = new DateTimeUtil();
+ const displayName = this.checkName(`REC_${mDateTimeUtil.getDate()}_${mDateTimeUtil.getTime()}`) + '.jpg';
+ Logger.info(this.tag, 'get Image display name is: ' + displayName);
+ this.ImagePrepareFile = fs.openSync(this.context.filesDir + '/' + displayName, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
+ let fdNumber: number = this.ImagePrepareFile.fd;
+ Logger.info(this.tag, 'get Image File fd is: ' + JSON.stringify(fdNumber));
+ return fdNumber;
+ }
+
+ public async closeImageFile() {
+ if (this.ImagePrepareFile) {
+ await fs.close(this.ImagePrepareFile);
+ Logger.info(this.tag, 'close Image File end');
+ }
+ }
+
+ public async createVideoFd() {
+ Logger.info(this.tag, 'get Recorder File Fd');
+ const mDateTimeUtil = new DateTimeUtil();
+ const displayName = this.checkName(`REC_${mDateTimeUtil.getDate()}_${mDateTimeUtil.getTime()}`) + '.mp4';
+ Logger.info(this.tag, 'get Recorder display name is: ' + displayName);
+ this.VideoPrepareFile = fs.openSync(this.context.filesDir + '/' + displayName, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
+ let fdNumber: number = this.VideoPrepareFile.fd;
+ Logger.info(this.tag, 'get Recorder File fd is: ' + JSON.stringify(fdNumber));
+ return fdNumber;
+ }
+
+ public async closeVideoFile() {
+ if (this.VideoPrepareFile) {
+ await fs.close(this.VideoPrepareFile);
+ Logger.info(this.tag, 'close Video File end');
+ }
+ }
+
+ public async createAudioFd() {
+ Logger.info(this.tag, 'get Recorder File Fd');
+ const mDateTimeUtil = new DateTimeUtil();
+ const displayName = this.checkName(`REC_${mDateTimeUtil.getDate()}_${mDateTimeUtil.getTime()}`) + '.wav';
+ Logger.info(this.tag, 'get Recorder display name is: ' + displayName);
+ this.AudioPrepareFile = fs.openSync(this.context.filesDir + '/' + displayName, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
+ let fdNumber: number = this.AudioPrepareFile.fd;
+ Logger.info(this.tag, 'get Recorder File fd is: ' + JSON.stringify(fdNumber));
+ return fdNumber;
+ }
+
+ public async closeAudioFile() {
+ if (this.AudioPrepareFile) {
+ await fs.close(this.AudioPrepareFile);
+ Logger.info(this.tag, 'close Audio File end');
+ }
+ }
+
+ private checkName(name: string): string {
+ if (this.lastSaveTime == name) {
+ this.saveIndex += 1;
+ return `${name}_${this.saveIndex}`;
+ }
+ this.lastSaveTime = name;
+ this.saveIndex = 0;
+ Logger.info(this.tag, 'get Recorder File name is: ' + name);
+ return name;
+ }
+}
\ No newline at end of file
diff --git a/DistributedAppDev/hapAppDcameraSample/entry/src/main/module.json5 b/DistributedAppDev/hapAppDcameraSample/entry/src/main/module.json5
new file mode 100644
index 0000000000000000000000000000000000000000..75b6546c11eae6e19dd4296e58478d077683a704
--- /dev/null
+++ b/DistributedAppDev/hapAppDcameraSample/entry/src/main/module.json5
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2025 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+{
+ "module": {
+ "name": "entry",
+ "type": "entry",
+ "description": "$string:module_desc",
+ "mainElement": "EntryAbility",
+ "deviceTypes": [
+ "phone",
+ "tablet",
+ "2in1"
+ ],
+ "deliveryWithInstall": true,
+ "installationFree": false,
+ "pages": "$profile:main_pages",
+ "abilities": [
+ {
+ "name": "EntryAbility",
+ "srcEntry": "./ets/entryability/EntryAbility.ts",
+ "description": "$string:EntryAbility_desc",
+ "icon": "$media:layered_image",
+ "label": "$string:EntryAbility_label",
+ "startWindowIcon": "$media:startIcon",
+ "startWindowBackground": "$color:start_window_background",
+ "exported": true,
+ "skills": [
+ {
+ "entities": [
+ "entity.system.home"
+ ],
+ "actions": [
+ "action.system.home"
+ ]
+ }
+ ]
+ }
+ ],
+ "requestPermissions": [
+ {
+ "name": 'ohos.permission.DISTRIBUTED_DATASYNC',
+ "reason": "$string:reason",
+ "usedScene": {
+ "abilities": [
+ "EntryAbility"
+ ],
+ "when": "inuse"
+ }
+ },
+ {
+ "name": 'ohos.permission.USE_BLUETOOTH',
+ "reason": '$string:reason'
+ },
+ {
+ "name": 'ohos.permission.DISCOVER_BLUETOOTH',
+ "reason": '$string:reason'
+ },
+ {
+ "name": "ohos.permission.MEDIA_LOCATION",
+ "reason": "$string:reason",
+ "usedScene": {
+ "abilities": [
+ "EntryAbility"
+ ],
+ "when": "inuse"
+ }
+ },
+ {
+ "name": "ohos.permission.READ_MEDIA",
+ "reason": "$string:reason",
+ "usedScene": {
+ "abilities": [
+ "EntryAbility"
+ ],
+ "when": "inuse"
+ }
+ },
+ {
+ "name": "ohos.permission.WRITE_MEDIA",
+ "reason": "$string:reason",
+ "usedScene": {
+ "abilities": [
+ "EntryAbility"
+ ],
+ "when": "inuse"
+ }
+ },
+ {
+ "name": "ohos.permission.CAMERA",
+ "reason": "$string:reason",
+ "usedScene": {
+ "abilities": [
+ "EntryAbility"
+ ],
+ "when": "inuse"
+ }
+ },
+ {
+ "name": "ohos.permission.MICROPHONE",
+ "reason": "$string:reason",
+ "usedScene": {
+ "abilities": [
+ "EntryAbility"
+ ],
+ "when": "inuse"
+ }
+ }
+ ],
+ "extensionAbilities": [
+ {
+ "name": "EntryBackupAbility",
+ "srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets",
+ "type": "backup",
+ "exported": false,
+ "metadata": [
+ {
+ "name": "ohos.extension.backup",
+ "resource": "$profile:backup_config"
+ }
+ ],
+ }
+ ],
+ "deliveryWithInstall": true
+ }
+}
\ No newline at end of file
diff --git a/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/base/element/color.json b/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/base/element/color.json
new file mode 100644
index 0000000000000000000000000000000000000000..0d98b5c17780f121191f2768bdf0cd0bc6e9b9e7
--- /dev/null
+++ b/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/base/element/color.json
@@ -0,0 +1,28 @@
+{
+ "color": [
+ {
+ "name": "start_window_background",
+ "value": "#FFFFFF"
+ },
+ {
+ "name": "button_background",
+ "value": "#FFFFFF"
+ },
+ {
+ "name": "homepage_background",
+ "value": "#F1F3F5"
+ },
+ {
+ "name": "title_color",
+ "value": "#000000"
+ },
+ {
+ "name": "button_color",
+ "value": "#007DFF"
+ },
+ {
+ "name": "divider_color",
+ "value": "#182431"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/base/element/string.json b/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/base/element/string.json
new file mode 100644
index 0000000000000000000000000000000000000000..81a79fefaf43bb5907e974fcf86bccfd8c552a3d
--- /dev/null
+++ b/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/base/element/string.json
@@ -0,0 +1,84 @@
+{
+ "string": [
+ {
+ "name": "module_desc",
+ "value": "This module template implements List functions."
+ },
+ {
+ "name": "ability_desc",
+ "value": "This ability loads ListPage"
+ },
+ {
+ "name": "ability_label",
+ "value": "List Ability"
+ },
+ {
+ "name": "page_title",
+ "value": "Title"
+ },
+ {
+ "name": "page_title_video",
+ "value": "VideoRecorder"
+ },
+ {
+ "name": "page_title_audio",
+ "value": "AudioRecorder"
+ },
+ {
+ "name": "button_confirm",
+ "value": "confirm"
+ },
+ {
+ "name": "button_cancel",
+ "value": "cancel"
+ },
+ {
+ "name": "audio_parameter",
+ "value": "Select audio samplerate"
+ },
+ {
+ "name": "audio_samplerate",
+ "value": "audio samplerate: "
+ },
+ {
+ "name": "video_parameter",
+ "value": "Select video resolution"
+ },
+ {
+ "name": "video_resolution",
+ "value": "video resolution: "
+ },
+ {
+ "name": "reason",
+ "value": "Permission"
+ },
+ {
+ "name": "choiceDevice",
+ "value": "Choice Device"
+ },
+ {
+ "name": "cancel",
+ "value": "Cancel"
+ },
+ {
+ "name": "distributed_calculator",
+ "value": "distributed calculator"
+ },
+ {
+ "name": "distributed_permission",
+ "value": "Allow data exchange between different devices"
+ },
+ {
+ "name": "localhost",
+ "value": "localhost"
+ },
+ {
+ "name": "EntryAbility_desc",
+ "value": "EntryAbility"
+ },
+ {
+ "name": "EntryAbility_label",
+ "value": "EntryAbility"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/base/media/background.png b/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/base/media/background.png
new file mode 100644
index 0000000000000000000000000000000000000000..f939c9fa8cc8914832e602198745f592a0dfa34d
Binary files /dev/null and b/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/base/media/background.png differ
diff --git a/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/base/media/checked.png b/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/base/media/checked.png
new file mode 100644
index 0000000000000000000000000000000000000000..a77ded514ac884365ec515801bb34c68e6e5b7f8
Binary files /dev/null and b/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/base/media/checked.png differ
diff --git a/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/base/media/equal.png b/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/base/media/equal.png
new file mode 100644
index 0000000000000000000000000000000000000000..9fc25a3e16bdde93933d4bb2571631e1d382f1fb
Binary files /dev/null and b/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/base/media/equal.png differ
diff --git a/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/base/media/foreground.png b/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/base/media/foreground.png
new file mode 100644
index 0000000000000000000000000000000000000000..4483ddad1f079e1089d685bd204ee1cfe1d01902
Binary files /dev/null and b/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/base/media/foreground.png differ
diff --git a/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/base/media/ic_hop.svg b/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/base/media/ic_hop.svg
new file mode 100644
index 0000000000000000000000000000000000000000..bc30a02a1d3ccffbad1b3670507e8c849a073395
--- /dev/null
+++ b/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/base/media/ic_hop.svg
@@ -0,0 +1,18 @@
+
+
\ No newline at end of file
diff --git a/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/base/media/ic_hop_normal.png b/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/base/media/ic_hop_normal.png
new file mode 100644
index 0000000000000000000000000000000000000000..435ec58bbde7bc034fe558dc890af0b653ae2e2f
Binary files /dev/null and b/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/base/media/ic_hop_normal.png differ
diff --git a/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/base/media/ic_hop_normal1.svg b/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/base/media/ic_hop_normal1.svg
new file mode 100644
index 0000000000000000000000000000000000000000..e104151dd4a2517dc6041d37e91901f13c8edd46
--- /dev/null
+++ b/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/base/media/ic_hop_normal1.svg
@@ -0,0 +1,18 @@
+
+
\ No newline at end of file
diff --git a/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/base/media/icon.png b/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/base/media/icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c
Binary files /dev/null and b/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/base/media/icon.png differ
diff --git a/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/base/media/layered_image.json b/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/base/media/layered_image.json
new file mode 100644
index 0000000000000000000000000000000000000000..fb49920440fb4d246c82f9ada275e26123a2136a
--- /dev/null
+++ b/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/base/media/layered_image.json
@@ -0,0 +1,7 @@
+{
+ "layered-image":
+ {
+ "background" : "$media:background",
+ "foreground" : "$media:foreground"
+ }
+}
\ No newline at end of file
diff --git a/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/base/media/startIcon.png b/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/base/media/startIcon.png
new file mode 100644
index 0000000000000000000000000000000000000000..205ad8b5a8a42e8762fbe4899b8e5e31ce822b8b
Binary files /dev/null and b/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/base/media/startIcon.png differ
diff --git a/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/base/media/uncheck.png b/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/base/media/uncheck.png
new file mode 100644
index 0000000000000000000000000000000000000000..cba71b7ec168e67e261151ee8cb9e950f53e7cbb
Binary files /dev/null and b/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/base/media/uncheck.png differ
diff --git a/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/base/profile/backup_config.json b/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/base/profile/backup_config.json
new file mode 100644
index 0000000000000000000000000000000000000000..78f40ae7c494d71e2482278f359ec790ca73471a
--- /dev/null
+++ b/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/base/profile/backup_config.json
@@ -0,0 +1,3 @@
+{
+ "allowToBackupRestore": true
+}
\ No newline at end of file
diff --git a/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/base/profile/main_pages.json b/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/base/profile/main_pages.json
new file mode 100644
index 0000000000000000000000000000000000000000..0b109f5a6ef798d68040fb3472c56ab12efae6da
--- /dev/null
+++ b/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/base/profile/main_pages.json
@@ -0,0 +1,5 @@
+{
+ "src": [
+ "recorder/VideoRecorder"
+ ]
+}
diff --git a/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/dark/element/color.json b/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/dark/element/color.json
new file mode 100644
index 0000000000000000000000000000000000000000..79b11c2747aec33e710fd3a7b2b3c94dd9965499
--- /dev/null
+++ b/DistributedAppDev/hapAppDcameraSample/entry/src/main/resources/dark/element/color.json
@@ -0,0 +1,8 @@
+{
+ "color": [
+ {
+ "name": "start_window_background",
+ "value": "#000000"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/DistributedAppDev/hapAppDcameraSample/entry/src/ohosTest/ets/TestRunner/OpenHarmonyTestRunner.ts b/DistributedAppDev/hapAppDcameraSample/entry/src/ohosTest/ets/TestRunner/OpenHarmonyTestRunner.ts
new file mode 100644
index 0000000000000000000000000000000000000000..41cbf08890303bfe68c7c65422fd294ff04d2ff9
--- /dev/null
+++ b/DistributedAppDev/hapAppDcameraSample/entry/src/ohosTest/ets/TestRunner/OpenHarmonyTestRunner.ts
@@ -0,0 +1,65 @@
+/*
+* Copyright (c) 2025 Huawei Device Co., Ltd.
+* Licensed under the Apache License, Version 2.0 (the "License")
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+import { hilog } from '@kit.PerformanceAnalysisKit';
+import { TestRunner, abilityDelegatorRegistry } from '@kit.TestKit';
+
+var abilityDelegator = undefined;
+var abilityDelegatorArguments = undefined;
+
+const TAG = "DCameraTestRunner"
+
+async function onAbilityCreateCallback() {
+ hilog.info(0x0000, TAG, '%{public}s', 'onAbilityCreateCallback');
+}
+
+async function addAbilityMonitorCallback(err: any) {
+ hilog.info(0x0000, TAG, 'addAbilityMonitorCallback : %{public}s', JSON.stringify(err) ?? '');
+}
+
+export default class OpenHarmonyTestRunner implements TestRunner {
+ constructor() {
+ }
+
+ onPrepare() {
+ hilog.info(0x0000, TAG, '%{public}s', 'OpenHarmonyTestRunner OnPrepare ');
+ }
+
+ async onRun() {
+ hilog.info(0x0000, TAG, '%{public}s', 'OpenHarmonyTestRunner onRun run');
+ abilityDelegatorArguments = abilityDelegatorRegistry.getArguments();
+ abilityDelegator = abilityDelegatorRegistry.getAbilityDelegator();
+ var testAbilityName = abilityDelegatorArguments.bundleName + '.TestAbility';
+ let lMonitor = {
+ abilityName: testAbilityName,
+ onAbilityCreate: onAbilityCreateCallback,
+ };
+ abilityDelegator.addAbilityMonitor(lMonitor, addAbilityMonitorCallback);
+ var cmd = 'aa start -d 0 -a TestAbility' + ' -b ' + abilityDelegatorArguments.bundleName;
+ var debug = abilityDelegatorArguments.parameters['-D'];
+ if (debug == 'true')
+ {
+ cmd += ' -D'
+ }
+ hilog.info(0x0000, TAG, 'cmd : %{public}s', cmd);
+ abilityDelegator.executeShellCommand(cmd,
+ (err: any, d: any) => {
+ hilog.info(0x0000, TAG, 'executeShellCommand : err : %{public}s', JSON.stringify(err) ?? '');
+ hilog.info(0x0000, TAG, 'executeShellCommand : data : %{public}s', d.stdResult ?? '');
+ hilog.info(0x0000, TAG, 'executeShellCommand : data : %{public}s', d.exitCode ?? '');
+ })
+ hilog.info(0x0000, TAG, '%{public}s', 'OpenHarmonyTestRunner onRun end');
+ }
+}
\ No newline at end of file
diff --git a/DistributedAppDev/hapAppDcameraSample/entry/src/ohosTest/ets/test/List.test.ets b/DistributedAppDev/hapAppDcameraSample/entry/src/ohosTest/ets/test/List.test.ets
new file mode 100644
index 0000000000000000000000000000000000000000..d74718092c99a0ba2b840d1950b2c910e2ea0483
--- /dev/null
+++ b/DistributedAppDev/hapAppDcameraSample/entry/src/ohosTest/ets/test/List.test.ets
@@ -0,0 +1,22 @@
+/*
+* Copyright (c) 2025 Huawei Device Co., Ltd.
+* Licensed under the Apache License, Version 2.0 (the "License")
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+// 1. 导入您要运行的第一个测试用例
+import DCameraSimpleTest from './basicAbility.test';
+
+export default function testsuite() {
+ // 2. 在这里注册您的测试用例
+ DCameraSimpleTest();
+}
\ No newline at end of file
diff --git a/DistributedAppDev/hapAppDcameraSample/entry/src/ohosTest/ets/test/basicAbility.test.ets b/DistributedAppDev/hapAppDcameraSample/entry/src/ohosTest/ets/test/basicAbility.test.ets
new file mode 100644
index 0000000000000000000000000000000000000000..0ed6011a4f4c587dafc8360a8d9a41e083035539
--- /dev/null
+++ b/DistributedAppDev/hapAppDcameraSample/entry/src/ohosTest/ets/test/basicAbility.test.ets
@@ -0,0 +1,122 @@
+/*
+* Copyright (c) 2025 Huawei Device Co., Ltd.
+* Licensed under the Apache License, Version 2.0 (the "License")
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+import { describe, it, expect, TestType, beforeAll, afterAll } from '@ohos/hypium';
+import { abilityDelegatorRegistry, Driver, ON, Component } from '@kit.TestKit';
+import { Want } from '@kit.AbilityKit';
+
+const delegator: abilityDelegatorRegistry.AbilityDelegator = abilityDelegatorRegistry.getAbilityDelegator();
+const bundleName = "com.ohos.HapAppDcameraSample";
+const TAG = "DCameraNewTest";
+const PERMISSION_DIALOG_COUNT = 5;
+
+let countBefore = 0;
+let driver: Driver;
+let context: Context;
+// -----------------
+
+function sleep(time: number) {
+ return new Promise((resolve: (value: void) => void) => setTimeout(resolve, time));
+}
+
+/**
+ * 辅助函数:处理权限弹窗 (保持不变)
+ */
+async function handlePermissionDialogs(driver: Driver, count: number) {
+ console.info(`${TAG}: 正在等待 ${count} 个权限弹窗...`);
+ await sleep(2000); // 等待第一个弹窗
+
+ for (let i = 1; i <= count; i++) {
+ console.info(`${TAG}: 正在处理第 ${i}/${count} 个弹窗...`);
+ let allowButton: Component | null = null;
+ try {
+ console.info(`${TAG}: ...正在查找 "允许" ...`);
+ allowButton = await driver.findComponent(ON.text("允许"));
+ } catch (err) {
+ console.warn(`${TAG}: ...未找到 "允许", 尝试 "Allow"...`);
+ try {
+ allowButton = await driver.findComponent(ON.text("Allow"));
+ } catch (err2) {
+ console.error(`${TAG}: 既未找到 "允许" 也未找到 "Allow". 测试失败。`);
+ throw new Error(`找不到第 ${i} 个权限弹窗的 '允许'/'Allow' 按钮。`);
+ }
+ }
+
+ expect(allowButton).not().assertNull();
+ await allowButton.click();
+ console.info(`${TAG}: ...已点击第 ${i}/${count} 个按钮。`);
+ await sleep(1000); // 等待下一个
+ }
+ console.info(`${TAG}: 所有 ${count} 个弹窗处理完毕。`);
+}
+
+export default function DCameraNewTest() {
+ describe('DCameraNewTest', () => {
+
+ // 1. 使用 beforeAll 在所有测试前启动一次应用
+ beforeAll(async () => {
+ console.info(`${TAG}: beforeAll: 启动应用...`);
+ const want: Want = {
+ bundleName: bundleName,
+ abilityName: 'EntryAbility'
+ };
+ await delegator.startAbility(want);
+ driver = Driver.create();
+ context = delegator.getAppContext();
+ console.info(`${TAG}: beforeAll: 应用已启动,等待弹窗...`);
+ });
+
+ // 2. 使用 afterAll 在所有测试后清理一次
+ afterAll(async () => {
+ console.info(`${TAG}: afterAll: 按返回键退出应用。`);
+ await driver.pressBack();
+ console.info(`${TAG}: afterAll: 清理完毕。`);
+ });
+
+ /**
+ * @tc.number: DCameraNewTest_001
+ * @tc.name: test01_HandlePermissions
+ * @tc.desc: 步骤 1: 处理所有 5 个权限弹窗
+ * @tc.type: TestType.FUNCTION
+ */
+ it('test01_HandlePermissions', TestType.FUNCTION, async (done: () => void) => {
+ console.info(`${TAG}: test01: 开始处理权限...`);
+ await handlePermissionDialogs(driver, PERMISSION_DIALOG_COUNT);
+ console.info(`${TAG}: test01: 权限处理完毕。`);
+ done();
+ });
+
+ /**
+ * @tc.number: DCameraNewTest_002
+ * @tc.name: test02_ClickConnectRemote
+ * @tc.desc: 步骤 2: 查找并点击 "Connect Remote" 按钮
+ * @tc.type: TestType.FUNCTION
+ */
+ it('test02_ClickConnectRemote', TestType.FUNCTION, async (done: () => void) => {
+ const buttonText = "Connect Remote";
+ console.info(`${TAG}: test02: 正在查找 '${buttonText}'...`);
+
+ // 等待权限弹窗完全消失,页面渲染出来
+ await sleep(5000);
+ const connectBtn = await driver.findComponent(ON.text(buttonText));
+ expect(connectBtn).not().assertNull();
+
+ await connectBtn.click();
+ console.info(`${TAG}: test02: 已点击 '${buttonText}' (相机开始异步初始化)。`);
+ await sleep(5000);
+ done();
+ });
+ });
+}
\ No newline at end of file
diff --git a/DistributedAppDev/hapAppDcameraSample/entry/src/ohosTest/ets/testability/TestAbility.ets b/DistributedAppDev/hapAppDcameraSample/entry/src/ohosTest/ets/testability/TestAbility.ets
new file mode 100644
index 0000000000000000000000000000000000000000..6af22264cc3161e5f4607459f367bf8798be5072
--- /dev/null
+++ b/DistributedAppDev/hapAppDcameraSample/entry/src/ohosTest/ets/testability/TestAbility.ets
@@ -0,0 +1,46 @@
+/*
+* Copyright (c) 2025 Huawei Device Co., Ltd.
+* Licensed under the Apache License, Version 2.0 (the "License")
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+import { Hypium } from '@ohos/hypium';
+import testsuite from '../test/List.test';
+import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
+import { window } from '@kit.ArkUI';
+import { hilog } from '@kit.PerformanceAnalysisKit';
+import { abilityDelegatorRegistry } from '@kit.TestKit';
+
+const TAG = 'DCameraTestAbility';
+export default class TestAbility extends UIAbility {
+ onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
+ hilog.info(0x0000, TAG, "TestAbility onCreate");
+ hilog.info(0x0000, TAG, "want param:" + JSON.stringify(want));
+ hilog.info(0x0000, TAG, "launchParam:" + JSON.stringify(launchParam));
+ let abilityDelegator = abilityDelegatorRegistry.getAbilityDelegator();
+ let abilityDelegatorArguments = abilityDelegatorRegistry.getArguments();
+ hilog.info(0x0000, TAG, "start run testcase!!!");
+ Hypium.hypiumTest(abilityDelegator, abilityDelegatorArguments, testsuite);
+ }
+
+ onWindowStageCreate(windowStage: window.WindowStage) {
+ hilog.info(0x0000, TAG, '%{public}s', 'TestAbility onWindowStageCreate');
+ windowStage.loadContent('testability/pages/Index', (err, data) => {
+ if (err.code) {
+ hilog.error(0x0000, TAG, 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
+ return;
+ }
+ hilog.info(0x0000, TAG, 'Succeeded in loading the content. Data: %{public}s',
+ JSON.stringify(data) ?? '');
+ });
+ }
+}
\ No newline at end of file
diff --git a/DistributedAppDev/hapAppDcameraSample/entry/src/ohosTest/ets/testability/pages/Index.ets b/DistributedAppDev/hapAppDcameraSample/entry/src/ohosTest/ets/testability/pages/Index.ets
new file mode 100644
index 0000000000000000000000000000000000000000..81b4d8381bfc674cd198d7c46ffaee1af582fe2c
--- /dev/null
+++ b/DistributedAppDev/hapAppDcameraSample/entry/src/ohosTest/ets/testability/pages/Index.ets
@@ -0,0 +1,32 @@
+/*
+* Copyright (c) 2025 Huawei Device Co., Ltd.
+* Licensed under the Apache License, Version 2.0 (the "License")
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+@Entry
+@Component
+struct Index {
+ @State message: string = 'Test Page'
+
+ build() {
+ Row() {
+ Column() {
+ Text(this.message)
+ .fontSize(50)
+ .fontWeight(FontWeight.Bold)
+ }
+ .width('100%')
+ }
+ .height('100%')
+ }
+}
\ No newline at end of file
diff --git a/DistributedAppDev/hapAppDcameraSample/entry/src/ohosTest/module.json5 b/DistributedAppDev/hapAppDcameraSample/entry/src/ohosTest/module.json5
new file mode 100644
index 0000000000000000000000000000000000000000..847e3b8001f656176480ed4f91f5482fa0495893
--- /dev/null
+++ b/DistributedAppDev/hapAppDcameraSample/entry/src/ohosTest/module.json5
@@ -0,0 +1,51 @@
+/*
+* Copyright (c) 2025 Huawei Device Co., Ltd.
+* Licensed under the Apache License, Version 2.0 (the "License")
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+{
+ "module": {
+ "name": "entry_test",
+ "type": "feature",
+ "description": "$string:module_test_desc",
+ "mainElement": "TestAbility",
+ "deviceTypes": [
+ "default"
+ ],
+ "deliveryWithInstall": true,
+ "installationFree": false,
+ "pages": "$profile:test_pages",
+ "abilities": [
+ {
+ "name": "TestAbility",
+ "srcEntry": "./ets/testability/TestAbility.ets",
+ "description": "$string:TestAbility_desc",
+ "icon": "$media:icon",
+ "label": "$string:TestAbility_label",
+ "visible": true,
+ "startWindowIcon": "$media:icon",
+ "startWindowBackground": "$color:start_window_background",
+ "skills": [
+ {
+ "actions": [
+ "action.system.home"
+ ],
+ "entities": [
+ "entity.system.home"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/DistributedAppDev/hapAppDcameraSample/entry/src/ohosTest/resources/base/element/color.json b/DistributedAppDev/hapAppDcameraSample/entry/src/ohosTest/resources/base/element/color.json
new file mode 100644
index 0000000000000000000000000000000000000000..3c712962da3c2751c2b9ddb53559afcbd2b54a02
--- /dev/null
+++ b/DistributedAppDev/hapAppDcameraSample/entry/src/ohosTest/resources/base/element/color.json
@@ -0,0 +1,8 @@
+{
+ "color": [
+ {
+ "name": "start_window_background",
+ "value": "#FFFFFF"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/DistributedAppDev/hapAppDcameraSample/entry/src/ohosTest/resources/base/element/string.json b/DistributedAppDev/hapAppDcameraSample/entry/src/ohosTest/resources/base/element/string.json
new file mode 100644
index 0000000000000000000000000000000000000000..0c2223e156625b1572455ae495be6d909e44e6f0
--- /dev/null
+++ b/DistributedAppDev/hapAppDcameraSample/entry/src/ohosTest/resources/base/element/string.json
@@ -0,0 +1,16 @@
+{
+ "string": [
+ {
+ "name": "module_test_desc",
+ "value": "test ability description"
+ },
+ {
+ "name": "TestAbility_desc",
+ "value": "the test ability"
+ },
+ {
+ "name": "TestAbility_label",
+ "value": "DCamera Test"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/DistributedAppDev/hapAppDcameraSample/entry/src/ohosTest/resources/base/media/icon.png b/DistributedAppDev/hapAppDcameraSample/entry/src/ohosTest/resources/base/media/icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c
Binary files /dev/null and b/DistributedAppDev/hapAppDcameraSample/entry/src/ohosTest/resources/base/media/icon.png differ
diff --git a/DistributedAppDev/hapAppDcameraSample/entry/src/ohosTest/resources/base/profile/test_pages.json b/DistributedAppDev/hapAppDcameraSample/entry/src/ohosTest/resources/base/profile/test_pages.json
new file mode 100644
index 0000000000000000000000000000000000000000..089b475972bda81924d24fc2a07393d9eeeb69b7
--- /dev/null
+++ b/DistributedAppDev/hapAppDcameraSample/entry/src/ohosTest/resources/base/profile/test_pages.json
@@ -0,0 +1,5 @@
+{
+ "src": [
+ "testability/pages/Index"
+ ]
+}
\ No newline at end of file
diff --git a/DistributedAppDev/hapAppDcameraSample/hvigor/hvigor-config.json5 b/DistributedAppDev/hapAppDcameraSample/hvigor/hvigor-config.json5
new file mode 100644
index 0000000000000000000000000000000000000000..5282eefe0593f56f8ddabfbf05783dc836c3c8b4
--- /dev/null
+++ b/DistributedAppDev/hapAppDcameraSample/hvigor/hvigor-config.json5
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2025 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+{
+ "modelVersion": "5.0.1",
+ "dependencies": {
+ },
+ "execution": {
+ // "analyze": "normal", /* Define the build analyze mode. Value: [ "normal" | "advanced" | false ]. Default: "normal" */
+ // "daemon": true, /* Enable daemon compilation. Value: [ true | false ]. Default: true */
+ // "incremental": true, /* Enable incremental compilation. Value: [ true | false ]. Default: true */
+ // "parallel": true, /* Enable parallel compilation. Value: [ true | false ]. Default: true */
+ // "typeCheck": false, /* Enable typeCheck. Value: [ true | false ]. Default: false */
+ },
+ "logging": {
+ // "level": "info" /* Define the log level. Value: [ "debug" | "info" | "warn" | "error" ]. Default: "info" */
+ },
+ "debugging": {
+ // "stacktrace": false /* Disable stacktrace compilation. Value: [ true | false ]. Default: false */
+ },
+ "nodeOptions": {
+ // "maxOldSpaceSize": 8192 /* Enable nodeOptions maxOldSpaceSize compilation. Unit M. Used for the daemon process. Default: 8192*/
+ // "exposeGC": true /* Enable to trigger garbage collection explicitly. Default: true*/
+ }
+}
diff --git a/DistributedAppDev/hapAppDcameraSample/hvigorfile.ts b/DistributedAppDev/hapAppDcameraSample/hvigorfile.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6b365cacd0191d3b1178eb6b9807b1ae0add6271
--- /dev/null
+++ b/DistributedAppDev/hapAppDcameraSample/hvigorfile.ts
@@ -0,0 +1,20 @@
+/*
+ * Copyright (c) 2025 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { appTasks } from '@ohos/hvigor-ohos-plugin';
+
+export default {
+ system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
+ plugins:[] /* Custom plugin to extend the functionality of Hvigor. */
+}
diff --git a/DistributedAppDev/hapAppDcameraSample/oh-package.json5 b/DistributedAppDev/hapAppDcameraSample/oh-package.json5
new file mode 100644
index 0000000000000000000000000000000000000000..9b142d655b140175b33fe2a0c804ca498119f87f
--- /dev/null
+++ b/DistributedAppDev/hapAppDcameraSample/oh-package.json5
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2025 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+{
+ "modelVersion": "5.0.1",
+ "description": "Please describe the basic information.",
+ "dependencies": {
+ },
+ "devDependencies": {
+ "@ohos/hypium": "1.0.19",
+ "@ohos/hamock": "1.0.0"
+ }
+}
diff --git a/DistributedAppDev/hapAppDcameraSample/ohosTest.md b/DistributedAppDev/hapAppDcameraSample/ohosTest.md
new file mode 100644
index 0000000000000000000000000000000000000000..7e1dac5a0955327a71b7e0fa8b921bff278e277b
--- /dev/null
+++ b/DistributedAppDev/hapAppDcameraSample/ohosTest.md
@@ -0,0 +1,18 @@
+# 分布式相机 测试用例归档
+
+## 用例表
+
+| 测试功能 | 预置条件 | 输入 | 预期输出 | 是否自动 | 测试结果 |
+| :--- | :--- | :--- | :--- | :--- | :--- |
+| **基础功能** | | | | | |
+| 拉起应用 | 设备正常运行 | 点击应用图标 | 成功拉起应用主界面,显示连接按钮和预览区域 | 是 | Pass |
+| 申请权限 | 成功拉起应用 | | 弹出系统权限申请对话框,包含相机、麦克风等权限 | 是 | Pass |
+| 申请权限 | 在权限申请对话框中 | 点击“同意” | 权限申请成功,应用功能可正常使用 | 是 | Pass |
+| **核心功能** | | | | | |
+| 连接远端相机 | 1. 两台设备在同一局域网下
2. 两台设备已成功组网 | 点击 "Connect Remote" 按钮 | 成功连接远端相机,预览区域开始实时显示远端画面 | 否 | Pass |
+| 预览功能 | 已成功连接远端相机 | | 预览画面清晰、流畅,与远端设备摄像头所见场景一致 | 是 | Pass |
+| 拍照功能 | 已成功连接远端相机并显示预览 | 点击界面下方的圆形拍照按钮 | 成功触发远端相机拍照,日志显示照片已保存成功 | 是 | Pass |
+| **稳定性与资源管理** | | | | | |
+| 断开连接/资源释放 | 应用正在运行并已连接相机 | 退出应用(如上滑关闭)或使应用进入后台 | 应用成功释放所有相机资源,日志显示"releaseCamera"序列完成 | 是 | Pass |
+| 重复连接 | 已成功连接远端相机 | 再次点击 "Connect Remote" 按钮 | 应用无异常或崩溃,能够处理重复初始化的请求 | 是 | Pass |
+| 无远端设备 | 1. 两台设备未组网
2. 局域网内无分布式设备 | 点击 "Connect Remote" 按钮 | 应用无异常崩溃,日志提示未找到远端设备 | 否 | Pass |
\ No newline at end of file
diff --git a/DistributedAppDev/hapAppDcameraSample/screenshot/image.png b/DistributedAppDev/hapAppDcameraSample/screenshot/image.png
new file mode 100644
index 0000000000000000000000000000000000000000..c82a8c583baf7d48f7c13e99125173ea376f7981
Binary files /dev/null and b/DistributedAppDev/hapAppDcameraSample/screenshot/image.png differ
diff --git a/DistributedAppDev/hapAppDcameraSample/screenshot/image2.png b/DistributedAppDev/hapAppDcameraSample/screenshot/image2.png
new file mode 100644
index 0000000000000000000000000000000000000000..c4ec4a9c1665b7ef9ccd6dafbb8c034c39cee18d
Binary files /dev/null and b/DistributedAppDev/hapAppDcameraSample/screenshot/image2.png differ
diff --git a/DistributedAppDev/hapAppDcameraSample/screenshot/image3.png b/DistributedAppDev/hapAppDcameraSample/screenshot/image3.png
new file mode 100644
index 0000000000000000000000000000000000000000..7b24ac9c0251f33add5c4f8d3413e5c575d8088f
Binary files /dev/null and b/DistributedAppDev/hapAppDcameraSample/screenshot/image3.png differ
diff --git a/DistributedAppDev/hapAppDcameraSample/screenshot/image4.png b/DistributedAppDev/hapAppDcameraSample/screenshot/image4.png
new file mode 100644
index 0000000000000000000000000000000000000000..e3d015a563de17e6422a95723eeaf7c2c41711e4
Binary files /dev/null and b/DistributedAppDev/hapAppDcameraSample/screenshot/image4.png differ
diff --git a/DistributedAppDev/hapAppDcameraSample/screenshot/image5.png b/DistributedAppDev/hapAppDcameraSample/screenshot/image5.png
new file mode 100644
index 0000000000000000000000000000000000000000..c673d080e2310b76bb98ae9648c12033b789a0b2
Binary files /dev/null and b/DistributedAppDev/hapAppDcameraSample/screenshot/image5.png differ