diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index 73f69e0..0000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,8 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
-# Datasource local storage ignored files
-/dataSources/
-/dataSources.local.xml
-# Editor-based HTTP Client requests
-/httpRequests/
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
deleted file mode 100644
index a1757ae..0000000
--- a/.idea/compiler.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/libraries/Dart_SDK.xml b/.idea/libraries/Dart_SDK.xml
deleted file mode 100644
index 7d5efcd..0000000
--- a/.idea/libraries/Dart_SDK.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/libraries/Flutter_Plugins.xml b/.idea/libraries/Flutter_Plugins.xml
deleted file mode 100644
index 53449da..0000000
--- a/.idea/libraries/Flutter_Plugins.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index 8fc5e91..0000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
deleted file mode 100644
index 101e2c7..0000000
--- a/.idea/modules.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/runConfigurations/example_lib_main_dart.xml b/.idea/runConfigurations/example_lib_main_dart.xml
deleted file mode 100644
index 5fd9159..0000000
--- a/.idea/runConfigurations/example_lib_main_dart.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 94a25f7..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dbfd18c..5897269 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,6 @@
+# 2.1.1
+* feat: 适配 OHOS (HarmonyOS) 平台
+
# 2.1.0
* fix: #4
diff --git a/README.md b/README.md
index d31300c..2d4e1d0 100644
--- a/README.md
+++ b/README.md
@@ -2,10 +2,10 @@
[](https://pub.dartlang.org/packages/fk_user_agent)
[]()
-[]()
+[]()
[](/LICENSE)
-Retrieve Android/iOS device user agents in Flutter.
+Retrieve Android/iOS/OHOS device user agents in Flutter.
### Example user-agents:
@@ -13,6 +13,7 @@ Retrieve Android/iOS device user agents in Flutter.
| ------ | ---------- | ------------------ |
| iOS | CFNetwork/897.15 Darwin/17.5.0 (iPhone/6s iOS/11.3) | Mozilla/5.0 (iPhone; CPU iPhone OS 11_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E217 |
| Android | Dalvik/2.1.0 (Linux; U; Android 5.1.1; Android SDK built for x86 Build/LMY48X) | Mozilla/5.0 (Linux; Android 5.1.1; Android SDK built for x86 Build/LMY48X) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/39.0.0.0 Mobile Safari/537.36 |
+| OHOS | HarmonyOS/6.0.0 (phone; HUAWEI BLK-AL80; OpenHarmony-6.0.2.130) | Mozilla/5.0 (Phone; OpenHarmony 6.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/132.0.0.0 Safari/537.36 ArkWeb/6.0.0.130 Mobile |
### Additionally:
@@ -40,6 +41,18 @@ Android version returns:
- applicationBuildNumber
- packageUserAgent
+OHOS (HarmonyOS) version returns:
+- systemName
+- systemVersion
+- packageName
+- shortPackageName
+- applicationName
+- applicationVersion
+- applicationBuildNumber
+- packageUserAgent
+- userAgent
+- webViewUserAgent
+
### Credits 👍
Based of https://github.com/j0j00/flutter_user_agent
\ No newline at end of file
diff --git a/example/ohos/.gitignore b/example/ohos/.gitignore
new file mode 100644
index 0000000..6ca13b3
--- /dev/null
+++ b/example/ohos/.gitignore
@@ -0,0 +1,19 @@
+/node_modules
+/oh_modules
+/local.properties
+/.idea
+**/build
+/.hvigor
+.cxx
+/.clangd
+/.clang-format
+/.clang-tidy
+**/.test
+*.har
+**/BuildProfile.ets
+**/oh-package-lock.json5
+
+**/src/main/resources/rawfile/flutter_assets/
+**/libs/arm64-v8a/libapp.so
+**/libs/arm64-v8a/libflutter.so
+**/libs/arm64-v8a/libvmservice_snapshot.so
diff --git a/example/ohos/AppScope/app.json5 b/example/ohos/AppScope/app.json5
new file mode 100644
index 0000000..831ef95
--- /dev/null
+++ b/example/ohos/AppScope/app.json5
@@ -0,0 +1,10 @@
+{
+ "app": {
+ "bundleName": "io.flutterfastkit.fk_user_agent.fk_user_agent_ohos_example",
+ "vendor": "example",
+ "versionCode": 1000000,
+ "versionName": "1.0.0",
+ "icon": "$media:app_icon",
+ "label": "$string:app_name"
+ }
+}
\ No newline at end of file
diff --git a/example/ohos/AppScope/resources/base/element/string.json b/example/ohos/AppScope/resources/base/element/string.json
new file mode 100644
index 0000000..c428e52
--- /dev/null
+++ b/example/ohos/AppScope/resources/base/element/string.json
@@ -0,0 +1,8 @@
+{
+ "string": [
+ {
+ "name": "app_name",
+ "value": "fk_user_agent_ohos_example"
+ }
+ ]
+}
diff --git a/example/ohos/AppScope/resources/base/media/app_icon.png b/example/ohos/AppScope/resources/base/media/app_icon.png
new file mode 100644
index 0000000..ce307a8
Binary files /dev/null and b/example/ohos/AppScope/resources/base/media/app_icon.png differ
diff --git a/example/ohos/build-profile.json5 b/example/ohos/build-profile.json5
new file mode 100644
index 0000000..244f72b
--- /dev/null
+++ b/example/ohos/build-profile.json5
@@ -0,0 +1,32 @@
+{
+ "app": {
+ "signingConfigs": [],
+ "products": [
+ {
+ "name": "default",
+ "signingConfig": "default",
+ "compatibleSdkVersion": "5.0.0(12)",
+ "runtimeOS": "HarmonyOS",
+ "buildOption": {
+ "strictMode": {
+ "useNormalizedOHMUrl": true
+ }
+ }
+ }
+ ]
+ },
+ "modules": [
+ {
+ "name": "entry",
+ "srcPath": "./entry",
+ "targets": [
+ {
+ "name": "default",
+ "applyToProducts": [
+ "default"
+ ]
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/example/ohos/entry/.gitignore b/example/ohos/entry/.gitignore
new file mode 100644
index 0000000..2795a1c
--- /dev/null
+++ b/example/ohos/entry/.gitignore
@@ -0,0 +1,7 @@
+
+/node_modules
+/oh_modules
+/.preview
+/build
+/.cxx
+/.test
\ No newline at end of file
diff --git a/example/ohos/entry/build-profile.json5 b/example/ohos/entry/build-profile.json5
new file mode 100644
index 0000000..ac329f6
--- /dev/null
+++ b/example/ohos/entry/build-profile.json5
@@ -0,0 +1,14 @@
+{
+ "apiType": 'stageMode',
+ "buildOption": {
+ },
+ "targets": [
+ {
+ "name": "default",
+ "runtimeOS": "HarmonyOS"
+ },
+ {
+ "name": "ohosTest",
+ }
+ ]
+}
diff --git a/example/ohos/entry/hvigorfile.ts b/example/ohos/entry/hvigorfile.ts
new file mode 100644
index 0000000..e12481a
--- /dev/null
+++ b/example/ohos/entry/hvigorfile.ts
@@ -0,0 +1,16 @@
+/*
+ * Copyright (C) 2026 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+// Script for compiling build behavior. It is built in the build plug-in and cannot be modified currently.
+export { hapTasks } from '@ohos/hvigor-ohos-plugin';
diff --git a/example/ohos/entry/oh-package.json5 b/example/ohos/entry/oh-package.json5
new file mode 100644
index 0000000..15a8db2
--- /dev/null
+++ b/example/ohos/entry/oh-package.json5
@@ -0,0 +1,12 @@
+{
+ "name": "entry",
+ "version": "1.0.0",
+ "description": "Please describe the basic information.",
+ "main": "",
+ "author": "",
+ "license": "",
+ "dependencies": {
+ "fk_user_agent": "file:../har/fk_user_agent.har",
+ "@ohos/flutter_ohos": "har/flutter.har"
+ }
+}
\ No newline at end of file
diff --git a/example/ohos/entry/src/main/ets/entryability/EntryAbility.ets b/example/ohos/entry/src/main/ets/entryability/EntryAbility.ets
new file mode 100644
index 0000000..7c844d1
--- /dev/null
+++ b/example/ohos/entry/src/main/ets/entryability/EntryAbility.ets
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2026 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { FlutterAbility, FlutterEngine } from '@ohos/flutter_ohos';
+import { GeneratedPluginRegistrant } from '../plugins/GeneratedPluginRegistrant';
+
+export default class EntryAbility extends FlutterAbility {
+ configureFlutterEngine(flutterEngine: FlutterEngine) {
+ super.configureFlutterEngine(flutterEngine)
+ GeneratedPluginRegistrant.registerWith(flutterEngine)
+ }
+}
diff --git a/example/ohos/entry/src/main/ets/pages/Index.ets b/example/ohos/entry/src/main/ets/pages/Index.ets
new file mode 100644
index 0000000..4ba3a35
--- /dev/null
+++ b/example/ohos/entry/src/main/ets/pages/Index.ets
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2026 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+ import common from '@ohos.app.ability.common';
+import { FlutterPage } from '@ohos/flutter_ohos'
+
+let storage = LocalStorage.getShared()
+const EVENT_BACK_PRESS = 'EVENT_BACK_PRESS'
+
+@Entry(storage)
+@Component
+struct Index {
+ private context = getContext(this) as common.UIAbilityContext
+ @LocalStorageLink('viewId') viewId: string = "";
+
+ build() {
+ Column() {
+ FlutterPage({ viewId: this.viewId })
+ }
+ }
+
+ onBackPress(): boolean {
+ this.context.eventHub.emit(EVENT_BACK_PRESS)
+ return true
+ }
+}
diff --git a/example/ohos/entry/src/main/ets/plugins/GeneratedPluginRegistrant.ets b/example/ohos/entry/src/main/ets/plugins/GeneratedPluginRegistrant.ets
new file mode 100644
index 0000000..4087d01
--- /dev/null
+++ b/example/ohos/entry/src/main/ets/plugins/GeneratedPluginRegistrant.ets
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2026 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import { FlutterEngine, Log } from '@ohos/flutter_ohos';
+import FkUserAgentPlugin from 'fk_user_agent';
+
+/**
+ * Generated file. Do not edit.
+ * This file is generated by the Flutter tool based on the
+ * plugins that support the Ohos platform.
+ */
+
+const TAG = "GeneratedPluginRegistrant";
+
+export class GeneratedPluginRegistrant {
+
+ static registerWith(flutterEngine: FlutterEngine) {
+ try {
+ flutterEngine.getPlugins()?.add(new FkUserAgentPlugin());
+ } catch (e) {
+ Log.e(
+ TAG,
+ "Tried to register plugins with FlutterEngine ("
+ + flutterEngine
+ + ") failed.");
+ Log.e(TAG, "Received exception while registering", e);
+ }
+ }
+}
diff --git a/example/ohos/entry/src/main/module.json5 b/example/ohos/entry/src/main/module.json5
new file mode 100644
index 0000000..5664f68
--- /dev/null
+++ b/example/ohos/entry/src/main/module.json5
@@ -0,0 +1,39 @@
+{
+ "module": {
+ "name": "entry",
+ "type": "entry",
+ "description": "$string:module_desc",
+ "mainElement": "EntryAbility",
+ "deviceTypes": [
+ "phone"
+ ],
+ "deliveryWithInstall": true,
+ "installationFree": false,
+ "pages": "$profile:main_pages",
+ "abilities": [
+ {
+ "name": "EntryAbility",
+ "srcEntry": "./ets/entryability/EntryAbility.ets",
+ "description": "$string:EntryAbility_desc",
+ "icon": "$media:icon",
+ "label": "$string:EntryAbility_label",
+ "startWindowIcon": "$media:icon",
+ "startWindowBackground": "$color:start_window_background",
+ "exported": true,
+ "skills": [
+ {
+ "entities": [
+ "entity.system.home"
+ ],
+ "actions": [
+ "action.system.home"
+ ]
+ }
+ ]
+ }
+ ],
+ "requestPermissions": [
+ {"name" : "ohos.permission.INTERNET"},
+ ]
+ }
+}
diff --git a/example/ohos/entry/src/main/resources/base/element/color.json b/example/ohos/entry/src/main/resources/base/element/color.json
new file mode 100644
index 0000000..3c71296
--- /dev/null
+++ b/example/ohos/entry/src/main/resources/base/element/color.json
@@ -0,0 +1,8 @@
+{
+ "color": [
+ {
+ "name": "start_window_background",
+ "value": "#FFFFFF"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/example/ohos/entry/src/main/resources/base/element/string.json b/example/ohos/entry/src/main/resources/base/element/string.json
new file mode 100644
index 0000000..c6676af
--- /dev/null
+++ b/example/ohos/entry/src/main/resources/base/element/string.json
@@ -0,0 +1,16 @@
+{
+ "string": [
+ {
+ "name": "module_desc",
+ "value": "module description"
+ },
+ {
+ "name": "EntryAbility_desc",
+ "value": "description"
+ },
+ {
+ "name": "EntryAbility_label",
+ "value": "fk_user_agent_ohos_example"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/example/ohos/entry/src/main/resources/base/media/icon.png b/example/ohos/entry/src/main/resources/base/media/icon.png
new file mode 100644
index 0000000..ce307a8
Binary files /dev/null and b/example/ohos/entry/src/main/resources/base/media/icon.png differ
diff --git a/example/ohos/entry/src/main/resources/base/profile/main_pages.json b/example/ohos/entry/src/main/resources/base/profile/main_pages.json
new file mode 100644
index 0000000..1898d94
--- /dev/null
+++ b/example/ohos/entry/src/main/resources/base/profile/main_pages.json
@@ -0,0 +1,5 @@
+{
+ "src": [
+ "pages/Index"
+ ]
+}
diff --git a/example/ohos/entry/src/main/resources/en_US/element/string.json b/example/ohos/entry/src/main/resources/en_US/element/string.json
new file mode 100644
index 0000000..c6676af
--- /dev/null
+++ b/example/ohos/entry/src/main/resources/en_US/element/string.json
@@ -0,0 +1,16 @@
+{
+ "string": [
+ {
+ "name": "module_desc",
+ "value": "module description"
+ },
+ {
+ "name": "EntryAbility_desc",
+ "value": "description"
+ },
+ {
+ "name": "EntryAbility_label",
+ "value": "fk_user_agent_ohos_example"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/example/ohos/entry/src/main/resources/zh_CN/element/string.json b/example/ohos/entry/src/main/resources/zh_CN/element/string.json
new file mode 100644
index 0000000..6fb96a3
--- /dev/null
+++ b/example/ohos/entry/src/main/resources/zh_CN/element/string.json
@@ -0,0 +1,16 @@
+{
+ "string": [
+ {
+ "name": "module_desc",
+ "value": "模块描述"
+ },
+ {
+ "name": "EntryAbility_desc",
+ "value": "description"
+ },
+ {
+ "name": "EntryAbility_label",
+ "value": "fk_user_agent_ohos_example"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/example/ohos/entry/src/ohosTest/ets/test/Ability.test.ets b/example/ohos/entry/src/ohosTest/ets/test/Ability.test.ets
new file mode 100644
index 0000000..54c6400
--- /dev/null
+++ b/example/ohos/entry/src/ohosTest/ets/test/Ability.test.ets
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2026 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ import { hilog } from '@kit.PerformanceAnalysisKit';
+import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium';
+
+export default function abilityTest() {
+ describe('ActsAbilityTest', () => {
+ // Defines a test suite. Two parameters are supported: test suite name and test suite function.
+ beforeAll(() => {
+ // Presets an action, which is performed only once before all test cases of the test suite start.
+ // This API supports only one parameter: preset action function.
+ })
+ beforeEach(() => {
+ // Presets an action, which is performed before each unit test case starts.
+ // The number of execution times is the same as the number of test cases defined by **it**.
+ // This API supports only one parameter: preset action function.
+ })
+ afterEach(() => {
+ // Presets a clear action, which is performed after each unit test case ends.
+ // The number of execution times is the same as the number of test cases defined by **it**.
+ // This API supports only one parameter: clear action function.
+ })
+ afterAll(() => {
+ // Presets a clear action, which is performed after all test cases of the test suite end.
+ // This API supports only one parameter: clear action function.
+ })
+ it('assertContain', 0, () => {
+ // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function.
+ hilog.info(0x0000, 'testTag', '%{public}s', 'it begin');
+ let a = 'abc';
+ let b = 'b';
+ // Defines a variety of assertion methods, which are used to declare expected boolean conditions.
+ expect(a).assertContain(b);
+ expect(a).assertEqual(a);
+ })
+ })
+}
diff --git a/example/ohos/entry/src/ohosTest/ets/test/List.test.ets b/example/ohos/entry/src/ohosTest/ets/test/List.test.ets
new file mode 100644
index 0000000..a54238c
--- /dev/null
+++ b/example/ohos/entry/src/ohosTest/ets/test/List.test.ets
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2026 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ import abilityTest from './Ability.test';
+
+export default function testsuite() {
+ abilityTest();
+}
diff --git a/example/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts b/example/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts
new file mode 100644
index 0000000..d2f19f5
--- /dev/null
+++ b/example/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2026 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import hilog from '@ohos.hilog';
+import TestRunner from '@ohos.application.testRunner';
+import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry';
+
+var abilityDelegator = undefined
+var abilityDelegatorArguments = undefined
+
+async function onAbilityCreateCallback() {
+ hilog.info(0x0000, 'testTag', '%{public}s', 'onAbilityCreateCallback');
+}
+
+async function addAbilityMonitorCallback(err: any) {
+ hilog.info(0x0000, 'testTag', 'addAbilityMonitorCallback : %{public}s', JSON.stringify(err) ?? '');
+}
+
+export default class OpenHarmonyTestRunner implements TestRunner {
+ constructor() {
+ }
+
+ onPrepare() {
+ hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner OnPrepare ');
+ }
+
+ async onRun() {
+ hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner onRun run');
+ abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments()
+ abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator()
+ var testAbilityName = abilityDelegatorArguments.bundleName + '.TestAbility'
+ let lMonitor = {
+ abilityName: testAbilityName,
+ onAbilityCreate: onAbilityCreateCallback,
+ };
+ abilityDelegator.addAbilityMonitor(lMonitor, addAbilityMonitorCallback)
+ var cmd = 'aa start -d 0 -a TestAbility' + ' -b ' + abilityDelegatorArguments.bundleName
+ var debug = abilityDelegatorArguments.parameters['-D']
+ if (debug == 'true')
+ {
+ cmd += ' -D'
+ }
+ hilog.info(0x0000, 'testTag', 'cmd : %{public}s', cmd);
+ abilityDelegator.executeShellCommand(cmd,
+ (err: any, d: any) => {
+ hilog.info(0x0000, 'testTag', 'executeShellCommand : err : %{public}s', JSON.stringify(err) ?? '');
+ hilog.info(0x0000, 'testTag', 'executeShellCommand : data : %{public}s', d.stdResult ?? '');
+ hilog.info(0x0000, 'testTag', 'executeShellCommand : data : %{public}s', d.exitCode ?? '');
+ })
+ hilog.info(0x0000, 'testTag', '%{public}s', 'OpenHarmonyTestRunner onRun end');
+ }
+}
diff --git a/example/ohos/entry/src/ohosTest/module.json5 b/example/ohos/entry/src/ohosTest/module.json5
new file mode 100644
index 0000000..55725a9
--- /dev/null
+++ b/example/ohos/entry/src/ohosTest/module.json5
@@ -0,0 +1,13 @@
+{
+ "module": {
+ "name": "entry_test",
+ "type": "feature",
+ "deviceTypes": [
+ "phone",
+ "tablet",
+ "2in1"
+ ],
+ "deliveryWithInstall": true,
+ "installationFree": false
+ }
+}
diff --git a/example/ohos/entry/src/test/List.test.ets b/example/ohos/entry/src/test/List.test.ets
new file mode 100644
index 0000000..5eb96dd
--- /dev/null
+++ b/example/ohos/entry/src/test/List.test.ets
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2026 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ import localUnitTest from './LocalUnit.test';
+
+export default function testsuite() {
+ localUnitTest();
+}
diff --git a/example/ohos/entry/src/test/LocalUnit.test.ets b/example/ohos/entry/src/test/LocalUnit.test.ets
new file mode 100644
index 0000000..0dfeb77
--- /dev/null
+++ b/example/ohos/entry/src/test/LocalUnit.test.ets
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2026 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium';
+import FkUserAgentPlugin from 'fk_user_agent';
+
+const FkUserAgentPlugin: FkUserAgentPlugin = new FkUserAgentPlugin()
+export default function localUnitTest() {
+ describe('localUnitTest', () => {
+ it('getUniqueClassName', 0, () => {
+ expect(fkUserAgentPlugin.getUniqueClassName()).not().assertNull();
+ });
+ });
+}
diff --git a/example/ohos/hvigor/hvigor-config.json5 b/example/ohos/hvigor/hvigor-config.json5
new file mode 100644
index 0000000..941ccf5
--- /dev/null
+++ b/example/ohos/hvigor/hvigor-config.json5
@@ -0,0 +1,5 @@
+{
+ "modelVersion": "5.0.0",
+ "dependencies": {
+ }
+}
diff --git a/example/ohos/hvigorfile.ts b/example/ohos/hvigorfile.ts
new file mode 100644
index 0000000..f3cb9f1
--- /dev/null
+++ b/example/ohos/hvigorfile.ts
@@ -0,0 +1,6 @@
+import { appTasks } from '@ohos/hvigor-ohos-plugin';
+
+export default {
+ system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
+ plugins:[] /* Custom plugin to extend the functionality of Hvigor. */
+}
diff --git a/example/ohos/oh-package.json5 b/example/ohos/oh-package.json5
new file mode 100644
index 0000000..619fa86
--- /dev/null
+++ b/example/ohos/oh-package.json5
@@ -0,0 +1,20 @@
+{
+ "modelVersion": "5.0.0",
+ "name": "fk_user_agent_ohos_example",
+ "version": "1.0.0",
+ "description": "Please describe the basic information.",
+ "main": "",
+ "author": "",
+ "license": "",
+ "dependencies": {
+ "@ohos/flutter_ohos": "file:./har/flutter.har"
+ },
+ "devDependencies": {
+ "@ohos/hypium": "1.0.6"
+ },
+ "overrides": {
+ "@ohos/flutter_ohos": "file:./har/flutter.har",
+ "fk_user_agent": "file:./har/fk_user_agent.har",
+ "@ohos/flutter_module": "file:./entry"
+ }
+}
\ No newline at end of file
diff --git a/example/pubspec.lock b/example/pubspec.lock
deleted file mode 100644
index 6dd4be9..0000000
--- a/example/pubspec.lock
+++ /dev/null
@@ -1,161 +0,0 @@
-# Generated by pub
-# See https://dart.dev/tools/pub/glossary#lockfile
-packages:
- async:
- dependency: transitive
- description:
- name: async
- url: "https://pub.flutter-io.cn"
- source: hosted
- version: "2.5.0"
- boolean_selector:
- dependency: transitive
- description:
- name: boolean_selector
- url: "https://pub.flutter-io.cn"
- source: hosted
- version: "2.1.0"
- characters:
- dependency: transitive
- description:
- name: characters
- url: "https://pub.flutter-io.cn"
- source: hosted
- version: "1.1.0"
- charcode:
- dependency: transitive
- description:
- name: charcode
- url: "https://pub.flutter-io.cn"
- source: hosted
- version: "1.2.0"
- clock:
- dependency: transitive
- description:
- name: clock
- url: "https://pub.flutter-io.cn"
- source: hosted
- version: "1.1.0"
- collection:
- dependency: transitive
- description:
- name: collection
- url: "https://pub.flutter-io.cn"
- source: hosted
- version: "1.15.0"
- cupertino_icons:
- dependency: "direct main"
- description:
- name: cupertino_icons
- url: "https://pub.flutter-io.cn"
- source: hosted
- version: "1.0.2"
- fake_async:
- dependency: transitive
- description:
- name: fake_async
- url: "https://pub.flutter-io.cn"
- source: hosted
- version: "1.2.0"
- fk_user_agent:
- dependency: "direct main"
- description:
- path: ".."
- relative: true
- source: path
- version: "2.0.0"
- flutter:
- dependency: "direct main"
- description: flutter
- source: sdk
- version: "0.0.0"
- flutter_test:
- dependency: "direct dev"
- description: flutter
- source: sdk
- version: "0.0.0"
- matcher:
- dependency: transitive
- description:
- name: matcher
- url: "https://pub.flutter-io.cn"
- source: hosted
- version: "0.12.10"
- meta:
- dependency: transitive
- description:
- name: meta
- url: "https://pub.flutter-io.cn"
- source: hosted
- version: "1.3.0"
- path:
- dependency: transitive
- description:
- name: path
- url: "https://pub.flutter-io.cn"
- source: hosted
- version: "1.8.0"
- sky_engine:
- dependency: transitive
- description: flutter
- source: sdk
- version: "0.0.99"
- source_span:
- dependency: transitive
- description:
- name: source_span
- url: "https://pub.flutter-io.cn"
- source: hosted
- version: "1.8.0"
- stack_trace:
- dependency: transitive
- description:
- name: stack_trace
- url: "https://pub.flutter-io.cn"
- source: hosted
- version: "1.10.0"
- stream_channel:
- dependency: transitive
- description:
- name: stream_channel
- url: "https://pub.flutter-io.cn"
- source: hosted
- version: "2.1.0"
- string_scanner:
- dependency: transitive
- description:
- name: string_scanner
- url: "https://pub.flutter-io.cn"
- source: hosted
- version: "1.1.0"
- term_glyph:
- dependency: transitive
- description:
- name: term_glyph
- url: "https://pub.flutter-io.cn"
- source: hosted
- version: "1.2.0"
- test_api:
- dependency: transitive
- description:
- name: test_api
- url: "https://pub.flutter-io.cn"
- source: hosted
- version: "0.2.19"
- typed_data:
- dependency: transitive
- description:
- name: typed_data
- url: "https://pub.flutter-io.cn"
- source: hosted
- version: "1.3.0"
- vector_math:
- dependency: transitive
- description:
- name: vector_math
- url: "https://pub.flutter-io.cn"
- source: hosted
- version: "2.1.0"
-sdks:
- dart: ">=2.12.0 <3.0.0"
- flutter: ">=2.0.0"
diff --git a/ohos/.gitignore b/ohos/.gitignore
new file mode 100644
index 0000000..b6d2839
--- /dev/null
+++ b/ohos/.gitignore
@@ -0,0 +1,10 @@
+/node_modules
+/oh_modules
+/.preview
+/.idea
+/build
+/.cxx
+/.test
+/BuildProfile.ets
+/oh-package-lock.json5
+/local.properties
diff --git a/ohos/build-profile.json5 b/ohos/build-profile.json5
new file mode 100644
index 0000000..79961f9
--- /dev/null
+++ b/ohos/build-profile.json5
@@ -0,0 +1,10 @@
+{
+ "apiType": "stageMode",
+ "buildOption": {
+ },
+ "targets": [
+ {
+ "name": "default"
+ }
+ ]
+}
diff --git a/ohos/hvigorfile.ts b/ohos/hvigorfile.ts
new file mode 100644
index 0000000..5cbf835
--- /dev/null
+++ b/ohos/hvigorfile.ts
@@ -0,0 +1,15 @@
+/*
+ * Copyright (C) 2026 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export { harTasks } from '@ohos/hvigor-ohos-plugin';
diff --git a/ohos/index.ets b/ohos/index.ets
new file mode 100644
index 0000000..e2afc0b
--- /dev/null
+++ b/ohos/index.ets
@@ -0,0 +1,16 @@
+/*
+ * Copyright (C) 2026 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import FkUserAgentPlugin from './src/main/ets/components/plugin/FkUserAgentPlugin';
+export default FkUserAgentPlugin;
diff --git a/ohos/oh-package.json5 b/ohos/oh-package.json5
new file mode 100644
index 0000000..8a4e320
--- /dev/null
+++ b/ohos/oh-package.json5
@@ -0,0 +1,11 @@
+{
+ "name": "fk_user_agent",
+ "version": "1.0.0",
+ "description": "Please describe the basic information.",
+ "main": "index.ets",
+ "author": "",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@ohos/flutter_ohos": "file:./har/flutter.har"
+ }
+}
diff --git a/ohos/src/main/ets/components/plugin/FkUserAgentPlugin.ets b/ohos/src/main/ets/components/plugin/FkUserAgentPlugin.ets
new file mode 100644
index 0000000..ccca810
--- /dev/null
+++ b/ohos/src/main/ets/components/plugin/FkUserAgentPlugin.ets
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2026 Huawei Device Co., Ltd.
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import {
+ FlutterPlugin,
+ FlutterPluginBinding,
+ MethodCall,
+ MethodCallHandler,
+ MethodChannel,
+ MethodResult,
+} from '@ohos/flutter_ohos';
+import { BusinessError } from '@kit.BasicServicesKit';
+import { bundleManager } from '@kit.AbilityKit';
+import { webview } from '@kit.ArkWeb';
+import { hilog } from '@kit.PerformanceAnalysisKit';
+import common from '@ohos.app.ability.common';
+import deviceInfo from '@ohos.deviceInfo';
+
+const DOMAIN = 0x0001;
+const TAG = 'FkUserAgentPlugin';
+
+/** FkUserAgentPlugin **/
+export default class FkUserAgentPlugin implements FlutterPlugin, MethodCallHandler {
+ private channel: MethodChannel | null = null;
+ private webEngineInitialized: boolean = false;
+
+ constructor() {
+ }
+
+ getUniqueClassName(): string {
+ return "FkUserAgentPlugin"
+ }
+
+ onAttachedToEngine(binding: FlutterPluginBinding): void {
+ this.channel = new MethodChannel(binding.getBinaryMessenger(), "fk_user_agent");
+ this.channel.setMethodCallHandler(this)
+ }
+
+ onDetachedFromEngine(binding: FlutterPluginBinding): void {
+ if (this.channel != null) {
+ this.channel.setMethodCallHandler(null)
+ }
+ }
+
+ onMethodCall(call: MethodCall, result: MethodResult): void {
+ if (call.method == "getProperties") {
+ this.getProperties(result)
+ } else {
+ result.notImplemented()
+ }
+ }
+
+ /**
+ * 初始化 ArkWeb 引擎,以便通过静态方法 getDefaultUserAgent() 获取真实 User-Agent。
+ * 全局只需调用一次。
+ */
+ private initWebEngine() {
+ if (this.webEngineInitialized) {
+ return;
+ }
+ try {
+ webview.WebviewController.initializeWebEngine();
+ this.webEngineInitialized = true;
+ } catch (error) {
+ hilog.error(DOMAIN, TAG, `initializeWebEngine failed: ${(error as BusinessError).message}`);
+ }
+ }
+
+ /**
+ * 通过 ArkWeb 静态方法获取系统默认 Web User-Agent。
+ * getDefaultUserAgent() 从 API version 14 开始支持,无需绑定 Web 组件。
+ * 如果调用失败则返回空字符串。
+ */
+ private getWebViewUserAgent(): string {
+ this.initWebEngine();
+ try {
+ return webview.WebviewController.getDefaultUserAgent();
+ } catch (error) {
+ hilog.error(DOMAIN, TAG, `getDefaultUserAgent failed: ${(error as BusinessError).message}`);
+ return '';
+ }
+ }
+
+ getProperties(result: MethodResult) {
+ // deviceInfo 提供的设备信息(无需权限)
+ const deviceType = deviceInfo.deviceType; //设备类型,如 phone/tablet/2in1
+ const systemName = deviceInfo.distributionOSName; //发行版系统名称,如 OpenHarmony
+ const systemVersion = deviceInfo.distributionOSVersion; //发行版系统版本号,如 5.0.0
+ const deviceBrand = deviceInfo.brand; //设备品牌
+ const systemModel = deviceInfo.productModel; //认证型号
+ const osFullName = deviceInfo.osFullName; //系统完整名称,如 OpenHarmony-6.0.2.130
+
+ bundleManager.getBundleInfoForSelf(bundleManager.BundleFlag.GET_BUNDLE_INFO_WITH_APPLICATION)
+ .then((bundleInfo) => {
+ const packageName = bundleInfo.name;
+ const shortPackageName = packageName.substring(packageName.lastIndexOf(".") + 1);
+ const applicationVersion = bundleInfo.versionName; //应用版本名
+ const buildNumber = bundleInfo.versionCode.toString(); //应用版本号
+ let applicationName = "";
+
+ try {
+ const context = getContext(this) as common.UIAbilityContext
+ applicationName = context.resourceManager.getStringSync(bundleInfo.appInfo.labelId); //应用名称
+ } catch (error) {
+ hilog.error(DOMAIN, TAG, 'getStringSync failed');
+ }
+
+ // 系统名称/系统版本 (设备类型; 设备品牌 认证型号; 系统完整名称)
+ const userAgent = `${systemName}/${systemVersion} (${deviceType}; ${deviceBrand} ${systemModel}; ${osFullName})`;
+ const packageUserAgent = `${shortPackageName}/${applicationVersion}.${buildNumber} ${userAgent}`;
+
+ // 通过 ArkWeb API 获取真实的 WebView User-Agent
+ const webViewUserAgent = this.getWebViewUserAgent();
+
+ const properties: Record = {
+ "systemName": "OpenHarmony",
+ "systemVersion": systemVersion,
+ "packageName": packageName,
+ "shortPackageName": shortPackageName,
+ "applicationName": applicationName,
+ "applicationVersion": applicationVersion,
+ "applicationBuildNumber": buildNumber,
+ "packageUserAgent": packageUserAgent,
+ "userAgent": userAgent,
+ "webViewUserAgent": webViewUserAgent,
+ };
+ result.success(properties)
+ })
+ .catch((error: BusinessError) => {
+ hilog.error(DOMAIN, TAG, `get bundleInfo failed, error is ${error}`);
+ result.error("BUNDLE_INFO_ERROR", "Failed to get bundle info", error.message);
+ })
+ }
+}
diff --git a/ohos/src/main/module.json5 b/ohos/src/main/module.json5
new file mode 100644
index 0000000..e858214
--- /dev/null
+++ b/ohos/src/main/module.json5
@@ -0,0 +1,10 @@
+{
+ "module": {
+ "name": "fk_user_agent",
+ "type": "har",
+ "deviceTypes": [
+ "default",
+ "tablet"
+ ]
+ }
+}
diff --git a/pubspec.lock b/pubspec.lock
deleted file mode 100644
index 6c77e39..0000000
--- a/pubspec.lock
+++ /dev/null
@@ -1,147 +0,0 @@
-# Generated by pub
-# See https://dart.dev/tools/pub/glossary#lockfile
-packages:
- async:
- dependency: transitive
- description:
- name: async
- url: "https://pub.flutter-io.cn"
- source: hosted
- version: "2.5.0"
- boolean_selector:
- dependency: transitive
- description:
- name: boolean_selector
- url: "https://pub.flutter-io.cn"
- source: hosted
- version: "2.1.0"
- characters:
- dependency: transitive
- description:
- name: characters
- url: "https://pub.flutter-io.cn"
- source: hosted
- version: "1.1.0"
- charcode:
- dependency: transitive
- description:
- name: charcode
- url: "https://pub.flutter-io.cn"
- source: hosted
- version: "1.2.0"
- clock:
- dependency: transitive
- description:
- name: clock
- url: "https://pub.flutter-io.cn"
- source: hosted
- version: "1.1.0"
- collection:
- dependency: transitive
- description:
- name: collection
- url: "https://pub.flutter-io.cn"
- source: hosted
- version: "1.15.0"
- fake_async:
- dependency: transitive
- description:
- name: fake_async
- url: "https://pub.flutter-io.cn"
- source: hosted
- version: "1.2.0"
- flutter:
- dependency: "direct main"
- description: flutter
- source: sdk
- version: "0.0.0"
- flutter_test:
- dependency: "direct dev"
- description: flutter
- source: sdk
- version: "0.0.0"
- matcher:
- dependency: transitive
- description:
- name: matcher
- url: "https://pub.flutter-io.cn"
- source: hosted
- version: "0.12.10"
- meta:
- dependency: transitive
- description:
- name: meta
- url: "https://pub.flutter-io.cn"
- source: hosted
- version: "1.3.0"
- path:
- dependency: transitive
- description:
- name: path
- url: "https://pub.flutter-io.cn"
- source: hosted
- version: "1.8.0"
- sky_engine:
- dependency: transitive
- description: flutter
- source: sdk
- version: "0.0.99"
- source_span:
- dependency: transitive
- description:
- name: source_span
- url: "https://pub.flutter-io.cn"
- source: hosted
- version: "1.8.0"
- stack_trace:
- dependency: transitive
- description:
- name: stack_trace
- url: "https://pub.flutter-io.cn"
- source: hosted
- version: "1.10.0"
- stream_channel:
- dependency: transitive
- description:
- name: stream_channel
- url: "https://pub.flutter-io.cn"
- source: hosted
- version: "2.1.0"
- string_scanner:
- dependency: transitive
- description:
- name: string_scanner
- url: "https://pub.flutter-io.cn"
- source: hosted
- version: "1.1.0"
- term_glyph:
- dependency: transitive
- description:
- name: term_glyph
- url: "https://pub.flutter-io.cn"
- source: hosted
- version: "1.2.0"
- test_api:
- dependency: transitive
- description:
- name: test_api
- url: "https://pub.flutter-io.cn"
- source: hosted
- version: "0.2.19"
- typed_data:
- dependency: transitive
- description:
- name: typed_data
- url: "https://pub.flutter-io.cn"
- source: hosted
- version: "1.3.0"
- vector_math:
- dependency: transitive
- description:
- name: vector_math
- url: "https://pub.flutter-io.cn"
- source: hosted
- version: "2.1.0"
-sdks:
- dart: ">=2.12.0 <3.0.0"
- flutter: ">=2.0.0"
diff --git a/pubspec.yaml b/pubspec.yaml
index f3e54f6..7fd573b 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,6 +1,6 @@
name: fk_user_agent
-description: Retrieve Android/iOS device user agents in Flutter.
-version: 2.1.0
+description: Retrieve Android/iOS/OHOS device user agents in Flutter.
+version: 2.1.1
homepage: https://github.com/flutter-fast-kit/fk_user_agent
environment:
@@ -31,6 +31,8 @@ flutter:
pluginClass: FkUserAgentPlugin
ios:
pluginClass: FkUserAgentPlugin
+ ohos:
+ pluginClass: FkUserAgentPlugin
# To add assets to your plugin package, add an assets section, like this:
# assets: