diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..2a49801dacea074448893f4f691e2bd731d6c9bd
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,9 @@
+/node_modules
+/local.properties
+**/local.properties
+/.idea
+**/build
+/.hvigor
+.preview
+*/.preview
+**/package-lock.json
diff --git a/AppScope/app.json5 b/AppScope/app.json5
new file mode 100644
index 0000000000000000000000000000000000000000..6f46fbf6ea9c9235806158be6b74b625c26c9931
--- /dev/null
+++ b/AppScope/app.json5
@@ -0,0 +1,11 @@
+{
+ "app": {
+ "bundleName": "com.hihope.recorder",
+ "vendor": "example",
+ "versionCode": 1000000,
+ "versionName": "1.0.0",
+ "icon": "$media:app_icon",
+ "label": "$string:app_name",
+ "distributedNotificationEnabled": true
+ }
+}
diff --git a/AppScope/resources/base/element/string.json b/AppScope/resources/base/element/string.json
new file mode 100644
index 0000000000000000000000000000000000000000..ec39b5f097e9a18e1989672fa4be0b82d769211f
--- /dev/null
+++ b/AppScope/resources/base/element/string.json
@@ -0,0 +1,8 @@
+{
+ "string": [
+ {
+ "name": "app_name",
+ "value": "录音机"
+ }
+ ]
+}
diff --git a/AppScope/resources/base/media/app_icon.png b/AppScope/resources/base/media/app_icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..b186c704fd1345ae527fabc8a479fc92470a8c6b
Binary files /dev/null and b/AppScope/resources/base/media/app_icon.png differ
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..261eeb9e9f8b2b4b0d119366dda99c6fd7d35c64
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/OAT.xml b/OAT.xml
new file mode 100644
index 0000000000000000000000000000000000000000..67891321dae1e5bf29c3fc1b5549736ec4110030
--- /dev/null
+++ b/OAT.xml
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.en.md b/README.en.md
deleted file mode 100644
index 8ff59b37b86f4648985688d454507cb1daababf7..0000000000000000000000000000000000000000
--- a/README.en.md
+++ /dev/null
@@ -1,36 +0,0 @@
-# applications_recorder
-
-#### Description
-{**When you're done, you can delete the content in this README and update the file with details for others getting started with your repository**}
-
-#### Software Architecture
-Software architecture description
-
-#### Installation
-
-1. xxxx
-2. xxxx
-3. xxxx
-
-#### Instructions
-
-1. xxxx
-2. xxxx
-3. xxxx
-
-#### Contribution
-
-1. Fork the repository
-2. Create Feat_xxx branch
-3. Commit your code
-4. Create Pull Request
-
-
-#### Gitee Feature
-
-1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md
-2. Gitee blog [blog.gitee.com](https://blog.gitee.com)
-3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore)
-4. The most valuable open source project [GVP](https://gitee.com/gvp)
-5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help)
-6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
diff --git a/README.md b/README.md
deleted file mode 100644
index 15a45c8d98ce999cea8c9a77397bd2ff4267238d..0000000000000000000000000000000000000000
--- a/README.md
+++ /dev/null
@@ -1,39 +0,0 @@
-# applications_recorder
-
-#### 介绍
-{**以下是 Gitee 平台说明,您可以替换此简介**
-Gitee 是 OSCHINA 推出的基于 Git 的代码托管平台(同时支持 SVN)。专为开发者提供稳定、高效、安全的云端软件开发协作平台
-无论是个人、团队、或是企业,都能够用 Gitee 实现代码托管、项目管理、协作开发。企业项目请看 [https://gitee.com/enterprises](https://gitee.com/enterprises)}
-
-#### 软件架构
-软件架构说明
-
-
-#### 安装教程
-
-1. xxxx
-2. xxxx
-3. xxxx
-
-#### 使用说明
-
-1. xxxx
-2. xxxx
-3. xxxx
-
-#### 参与贡献
-
-1. Fork 本仓库
-2. 新建 Feat_xxx 分支
-3. 提交代码
-4. 新建 Pull Request
-
-
-#### 特技
-
-1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
-2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com)
-3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目
-4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目
-5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
-6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
diff --git a/README_zh.md b/README_zh.md
new file mode 100644
index 0000000000000000000000000000000000000000..0070749e8efb95552829f3c42b2befc4dfe6d6e5
--- /dev/null
+++ b/README_zh.md
@@ -0,0 +1,39 @@
+# 录音机应用
+
+## 简介
+录音机应用可以通过麦克风进行音频录制并完成播放。
+录音机应用采用扩展的TS语言(ArkTS)开发,主要的结构如下:
+
+- **product**
+ 业务形态层:区分不同产品、不同屏幕的各形态应用,含有个性化业务,组件的配置,以及个性化资源包。
+
+- **feature**
+ 公共特性层:抽象的公共特性组件集合,可以被各应用形态引用。
+
+- **common**
+ 公共能力层:基础能力集,每个应用形态都必须依赖的模块,包含工具类和通用的资源包
+
+## 目录
+### 目录结构
+```
+/recorder/
+├── common # 公共能力层目录
+├── feature # 公共特性层目录
+│ └── model # 数据格式目录
+│ └── controller # 控制逻辑目录
+├── product # 业务形态层目录
+```
+
+## 安装
+对应用完成签名,打包后,使用`hdc_std install "hap包地址"`命令进行安装
+
+
+
+
+## 约束
+- 开发环境
+ - **DevEco Studio for OpenHarmony**: 版本号大于3.0.0.992,下载安装OpenHarmony SDK API Version 9。(初始的IDE配置可以参考IDE的使用文档)
+- 语言版本
+ - ArkTS
+- 限制
+ - 本示例仅支持标准系统上运行
\ No newline at end of file
diff --git a/build-profile.json5 b/build-profile.json5
new file mode 100644
index 0000000000000000000000000000000000000000..ec555ee5bb89d82226cf4cbd05c14269796b6aeb
--- /dev/null
+++ b/build-profile.json5
@@ -0,0 +1,47 @@
+{
+ "app": {
+ "signingConfigs": [],
+ "compileSdkVersion": 9,
+ "compatibleSdkVersion": 9,
+ "products": [
+ {
+ "name": "default",
+ "signingConfig": "default",
+ }
+ ]
+ },
+ "modules": [
+ {
+ "name": "phone",
+ "srcPath": "./product/phone",
+ "targets": [
+ {
+ "name": "default",
+ "applyToProducts": [
+ "default"
+ ]
+ }
+ ]
+ },
+ {
+ "name": "tablet",
+ "srcPath": "./product/tablet",
+ "targets": [
+ {
+ "name": "default",
+ "applyToProducts": [
+ "default"
+ ]
+ }
+ ]
+ },
+ {
+ "name": "common",
+ "srcPath": "./common"
+ },
+ {
+ "name": "recorder",
+ "srcPath": "./feature/recorder"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/common/.gitignore b/common/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..4f9a973815d0b5e49bc8547681a6b4bc7a178d12
--- /dev/null
+++ b/common/.gitignore
@@ -0,0 +1,3 @@
+/node_modules
+/.preview
+/build
\ No newline at end of file
diff --git a/common/build-profile.json5 b/common/build-profile.json5
new file mode 100644
index 0000000000000000000000000000000000000000..35dff6d53cb3a241f8de4fb68bd01d38ade0f108
--- /dev/null
+++ b/common/build-profile.json5
@@ -0,0 +1,5 @@
+{
+ "apiType": "stageMode",
+ "buildOption": {
+ }
+}
diff --git a/common/hvigorfile.js b/common/hvigorfile.js
new file mode 100644
index 0000000000000000000000000000000000000000..42ed4b4a54a873e2b53441556aae93fab24b794f
--- /dev/null
+++ b/common/hvigorfile.js
@@ -0,0 +1,3 @@
+// Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently.
+module.exports = require('@ohos/hvigor-ohos-plugin').harTasks
+
diff --git a/common/index.ets b/common/index.ets
new file mode 100644
index 0000000000000000000000000000000000000000..af9c605b34eec7395760f18018aea72ecbd1e703
--- /dev/null
+++ b/common/index.ets
@@ -0,0 +1,20 @@
+/**
+ * Copyright (c) 2022 HiHope Open Source Organization.
+ * 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 as ConfigData } from './src/main/ets/utils/ConfigData'
+
+export { default as Util } from './src/main/ets/utils/Util'
+
+export { default as LogUtil } from './src/main/ets/utils/LogUtil'
diff --git a/common/package.json b/common/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..6845131bfa2ddc9998b03c56d12c85edc1a861e2
--- /dev/null
+++ b/common/package.json
@@ -0,0 +1,14 @@
+{
+ "license": "ISC",
+ "types": "",
+ "devDependencies": {},
+ "name": "@ohos/common",
+ "description": "a npm package which contains arkUI2.0 page",
+ "ohos": {
+ "org": ""
+ },
+ "main": "src/main/ets/components/MainPage/MainPage.ets",
+ "repository": {},
+ "version": "1.0.0",
+ "dependencies": {}
+}
diff --git a/common/src/main/ets/utils/ConfigData.ets b/common/src/main/ets/utils/ConfigData.ets
new file mode 100644
index 0000000000000000000000000000000000000000..728136a483d65992eec74b7fd766c0e7792b66cb
--- /dev/null
+++ b/common/src/main/ets/utils/ConfigData.ets
@@ -0,0 +1,25 @@
+/**
+ * Copyright (c) 2022 HiHope Open Source Organization.
+ * 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 class ConfigData {
+ WH_100_100 = '100%';
+ WH_50_100 = '50%';
+ WH_40_100 = '40%';
+ WH_20_100 = '20%';
+}
+
+let configData = new ConfigData();
+
+export default configData as ConfigData;
\ No newline at end of file
diff --git a/common/src/main/ets/utils/LogUtil.ts b/common/src/main/ets/utils/LogUtil.ts
new file mode 100644
index 0000000000000000000000000000000000000000..bbead9dd9befd61c6caee01842404061533ef8aa
--- /dev/null
+++ b/common/src/main/ets/utils/LogUtil.ts
@@ -0,0 +1,44 @@
+/**
+ * Copyright (c) 2022 HiHope Open Source Organization.
+ * 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";
+
+const DOMAIN = 0x0500;
+const TAG = "[Recorder]";
+
+/**
+ * log package tool class
+ */
+export default class LogUtil {
+ static debug(msg): void {
+ HiLog.debug(DOMAIN, TAG, msg);
+ }
+
+ static log(msg): void {
+ HiLog.info(DOMAIN, TAG, msg);
+ }
+
+ static info(msg): void {
+ HiLog.info(DOMAIN, TAG, msg);
+ }
+
+ static warn(msg): void {
+ HiLog.warn(DOMAIN, TAG, msg);
+ }
+
+ static error(msg): void {
+ HiLog.error(DOMAIN, TAG, msg);
+ }
+}
\ No newline at end of file
diff --git a/common/src/main/ets/utils/Util.ets b/common/src/main/ets/utils/Util.ets
new file mode 100644
index 0000000000000000000000000000000000000000..2887d8f9f4238430c8934332067eab7c79c826c6
--- /dev/null
+++ b/common/src/main/ets/utils/Util.ets
@@ -0,0 +1,109 @@
+/**
+ * Copyright (c) 2022 HiHope Open Source Organization.
+ * 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 Util {
+
+ /**
+ * Time format is converted to seconds
+ *
+ * @param timestamp
+ * @param hasHour
+ */
+ static timeFormat(timestamp: number, hasHour: boolean = true): string {
+ if (timestamp < 0) {
+ return "";
+ }
+ let timeStr = "";
+ let hour = Math.floor(timestamp % (24 * 60 * 60 * 1000) / (60 * 60 * 1000));
+ if (hasHour || hour > 0) {
+ timeStr += this.addZero(hour) + ":";
+ }
+ let min = Math.floor(timestamp % (60 * 60 * 1000) / (60 * 1000));
+ timeStr += this.addZero(min) + ":";
+ let sec = Math.floor(timestamp % (60 * 1000) / 1000);
+ timeStr += this.addZero(sec);
+ return timeStr;
+ }
+
+ /**
+ * Time format is converted to milliseconds
+ *
+ * @param timestamp
+ */
+ static timeFormatMs(timestamp: number): string {
+ if (timestamp <= 0) {
+ return "00:00.00";
+ }
+ let timeStr = "";
+ let hour = Math.floor(timestamp % (24 * 60 * 60 * 1000) / (60 * 60 * 1000));
+ if (hour > 0) {
+ timeStr += this.addZero(hour) + ":";
+ }
+ let min = Math.floor(timestamp % (60 * 60 * 1000) / (60 * 1000));
+ timeStr += this.addZero(min) + ":";
+ let sec = Math.floor(timestamp % (60 * 1000) / 1000);
+ timeStr += this.addZero(sec);
+ if (hour < 1) {
+ let mSec = Math.floor(timestamp % 1000 / 10);
+ timeStr += "." + this.addZero(mSec);
+ }
+ return timeStr;
+ }
+
+ /**
+ * Gets the date at the specified time
+ *
+ * @param date
+ */
+ static getDateFormat(date: number): string {
+ let nowDate = new Date(date);
+ return nowDate.getFullYear() + "/" + this.addZero(nowDate.getMonth() + 1) + "/" + this.addZero(nowDate.getDate());
+ }
+
+ /**
+ * Gets the hour, minute and second of the specified time
+ *
+ * @param date
+ */
+ static getTimeFormat(date: number): string {
+ let nowDate = new Date(date);
+ return this.addZero(nowDate.getHours()) + ":" + this.addZero(nowDate.getMinutes()) + ":" + this.addZero(nowDate.getSeconds());
+ }
+
+ /**
+ * Gets the current time date
+ */
+ static getDate(): string {
+ let nowDate = new Date();
+ return nowDate.getFullYear() + this.addZero(nowDate.getMonth() + 1) + this.addZero(nowDate.getDate());
+ }
+
+ /**
+ * Gets the hour, minute and second of the current time
+ */
+ static getTime(): string {
+ let nowDate = new Date();
+ return this.addZero(nowDate.getHours()) + this.addZero(nowDate.getMinutes()) + this.addZero(nowDate.getSeconds());
+ }
+
+ /**
+ * If the number is less than 10, add "0".
+ *
+ * @param num
+ */
+ static addZero(num: number): string{
+ return (num < 10 ? "0" + num : num).toString();
+ }
+}
\ No newline at end of file
diff --git a/common/src/main/module.json5 b/common/src/main/module.json5
new file mode 100644
index 0000000000000000000000000000000000000000..56a91a87ba3ea303b57e4b8e5f405e9d6d2a800a
--- /dev/null
+++ b/common/src/main/module.json5
@@ -0,0 +1,11 @@
+{
+ "module": {
+ "name": "common",
+ "type": "har",
+ "deviceTypes": [
+ "default",
+ "tablet"
+ ],
+ "uiSyntax": "ets"
+ }
+}
diff --git a/common/src/main/resources/base/element/color.json b/common/src/main/resources/base/element/color.json
new file mode 100644
index 0000000000000000000000000000000000000000..ffe7272ac3029e44fd8fe56844ac7a59808be8a2
--- /dev/null
+++ b/common/src/main/resources/base/element/color.json
@@ -0,0 +1,32 @@
+{
+ "color": [
+ {
+ "name": "white",
+ "value": "#FFFFFF"
+ },
+ {
+ "name": "background_color",
+ "value": "#F1F3F5"
+ },
+ {
+ "name": "shadow_color",
+ "value": "#11000000"
+ },
+ {
+ "name": "pointer_color",
+ "value": "#007DFF"
+ },
+ {
+ "name": "warn_color",
+ "value": "#FF595A"
+ },
+ {
+ "name": "hint_color",
+ "value": "#007DFF"
+ },
+ {
+ "name": "text_color",
+ "value": "#182431"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/common/src/main/resources/base/element/string.json b/common/src/main/resources/base/element/string.json
new file mode 100644
index 0000000000000000000000000000000000000000..2375b2de43748384262b8e07fa36405169d878c8
--- /dev/null
+++ b/common/src/main/resources/base/element/string.json
@@ -0,0 +1,56 @@
+{
+ "string": [
+ {
+ "name": "page_show",
+ "value": "page from npm package"
+ },
+ {
+ "name": "title",
+ "value": "录音机"
+ },
+ {
+ "name": "search_recording_files",
+ "value": "搜索录音文件"
+ },
+ {
+ "name": "mark",
+ "value": "标记"
+ },
+ {
+ "name": "pause",
+ "value": "暂停"
+ },
+ {
+ "name": "continue_record",
+ "value": "继续"
+ },
+ {
+ "name": "predict_hint",
+ "value": "预计还能录制10小时以上"
+ },
+ {
+ "name": "delete_hint",
+ "value": "是否删除此条录音?"
+ },
+ {
+ "name": "cancel",
+ "value": "取消"
+ },
+ {
+ "name": "delete",
+ "value": "删除"
+ },
+ {
+ "name": "has_call_hint",
+ "value": "通话状态无法录音"
+ },
+ {
+ "name": "recording_hint",
+ "value": "录音机正在录音"
+ },
+ {
+ "name": "suspend_record_hint",
+ "value": "录音机暂停录音"
+ }
+ ]
+}
diff --git a/common/src/main/resources/base/media/bg_record.png b/common/src/main/resources/base/media/bg_record.png
new file mode 100644
index 0000000000000000000000000000000000000000..41c12ccb5bb90cd9c05508690ca0b88f33e96008
Binary files /dev/null and b/common/src/main/resources/base/media/bg_record.png differ
diff --git a/common/src/main/resources/base/media/ic_back.svg b/common/src/main/resources/base/media/ic_back.svg
new file mode 100644
index 0000000000000000000000000000000000000000..6f7f039226e90d3ef91b0e83c4ad29236858e868
--- /dev/null
+++ b/common/src/main/resources/base/media/ic_back.svg
@@ -0,0 +1,10 @@
+
+
\ No newline at end of file
diff --git a/common/src/main/resources/base/media/ic_clock_add.svg b/common/src/main/resources/base/media/ic_clock_add.svg
new file mode 100644
index 0000000000000000000000000000000000000000..3ec94e4b1e5f198844f5136dc14c9ff00ab95887
--- /dev/null
+++ b/common/src/main/resources/base/media/ic_clock_add.svg
@@ -0,0 +1,8 @@
+
+
\ No newline at end of file
diff --git a/common/src/main/resources/base/media/ic_clock_enhance.svg b/common/src/main/resources/base/media/ic_clock_enhance.svg
new file mode 100644
index 0000000000000000000000000000000000000000..14a0ba5bbea05082e4659a124dc89b79f523509b
--- /dev/null
+++ b/common/src/main/resources/base/media/ic_clock_enhance.svg
@@ -0,0 +1,10 @@
+
+
\ No newline at end of file
diff --git a/common/src/main/resources/base/media/ic_clock_mark.svg b/common/src/main/resources/base/media/ic_clock_mark.svg
new file mode 100644
index 0000000000000000000000000000000000000000..330eda737a418114cc3389b4c14cbcada67ec5e9
--- /dev/null
+++ b/common/src/main/resources/base/media/ic_clock_mark.svg
@@ -0,0 +1,8 @@
+
+
\ No newline at end of file
diff --git a/common/src/main/resources/base/media/ic_clock_play.svg b/common/src/main/resources/base/media/ic_clock_play.svg
new file mode 100644
index 0000000000000000000000000000000000000000..4a6c56994bd7736e81307833789775d51b71cf6c
--- /dev/null
+++ b/common/src/main/resources/base/media/ic_clock_play.svg
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/common/src/main/resources/base/media/ic_clock_record.svg b/common/src/main/resources/base/media/ic_clock_record.svg
new file mode 100644
index 0000000000000000000000000000000000000000..c8f7906490d4e4ae103cf1d35fd3e5c4c55f27f0
--- /dev/null
+++ b/common/src/main/resources/base/media/ic_clock_record.svg
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/common/src/main/resources/base/media/ic_clock_stop.svg b/common/src/main/resources/base/media/ic_clock_stop.svg
new file mode 100644
index 0000000000000000000000000000000000000000..02964494e7ad6466ce03c368befe5a7e3c1ed01b
--- /dev/null
+++ b/common/src/main/resources/base/media/ic_clock_stop.svg
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/common/src/main/resources/base/media/ic_clock_suspend.svg b/common/src/main/resources/base/media/ic_clock_suspend.svg
new file mode 100644
index 0000000000000000000000000000000000000000..bf6f0ea2eabe25b77ef0bed04d52cb1e6ac6bb0b
--- /dev/null
+++ b/common/src/main/resources/base/media/ic_clock_suspend.svg
@@ -0,0 +1,8 @@
+
+
\ No newline at end of file
diff --git a/common/src/main/resources/base/media/ic_record_play.svg b/common/src/main/resources/base/media/ic_record_play.svg
new file mode 100644
index 0000000000000000000000000000000000000000..34c0e947768885c982a4031b2df92c1fe7b014ab
--- /dev/null
+++ b/common/src/main/resources/base/media/ic_record_play.svg
@@ -0,0 +1,8 @@
+
+
\ No newline at end of file
diff --git a/common/src/main/resources/base/media/ic_record_search.svg b/common/src/main/resources/base/media/ic_record_search.svg
new file mode 100644
index 0000000000000000000000000000000000000000..1b2a8aa35dc2c2ea6b47fad1fdfe319947701b43
--- /dev/null
+++ b/common/src/main/resources/base/media/ic_record_search.svg
@@ -0,0 +1,8 @@
+
+
\ No newline at end of file
diff --git a/common/src/main/resources/base/media/ic_record_stop.svg b/common/src/main/resources/base/media/ic_record_stop.svg
new file mode 100644
index 0000000000000000000000000000000000000000..2b7ceaa1d1b8a10b1b18f0a1515477874df4b357
--- /dev/null
+++ b/common/src/main/resources/base/media/ic_record_stop.svg
@@ -0,0 +1,9 @@
+
+
\ No newline at end of file
diff --git a/common/src/main/resources/base/media/public_more.svg b/common/src/main/resources/base/media/public_more.svg
new file mode 100644
index 0000000000000000000000000000000000000000..400c3ef1abe1ebe1b0637b20c1c4b9711812b41e
--- /dev/null
+++ b/common/src/main/resources/base/media/public_more.svg
@@ -0,0 +1,13 @@
+
+
\ No newline at end of file
diff --git a/feature/recorder/.gitignore b/feature/recorder/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..4f9a973815d0b5e49bc8547681a6b4bc7a178d12
--- /dev/null
+++ b/feature/recorder/.gitignore
@@ -0,0 +1,3 @@
+/node_modules
+/.preview
+/build
\ No newline at end of file
diff --git a/feature/recorder/build-profile.json5 b/feature/recorder/build-profile.json5
new file mode 100644
index 0000000000000000000000000000000000000000..35dff6d53cb3a241f8de4fb68bd01d38ade0f108
--- /dev/null
+++ b/feature/recorder/build-profile.json5
@@ -0,0 +1,5 @@
+{
+ "apiType": "stageMode",
+ "buildOption": {
+ }
+}
diff --git a/feature/recorder/hvigorfile.js b/feature/recorder/hvigorfile.js
new file mode 100644
index 0000000000000000000000000000000000000000..42ed4b4a54a873e2b53441556aae93fab24b794f
--- /dev/null
+++ b/feature/recorder/hvigorfile.js
@@ -0,0 +1,3 @@
+// Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently.
+module.exports = require('@ohos/hvigor-ohos-plugin').harTasks
+
diff --git a/feature/recorder/index.ets b/feature/recorder/index.ets
new file mode 100644
index 0000000000000000000000000000000000000000..98c643021853a081786f585ce73e679d2f0a3df7
--- /dev/null
+++ b/feature/recorder/index.ets
@@ -0,0 +1,24 @@
+/**
+ * Copyright (c) 2022 HiHope Open Source Organization.
+ * 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 { RecordController, RecordState } from './src/main/ets/controller/RecordController'
+
+export { default as MediaController } from './src/main/ets/controller/MediaController'
+
+export { AudioController, AudioState } from './src/main/ets/controller/AudioController'
+
+export { StateController, ControlState } from './src/main/ets/controller/StateController'
+
+export { RecordData } from './src/main/ets/model/RecordData'
diff --git a/feature/recorder/package.json b/feature/recorder/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..83c74cf381e47f923fd3171be67a35594e7224e6
--- /dev/null
+++ b/feature/recorder/package.json
@@ -0,0 +1,16 @@
+{
+ "license": "ISC",
+ "types": "",
+ "devDependencies": {},
+ "name": "@ohos/recorder",
+ "description": "a npm package which contains arkUI2.0 page",
+ "ohos": {
+ "org": ""
+ },
+ "main": "src/main/ets/components/MainPage/MainPage.ets",
+ "repository": {},
+ "version": "1.0.0",
+ "dependencies": {
+ "@ohos/common": "file:../../common"
+ }
+}
diff --git a/feature/recorder/src/main/ets/controller/AudioController.ets b/feature/recorder/src/main/ets/controller/AudioController.ets
new file mode 100644
index 0000000000000000000000000000000000000000..81247180eeff1ff8ad64186231295024a31c279e
--- /dev/null
+++ b/feature/recorder/src/main/ets/controller/AudioController.ets
@@ -0,0 +1,253 @@
+/**
+ * Copyright (c) 2022 HiHope Open Source Organization.
+ * 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 mediaLibrary from '@ohos.multimedia.mediaLibrary';
+import media from '@ohos.multimedia.media';
+import { LogUtil } from '@ohos/common';
+
+export enum AudioState {
+ /**
+ * The initial state
+ */
+ IDLE,
+ /**
+ * Continuously update time starting from IDLE state or PAUSED state
+ */
+ RUNNING,
+ /**
+ * Click Pause from the RUNNING state
+ */
+ PAUSED,
+}
+
+const NOTIFY_INTERVAL_MS = 50;
+const TAG = "AudioController : ";
+
+export class AudioController {
+ private audioPlayer: media.AudioPlayer = undefined;
+ private state: AudioState = AudioState.IDLE;
+ private playFile: mediaLibrary.FileAsset = undefined;
+ private dataLoad: boolean = false;
+ private finishListener: () => void = undefined;
+ private currentTimeMs: number = 0;
+ private intervalId: number = -1;
+ private timeUpdateListener: (currentTimeMs: number) => void = undefined;
+ private stateUpdateListener: (AudioState) => void = undefined;
+
+ /**
+ * Set the time update listener
+ *
+ * @param listener
+ */
+ public setTimeUpdateListener(listener: (currentTimeMs: number) => void) {
+ LogUtil.info(`${TAG} setTimeUpdateListener`);
+ this.timeUpdateListener = listener;
+ }
+
+ /**
+ * Set the status update listener
+ *
+ * @param listener
+ */
+ public setStateUpdateListener(listener: (AudioState) => void) {
+ LogUtil.info(`${TAG} setStateUpdateListener`);
+ this.stateUpdateListener = listener;
+ }
+
+ /**
+ * Notification time update
+ */
+ private notifyTimeChanges() {
+ if (this.timeUpdateListener) {
+ this.timeUpdateListener(this.currentTimeMs);
+ }
+ }
+
+ /**
+ * Notification of status updates
+ */
+ private async notifyStateChanges() {
+ if (this.stateUpdateListener) {
+ LogUtil.info(`${TAG} state update ${this.state}`);
+ this.stateUpdateListener(this.state);
+ }
+ }
+
+ /**
+ * Initialize the audio player
+ *
+ * @param playSrc
+ * @param callback
+ */
+ initAudioPlayer(playSrc: mediaLibrary.FileAsset, callback: () => void) {
+ LogUtil.info(`${TAG} initAudioPlayer`);
+ this.playFile = playSrc;
+ this.dataLoad = false;
+ this.release();
+ LogUtil.info(`${TAG} createAudioPlayer`);
+ this.audioPlayer = media.createAudioPlayer();
+ this.audioPlayer.on('dataLoad', () => {
+ this.dataLoad = true;
+ LogUtil.info(`${TAG} audioPlayer dataLoad success`);
+ if (callback) {
+ callback();
+ }
+ })
+ this.audioPlayer.on('stop', () => {
+ LogUtil.info(`${TAG} audioPlayer stop success`);
+ this.release();
+ })
+ this.audioPlayer.on('error', (error) => {
+ LogUtil.error(`${TAG} audioPlayer error. info:${error}`);
+ })
+ this.audioPlayer.on('timeUpdate', (num) => {
+ LogUtil.info(`${TAG} audioPlayer timeUpdate. time:${num}`);
+ })
+ this.audioPlayer.on('audioInterrupt', (num) => {
+ LogUtil.warn(`${TAG} audioPlayer audioInterrupt. time:${num}`);
+ })
+ this.audioPlayer.on('finish', () => {
+ LogUtil.info(`${TAG} audioPlayer finish.`);
+ clearInterval(this.intervalId);
+ this.intervalId = -1;
+ this.state = AudioState.IDLE;
+ this.notifyStateChanges();
+ })
+ LogUtil.info(`${TAG} audioPlayer reset.`)
+ this.audioPlayer.reset();
+ LogUtil.info(`${TAG} read FileAsset`);
+ let fdPath = playSrc.open('r');
+ fdPath.then(fdNumber => {
+ LogUtil.info(`${TAG} audioPlayer set src`);
+ this.audioPlayer.src = `fd://${fdNumber}`;
+ });
+ }
+
+ /**
+ * audioPlayer play
+ */
+ play() {
+ if (typeof (this.audioPlayer) != `undefined`) {
+ this.audioPlayer.on('play', () => {
+ LogUtil.info(`${TAG} audioPlayer play success`);
+ this.startTimer();
+ });
+ if (this.dataLoad) {
+ LogUtil.info(`${TAG} audioPlayer play`);
+ this.audioPlayer.play();
+ } else {
+ this.audioPlayer.on('dataLoad', () => {
+ LogUtil.info(`${TAG} audioPlayer dataLoad success`);
+ this.dataLoad = true;
+ LogUtil.info(`${TAG} audioPlayer play`);
+ this.audioPlayer.play();
+ });
+ }
+ }
+ }
+
+ /**
+ * audioPlayer seek time
+ *
+ * @param timeMs
+ */
+ seek(timeMs: number) {
+ if (typeof (this.audioPlayer) != `undefined`) {
+ LogUtil.info(`${TAG} audioPlayer time to jump. time:` + timeMs);
+ this.audioPlayer.seek(timeMs);
+ }
+ }
+
+ /**
+ * audioPlayer pause
+ */
+ pause() {
+ if (typeof (this.audioPlayer) != `undefined`) {
+ this.audioPlayer.on('pause', () => {
+ LogUtil.info(`${TAG} audioPlayer pause success`);
+ this.stopTimer();
+ });
+ LogUtil.info(`${TAG} audioPlayer pause`);
+ this.audioPlayer.pause();
+ }
+ }
+
+ /**
+ * audioPlayer stop
+ */
+ stop() {
+ if (typeof (this.audioPlayer) != `undefined` && this.state != AudioState.IDLE) {
+ LogUtil.info(`${TAG} audioPlayer stop`);
+ this.state = AudioState.IDLE;
+ this.notifyStateChanges();
+ clearInterval(this.intervalId);
+ this.intervalId = -1;
+ }
+ }
+
+ /**
+ * audioPlayer continue play
+ */
+ continuePlay() {
+ if (typeof (this.audioPlayer) != `undefined`) {
+ LogUtil.info(`${TAG} audioPlayer continue play`);
+ this.audioPlayer.play();
+ }
+ }
+
+ /**
+ * startTimer
+ */
+ startTimer() {
+ if (this.intervalId != -1) {
+ return;
+ }
+ this.state = AudioState.RUNNING;
+ this.notifyStateChanges();
+ LogUtil.info(`${TAG} audioPlayer show time update start`);
+ this.intervalId = setInterval(() => {
+ this.currentTimeMs = this.audioPlayer.currentTime;
+ this.notifyTimeChanges();
+ }, NOTIFY_INTERVAL_MS);
+ }
+
+ /**
+ * stopTimer
+ */
+ stopTimer() {
+ if (this.intervalId == -1) {
+ return;
+ }
+ this.state = AudioState.PAUSED;
+ this.notifyStateChanges();
+ LogUtil.info(`${TAG} audioPlayer show time update stop`);
+ clearInterval(this.intervalId);
+ this.intervalId = -1;
+ }
+
+ /**
+ * audioPlayer release
+ */
+ release() {
+ if (typeof (this.audioPlayer) != `undefined`) {
+ this.currentTimeMs = 0;
+ this.notifyTimeChanges();
+ LogUtil.info(`${TAG} audioPlayer release`);
+ this.audioPlayer.release();
+ this.audioPlayer = undefined;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/feature/recorder/src/main/ets/controller/MediaController.ets b/feature/recorder/src/main/ets/controller/MediaController.ets
new file mode 100644
index 0000000000000000000000000000000000000000..103d07284684cdb6ff8370a1d666290e04515bcd
--- /dev/null
+++ b/feature/recorder/src/main/ets/controller/MediaController.ets
@@ -0,0 +1,120 @@
+/**
+ * Copyright (c) 2022 HiHope Open Source Organization.
+ * 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 mediaLibrary from '@ohos.multimedia.mediaLibrary';
+import { Util } from '@ohos/common';
+import { RecordData } from '../model/RecordData';
+import data_preferences from '@ohos.data.preferences';
+import { LogUtil } from '@ohos/common';
+
+const TAG = 'MediaController : ';
+
+class MediaController {
+ private mMediaLibrary: mediaLibrary.MediaLibrary = mediaLibrary.getMediaLibrary(globalThis.abilityContext);
+ private preferences: data_preferences.Preferences = undefined;
+
+ constructor() {
+ this.initPreferences();
+ }
+
+ /**
+ * delete Record
+ *
+ * @param deleteRecordUri
+ * @param callback
+ */
+ deleteRecord(deleteRecordUri: string, callback: () => void) {
+ LogUtil.info(`${TAG} delete Asset. uri: ${deleteRecordUri}`)
+ this.mMediaLibrary.deleteAsset(deleteRecordUri).then(() => {
+ LogUtil.info(`${TAG} delete Asset successs.`);
+ callback();
+ });
+ }
+
+ /**
+ * init Preferences
+ */
+ async initPreferences() {
+ LogUtil.info(`${TAG} initPreferences.`);
+ this.preferences = await data_preferences.getPreferences(globalThis.abilityContext, 'myRecorderStore');
+ }
+
+ /**
+ * save File Duration to preferences
+ *
+ * @param name
+ * @param value
+ */
+ async saveFileDuration(name: string, value: number) {
+ LogUtil.info(`${TAG} saveFileDuration. fileName : ${name}. duration:${value}`);
+ if (this.preferences) {
+ await this.preferences.put(name, value);
+ this.preferences.flush();
+ LogUtil.info(`${TAG} save File Duration end.`);
+ }
+ }
+
+ /**
+ * get File Duration from preferences
+ *
+ * @param name
+ */
+ async getFileDuration(name: string) {
+ LogUtil.info(`${TAG} getFileDuration. fileName : ${name}.`);
+ return this.preferences.get(name, 0);
+ }
+
+ /**
+ * query All Audios
+ */
+ async queryAllAudios() {
+ LogUtil.info(`${TAG} queryAllAudios`);
+ let fileKeyObj = mediaLibrary.FileKey;
+ let fetchOp = {
+ selections: `${fileKeyObj.MEDIA_TYPE}=?`,
+ selectionArgs: [`${mediaLibrary.MediaType.AUDIO}`],
+ }
+ LogUtil.info(`${TAG} getFileAssets`);
+ const fetchFileResult = await this.mMediaLibrary.getFileAssets(fetchOp);
+ LogUtil.info(`${TAG} Assets count : ${fetchFileResult.getCount()}`);
+ let result: Array = [];
+ if (fetchFileResult.getCount() > 0) {
+ let fileAssets = await fetchFileResult.getAllObject();
+ for (let i = 0; i < fileAssets.length; i++) {
+ let record = new RecordData(fileAssets[i]);
+ if (record.duration == 0) {
+ await record.getDuration();
+ }
+ result.push(record);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * create Audio File
+ */
+ async createAudioFile() {
+ LogUtil.info(`${TAG} createAudioFile`);
+ this.mMediaLibrary = mediaLibrary.getMediaLibrary(globalThis.abilityContext);
+ let displayName = Util.getDate() + "_" + Util.getTime() + ".m4a";
+ LogUtil.info(`${TAG} displayName : ${displayName}`);
+ let publicPath = await this.mMediaLibrary.getPublicDirectory(mediaLibrary.DirectoryType.DIR_AUDIO);
+ LogUtil.info(`${TAG} publicPath : ${publicPath}`);
+ return await this.mMediaLibrary.createAsset(mediaLibrary.MediaType.AUDIO, displayName, publicPath);
+ }
+}
+
+export default new MediaController();
\ No newline at end of file
diff --git a/feature/recorder/src/main/ets/controller/RecordController.ets b/feature/recorder/src/main/ets/controller/RecordController.ets
new file mode 100644
index 0000000000000000000000000000000000000000..6dc9992f8c07eb8abf1f0e597fbacdb9426f6a8f
--- /dev/null
+++ b/feature/recorder/src/main/ets/controller/RecordController.ets
@@ -0,0 +1,233 @@
+/**
+ * Copyright (c) 2022 HiHope Open Source Organization.
+ * 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 media from '@ohos.multimedia.media';
+import { LogUtil } from '@ohos/common';
+import Notification from '@ohos.notification';
+
+const NOTIFY_INTERVAL_MS = 60;
+const NOTIFY_ID = 10023;
+const TAG = 'RecordController : ';
+
+let audioConfig = {
+ audioEncodeBitRate: 22050,
+ audioSampleRate: 22050,
+ numberOfChannels: 2,
+ uri: '',
+ location: { latitude: 31, longitude: 118 },
+ audioEncoderMime: media.CodecMimeType.AUDIO_AAC,
+ fileFormat: media.ContainerFormatType.CFT_MPEG_4A,
+}
+
+export enum RecordState {
+ IDLE,
+ RUNNING,
+ PAUSED,
+}
+
+export class RecordController {
+ private beforePauseTime: number = 0;
+ private startTimestamp: number = 0;
+ private notifierId: number = -1;
+ private state: RecordState = RecordState.PAUSED;
+ private currentTimeMs: number = 0;
+ private audioRecorder: media.AudioRecorder = undefined;
+ private timeUpdateListener: (currentTimeMs: number) => void = undefined;
+ private stateUpdateListener: (RecordState) => void = undefined;
+
+ /**
+ * Notification time update
+ */
+ private notifyTimeChanges() {
+ if (this.timeUpdateListener) {
+ this.timeUpdateListener(this.currentTimeMs);
+ }
+ }
+
+ /**
+ * Notification of status updates
+ */
+ private async notifyStateChanges() {
+ if (this.stateUpdateListener) {
+ LogUtil.info(`${TAG} state update. ${this.state}`);
+ this.stateUpdateListener(this.state);
+ }
+ }
+
+ /**
+ * Set the time update listener
+ *
+ * @param listener
+ */
+ public setTimeUpdateListener(listener: (currentTimeMs: number) => void) {
+ LogUtil.info(`${TAG} setTimeUpdateListener.`);
+ this.timeUpdateListener = listener;
+ }
+
+ /**
+ * Set the status update listener
+ *
+ * @param listener
+ */
+ public setStateUpdateListener(listener: (RecordState) => void) {
+ LogUtil.info(`${TAG} setStateUpdateListener.`);
+ this.stateUpdateListener = listener;
+ }
+
+ /**
+ * init AudioRecorder
+ */
+ initAudioRecorder() {
+ LogUtil.info(`${TAG} initAudioRecorder.`);
+ let recording_hint
+ let suspend_record_hint
+ globalThis.abilityContext.resourceManager.getString($r('app.string.recording_hint').id).then((value) => {
+ recording_hint = value
+ })
+ globalThis.abilityContext.resourceManager.getString($r('app.string.suspend_record_hint').id).then((value) => {
+ suspend_record_hint = value
+ })
+ this.audioRecorder = media.createAudioRecorder();
+ this.audioRecorder.on('prepare', () => {
+ LogUtil.info(`${TAG} audioRecorder prepare success.`);
+ this.audioRecorder.start();
+ });
+ this.audioRecorder.on('start', () => {
+ LogUtil.info(`${TAG} audioRecorder start success.`);
+ this.publishNotification(recording_hint)
+ this.startTime();
+ });
+ this.audioRecorder.on('pause', () => {
+ LogUtil.info(`${TAG} audioRecorder pause success`);
+ this.publishNotification(suspend_record_hint)
+ clearInterval(this.notifierId);
+ this.notifierId = -1;
+ this.beforePauseTime += new Date().getTime() - this.startTimestamp;
+ this.state = RecordState.PAUSED;
+ this.notifyStateChanges();
+ });
+ this.audioRecorder.on('resume', () => {
+ LogUtil.info(`${TAG} audioRecorder resume success`);
+ this.publishNotification(recording_hint)
+ this.startTime();
+ });
+ this.audioRecorder.on('stop', () => {
+ LogUtil.info(`${TAG} audioRecorder stop success`);
+ this.logoutNotification()
+ this.audioRecorder.release();
+ clearInterval(this.notifierId);
+ this.notifierId = -1;
+ this.state = RecordState.IDLE;
+ this.currentTimeMs = 0;
+ this.notifyStateChanges();
+ this.notifyTimeChanges();
+ });
+ }
+
+ /**
+ * Gets the sound size of the recording
+ */
+ getSoundVolume() { //todo
+ return Math.random() * 100 + 5;
+ }
+
+ /**
+ * Recorder start
+ *
+ * @param pathName
+ */
+ startRecorder(pathName) {
+ if (typeof (this.audioRecorder) != 'undefined') {
+ audioConfig.uri = pathName;
+ LogUtil.info(`${TAG} audioRecorder prepare.`);
+ this.audioRecorder.prepare(audioConfig);
+ } else {
+ LogUtil.error(`${TAG} case failed, audioRecorder is null.`);
+ }
+ }
+
+ /**
+ * start Time
+ */
+ startTime() {
+ this.state = RecordState.RUNNING;
+ this.startTimestamp = new Date().getTime();
+ LogUtil.info(`${TAG} audioRecorder show time update start`);
+ this.notifierId = setInterval(() => {
+ let timestamp = new Date().getTime();
+ this.currentTimeMs = this.beforePauseTime + timestamp - this.startTimestamp;
+ this.notifyTimeChanges();
+ }, NOTIFY_INTERVAL_MS);
+ this.notifyStateChanges();
+ }
+
+ /**
+ * Recorder pause
+ */
+ pause() {
+ if (typeof (this.audioRecorder) != `undefined`) {
+ LogUtil.info(`${TAG} audioRecorder pause`);
+ this.audioRecorder.pause();
+ }
+ }
+
+ /**
+ * Recorder resume
+ */
+ resume() {
+ if (typeof (this.audioRecorder) != `undefined`) {
+ LogUtil.info(`${TAG} audioRecorder resume`);
+ this.audioRecorder.resume();
+ }
+ }
+
+ /**
+ * Recorder stop
+ */
+ stop(callback?) {
+ if (typeof (this.audioRecorder) != `undefined`) {
+ LogUtil.info(`${TAG} audioRecorder stop`);
+ this.audioRecorder.stop();
+ if (callback) {
+ callback();
+ }
+ }
+ }
+
+ /**
+ * publish Notification
+ */
+ publishNotification(text) {
+ var notificationRequest = {
+ id: NOTIFY_ID,
+ content: {
+ contentType: Notification.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
+ normal: {
+ title: text,
+ text: ``,
+ additionalText: ``
+ }
+ }
+ }
+ Notification.publish(notificationRequest)
+ }
+
+ /**
+ * logout Notification
+ */
+ logoutNotification() {
+ Notification.cancel(NOTIFY_ID)
+ }
+}
\ No newline at end of file
diff --git a/feature/recorder/src/main/ets/controller/StateController.ets b/feature/recorder/src/main/ets/controller/StateController.ets
new file mode 100644
index 0000000000000000000000000000000000000000..b8c1ea6f3e12f2cdfe000c6174faea17018a9e45
--- /dev/null
+++ b/feature/recorder/src/main/ets/controller/StateController.ets
@@ -0,0 +1,64 @@
+/**
+ * Copyright (c) 2022 HiHope Open Source Organization.
+ * 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 { LogUtil } from '@ohos/common';
+
+const TAG = 'ControlState : ';
+
+export enum ControlState {
+ START,
+ END,
+}
+
+export class StateController {
+ private state: ControlState = ControlState.END;
+ private stateUpdateListener: (RecordState) => void = undefined;
+
+ /**
+ * Set the status update listener
+ *
+ * @param listener
+ */
+ public setStateUpdateListener(listener: (RecordState) => void) {
+ this.stateUpdateListener = listener;
+ }
+
+ /**
+ * Notification of status updates
+ */
+ private async notifyStateChanges() {
+ if (this.stateUpdateListener) {
+ this.stateUpdateListener(this.state);
+ }
+ }
+
+ /**
+ * Announce the start of recording
+ */
+ public start() {
+ LogUtil.info(`${TAG} record start.`);
+ this.state = ControlState.START;
+ this.notifyStateChanges();
+ }
+
+ /**
+ * Announce the end of recording
+ */
+ public end() {
+ LogUtil.info(`${TAG} record end.`);
+ this.state = ControlState.END;
+ this.notifyStateChanges();
+ }
+}
\ No newline at end of file
diff --git a/feature/recorder/src/main/ets/model/RecordData.ets b/feature/recorder/src/main/ets/model/RecordData.ets
new file mode 100644
index 0000000000000000000000000000000000000000..60e96623cfaa5b9537d20eacafe8b215a76aee66
--- /dev/null
+++ b/feature/recorder/src/main/ets/model/RecordData.ets
@@ -0,0 +1,44 @@
+/**
+ * Copyright (c) 2022 HiHope Open Source Organization.
+ * 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 { default as MediaController } from '../controller/MediaController';
+import mediaLibrary from '@ohos.multimedia.mediaLibrary';
+
+export class RecordData {
+ fileAsset: mediaLibrary.FileAsset;
+ title: string;
+ duration: number;
+ date: number;
+
+ constructor(fileAsset: mediaLibrary.FileAsset) {
+ this.fileAsset = fileAsset;
+ if (fileAsset) {
+ if (fileAsset.duration > 0) {
+ this.duration = fileAsset.duration;
+ } else {
+ this.duration = 0;
+ }
+ this.date = fileAsset.dateAdded;
+ this.title = fileAsset.title;
+ } else {
+ this.duration = 0;
+ this.title = '';
+ }
+ }
+
+ async getDuration() {
+ this.duration = Number(await MediaController.getFileDuration(this.fileAsset.title));
+ }
+}
\ No newline at end of file
diff --git a/feature/recorder/src/main/module.json5 b/feature/recorder/src/main/module.json5
new file mode 100644
index 0000000000000000000000000000000000000000..ed1e0395a095843abbd6047df13d59d682c9c980
--- /dev/null
+++ b/feature/recorder/src/main/module.json5
@@ -0,0 +1,11 @@
+{
+ "module": {
+ "name": "recorder",
+ "type": "har",
+ "deviceTypes": [
+ "default",
+ "tablet"
+ ],
+ "uiSyntax": "ets"
+ }
+}
diff --git a/feature/recorder/src/main/resources/base/element/string.json b/feature/recorder/src/main/resources/base/element/string.json
new file mode 100644
index 0000000000000000000000000000000000000000..1e76de0c66777cfe83568615c5c2e68c61d23fed
--- /dev/null
+++ b/feature/recorder/src/main/resources/base/element/string.json
@@ -0,0 +1,8 @@
+{
+ "string": [
+ {
+ "name": "page_show",
+ "value": "page from npm package"
+ }
+ ]
+}
diff --git a/figures/buildHap.png b/figures/buildHap.png
new file mode 100644
index 0000000000000000000000000000000000000000..7b2bf97258248c2e28a44150b129965e1a4d71d5
Binary files /dev/null and b/figures/buildHap.png differ
diff --git a/figures/install.png b/figures/install.png
new file mode 100644
index 0000000000000000000000000000000000000000..938d678caa5ca490f39607eca80f55d46836f6d5
Binary files /dev/null and b/figures/install.png differ
diff --git a/figures/record.png b/figures/record.png
new file mode 100644
index 0000000000000000000000000000000000000000..5fdd630e7d12482e0d01b1df78b61b1cf05858f8
Binary files /dev/null and b/figures/record.png differ
diff --git a/figures/signature.png b/figures/signature.png
new file mode 100644
index 0000000000000000000000000000000000000000..401a2e346be5237989fdb4880fbf53a76c76a8ee
Binary files /dev/null and b/figures/signature.png differ
diff --git a/hvigorfile.js b/hvigorfile.js
new file mode 100644
index 0000000000000000000000000000000000000000..5f2735e3deeaf655828407544bbed9365c258278
--- /dev/null
+++ b/hvigorfile.js
@@ -0,0 +1,2 @@
+// Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently.
+module.exports = require('@ohos/hvigor-ohos-plugin').appTasks
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..5694510c41deffd4f7a9c13b7ba9ce20e3190e33
--- /dev/null
+++ b/package.json
@@ -0,0 +1,18 @@
+{
+ "license": "ISC",
+ "devDependencies": {},
+ "name": "ohosrecorder",
+ "ohos": {
+ "org": "huawei",
+ "directoryLevel": "project",
+ "buildTool": "hvigor"
+ },
+ "description": "example description",
+ "repository": {},
+ "version": "1.0.0",
+ "dependencies": {
+ "@ohos/hypium": "1.0.2",
+ "@ohos/hvigor-ohos-plugin": "1.2.2",
+ "@ohos/hvigor": "1.2.2"
+ }
+}
diff --git a/product/phone/.gitignore b/product/phone/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..5a6ba80fa3d9498a23ae8ae7d9518f8743fa8a96
--- /dev/null
+++ b/product/phone/.gitignore
@@ -0,0 +1,4 @@
+/node_modules
+/.preview
+/build
+/.cxx
\ No newline at end of file
diff --git a/product/phone/build-profile.json5 b/product/phone/build-profile.json5
new file mode 100644
index 0000000000000000000000000000000000000000..f8f03407f77914b43168aeca6bb0929efd6700b4
--- /dev/null
+++ b/product/phone/build-profile.json5
@@ -0,0 +1,13 @@
+{
+ "apiType": 'stageMode',
+ "buildOption": {
+ },
+ "targets": [
+ {
+ "name": "default"
+ },
+ {
+ "name": "ohosTest",
+ }
+ ]
+}
\ No newline at end of file
diff --git a/product/phone/hvigorfile.js b/product/phone/hvigorfile.js
new file mode 100644
index 0000000000000000000000000000000000000000..d7720ee6a7aad5c617d1fd2f6fc8c87067bfa32c
--- /dev/null
+++ b/product/phone/hvigorfile.js
@@ -0,0 +1,2 @@
+// Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently.
+module.exports = require('@ohos/hvigor-ohos-plugin').hapTasks
diff --git a/product/phone/package-lock.json b/product/phone/package-lock.json
new file mode 100644
index 0000000000000000000000000000000000000000..0b4f8dc111bbf1ecc45dc631e7ad3bf42e596677
--- /dev/null
+++ b/product/phone/package-lock.json
@@ -0,0 +1,22 @@
+{
+ "name": "phone",
+ "version": "1.0.0",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "@ohos/common": {
+ "version": "file:../../common"
+ },
+ "@ohos/recorder": {
+ "version": "file:../../feature/recorder",
+ "requires": {
+ "@ohos/common": "file:../../common"
+ },
+ "dependencies": {
+ "@ohos/common": {
+ "version": "file:../../common"
+ }
+ }
+ }
+ }
+}
diff --git a/product/phone/package.json b/product/phone/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..9c0ae7b1545ebb53266c0a066a6a60a5007fe4b9
--- /dev/null
+++ b/product/phone/package.json
@@ -0,0 +1,17 @@
+{
+ "license": "ISC",
+ "devDependencies": {},
+ "name": "phone",
+ "ohos": {
+ "org": "huawei",
+ "directoryLevel": "module",
+ "buildTool": "hvigor"
+ },
+ "description": "example description",
+ "repository": {},
+ "version": "1.0.0",
+ "dependencies": {
+ "@ohos/recorder": "file:../../feature/recorder",
+ "@ohos/common": "file:../../common"
+ }
+}
diff --git a/product/phone/src/main/ets/Application/MyAbilityStage.ts b/product/phone/src/main/ets/Application/MyAbilityStage.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0a9efc1ed476ee2dfdca792a1632b37b99922670
--- /dev/null
+++ b/product/phone/src/main/ets/Application/MyAbilityStage.ts
@@ -0,0 +1,22 @@
+/**
+ * Copyright (c) 2022 HiHope Open Source Organization.
+ * 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 AbilityStage from "@ohos.application.AbilityStage"
+
+export default class MyAbilityStage extends AbilityStage {
+ onCreate() {
+ console.log("[Demo] MyAbilityStage onCreate")
+ }
+}
\ No newline at end of file
diff --git a/product/phone/src/main/ets/MainAbility/MainAbility.ts b/product/phone/src/main/ets/MainAbility/MainAbility.ts
new file mode 100644
index 0000000000000000000000000000000000000000..67501c1826d9d010967d72a2428c02442e5d1423
--- /dev/null
+++ b/product/phone/src/main/ets/MainAbility/MainAbility.ts
@@ -0,0 +1,61 @@
+/**
+ * Copyright (c) 2022 HiHope Open Source Organization.
+ * 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 Ability from '@ohos.application.Ability'
+import Notification from '@ohos.notification';
+
+export default class MainAbility extends Ability {
+ onCreate(want, launchParam) {
+ console.log("[Demo] MainAbility onCreate")
+ globalThis.abilityWant = want;
+ globalThis.abilityContext = this.context;
+ }
+
+ onDestroy() {
+ Notification.cancelAll()
+ console.log("[Demo] MainAbility onDestroy")
+ }
+
+ onWindowStageCreate(windowStage) {
+ // Main window is created, set main page for this ability
+ console.log("[Demo] MainAbility onWindowStageCreate")
+ this.context.requestPermissionsFromUser(['ohos.permission.MICROPHONE', 'ohos.permission.WRITE_MEDIA', 'ohos.permission.READ_MEDIA']).then(() => {
+ Notification.requestEnableNotification().then(() => {
+ windowStage.loadContent("pages/index", (err, data) => {
+ if (err.code) {
+ console.error('Failed to load the content. Cause:' + JSON.stringify(err));
+ return;
+ }
+ console.info('Succeeded in loading the content. Data: ' + JSON.stringify(data))
+ });
+ })
+ })
+ }
+
+ onWindowStageDestroy() {
+ // Main window is destroyed, release UI related resources
+ console.log("[Demo] MainAbility onWindowStageDestroy")
+ }
+
+ onForeground() {
+ // Ability has brought to foreground
+ console.log("[Demo] MainAbility onForeground")
+ }
+
+ onBackground() {
+ // Ability has back to background
+ console.log("[Demo] MainAbility onBackground")
+ }
+};
diff --git a/product/phone/src/main/ets/pages/RecordingPage.ets b/product/phone/src/main/ets/pages/RecordingPage.ets
new file mode 100644
index 0000000000000000000000000000000000000000..a99deb48a0189c59e7c02649a64753eaf836d043
--- /dev/null
+++ b/product/phone/src/main/ets/pages/RecordingPage.ets
@@ -0,0 +1,252 @@
+/**
+ * Copyright (c) 2022 HiHope Open Source Organization.
+ * 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 { ConfigData, Util } from '@ohos/common'
+import { RecordController, MediaController, RecordState } from '@ohos/recorder'
+import router from '@ohos.router';
+import mediaLibrary from '@ohos.multimedia.mediaLibrary'
+import prompt from '@ohos.prompt';
+import call from '@ohos.telephony.call';
+
+const TIME_INTERVAL_S = 2;
+const TIME_COUNT = 2 * 2 + 1;
+const VOICE_FREQUENCY = 25;
+
+@Entry
+@Component
+struct RecordingPage {
+ @State currentTime: number = 0
+ @State state: RecordState = RecordState.IDLE
+ private recordController: RecordController = new RecordController()
+ @State fileAsset: mediaLibrary.FileAsset = undefined
+ heightList: Array = []
+ mIntervalId = -1
+
+ async aboutToAppear() {
+ let that = this
+ this.recordController.setTimeUpdateListener((currentTimeMs: number) => {
+ if (Math.floor(currentTimeMs / (TIME_COUNT / 2 * 1000 * TIME_INTERVAL_S / VOICE_FREQUENCY)) - Math.floor(this.currentTime / (TIME_COUNT / 2 * 1000 * TIME_INTERVAL_S / VOICE_FREQUENCY)) == 1) {
+ that.heightList.splice(0, 1)
+ that.heightList.push(this.recordController.getSoundVolume())
+ }
+ this.currentTime = currentTimeMs
+ })
+ this.recordController.setStateUpdateListener((state) => {
+ this.state = state
+ })
+ this.recordController.initAudioRecorder()
+ this.fileAsset = await MediaController.createAudioFile()
+ let fd = await this.fileAsset.open('Rw')
+ this.recordController.startRecorder(`fd://${fd}`)
+ this.initHeightList()
+ }
+
+ aboutToDisappear() {
+ if (this.state != RecordState.IDLE) {
+ this.recordController.stop()
+ }
+ }
+
+ initHeightList() {
+ this.heightList = []
+ for (let index = 0; index < VOICE_FREQUENCY; index++) {
+ this.heightList.push(0)
+ }
+ }
+
+ build() {
+ Column() {
+ Row() {
+ Row() {
+ Image($r("app.media.ic_back"))
+ .width($r("app.float.wh_value_32"))
+ .height($r("app.float.wh_value_32"))
+ .margin({ left: $r("app.float.distance_32"), right: $r("app.float.distance_16") })
+ Text($r("app.string.title"))
+ .fontColor($r("app.color.text_color"))
+ .fontSize($r("app.float.font_40"))
+ .lineHeight($r("app.float.wh_value_37_5"))
+ }
+ .onClick(() => {
+ router.back()
+ })
+
+ Image($r("app.media.ic_clock_enhance"))
+ .width($r("app.float.wh_value_32"))
+ .height($r("app.float.wh_value_32"))
+ .margin({ right: $r("app.float.distance_32") })
+ }
+ .height($r("app.float.wh_value_56"))
+ .width(ConfigData.WH_100_100)
+ .justifyContent(FlexAlign.SpaceBetween)
+
+ Stack({ alignContent: Alignment.Top }) {
+ Column() {
+ Row() {
+ ForEach(Array.from(Array(TIME_COUNT + 1).keys()), (item) => {
+ Column() {
+ Text(Util.timeFormat(((item - (TIME_COUNT - 1) / 2) * 1000 * TIME_INTERVAL_S + Math.floor(this.currentTime / (1000 * TIME_INTERVAL_S)) * (1000 * TIME_INTERVAL_S)), false))
+ .textAlign(TextAlign.Center)
+ .fontColor($r("app.color.text_color"))
+ .fontSize($r("app.float.font_20"))
+ .opacity($r("app.float.opacity_6"))
+ Row() {
+ ForEach(Array.from(Array(5).keys()), (scaleItem) => {
+ Rect()
+ .width($r("app.float.wh_value_2_5"))
+ .fill($r("app.color.text_color"))
+ .height(scaleItem == 2 ? $r("app.float.wh_value_26_5") : $r("app.float.wh_value_13_5"))
+ .opacity(scaleItem == 2 ? $r("app.float.opacity_4") : $r("app.float.opacity_2"))
+ }, scaleItem => scaleItem)
+ }.width(ConfigData.WH_100_100)
+ .alignItems(VerticalAlign.Top)
+ .height($r("app.float.wh_value_26_5"))
+ .justifyContent(FlexAlign.SpaceAround)
+ }.width((100 / TIME_COUNT) + "%")
+ }, item => item)
+ }
+ .offset({
+ x: (-(100 / TIME_COUNT) * (this.currentTime % (TIME_INTERVAL_S * 1000) / (TIME_INTERVAL_S * 1000))) + "%"
+ })
+
+ Row() {
+ Row() {
+ Row() {
+ ForEach(Array.from(Array(VOICE_FREQUENCY).keys()), (item) => {
+ Rect()
+ .width($r("app.float.wh_value_2_5"))
+ .fill($r("app.color.text_color"))
+ .height(this.heightList[item])
+ .opacity($r("app.float.opacity_2"))
+ }, item => item)
+ }.width(ConfigData.WH_100_100)
+ .justifyContent(FlexAlign.SpaceAround)
+ .offset({
+ x: (-(100 / VOICE_FREQUENCY) * (this.currentTime % (TIME_COUNT / 2 * 1000 * TIME_INTERVAL_S / VOICE_FREQUENCY) / (TIME_COUNT / 2 * 1000 * TIME_INTERVAL_S / VOICE_FREQUENCY) - 1)) + "%"
+ })
+ }
+ .clip(true)
+ .width(ConfigData.WH_50_100)
+ .height(ConfigData.WH_100_100)
+ }
+ .justifyContent(FlexAlign.Start)
+ .width(ConfigData.WH_100_100)
+ .height($r("app.float.wh_value_266_5"))
+ .backgroundColor($r("app.color.shadow_color"))
+ }
+
+ Column() {
+ Rect()
+ .height($r("app.float.wh_value_304"))
+ .width($r("app.float.wh_value_2_5"))
+ .fill($r("app.color.pointer_color"))
+ Circle()
+ .height($r("app.float.wh_value_10_5"))
+ .width($r("app.float.wh_value_10_5"))
+ .fill($r("app.color.pointer_color"))
+ }.margin({ top: $r("app.float.distance_16") })
+
+ }.width(ConfigData.WH_100_100)
+ .height($r("app.float.wh_value_346_5"))
+
+
+ Text(Util.timeFormatMs(this.currentTime))
+ .fontColor($r("app.color.text_color"))
+ .fontSize($r("app.float.font_96"))
+ .lineHeight($r("app.float.wh_value_85_5"))
+ Text($r("app.string.predict_hint"))
+ .fontColor($r("app.color.text_color"))
+ .fontSize($r("app.float.font_28"))
+ .opacity($r("app.float.opacity_6"))
+ .visibility(this.currentTime > (10 * 1000) ? Visibility.Hidden : Visibility.Visible)
+ Blank()
+ Row() {
+ Column() {
+ Image($r("app.media.ic_clock_mark"))
+ .width($r("app.float.wh_value_32"))
+ .height($r("app.float.wh_value_32"))
+ Text($r("app.string.mark"))
+ .fontColor($r("app.color.text_color"))
+ .fontSize($r("app.float.font_20"))
+ .lineHeight($r("app.float.wh_value_18_5"))
+ }
+ .justifyContent(FlexAlign.Center)
+ .height($r("app.float.wh_value_64"))
+ .width($r("app.float.wh_value_64"))
+ .borderRadius($r("app.float.wh_value_64"))
+
+ Column() {
+ Image(this.state == RecordState.IDLE ? $r("app.media.ic_clock_record") : $r("app.media.ic_clock_stop"))
+ .width($r("app.float.wh_value_32"))
+ .height($r("app.float.wh_value_32"))
+ }
+ .justifyContent(FlexAlign.Center)
+ .height($r("app.float.wh_value_64"))
+ .width($r("app.float.wh_value_64"))
+ .borderRadius($r("app.float.wh_value_64"))
+ .backgroundColor($r("app.color.white"))
+ .onClick(async () => {
+ if (this.state == RecordState.IDLE) {
+ let hasCall = await call.hasCall()
+ if (hasCall) {
+ prompt.showToast({ message: $r('app.string.has_call_hint') })
+ } else {
+ this.recordController.initAudioRecorder()
+ this.fileAsset = await MediaController.createAudioFile()
+ let fd = await this.fileAsset.open('Rw')
+ this.recordController.startRecorder(`fd://${fd}`)
+ }
+ } else {
+ this.recordController.stop(() => {
+ this.initHeightList()
+ MediaController.saveFileDuration(this.fileAsset.title, this.currentTime)
+ router.replace({
+ url: "pages/index"
+ })
+ })
+ }
+ })
+
+ Column() {
+ Image(this.state == RecordState.RUNNING ? $r("app.media.ic_clock_suspend") : $r("app.media.ic_clock_play"))
+ .width($r("app.float.wh_value_32"))
+ .height($r("app.float.wh_value_32"))
+ Text(this.state == RecordState.RUNNING ? $r("app.string.pause") : $r("app.string.continue_record"))
+ .fontColor($r("app.color.text_color"))
+ .fontSize($r("app.float.font_20"))
+ .lineHeight($r("app.float.wh_value_18_5"))
+ }
+ .justifyContent(FlexAlign.Center)
+ .height($r("app.float.wh_value_64"))
+ .width($r("app.float.wh_value_64"))
+ .borderRadius($r("app.float.wh_value_64"))
+ .onClick(() => {
+ if (this.state == RecordState.RUNNING) {
+ this.recordController.pause()
+ } else {
+ this.recordController.resume()
+ }
+ })
+ }
+ .margin({ bottom: $r("app.float.wh_value_32") })
+ .width(ConfigData.WH_100_100)
+ .padding({ left: $r("app.float.distance_80"), right: $r("app.float.distance_80") })
+ .justifyContent(FlexAlign.SpaceBetween)
+ }
+ .height(ConfigData.WH_100_100)
+ .width(ConfigData.WH_100_100)
+ .backgroundColor($r("app.color.background_color"))
+ }
+}
\ No newline at end of file
diff --git a/product/phone/src/main/ets/pages/index.ets b/product/phone/src/main/ets/pages/index.ets
new file mode 100644
index 0000000000000000000000000000000000000000..fa199ba7c4baee87b7bb8e3af2af72110c18eac2
--- /dev/null
+++ b/product/phone/src/main/ets/pages/index.ets
@@ -0,0 +1,305 @@
+/**
+ * Copyright (c) 2022 HiHope Open Source Organization.
+ * 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 { ConfigData, Util } from '@ohos/common'
+import router from '@ohos.router';
+import { AudioController, AudioState, MediaController, RecordData } from '@ohos/recorder'
+import call from '@ohos.telephony.call';
+import prompt from '@ohos.prompt';
+
+@Entry
+@Component
+struct Index {
+ @State recordList: Array = []
+ @State selectedIndex: number = -1
+ @State currentTime: number = 0
+ private audioController: AudioController = new AudioController()
+ @State state: AudioState = AudioState.IDLE;
+ @State filterText: string = "";
+ private deleteRecord: RecordData = undefined
+ confirm: () => void = () => {
+ MediaController.deleteRecord(this.deleteRecord.fileAsset.uri, () => {
+ for (let i = this.recordList.length - 1;i >= 0; i--) {
+ if (this.deleteRecord.fileAsset.uri == this.recordList[i].fileAsset.uri) {
+ this.recordList.splice(i, 1)
+ }
+ }
+ })
+ }
+ dialogController: CustomDialogController = new CustomDialogController({
+ builder: DeleteRecordDialog({ deleteRecordTitle: this.deleteRecord.title, confirm: this.confirm }),
+ autoCancel: true,
+ alignment: DialogAlignment.Bottom,
+ offset: { dy: -64, dx: 0 }
+ })
+
+ async getAudios() {
+ this.recordList = await MediaController.queryAllAudios()
+ }
+
+ aboutToAppear() {
+ this.getAudios()
+ this.audioController.setTimeUpdateListener((currentTime: number) => {
+ this.currentTime = currentTime
+ })
+ this.audioController.setStateUpdateListener((state: AudioState) => {
+ if (state == AudioState.IDLE) {
+ this.selectedIndex = -1
+ }
+ this.state = state
+ })
+ }
+
+ build() {
+ Column() {
+ Row() {
+ Image($r("app.media.ic_clock_add"))
+ .width($r("app.float.wh_value_32"))
+ .height($r("app.float.wh_value_32"))
+ .margin({ right: $r("app.float.distance_32") })
+
+ Image($r("app.media.public_more"))
+ .width($r("app.float.wh_value_32"))
+ .height($r("app.float.wh_value_32"))
+ .margin({ right: $r("app.float.distance_32") })
+ }
+ .height($r("app.float.wh_value_56"))
+ .width(ConfigData.WH_100_100)
+ .justifyContent(FlexAlign.End)
+
+ Row() {
+ Text($r("app.string.title"))
+ .fontSize($r("app.float.font_48"))
+ .fontColor($r("app.color.text_color"))
+ .fontWeight(500)
+ .margin({ left: $r("app.float.distance_32") })
+ }
+ .height($r("app.float.wh_value_56"))
+ .width(ConfigData.WH_100_100)
+
+ Column() {
+ Stack({ alignContent: Alignment.Start }) {
+ TextInput({ placeholder: $r("app.string.search_recording_files") })
+ .width(ConfigData.WH_100_100)
+ .height($r("app.float.wh_value_53_5"))
+ .backgroundColor($r("app.color.white"))
+ .borderRadius($r("app.float.distance_53_5"))
+ .padding({ left: $r("app.float.distance_53_5") })
+ .onChange((value) => {
+ this.filterText = value
+ })
+ Image($r("app.media.ic_record_search"))
+ .width($r("app.float.wh_value_32"))
+ .height($r("app.float.wh_value_32"))
+ .margin({ left: $r("app.float.wh_value_16") })
+ }.width(ConfigData.WH_100_100)
+ .height($r("app.float.wh_value_53_5"))
+
+ Column() {
+ List() {
+ ForEach([...this.recordList].reverse().map((value, index) => {
+ return { i: index, data: value }
+ }), (item) => {
+ ListItem() {
+ Column() {
+ Row() {
+ Column() {
+ Text(item.data.title)
+ .fontColor($r("app.color.text_color"))
+ .fontSize($r("app.float.font_32"))
+ .width($r("app.float.wh_value_280"))
+ .lineHeight($r("app.float.wh_value_28"))
+ Text(Util.getDateFormat(item.data.date))
+ .fontColor($r("app.color.text_color"))
+ .opacity($r("app.float.opacity_6"))
+ .fontSize($r("app.float.font_28"))
+ .width($r("app.float.wh_value_280"))
+ .lineHeight($r("app.float.wh_value_25_5"))
+ }
+
+ Row() {
+ Text(Util.timeFormat(item.data.duration))
+ .fontColor($r("app.color.text_color"))
+ .opacity($r("app.float.opacity_6"))
+ .fontSize($r("app.float.font_28"))
+ .margin({ right: $r("app.float.distance_2_5") })
+ Image((item.i == this.selectedIndex && this.state == AudioState.RUNNING) ? $r("app.media.ic_record_stop") : $r("app.media.ic_record_play"))
+ .width($r("app.float.wh_value_32"))
+ .height($r("app.float.wh_value_32"))
+ }
+ }
+ .height($r("app.float.wh_value_85_5"))
+ .width(ConfigData.WH_100_100)
+ .justifyContent(FlexAlign.SpaceBetween)
+ .padding({ left: $r("app.float.distance_21_5"), right: $r("app.float.distance_21_5") })
+ .onClick(() => {
+ if (this.selectedIndex != item.i || this.state == AudioState.IDLE) {
+ this.selectedIndex = item.i
+ this.audioController.initAudioPlayer(item.data.fileAsset, () => {
+ this.audioController.play()
+ })
+ } else if (this.state == AudioState.RUNNING) {
+ this.audioController.pause()
+ } else if (this.state == AudioState.PAUSED) {
+ this.audioController.continuePlay()
+ }
+ })
+ .gesture(
+ LongPressGesture()
+ .onAction(() => {
+ this.audioController.stop()
+ this.deleteRecord = item.data
+ this.dialogController.open()
+ })
+ )
+
+ if (this.selectedIndex == item.i) {
+ Column() {
+ Slider({
+ value: this.currentTime,
+ min: 0,
+ max: item.data.duration,
+ style: SliderStyle.OutSet
+ })
+ .width($r("app.float.wh_value_433"))
+ .onChange((value: number, mode: SliderChangeMode) => {
+ if (mode == SliderChangeMode.End || mode == 3) {
+ this.currentTime = value
+ this.audioController.seek(value)
+ }
+ })
+
+ Row() {
+ Text(Util.timeFormat(this.currentTime))
+ .fontColor($r("app.color.text_color"))
+ .opacity($r("app.float.opacity_6"))
+ .fontSize($r("app.float.font_20"))
+ .margin({ right: $r("app.float.distance_2_5") })
+ Text(Util.timeFormat(item.data.duration))
+ .fontColor($r("app.color.text_color"))
+ .opacity($r("app.float.opacity_6"))
+ .fontSize($r("app.float.font_20"))
+ .margin({ right: $r("app.float.distance_2_5") })
+ }
+ .width($r("app.float.wh_value_405"))
+ .justifyContent(FlexAlign.SpaceBetween)
+
+ }.height($r("app.float.wh_value_85_5"))
+ .width(ConfigData.WH_100_100)
+ }
+ }
+ }.visibility(item.data.title.indexOf(this.filterText) >= 0 ? Visibility.Visible : Visibility.None)
+ }, item => item.i)
+
+ }.width(ConfigData.WH_100_100)
+ .backgroundColor($r("app.color.white"))
+ .borderRadius($r("app.float.wh_value_32"))
+ .divider({
+ strokeWidth: $r("app.float.wh_value_1"),
+ color: $r("app.color.shadow_color"),
+ startMargin: $r("app.float.distance_16"),
+ endMargin: $r("app.float.distance_16")
+ })
+ }.margin({ top: $r("app.float.distance_22_5") })
+ .flexShrink(1)
+
+ Blank()
+ Row() {
+ Image($r("app.media.ic_clock_record"))
+ .width($r("app.float.wh_value_32"))
+ .height($r("app.float.wh_value_32"))
+ }
+ .width($r("app.float.wh_value_64"))
+ .height($r("app.float.wh_value_64"))
+ .justifyContent(FlexAlign.Center)
+ .borderRadius($r("app.float.wh_value_32"))
+ .backgroundColor($r("app.color.white"))
+ .shadow({ radius: $r("app.float.distance_16"), color: $r("app.color.shadow_color") })
+ .margin({ bottom: $r("app.float.distance_32"), top: $r("app.float.distance_16") })
+ .onClick(async() => {
+ this.audioController.stop()
+ let hasCall = await call.hasCall()
+ if (hasCall) {
+ prompt.showToast({ message: $r('app.string.has_call_hint') })
+ } else {
+ router.replace({
+ url: "pages/RecordingPage"
+ })
+ }
+ })
+ }
+ .height(ConfigData.WH_100_100)
+ .flexShrink(1)
+ .justifyContent(FlexAlign.Center)
+ .padding({
+ left: $r("app.float.distance_16"),
+ right: $r("app.float.distance_16"),
+ top: $r("app.float.distance_6_5")
+ })
+
+ }
+ .height(ConfigData.WH_100_100)
+ .width(ConfigData.WH_100_100)
+ .backgroundColor($r("app.color.background_color"))
+ }
+}
+
+@CustomDialog
+struct DeleteRecordDialog {
+ private controller: CustomDialogController
+ private deleteRecordTitle: string
+ private confirm: () => void
+
+ build() {
+ Column() {
+ Text(this.deleteRecordTitle)
+ .fontSize($r("app.float.font_28"))
+ .fontColor($r("app.color.text_color"))
+ .opacity($r("app.float.opacity_6"))
+ .width(ConfigData.WH_100_100)
+ .margin({ left: $r("app.float.distance_16") })
+ Text($r("app.string.delete_hint"))
+ .fontSize($r("app.float.font_32"))
+ .margin($r("app.float.distance_16"))
+ .fontColor($r("app.color.text_color"))
+ Row() {
+ Text($r("app.string.cancel"))
+ .fontSize($r("app.float.font_32"))
+ .fontColor($r("app.color.text_color"))
+ .width($r("app.float.wh_value_64"))
+ .textAlign(TextAlign.Center)
+ .onClick(() => {
+ this.controller.close()
+ })
+ .fontColor($r("app.color.hint_color"))
+ Divider().vertical(true).opacity($r("app.float.opacity_4"))
+ Text($r("app.string.delete"))
+ .fontSize($r("app.float.font_32"))
+ .textAlign(TextAlign.Center)
+ .width($r("app.float.wh_value_64"))
+ .onClick(() => {
+ if (this.confirm) {
+ this.confirm()
+ this.controller.close()
+ }
+ })
+ .fontColor($r("app.color.warn_color"))
+ }.height($r("app.float.wh_value_32"))
+ .width(ConfigData.WH_100_100)
+ .padding({ left: $r("app.float.wh_value_32"), right: $r("app.float.wh_value_32") })
+ .justifyContent(FlexAlign.SpaceAround)
+ }.padding($r("app.float.distance_24"))
+ }
+}
\ No newline at end of file
diff --git a/product/phone/src/main/module.json5 b/product/phone/src/main/module.json5
new file mode 100644
index 0000000000000000000000000000000000000000..4bd4b498d60cc1c008a6ae0a718dcc857f6cd850
--- /dev/null
+++ b/product/phone/src/main/module.json5
@@ -0,0 +1,49 @@
+{
+ "module": {
+ "name": "phone",
+ "type": "entry",
+ "srcEntrance": "./ets/Application/MyAbilityStage.ts",
+ "description": "$string:phone_desc",
+ "mainElement": "MainAbility",
+ "deviceTypes": [
+ "default"
+ ],
+ "deliveryWithInstall": true,
+ "installationFree": false,
+ "pages": "$profile:main_pages",
+ "uiSyntax": "ets",
+ "abilities": [
+ {
+ "name": "MainAbility",
+ "srcEntrance": "./ets/MainAbility/MainAbility.ts",
+ "description": "$string:MainAbility_desc",
+ "icon": "$media:icon",
+ "label": "$string:MainAbility_label",
+ "startWindowIcon": "$media:icon",
+ "startWindowBackground": "$color:white",
+ "visible": true,
+ "skills": [
+ {
+ "entities": [
+ "entity.system.home"
+ ],
+ "actions": [
+ "action.system.home"
+ ]
+ }
+ ]
+ }
+ ],
+ "requestPermissions": [
+ {
+ "name": "ohos.permission.MICROPHONE"
+ },
+ {
+ "name": "ohos.permission.WRITE_MEDIA"
+ },
+ {
+ "name": "ohos.permission.READ_MEDIA"
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/product/phone/src/main/resources/base/element/color.json b/product/phone/src/main/resources/base/element/color.json
new file mode 100644
index 0000000000000000000000000000000000000000..1bbc9aa9617e97c45440e1d3d66afc1154837012
--- /dev/null
+++ b/product/phone/src/main/resources/base/element/color.json
@@ -0,0 +1,8 @@
+{
+ "color": [
+ {
+ "name": "white",
+ "value": "#FFFFFF"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/product/phone/src/main/resources/base/element/float.json b/product/phone/src/main/resources/base/element/float.json
new file mode 100644
index 0000000000000000000000000000000000000000..de34807f00ebc18ce411b6180014a8929c02ea8f
--- /dev/null
+++ b/product/phone/src/main/resources/base/element/float.json
@@ -0,0 +1,164 @@
+{
+ "float": [
+ {
+ "name": "wh_value_1",
+ "value": "1vp"
+ },
+ {
+ "name": "wh_value_2_5",
+ "value": "2.5vp"
+ },
+ {
+ "name": "wh_value_10_5",
+ "value": "10.5vp"
+ },
+ {
+ "name": "wh_value_13_5",
+ "value": "13.5vp"
+ },
+ {
+ "name": "wh_value_16",
+ "value": "16vp"
+ },
+ {
+ "name": "wh_value_18_5",
+ "value": "18.5vp"
+ },
+ {
+ "name": "wh_value_21_5",
+ "value": "21.5vp"
+ },
+ {
+ "name": "wh_value_25_5",
+ "value": "25.5vp"
+ },
+ {
+ "name": "wh_value_26_5",
+ "value": "26.5vp"
+ },
+ {
+ "name": "wh_value_28",
+ "value": "28vp"
+ },
+ {
+ "name": "wh_value_32",
+ "value": "32vp"
+ },
+ {
+ "name": "wh_value_37_5",
+ "value": "37.5vp"
+ },
+ {
+ "name": "wh_value_53_5",
+ "value": "53.5vp"
+ },
+ {
+ "name": "wh_value_56",
+ "value": "56vp"
+ },
+ {
+ "name": "wh_value_64",
+ "value": "64vp"
+ },
+ {
+ "name": "wh_value_85_5",
+ "value": "85.5vp"
+ },
+ {
+ "name": "wh_value_266_5",
+ "value": "266.5vp"
+ },
+ {
+ "name": "wh_value_280",
+ "value": "280vp"
+ },
+ {
+ "name": "wh_value_304",
+ "value": "304vp"
+ },
+ {
+ "name": "wh_value_346_5",
+ "value": "346.5vp"
+ },
+ {
+ "name": "wh_value_405",
+ "value": "405vp"
+ },
+ {
+ "name": "wh_value_433",
+ "value": "433vp"
+ },
+ {
+ "name": "font_20",
+ "value": "20px"
+ },
+ {
+ "name": "font_28",
+ "value": "28px"
+ },
+ {
+ "name": "font_32",
+ "value": "32px"
+ },
+ {
+ "name": "font_40",
+ "value": "40px"
+ },
+ {
+ "name": "font_48",
+ "value": "48px"
+ },
+ {
+ "name": "font_96",
+ "value": "96px"
+ },
+ {
+ "name": "distance_2_5",
+ "value": "2.5vp"
+ },
+ {
+ "name": "distance_6_5",
+ "value": "6.5vp"
+ },
+ {
+ "name": "distance_16",
+ "value": "16vp"
+ },
+ {
+ "name": "distance_21_5",
+ "value": "21.5vp"
+ },
+ {
+ "name": "distance_22_5",
+ "value": "22.5vp"
+ },
+ {
+ "name": "distance_24",
+ "value": "24vp"
+ },
+ {
+ "name": "distance_32",
+ "value": "32vp"
+ },
+ {
+ "name": "distance_80",
+ "value": "80vp"
+ },
+ {
+ "name": "distance_53_5",
+ "value": "53.5vp"
+ },
+ {
+ "name": "opacity_2",
+ "value": "0.2"
+ },
+ {
+ "name": "opacity_4",
+ "value": "0.4"
+ },
+ {
+ "name": "opacity_6",
+ "value": "0.6"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/product/phone/src/main/resources/base/element/string.json b/product/phone/src/main/resources/base/element/string.json
new file mode 100644
index 0000000000000000000000000000000000000000..a93532c0e4937cd663f2b45f3fd9ab4c86a92e97
--- /dev/null
+++ b/product/phone/src/main/resources/base/element/string.json
@@ -0,0 +1,16 @@
+{
+ "string": [
+ {
+ "name": "phone_desc",
+ "value": "description"
+ },
+ {
+ "name": "MainAbility_desc",
+ "value": "description"
+ },
+ {
+ "name": "MainAbility_label",
+ "value": "录音机"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/product/phone/src/main/resources/base/media/icon.png b/product/phone/src/main/resources/base/media/icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..b186c704fd1345ae527fabc8a479fc92470a8c6b
Binary files /dev/null and b/product/phone/src/main/resources/base/media/icon.png differ
diff --git a/product/phone/src/main/resources/base/profile/main_pages.json b/product/phone/src/main/resources/base/profile/main_pages.json
new file mode 100644
index 0000000000000000000000000000000000000000..8b9f2c28b5bfe9a27358b083d8d6ede9543648de
--- /dev/null
+++ b/product/phone/src/main/resources/base/profile/main_pages.json
@@ -0,0 +1,6 @@
+{
+ "src": [
+ "pages/index",
+ "pages/RecordingPage"
+ ]
+}
diff --git a/product/phone/src/ohosTest/ets/Application/TestAbilityStage.ts b/product/phone/src/ohosTest/ets/Application/TestAbilityStage.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8b2b4c83f77af465031d452094ce40d8b7d8f887
--- /dev/null
+++ b/product/phone/src/ohosTest/ets/Application/TestAbilityStage.ts
@@ -0,0 +1,22 @@
+/**
+ * Copyright (c) 2022 HiHope Open Source Organization.
+ * 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 AbilityStage from "@ohos.application.AbilityStage"
+
+export default class TestAbilityStage extends AbilityStage {
+ onCreate() {
+ console.log("[Demo] TestAbilityStage onCreate")
+ globalThis.abilityContext = this.context
+ }
+}
\ No newline at end of file
diff --git a/product/phone/src/ohosTest/ets/TestAbility/TestAbility.ts b/product/phone/src/ohosTest/ets/TestAbility/TestAbility.ts
new file mode 100644
index 0000000000000000000000000000000000000000..440afc42383533e67be796608cdb600d915fd4b4
--- /dev/null
+++ b/product/phone/src/ohosTest/ets/TestAbility/TestAbility.ts
@@ -0,0 +1,59 @@
+/**
+ * Copyright (c) 2022 HiHope Open Source Organization.
+ * 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 Ability from '@ohos.application.Ability'
+import AbilityDelegatorRegistry from '@ohos.application.abilityDelegatorRegistry'
+import { Hypium } from '@ohos/hypium'
+import testsuite from '../test/List.test'
+
+export default class TestAbility extends Ability {
+ onCreate(want, launchParam) {
+ console.log('TestAbility onCreate')
+ var abilityDelegator: any
+ abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator()
+ var abilityDelegatorArguments: any
+ abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments()
+ console.info('start run testcase!!!')
+ Hypium.hypiumTest(abilityDelegator, abilityDelegatorArguments, testsuite)
+ }
+
+ onDestroy() {
+ console.log('TestAbility onDestroy')
+ }
+
+ onWindowStageCreate(windowStage) {
+ console.log('TestAbility onWindowStageCreate')
+ windowStage.loadContent("TestAbility/pages/index", (err, data) => {
+ if (err.code) {
+ console.error('Failed to load the content. Cause:' + JSON.stringify(err));
+ return;
+ }
+ console.info('Succeeded in loading the content. Data: ' + JSON.stringify(data))
+ });
+
+ globalThis.abilityContext = this.context;
+ }
+
+ onWindowStageDestroy() {
+ console.log('TestAbility onWindowStageDestroy')
+ }
+
+ onForeground() {
+ console.log('TestAbility onForeground')
+ }
+
+ onBackground() {
+ console.log('TestAbility onBackground')
+ }
+};
\ No newline at end of file
diff --git a/product/phone/src/ohosTest/ets/TestAbility/pages/index.ets b/product/phone/src/ohosTest/ets/TestAbility/pages/index.ets
new file mode 100644
index 0000000000000000000000000000000000000000..2a02d2b4125e012d3b57589d2305193ad8aa202b
--- /dev/null
+++ b/product/phone/src/ohosTest/ets/TestAbility/pages/index.ets
@@ -0,0 +1,48 @@
+/**
+ * Copyright (c) 2022 HiHope Open Source Organization.
+ * 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 router from '@ohos.router';
+
+@Entry
+@Component
+struct Index {
+ aboutToAppear() {
+ console.info('TestAbility index aboutToAppear')
+ }
+ @State message: string = 'Hello World'
+ build() {
+ Row() {
+ Column() {
+ Text(this.message)
+ .fontSize(50)
+ .fontWeight(FontWeight.Bold)
+ Button() {
+ Text('next page')
+ .fontSize(20)
+ .fontWeight(FontWeight.Bold)
+ }.type(ButtonType.Capsule)
+ .margin({
+ top: 20
+ })
+ .backgroundColor('#0D9FFB')
+ .width('35%')
+ .height('5%')
+ .onClick(()=>{
+ })
+ }
+ .width('100%')
+ }
+ .height('100%')
+ }
+ }
\ No newline at end of file
diff --git a/product/phone/src/ohosTest/ets/TestRunner/OpenHarmonyTestRunner.ts b/product/phone/src/ohosTest/ets/TestRunner/OpenHarmonyTestRunner.ts
new file mode 100644
index 0000000000000000000000000000000000000000..66154a400bdc3c79f82d0a396f190713e7a5919f
--- /dev/null
+++ b/product/phone/src/ohosTest/ets/TestRunner/OpenHarmonyTestRunner.ts
@@ -0,0 +1,78 @@
+/**
+ * Copyright (c) 2022 HiHope Open Source Organization.
+ * 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 TestRunner from '@ohos.application.testRunner'
+import AbilityDelegatorRegistry from '@ohos.application.abilityDelegatorRegistry'
+
+var abilityDelegator = undefined
+var abilityDelegatorArguments = undefined
+
+function translateParamsToString(parameters) {
+ const keySet = new Set([
+ '-s class', '-s notClass', '-s suite', '-s it',
+ '-s level', '-s testType', '-s size', '-s timeout',
+ '-s dryRun'
+ ])
+ let targetParams = '';
+ for (const key in parameters) {
+ if (keySet.has(key)) {
+ targetParams = `${targetParams} ${key} ${parameters[key]}`
+ }
+ }
+ return targetParams.trim()
+}
+
+async function onAbilityCreateCallback() {
+ console.log("onAbilityCreateCallback");
+}
+
+async function addAbilityMonitorCallback(err: any) {
+ console.info("addAbilityMonitorCallback : " + JSON.stringify(err))
+}
+
+export default class OpenHarmonyTestRunner implements TestRunner {
+ constructor() {
+ }
+
+ onPrepare() {
+ console.info("OpenHarmonyTestRunner OnPrepare ")
+ }
+
+ async onRun() {
+ console.log('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
+ cmd += ' '+translateParamsToString(abilityDelegatorArguments.parameters)
+ var debug = abilityDelegatorArguments.parameters["-D"]
+ if (debug == 'true')
+ {
+ cmd += ' -D'
+ }
+ console.info('cmd : '+cmd)
+ abilityDelegator.executeShellCommand(cmd,
+ (err: any, d: any) => {
+ console.info('executeShellCommand : err : ' + JSON.stringify(err));
+ console.info('executeShellCommand : data : ' + d.stdResult);
+ console.info('executeShellCommand : data : ' + d.exitCode);
+ })
+ console.info('OpenHarmonyTestRunner onRun end')
+ }
+};
\ No newline at end of file
diff --git a/product/phone/src/ohosTest/ets/test/Ability.test.ets b/product/phone/src/ohosTest/ets/test/Ability.test.ets
new file mode 100644
index 0000000000000000000000000000000000000000..a18de15cf90def60208f3046e0a20bec16fd0831
--- /dev/null
+++ b/product/phone/src/ohosTest/ets/test/Ability.test.ets
@@ -0,0 +1,359 @@
+/**
+ * Copyright (c) 2022 HiHope Open Source Organization.
+ * 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, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium';
+import { Util } from '@ohos/common';
+import { RecordController, RecordState, StateController, ControlState } from '@ohos/recorder';
+
+export default function abilityTest() {
+ describe('ActsAbilityTest', function () {
+
+ it('StateControllerStart', 0, async function () {
+ let mStateController = new StateController();
+ let testState = false;
+ mStateController.setStateUpdateListener((state) => {
+ if (testState) {
+ expect(state).assertEqual(ControlState.START);
+ testState = false;
+ }
+ });
+ testState = true;
+ mStateController.start();
+ })
+
+ it('StateControllerEnd', 0, async function () {
+ let mStateController = new StateController();
+ let testState = false;
+ mStateController.setStateUpdateListener((state) => {
+ if (testState) {
+ expect(state).assertEqual(ControlState.END);
+ testState = false;
+ }
+ });
+ mStateController.start();
+ testState = true;
+ setTimeout(() => {
+ mStateController.end();
+ }, 100);
+ })
+
+ it('addZeroMin', 0, async function () {
+ let num = 0;
+ let res = Util.addZero(num);
+ expect(res).assertEqual('00');
+ })
+
+ it('addZeroMinus', 0, async function () {
+ let num = -1;
+ let res = Util.addZero(num);
+ expect(res).assertEqual('0-1');
+ })
+
+ it('addZeroMax', 0, async function () {
+ let num = 9;
+ let res = Util.addZero(num);
+ expect(res).assertEqual('09');
+ })
+
+ it('addZeroOver', 0, async function () {
+ let num = 10;
+ let res = Util.addZero(num);
+ expect(res).assertEqual('10');
+ })
+
+ it('addZeroNaN', 0, async function () {
+ let num = NaN;
+ let res = Util.addZero(num);
+ expect(res).assertEqual('NaN');
+ })
+
+ it('getTime', 0, async function () {
+ let nowDate = new Date();
+ let time = Util.getTime();
+ let res = false;
+ if (parseInt(time.substring(0, 2)) == nowDate.getHours() && parseInt(time.substring(2, 4)) == nowDate.getMinutes() && parseInt(time.substring(4, 6)) == nowDate.getSeconds()) {
+ res = true;
+ }
+ expect(res).assertTrue();
+ })
+
+ it('getDate', 0, async function () {
+ let nowDate = new Date();
+ let time = Util.getDate();
+ let res = false;
+ if (parseInt(time.substring(0, 4)) == nowDate.getFullYear() && parseInt(time.substring(4, 6)) == nowDate.getMonth() + 1 && parseInt(time.substring(6, 8)) == nowDate.getDate()) {
+ res = true;
+ }
+ expect(res).assertTrue();
+ })
+
+ it('getTimeFormatZero', 0, async function () {
+ let time = 0;
+ let res = Util.getTimeFormat(time);
+ expect(res).assertEqual('00:00:00');
+ })
+
+ it('getTimeFormat', 0, async function () {
+ let time = new Date('2022-11-11T11:11:11').getTime();
+ let res = Util.getTimeFormat(time);
+ expect(res).assertEqual('11:11:11');
+ })
+
+ it('getDateFormat', 0, async function () {
+ let time = new Date('2022-11-11T11:11:11').getTime();
+ let res = Util.getDateFormat(time);
+ expect(res).assertEqual('2022/11/11');
+ })
+
+ it('getDateFormatZero', 0, async function () {
+ let time = 0;
+ let res = Util.getDateFormat(time);
+ expect(res).assertEqual('1970/01/01');
+ })
+
+ it('timeFormatMsZero', 0, async function () {
+ let time = 0;
+ let res = Util.timeFormatMs(time);
+ expect(res).assertEqual('00:00.00');
+ })
+
+ it('timeFormatMsMinus', 0, async function () {
+ let time = -1;
+ let res = Util.timeFormatMs(time);
+ expect(res).assertEqual('00:00.00');
+ })
+
+ it('timeFormatMsMin', 0, async function () {
+ let time = 9;
+ let res = Util.timeFormatMs(time);
+ expect(res).assertEqual('00:00.00');
+ })
+
+ it('timeFormatMsMs', 0, async function () {
+ let time = 10;
+ let res = Util.timeFormatMs(time);
+ expect(res).assertEqual('00:00.01');
+ })
+
+ it('timeFormatMsSecond', 0, async function () {
+ let time = 1000;
+ let res = Util.timeFormatMs(time);
+ expect(res).assertEqual('00:01.00');
+ })
+
+ it('timeFormatMsMinute', 0, async function () {
+ let time = 60 * 1000;
+ let res = Util.timeFormatMs(time);
+ expect(res).assertEqual('01:00.00');
+ })
+
+ it('timeFormatMsHour', 0, async function () {
+ let time = 60 * 60 * 1000;
+ let res = Util.timeFormatMs(time);
+ expect(res).assertEqual('01:00:00');
+ })
+
+ it('timeFormatMsDay', 0, async function () {
+ let time = 24 * 60 * 60 * 1000;
+ let res = Util.timeFormatMs(time);
+ expect(res).assertEqual('00:00.00');
+ })
+
+ it('timeFormatMinus', 0, async function () {
+ let time = -1;
+ let res = Util.timeFormat(time);
+ expect(res).assertEqual('');
+ })
+
+ it('timeFormatZeroH', 0, async function () {
+ let time = 0;
+ let res = Util.timeFormat(time);
+ expect(res).assertEqual(`00:00:00`);
+ })
+
+ it('timeFormatZero', 0, async function () {
+ let time = 0;
+ let res = Util.timeFormat(time, false);
+ expect(res).assertEqual(`00:00`);
+ })
+
+ it('timeFormatMinH', 0, async function () {
+ let time = 999;
+ let res = Util.timeFormat(time);
+ expect(res).assertEqual(`00:00:00`);
+ })
+
+ it('timeFormatMin', 0, async function () {
+ let time = 999;
+ let res = Util.timeFormat(time, false);
+ expect(res).assertEqual(`00:00`);
+ })
+
+ it('timeFormatSecondH', 0, async function () {
+ let time = 1000;
+ let res = Util.timeFormat(time);
+ expect(res).assertEqual(`00:00:01`);
+ })
+
+ it('timeFormatSecond', 0, async function () {
+ let time = 1000;
+ let res = Util.timeFormat(time, false);
+ expect(res).assertEqual(`00:01`);
+ })
+
+ it('timeFormatMinuteH', 0, async function () {
+ let time = 60 * 1000;
+ let res = Util.timeFormat(time);
+ expect(res).assertEqual(`00:01:00`);
+ })
+
+ it('timeFormatMinute', 0, async function () {
+ let time = 60 * 1000;
+ let res = Util.timeFormat(time, false);
+ expect(res).assertEqual(`01:00`);
+ })
+
+ it('timeFormatHour', 0, async function () {
+ let time = 60 * 60 * 1000;
+ let res = Util.timeFormat(time);
+ expect(res).assertEqual(`01:00:00`);
+ })
+
+ it('timeFormatDay', 0, async function () {
+ let time = 24 * 60 * 60 * 1000;
+ let res = Util.timeFormat(time, false);
+ expect(res).assertEqual(`00:00`);
+ })
+
+ it('RecordControllerStartTime', 0, async function () {
+ let mRecordController = new RecordController();
+ mRecordController.initAudioRecorder();
+ let testState = false;
+ mRecordController.setStateUpdateListener((state) => {
+ if (testState) {
+ expect(state).assertEqual(RecordState.RUNNING);
+ testState = false;
+ }
+ });
+ testState = true;
+ mRecordController.startTime();
+ })
+
+ it('RecordStartTime', 0, async function () {
+ let mRecordController = new RecordController();
+ mRecordController.initAudioRecorder();
+ let testState = false;
+ mRecordController.setTimeUpdateListener((time) => {
+ if (testState) {
+ expect(time > 0).assertTrue();
+ testState = false;
+ }
+ });
+ testState = true;
+ mRecordController.startTime();
+ })
+
+ it('RecordControllerPause', 0, async function () {
+ let mRecordController = new RecordController();
+ mRecordController.initAudioRecorder();
+ let testState = false;
+ mRecordController.setStateUpdateListener((state) => {
+ if (testState) {
+ expect(state).assertEqual(RecordState.PAUSED);
+ testState = false;
+ }
+ });
+ mRecordController.startTime();
+ testState = true;
+ setTimeout(() => {
+ mRecordController.pause();
+ }, 100);
+ })
+
+ it('RecordControllerResume', 0, async function () {
+ let mRecordController = new RecordController();
+ mRecordController.initAudioRecorder();
+ let testState = false;
+ mRecordController.setStateUpdateListener((state) => {
+ if (testState) {
+ expect(state).assertEqual(RecordState.RUNNING);
+ testState = false;
+ }
+ });
+ mRecordController.startTime();
+ mRecordController.pause();
+ testState = true;
+ setTimeout(() => {
+ mRecordController.resume();
+ }, 100);
+ })
+
+ it('RecordControllerPauseStop', 0, async function () {
+ let mRecordController = new RecordController();
+ mRecordController.initAudioRecorder();
+ let testState = false;
+ mRecordController.setStateUpdateListener((state) => {
+ if (testState) {
+ expect(state).assertEqual(RecordState.IDLE);
+ testState = false;
+ }
+ });
+ mRecordController.startTime();
+ mRecordController.pause();
+ setTimeout(() => {
+ mRecordController.stop(() => {
+ testState = true;
+ });
+ }, 100);
+ })
+
+ it('RecordControllerStop', 0, async function () {
+ let mRecordController = new RecordController();
+ mRecordController.initAudioRecorder();
+ let testState = false;
+ mRecordController.setStateUpdateListener((state) => {
+ if (testState) {
+ expect(state).assertEqual(RecordState.IDLE);
+ testState = false;
+ }
+ });
+ mRecordController.startTime();
+ setTimeout(() => {
+ mRecordController.stop(() => {
+ testState = true;
+ });
+ }, 100);
+ })
+
+ it('RecordStop', 0, async function () {
+ let mRecordController = new RecordController();
+ mRecordController.initAudioRecorder();
+ let testState = false;
+ mRecordController.setTimeUpdateListener((time) => {
+ if (testState) {
+ expect(time).assertEqual(0);
+ testState = false;
+ }
+ });
+ mRecordController.startTime();
+ setTimeout(() => {
+ mRecordController.stop(() => {
+ testState = true;
+ });
+ }, 100);
+ })
+
+ })
+}
\ No newline at end of file
diff --git a/product/phone/src/ohosTest/ets/test/List.test.ets b/product/phone/src/ohosTest/ets/test/List.test.ets
new file mode 100644
index 0000000000000000000000000000000000000000..fd9764189c525a7ae769839735e6e775c3778077
--- /dev/null
+++ b/product/phone/src/ohosTest/ets/test/List.test.ets
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2022 HiHope Open Source Organization.
+ * 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 abilityTest from './Ability.test'
+
+export default function testsuite() {
+ abilityTest()
+}
\ No newline at end of file
diff --git a/product/phone/src/ohosTest/module.json5 b/product/phone/src/ohosTest/module.json5
new file mode 100644
index 0000000000000000000000000000000000000000..809f37570e1045d13f747149ba5d7ae859b34346
--- /dev/null
+++ b/product/phone/src/ohosTest/module.json5
@@ -0,0 +1,38 @@
+{
+ "module": {
+ "name": "phone_test",
+ "type": "feature",
+ "srcEntrance": "./ets/Application/TestAbilityStage.ts",
+ "description": "$string:entry_test_desc",
+ "mainElement": "TestAbility",
+ "deviceTypes": [
+ "default"
+ ],
+ "deliveryWithInstall": true,
+ "installationFree": false,
+ "pages": "$profile:test_pages",
+ "uiSyntax": "ets",
+ "abilities": [
+ {
+ "name": "TestAbility",
+ "srcEntrance": "./ets/TestAbility/TestAbility.ts",
+ "description": "$string:TestAbility_desc",
+ "icon": "$media:icon",
+ "label": "$string:TestAbility_label",
+ "visible": true,
+ "startWindowIcon": "$media:icon",
+ "startWindowBackground": "$color:white",
+ "skills": [
+ {
+ "actions": [
+ "action.system.home"
+ ],
+ "entities": [
+ "entity.system.home"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/product/phone/src/ohosTest/resources/base/element/color.json b/product/phone/src/ohosTest/resources/base/element/color.json
new file mode 100644
index 0000000000000000000000000000000000000000..1bbc9aa9617e97c45440e1d3d66afc1154837012
--- /dev/null
+++ b/product/phone/src/ohosTest/resources/base/element/color.json
@@ -0,0 +1,8 @@
+{
+ "color": [
+ {
+ "name": "white",
+ "value": "#FFFFFF"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/product/phone/src/ohosTest/resources/base/element/string.json b/product/phone/src/ohosTest/resources/base/element/string.json
new file mode 100644
index 0000000000000000000000000000000000000000..36d4230c53e9f5a07ae343ad8dc9808341975e3b
--- /dev/null
+++ b/product/phone/src/ohosTest/resources/base/element/string.json
@@ -0,0 +1,16 @@
+{
+ "string": [
+ {
+ "name": "entry_test_desc",
+ "value": "test ability description"
+ },
+ {
+ "name": "TestAbility_desc",
+ "value": "the test ability"
+ },
+ {
+ "name": "TestAbility_label",
+ "value": "test label"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/product/phone/src/ohosTest/resources/base/media/icon.png b/product/phone/src/ohosTest/resources/base/media/icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c
Binary files /dev/null and b/product/phone/src/ohosTest/resources/base/media/icon.png differ
diff --git a/product/phone/src/ohosTest/resources/base/profile/test_pages.json b/product/phone/src/ohosTest/resources/base/profile/test_pages.json
new file mode 100644
index 0000000000000000000000000000000000000000..fcef82b4dfc18e28106ff9ecd1c8b48ec74d18a4
--- /dev/null
+++ b/product/phone/src/ohosTest/resources/base/profile/test_pages.json
@@ -0,0 +1,5 @@
+{
+ "src": [
+ "TestAbility/pages/index"
+ ]
+}
diff --git a/product/tablet/.gitignore b/product/tablet/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..5a6ba80fa3d9498a23ae8ae7d9518f8743fa8a96
--- /dev/null
+++ b/product/tablet/.gitignore
@@ -0,0 +1,4 @@
+/node_modules
+/.preview
+/build
+/.cxx
\ No newline at end of file
diff --git a/product/tablet/build-profile.json5 b/product/tablet/build-profile.json5
new file mode 100644
index 0000000000000000000000000000000000000000..f8f03407f77914b43168aeca6bb0929efd6700b4
--- /dev/null
+++ b/product/tablet/build-profile.json5
@@ -0,0 +1,13 @@
+{
+ "apiType": 'stageMode',
+ "buildOption": {
+ },
+ "targets": [
+ {
+ "name": "default"
+ },
+ {
+ "name": "ohosTest",
+ }
+ ]
+}
\ No newline at end of file
diff --git a/product/tablet/hvigorfile.js b/product/tablet/hvigorfile.js
new file mode 100644
index 0000000000000000000000000000000000000000..d7720ee6a7aad5c617d1fd2f6fc8c87067bfa32c
--- /dev/null
+++ b/product/tablet/hvigorfile.js
@@ -0,0 +1,2 @@
+// Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently.
+module.exports = require('@ohos/hvigor-ohos-plugin').hapTasks
diff --git a/product/tablet/package-lock.json b/product/tablet/package-lock.json
new file mode 100644
index 0000000000000000000000000000000000000000..a45b41de70ebfabae4fa1f8b882a81d993137fd3
--- /dev/null
+++ b/product/tablet/package-lock.json
@@ -0,0 +1,22 @@
+{
+ "name": "tablet",
+ "version": "1.0.0",
+ "lockfileVersion": 1,
+ "requires": true,
+ "dependencies": {
+ "@ohos/common": {
+ "version": "file:../../common"
+ },
+ "@ohos/recorder": {
+ "version": "file:../../feature/recorder",
+ "requires": {
+ "@ohos/common": "file:../../common"
+ },
+ "dependencies": {
+ "@ohos/common": {
+ "version": "file:../../common"
+ }
+ }
+ }
+ }
+}
diff --git a/product/tablet/package.json b/product/tablet/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..5195036991e533f6949ea0822aa8ffe0098035cc
--- /dev/null
+++ b/product/tablet/package.json
@@ -0,0 +1,17 @@
+{
+ "license": "ISC",
+ "devDependencies": {},
+ "name": "tablet",
+ "ohos": {
+ "org": "huawei",
+ "directoryLevel": "module",
+ "buildTool": "hvigor"
+ },
+ "description": "example description",
+ "repository": {},
+ "version": "1.0.0",
+ "dependencies": {
+ "@ohos/recorder": "file:../../feature/recorder",
+ "@ohos/common": "file:../../common"
+ }
+}
diff --git a/product/tablet/src/main/ets/Application/MyAbilityStage.ts b/product/tablet/src/main/ets/Application/MyAbilityStage.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0a9efc1ed476ee2dfdca792a1632b37b99922670
--- /dev/null
+++ b/product/tablet/src/main/ets/Application/MyAbilityStage.ts
@@ -0,0 +1,22 @@
+/**
+ * Copyright (c) 2022 HiHope Open Source Organization.
+ * 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 AbilityStage from "@ohos.application.AbilityStage"
+
+export default class MyAbilityStage extends AbilityStage {
+ onCreate() {
+ console.log("[Demo] MyAbilityStage onCreate")
+ }
+}
\ No newline at end of file
diff --git a/product/tablet/src/main/ets/MainAbility/MainAbility.ts b/product/tablet/src/main/ets/MainAbility/MainAbility.ts
new file mode 100644
index 0000000000000000000000000000000000000000..67501c1826d9d010967d72a2428c02442e5d1423
--- /dev/null
+++ b/product/tablet/src/main/ets/MainAbility/MainAbility.ts
@@ -0,0 +1,61 @@
+/**
+ * Copyright (c) 2022 HiHope Open Source Organization.
+ * 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 Ability from '@ohos.application.Ability'
+import Notification from '@ohos.notification';
+
+export default class MainAbility extends Ability {
+ onCreate(want, launchParam) {
+ console.log("[Demo] MainAbility onCreate")
+ globalThis.abilityWant = want;
+ globalThis.abilityContext = this.context;
+ }
+
+ onDestroy() {
+ Notification.cancelAll()
+ console.log("[Demo] MainAbility onDestroy")
+ }
+
+ onWindowStageCreate(windowStage) {
+ // Main window is created, set main page for this ability
+ console.log("[Demo] MainAbility onWindowStageCreate")
+ this.context.requestPermissionsFromUser(['ohos.permission.MICROPHONE', 'ohos.permission.WRITE_MEDIA', 'ohos.permission.READ_MEDIA']).then(() => {
+ Notification.requestEnableNotification().then(() => {
+ windowStage.loadContent("pages/index", (err, data) => {
+ if (err.code) {
+ console.error('Failed to load the content. Cause:' + JSON.stringify(err));
+ return;
+ }
+ console.info('Succeeded in loading the content. Data: ' + JSON.stringify(data))
+ });
+ })
+ })
+ }
+
+ onWindowStageDestroy() {
+ // Main window is destroyed, release UI related resources
+ console.log("[Demo] MainAbility onWindowStageDestroy")
+ }
+
+ onForeground() {
+ // Ability has brought to foreground
+ console.log("[Demo] MainAbility onForeground")
+ }
+
+ onBackground() {
+ // Ability has back to background
+ console.log("[Demo] MainAbility onBackground")
+ }
+};
diff --git a/product/tablet/src/main/ets/pages/RecordingPage.ets b/product/tablet/src/main/ets/pages/RecordingPage.ets
new file mode 100644
index 0000000000000000000000000000000000000000..f352c8b972a6eaf623dd618f9447fc647f984cb6
--- /dev/null
+++ b/product/tablet/src/main/ets/pages/RecordingPage.ets
@@ -0,0 +1,239 @@
+/**
+ * Copyright (c) 2022 HiHope Open Source Organization.
+ * 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 { ConfigData, Util } from '@ohos/common'
+import { RecordController, MediaController, RecordState } from '@ohos/recorder'
+import mediaLibrary from '@ohos.multimedia.mediaLibrary'
+import { StateController } from '@ohos/recorder'
+import prompt from '@ohos.prompt';
+import call from '@ohos.telephony.call';
+
+const TIME_INTERVAL_S = 2;
+const TIME_COUNT = 2 * 6 + 1;
+const VOICE_FREQUENCY = 60;
+
+@Component
+export struct RecordingPage {
+ @State currentTime: number = 0
+ @State state: RecordState = RecordState.IDLE
+ private recordController: RecordController = new RecordController()
+ @State fileAsset: mediaLibrary.FileAsset = undefined
+ private stateController = new StateController()
+ heightList: Array = []
+
+ async aboutToAppear() {
+ let that = this
+ this.recordController.setTimeUpdateListener((currentTimeMs: number) => {
+ if (Math.floor(currentTimeMs / (TIME_COUNT / 2 * 1000 * TIME_INTERVAL_S / VOICE_FREQUENCY)) - Math.floor(this.currentTime / (TIME_COUNT / 2 * 1000 * TIME_INTERVAL_S / VOICE_FREQUENCY)) == 1) {
+ that.heightList.splice(0, 1)
+ that.heightList.push(this.recordController.getSoundVolume())
+ }
+ this.currentTime = currentTimeMs
+ })
+ this.recordController.setStateUpdateListener((state) => {
+ this.state = state
+ })
+ this.initHeightList()
+ }
+
+ initHeightList() {
+ this.heightList = []
+ for (let index = 0; index < VOICE_FREQUENCY; index++) {
+ this.heightList.push(0)
+ }
+ }
+
+ aboutToDisappear() {
+ if (this.state != RecordState.IDLE) {
+ this.recordController.stop()
+ }
+ }
+
+ build() {
+ Column() {
+ Column() {
+ Row() {
+ Image($r("app.media.ic_clock_enhance"))
+ .width($r("app.float.wh_value_12"))
+ .height($r("app.float.wh_value_12"))
+ .margin({ right: $r("app.float.distance_12") })
+ }
+ .height($r("app.float.wh_value_28"))
+ .width(ConfigData.WH_100_100)
+ .justifyContent(FlexAlign.End)
+
+ Stack({ alignContent: Alignment.Top }) {
+ Column() {
+ Row() {
+ ForEach(Array.from(Array(TIME_COUNT + 1).keys()), (item) => {
+ Column() {
+ Text(Util.timeFormat(((item - (TIME_COUNT - 1) / 2) * 1000 * TIME_INTERVAL_S + Math.floor(this.currentTime / (1000 * TIME_INTERVAL_S)) * (1000 * TIME_INTERVAL_S)), false))
+ .textAlign(TextAlign.Center)
+ .fontColor($r("app.color.text_color"))
+ .fontSize($r("app.float.font_10"))
+ .opacity($r("app.float.opacity_6"))
+ Row() {
+ ForEach(Array.from(Array(5).keys()), (scaleItem) => {
+ Rect()
+ .width($r("app.float.wh_value_1"))
+ .fill($r("app.color.text_color"))
+ .height(scaleItem == 2 ? $r("app.float.wh_value_10") : $r("app.float.wh_value_5"))
+ .opacity(scaleItem == 2 ? $r("app.float.opacity_4") : $r("app.float.opacity_2"))
+ }, scaleItem => scaleItem)
+ }.width(ConfigData.WH_100_100)
+ .alignItems(VerticalAlign.Top)
+ .height($r("app.float.wh_value_10"))
+ .justifyContent(FlexAlign.SpaceAround)
+ }.width((100 / TIME_COUNT) + "%")
+ }, item => item)
+ }
+ .offset({
+ x: (-(100 / TIME_COUNT) * (this.currentTime % (TIME_INTERVAL_S * 1000) / (TIME_INTERVAL_S * 1000))) + "%"
+ })
+
+ Row() {
+ Row() {
+ Row() {
+ ForEach(Array.from(Array(VOICE_FREQUENCY).keys()), (item) => {
+ Rect()
+ .width($r("app.float.wh_value_1"))
+ .fill($r("app.color.text_color"))
+ .height(this.heightList[item] * 0.5)
+ .opacity($r("app.float.opacity_2"))
+ }, item => item)
+ }.width(ConfigData.WH_100_100)
+ .justifyContent(FlexAlign.SpaceAround)
+ .offset({
+ x: (-(100 / VOICE_FREQUENCY) * (this.currentTime % (TIME_COUNT / 2 * 1000 * TIME_INTERVAL_S / VOICE_FREQUENCY) / (TIME_COUNT / 2 * 1000 * TIME_INTERVAL_S / VOICE_FREQUENCY) - 1)) + "%"
+ })
+ }
+ .clip(true)
+ .width(ConfigData.WH_50_100)
+ .height(ConfigData.WH_100_100)
+ }
+ .justifyContent(FlexAlign.Start)
+ .width(ConfigData.WH_100_100)
+ .height($r("app.float.wh_value_100"))
+ .backgroundColor($r("app.color.shadow_color"))
+ }
+
+ Column() {
+ Rect()
+ .height($r("app.float.wh_value_114"))
+ .width($r("app.float.wh_value_1"))
+ .fill($r("app.color.pointer_color"))
+ Circle()
+ .height($r("app.float.wh_value_4"))
+ .width($r("app.float.wh_value_4"))
+ .fill($r("app.color.pointer_color"))
+ }
+ .margin({ top: $r("app.float.distance_6") })
+ }
+ .width(ConfigData.WH_100_100)
+ .clip(true)
+ .height($r("app.float.wh_value_128"))
+ .margin({ top: $r("app.float.wh_value_72") })
+
+ Text(Util.timeFormatMs(this.currentTime))
+ .fontColor($r("app.color.text_color"))
+ .fontSize($r("app.float.font_48"))
+ .lineHeight($r("app.float.wh_value_32"))
+ Text($r("app.string.predict_hint"))
+ .fontColor($r("app.color.text_color"))
+ .fontSize($r("app.float.font_14"))
+ .opacity($r("app.float.opacity_6"))
+ .visibility(this.currentTime > (10 * 1000) ? Visibility.Hidden : Visibility.Visible)
+ }
+ .visibility(this.state == RecordState.IDLE ? Visibility.Hidden : Visibility.Visible)
+
+ Blank()
+ Row() {
+ Column() {
+ Image($r("app.media.ic_clock_mark"))
+ .width($r("app.float.wh_value_12"))
+ .height($r("app.float.wh_value_12"))
+ Text($r("app.string.mark"))
+ .fontColor($r("app.color.text_color"))
+ .fontSize($r("app.float.font_10"))
+ .lineHeight($r("app.float.wh_value_8"))
+ }
+ .justifyContent(FlexAlign.Center)
+ .height($r("app.float.wh_value_24"))
+ .width($r("app.float.wh_value_24"))
+ .borderRadius($r("app.float.wh_value_24"))
+ .visibility(this.state == RecordState.IDLE ? Visibility.Hidden : Visibility.Visible)
+
+ Column() {
+ Image(this.state == RecordState.IDLE ? $r("app.media.ic_clock_record") : $r("app.media.ic_clock_stop"))
+ .width($r("app.float.wh_value_12"))
+ .height($r("app.float.wh_value_12"))
+ }
+ .justifyContent(FlexAlign.Center)
+ .height($r("app.float.wh_value_24"))
+ .width($r("app.float.wh_value_24"))
+ .borderRadius($r("app.float.wh_value_24"))
+ .backgroundColor($r("app.color.white"))
+ .onClick(async () => {
+ if (this.state == RecordState.IDLE) {
+ let hasCall = await call.hasCall()
+ if (hasCall) {
+ prompt.showToast({ message: $r('app.string.has_call_hint') })
+ } else {
+ this.stateController.start()
+ this.recordController.initAudioRecorder()
+ this.fileAsset = await MediaController.createAudioFile()
+ let fd = await this.fileAsset.open('Rw')
+ this.recordController.startRecorder(`fd://${fd}`)
+ }
+ } else {
+ this.recordController.stop(() => {
+ this.initHeightList()
+ MediaController.saveFileDuration(this.fileAsset.title, this.currentTime)
+ this.stateController.end()
+ })
+ }
+ })
+
+ Column() {
+ Image(this.state == RecordState.RUNNING ? $r("app.media.ic_clock_suspend") : $r("app.media.ic_clock_play"))
+ .width($r("app.float.wh_value_12"))
+ .height($r("app.float.wh_value_12"))
+ Text(this.state == RecordState.RUNNING ? $r("app.string.pause") : $r("app.string.continue_record"))
+ .fontColor($r("app.color.text_color"))
+ .fontSize($r("app.float.font_10"))
+ .lineHeight($r("app.float.wh_value_8"))
+ }
+ .justifyContent(FlexAlign.Center)
+ .height($r("app.float.wh_value_24"))
+ .width($r("app.float.wh_value_24"))
+ .borderRadius($r("app.float.wh_value_24"))
+ .onClick(() => {
+ if (this.state == RecordState.RUNNING) {
+ this.recordController.pause()
+ } else {
+ this.recordController.resume()
+ }
+ })
+ .visibility(this.state == RecordState.IDLE ? Visibility.Hidden : Visibility.Visible)
+ }
+ .margin({ bottom: $r("app.float.wh_value_12") })
+ .width(ConfigData.WH_100_100)
+ .padding({ left: $r("app.float.distance_135"), right: $r("app.float.distance_135") })
+ .justifyContent(FlexAlign.SpaceBetween)
+ }
+ .height(ConfigData.WH_100_100)
+ .width(ConfigData.WH_100_100)
+ }
+}
diff --git a/product/tablet/src/main/ets/pages/index.ets b/product/tablet/src/main/ets/pages/index.ets
new file mode 100644
index 0000000000000000000000000000000000000000..8fa920ff4ceab54d393a1a90f49c00a1a2e3f68d
--- /dev/null
+++ b/product/tablet/src/main/ets/pages/index.ets
@@ -0,0 +1,53 @@
+/**
+ * Copyright (c) 2022 HiHope Open Source Organization.
+ * 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 { ConfigData } from '@ohos/common'
+import { recordList } from './recordList'
+import { RecordingPage } from './RecordingPage'
+import { StateController } from '@ohos/recorder'
+
+@Entry
+@Component
+struct Index {
+ private stateController = new StateController()
+
+ build() {
+ Row() {
+ Column() {
+ recordList({ stateController: this.stateController })
+ }
+ .padding({
+ left: $r("app.float.distance_12"),
+ right: $r("app.float.distance_12"),
+ top: $r("app.float.distance_8"),
+ bottom: $r("app.float.distance_8"),
+ })
+ .width($r("app.float.wh_value_256"))
+ .height(ConfigData.WH_100_100)
+
+ Divider().vertical(true).opacity($r("app.float.opacity_2"))
+ Column() {
+ RecordingPage({ stateController: this.stateController })
+ }
+ .height(ConfigData.WH_100_100)
+ .width($r("app.float.wh_value_383"))
+
+ }
+ .backgroundColor($r("app.color.background_color"))
+ .width(ConfigData.WH_100_100)
+ .height(ConfigData.WH_100_100)
+ }
+}
+
diff --git a/product/tablet/src/main/ets/pages/recordList.ets b/product/tablet/src/main/ets/pages/recordList.ets
new file mode 100644
index 0000000000000000000000000000000000000000..9a5f73b760f40c0d643f874bd108680b470394d2
--- /dev/null
+++ b/product/tablet/src/main/ets/pages/recordList.ets
@@ -0,0 +1,292 @@
+/**
+ * Copyright (c) 2022 HiHope Open Source Organization.
+ * 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 { ConfigData, Util } from '@ohos/common'
+import { AudioController, AudioState, MediaController, RecordData } from '@ohos/recorder'
+import { StateController, ControlState } from '@ohos/recorder'
+
+@Component
+export struct recordList {
+ @State recordList: Array = []
+ @State selectedIndex: number = -1
+ @State currentTime: number = 0
+ private audioController: AudioController = new AudioController()
+ @State state: AudioState = AudioState.IDLE;
+ @State filterText: string = "";
+ private deleteRecord: RecordData = undefined
+ private stateController = new StateController()
+ @State disable: boolean = false
+ confirm: () => void = () => {
+ MediaController.deleteRecord(this.deleteRecord.fileAsset.uri, () => {
+ for (let i = this.recordList.length - 1;i >= 0; i--) {
+ if (this.deleteRecord.fileAsset.uri == this.recordList[i].fileAsset.uri) {
+ this.recordList.splice(i, 1)
+ }
+ }
+ })
+ }
+ dialogController: CustomDialogController = new CustomDialogController({
+ builder: DeleteRecordDialog({ deleteRecordTitle: this.deleteRecord.title, confirm: this.confirm }),
+ autoCancel: true,
+ alignment: DialogAlignment.Bottom,
+ offset: { dy: -16, dx: 0 }
+ })
+
+ async getAudios() {
+ this.recordList = await MediaController.queryAllAudios()
+ }
+
+ aboutToAppear() {
+ this.getAudios()
+ this.stateController.setStateUpdateListener((state) => {
+ if (state == ControlState.START) {
+ this.disable = true
+ this.audioController.stop()
+ } else {
+ this.disable = false
+ this.getAudios()
+ }
+ })
+ this.audioController.setTimeUpdateListener((currentTime: number) => {
+ this.currentTime = currentTime
+ })
+ this.audioController.setStateUpdateListener((state: AudioState) => {
+ if (state == AudioState.IDLE) {
+ this.selectedIndex = -1
+ }
+ this.state = state
+ })
+ }
+
+ build() {
+ Column() {
+ Row() {
+ Image($r("app.media.ic_clock_add"))
+ .width($r("app.float.wh_value_12"))
+ .height($r("app.float.wh_value_12"))
+ .margin({ right: $r("app.float.distance_12") })
+
+ Image($r("app.media.public_more"))
+ .width($r("app.float.wh_value_12"))
+ .height($r("app.float.wh_value_12"))
+ .margin({ right: $r("app.float.distance_12") })
+ }
+ .height($r("app.float.wh_value_28"))
+ .width(ConfigData.WH_100_100)
+ .justifyContent(FlexAlign.End)
+
+ Row() {
+ Text($r("app.string.title"))
+ .fontSize($r("app.float.font_30"))
+ .fontColor($r("app.color.text_color"))
+ .fontWeight(500)
+ .margin({ left: $r("app.float.distance_12") })
+ }
+ .height($r("app.float.wh_value_21"))
+ .width(ConfigData.WH_100_100)
+
+ Column() {
+ Stack({ alignContent: Alignment.Start }) {
+ TextInput({ placeholder: $r("app.string.search_recording_files") })
+ .placeholderFont({ size: $r("app.float.font_16") })
+ .width(ConfigData.WH_100_100)
+ .height($r("app.float.wh_value_21"))
+ .backgroundColor($r("app.color.white"))
+ .borderRadius($r("app.float.distance_21"))
+ .padding({ left: $r("app.float.distance_21") })
+ .onChange((value) => {
+ this.filterText = value
+ })
+ Image($r("app.media.ic_record_search"))
+ .width($r("app.float.wh_value_12"))
+ .height($r("app.float.wh_value_12"))
+ .margin({ left: $r("app.float.distance_6") })
+ }.width(ConfigData.WH_100_100)
+ .height($r("app.float.wh_value_21"))
+
+ Column() {
+ List() {
+ ForEach([...this.recordList].reverse().map((value, index) => {
+ return { i: index, data: value }
+ }), (item) => {
+ ListItem() {
+ Column() {
+ Row() {
+ Column() {
+ Text(item.data.title)
+ .fontColor($r("app.color.text_color"))
+ .fontSize($r("app.float.font_16"))
+ .width($r("app.float.wh_value_105"))
+ .lineHeight($r("app.float.wh_value_10_5"))
+ Text(Util.getDateFormat(item.data.date))
+ .fontColor($r("app.color.text_color"))
+ .opacity($r("app.float.opacity_6"))
+ .fontSize($r("app.float.font_14"))
+ .width($r("app.float.wh_value_105"))
+ .lineHeight($r("app.float.wh_value_9_5"))
+ }
+
+ Row() {
+ Text(Util.timeFormat(item.data.duration))
+ .fontColor($r("app.color.text_color"))
+ .opacity($r("app.float.opacity_6"))
+ .fontSize($r("app.float.font_14"))
+ .margin({ right: $r("app.float.distance_2") })
+ Image((item.i == this.selectedIndex && this.state == AudioState.RUNNING) ? $r("app.media.ic_record_stop") : $r("app.media.ic_record_play"))
+ .width($r("app.float.wh_value_12"))
+ .height($r("app.float.wh_value_12"))
+ }
+ }
+ .height($r("app.float.wh_value_32"))
+ .width(ConfigData.WH_100_100)
+ .justifyContent(FlexAlign.SpaceBetween)
+ .padding({ left: $r("app.float.distance_12"), right: $r("app.float.distance_12") })
+ .onClick(() => {
+ if (this.selectedIndex != item.i || this.state == AudioState.IDLE) {
+ this.selectedIndex = item.i
+ this.audioController.initAudioPlayer(item.data.fileAsset, () => {
+ this.audioController.play()
+ })
+ } else if (this.state == AudioState.RUNNING) {
+ this.audioController.pause()
+ } else if (this.state == AudioState.PAUSED) {
+ this.audioController.continuePlay()
+ }
+ })
+ .gesture(
+ LongPressGesture()
+ .onAction(() => {
+ this.audioController.stop()
+ this.deleteRecord = item.data
+ this.dialogController.open()
+ })
+ )
+
+ if (this.selectedIndex == item.i) {
+ Column() {
+ Slider({
+ value: this.currentTime,
+ min: 0,
+ max: item.data.duration,
+ style: SliderStyle.OutSet
+ })
+ .trackThickness($r("app.float.wh_value_2"))
+ .height($r("app.float.wh_value_8"))
+ .width($r("app.float.wh_value_232"))
+ .onChange((value: number, mode: SliderChangeMode) => {
+ if (mode == SliderChangeMode.End || mode == 3) {
+ this.currentTime = value
+ this.audioController.seek(value)
+ }
+ })
+ .offset({ y: $r("app.float.wh_value_minus_19") })
+
+ Row() {
+ Text(Util.timeFormat(this.currentTime))
+ .fontColor($r("app.color.text_color"))
+ .opacity($r("app.float.opacity_6"))
+ .fontSize($r("app.float.font_10"))
+ .margin({ right: $r("app.float.distance_2") })
+ Text(Util.timeFormat(item.data.duration))
+ .fontColor($r("app.color.text_color"))
+ .opacity($r("app.float.opacity_6"))
+ .fontSize($r("app.float.font_10"))
+ .margin({ right: $r("app.float.distance_2") })
+ }
+ .margin({ top: $r("app.float.distance_6") })
+ .width($r("app.float.wh_value_208"))
+ .justifyContent(FlexAlign.SpaceBetween)
+ }
+ .padding({ top: $r("app.float.distance_4") })
+ .height($r("app.float.wh_value_32"))
+ .width(ConfigData.WH_100_100)
+ }
+ }
+ }.visibility(item.data.title.indexOf(this.filterText) >= 0 ? Visibility.Visible : Visibility.None)
+ }, item => item.i)
+
+ }
+ .width(ConfigData.WH_100_100)
+ .backgroundColor($r("app.color.white"))
+ .borderRadius($r("app.float.wh_value_12"))
+ .divider({
+ strokeWidth: $r("app.float.wh_value_0_5"),
+ color: $r("app.color.shadow_color"),
+ startMargin: $r("app.float.distance_12"),
+ endMargin: $r("app.float.distance_12")
+ })
+ .enabled(!this.disable)
+ .opacity(this.disable ? $r("app.float.opacity_6") : $r("app.float.opacity_full"))
+ }
+ .flexShrink(1)
+ .margin({ top: $r("app.float.distance_8") })
+ }
+ .flexShrink(1)
+ .margin({ top: $r("app.float.distance_6"), bottom: $r("app.float.distance_12") })
+ }
+ .width(ConfigData.WH_100_100)
+ .height(ConfigData.WH_100_100)
+ }
+}
+
+@CustomDialog
+struct DeleteRecordDialog {
+ private controller: CustomDialogController
+ private deleteRecordTitle: string
+ private confirm: () => void
+
+ build() {
+ Column() {
+ Text(this.deleteRecordTitle)
+ .fontSize($r("app.float.font_14"))
+ .fontColor($r("app.color.text_color"))
+ .opacity($r("app.float.opacity_6"))
+ .width(ConfigData.WH_100_100)
+ .margin({ left: $r("app.float.distance_6") })
+ Text($r("app.string.delete_hint"))
+ .fontSize($r("app.float.font_16"))
+ .margin($r("app.float.distance_6"))
+ .fontColor($r("app.color.text_color"))
+ Row() {
+ Text($r("app.string.cancel"))
+ .fontSize($r("app.float.font_16"))
+ .fontColor($r("app.color.text_color"))
+ .width($r("app.float.wh_value_24"))
+ .textAlign(TextAlign.Center)
+ .onClick(() => {
+ this.controller.close()
+ })
+ .fontColor($r("app.color.hint_color"))
+ Divider().vertical(true).opacity($r("app.float.opacity_4"))
+ Text($r("app.string.delete"))
+ .fontSize($r("app.float.font_16"))
+ .textAlign(TextAlign.Center)
+ .width($r("app.float.wh_value_24"))
+ .onClick(() => {
+ if (this.confirm) {
+ this.confirm()
+ this.controller.close()
+ }
+ })
+ .fontColor($r("app.color.warn_color"))
+ }
+ .height($r("app.float.wh_value_12"))
+ .width(ConfigData.WH_100_100)
+ .padding({ left: $r("app.float.wh_value_12"), right: $r("app.float.wh_value_12") })
+ .justifyContent(FlexAlign.SpaceAround)
+ }
+ .padding($r("app.float.distance_12"))
+ }
+}
\ No newline at end of file
diff --git a/product/tablet/src/main/module.json5 b/product/tablet/src/main/module.json5
new file mode 100644
index 0000000000000000000000000000000000000000..6a30a379752a06bb87b982e16b0c00d685259c05
--- /dev/null
+++ b/product/tablet/src/main/module.json5
@@ -0,0 +1,49 @@
+{
+ "module": {
+ "name": "tablet",
+ "type": "entry",
+ "srcEntrance": "./ets/Application/MyAbilityStage.ts",
+ "description": "$string:tablet_desc",
+ "mainElement": "MainAbility",
+ "deviceTypes": [
+ "tablet"
+ ],
+ "deliveryWithInstall": true,
+ "installationFree": false,
+ "pages": "$profile:main_pages",
+ "uiSyntax": "ets",
+ "abilities": [
+ {
+ "name": "MainAbility",
+ "srcEntrance": "./ets/MainAbility/MainAbility.ts",
+ "description": "$string:MainAbility_desc",
+ "icon": "$media:icon",
+ "label": "$string:MainAbility_label",
+ "startWindowIcon": "$media:icon",
+ "startWindowBackground": "$color:white",
+ "visible": true,
+ "skills": [
+ {
+ "entities": [
+ "entity.system.home"
+ ],
+ "actions": [
+ "action.system.home"
+ ]
+ }
+ ]
+ }
+ ],
+ "requestPermissions": [
+ {
+ "name": "ohos.permission.MICROPHONE"
+ },
+ {
+ "name": "ohos.permission.WRITE_MEDIA"
+ },
+ {
+ "name": "ohos.permission.READ_MEDIA"
+ }
+ ]
+ }
+}
\ No newline at end of file
diff --git a/product/tablet/src/main/resources/base/element/color.json b/product/tablet/src/main/resources/base/element/color.json
new file mode 100644
index 0000000000000000000000000000000000000000..1bbc9aa9617e97c45440e1d3d66afc1154837012
--- /dev/null
+++ b/product/tablet/src/main/resources/base/element/color.json
@@ -0,0 +1,8 @@
+{
+ "color": [
+ {
+ "name": "white",
+ "value": "#FFFFFF"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/product/tablet/src/main/resources/base/element/float.json b/product/tablet/src/main/resources/base/element/float.json
new file mode 100644
index 0000000000000000000000000000000000000000..85e2fd0a48f5ac42fa88572828eeb0f27377a392
--- /dev/null
+++ b/product/tablet/src/main/resources/base/element/float.json
@@ -0,0 +1,164 @@
+{
+ "float": [
+ {
+ "name": "wh_value_0_5",
+ "value": "0.5vp"
+ },
+ {
+ "name": "wh_value_1",
+ "value": "1vp"
+ },
+ {
+ "name": "wh_value_2",
+ "value": "2vp"
+ },
+ {
+ "name": "wh_value_4",
+ "value": "4vp"
+ },
+ {
+ "name": "wh_value_5",
+ "value": "5vp"
+ },
+ {
+ "name": "wh_value_8",
+ "value": "8vp"
+ },
+ {
+ "name": "wh_value_10",
+ "value": "10vp"
+ },
+ {
+ "name": "wh_value_9_5",
+ "value": "9.5vp"
+ },
+ {
+ "name": "wh_value_10_5",
+ "value": "10.5vp"
+ },
+ {
+ "name": "wh_value_12",
+ "value": "12vp"
+ },
+ {
+ "name": "wh_value_minus_19",
+ "value": "-19vp"
+ },
+ {
+ "name": "wh_value_21",
+ "value": "21vp"
+ },
+ {
+ "name": "wh_value_24",
+ "value": "24vp"
+ },
+ {
+ "name": "wh_value_28",
+ "value": "28vp"
+ },
+ {
+ "name": "wh_value_32",
+ "value": "32vp"
+ },
+ {
+ "name": "wh_value_72",
+ "value": "72vp"
+ },
+ {
+ "name": "wh_value_100",
+ "value": "100vp"
+ },
+ {
+ "name": "wh_value_105",
+ "value": "105vp"
+ },
+ {
+ "name": "wh_value_114",
+ "value": "114vp"
+ },
+ {
+ "name": "wh_value_128",
+ "value": "128vp"
+ },
+ {
+ "name": "wh_value_208",
+ "value": "208vp"
+ },
+ {
+ "name": "wh_value_232",
+ "value": "232vp"
+ },
+ {
+ "name": "wh_value_256",
+ "value": "256vp"
+ },
+ {
+ "name": "wh_value_383",
+ "value": "383vp"
+ },
+ {
+ "name": "distance_2",
+ "value": "2vp"
+ },
+ {
+ "name": "distance_4",
+ "value": "4vp"
+ },
+ {
+ "name": "distance_6",
+ "value": "6vp"
+ },
+ {
+ "name": "distance_8",
+ "value": "8vp"
+ },
+ {
+ "name": "font_10",
+ "value": "10px"
+ },
+ {
+ "name": "font_14",
+ "value": "14px"
+ },
+ {
+ "name": "font_16",
+ "value": "16px"
+ },
+ {
+ "name": "font_30",
+ "value": "30px"
+ },
+ {
+ "name": "font_48",
+ "value": "48px"
+ },
+ {
+ "name": "distance_12",
+ "value": "12vp"
+ },
+ {
+ "name": "distance_21",
+ "value": "21vp"
+ },
+ {
+ "name": "distance_135",
+ "value": "135vp"
+ },
+ {
+ "name": "opacity_2",
+ "value": "0.2"
+ },
+ {
+ "name": "opacity_4",
+ "value": "0.4"
+ },
+ {
+ "name": "opacity_6",
+ "value": "0.6"
+ },
+ {
+ "name": "opacity_full",
+ "value": "1"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/product/tablet/src/main/resources/base/element/string.json b/product/tablet/src/main/resources/base/element/string.json
new file mode 100644
index 0000000000000000000000000000000000000000..f52f06094e3cf5ef9e7c03278701f438456a19ee
--- /dev/null
+++ b/product/tablet/src/main/resources/base/element/string.json
@@ -0,0 +1,16 @@
+{
+ "string": [
+ {
+ "name": "tablet_desc",
+ "value": "description"
+ },
+ {
+ "name": "MainAbility_desc",
+ "value": "description"
+ },
+ {
+ "name": "MainAbility_label",
+ "value": "录音机"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/product/tablet/src/main/resources/base/media/icon.png b/product/tablet/src/main/resources/base/media/icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..b186c704fd1345ae527fabc8a479fc92470a8c6b
Binary files /dev/null and b/product/tablet/src/main/resources/base/media/icon.png differ
diff --git a/product/tablet/src/main/resources/base/profile/main_pages.json b/product/tablet/src/main/resources/base/profile/main_pages.json
new file mode 100644
index 0000000000000000000000000000000000000000..feec276e105eeb8d621c20aaf838f318b0a94150
--- /dev/null
+++ b/product/tablet/src/main/resources/base/profile/main_pages.json
@@ -0,0 +1,5 @@
+{
+ "src": [
+ "pages/index"
+ ]
+}
diff --git a/product/tablet/src/ohosTest/ets/Application/TestAbilityStage.ts b/product/tablet/src/ohosTest/ets/Application/TestAbilityStage.ts
new file mode 100644
index 0000000000000000000000000000000000000000..fedbf385606de8abde4622b8e3da4539b9a173ff
--- /dev/null
+++ b/product/tablet/src/ohosTest/ets/Application/TestAbilityStage.ts
@@ -0,0 +1,22 @@
+/**
+ * Copyright (c) 2022 HiHope Open Source Organization.
+ * 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 AbilityStage from "@ohos.application.AbilityStage"
+
+export default class TestAbilityStage extends AbilityStage {
+ onCreate() {
+ console.log("[Demo] TestAbilityStage onCreate")
+ globalThis.abilityContext = this.context;
+ }
+}
\ No newline at end of file
diff --git a/product/tablet/src/ohosTest/ets/TestAbility/TestAbility.ts b/product/tablet/src/ohosTest/ets/TestAbility/TestAbility.ts
new file mode 100644
index 0000000000000000000000000000000000000000..440afc42383533e67be796608cdb600d915fd4b4
--- /dev/null
+++ b/product/tablet/src/ohosTest/ets/TestAbility/TestAbility.ts
@@ -0,0 +1,59 @@
+/**
+ * Copyright (c) 2022 HiHope Open Source Organization.
+ * 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 Ability from '@ohos.application.Ability'
+import AbilityDelegatorRegistry from '@ohos.application.abilityDelegatorRegistry'
+import { Hypium } from '@ohos/hypium'
+import testsuite from '../test/List.test'
+
+export default class TestAbility extends Ability {
+ onCreate(want, launchParam) {
+ console.log('TestAbility onCreate')
+ var abilityDelegator: any
+ abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator()
+ var abilityDelegatorArguments: any
+ abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments()
+ console.info('start run testcase!!!')
+ Hypium.hypiumTest(abilityDelegator, abilityDelegatorArguments, testsuite)
+ }
+
+ onDestroy() {
+ console.log('TestAbility onDestroy')
+ }
+
+ onWindowStageCreate(windowStage) {
+ console.log('TestAbility onWindowStageCreate')
+ windowStage.loadContent("TestAbility/pages/index", (err, data) => {
+ if (err.code) {
+ console.error('Failed to load the content. Cause:' + JSON.stringify(err));
+ return;
+ }
+ console.info('Succeeded in loading the content. Data: ' + JSON.stringify(data))
+ });
+
+ globalThis.abilityContext = this.context;
+ }
+
+ onWindowStageDestroy() {
+ console.log('TestAbility onWindowStageDestroy')
+ }
+
+ onForeground() {
+ console.log('TestAbility onForeground')
+ }
+
+ onBackground() {
+ console.log('TestAbility onBackground')
+ }
+};
\ No newline at end of file
diff --git a/product/tablet/src/ohosTest/ets/TestAbility/pages/index.ets b/product/tablet/src/ohosTest/ets/TestAbility/pages/index.ets
new file mode 100644
index 0000000000000000000000000000000000000000..2a02d2b4125e012d3b57589d2305193ad8aa202b
--- /dev/null
+++ b/product/tablet/src/ohosTest/ets/TestAbility/pages/index.ets
@@ -0,0 +1,48 @@
+/**
+ * Copyright (c) 2022 HiHope Open Source Organization.
+ * 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 router from '@ohos.router';
+
+@Entry
+@Component
+struct Index {
+ aboutToAppear() {
+ console.info('TestAbility index aboutToAppear')
+ }
+ @State message: string = 'Hello World'
+ build() {
+ Row() {
+ Column() {
+ Text(this.message)
+ .fontSize(50)
+ .fontWeight(FontWeight.Bold)
+ Button() {
+ Text('next page')
+ .fontSize(20)
+ .fontWeight(FontWeight.Bold)
+ }.type(ButtonType.Capsule)
+ .margin({
+ top: 20
+ })
+ .backgroundColor('#0D9FFB')
+ .width('35%')
+ .height('5%')
+ .onClick(()=>{
+ })
+ }
+ .width('100%')
+ }
+ .height('100%')
+ }
+ }
\ No newline at end of file
diff --git a/product/tablet/src/ohosTest/ets/TestRunner/OpenHarmonyTestRunner.ts b/product/tablet/src/ohosTest/ets/TestRunner/OpenHarmonyTestRunner.ts
new file mode 100644
index 0000000000000000000000000000000000000000..66154a400bdc3c79f82d0a396f190713e7a5919f
--- /dev/null
+++ b/product/tablet/src/ohosTest/ets/TestRunner/OpenHarmonyTestRunner.ts
@@ -0,0 +1,78 @@
+/**
+ * Copyright (c) 2022 HiHope Open Source Organization.
+ * 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 TestRunner from '@ohos.application.testRunner'
+import AbilityDelegatorRegistry from '@ohos.application.abilityDelegatorRegistry'
+
+var abilityDelegator = undefined
+var abilityDelegatorArguments = undefined
+
+function translateParamsToString(parameters) {
+ const keySet = new Set([
+ '-s class', '-s notClass', '-s suite', '-s it',
+ '-s level', '-s testType', '-s size', '-s timeout',
+ '-s dryRun'
+ ])
+ let targetParams = '';
+ for (const key in parameters) {
+ if (keySet.has(key)) {
+ targetParams = `${targetParams} ${key} ${parameters[key]}`
+ }
+ }
+ return targetParams.trim()
+}
+
+async function onAbilityCreateCallback() {
+ console.log("onAbilityCreateCallback");
+}
+
+async function addAbilityMonitorCallback(err: any) {
+ console.info("addAbilityMonitorCallback : " + JSON.stringify(err))
+}
+
+export default class OpenHarmonyTestRunner implements TestRunner {
+ constructor() {
+ }
+
+ onPrepare() {
+ console.info("OpenHarmonyTestRunner OnPrepare ")
+ }
+
+ async onRun() {
+ console.log('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
+ cmd += ' '+translateParamsToString(abilityDelegatorArguments.parameters)
+ var debug = abilityDelegatorArguments.parameters["-D"]
+ if (debug == 'true')
+ {
+ cmd += ' -D'
+ }
+ console.info('cmd : '+cmd)
+ abilityDelegator.executeShellCommand(cmd,
+ (err: any, d: any) => {
+ console.info('executeShellCommand : err : ' + JSON.stringify(err));
+ console.info('executeShellCommand : data : ' + d.stdResult);
+ console.info('executeShellCommand : data : ' + d.exitCode);
+ })
+ console.info('OpenHarmonyTestRunner onRun end')
+ }
+};
\ No newline at end of file
diff --git a/product/tablet/src/ohosTest/ets/test/Ability.test.ets b/product/tablet/src/ohosTest/ets/test/Ability.test.ets
new file mode 100644
index 0000000000000000000000000000000000000000..a18de15cf90def60208f3046e0a20bec16fd0831
--- /dev/null
+++ b/product/tablet/src/ohosTest/ets/test/Ability.test.ets
@@ -0,0 +1,359 @@
+/**
+ * Copyright (c) 2022 HiHope Open Source Organization.
+ * 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, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium';
+import { Util } from '@ohos/common';
+import { RecordController, RecordState, StateController, ControlState } from '@ohos/recorder';
+
+export default function abilityTest() {
+ describe('ActsAbilityTest', function () {
+
+ it('StateControllerStart', 0, async function () {
+ let mStateController = new StateController();
+ let testState = false;
+ mStateController.setStateUpdateListener((state) => {
+ if (testState) {
+ expect(state).assertEqual(ControlState.START);
+ testState = false;
+ }
+ });
+ testState = true;
+ mStateController.start();
+ })
+
+ it('StateControllerEnd', 0, async function () {
+ let mStateController = new StateController();
+ let testState = false;
+ mStateController.setStateUpdateListener((state) => {
+ if (testState) {
+ expect(state).assertEqual(ControlState.END);
+ testState = false;
+ }
+ });
+ mStateController.start();
+ testState = true;
+ setTimeout(() => {
+ mStateController.end();
+ }, 100);
+ })
+
+ it('addZeroMin', 0, async function () {
+ let num = 0;
+ let res = Util.addZero(num);
+ expect(res).assertEqual('00');
+ })
+
+ it('addZeroMinus', 0, async function () {
+ let num = -1;
+ let res = Util.addZero(num);
+ expect(res).assertEqual('0-1');
+ })
+
+ it('addZeroMax', 0, async function () {
+ let num = 9;
+ let res = Util.addZero(num);
+ expect(res).assertEqual('09');
+ })
+
+ it('addZeroOver', 0, async function () {
+ let num = 10;
+ let res = Util.addZero(num);
+ expect(res).assertEqual('10');
+ })
+
+ it('addZeroNaN', 0, async function () {
+ let num = NaN;
+ let res = Util.addZero(num);
+ expect(res).assertEqual('NaN');
+ })
+
+ it('getTime', 0, async function () {
+ let nowDate = new Date();
+ let time = Util.getTime();
+ let res = false;
+ if (parseInt(time.substring(0, 2)) == nowDate.getHours() && parseInt(time.substring(2, 4)) == nowDate.getMinutes() && parseInt(time.substring(4, 6)) == nowDate.getSeconds()) {
+ res = true;
+ }
+ expect(res).assertTrue();
+ })
+
+ it('getDate', 0, async function () {
+ let nowDate = new Date();
+ let time = Util.getDate();
+ let res = false;
+ if (parseInt(time.substring(0, 4)) == nowDate.getFullYear() && parseInt(time.substring(4, 6)) == nowDate.getMonth() + 1 && parseInt(time.substring(6, 8)) == nowDate.getDate()) {
+ res = true;
+ }
+ expect(res).assertTrue();
+ })
+
+ it('getTimeFormatZero', 0, async function () {
+ let time = 0;
+ let res = Util.getTimeFormat(time);
+ expect(res).assertEqual('00:00:00');
+ })
+
+ it('getTimeFormat', 0, async function () {
+ let time = new Date('2022-11-11T11:11:11').getTime();
+ let res = Util.getTimeFormat(time);
+ expect(res).assertEqual('11:11:11');
+ })
+
+ it('getDateFormat', 0, async function () {
+ let time = new Date('2022-11-11T11:11:11').getTime();
+ let res = Util.getDateFormat(time);
+ expect(res).assertEqual('2022/11/11');
+ })
+
+ it('getDateFormatZero', 0, async function () {
+ let time = 0;
+ let res = Util.getDateFormat(time);
+ expect(res).assertEqual('1970/01/01');
+ })
+
+ it('timeFormatMsZero', 0, async function () {
+ let time = 0;
+ let res = Util.timeFormatMs(time);
+ expect(res).assertEqual('00:00.00');
+ })
+
+ it('timeFormatMsMinus', 0, async function () {
+ let time = -1;
+ let res = Util.timeFormatMs(time);
+ expect(res).assertEqual('00:00.00');
+ })
+
+ it('timeFormatMsMin', 0, async function () {
+ let time = 9;
+ let res = Util.timeFormatMs(time);
+ expect(res).assertEqual('00:00.00');
+ })
+
+ it('timeFormatMsMs', 0, async function () {
+ let time = 10;
+ let res = Util.timeFormatMs(time);
+ expect(res).assertEqual('00:00.01');
+ })
+
+ it('timeFormatMsSecond', 0, async function () {
+ let time = 1000;
+ let res = Util.timeFormatMs(time);
+ expect(res).assertEqual('00:01.00');
+ })
+
+ it('timeFormatMsMinute', 0, async function () {
+ let time = 60 * 1000;
+ let res = Util.timeFormatMs(time);
+ expect(res).assertEqual('01:00.00');
+ })
+
+ it('timeFormatMsHour', 0, async function () {
+ let time = 60 * 60 * 1000;
+ let res = Util.timeFormatMs(time);
+ expect(res).assertEqual('01:00:00');
+ })
+
+ it('timeFormatMsDay', 0, async function () {
+ let time = 24 * 60 * 60 * 1000;
+ let res = Util.timeFormatMs(time);
+ expect(res).assertEqual('00:00.00');
+ })
+
+ it('timeFormatMinus', 0, async function () {
+ let time = -1;
+ let res = Util.timeFormat(time);
+ expect(res).assertEqual('');
+ })
+
+ it('timeFormatZeroH', 0, async function () {
+ let time = 0;
+ let res = Util.timeFormat(time);
+ expect(res).assertEqual(`00:00:00`);
+ })
+
+ it('timeFormatZero', 0, async function () {
+ let time = 0;
+ let res = Util.timeFormat(time, false);
+ expect(res).assertEqual(`00:00`);
+ })
+
+ it('timeFormatMinH', 0, async function () {
+ let time = 999;
+ let res = Util.timeFormat(time);
+ expect(res).assertEqual(`00:00:00`);
+ })
+
+ it('timeFormatMin', 0, async function () {
+ let time = 999;
+ let res = Util.timeFormat(time, false);
+ expect(res).assertEqual(`00:00`);
+ })
+
+ it('timeFormatSecondH', 0, async function () {
+ let time = 1000;
+ let res = Util.timeFormat(time);
+ expect(res).assertEqual(`00:00:01`);
+ })
+
+ it('timeFormatSecond', 0, async function () {
+ let time = 1000;
+ let res = Util.timeFormat(time, false);
+ expect(res).assertEqual(`00:01`);
+ })
+
+ it('timeFormatMinuteH', 0, async function () {
+ let time = 60 * 1000;
+ let res = Util.timeFormat(time);
+ expect(res).assertEqual(`00:01:00`);
+ })
+
+ it('timeFormatMinute', 0, async function () {
+ let time = 60 * 1000;
+ let res = Util.timeFormat(time, false);
+ expect(res).assertEqual(`01:00`);
+ })
+
+ it('timeFormatHour', 0, async function () {
+ let time = 60 * 60 * 1000;
+ let res = Util.timeFormat(time);
+ expect(res).assertEqual(`01:00:00`);
+ })
+
+ it('timeFormatDay', 0, async function () {
+ let time = 24 * 60 * 60 * 1000;
+ let res = Util.timeFormat(time, false);
+ expect(res).assertEqual(`00:00`);
+ })
+
+ it('RecordControllerStartTime', 0, async function () {
+ let mRecordController = new RecordController();
+ mRecordController.initAudioRecorder();
+ let testState = false;
+ mRecordController.setStateUpdateListener((state) => {
+ if (testState) {
+ expect(state).assertEqual(RecordState.RUNNING);
+ testState = false;
+ }
+ });
+ testState = true;
+ mRecordController.startTime();
+ })
+
+ it('RecordStartTime', 0, async function () {
+ let mRecordController = new RecordController();
+ mRecordController.initAudioRecorder();
+ let testState = false;
+ mRecordController.setTimeUpdateListener((time) => {
+ if (testState) {
+ expect(time > 0).assertTrue();
+ testState = false;
+ }
+ });
+ testState = true;
+ mRecordController.startTime();
+ })
+
+ it('RecordControllerPause', 0, async function () {
+ let mRecordController = new RecordController();
+ mRecordController.initAudioRecorder();
+ let testState = false;
+ mRecordController.setStateUpdateListener((state) => {
+ if (testState) {
+ expect(state).assertEqual(RecordState.PAUSED);
+ testState = false;
+ }
+ });
+ mRecordController.startTime();
+ testState = true;
+ setTimeout(() => {
+ mRecordController.pause();
+ }, 100);
+ })
+
+ it('RecordControllerResume', 0, async function () {
+ let mRecordController = new RecordController();
+ mRecordController.initAudioRecorder();
+ let testState = false;
+ mRecordController.setStateUpdateListener((state) => {
+ if (testState) {
+ expect(state).assertEqual(RecordState.RUNNING);
+ testState = false;
+ }
+ });
+ mRecordController.startTime();
+ mRecordController.pause();
+ testState = true;
+ setTimeout(() => {
+ mRecordController.resume();
+ }, 100);
+ })
+
+ it('RecordControllerPauseStop', 0, async function () {
+ let mRecordController = new RecordController();
+ mRecordController.initAudioRecorder();
+ let testState = false;
+ mRecordController.setStateUpdateListener((state) => {
+ if (testState) {
+ expect(state).assertEqual(RecordState.IDLE);
+ testState = false;
+ }
+ });
+ mRecordController.startTime();
+ mRecordController.pause();
+ setTimeout(() => {
+ mRecordController.stop(() => {
+ testState = true;
+ });
+ }, 100);
+ })
+
+ it('RecordControllerStop', 0, async function () {
+ let mRecordController = new RecordController();
+ mRecordController.initAudioRecorder();
+ let testState = false;
+ mRecordController.setStateUpdateListener((state) => {
+ if (testState) {
+ expect(state).assertEqual(RecordState.IDLE);
+ testState = false;
+ }
+ });
+ mRecordController.startTime();
+ setTimeout(() => {
+ mRecordController.stop(() => {
+ testState = true;
+ });
+ }, 100);
+ })
+
+ it('RecordStop', 0, async function () {
+ let mRecordController = new RecordController();
+ mRecordController.initAudioRecorder();
+ let testState = false;
+ mRecordController.setTimeUpdateListener((time) => {
+ if (testState) {
+ expect(time).assertEqual(0);
+ testState = false;
+ }
+ });
+ mRecordController.startTime();
+ setTimeout(() => {
+ mRecordController.stop(() => {
+ testState = true;
+ });
+ }, 100);
+ })
+
+ })
+}
\ No newline at end of file
diff --git a/product/tablet/src/ohosTest/ets/test/List.test.ets b/product/tablet/src/ohosTest/ets/test/List.test.ets
new file mode 100644
index 0000000000000000000000000000000000000000..fd9764189c525a7ae769839735e6e775c3778077
--- /dev/null
+++ b/product/tablet/src/ohosTest/ets/test/List.test.ets
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2022 HiHope Open Source Organization.
+ * 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 abilityTest from './Ability.test'
+
+export default function testsuite() {
+ abilityTest()
+}
\ No newline at end of file
diff --git a/product/tablet/src/ohosTest/module.json5 b/product/tablet/src/ohosTest/module.json5
new file mode 100644
index 0000000000000000000000000000000000000000..1e6c0dc1a9e06b7c4eb24cbe797d74fe26d3ba3d
--- /dev/null
+++ b/product/tablet/src/ohosTest/module.json5
@@ -0,0 +1,38 @@
+{
+ "module": {
+ "name": "tablet_test",
+ "type": "feature",
+ "srcEntrance": "./ets/Application/TestAbilityStage.ts",
+ "description": "$string:entry_test_desc",
+ "mainElement": "TestAbility",
+ "deviceTypes": [
+ "tablet"
+ ],
+ "deliveryWithInstall": true,
+ "installationFree": false,
+ "pages": "$profile:test_pages",
+ "uiSyntax": "ets",
+ "abilities": [
+ {
+ "name": "TestAbility",
+ "srcEntrance": "./ets/TestAbility/TestAbility.ts",
+ "description": "$string:TestAbility_desc",
+ "icon": "$media:icon",
+ "label": "$string:TestAbility_label",
+ "visible": true,
+ "startWindowIcon": "$media:icon",
+ "startWindowBackground": "$color:white",
+ "skills": [
+ {
+ "actions": [
+ "action.system.home"
+ ],
+ "entities": [
+ "entity.system.home"
+ ]
+ }
+ ]
+ }
+ ]
+ }
+}
diff --git a/product/tablet/src/ohosTest/resources/base/element/color.json b/product/tablet/src/ohosTest/resources/base/element/color.json
new file mode 100644
index 0000000000000000000000000000000000000000..1bbc9aa9617e97c45440e1d3d66afc1154837012
--- /dev/null
+++ b/product/tablet/src/ohosTest/resources/base/element/color.json
@@ -0,0 +1,8 @@
+{
+ "color": [
+ {
+ "name": "white",
+ "value": "#FFFFFF"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/product/tablet/src/ohosTest/resources/base/element/string.json b/product/tablet/src/ohosTest/resources/base/element/string.json
new file mode 100644
index 0000000000000000000000000000000000000000..36d4230c53e9f5a07ae343ad8dc9808341975e3b
--- /dev/null
+++ b/product/tablet/src/ohosTest/resources/base/element/string.json
@@ -0,0 +1,16 @@
+{
+ "string": [
+ {
+ "name": "entry_test_desc",
+ "value": "test ability description"
+ },
+ {
+ "name": "TestAbility_desc",
+ "value": "the test ability"
+ },
+ {
+ "name": "TestAbility_label",
+ "value": "test label"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/product/tablet/src/ohosTest/resources/base/media/icon.png b/product/tablet/src/ohosTest/resources/base/media/icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c
Binary files /dev/null and b/product/tablet/src/ohosTest/resources/base/media/icon.png differ
diff --git a/product/tablet/src/ohosTest/resources/base/profile/test_pages.json b/product/tablet/src/ohosTest/resources/base/profile/test_pages.json
new file mode 100644
index 0000000000000000000000000000000000000000..fcef82b4dfc18e28106ff9ecd1c8b48ec74d18a4
--- /dev/null
+++ b/product/tablet/src/ohosTest/resources/base/profile/test_pages.json
@@ -0,0 +1,5 @@
+{
+ "src": [
+ "TestAbility/pages/index"
+ ]
+}