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 @@ + + + icon-hop + + + + + + + + + + + + + + + \ 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 @@ + + + icon-hop + + + + + + + + + + + + + + + \ 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