diff --git a/.ci.yaml b/.ci.yaml index c5d3d3b167e71f0c5f428c8a10e7dfd31c38daae..13005c7b58beaa32850897877c099cbea8e54298 100644 --- a/.ci.yaml +++ b/.ci.yaml @@ -1935,10 +1935,22 @@ targets: - name: Linux_android_emu android_defines_test recipe: devicelab/devicelab_drone timeout: 60 + dimensions: { + kvm: "1", + cores: "8", + machine_type: "n1-standard-8" + } properties: + device_type: "none" tags: > ["devicelab", "linux"] + ["devicelab", "linux"] task_name: android_defines_test + dependencies: >- + [ + {"dependency": "android_virtual_device", "version": "31"} + ] + use_emulator: "true" - name: Linux_pixel_7pro android_obfuscate_test recipe: devicelab/devicelab_drone diff --git a/OAT.xml b/OAT.xml new file mode 100644 index 0000000000000000000000000000000000000000..db0de33e4814799957492b0df1699b0a167a7d5c --- /dev/null +++ b/OAT.xml @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/README b/README new file mode 100644 index 0000000000000000000000000000000000000000..dae9e7f450b2ed136432c4d9838481fa0b5e002f --- /dev/null +++ b/README @@ -0,0 +1,29 @@ +The Dart SDK is a set of tools and libraries for the Dart programming language. + +You can find information about Dart online at https://dart.dev/. + +Here's a brief guide to what's in here: + +bin/ Binaries/scripts to compile, run, and manage Dart apps. + dart Command line Dart tool + dartaotruntime Minimal Dart runtime for running AOT modules + resources/ Resource files for dartdoc and devtools + snapshots/ AppAOT and AppJIT snapshots of various tools + utils/ Tools used by Dart compilers + +include/ header files that define the Dart embedding API for use by + - C/C++ applications that embed the Dart Virtual machine + - native libraries loaded into a dart application using FFI + (https://dart.dev/guides/libraries/c-interop) + +lib/ Libraries that are shipped with the Dart runtime. More + information is available at https://api.dart.dev. + +LICENSE Description of Dart SDK license + +README This file + +revision The git commit ID of the SDK build + (for example, 020b3efd3f0023c5db2097787f7cf778db837a8f). + +version The version number of the SDK (for example, 2.12.1). diff --git a/README.OpenSource b/README.OpenSource new file mode 100644 index 0000000000000000000000000000000000000000..fe2476c6851f98cc561f03ebda3d632089d315fe --- /dev/null +++ b/README.OpenSource @@ -0,0 +1,11 @@ +[ + { + "Name": "flutter", + "License": "BSD 3-Clause License", + "License File": "LICENSE", + "Version Number": "3.7.12", + "Owner": "aibin@openvalley.net", + "Upstream URL": "https://github.com/flutter/flutter/", + "Description": "Flutter makes it easy and fast to build beautiful mobile apps. Flutter is Google''s mobile app SDK for crafting high-quality native interfaces on iOS and Android in record time. Flutter works with existing code, is used by developers and organization" + } +] \ No newline at end of file diff --git a/README.en.md b/README.en.md new file mode 100644 index 0000000000000000000000000000000000000000..04aa77e87ac557f14d95572f00f22881a515de4c --- /dev/null +++ b/README.en.md @@ -0,0 +1,326 @@ +Flutter SDK repository +============== + +Original warehouse source: https://github.com/flutter/flutter + +## Warehouse description +1. This repository is a compatible extension of Flutter SDK for the OpenHarmony platform, and can support IDE or terminal use of Flutter Tools instructions to compile and build OpenHarmony applications. +2. This repository is built based on the Flutter official community version 3.22.0 + * [sdk base version](https://github.com/flutter/flutter/commit/5dcb86f68f239346676ceb1ed1ea385bd215fba1) + * [engine base version](https://github.com/flutter/engine/commit/f6344b75dcf861d8bf1f1322780b8811f982e31a) + +## Upgrade Guide +1. If your project is upgrading from HarmonyOS version 3.7.12 to version 3.22.0: + * Environment dependencies: Configuration remains consistent between the two versions, no additional modifications required. + * For new features and changes from 3.7.12 to 3.22.0, please refer to the [Release Notes](https://docs.flutter.dev/release/release-notes). + * For official compatibility changes, please refer to the [Upgrade Guide](https://docs.flutter.dev/release/breaking-changes). + * Rendering Engine: Added impeller-vulkan mode (default, can be switched to skia-gl). + * Third-party libraries: + - Pure Dart libraries should be upgraded to the specified version to support 3.22.0. + - The packages in [OpenHarmony-SIG/flutter_packages](https://gitee.com/openharmony-sig/flutter_packages/blob/master/README.md) have undergone a basic usability test for version 3.22.0. If you encounter any issues during use, please create an issue for tracking. +2. If your project is migrating from Android or iOS to the HarmonyOS adaptation for version 3.22.0, please refer to the remaining guide documents. + +## Development document +[Docs](https://gitee.com/openharmony-sig/flutter_samples/tree/master/ohos/docs) + +## Environment dependencies + +* development system + + Flutter Tools commands are currently supported on Linux, Mac and Windows. + +* Environment configuration + + **Please download the supporting development tool from [OpenHarmony SDK](https://developer.huawei.com/consumer/cn/develop)** + *The following environment variable configuration is for Unix-like systems (Linux, Mac). You can directly refer to the configuration below. For environment variable configuration under Windows, please set it in ‘Edit System Environment Variables’* + + 1. Configure the HarmonyOS SDK and environment variables + * API12, deveco-studio-5.0 or command-line-tools-5.0 (Recommended to use version 5.0.0 Release or later) + * Configure Java17 + * Configure environment variables (SDK, node, ohpm, hvigor) + + ```sh + export TOOL_HOME=/Applications/DevEco-Studio.app/Contents # For mac + export DEVECO_SDK_HOME=$TOOL_HOME/sdk # command-line-tools/sdk + export PATH=$TOOL_HOME/tools/ohpm/bin:$PATH # command-line-tools/ohpm/bin + export PATH=$TOOL_HOME/tools/hvigor/bin:$PATH # command-line-tools/hvigor/bin + export PATH=$TOOL_HOME/tools/node/bin:$PATH # command-line-tools/tool/node/bin + ``` + + 2. Download the current warehouse code `git clone https://gitee.com/openharmony-sig/flutter_flutter.git` Specify the dev or master branch and configure the environment + + ```sh + export PATH=/bin:$PATH + export PUB_CACHE=D:/PUB + # Domestic mirror + export PUB_HOSTED_URL=https://pub.flutter-io.cn + export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn + ``` + + 3. becomes an optional parameter and may not be passed. + - Usage example: `--local-engine=src/out/` + - You can download [compiled product](https://docs.qq.com/sheet/DUnljRVBYUWZKZEtF?tab=BB08J2) from this path. + - The engine path points to the directory that needs to be accompanied by 'src/out'. + + For the configuration of all the above environment variables (for environment variable configuration under Windows, please set it in 'Edit System Environment Variables'), you can refer to the following example (please replace user and specific code path with the actual path): + + ```sh + # Dependent cache + export PUB_CACHE=D:/PUB(Custom path) + + # Domestic mirror + export PUB_HOSTED_URL=https://pub.flutter-io.cn + export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn + + # The flutter_flutter directory pulled from Gitee + export PATH=/home//ohos/flutter_flutter/bin:$PATH + + # HarmonyOS SDK + export TOOL_HOME=/Applications/DevEco-Studio.app/Contents # For mac + export DEVECO_SDK_HOME=$TOOL_HOME/sdk # command-line-tools/sdk + export PATH=$TOOL_HOME/tools/ohpm/bin:$PATH # command-line-tools/ohpm/bin + export PATH=$TOOL_HOME/tools/hvigor/bin:$PATH # command-line-tools/hvigor/bin + export PATH=$TOOL_HOME/tools/node/bin:$PATH # command-line-tools/tool/node/bin + ``` + +## Build steps + +1. Run `flutter doctor -v` to check whether the environment variable configuration is correct. **Futter** and **OpenHarmony** should both be ok. If the two prompts indicate that the environment is missing, just follow the prompts to fill in the corresponding environment. + +2. Create the project and compile the command. The compiled product is under \/ohos/entry/build/default/outputs/default/entry-default-signed.hap. + + ``` + # Create project + flutter create --platforms ohos + + # Go to the project root directory for compilation + # Example: flutter build hap [--target-platform ohos-arm64] --local-engine=/src/out/ohos_release_arm64 --release + flutter build hap --target-platform ohos-arm64 -- --local-engine=/src/out/ --local-engine-host=src/out// + ``` + 2.1 Create a Project and Enable the Impeller + Currently, the Flutter OHOS platform supports the impeller-vulkan rendering mode, which can be controlled via a switch. The switch is located in the 'buildinfo.json5' file. If you choose to disable the Impeller rendering, you can change the value in the JSON file to false. The next time you run the project, the Impeller will be disabled. + File path: `ohos/entry/src/main/resources/rawfile/buildinfo.json5` + (After the initial `flutter create`, the configuration file is located in the `profile` directory. After the first `run` or `build`, it will be moved to the `rawfile` directory.) + + File content: + ```json + { + "string": [ + { + "name": "enable_impeller", + "value": "true" + } + ] + } + ``` + By default, the Impeller option is enabled in new projects. + For existing projects, you can copy the above buildinfo.json5 file to the corresponding directory(rawfile) in your project and modify the value to enable or disable the switch. If the switch does not exist, then will set enable-impeller by default. + +3. After discovering the ohos device through the `flutter devices` command, use `hdc -t install ` to install it. + +4. You can also directly use the following command to run: + ``` + # Example: flutter run [--local-engine=/src/out/ohos_debug_unopt_arm64] [--local-engine-host=/src/out/host_debug_unopt] -d + flutter run --debug -d + ``` + +5. Build app package command: + ``` + # Example: flutter run --local-engine=/src/out/ohos_debug_unopt_arm64 -d + flutter run --debug --local-engine=/home/user/engine_make/src/out/ohos_debug_unopt_arm64 -d --local-engine-host=src/out// + ``` + +## Release Notes + - [3.22.0-ohos-0.1.0 Beta](/release-notes/Flutter%203.22.0-ohos%200.1.0%20ReleaseNote.en.md) + +## Compatible command list developed by OpenHarmony + +| Command name | Command description | Instructions for use | +| ------- | ------- |-------------------------------------------------------------------| +| doctor | environment detection | flutter doctor | +| config | environment configuration | flutter config --\ \ | +| create | Create a new project | flutter create --platforms ohos,android,ios --org \ \ | +| create | Create module template | flutter create -t module \ | +| create | Create plugin template | flutter create -t plugin --platforms ohos,android,ios \ | +| create | Create plugin_ffi template | flutter create -t plugin_ffi --platforms ohos,android,ios \ | +| devices | Connected device discovery | flutter devices | +| install | application installation | flutter install -t \ \ | +| assemble | resource packaging | flutter assemble | +| build | Test application build | flutter build hap --debug [--target-platform ohos-arm64] [--local-engine=\] | +| build | Formal application build | flutter build hap --release [--target-platform ohos-arm64] [--local-engine=\] | +| run | application run | flutter run [--local-engine=\] | +| attach | debug mode | flutter attach | +| screenshot | screenshot | flutter screenshot | +| pub | Obtains the dependencies.| flutter pub get | +| clean | Clears the project dependencies.| flutter clean | +| cache | Clears global cache data.| flutter pub cache clean | + +Attachment: [Flutter third-party library adaptation plan](https://docs.qq.com/sheet/DVVJDWWt1V09zUFN2) + +## Common Problem + +1. After switching to FLUTTER_STORAGE_BASE_URL, you need to delete the \/bin/cache directory and execute Flutter clean in the project before running it again. + +2. If an error message appears: `The SDK license agreement is not accepted`, please execute the following command and compile again: + + ``` + ./ohsdkmgr install ets:9 js:9 native:9 previewer:9 toolchains:9 --sdk-directory='/home/xc/code/sdk/ohos-sdk/' --accept-license + ``` + +3. If you are using the Beta version of DevEco Studio and encounter the error "must have required property 'compatibleSdkVersion', location: demo/ohos/build-profile.json5:17:11" when compiling the project, Modify the hvigor/hvigor-config.json5 file by referring to section ‘6 Create the project and run Hello World’ [Configuration Plug-in] in《DevEco Studio Environment configuration guide.docx》. + +4. If you are prompted with an installation error: `fail to verify pkcs7 file`, please execute the command + + ``` + hdc shell param set persist.bms.ohCert.verify true + ``` +5. Linux virtual machine cannot directly discover OpenHarmony devices through hdc + + Solution: In the Windows host, open the hdc server. The specific instructions are as follows: + ``` + hdc kill + hdc -s serverIP:8710 -m + ``` + Configure environment variables in linux: + ``` + HDC_SERVER= + HDC_SERVER_PORT=8710 + ``` + + After the configuration is completed, the flutter sdk can complete the device connection through the hdc server. You can also refer to [official guidance](https://docs.openharmony.cn/pages/v4.0/zh-cn/device-dev/subsystems/subsys-toolchain -hdc-guide.md/#hdc-client%E5%A6%82%E4%BD%95%E8%BF%9C%E7%A8%8B%E8%AE%BF%E9%97%AEhdc-server) . + +6. An error occurred when building the Hap task: Error: The hvigor depends on the npmrc file. Configure the npmrc file first. + + + Please create the file `.npmrc` in the user directory `~`. For this configuration, please refer to [DevEco Studio official documentation](https://developer.harmonyos.com/cn/docs/documentation/doc-guides-V3/environment_config -0000001052902427-V3), the edited content is as follows: + + ``` + registry=https://repo.huaweicloud.com/repository/npm/ + @ohos:registry=https://repo.harmonyos.com/npm/ + ``` +7. Symptom Logs are lost during log query。 + Solution:Disable global logs and enable only logs in your domain + + ``` + Step one:Disable log printing for all fields(Some special logs cannot be closed) + hdc shell hilog -b X + Step two:Open logs for your domain only + hdc shell hilog -D + indicates the level of log printing:D/I/W/E/F,is the number before the Tag + Example: + To print logs about 'A00000/XComFlutterOHOS_Native', set 'hdc shell hilog -b D -D A00000' + annotation:The above Settings will be invalid after the machine is restarted, and you need to reset them if you want to continue using them + ``` +8. If the debug signature application cannot be started on API 11BETA1, it can be resolved by changing the signature to an official signature or opening the developer mode on the mobile terminal (steps: Settings -> General -> Developer mode). + +9. If `Invalid CEN header (invalid zip64 extra data field size)` is abnormal, please replace the JDK version, see [JDK-8313765] + +10. An error occurs when running a debug version of the Flutter application on a HarmonyOS device (release and profile versions are normal) + 1. Error message: `Error while initializing the Dart VM: Wrong full snapshot version, expected '8af474944053df1f0a3be6e6165fa7cf' found 'adb4292f3ec25074ca70abcd2d5c7251'` + 2. Solution: Perform the following actions in sequence + 1. Set environment variables `export FLUTTER_STORAGE_BASE_URL=https://flutter-ohos.obs.cn-south-1.myhuaweicloud.com` + 1. Delete the cache in the/bin/cache directory + 2. Execute `fluent clean` to clear the project compilation cache + 3. Execute `flutter run -d $DEVICE --debug` + 3. Additional information: If a similar error occurs while running Android or iOS, you can also try restoring the environment variable FLUTTER_STORAGE_BASE_URL , clearing the cache, and then running again. + +11. After the ROM update of Beta 2 version, it no longer supports requesting anonymous memory with execution permission, resulting in debug crashing. + 1. Solution: Update flutter_flutter to a version after a44b8a6d (2024-07-25). + 2. Key logs: + + ``` + #20 at attachToNative (oh_modules/.ohpm/@ohos+flutter_ohos@g8zhdaqwu8gotysbmqcstpfpcpy=/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/FlutterNapi.ets:78:32) + #21 at attachToNapi (oh_modules/.ohpm/@ohos+flutter_ohos@g8zhdaqwu8gotysbmqcstpfpcpy=/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/FlutterEngine.ets:144:5) + #22 at init (oh_modules/.ohpm/@ohos+flutter_ohos@g8zhdaqwu8gotysbmqcstpfpcpy=/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/FlutterEngine.ets:133:7) + ``` + +12. Build Hap command directly execute `flutter build hap`, no longer need `--local-engine` parameter, directly from the cloud to obtain the compilation product + +13. After the environment is configured, the system crashes when the flutter command is executed。 + 1. Solution:Add git environment variable configuration in windows environment。 + ``` + export PATH=/cmd:$PATH + ``` + +14. If `flutter pub cache clean` is executed normally, `flutter clean` will report an error. If update command is executed according to the error message, it has no effect。 + 1. Solution:To avoid this problem, comment out the configuration in the build.json5 file。 + 2. Error message: + ``` + #Parse ohos module. json5 error: Exception: Can not found module.json5 at + #D:\pub_cache\git\flutter_packages-b00939bb44d018f0710d1b080d91dcf4c34ed06\packages\video_player\video_player_ohos\ohossrc\main\module.json5. + #You need to update the Flutter plugin project structure. + #See + #https://gitee.com/openharmony-sig/flutter_samples/tree/master/ohos/docs/09_specifications/update_flutter_plugin_structure.md + ``` + +15. An error message indicating path verification occurs when `flutter build hap` is executed。 + 1. Solution: + · Open the ohos-project-build-profile-schema.json file in deveco installation path D:\DevEco Studio\tools\hvigor\hvigor-ohos-plugin\res\schemas。 + · Find the line containing: "pattern": "^(\\./|\\.\\./)[\\s\\S]+$" in the file and delete it。 + 2. Error message: + ``` + #hvigor ERROR: Schema validate failed. + # Detail: Please check the following fields. + #instancePath: 'modules[1].scrPath', + #keyword: 'pattern' + #params: { pattern:'^(\\./|\\.\\./)[\\s\\S]+$' }, + #message: 'must match pattern "^(\\./|\\.\\./)[\\s\\S]+$"', + #location: 'D:/work/videoplayerdemo/video_cannot_stop_at_background/ohos/build-profile.json:42:146' + ``` + +16. Execute `flutter build hap` report an error。 + 1. Solution:Open the core-module-model-impl.js file in deveco installation path D:\DevEco Studio\tools\hvigor\hvigor-ohos-plugin\src\model\module。, + Modify the findBelongProjectPath method (requires administrator rights, can be saved as and replaced) + ``` + findBelongProjectPath(e) { + if (e === path_1.default.dirname(e)) { + return this.parentProject.getProjectDir() + } + } + ``` + 2. Error message: + ``` + # hvigor ERROR: Cannot find belonging project path for module at D:\. + # hvigor ERROR: BUILD FAILED in 2s 556ms. + #Running Hvigor task assembleHap... + #Oops; flutter has exited unexpectedly: "ProcessException: The command failed + # -

- - - Flutter - -

- - -[![Flutter CI Status](https://flutter-dashboard.appspot.com/api/public/build-status-badge?repo=flutter)](https://flutter-dashboard.appspot.com/#/build?repo=flutter) -[![Discord badge][]][Discord instructions] -[![Twitter handle][]][Twitter badge] -[![codecov](https://codecov.io/gh/flutter/flutter/branch/master/graph/badge.svg?token=11yDrJU2M2)](https://codecov.io/gh/flutter/flutter) -[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/5631/badge)](https://bestpractices.coreinfrastructure.org/projects/5631) -[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/flutter/flutter/badge)](https://deps.dev/project/github/flutter%2Fflutter) -[![SLSA 1](https://slsa.dev/images/gh-badge-level1.svg)](https://slsa.dev) - -Flutter is Google's SDK for crafting beautiful, fast user experiences for -mobile, web, and desktop from a single codebase. Flutter works with existing -code, is used by developers and organizations around the world, and is free and -open source. - -## Documentation - -* [Install Flutter](https://flutter.dev/get-started/) -* [Flutter documentation](https://docs.flutter.dev/) -* [Development wiki](https://github.com/flutter/flutter/wiki) -* [Contributing to Flutter](https://github.com/flutter/flutter/blob/master/CONTRIBUTING.md) - -For announcements about new releases, follow the -[flutter-announce@googlegroups.com](https://groups.google.com/forum/#!forum/flutter-announce) -mailing list. Our documentation also tracks [breaking -changes](https://docs.flutter.dev/release/breaking-changes) across releases. - -## Terms of service - -The Flutter tool may occasionally download resources from Google servers. By -downloading or using the Flutter SDK, you agree to the Google Terms of Service: -https://policies.google.com/terms - -For example, when installed from GitHub (as opposed to from a prepackaged -archive), the Flutter tool will download the Dart SDK from Google servers -immediately when first run, as it is used to execute the `flutter` tool itself. -This will also occur when Flutter is upgraded (e.g. by running the `flutter -upgrade` command). - -## About Flutter - -We think Flutter will help you create beautiful, fast apps, with a productive, -extensible and open development model, whether you're targeting iOS or Android, -web, Windows, macOS, Linux or embedding it as the UI toolkit for a platform of -your choice. - -### Beautiful user experiences - -We want to enable designers to deliver their full creative vision without being -forced to water it down due to limitations of the underlying framework. -Flutter's [layered architecture] gives you control over every pixel on the -screen and its powerful compositing capabilities let you overlay and animate -graphics, video, text, and controls without limitation. Flutter includes a full -[set of widgets][widget catalog] that deliver pixel-perfect experiences whether -you're building for iOS ([Cupertino]) or other platforms ([Material]), along with -support for customizing or creating entirely new visual components. - -

Reflectly hero image

- -### Fast results - -Flutter is fast. It's powered by hardware-accelerated 2D graphics -libraries like [Skia] (that underpins Chrome and Android) and -[Impeller]. We architected Flutter to -support glitch-free, jank-free graphics at the native speed of your device. - -Flutter code is powered by the world-class [Dart platform], which enables -compilation to 32-bit and 64-bit ARM machine code for iOS and Android, -JavaScript and WebAssembly for the web, as well as Intel x64 and ARM -for desktop devices. - -

Dart diagram

- -### Productive development - -Flutter offers [stateful hot reload][Hot reload], allowing you to make changes to your code -and see the results instantly without restarting your app or losing its state. - -[![Hot reload animation][]][Hot reload] - -### Extensible and open model - -Flutter works with any development tool (or none at all), and also includes -editor plug-ins for both [Visual Studio Code] and [IntelliJ / Android Studio]. -Flutter provides [tens of thousands of packages][Flutter packages] to speed your -development, regardless of your target platform. And accessing other native code -is easy, with support for both FFI ([on Android][Android FFI], [on iOS][iOS FFI], -[on macOS][macOS FFI], and [on Windows][Windows FFI]) as well as -[platform-specific APIs][platform channels]. - -Flutter is a fully open-source project, and we welcome contributions. -Information on how to get started can be found in our -[contributor guide](CONTRIBUTING.md). - -[flutter.dev]: https://flutter.dev -[Discord instructions]: https://github.com/flutter/flutter/wiki/Chat -[Discord badge]: https://img.shields.io/discord/608014603317936148?logo=discord -[Twitter handle]: https://img.shields.io/twitter/follow/flutterdev.svg?style=social&label=Follow -[Twitter badge]: https://twitter.com/intent/follow?screen_name=flutterdev -[layered architecture]: https://docs.flutter.dev/resources/inside-flutter -[architectural overview]: https://docs.flutter.dev/resources/architectural-overview -[widget catalog]: https://flutter.dev/widgets/ -[Cupertino]: https://docs.flutter.dev/development/ui/widgets/cupertino -[Material]: https://docs.flutter.dev/development/ui/widgets/material -[Skia]: https://skia.org/ -[Dart platform]: https://dart.dev/ -[Hot reload animation]: https://github.com/flutter/website/blob/main/src/assets/images/docs/tools/android-studio/hot-reload.gif?raw=true -[Hot reload]: https://docs.flutter.dev/development/tools/hot-reload -[Visual Studio Code]: https://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter -[IntelliJ / Android Studio]: https://plugins.jetbrains.com/plugin/9212-flutter -[Flutter packages]: https://pub.dev/flutter -[Android FFI]: https://docs.flutter.dev/development/platform-integration/android/c-interop -[iOS FFI]: https://docs.flutter.dev/development/platform-integration/ios/c-interop -[macOS FFI]: https://docs.flutter.dev/development/platform-integration/macos/c-interop -[Windows FFI]: https://docs.flutter.dev/development/platform-integration/windows/building#integrating-with-windows -[platform channels]: https://docs.flutter.dev/development/platform-integration/platform-channels -[interop example]: https://github.com/flutter/flutter/tree/master/examples/platform_channel -[Impeller]: https://docs.flutter.dev/perf/impeller +Flutter SDK 仓库 +============== + +原始仓来源:https://github.com/flutter/flutter + +## 仓库说明 +1. 本仓库是基于Flutter SDK对于OpenHarmony平台的兼容拓展,可支持IDE或者终端使用Flutter Tools指令编译和构建OpenHarmony应用程序。 +2. 本仓库基于Flutter官方社区3.22.0版本构建 + * [sdk基础版本链接](https://github.com/flutter/flutter/commit/5dcb86f68f239346676ceb1ed1ea385bd215fba1) + * [engine基础版本链接](https://github.com/flutter/engine/commit/f6344b75dcf861d8bf1f1322780b8811f982e31a) + +## 升级指导 +1. 如果您的项目希望从鸿蒙3.7.12版本升级到3.22.0版本 + * 环境依赖:两者环境配置一致,无需额外修改 + * 从3.7.12->3.22.0的官方特性新增与变更请参考[Release Notes](https://docs.flutter.dev/release/release-notes) + * 官方兼容性变更请参考[升级指导](https://docs.flutter.dev/release/breaking-changes) + * 渲染引擎:新增impeller-vulkan模式(默认,可切换为skia-gl) + * 三方库 + - 纯dart库请升级到指定版本以支持3.22.0 + - [OpenHarmony-SIG/flutter_packages](https://gitee.com/openharmony-sig/flutter_packages/blob/master/README.md)中的package在3.22.0版本已经过一轮简单的可用性测试,如果在您使用中有任何问题,烦请创建issue跟踪解决。 + +2. 如果您的项目希望从安卓或ios等版本迁移到鸿蒙适配3.22.0版本,请参考剩余指导文档。 + +## 开发文档 +[参考文档](https://gitee.com/openharmony-sig/flutter_samples/tree/master/ohos/docs) + +## 环境依赖 + +* 开发系统 + + Flutter Tools指令目前已支持在Linux、Mac和Windows下使用。 + +* 环境配置 + **请从[鸿蒙SDK](https://developer.huawei.com/consumer/cn/develop)下载配套开发工具** + *下列环境变量配置,类Unix系统(Linux、Mac),下可直接参照配置,Windows下环境变量配置请在‘编辑系统环境变量’中设置* + + 1. 配置HarmonyOS SDK和环境变量 + * API12, deveco-studio-5.0 或 command-line-tools-5.0 (推荐使用5.0.0 Release或更新版本) + * 配置 Java17 + * 配置环境变量 (SDK, node, ohpm, hvigor) + + ```sh + export TOOL_HOME=/Applications/DevEco-Studio.app/Contents # mac环境 + export DEVECO_SDK_HOME=$TOOL_HOME/sdk # command-line-tools/sdk + export PATH=$TOOL_HOME/tools/ohpm/bin:$PATH # command-line-tools/ohpm/bin + export PATH=$TOOL_HOME/tools/hvigor/bin:$PATH # command-line-tools/hvigor/bin + export PATH=$TOOL_HOME/tools/node/bin:$PATH # command-line-tools/tool/node/bin + ``` + + 2. 通过代码工具下载当前仓库代码`git clone https://gitee.com/openharmony-sig/flutter_flutter.git`,指定dev或master分支,并配置环境 + + ```sh + export PUB_CACHE=D:/PUB + export PATH=/bin:$PATH + export PUB_HOSTED_URL=https://pub.flutter-io.cn + export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn + ``` + + 3. 应用构建依赖flutter engine构建产物与engine host,默认从云端获取。也可以手工指定 + - 使用示例:`--local-engine=src/out/ --local-engine-host=src/our/` + 均在 `src/out` 路径下。不同构建类型的产物分别在 `ohos_debug_unopt_arm64`、 `ohos_release_arm64` 和 `ohos_profile_arm64` 目录下。engine host 的构建类型也有三种,分别在 `host_debug_unopt` 、`host_release` 与 `host_profile` 目录中。构建需要根据不同的构建类型来指定不同的目录。 + + ```sh + #依赖缓存 + export PUB_CACHE=D:/PUB(自定义路径) + + # 国内镜像 + export PUB_HOSTED_URL=https://pub.flutter-io.cn + export FLUTTER_STORAGE_BASE_URL=https://storage.flutter-io.cn + + # 拉取下来的flutter_flutter/bin目录 + export PATH=/home//ohos/flutter_flutter/bin:$PATH + + # HamonyOS SDK + export TOOL_HOME=/Applications/DevEco-Studio.app/Contents # mac环境 + export DEVECO_SDK_HOME=$TOOL_HOME/sdk # command-line-tools/sdk + export PATH=$TOOL_HOME/tools/ohpm/bin:$PATH # command-line-tools/ohpm/bin + export PATH=$TOOL_HOME/tools/hvigor/bin:$PATH # command-line-tools/hvigor/bin + export PATH=$TOOL_HOME/tools/node/bin:$PATH # command-line-tools/tool/node/bin + ``` + +## 构建步骤 + +1. 运行 `flutter doctor -v` 检查环境变量配置是否正确,**Futter**与**OpenHarmony**应都为ok标识,若两处提示缺少环境,按提示补上相应环境即可。 + +2. 创建工程与编译命令,编译产物在\/ohos/entry/build/default/outputs/default/entry-default-signed.hap下。 + + ``` + # 创建工程 + flutter create --platforms ohos + + # 进入工程根目录编译 + # 示例:flutter build hap [--target-platform ohos-arm64] [--local-engine=/src/out/ohos_release_arm64] --release + flutter build hap --target-platform ohos-arm64 -- [--local-engine=src/out/ --local-engine-host=src/out//] + ``` + 2.1 创建工程并打开impeller开关 + 当前Flutter ohos平台中支持impeller-vulkan渲染模式,可通过开关控制是否打开。 + 开关位于`buildinfo.json5`文件中,如果选择关闭impeller渲染,可将json文件中的value改为false。下一次运行时即可关闭。 + 文件路径:`ohos/entry/src/main/resources/rawfile/buildinfo.json5` + (初次flutter create之后,配置文件位于profile目录,首次run或build之后会搬移到rawfile目录) + + 文件内容: + ```json + { + "string": [ + { + "name": "enable_impeller", + "value": "true" + } + ] + } + ``` + 新建工程默认打开impeller选项。 + 对于旧工程,可将以上buildinfo.json5文件复制到工程目录的对应路径下(rawfile目录),并修改value值即可实现开关功能。如果不添加开关,则默认打开enable-impeller。 + +3. 通过`flutter devices`指令发现ohos设备之后,使用 `hdc -t install `进行安装。 + +4. 也可直接使用下列指令运行: +``` + flutter run --debug [--local-engine=/src/out/ohos_debug_unopt_arm64] [--local-engine-host=/src/out/host_debug_unopt] -d +``` + +5. 构建app包命令: + ``` + # 示例:flutter build app --release [--local-engine=/src/out/ohos_release_arm64] [--local-engine-host=/src/out/host_release] + flutter build app --release + ``` + +## 版本说明 + - [3.22.0-ohos-0.1.0 Beta](/release-notes/Flutter%203.22.0-ohos%200.1.0%20ReleaseNote.md) + +## 已兼容OpenHarmony开发的指令列表 +| 指令名称 | 指令描述 | 使用说明 | +| ------- | ------- |-------------------------------------------------------------------| +| doctor | 环境检测 | flutter doctor | +| config | 环境配置 | flutter config --\ \ | +| create | 创建新项目 | flutter create --platforms ohos,android,ios --org \ \ | +| create | 创建module模板 | flutter create -t module \ | +| create | 创建plugin模板 | flutter create -t plugin --platforms ohos,android,ios \ | +| create | 创建plugin_ffi模板 | flutter create -t plugin_ffi --platforms ohos,android,ios \ | +| devices | 已连接设备查找 | flutter devices | +| install | 应用安装 | flutter install -t \ \ | +| assemble | 资源打包 | flutter assemble | +| build | 测试应用构建 | flutter build hap --debug [--target-platform ohos-arm64] [--local-engine=\<兼容ohos的debug engine产物路径\>] | +| build | 正式应用构建 | flutter build hap --release [--target-platform ohos-arm64] [--local-engine=\<兼容ohos的release engine产物路径\>] | +| run | 应用运行 | flutter run [--local-engine=\<兼容ohos的engine产物路径\>] | +| attach | 调试模式 | flutter attach | +| screenshot | 截屏 | flutter screenshot | +| pub | 获取依赖 | flutter pub get | +| clean | 清除项目依赖 | flutter clean | +| cache | 清除全局缓存数据 | flutter pub cache clean | + +附:[Flutter三方库适配计划](https://docs.qq.com/sheet/DVVJDWWt1V09zUFN2) + + +## 常见问题 + +1. 切换FLUTTER_STORAGE_BASE_URL后需删除\/bin/cache 目录,并在项目中执行flutter clean后再运行 + +2. 若出现报错:`The SDK license agreement is not accepted`,参考执行以下命令后再次编译: + + ``` + ./ohsdkmgr install ets:9 js:9 native:9 previewer:9 toolchains:9 --sdk-directory='/home/xc/code/sdk/ohos-sdk/' --accept-license + ``` + +3. 如果你使用的是DevEco Studio的Beta版本,编译工程时遇到“must have required property 'compatibleSdkVersion', location: demo/ohos/build-profile.json5:17:11"错误,请参考《DevEco Studio环境配置指导.docx》中的‘6 创建工程和运行Hello World’【配置插件】章节修改 hvigor/hvigor-config.json5文件。 + +4. 若提示安装报错:`fail to verify pkcs7 file` 请执行指令 + + ``` + hdc shell param set persist.bms.ohCert.verify true + ``` +5. linux虚拟机通过hdc无法直接发现OpenHarmony设备 + + 解决方案:在windows宿主机中,开启hdc server,具体指令如下: + ``` + hdc kill + hdc -s serverIP:8710 -m + ``` + 在linux中配置环境变量: + ``` + HDC_SERVER= + HDC_SERVER_PORT=8710 + ``` + + 配置完成后flutter sdk可以通过hdc server完成设备连接,也可参考[官方指导](https://docs.openharmony.cn/pages/v4.0/zh-cn/device-dev/subsystems/subsys-toolchain-hdc-guide.md/#hdc-client%E5%A6%82%E4%BD%95%E8%BF%9C%E7%A8%8B%E8%AE%BF%E9%97%AEhdc-server)。 + +6. 构建Hap任务时报错:Error: The hvigor depends on the npmrc file. Configure the npmrc file first. + + + 请在用户目录`~`下创建文件`.npmrc`,该配置也可参考[DevEco Studio官方文档](https://developer.harmonyos.com/cn/docs/documentation/doc-guides-V3/environment_config-0000001052902427-V3),编辑内容如下: + + ``` + registry=https://repo.huaweicloud.com/repository/npm/ + @ohos:registry=https://repo.harmonyos.com/npm/ + ``` + +7. 查日志时,存在日志丢失现象。 + 解决方案:关闭全局日志,只打开自己领域的日志 + + ``` + 步骤一:关闭所有领域的日志打印(部分特殊日志无法关闭) + hdc shell hilog -b X + 步骤二:只打开自己领域的日志 + hdc shell hilog -D + 其中为日志打印的级别:D/I/W/E/F,为Tag前面的数字 + 举例: + 打印A00000/XComFlutterOHOS_Native的日志,需要设置hdc shell hilog -b D -D A00000 + 注:上面的设置在机器重启后会失效,如果要继续使用,需要重新设置。 + ``` +8. 若Api11 Beta1版本的机器上无法启动debug签名的应用,可以通过将签名换成正式签名,或在手机端打开开发者模式解决(步骤:设置->通用->开发者模式) + +9. 如果报`Invalid CEN header (invalid zip64 extra data field size)`异常,请更换Jdk版本,参见[JDK-8313765](https://bugs.openjdk.org/browse/JDK-8313765) + +10. 运行debug版本的flutter应用用到鸿蒙设备后报错(release和profile版本正常) + 1. 报错信息: `Error while initializing the Dart VM: Wrong full snapshot version, expected '8af474944053df1f0a3be6e6165fa7cf' found 'adb4292f3ec25074ca70abcd2d5c7251'` + 2. 解决方案: 依次执行以下操作 + 1. 设置环境变量 `export FLUTTER_STORAGE_BASE_URL=https://flutter-ohos.obs.cn-south-1.myhuaweicloud.com` + 2. 删除 /bin/cache 目录下的缓存 + 3. 执行 `flutter clean`,清除项目编译缓存 + 4. 运行 `flutter run -d $DEVICE --debug` + 3. 补充信息: 运行android或ios出现类似错误,也可以尝试还原环境变量 FLUTTER_STORAGE_BASE_URL ,清除缓存后重新运行。 + +11. Beta2版本的ROM更新后,不再支持申请有执行权限的匿名内存,导致debug运行闪退。 + 1. 解决方案:更新 flutter_flutter 到 a44b8a6d (2024-07-25) 之后的版本。 + 2. 关键日志: + + ``` + #20 at attachToNative (oh_modules/.ohpm/@ohos+flutter_ohos@g8zhdaqwu8gotysbmqcstpfpcpy=/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/FlutterNapi.ets:78:32) + #21 at attachToNapi (oh_modules/.ohpm/@ohos+flutter_ohos@g8zhdaqwu8gotysbmqcstpfpcpy=/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/FlutterEngine.ets:144:5) + #22 at init (oh_modules/.ohpm/@ohos+flutter_ohos@g8zhdaqwu8gotysbmqcstpfpcpy=/oh_modules/@ohos/flutter_ohos/src/main/ets/embedding/engine/FlutterEngine.ets:133:7) + ``` + +12. 构建Hap命令直接执行`flutter build hap`即可,不再需要`--local-engine`参数,直接从云端获取编译产物。 + +13. 配置环境完成后执行 flutter 命令 出现闪退。 + 1. 解决方案:windows环境中添加git环境变量配置。 + ``` + export PATH=/cmd:$PATH + ``` + +14. 执行`flutter pub cache clean` 正常 执行`flutter clean` 报错,按照报错信息执行 update 命令也没有效果。 + 1. 解决方案:通过注释掉 build.json5 文件中的配置规避。 + 2. 报错信息: + ``` + #Parse ohos module. json5 error: Exception: Can not found module.json5 at + #D:\pub_cache\git\flutter_packages-b00939bb44d018f0710d1b080d91dcf4c34ed06\packages\video_player\video_player_ohos\ohossrc\main\module.json5. + #You need to update the Flutter plugin project structure. + #See + #https://gitee.com/openharmony-sig/flutter_samples/tree/master/ohos/docs/09_specifications/update_flutter_plugin_structure.md + ``` + +15. 执行`flutter build hap` 时遇到路径校验报错。 + 1. 解决方案: + ·打开 deveco 安装路径 D:\DevEco Studio\tools\hvigor\hvigor-ohos-plugin\res\schemas 下的 ohos-project-build-profile-schema.json文件。 + ·在该文件中找到包含:"pattern": "^(\\./|\\.\\./)[\\s\\S]+$"的行,并删除此行。 + 2. 报错信息: + ``` + #hvigor ERROR: Schema validate failed. + # Detail: Please check the following fields. + #instancePath: 'modules[1].scrPath', + #keyword: 'pattern' + #params: { pattern:'^(\\./|\\.\\./)[\\s\\S]+$' }, + #message: 'must match pattern "^(\\./|\\.\\./)[\\s\\S]+$"', + #location: 'D:/work/videoplayerdemo/video_cannot_stop_at_background/ohos/build-profile.json:42:146' + ``` + +16. 执行`flutter build hap` 报错。 + 1. 解决方案:打开 deveco 安装路径 D:\DevEco Studio\tools\hvigor\hvigor-ohos-plugin\src\model\module 下的 core-module-model-impl.js, + 修改 findBelongProjectPath 方法(需要管理员权限,可另存为后替换) + ``` + findBelongProjectPath(e) { + if (e === path_1.default.dirname(e)) { + return this.parentProject.getProjectDir() + } + } + ``` + 2. 报错信息: + ``` + # hvigor ERROR: Cannot find belonging project path for module at D:\. + # hvigor ERROR: BUILD FAILED in 2s 556ms. + #Running Hvigor task assembleHap... + #Oops; flutter has exited unexpectedly: "ProcessException: The command failed + # { + if (err.code) { + hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); + return; + } + hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', + JSON.stringify(data) ?? ''); + }); + } + + onWindowStageDestroy() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageDestroy'); + } + + onForeground() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onForeground'); + } + + onBackground() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onBackground'); + } +} \ No newline at end of file diff --git a/dev/benchmarks/complex_layout/ohos/entry/src/ohosTest/ets/testability/pages/Index.ets b/dev/benchmarks/complex_layout/ohos/entry/src/ohosTest/ets/testability/pages/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..cef0447cd2f137ef82d223ead2e156808878ab90 --- /dev/null +++ b/dev/benchmarks/complex_layout/ohos/entry/src/ohosTest/ets/testability/pages/Index.ets @@ -0,0 +1,49 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development 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'; + +@Entry +@Component +struct Index { + aboutToAppear() { + hilog.info(0x0000, 'testTag', '%{public}s', '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/dev/benchmarks/complex_layout/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts b/dev/benchmarks/complex_layout/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts new file mode 100644 index 0000000000000000000000000000000000000000..1def08f2e9dcbfa3454a07b7a3b82b173bb90d02 --- /dev/null +++ b/dev/benchmarks/complex_layout/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts @@ -0,0 +1,64 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development 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'); + } +} \ No newline at end of file diff --git a/dev/benchmarks/complex_layout/ohos/entry/src/ohosTest/module.json5 b/dev/benchmarks/complex_layout/ohos/entry/src/ohosTest/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..fab77ce2e0c61e3ad010bab5b27ccbd15f9a8c96 --- /dev/null +++ b/dev/benchmarks/complex_layout/ohos/entry/src/ohosTest/module.json5 @@ -0,0 +1,51 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +{ + "module": { + "name": "entry_test", + "type": "feature", + "description": "$string:module_test_desc", + "mainElement": "TestAbility", + "deviceTypes": [ + "phone" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:test_pages", + "abilities": [ + { + "name": "TestAbility", + "srcEntry": "./ets/testability/TestAbility.ets", + "description": "$string:TestAbility_desc", + "icon": "$media:icon", + "label": "$string:TestAbility_label", + "exported": true, + "startWindowIcon": "$media:icon", + "startWindowBackground": "$color:start_window_background", + "skills": [ + { + "actions": [ + "action.system.home" + ], + "entities": [ + "entity.system.home" + ] + } + ] + } + ] + } +} diff --git a/dev/benchmarks/complex_layout/ohos/entry/src/ohosTest/resources/base/element/color.json b/dev/benchmarks/complex_layout/ohos/entry/src/ohosTest/resources/base/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..3c712962da3c2751c2b9ddb53559afcbd2b54a02 --- /dev/null +++ b/dev/benchmarks/complex_layout/ohos/entry/src/ohosTest/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + } + ] +} \ No newline at end of file diff --git a/dev/benchmarks/complex_layout/ohos/entry/src/ohosTest/resources/base/element/string.json b/dev/benchmarks/complex_layout/ohos/entry/src/ohosTest/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..65d8fa5a7cf54aa3943dcd0214f58d1771bc1f6c --- /dev/null +++ b/dev/benchmarks/complex_layout/ohos/entry/src/ohosTest/resources/base/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_test_desc", + "value": "test ability description" + }, + { + "name": "TestAbility_desc", + "value": "the test ability" + }, + { + "name": "TestAbility_label", + "value": "test label" + } + ] +} \ No newline at end of file diff --git a/dev/benchmarks/complex_layout/ohos/entry/src/ohosTest/resources/base/media/icon.png b/dev/benchmarks/complex_layout/ohos/entry/src/ohosTest/resources/base/media/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c Binary files /dev/null and b/dev/benchmarks/complex_layout/ohos/entry/src/ohosTest/resources/base/media/icon.png differ diff --git a/dev/benchmarks/complex_layout/ohos/entry/src/ohosTest/resources/base/profile/test_pages.json b/dev/benchmarks/complex_layout/ohos/entry/src/ohosTest/resources/base/profile/test_pages.json new file mode 100644 index 0000000000000000000000000000000000000000..b7e7343cacb32ce982a45e76daad86e435e054fe --- /dev/null +++ b/dev/benchmarks/complex_layout/ohos/entry/src/ohosTest/resources/base/profile/test_pages.json @@ -0,0 +1,5 @@ +{ + "src": [ + "testability/pages/Index" + ] +} diff --git a/dev/benchmarks/complex_layout/ohos/hvigor/hvigor-config.json5 b/dev/benchmarks/complex_layout/ohos/hvigor/hvigor-config.json5 new file mode 100644 index 0000000000000000000000000000000000000000..541ba35711b75986f9295410ee38fdb8f2572878 --- /dev/null +++ b/dev/benchmarks/complex_layout/ohos/hvigor/hvigor-config.json5 @@ -0,0 +1,20 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +{ + "modelVersion": "5.0.0", + "dependencies": { + } +} \ No newline at end of file diff --git a/dev/benchmarks/complex_layout/ohos/hvigorfile.ts b/dev/benchmarks/complex_layout/ohos/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..8f2d2aafe6d6a3a71a9944ebd0c91fbc308ac9d1 --- /dev/null +++ b/dev/benchmarks/complex_layout/ohos/hvigorfile.ts @@ -0,0 +1,21 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import { appTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} \ No newline at end of file diff --git a/dev/benchmarks/complex_layout/ohos/oh-package.json5 b/dev/benchmarks/complex_layout/ohos/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..0547594c0d9fe2de363a80af708744809205c3d1 --- /dev/null +++ b/dev/benchmarks/complex_layout/ohos/oh-package.json5 @@ -0,0 +1,33 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +{ + "modelVersion": "5.0.0", + "name": "complex_layout", + "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" + } +} diff --git a/dev/benchmarks/macrobenchmarks/ohos/.gitignore b/dev/benchmarks/macrobenchmarks/ohos/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..6ca13b3170eec5dd5ac5ad7f1c4dd0118845f473 --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/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/dev/benchmarks/macrobenchmarks/ohos/AppScope/app.json5 b/dev/benchmarks/macrobenchmarks/ohos/AppScope/app.json5 new file mode 100644 index 0000000000000000000000000000000000000000..2da1f2376301f72fca68975274e6a17c2d5b10cb --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/ohos/AppScope/app.json5 @@ -0,0 +1,10 @@ +{ + "app": { + "bundleName": "com.example.macrobenchmarks", + "vendor": "example", + "versionCode": 1000000, + "versionName": "1.0.0", + "icon": "$media:app_icon", + "label": "$string:app_name" + } +} diff --git a/dev/benchmarks/macrobenchmarks/ohos/AppScope/resources/base/element/string.json b/dev/benchmarks/macrobenchmarks/ohos/AppScope/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..eb870aabd077d622d40df3a6fc2caedae864e22a --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/ohos/AppScope/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "macrobenchmarks" + } + ] +} diff --git a/dev/benchmarks/macrobenchmarks/ohos/AppScope/resources/base/media/app_icon.png b/dev/benchmarks/macrobenchmarks/ohos/AppScope/resources/base/media/app_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c Binary files /dev/null and b/dev/benchmarks/macrobenchmarks/ohos/AppScope/resources/base/media/app_icon.png differ diff --git a/dev/benchmarks/macrobenchmarks/ohos/build-profile.json5 b/dev/benchmarks/macrobenchmarks/ohos/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..5fcb3dc466ec0efe97a15ac93a397ddcc0ec6c56 --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/ohos/build-profile.json5 @@ -0,0 +1,42 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +{ + "app": { + "signingConfigs": [], + "products": [ + { + "name": "default", + "signingConfig": "default", + "compatibleSdkVersion": "4.1.0(11)", + "runtimeOS": "HarmonyOS", + } + ] + }, + "modules": [ + { + "name": "entry", + "srcPath": "./entry", + "targets": [ + { + "name": "default", + "applyToProducts": [ + "default" + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/dev/benchmarks/macrobenchmarks/ohos/dta/icudtl.dat b/dev/benchmarks/macrobenchmarks/ohos/dta/icudtl.dat new file mode 100644 index 0000000000000000000000000000000000000000..d1f10917ab52e3ebd251abd7f5377d7196b80d67 Binary files /dev/null and b/dev/benchmarks/macrobenchmarks/ohos/dta/icudtl.dat differ diff --git a/dev/benchmarks/macrobenchmarks/ohos/entry/.gitignore b/dev/benchmarks/macrobenchmarks/ohos/entry/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..2795a1c5b1fe53659dd1b71d90ba0592eaf7e043 --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/ohos/entry/.gitignore @@ -0,0 +1,7 @@ + +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ No newline at end of file diff --git a/dev/benchmarks/macrobenchmarks/ohos/entry/build-profile.json5 b/dev/benchmarks/macrobenchmarks/ohos/entry/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..633d360fbc91a3186a23b66ab71b27e5618944cb --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/ohos/entry/build-profile.json5 @@ -0,0 +1,29 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +{ + "apiType": 'stageMode', + "buildOption": { + }, + "targets": [ + { + "name": "default", + "runtimeOS": "HarmonyOS" + }, + { + "name": "ohosTest", + } + ] +} \ No newline at end of file diff --git a/dev/benchmarks/macrobenchmarks/ohos/entry/hvigorfile.ts b/dev/benchmarks/macrobenchmarks/ohos/entry/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..894fc15c6b793f085e6c8506e43d719af658e8ff --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/ohos/entry/hvigorfile.ts @@ -0,0 +1,17 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development 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/dev/benchmarks/macrobenchmarks/ohos/entry/oh-package.json5 b/dev/benchmarks/macrobenchmarks/ohos/entry/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..dabaee13151003854589516fcfb20ccd29c44b6b --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/ohos/entry/oh-package.json5 @@ -0,0 +1,26 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +{ + "name": "entry", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "", + "author": "", + "license": "", + "dependencies": { + }, +} + diff --git a/dev/benchmarks/macrobenchmarks/ohos/entry/src/main/ets/entryability/EntryAbility.ets b/dev/benchmarks/macrobenchmarks/ohos/entry/src/main/ets/entryability/EntryAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..8bc48be8773196f34cccb15cf517f87f5c6b94d2 --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/ohos/entry/src/main/ets/entryability/EntryAbility.ets @@ -0,0 +1,24 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development 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/dev/benchmarks/macrobenchmarks/ohos/entry/src/main/ets/pages/Index.ets b/dev/benchmarks/macrobenchmarks/ohos/entry/src/main/ets/pages/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..1125f9fdd95f4310a182c1c9e3680f37f73686c9 --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/ohos/entry/src/main/ets/pages/Index.ets @@ -0,0 +1,38 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development 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 + } +} \ No newline at end of file diff --git a/dev/benchmarks/macrobenchmarks/ohos/entry/src/main/ets/plugins/GeneratedPluginRegistrant.ets b/dev/benchmarks/macrobenchmarks/ohos/entry/src/main/ets/plugins/GeneratedPluginRegistrant.ets new file mode 100644 index 0000000000000000000000000000000000000000..f28ced70d552f7990164a9caa7c82413e11534a6 --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/ohos/entry/src/main/ets/plugins/GeneratedPluginRegistrant.ets @@ -0,0 +1,24 @@ +import { FlutterEngine, Log } from '@ohos/flutter_ohos'; + +/** + * 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 { + } catch (e) { + Log.e( + TAG, + "Tried to register plugins with FlutterEngine (" + + flutterEngine + + ") failed."); + Log.e(TAG, "Received exception while registering", e); + } + } +} diff --git a/dev/benchmarks/macrobenchmarks/ohos/entry/src/main/module.json5 b/dev/benchmarks/macrobenchmarks/ohos/entry/src/main/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..7bbf78b18f39991b1404061c7437538c7d532bb7 --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/ohos/entry/src/main/module.json5 @@ -0,0 +1,53 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +{ + "module": { + "name": "entry", + "type": "entry", + "description": "$string:module_desc", + "mainElement": "EntryAbility", + "deviceTypes": [ + "phone" + ], + "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"}, + ] + } +} \ No newline at end of file diff --git a/dev/benchmarks/macrobenchmarks/ohos/entry/src/main/resources/base/element/color.json b/dev/benchmarks/macrobenchmarks/ohos/entry/src/main/resources/base/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..3c712962da3c2751c2b9ddb53559afcbd2b54a02 --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/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/dev/benchmarks/macrobenchmarks/ohos/entry/src/main/resources/base/element/string.json b/dev/benchmarks/macrobenchmarks/ohos/entry/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..9d40485cdb4242c5b91b815ff4a7a976dad9cf9c --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/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": "macrobenchmarks" + } + ] +} \ No newline at end of file diff --git a/dev/benchmarks/macrobenchmarks/ohos/entry/src/main/resources/base/media/icon.png b/dev/benchmarks/macrobenchmarks/ohos/entry/src/main/resources/base/media/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c Binary files /dev/null and b/dev/benchmarks/macrobenchmarks/ohos/entry/src/main/resources/base/media/icon.png differ diff --git a/dev/benchmarks/macrobenchmarks/ohos/entry/src/main/resources/base/profile/main_pages.json b/dev/benchmarks/macrobenchmarks/ohos/entry/src/main/resources/base/profile/main_pages.json new file mode 100644 index 0000000000000000000000000000000000000000..1898d94f58d6128ab712be2c68acc7c98e9ab9ce --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/ohos/entry/src/main/resources/base/profile/main_pages.json @@ -0,0 +1,5 @@ +{ + "src": [ + "pages/Index" + ] +} diff --git a/dev/benchmarks/macrobenchmarks/ohos/entry/src/main/resources/en_US/element/string.json b/dev/benchmarks/macrobenchmarks/ohos/entry/src/main/resources/en_US/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..9d40485cdb4242c5b91b815ff4a7a976dad9cf9c --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/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": "macrobenchmarks" + } + ] +} \ No newline at end of file diff --git a/dev/benchmarks/macrobenchmarks/ohos/entry/src/main/resources/zh_CN/element/string.json b/dev/benchmarks/macrobenchmarks/ohos/entry/src/main/resources/zh_CN/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..fdb86ef3cd7a7404954005ab0eaab8c103b11226 --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/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": "macrobenchmarks" + } + ] +} \ No newline at end of file diff --git a/dev/benchmarks/macrobenchmarks/ohos/entry/src/ohosTest/ets/test/Ability.test.ets b/dev/benchmarks/macrobenchmarks/ohos/entry/src/ohosTest/ets/test/Ability.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..25d4c71ff3cd584f5d64f6f8c0ac864928c234c4 --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/ohos/entry/src/ohosTest/ets/test/Ability.test.ets @@ -0,0 +1,50 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development 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 { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium' + +export default function abilityTest() { + describe('ActsAbilityTest', function () { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(function () { + // 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(function () { + // 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(function () { + // 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(function () { + // 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, function () { + // 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) + }) + }) +} \ No newline at end of file diff --git a/dev/benchmarks/macrobenchmarks/ohos/entry/src/ohosTest/ets/test/List.test.ets b/dev/benchmarks/macrobenchmarks/ohos/entry/src/ohosTest/ets/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..f4140030e65d20df6af30a6bf51e464dea8f8aa6 --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/ohos/entry/src/ohosTest/ets/test/List.test.ets @@ -0,0 +1,20 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development 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() +} \ No newline at end of file diff --git a/dev/benchmarks/macrobenchmarks/ohos/entry/src/ohosTest/ets/testability/TestAbility.ets b/dev/benchmarks/macrobenchmarks/ohos/entry/src/ohosTest/ets/testability/TestAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..4ca645e6013cfce8e7dbb728313cb8840c4da660 --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/ohos/entry/src/ohosTest/ets/testability/TestAbility.ets @@ -0,0 +1,63 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development 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 UIAbility from '@ohos.app.ability.UIAbility'; +import AbilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; +import hilog from '@ohos.hilog'; +import { Hypium } from '@ohos/hypium'; +import testsuite from '../test/List.test'; +import window from '@ohos.window'; + +export default class TestAbility extends UIAbility { + onCreate(want, launchParam) { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onCreate'); + hilog.info(0x0000, 'testTag', '%{public}s', 'want param:' + JSON.stringify(want) ?? ''); + hilog.info(0x0000, 'testTag', '%{public}s', 'launchParam:'+ JSON.stringify(launchParam) ?? ''); + var abilityDelegator: any + abilityDelegator = AbilityDelegatorRegistry.getAbilityDelegator() + var abilityDelegatorArguments: any + abilityDelegatorArguments = AbilityDelegatorRegistry.getArguments() + hilog.info(0x0000, 'testTag', '%{public}s', 'start run testcase!!!'); + Hypium.hypiumTest(abilityDelegator, abilityDelegatorArguments, testsuite) + } + + onDestroy() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onDestroy'); + } + + onWindowStageCreate(windowStage: window.WindowStage) { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageCreate'); + windowStage.loadContent('testability/pages/Index', (err, data) => { + if (err.code) { + hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? ''); + return; + } + hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', + JSON.stringify(data) ?? ''); + }); + } + + onWindowStageDestroy() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onWindowStageDestroy'); + } + + onForeground() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onForeground'); + } + + onBackground() { + hilog.info(0x0000, 'testTag', '%{public}s', 'TestAbility onBackground'); + } +} \ No newline at end of file diff --git a/dev/benchmarks/macrobenchmarks/ohos/entry/src/ohosTest/ets/testability/pages/Index.ets b/dev/benchmarks/macrobenchmarks/ohos/entry/src/ohosTest/ets/testability/pages/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..cef0447cd2f137ef82d223ead2e156808878ab90 --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/ohos/entry/src/ohosTest/ets/testability/pages/Index.ets @@ -0,0 +1,49 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development 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'; + +@Entry +@Component +struct Index { + aboutToAppear() { + hilog.info(0x0000, 'testTag', '%{public}s', '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/dev/benchmarks/macrobenchmarks/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts b/dev/benchmarks/macrobenchmarks/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts new file mode 100644 index 0000000000000000000000000000000000000000..1def08f2e9dcbfa3454a07b7a3b82b173bb90d02 --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/ohos/entry/src/ohosTest/ets/testrunner/OpenHarmonyTestRunner.ts @@ -0,0 +1,64 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development 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'); + } +} \ No newline at end of file diff --git a/dev/benchmarks/macrobenchmarks/ohos/entry/src/ohosTest/module.json5 b/dev/benchmarks/macrobenchmarks/ohos/entry/src/ohosTest/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..fab77ce2e0c61e3ad010bab5b27ccbd15f9a8c96 --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/ohos/entry/src/ohosTest/module.json5 @@ -0,0 +1,51 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +{ + "module": { + "name": "entry_test", + "type": "feature", + "description": "$string:module_test_desc", + "mainElement": "TestAbility", + "deviceTypes": [ + "phone" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:test_pages", + "abilities": [ + { + "name": "TestAbility", + "srcEntry": "./ets/testability/TestAbility.ets", + "description": "$string:TestAbility_desc", + "icon": "$media:icon", + "label": "$string:TestAbility_label", + "exported": true, + "startWindowIcon": "$media:icon", + "startWindowBackground": "$color:start_window_background", + "skills": [ + { + "actions": [ + "action.system.home" + ], + "entities": [ + "entity.system.home" + ] + } + ] + } + ] + } +} diff --git a/dev/benchmarks/macrobenchmarks/ohos/entry/src/ohosTest/resources/base/element/color.json b/dev/benchmarks/macrobenchmarks/ohos/entry/src/ohosTest/resources/base/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..3c712962da3c2751c2b9ddb53559afcbd2b54a02 --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/ohos/entry/src/ohosTest/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + } + ] +} \ No newline at end of file diff --git a/dev/benchmarks/macrobenchmarks/ohos/entry/src/ohosTest/resources/base/element/string.json b/dev/benchmarks/macrobenchmarks/ohos/entry/src/ohosTest/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..65d8fa5a7cf54aa3943dcd0214f58d1771bc1f6c --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/ohos/entry/src/ohosTest/resources/base/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_test_desc", + "value": "test ability description" + }, + { + "name": "TestAbility_desc", + "value": "the test ability" + }, + { + "name": "TestAbility_label", + "value": "test label" + } + ] +} \ No newline at end of file diff --git a/dev/benchmarks/macrobenchmarks/ohos/entry/src/ohosTest/resources/base/media/icon.png b/dev/benchmarks/macrobenchmarks/ohos/entry/src/ohosTest/resources/base/media/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c Binary files /dev/null and b/dev/benchmarks/macrobenchmarks/ohos/entry/src/ohosTest/resources/base/media/icon.png differ diff --git a/dev/benchmarks/macrobenchmarks/ohos/entry/src/ohosTest/resources/base/profile/test_pages.json b/dev/benchmarks/macrobenchmarks/ohos/entry/src/ohosTest/resources/base/profile/test_pages.json new file mode 100644 index 0000000000000000000000000000000000000000..b7e7343cacb32ce982a45e76daad86e435e054fe --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/ohos/entry/src/ohosTest/resources/base/profile/test_pages.json @@ -0,0 +1,5 @@ +{ + "src": [ + "testability/pages/Index" + ] +} diff --git a/dev/benchmarks/macrobenchmarks/ohos/hvigor/hvigor-config.json5 b/dev/benchmarks/macrobenchmarks/ohos/hvigor/hvigor-config.json5 new file mode 100644 index 0000000000000000000000000000000000000000..541ba35711b75986f9295410ee38fdb8f2572878 --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/ohos/hvigor/hvigor-config.json5 @@ -0,0 +1,20 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +{ + "modelVersion": "5.0.0", + "dependencies": { + } +} \ No newline at end of file diff --git a/dev/benchmarks/macrobenchmarks/ohos/hvigorfile.ts b/dev/benchmarks/macrobenchmarks/ohos/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..8f2d2aafe6d6a3a71a9944ebd0c91fbc308ac9d1 --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/ohos/hvigorfile.ts @@ -0,0 +1,21 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +import { appTasks } from '@ohos/hvigor-ohos-plugin'; + +export default { + system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */ + plugins:[] /* Custom plugin to extend the functionality of Hvigor. */ +} \ No newline at end of file diff --git a/dev/benchmarks/macrobenchmarks/ohos/oh-package.json5 b/dev/benchmarks/macrobenchmarks/ohos/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..c640999ec2753fcc36c68f3ac72342b35278a71d --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/ohos/oh-package.json5 @@ -0,0 +1,33 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development Co., Ltd. +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +{ + "modelVersion": "5.0.0", + "name": "macrobenchmarks", + "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" + } +} diff --git a/dev/benchmarks/macrobenchmarks/test_driver/large_image_changer_test.dart b/dev/benchmarks/macrobenchmarks/test_driver/large_image_changer_test.dart index 184c6db9fd4737d97507a4e1ed0c23f01523a6a2..dca971527bd1aedb0409690f8e0f253aedf31d0c 100644 --- a/dev/benchmarks/macrobenchmarks/test_driver/large_image_changer_test.dart +++ b/dev/benchmarks/macrobenchmarks/test_driver/large_image_changer_test.dart @@ -23,6 +23,7 @@ Future main() async { }); } case 'TargetPlatform.android': + case 'TargetPlatform.ohos': { // Just run for 20 seconds to collect memory usage. The widget itself // animates during this time. diff --git a/dev/devicelab/lib/tasks/perf_tests.dart b/dev/devicelab/lib/tasks/perf_tests.dart index c6d1a2e22a537f609851d901860f23d51a8c600a..59cf2bce0e4759add4e90cfcdd6929101e18c58b 100644 --- a/dev/devicelab/lib/tasks/perf_tests.dart +++ b/dev/devicelab/lib/tasks/perf_tests.dart @@ -1382,6 +1382,8 @@ class PerfTest { '--use-existing-app', existingApp ], + if (writeSkslFileName != null) ...['--write-sksl-on-exit', writeSkslFileName], + if (cacheSkSL) '--cache-sksl', if (dartDefine.isNotEmpty) ...['--dart-define', dartDefine], if (enableImpeller != null && enableImpeller!) '--enable-impeller', if (enableImpeller != null && !enableImpeller!) @@ -1399,7 +1401,8 @@ class PerfTest { await resetPlist(); } - final Map data = json.decode( + final Map data = json.decode + file('${_testOutputDirectory(testDirectory)}/$resultFilename.json').readAsStringSync(), ) as Map; diff --git a/dev/integration_tests/flutter_gallery/lib/demo/material/drawer_demo.dart b/dev/integration_tests/flutter_gallery/lib/demo/material/drawer_demo.dart index bf1291f9b8d92d02da5478c8d6b854b3ef3c696d..82e402eab037b1d5695ca9957f15b289a70429dc 100644 --- a/dev/integration_tests/flutter_gallery/lib/demo/material/drawer_demo.dart +++ b/dev/integration_tests/flutter_gallery/lib/demo/material/drawer_demo.dart @@ -65,6 +65,7 @@ class _DrawerDemoState extends State with TickerProviderStateMixin { case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.linux: + case TargetPlatform.ohos: case TargetPlatform.windows: return Icons.arrow_back; case TargetPlatform.iOS: diff --git a/dev/integration_tests/flutter_gallery/lib/gallery/options.dart b/dev/integration_tests/flutter_gallery/lib/gallery/options.dart index 12eecbe4986d88b3eb72825ac1d90dab5362b7d8..9d2c76dabe759ab30e42477bf7d89e3d14e97cfd 100644 --- a/dev/integration_tests/flutter_gallery/lib/gallery/options.dart +++ b/dev/integration_tests/flutter_gallery/lib/gallery/options.dart @@ -414,6 +414,7 @@ class _PlatformItem extends StatelessWidget { TargetPlatform.android => 'Mountain View', TargetPlatform.fuchsia => 'Fuchsia', TargetPlatform.iOS => 'Cupertino', + TargetPlatform.ohos => 'Ohos', TargetPlatform.linux => 'Material Desktop (linux)', TargetPlatform.macOS => 'Material Desktop (macOS)', TargetPlatform.windows => 'Material Desktop (Windows)', diff --git a/dev/integration_tests/new_gallery/lib/studies/reply/mail_card_preview.dart b/dev/integration_tests/new_gallery/lib/studies/reply/mail_card_preview.dart index 86058ca5556c8b0a0028db8c5b506e8b5a8d21cd..1ea27c4bf66c1af239b9d8fa9fd9164bf4af3fe8 100644 --- a/dev/integration_tests/new_gallery/lib/studies/reply/mail_card_preview.dart +++ b/dev/integration_tests/new_gallery/lib/studies/reply/mail_card_preview.dart @@ -266,6 +266,7 @@ class _PicturePreview extends StatelessWidget { switch (defaultTargetPlatform) { case TargetPlatform.iOS: case TargetPlatform.android: + case TargetPlatform.ohos: return true; case TargetPlatform.fuchsia: case TargetPlatform.linux: diff --git a/dev/integration_tests/new_gallery/lib/studies/reply/mail_view_page.dart b/dev/integration_tests/new_gallery/lib/studies/reply/mail_view_page.dart index 914394e81375c6c1abbc2fda27ec1958c03e0263..476c439391459686e8fb8e4256b2ab2b2ce840f6 100644 --- a/dev/integration_tests/new_gallery/lib/studies/reply/mail_view_page.dart +++ b/dev/integration_tests/new_gallery/lib/studies/reply/mail_view_page.dart @@ -144,6 +144,7 @@ class _PictureGrid extends StatelessWidget { switch (defaultTargetPlatform) { case TargetPlatform.iOS: case TargetPlatform.android: + case TargetPlatform.ohos: return true; case TargetPlatform.fuchsia: case TargetPlatform.linux: diff --git a/dev/tools/bin/find_commit.dart b/dev/tools/bin/find_commit.dart index 33289f90f33ed4fdf98016c8c4a6917e74de0689..3f6eedae189a0ba6a6cc5aa516d04a897348e1db 100644 --- a/dev/tools/bin/find_commit.dart +++ b/dev/tools/bin/find_commit.dart @@ -10,7 +10,7 @@ import 'dart:io'; -const bool debugLogging = false; +const bool debugLogging = true; void log(String message) { if (debugLogging) { @@ -49,12 +49,18 @@ String findCommit({ } } } + final String currentBranch = git(secondaryRepoDirectory, [ + 'rev-parse', + '--abbrev-ref', + 'HEAD' + ]).trim(); + return git(secondaryRepoDirectory, [ 'log', '--format=%H', '--until=${anchor.toIso8601String()}', '--max-count=1', - secondaryBranch, + currentBranch, '--', ]); } @@ -62,7 +68,7 @@ String findCommit({ String git(String workingDirectory, List arguments, {bool allowFailure = false}) { final ProcessResult result = Process.runSync('git', arguments, workingDirectory: workingDirectory); if (!allowFailure && result.exitCode != 0 || '${result.stderr}'.isNotEmpty) { - throw ProcessException('git', arguments, '${result.stdout}${result.stderr}', result.exitCode); + throw ProcessException('git', arguments, 'working directory: "$workingDirectory"\n${result.stdout}\n${result.stderr}', result.exitCode); } return '${result.stdout}'; } diff --git a/dev/tools/bin/generate_gradle_lockfiles.dart b/dev/tools/bin/generate_gradle_lockfiles.dart index 4695b18813af41ec8515a996f3b5cfbcd8bd93bf..37575a201f7b9c7e646b86204d2d6e49b6aef580 100644 --- a/dev/tools/bin/generate_gradle_lockfiles.dart +++ b/dev/tools/bin/generate_gradle_lockfiles.dart @@ -174,6 +174,24 @@ const String rootGradleFileContent = r''' // To update all the build.gradle files in the Flutter repo, // See dev/tools/bin/generate_gradle_lockfiles.dart. +buildscript { + ext.kotlin_version = '1.7.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.3.0' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } + + configurations.classpath { + resolutionStrategy.activateDependencyLocking() + } +} + +>>>>>>> dev allprojects { repositories { google() diff --git a/examples/api/lib/material/context_menu/context_menu_controller.0.dart b/examples/api/lib/material/context_menu/context_menu_controller.0.dart index 99e77ec9353d25cc9373852687dfb03f5c0fc222..100b0a54f07c78bfcd43a59b5a4e70c3010cf41a 100644 --- a/examples/api/lib/material/context_menu/context_menu_controller.0.dart +++ b/examples/api/lib/material/context_menu/context_menu_controller.0.dart @@ -120,6 +120,7 @@ class _ContextMenuRegionState extends State<_ContextMenuRegion> { switch (defaultTargetPlatform) { case TargetPlatform.android: case TargetPlatform.iOS: + case TargetPlatform.ohos: return true; case TargetPlatform.macOS: case TargetPlatform.fuchsia: diff --git a/examples/api/lib/material/dialog/adaptive_alert_dialog.0.dart b/examples/api/lib/material/dialog/adaptive_alert_dialog.0.dart index 08b760cfb313d5d27b5ebd67eaae555fda43a2a2..9cb3f05ddb862170eaf8083658b11c8f96de8422 100644 --- a/examples/api/lib/material/dialog/adaptive_alert_dialog.0.dart +++ b/examples/api/lib/material/dialog/adaptive_alert_dialog.0.dart @@ -41,6 +41,7 @@ class AdaptiveDialogExample extends StatelessWidget { case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: return TextButton(onPressed: onPressed, child: child); case TargetPlatform.iOS: case TargetPlatform.macOS: diff --git a/examples/api/lib/material/menu_anchor/menu_anchor.1.dart b/examples/api/lib/material/menu_anchor/menu_anchor.1.dart index e6b6d07f2e47673f84fcf86f574dcc42328242c4..5a2c37a7a8606a0f8600fbd9ce24f8296aca33b1 100644 --- a/examples/api/lib/material/menu_anchor/menu_anchor.1.dart +++ b/examples/api/lib/material/menu_anchor/menu_anchor.1.dart @@ -229,6 +229,7 @@ class _MyContextMenuState extends State { case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: // Don't open the menu on these platforms with a Ctrl-tap (or a // tap). break; diff --git a/examples/api/lib/material/switch/switch.4.dart b/examples/api/lib/material/switch/switch.4.dart index 4d64608f86fe3a0e864fc9f500eef049abed9756..6084b77b31519fbe56fece1f86b4130034a2bdcd 100644 --- a/examples/api/lib/material/switch/switch.4.dart +++ b/examples/api/lib/material/switch/switch.4.dart @@ -117,6 +117,7 @@ class _SwitchThemeAdaptation extends Adaptation { case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: return defaultValue; case TargetPlatform.iOS: case TargetPlatform.macOS: diff --git a/examples/api/lib/widgets/draggable_scrollable_sheet/draggable_scrollable_sheet.0.dart b/examples/api/lib/widgets/draggable_scrollable_sheet/draggable_scrollable_sheet.0.dart index f904102678b41d14fef629fb9f075cdb83774e7d..87f9a106352ebf53bffe0286ab75c5a5d458a14a 100644 --- a/examples/api/lib/widgets/draggable_scrollable_sheet/draggable_scrollable_sheet.0.dart +++ b/examples/api/lib/widgets/draggable_scrollable_sheet/draggable_scrollable_sheet.0.dart @@ -97,6 +97,7 @@ class _DraggableScrollableSheetExampleState extends State with TickerProviderSt case TargetPlatform.android: case TargetPlatform.iOS: case TargetPlatform.fuchsia: + case TargetPlatform.ohos: return false; } } diff --git a/examples/api/test/material/context_menu/editable_text_toolbar_builder.2_test.dart b/examples/api/test/material/context_menu/editable_text_toolbar_builder.2_test.dart index 2bcf2ea61d78a61d85d0edb160845116f53fd813..88539c89d1c79d4cdd0424c9741ab4105d196b28 100644 --- a/examples/api/test/material/context_menu/editable_text_toolbar_builder.2_test.dart +++ b/examples/api/test/material/context_menu/editable_text_toolbar_builder.2_test.dart @@ -34,6 +34,7 @@ void main() { expect(find.byType(CupertinoTextSelectionToolbarButton), findsAtLeastNWidgets(1)); case TargetPlatform.android: case TargetPlatform.fuchsia: + case TargetPlatform.ohos: expect(find.byType(TextSelectionToolbarTextButton), findsAtLeastNWidgets(1)); case TargetPlatform.linux: case TargetPlatform.windows: diff --git a/examples/platform_view/lib/main.dart b/examples/platform_view/lib/main.dart index 5efefe72ffd4a3cd4be9aa2d0b73d92d28293f7c..e81b4243d15c658c2c6ddc6dd8982769f4aa5adc 100644 --- a/examples/platform_view/lib/main.dart +++ b/examples/platform_view/lib/main.dart @@ -51,6 +51,7 @@ class _MyHomePageState extends State { return switch (defaultTargetPlatform) { TargetPlatform.android => const Text('Continue in Android view'), TargetPlatform.iOS => const Text('Continue in iOS view'), + TargetPlatform.ohos => const Text('Continue in Ohos view'), TargetPlatform.windows => const Text('Continue in Windows view'), TargetPlatform.macOS => const Text('Continue in macOS view'), TargetPlatform.linux => const Text('Continue in Linux view'), diff --git a/packages/flutter/lib/src/cupertino/adaptive_text_selection_toolbar.dart b/packages/flutter/lib/src/cupertino/adaptive_text_selection_toolbar.dart index 0f88c306d33913b1e695bd9a8b4addd6087822b2..1fc2d7a161313ed2c4d41ce84d67cf9633ec19b9 100644 --- a/packages/flutter/lib/src/cupertino/adaptive_text_selection_toolbar.dart +++ b/packages/flutter/lib/src/cupertino/adaptive_text_selection_toolbar.dart @@ -188,6 +188,7 @@ class CupertinoAdaptiveTextSelectionToolbar extends StatelessWidget { case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.iOS: + case TargetPlatform.ohos: return buttonItems.map((ContextMenuButtonItem buttonItem) { return CupertinoTextSelectionToolbarButton.buttonItem( buttonItem: buttonItem, @@ -218,6 +219,7 @@ class CupertinoAdaptiveTextSelectionToolbar extends StatelessWidget { case TargetPlatform.android: case TargetPlatform.iOS: case TargetPlatform.fuchsia: + case TargetPlatform.ohos: return CupertinoTextSelectionToolbar( anchorAbove: anchors.primaryAnchor, anchorBelow: anchors.secondaryAnchor ?? anchors.primaryAnchor, diff --git a/packages/flutter/lib/src/cupertino/app.dart b/packages/flutter/lib/src/cupertino/app.dart index b82216954c1d3c779d3d3db61ee4c652fa76c304..d5988efd145ebbccc7f76a37f5733eee062031e8 100644 --- a/packages/flutter/lib/src/cupertino/app.dart +++ b/packages/flutter/lib/src/cupertino/app.dart @@ -469,6 +469,7 @@ class CupertinoScrollBehavior extends ScrollBehavior { controller: details.controller, child: child, ); + case TargetPlatform.ohos: case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.iOS: diff --git a/packages/flutter/lib/src/cupertino/picker.dart b/packages/flutter/lib/src/cupertino/picker.dart index 88a70f1cc842bf31c7ee1af9c3c116c52adf5bf4..05f390c55392ce0320e55a92a68f12d3839724e9 100644 --- a/packages/flutter/lib/src/cupertino/picker.dart +++ b/packages/flutter/lib/src/cupertino/picker.dart @@ -243,6 +243,7 @@ class _CupertinoPickerState extends State { case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.macOS: + case TargetPlatform.ohos: case TargetPlatform.windows: hasSuitableHapticHardware = false; } diff --git a/packages/flutter/lib/src/cupertino/radio.dart b/packages/flutter/lib/src/cupertino/radio.dart index 52b752c847126c287d89171152c8f9c2082b18bc..48bcfa16ba91fdeb1ed0c5cb489104824bf31285 100644 --- a/packages/flutter/lib/src/cupertino/radio.dart +++ b/packages/flutter/lib/src/cupertino/radio.dart @@ -248,6 +248,7 @@ class _CupertinoRadioState extends State> with TickerProvid case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: accessibilitySelected = null; case TargetPlatform.iOS: case TargetPlatform.macOS: diff --git a/packages/flutter/lib/src/cupertino/switch.dart b/packages/flutter/lib/src/cupertino/switch.dart index b864fd5e98cee267cab4b9f1c6d1b71290901922..668c04ae5604f275a01142cad38a8e588c3c449b 100644 --- a/packages/flutter/lib/src/cupertino/switch.dart +++ b/packages/flutter/lib/src/cupertino/switch.dart @@ -348,6 +348,7 @@ class _CupertinoSwitchState extends State with TickerProviderSt case TargetPlatform.linux: case TargetPlatform.macOS: case TargetPlatform.windows: + case TargetPlatform.ohos: break; } } diff --git a/packages/flutter/lib/src/cupertino/text_field.dart b/packages/flutter/lib/src/cupertino/text_field.dart index 9948bd96d98a1dd14cffe369c5da6200eab03aab..af97efd97977f6e1646a5d93cd5b005a5f272668 100644 --- a/packages/flutter/lib/src/cupertino/text_field.dart +++ b/packages/flutter/lib/src/cupertino/text_field.dart @@ -887,6 +887,7 @@ class CupertinoTextField extends StatefulWidget { switch (defaultTargetPlatform) { case TargetPlatform.android: case TargetPlatform.iOS: + case TargetPlatform.ohos: return CupertinoTextMagnifier( controller: controller, magnifierInfo: magnifierInfo, @@ -1068,7 +1069,9 @@ class _CupertinoTextFieldState extends State with Restoratio case TargetPlatform.windows: case TargetPlatform.fuchsia: case TargetPlatform.android: - if (cause == SelectionChangedCause.longPress) { + case TargetPlatform.ohos: + if (cause == SelectionChangedCause.longPress + || cause == SelectionChangedCause.drag) { _editableText.bringIntoView(selection.extent); } } @@ -1077,6 +1080,7 @@ class _CupertinoTextFieldState extends State with Restoratio case TargetPlatform.iOS: case TargetPlatform.fuchsia: case TargetPlatform.android: + case TargetPlatform.ohos: break; case TargetPlatform.macOS: case TargetPlatform.linux: @@ -1266,6 +1270,7 @@ class _CupertinoTextFieldState extends State with Restoratio case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.linux: + case TargetPlatform.ohos: textSelectionControls ??= cupertinoTextSelectionHandleControls; case TargetPlatform.macOS: diff --git a/packages/flutter/lib/src/foundation/_platform_io.dart b/packages/flutter/lib/src/foundation/_platform_io.dart index 7fd4f35ff7ac8daec4233d00f74d04a51300c47e..e6fd464314341dece45d25b582fd269af9c79ca2 100644 --- a/packages/flutter/lib/src/foundation/_platform_io.dart +++ b/packages/flutter/lib/src/foundation/_platform_io.dart @@ -25,6 +25,8 @@ platform.TargetPlatform get defaultTargetPlatform { result = platform.TargetPlatform.macOS; } else if (Platform.isWindows) { result = platform.TargetPlatform.windows; + } else if (Platform.operatingSystem == 'ohos') { + result = platform.TargetPlatform.ohos; } assert(() { if (Platform.environment.containsKey('FLUTTER_TEST')) { diff --git a/packages/flutter/lib/src/foundation/_platform_web.dart b/packages/flutter/lib/src/foundation/_platform_web.dart index 161921fba4313a7e0a2f1cbfc4b4b929a26199a3..ca40ccb5c20c9d0f7ec8d598bf63bca63b6b1500 100644 --- a/packages/flutter/lib/src/foundation/_platform_web.dart +++ b/packages/flutter/lib/src/foundation/_platform_web.dart @@ -52,6 +52,9 @@ final platform.TargetPlatform _browserPlatform = () { if (navigatorPlatform.contains('android')) { return platform.TargetPlatform.android; } + if (navigatorPlatform.contains('ohos')) { + return platform.TargetPlatform.ohos; + } // Since some phones can report a window.navigator.platform as Linux, fall // back to use CSS to disambiguate Android vs Linux desktop. If the CSS // indicates that a device has a "fine pointer" (mouse) as the primary diff --git a/packages/flutter/lib/src/foundation/platform.dart b/packages/flutter/lib/src/foundation/platform.dart index b1905aea1a95c10d56cf59e1529a77f4f367a339..64bec6567247bb6501420d8d660e8a56fb8c24d2 100644 --- a/packages/flutter/lib/src/foundation/platform.dart +++ b/packages/flutter/lib/src/foundation/platform.dart @@ -77,6 +77,9 @@ enum TargetPlatform { /// Windows: windows, + + /// Ohos + ohos, } /// Override the [defaultTargetPlatform] in debug builds. diff --git a/packages/flutter/lib/src/material/action_buttons.dart b/packages/flutter/lib/src/material/action_buttons.dart index c417bfb2e19fcbc07d4d1d81b69d827fc1da369d..ed842ecee1c12328ca189065f518a6f07c07f082 100644 --- a/packages/flutter/lib/src/material/action_buttons.dart +++ b/packages/flutter/lib/src/material/action_buttons.dart @@ -110,6 +110,7 @@ class _ActionIcon extends StatelessWidget { case TargetPlatform.windows: case TargetPlatform.iOS: case TargetPlatform.macOS: + case TargetPlatform.ohos: semanticsLabel = null; } @@ -150,6 +151,7 @@ class BackButtonIcon extends StatelessWidget { case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: return Icons.arrow_back; case TargetPlatform.iOS: case TargetPlatform.macOS: diff --git a/packages/flutter/lib/src/material/adaptive_text_selection_toolbar.dart b/packages/flutter/lib/src/material/adaptive_text_selection_toolbar.dart index cb5c29d1fddbe78135061b80d9c188fad2299f80..89f0cde9f685f436d7fc12b54722884a567912a9 100644 --- a/packages/flutter/lib/src/material/adaptive_text_selection_toolbar.dart +++ b/packages/flutter/lib/src/material/adaptive_text_selection_toolbar.dart @@ -209,6 +209,7 @@ class AdaptiveTextSelectionToolbar extends StatelessWidget { case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.linux: + case TargetPlatform.ohos: case TargetPlatform.windows: assert(debugCheckHasMaterialLocalizations(context)); final MaterialLocalizations localizations = MaterialLocalizations.of(context); @@ -256,6 +257,7 @@ class AdaptiveTextSelectionToolbar extends StatelessWidget { }); case TargetPlatform.fuchsia: case TargetPlatform.android: + case TargetPlatform.ohos: final List buttons = []; for (int i = 0; i < buttonItems.length; i++) { final ContextMenuButtonItem buttonItem = buttonItems[i]; @@ -305,6 +307,7 @@ class AdaptiveTextSelectionToolbar extends StatelessWidget { anchorBelow: anchors.secondaryAnchor == null ? anchors.primaryAnchor : anchors.secondaryAnchor!, children: resultChildren, ); + case TargetPlatform.ohos: case TargetPlatform.android: return TextSelectionToolbar( anchorAbove: anchors.primaryAnchor, diff --git a/packages/flutter/lib/src/material/app.dart b/packages/flutter/lib/src/material/app.dart index 6560e015e3ddd376ab07ca42b903a980ac511d77..f77e0eda0002a44940b33b5b7e1ed5271786f235 100644 --- a/packages/flutter/lib/src/material/app.dart +++ b/packages/flutter/lib/src/material/app.dart @@ -852,6 +852,7 @@ class MaterialScrollBehavior extends ScrollBehavior { case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.iOS: + case TargetPlatform.ohos: return child; } } @@ -871,6 +872,7 @@ class MaterialScrollBehavior extends ScrollBehavior { case TargetPlatform.windows: return child; case TargetPlatform.android: + case TargetPlatform.ohos: switch (indicator) { case AndroidOverscrollIndicator.stretch: return StretchingOverscrollIndicator( diff --git a/packages/flutter/lib/src/material/app_bar.dart b/packages/flutter/lib/src/material/app_bar.dart index dee365886c73bd848c8f31cc5e4aac7806e0257b..a80c36e670c6ee9f36e63b4ecd005f9840ab941e 100644 --- a/packages/flutter/lib/src/material/app_bar.dart +++ b/packages/flutter/lib/src/material/app_bar.dart @@ -734,9 +734,11 @@ class AppBar extends StatefulWidget implements PreferredSizeWidget { bool platformCenter() { switch (theme.platform) { case TargetPlatform.android: + case TargetPlatform.ohos: case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: return false; case TargetPlatform.iOS: case TargetPlatform.macOS: @@ -973,7 +975,7 @@ class _AppBarState extends State { if (!widget.excludeHeaderSemantics) { title = Semantics( namesRoute: switch (theme.platform) { - TargetPlatform.android || TargetPlatform.fuchsia || TargetPlatform.linux || TargetPlatform.windows => true, + TargetPlatform.android || TargetPlatform.fuchsia || TargetPlatform.linux || TargetPlatform.windows || TargetPlatform.ohos => true, TargetPlatform.iOS || TargetPlatform.macOS => null, }, header: true, diff --git a/packages/flutter/lib/src/material/bottom_sheet.dart b/packages/flutter/lib/src/material/bottom_sheet.dart index e9a986aeba9d63f9333ac554566a743d5b59722d..cd58af5f7af55a56ab00a957793e3668002971f3 100644 --- a/packages/flutter/lib/src/material/bottom_sheet.dart +++ b/packages/flutter/lib/src/material/bottom_sheet.dart @@ -690,6 +690,7 @@ class _ModalBottomSheetState extends State<_ModalBottomSheet> { case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: return localizations.dialogLabel; } } diff --git a/packages/flutter/lib/src/material/calendar_date_picker.dart b/packages/flutter/lib/src/material/calendar_date_picker.dart index aa25b7e1f86dac6884a12cc63a37f4324cfc1022..c5920c494f7ebc04846668f8da03030054be7389 100644 --- a/packages/flutter/lib/src/material/calendar_date_picker.dart +++ b/packages/flutter/lib/src/material/calendar_date_picker.dart @@ -200,6 +200,7 @@ class _CalendarDatePickerState extends State { case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: HapticFeedback.vibrate(); case TargetPlatform.iOS: case TargetPlatform.macOS: @@ -277,6 +278,7 @@ class _CalendarDatePickerState extends State { ); case TargetPlatform.android: case TargetPlatform.iOS: + case TargetPlatform.ohos: case TargetPlatform.fuchsia: break; } diff --git a/packages/flutter/lib/src/material/checkbox.dart b/packages/flutter/lib/src/material/checkbox.dart index 5d289aca6d5a1ccfe8ddd09a2ff6a3cbfd112d10..b16eb63946d53fbc2058d12a262890751ec71b94 100644 --- a/packages/flutter/lib/src/material/checkbox.dart +++ b/packages/flutter/lib/src/material/checkbox.dart @@ -479,6 +479,7 @@ class _CheckboxState extends State with TickerProviderStateMixin, Togg case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: break; case TargetPlatform.iOS: case TargetPlatform.macOS: diff --git a/packages/flutter/lib/src/material/date_picker.dart b/packages/flutter/lib/src/material/date_picker.dart index f4f832f6f6ab4ba1c1299c5ade0b7a04205d4896..8912bb97327a474daa0809f73a509292a8d18ebc 100644 --- a/packages/flutter/lib/src/material/date_picker.dart +++ b/packages/flutter/lib/src/material/date_picker.dart @@ -1857,6 +1857,7 @@ class _CalendarDateRangePickerState extends State<_CalendarDateRangePicker> { case TargetPlatform.linux: case TargetPlatform.macOS: case TargetPlatform.windows: + case TargetPlatform.ohos: break; } } diff --git a/packages/flutter/lib/src/material/dialog.dart b/packages/flutter/lib/src/material/dialog.dart index 1659dad97a0ba028eb0f89415c37f5712615903f..730ec409fec46049315c323e15f930ae13a2c3a1 100644 --- a/packages/flutter/lib/src/material/dialog.dart +++ b/packages/flutter/lib/src/material/dialog.dart @@ -720,6 +720,7 @@ class AlertDialog extends StatelessWidget { case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: label ??= MaterialLocalizations.of(context).alertDialogLabel; } @@ -943,6 +944,7 @@ class _AdaptiveAlertDialog extends AlertDialog { case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: break; case TargetPlatform.iOS: case TargetPlatform.macOS: @@ -1217,6 +1219,7 @@ class SimpleDialog extends StatelessWidget { case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: label ??= MaterialLocalizations.of(context).dialogLabel; } @@ -1473,6 +1476,7 @@ Future showAdaptiveDialog({ case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: return showDialog( context: context, builder: builder, diff --git a/packages/flutter/lib/src/material/drawer.dart b/packages/flutter/lib/src/material/drawer.dart index e27eb149a6b916423d00cbe06f37848196b5b624..3567f43e1bfd2c9e9bf7e325fdc583968ed4bb57 100644 --- a/packages/flutter/lib/src/material/drawer.dart +++ b/packages/flutter/lib/src/material/drawer.dart @@ -246,6 +246,7 @@ class Drawer extends StatelessWidget { case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: label = semanticLabel ?? MaterialLocalizations.of(context).drawerLabel; } final bool useMaterial3 = Theme.of(context).useMaterial3; @@ -659,7 +660,7 @@ class DrawerControllerState extends State with SingleTickerPro Widget _buildDrawer(BuildContext context) { final bool isDesktop = switch (Theme.of(context).platform) { - TargetPlatform.android || TargetPlatform.iOS || TargetPlatform.fuchsia => false, + TargetPlatform.android || TargetPlatform.iOS || TargetPlatform.fuchsia || TargetPlatform.ohos => false, TargetPlatform.macOS || TargetPlatform.linux || TargetPlatform.windows => true, }; @@ -693,6 +694,8 @@ class DrawerControllerState extends State with SingleTickerPro switch (Theme.of(context).platform) { case TargetPlatform.android: platformHasBackButton = true; + break; + case TargetPlatform.ohos: case TargetPlatform.iOS: case TargetPlatform.macOS: case TargetPlatform.fuchsia: diff --git a/packages/flutter/lib/src/material/dropdown_menu.dart b/packages/flutter/lib/src/material/dropdown_menu.dart index 6ac09b52ee40db93e40e925c65f0350686474386..4672e079bc0ce29ad38f7320813740154d0757ba 100644 --- a/packages/flutter/lib/src/material/dropdown_menu.dart +++ b/packages/flutter/lib/src/material/dropdown_menu.dart @@ -486,7 +486,7 @@ class _DropdownMenuState extends State> { bool canRequestFocus() { return widget.focusNode?.canRequestFocus ?? widget.requestFocusOnTap ?? switch (Theme.of(context).platform) { - TargetPlatform.iOS || TargetPlatform.android || TargetPlatform.fuchsia => false, + TargetPlatform.iOS || TargetPlatform.android || TargetPlatform.fuchsia || TargetPlatform.ohos => false, TargetPlatform.macOS || TargetPlatform.linux || TargetPlatform.windows => true, }; } diff --git a/packages/flutter/lib/src/material/expansion_tile.dart b/packages/flutter/lib/src/material/expansion_tile.dart index 0c736c6639d68deddf3e0aafe90d3188997f1249..1bbb86e919cb40339b1b42069b833067c76124bb 100644 --- a/packages/flutter/lib/src/material/expansion_tile.dart +++ b/packages/flutter/lib/src/material/expansion_tile.dart @@ -702,6 +702,7 @@ class _ExpansionTileState extends State with SingleTickerProvider case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: break; } diff --git a/packages/flutter/lib/src/material/feedback.dart b/packages/flutter/lib/src/material/feedback.dart index cfb3a063480bf8029c5a7e1d605f4a9edf2d156f..cad8df3d64525b95da803ad3b36ef337d2401a4e 100644 --- a/packages/flutter/lib/src/material/feedback.dart +++ b/packages/flutter/lib/src/material/feedback.dart @@ -100,6 +100,7 @@ abstract final class Feedback { case TargetPlatform.linux: case TargetPlatform.macOS: case TargetPlatform.windows: + case TargetPlatform.ohos: return Future.value(); } } @@ -143,6 +144,7 @@ abstract final class Feedback { case TargetPlatform.linux: case TargetPlatform.macOS: case TargetPlatform.windows: + case TargetPlatform.ohos: return Future.value(); } } diff --git a/packages/flutter/lib/src/material/flexible_space_bar.dart b/packages/flutter/lib/src/material/flexible_space_bar.dart index 34e24a72ee81e718a4e6b9a83d92b86f01f99e25..46bf0d79e1247c06af98f0cec97480d4a22bd806 100644 --- a/packages/flutter/lib/src/material/flexible_space_bar.dart +++ b/packages/flutter/lib/src/material/flexible_space_bar.dart @@ -186,7 +186,7 @@ class FlexibleSpaceBar extends StatefulWidget { class _FlexibleSpaceBarState extends State { bool _getEffectiveCenterTitle(ThemeData theme) { return widget.centerTitle ?? switch (theme.platform) { - TargetPlatform.android || TargetPlatform.fuchsia || TargetPlatform.linux || TargetPlatform.windows => false, + TargetPlatform.android || TargetPlatform.fuchsia || TargetPlatform.linux || TargetPlatform.windows || TargetPlatform.ohos => false, TargetPlatform.iOS || TargetPlatform.macOS => true, }; } @@ -290,6 +290,7 @@ class _FlexibleSpaceBarState extends State { case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: title = Semantics( namesRoute: true, child: widget.title, diff --git a/packages/flutter/lib/src/material/icons.dart b/packages/flutter/lib/src/material/icons.dart index cdf5798213598a485ba870e5bdb648798af191c6..65b2920f4ea6c65c89e967f70a0636c394cca853 100644 --- a/packages/flutter/lib/src/material/icons.dart +++ b/packages/flutter/lib/src/material/icons.dart @@ -19,6 +19,7 @@ final class PlatformAdaptiveIcons implements Icons { case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: return false; case TargetPlatform.iOS: case TargetPlatform.macOS: diff --git a/packages/flutter/lib/src/material/magnifier.dart b/packages/flutter/lib/src/material/magnifier.dart index 6206d0e16b908d7cc86cceeea41b73d39a083d06..0a4f360beeced17bf4fe55d2f9b7299500df580a 100644 --- a/packages/flutter/lib/src/material/magnifier.dart +++ b/packages/flutter/lib/src/material/magnifier.dart @@ -54,6 +54,7 @@ class TextMagnifier extends StatefulWidget { magnifierInfo: magnifierInfo, ); case TargetPlatform.android: + case TargetPlatform.ohos: return TextMagnifier( magnifierInfo: magnifierInfo, ); diff --git a/packages/flutter/lib/src/material/menu_anchor.dart b/packages/flutter/lib/src/material/menu_anchor.dart index adf61adbf47a83dbf8710222f3dd93e3ab1448f1..9ad6b046889c2c830eeeecc085ccc9400097f701 100644 --- a/packages/flutter/lib/src/material/menu_anchor.dart +++ b/packages/flutter/lib/src/material/menu_anchor.dart @@ -2213,6 +2213,7 @@ class _LocalizedShortcutLabeler { switch (defaultTargetPlatform) { case TargetPlatform.android: case TargetPlatform.fuchsia: + case TargetPlatform.ohos: case TargetPlatform.linux: return localizations.keyboardKeyMeta; case TargetPlatform.windows: @@ -2230,6 +2231,7 @@ class _LocalizedShortcutLabeler { case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: return localizations.keyboardKeyAlt; case TargetPlatform.iOS: case TargetPlatform.macOS: @@ -2249,6 +2251,7 @@ class _LocalizedShortcutLabeler { case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: return localizations.keyboardKeyControl; case TargetPlatform.iOS: case TargetPlatform.macOS: @@ -2263,6 +2266,7 @@ class _LocalizedShortcutLabeler { case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: return localizations.keyboardKeyShift; case TargetPlatform.iOS: case TargetPlatform.macOS: @@ -3568,6 +3572,7 @@ bool get _isApple { case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: return false; } } diff --git a/packages/flutter/lib/src/material/page_transitions_theme.dart b/packages/flutter/lib/src/material/page_transitions_theme.dart index cbcfa2cf2647c0a27c22e46eb2644419c98953aa..3aab43477ab708a61f8eac41ca9a78664f0fe6c7 100644 --- a/packages/flutter/lib/src/material/page_transitions_theme.dart +++ b/packages/flutter/lib/src/material/page_transitions_theme.dart @@ -147,6 +147,82 @@ class _OpenUpwardsPageTransition extends StatelessWidget { } } + +// This transition is intended to match the default for Openharmony. +class _OpenRightwardsPageTransition extends StatelessWidget { + const _OpenRightwardsPageTransition({ + required this.animation, + required this.secondaryAnimation, + required this.child, + }); + + // The new page slides upwards just a little as its clip + // rectangle exposes the page from right to left. + static final Tween _primaryTranslationTween = Tween( + begin: const Offset(1.0, 0), + end: Offset.zero, + ); + + // The old page slides upwards a little as the new page appears. + static final Tween _secondaryTranslationTween = Tween( + begin: Offset.zero, + end: const Offset(-0.2, 0), + ); + + // Used by all of the transition animations. + static const Curve _transitionCurve = Cubic(0.20, 0.00, 0.00, 1.00); + + final Animation animation; + final Animation secondaryAnimation; + final Widget child; + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + final Size size = constraints.biggest; + + final CurvedAnimation primaryAnimation = CurvedAnimation( + parent: animation, + curve: _transitionCurve, + reverseCurve: _transitionCurve.flipped, + ); + + + final Animation primaryTranslationAnimation = _primaryTranslationTween.animate(primaryAnimation); + + final Animation secondaryTranslationAnimation = _secondaryTranslationTween.animate( + CurvedAnimation( + parent: secondaryAnimation, + curve: _transitionCurve, + reverseCurve: _transitionCurve.flipped, + ), + ); + + return AnimatedBuilder( + animation: animation, + builder: (BuildContext context, Widget? child) { + return child ?? Container(); + }, + child: AnimatedBuilder( + animation: secondaryAnimation, + child: FractionalTranslation( + translation: primaryTranslationAnimation.value, + child: child, + ), + builder: (BuildContext context, Widget? child) { + return FractionalTranslation( + translation: secondaryTranslationAnimation.value, + child: child, + ); + }, + ), + ); + }, + ); + } +} + // Zooms and fades a new page in, zooming out the previous page. This transition // is designed to match the Android Q activity transition. class _ZoomPageTransition extends StatelessWidget { @@ -600,6 +676,27 @@ class OpenUpwardsPageTransitionsBuilder extends PageTransitionsBuilder { } } +class OpenRightwardsPageTransitionsBuilder extends PageTransitionsBuilder { + /// Constructs a page transition animation that matches the transition used on + /// Openharmony. + const OpenRightwardsPageTransitionsBuilder(); + + @override + Widget buildTransitions( + PageRoute? route, + BuildContext? context, + Animation animation, + Animation secondaryAnimation, + Widget child, + ) { + return _OpenRightwardsPageTransition( + animation: animation, + secondaryAnimation: secondaryAnimation, + child: child, + ); + } +} + /// Used by [PageTransitionsTheme] to define a zooming [MaterialPageRoute] page /// transition animation that looks like the default page transition used on /// Android Q. @@ -757,6 +854,7 @@ class PageTransitionsTheme with Diagnosticable { static const Map _defaultBuilders = { TargetPlatform.android: ZoomPageTransitionsBuilder(), + TargetPlatform.ohos: OpenRightwardsPageTransitionsBuilder(), TargetPlatform.iOS: CupertinoPageTransitionsBuilder(), TargetPlatform.macOS: CupertinoPageTransitionsBuilder(), }; @@ -860,7 +958,7 @@ class _PageTransitionsThemeTransitionsState extends State<_PageTransitionsThe final PageTransitionsBuilder matchingBuilder = widget.builders[platform] ?? switch (platform) { TargetPlatform.iOS => const CupertinoPageTransitionsBuilder(), - TargetPlatform.android || TargetPlatform.fuchsia || TargetPlatform.windows || TargetPlatform.macOS || TargetPlatform.linux => const ZoomPageTransitionsBuilder(), + TargetPlatform.android || TargetPlatform.fuchsia || TargetPlatform.windows || TargetPlatform.macOS || TargetPlatform.linux || TargetPlatform.ohos => const ZoomPageTransitionsBuilder(), }; return matchingBuilder.buildTransitions( widget.route, diff --git a/packages/flutter/lib/src/material/popup_menu.dart b/packages/flutter/lib/src/material/popup_menu.dart index fa73e2e233a10c6ed3a6b854614a6074b4aff3d8..cc7004f543743df0352e09712898aa309e634b3a 100644 --- a/packages/flutter/lib/src/material/popup_menu.dart +++ b/packages/flutter/lib/src/material/popup_menu.dart @@ -996,6 +996,7 @@ Future showMenu({ case TargetPlatform.macOS: break; case TargetPlatform.android: + case TargetPlatform.ohos: case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: diff --git a/packages/flutter/lib/src/material/progress_indicator.dart b/packages/flutter/lib/src/material/progress_indicator.dart index 8b4cf3f72486d7c01cccc9d254bd01d7fc6b4f62..087d132ae07d6d675d66180e3a7aae6e0ec0e65f 100644 --- a/packages/flutter/lib/src/material/progress_indicator.dart +++ b/packages/flutter/lib/src/material/progress_indicator.dart @@ -777,6 +777,7 @@ class _CircularProgressIndicatorState extends State w case TargetPlatform.macOS: return _buildCupertinoIndicator(context); case TargetPlatform.android: + case TargetPlatform.ohos: case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: diff --git a/packages/flutter/lib/src/material/radio.dart b/packages/flutter/lib/src/material/radio.dart index f15260c2e7a603c3f5e9d00b9c75f3f8440f210b..5968da0197c04498f396656a31a834c52748e206 100644 --- a/packages/flutter/lib/src/material/radio.dart +++ b/packages/flutter/lib/src/material/radio.dart @@ -438,6 +438,7 @@ class _RadioState extends State> with TickerProviderStateMixin, Togg case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: break; case TargetPlatform.iOS: case TargetPlatform.macOS: @@ -528,6 +529,7 @@ class _RadioState extends State> with TickerProviderStateMixin, Togg case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: accessibilitySelected = null; case TargetPlatform.iOS: case TargetPlatform.macOS: diff --git a/packages/flutter/lib/src/material/range_slider.dart b/packages/flutter/lib/src/material/range_slider.dart index 31ca2db167269d0af04bad30ef8410f9b6d0de47..77a1b27c3fdccb3e5dbc33194d5a68dd0eff8aad 100644 --- a/packages/flutter/lib/src/material/range_slider.dart +++ b/packages/flutter/lib/src/material/range_slider.dart @@ -1116,6 +1116,7 @@ class _RenderRangeSlider extends RenderBox with RelayoutWhenSystemFontsChangeMix case TargetPlatform.linux: case TargetPlatform.macOS: case TargetPlatform.windows: + case TargetPlatform.ohos: // Matches Android implementation of material slider. return 0.05; } diff --git a/packages/flutter/lib/src/material/refresh_indicator.dart b/packages/flutter/lib/src/material/refresh_indicator.dart index 12cc5f4b6dd28c6d5b9fa3cb82aa2d645b62a134..ce256f1b9a737dbce0ab7b206b677535599109e3 100644 --- a/packages/flutter/lib/src/material/refresh_indicator.dart +++ b/packages/flutter/lib/src/material/refresh_indicator.dart @@ -608,6 +608,7 @@ class RefreshIndicatorState extends State with TickerProviderS case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: return materialIndicator; case TargetPlatform.iOS: case TargetPlatform.macOS: diff --git a/packages/flutter/lib/src/material/reorderable_list.dart b/packages/flutter/lib/src/material/reorderable_list.dart index 2d5c95c11c2aba9473ca37245f29e8c504aea6e4..f7309d673cf178b87e8fad9a4e11c0543dd872e7 100644 --- a/packages/flutter/lib/src/material/reorderable_list.dart +++ b/packages/flutter/lib/src/material/reorderable_list.dart @@ -356,6 +356,7 @@ class _ReorderableListViewState extends State { case TargetPlatform.iOS: case TargetPlatform.android: case TargetPlatform.fuchsia: + case TargetPlatform.ohos: return ReorderableDelayedDragStartListener( key: itemGlobalKey, index: index, diff --git a/packages/flutter/lib/src/material/scaffold.dart b/packages/flutter/lib/src/material/scaffold.dart index 59e97126aef367e0741599e91dd26827ebeb1616..bd42244943852a106b44f3bdde806e53bf9f708f 100644 --- a/packages/flutter/lib/src/material/scaffold.dart +++ b/packages/flutter/lib/src/material/scaffold.dart @@ -3042,6 +3042,7 @@ class ScaffoldState extends State with TickerProviderStateMixin, Resto removeBottomPadding: true, ); case TargetPlatform.android: + case TargetPlatform.ohos: case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: diff --git a/packages/flutter/lib/src/material/scrollbar.dart b/packages/flutter/lib/src/material/scrollbar.dart index 5f7393fb8915a9e52dd5b2ce057d10c03557b8c8..d94af932691d8e977467669775c6faa7347faee1 100644 --- a/packages/flutter/lib/src/material/scrollbar.dart +++ b/packages/flutter/lib/src/material/scrollbar.dart @@ -328,6 +328,7 @@ class _MaterialScrollbarState extends RawScrollbarState<_MaterialScrollbar> { switch (theme.platform) { case TargetPlatform.android: _useAndroidScrollbar = true; + case TargetPlatform.ohos: case TargetPlatform.iOS: case TargetPlatform.linux: case TargetPlatform.fuchsia: diff --git a/packages/flutter/lib/src/material/search.dart b/packages/flutter/lib/src/material/search.dart index bca887c6e804fd07cd3be2c9d23ccfecb4c2926b..e191b9bb0b00132f3ce03374176c4648e0235137 100644 --- a/packages/flutter/lib/src/material/search.dart +++ b/packages/flutter/lib/src/material/search.dart @@ -586,6 +586,7 @@ class _SearchPageState extends State<_SearchPage> { case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: routeName = searchFieldLabel; } diff --git a/packages/flutter/lib/src/material/search_anchor.dart b/packages/flutter/lib/src/material/search_anchor.dart index cf7d6c07f16dad3c4d01ecb34fe7d739c2b489e7..237670725be81b8f1e5f3b0b81067c79cd480e00 100644 --- a/packages/flutter/lib/src/material/search_anchor.dart +++ b/packages/flutter/lib/src/material/search_anchor.dart @@ -443,7 +443,7 @@ class _SearchAnchorState extends State { bool getShowFullScreenView() { return widget.isFullScreen ?? switch (Theme.of(context).platform) { - TargetPlatform.iOS || TargetPlatform.android || TargetPlatform.fuchsia => true, + TargetPlatform.iOS || TargetPlatform.android || TargetPlatform.fuchsia || TargetPlatform.ohos => true, TargetPlatform.macOS || TargetPlatform.linux || TargetPlatform.windows => false, }; } diff --git a/packages/flutter/lib/src/material/selectable_text.dart b/packages/flutter/lib/src/material/selectable_text.dart index 90a4ad7f414686f1d14d2998328ebae4c855887c..7821636966e893706484077e487dd68fcb3941da 100644 --- a/packages/flutter/lib/src/material/selectable_text.dart +++ b/packages/flutter/lib/src/material/selectable_text.dart @@ -142,6 +142,7 @@ class _SelectableTextSelectionGestureDetectorBuilder extends TextSelectionGestur case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: renderEditable.selectPosition(cause: SelectionChangedCause.tap); } } @@ -638,6 +639,7 @@ class _SelectableTextState extends State implements TextSelectio case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: // Do nothing. } } @@ -726,6 +728,7 @@ class _SelectableTextState extends State implements TextSelectio cursorOffset = Offset(iOSHorizontalOffset / MediaQuery.devicePixelRatioOf(context), 0); case TargetPlatform.android: + case TargetPlatform.ohos: case TargetPlatform.fuchsia: forcePressEnabled = false; textSelectionControls ??= materialTextSelectionHandleControls; diff --git a/packages/flutter/lib/src/material/selection_area.dart b/packages/flutter/lib/src/material/selection_area.dart index 2865c74416387c9e6c175ba69b4d2bd439f4298b..f82c7fc94fb44d199035565d27b1449fc332bae1 100644 --- a/packages/flutter/lib/src/material/selection_area.dart +++ b/packages/flutter/lib/src/material/selection_area.dart @@ -116,7 +116,7 @@ class _SelectionAreaState extends State { Widget build(BuildContext context) { assert(debugCheckHasMaterialLocalizations(context)); final TextSelectionControls controls = widget.selectionControls ?? switch (Theme.of(context).platform) { - TargetPlatform.android || TargetPlatform.fuchsia => materialTextSelectionHandleControls, + TargetPlatform.android || TargetPlatform.fuchsia || TargetPlatform.ohos => materialTextSelectionHandleControls, TargetPlatform.linux || TargetPlatform.windows => desktopTextSelectionHandleControls, TargetPlatform.iOS => cupertinoTextSelectionHandleControls, TargetPlatform.macOS => cupertinoDesktopTextSelectionHandleControls, diff --git a/packages/flutter/lib/src/material/slider.dart b/packages/flutter/lib/src/material/slider.dart index 923eb131f11678eeccfc1237a07254baad43b98e..c8739ebd99f30944092095193f852fc770f25101 100644 --- a/packages/flutter/lib/src/material/slider.dart +++ b/packages/flutter/lib/src/material/slider.dart @@ -759,6 +759,7 @@ class _SliderState extends State with TickerProviderStateMixin { final ThemeData theme = Theme.of(context); switch (theme.platform) { case TargetPlatform.android: + case TargetPlatform.ohos: case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: @@ -860,6 +861,7 @@ class _SliderState extends State with TickerProviderStateMixin { case TargetPlatform.iOS: case TargetPlatform.linux: case TargetPlatform.macOS: + case TargetPlatform.ohos: break; case TargetPlatform.windows: handleDidGainAccessibilityFocus = () { @@ -1395,6 +1397,7 @@ class _RenderSlider extends RenderBox with RelayoutWhenSystemFontsChangeMixin { return 0.1; case TargetPlatform.android: case TargetPlatform.fuchsia: + case TargetPlatform.ohos: case TargetPlatform.linux: case TargetPlatform.windows: // Matches Android implementation of material slider. diff --git a/packages/flutter/lib/src/material/switch.dart b/packages/flutter/lib/src/material/switch.dart index 35a3dae5507e99a6aeddf6b1027931cb0f47fe9f..3c68d39a02472591d9ded6d482a6a3b73795cc1f 100644 --- a/packages/flutter/lib/src/material/switch.dart +++ b/packages/flutter/lib/src/material/switch.dart @@ -586,6 +586,7 @@ class Switch extends StatelessWidget { case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: effectiveActiveThumbColor = activeColor; case TargetPlatform.iOS: case TargetPlatform.macOS: @@ -715,6 +716,7 @@ class _MaterialSwitchState extends State<_MaterialSwitch> with TickerProviderSta case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: updateCurve(); case TargetPlatform.iOS: case TargetPlatform.macOS: @@ -787,6 +789,7 @@ class _MaterialSwitchState extends State<_MaterialSwitch> with TickerProviderSta case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: return widget.size.width - _kSwitchMinSize; case TargetPlatform.iOS: case TargetPlatform.macOS: @@ -875,6 +878,7 @@ class _MaterialSwitchState extends State<_MaterialSwitch> with TickerProviderSta case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: switchConfig = theme.useMaterial3 ? _SwitchConfigM3(context) : _SwitchConfigM2(); defaults = theme.useMaterial3 ? _SwitchDefaultsM3(context) : _SwitchDefaultsM2(context); case TargetPlatform.iOS: @@ -1751,6 +1755,7 @@ class _SwitchThemeAdaptation extends Adaptation { case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: return defaultValue; case TargetPlatform.iOS: case TargetPlatform.macOS: diff --git a/packages/flutter/lib/src/material/text_field.dart b/packages/flutter/lib/src/material/text_field.dart index 99822ef0fac7895e3c61183ccd4dce81c2778af1..c15819e4c8b9a2653564b3d167a95fefdaaaeaaa 100644 --- a/packages/flutter/lib/src/material/text_field.dart +++ b/packages/flutter/lib/src/material/text_field.dart @@ -85,6 +85,7 @@ class _TextFieldSelectionGestureDetectorBuilder extends TextSelectionGestureDete case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: Feedback.forLongPress(_state.context); } } @@ -888,6 +889,7 @@ class TextField extends StatefulWidget { case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: return SpellCheckSuggestionsToolbar.editableText( editableTextState: editableTextState, ); @@ -1237,7 +1239,9 @@ class _TextFieldState extends State with RestorationMixin implements case TargetPlatform.windows: case TargetPlatform.fuchsia: case TargetPlatform.android: - if (cause == SelectionChangedCause.longPress) { + case TargetPlatform.ohos: + if (cause == SelectionChangedCause.longPress + || cause == SelectionChangedCause.drag) { _editableText?.bringIntoView(selection.extent); } } @@ -1246,6 +1250,7 @@ class _TextFieldState extends State with RestorationMixin implements case TargetPlatform.iOS: case TargetPlatform.fuchsia: case TargetPlatform.android: + case TargetPlatform.ohos: break; case TargetPlatform.macOS: case TargetPlatform.linux: @@ -1365,6 +1370,7 @@ class _TextFieldState extends State with RestorationMixin implements case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: spellCheckConfiguration = TextField.inferAndroidSpellCheckConfiguration( widget.spellCheckConfiguration, ); @@ -1416,13 +1422,14 @@ class _TextFieldState extends State with RestorationMixin implements case TargetPlatform.android: case TargetPlatform.fuchsia: + case TargetPlatform.ohos: forcePressEnabled = false; textSelectionControls ??= materialTextSelectionHandleControls; paintCursorAboveText = false; cursorOpacityAnimates ??= false; cursorColor = _hasError ? _errorColor : widget.cursorColor ?? selectionStyle.cursorColor ?? theme.colorScheme.primary; selectionColor = selectionStyle.selectionColor ?? theme.colorScheme.primary.withOpacity(0.40); - + break; case TargetPlatform.linux: forcePressEnabled = false; textSelectionControls ??= desktopTextSelectionHandleControls; diff --git a/packages/flutter/lib/src/material/text_selection_toolbar.dart b/packages/flutter/lib/src/material/text_selection_toolbar.dart index 96f00227cf8767213fc0f7414573253ddde45308..2ced3cfb707f6729c3f4791520caa614087b42d6 100644 --- a/packages/flutter/lib/src/material/text_selection_toolbar.dart +++ b/packages/flutter/lib/src/material/text_selection_toolbar.dart @@ -17,6 +17,8 @@ import 'material_localizations.dart'; import 'theme.dart'; const double _kToolbarHeight = 44.0; + +// Padding between the toolbar and the anchor. const double _kToolbarContentDistance = 8.0; /// A fully-functional Material-style text selection toolbar. diff --git a/packages/flutter/lib/src/material/theme_data.dart b/packages/flutter/lib/src/material/theme_data.dart index 1378bdfc3e968d77970e147ea8f26dbdbad9c386..6cd341993187f0281db6a9ec69d0da00d025b1e6 100644 --- a/packages/flutter/lib/src/material/theme_data.dart +++ b/packages/flutter/lib/src/material/theme_data.dart @@ -377,6 +377,7 @@ class ThemeData with Diagnosticable { case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.iOS: + case TargetPlatform.ohos: materialTapTargetSize ??= MaterialTapTargetSize.padded; case TargetPlatform.linux: case TargetPlatform.macOS: @@ -2366,7 +2367,7 @@ class VisualDensity with Diagnosticable { /// adaptive based on [defaultTargetPlatform]. static VisualDensity defaultDensityForPlatform(TargetPlatform platform) { return switch (platform) { - TargetPlatform.android || TargetPlatform.iOS || TargetPlatform.fuchsia => standard, + TargetPlatform.android || TargetPlatform.iOS || TargetPlatform.fuchsia || TargetPlatform.ohos => standard, TargetPlatform.linux || TargetPlatform.macOS || TargetPlatform.windows => compact, }; } diff --git a/packages/flutter/lib/src/material/time_picker.dart b/packages/flutter/lib/src/material/time_picker.dart index 1fe8d502c43df78f1fbf3377f7a6ef51a8407b76..b4790b25567964314530fafe098241d20a4e69c9 100644 --- a/packages/flutter/lib/src/material/time_picker.dart +++ b/packages/flutter/lib/src/material/time_picker.dart @@ -563,6 +563,7 @@ class _DayPeriodControl extends StatelessWidget { case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: _announceToAccessibility(context, MaterialLocalizations.of(context).anteMeridiemAbbreviation); case TargetPlatform.iOS: case TargetPlatform.macOS: @@ -581,6 +582,7 @@ class _DayPeriodControl extends StatelessWidget { case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: _announceToAccessibility(context, MaterialLocalizations.of(context).postMeridiemAbbreviation); case TargetPlatform.iOS: case TargetPlatform.macOS: @@ -2658,6 +2660,7 @@ class _TimePickerState extends State<_TimePicker> with RestorationMixin { case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: _vibrateTimer?.cancel(); _vibrateTimer = Timer(_kVibrateCommitDelay, () { HapticFeedback.vibrate(); diff --git a/packages/flutter/lib/src/material/tooltip.dart b/packages/flutter/lib/src/material/tooltip.dart index 4431019283dc1ba6e4279655e1a029e95ff330ea..dee828c7921ab0640774dbbbdf3939db3a56ae91 100644 --- a/packages/flutter/lib/src/material/tooltip.dart +++ b/packages/flutter/lib/src/material/tooltip.dart @@ -740,7 +740,8 @@ class TooltipState extends State with SingleTickerProviderStateMixin { TargetPlatform.windows => 24.0, TargetPlatform.android || TargetPlatform.fuchsia || - TargetPlatform.iOS => 32.0, + TargetPlatform.iOS || + TargetPlatform.ohos => 32.0, }; } @@ -751,7 +752,8 @@ class TooltipState extends State with SingleTickerProviderStateMixin { TargetPlatform.windows => const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0), TargetPlatform.android || TargetPlatform.fuchsia || - TargetPlatform.iOS => const EdgeInsets.symmetric(horizontal: 16.0, vertical: 4.0), + TargetPlatform.iOS || + TargetPlatform.ohos => const EdgeInsets.symmetric(horizontal: 16.0, vertical: 4.0), }; } @@ -762,7 +764,8 @@ class TooltipState extends State with SingleTickerProviderStateMixin { TargetPlatform.windows => 12.0, TargetPlatform.android || TargetPlatform.fuchsia || - TargetPlatform.iOS => 14.0, + TargetPlatform.iOS || + TargetPlatform.ohos => 14.0, }; } diff --git a/packages/flutter/lib/src/material/typography.dart b/packages/flutter/lib/src/material/typography.dart index 771c4f6e20dfff1b1bd6da515439e78af088858f..4faae73944d397057fb29df3ef1781034faca380 100644 --- a/packages/flutter/lib/src/material/typography.dart +++ b/packages/flutter/lib/src/material/typography.dart @@ -228,6 +228,11 @@ class Typography with Diagnosticable { case TargetPlatform.linux: black ??= blackHelsinki; white ??= whiteHelsinki; + break; + case TargetPlatform.ohos: + black ??= blackHelsinki; + white ??= whiteHelsinki; + break; case null: break; } diff --git a/packages/flutter/lib/src/painting/flutter_logo.dart b/packages/flutter/lib/src/painting/flutter_logo.dart index ecbdffd06164bda18464d32d1cecce102991b04a..fbdb4f6d311b61f75aeeb92c2350d0fc2f4f0a5b 100644 --- a/packages/flutter/lib/src/painting/flutter_logo.dart +++ b/packages/flutter/lib/src/painting/flutter_logo.dart @@ -240,7 +240,8 @@ class _FlutterLogoPainter extends BoxPainter { textDirection: TextDirection.ltr, ); _textPainter.layout(); - final ui.TextBox textSize = _textPainter.getBoxesForSelection(const TextSelection(baseOffset: 0, extentOffset: kLabel.length)).single; + final List boxList = _textPainter.getBoxesForSelection(const TextSelection(baseOffset: 0, extentOffset: kLabel.length)); + final ui.TextBox textSize = boxList.isNotEmpty ? boxList.single : ui.TextBox.fromLTRBD(0, 0, 0, 0, TextDirection.ltr); _textBoundingRect = Rect.fromLTRB(textSize.left, textSize.top, textSize.right, textSize.bottom); } diff --git a/packages/flutter/lib/src/rendering/editable.dart b/packages/flutter/lib/src/rendering/editable.dart index becab81f7a31b41315f236707707a105f653022d..85b684ef22f59c071e441b60c94f2840607f51cc 100644 --- a/packages/flutter/lib/src/rendering/editable.dart +++ b/packages/flutter/lib/src/rendering/editable.dart @@ -1811,6 +1811,7 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: // Override the height to take the full height of the glyph at the TextPosition // when not on iOS. iOS has special handling that creates a taller caret. // TODO(garyq): See the TODO for _computeCaretPrototype(). @@ -2205,6 +2206,8 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, extentOffset: position.offset, ); } + break; + case TargetPlatform.ohos: case TargetPlatform.fuchsia: case TargetPlatform.macOS: case TargetPlatform.linux: @@ -2272,6 +2275,7 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: _caretPrototype = Rect.fromLTWH(0.0, _kCaretHeightOffset, cursorWidth, cursorHeight - 2.0 * _kCaretHeightOffset); } } diff --git a/packages/flutter/lib/src/rendering/platform_view.dart b/packages/flutter/lib/src/rendering/platform_view.dart index 77866294bdbc5ad67b834e84b15fb106d62b8f13..39c1b0256e9ce3fb1e940a716843970bcc6f661f 100644 --- a/packages/flutter/lib/src/rendering/platform_view.dart +++ b/packages/flutter/lib/src/rendering/platform_view.dart @@ -270,6 +270,209 @@ class RenderAndroidView extends PlatformViewRenderBox { } } +class RenderOhosView extends PlatformViewRenderBox { + /// Creates a render object for an Ohos view. + RenderOhosView({ + required OhosViewController viewController, + required PlatformViewHitTestBehavior hitTestBehavior, + required Set> gestureRecognizers, + Clip clipBehavior = Clip.hardEdge, + }) : assert(viewController != null), + assert(hitTestBehavior != null), + assert(gestureRecognizers != null), + assert(clipBehavior != null), + _viewController = viewController, + _clipBehavior = clipBehavior, + super(controller: viewController, hitTestBehavior: hitTestBehavior, gestureRecognizers: gestureRecognizers) { + _viewController.pointTransformer = (Offset offset) => globalToLocal(offset); + updateGestureRecognizers(gestureRecognizers); + _viewController.addOnPlatformViewCreatedListener(_onPlatformViewCreated); + this.hitTestBehavior = hitTestBehavior; + _setOffset(); + } + + _PlatformViewState _state = _PlatformViewState.uninitialized; + + Size? _currentTextureSize; + + bool _isDisposed = false; + + /// The Ohos view controller for the Ohos view associated with this render object. + @override + OhosViewController get controller => _viewController; + + OhosViewController _viewController; + + /// Sets a new Ohos view controller. + @override + set controller(OhosViewController controller) { + assert(!_isDisposed); + assert(_viewController != null); + assert(controller != null); + if (_viewController == controller) { + return; + } + _viewController.removeOnPlatformViewCreatedListener(_onPlatformViewCreated); + super.controller = controller; + _viewController = controller; + _viewController.pointTransformer = (Offset offset) => globalToLocal(offset); + _sizePlatformView(); + if (_viewController.isCreated) { + markNeedsSemanticsUpdate(); + } + _viewController.addOnPlatformViewCreatedListener(_onPlatformViewCreated); + } + + /// {@macro flutter.material.Material.clipBehavior} + /// + /// Defaults to [Clip.hardEdge], and must not be null. + Clip get clipBehavior => _clipBehavior; + Clip _clipBehavior = Clip.hardEdge; + set clipBehavior(Clip value) { + assert(value != null); + if (value != _clipBehavior) { + _clipBehavior = value; + markNeedsPaint(); + markNeedsSemanticsUpdate(); + } + } + + void _onPlatformViewCreated(int id) { + assert(!_isDisposed); + markNeedsSemanticsUpdate(); + } + + @override + bool get sizedByParent => true; + + @override + bool get alwaysNeedsCompositing => true; + + @override + bool get isRepaintBoundary => true; + + @override + Size computeDryLayout(BoxConstraints constraints) { + return constraints.biggest; + } + + @override + void performResize() { + super.performResize(); + _sizePlatformView(); + } + + Future _sizePlatformView() async { + // Ohos virtual displays cannot have a zero size. + // Trying to size it to 0 crashes the app, which was happening when starting the app + // with a locked screen (see: https://github.com/flutter/flutter/issues/20456). + if (_state == _PlatformViewState.resizing || size.isEmpty) { + return; + } + + _state = _PlatformViewState.resizing; + markNeedsPaint(); + + Size targetSize; + do { + targetSize = size; + _currentTextureSize = await _viewController.setSize(targetSize); + if (_isDisposed) { + return; + } + // We've resized the platform view to targetSize, but it is possible that + // while we were resizing the render object's size was changed again. + // In that case we will resize the platform view again. + } while (size != targetSize); + + _state = _PlatformViewState.ready; + markNeedsPaint(); + } + + // Sets the offset of the underlying platform view on the platform side. + // + // This allows the Ohos native view to draw the a11y highlights in the same + // location on the screen as the platform view widget in the Flutter framework. + // + // It also allows platform code to obtain the correct position of the Ohos + // native view on the screen. + void _setOffset() { + SchedulerBinding.instance.addPostFrameCallback((_) async { + if (!_isDisposed) { + if (attached) { + await _viewController.setOffset(localToGlobal(Offset.zero)); + } + // Schedule a new post frame callback. + _setOffset(); + } + }); + } + + @override + void paint(PaintingContext context, Offset offset) { + if (_viewController.textureId == null || _currentTextureSize == null) { + return; + } + + // As resizing the Ohos view happens asynchronously we don't know exactly when is a + // texture frame with the new size is ready for consumption. + // TextureLayer is unaware of the texture frame's size and always maps it to the + // specified rect. If the rect we provide has a different size from the current texture frame's + // size the texture frame will be scaled. + // To prevent unwanted scaling artifacts while resizing, clip the texture. + // This guarantees that the size of the texture frame we're painting is always + // _currentOhosTextureSize. + final bool isTextureLargerThanWidget = _currentTextureSize!.width > size.width || + _currentTextureSize!.height > size.height; + if (isTextureLargerThanWidget && clipBehavior != Clip.none) { + _clipRectLayer.layer = context.pushClipRect( + true, + offset, + offset & size, + _paintTexture, + clipBehavior: clipBehavior, + oldLayer: _clipRectLayer.layer, + ); + return; + } + _clipRectLayer.layer = null; + _paintTexture(context, offset); + } + + final LayerHandle _clipRectLayer = LayerHandle(); + + @override + void dispose() { + _isDisposed = true; + _clipRectLayer.layer = null; + _viewController.removeOnPlatformViewCreatedListener(_onPlatformViewCreated); + super.dispose(); + } + + void _paintTexture(PaintingContext context, Offset offset) { + if (_currentTextureSize == null) { + return; + } + + context.addLayer(TextureLayer( + rect: offset & _currentTextureSize!, + textureId: _viewController.textureId!, + )); + } + + @override + void describeSemanticsConfiguration(SemanticsConfiguration config) { + // Don't call the super implementation since `platformViewId` should + // be set only when the platform view is created, but the concept of + // a "created" platform view belongs to this subclass. + config.isSemanticBoundary = true; + + if (_viewController.isCreated) { + config.platformViewId = _viewController.viewId; + } + } +} + /// Common render-layer functionality for iOS and macOS platform views. /// /// Provides the basic rendering logic for iOS and macOS platformviews. diff --git a/packages/flutter/lib/src/rendering/view.dart b/packages/flutter/lib/src/rendering/view.dart index c4842cfc00af57d78c392c3497b1cdb84c32e742..914e4da08cdd56cc980af539ac19cfc7eb4435b9 100644 --- a/packages/flutter/lib/src/rendering/view.dart +++ b/packages/flutter/lib/src/rendering/view.dart @@ -378,6 +378,8 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin switch (defaultTargetPlatform) { case TargetPlatform.android: lowerOverlayStyle = layer!.find(bottom); + break; + case TargetPlatform.ohos: case TargetPlatform.fuchsia: case TargetPlatform.iOS: case TargetPlatform.linux: diff --git a/packages/flutter/lib/src/services/binding.dart b/packages/flutter/lib/src/services/binding.dart index e6105ed6537f684abedf359afaa6a0ff94daca9a..134f7f888e1d71dda58eb090003920aa0d52c745 100644 --- a/packages/flutter/lib/src/services/binding.dart +++ b/packages/flutter/lib/src/services/binding.dart @@ -159,6 +159,9 @@ mixin ServicesBinding on BindingBase, SchedulerBinding { @mustCallSuper Future handleSystemMessage(Object systemMessage) async { final Map message = systemMessage as Map; + if (message['type'] is! String) { + return; + } final String type = message['type'] as String; switch (type) { case 'memoryPressure': diff --git a/packages/flutter/lib/src/services/keyboard_key.g.dart b/packages/flutter/lib/src/services/keyboard_key.g.dart index 4feaa7b0ad42181f0ba8a2f916daee633d46efb5..e83fd9bd017191e6e199dd296561e834e4a80cff 100644 --- a/packages/flutter/lib/src/services/keyboard_key.g.dart +++ b/packages/flutter/lib/src/services/keyboard_key.g.dart @@ -341,6 +341,12 @@ class LogicalKeyboardKey extends KeyboardKey { /// This is used by platform-specific code to generate Flutter key codes. static const int glfwPlane = 0x01800000000; + /// The plane value for the private keys defined by the ohos embedding. + /// + /// This is used by platform-specific code to generate Flutter key codes. + static const int ohosPlane = 0x01900000000; + + /// Represents the logical "Space" key on the keyboard. /// /// See the function [RawKeyEvent.logicalKey] for more information. diff --git a/packages/flutter/lib/src/services/keyboard_maps.g.dart b/packages/flutter/lib/src/services/keyboard_maps.g.dart index b94e1c82e532678ad0dbe8a379ad6765ccee57ac..cbba13388bace800df3f10120b4d90bd3c690740 100644 --- a/packages/flutter/lib/src/services/keyboard_maps.g.dart +++ b/packages/flutter/lib/src/services/keyboard_maps.g.dart @@ -538,6 +538,651 @@ const Map kAndroidNumPadMap = 163: LogicalKeyboardKey.numpadParenRight, }; +/// Maps Ohos-specific key codes to the matching [LogicalKeyboardKey]. +const Map kOhosToLogicalKey = { + 2000: LogicalKeyboardKey.digit0, + 2001: LogicalKeyboardKey.digit1, + 2002: LogicalKeyboardKey.digit2, + 2003: LogicalKeyboardKey.digit3, + 2004: LogicalKeyboardKey.digit4, + 2005: LogicalKeyboardKey.digit5, + 2006: LogicalKeyboardKey.digit6, + 2007: LogicalKeyboardKey.digit7, + 2008: LogicalKeyboardKey.digit8, + 2009: LogicalKeyboardKey.digit9, + 2010: LogicalKeyboardKey.asterisk, + 2011: LogicalKeyboardKey.numberSign, + 2012: LogicalKeyboardKey.arrowUp, + 2013: LogicalKeyboardKey.arrowDown, + 2014: LogicalKeyboardKey.arrowLeft, + 2015: LogicalKeyboardKey.arrowRight, + //2016: LogicalKeyboardKey.dpadCenter, + 2017: LogicalKeyboardKey.keyA, + 2018: LogicalKeyboardKey.keyB, + 2019: LogicalKeyboardKey.keyC, + 2020: LogicalKeyboardKey.keyD, + 2021: LogicalKeyboardKey.keyE, + 2022: LogicalKeyboardKey.keyF, + 2023: LogicalKeyboardKey.keyG, + 2024: LogicalKeyboardKey.keyH, + 2025: LogicalKeyboardKey.keyI, + 2026: LogicalKeyboardKey.keyJ, + 2027: LogicalKeyboardKey.keyK, + 2028: LogicalKeyboardKey.keyL, + 2029: LogicalKeyboardKey.keyM, + 2030: LogicalKeyboardKey.keyN, + 2031: LogicalKeyboardKey.keyO, + 2032: LogicalKeyboardKey.keyP, + 2033: LogicalKeyboardKey.keyQ, + 2034: LogicalKeyboardKey.keyR, + 2035: LogicalKeyboardKey.keyS, + 2036: LogicalKeyboardKey.keyT, + 2037: LogicalKeyboardKey.keyU, + 2038: LogicalKeyboardKey.keyV, + 2039: LogicalKeyboardKey.keyW, + 2040: LogicalKeyboardKey.keyX, + 2041: LogicalKeyboardKey.keyY, + 2042: LogicalKeyboardKey.keyZ, + 2043: LogicalKeyboardKey.comma, + 2044: LogicalKeyboardKey.period, + 2045: LogicalKeyboardKey.altLeft, + 2046: LogicalKeyboardKey.altRight, + 2047: LogicalKeyboardKey.shiftLeft, + 2048: LogicalKeyboardKey.shiftRight, + 2049: LogicalKeyboardKey.tab, + 2050: LogicalKeyboardKey.space, + 2052: LogicalKeyboardKey.launchWebBrowser, + 2053: LogicalKeyboardKey.launchMail, + 2054: LogicalKeyboardKey.enter, + 2055: LogicalKeyboardKey.backspace, + 2056: LogicalKeyboardKey.backquote, + 2057: LogicalKeyboardKey.minus, + 2058: LogicalKeyboardKey.equal, + 2059: LogicalKeyboardKey.bracketLeft, + 2060: LogicalKeyboardKey.bracketRight, + 2061: LogicalKeyboardKey.backslash, + 2062: LogicalKeyboardKey.semicolon, + 2063: LogicalKeyboardKey.quote, + 2064: LogicalKeyboardKey.slash, + 2067: LogicalKeyboardKey.contextMenu, + 2466: LogicalKeyboardKey.compose, + 2068: LogicalKeyboardKey.pageUp, + 2069: LogicalKeyboardKey.pageDown, + 2070: LogicalKeyboardKey.escape, + 2071: LogicalKeyboardKey.delete, + 2072: LogicalKeyboardKey.controlLeft, + 2073: LogicalKeyboardKey.controlRight, + 2074: LogicalKeyboardKey.capsLock, + 2075: LogicalKeyboardKey.scrollLock, + 2076: LogicalKeyboardKey.metaLeft, + 2077: LogicalKeyboardKey.metaRight, + 2078: LogicalKeyboardKey.fn, + 2079: LogicalKeyboardKey.printScreen, + 2080: LogicalKeyboardKey.pause, + 2081: LogicalKeyboardKey.home, + 2082: LogicalKeyboardKey.end, + 2083: LogicalKeyboardKey.insert, + 2084: LogicalKeyboardKey.browserForward, + 2085: LogicalKeyboardKey.mediaPlay, + 2643: LogicalKeyboardKey.play, + 2086: LogicalKeyboardKey.mediaPause, + 2087: LogicalKeyboardKey.mediaClose, + 2088: LogicalKeyboardKey.eject, + 2089: LogicalKeyboardKey.mediaRecord, + 2090: LogicalKeyboardKey.f1, + 2091: LogicalKeyboardKey.f2, + 2092: LogicalKeyboardKey.f3, + 2093: LogicalKeyboardKey.f4, + 2094: LogicalKeyboardKey.f5, + 2095: LogicalKeyboardKey.f6, + 2096: LogicalKeyboardKey.f7, + 2097: LogicalKeyboardKey.f8, + 2098: LogicalKeyboardKey.f9, + 2099: LogicalKeyboardKey.f10, + 2100: LogicalKeyboardKey.f11, + 2101: LogicalKeyboardKey.f12, + 2102: LogicalKeyboardKey.numLock, + 2103: LogicalKeyboardKey.numpad0, + 2104: LogicalKeyboardKey.numpad1, + 2105: LogicalKeyboardKey.numpad2, + 2106: LogicalKeyboardKey.numpad3, + 2107: LogicalKeyboardKey.numpad4, + 2108: LogicalKeyboardKey.numpad5, + 2109: LogicalKeyboardKey.numpad6, + 2110: LogicalKeyboardKey.numpad7, + 2111: LogicalKeyboardKey.numpad8, + 2112: LogicalKeyboardKey.numpad9, + 2113: LogicalKeyboardKey.numpadDivide, + 2114: LogicalKeyboardKey.numpadMultiply, + 2115: LogicalKeyboardKey.numpadSubtract, + 2116: LogicalKeyboardKey.numpadAdd, + 2117: LogicalKeyboardKey.numpadDecimal, + 2118: LogicalKeyboardKey.numpadComma, + 2119: LogicalKeyboardKey.numpadEnter, + 2120: LogicalKeyboardKey.numpadEqual, + 2121: LogicalKeyboardKey.numpadParenLeft, + 2122: LogicalKeyboardKey.numpadParenRight, + 16: LogicalKeyboardKey.audioVolumeUp, + 17: LogicalKeyboardKey.audioVolumeDown, + 18: LogicalKeyboardKey.power, + 22: LogicalKeyboardKey.microphoneVolumeMute, + 1: LogicalKeyboardKey.home, + 2: LogicalKeyboardKey.goBack, +// 2210: LogicalKeyboardKey.virtualMultitask, +// 2301: LogicalKeyboardKey.buttonA, +// 2302: LogicalKeyboardKey.buttonB, +// 2303: LogicalKeyboardKey.buttonC, +// 2304: LogicalKeyboardKey.buttonX, +// 2305: LogicalKeyboardKey.buttonY, +// 2306: LogicalKeyboardKey.buttonZ, +// 2307: LogicalKeyboardKey.buttonL1, +// 2308: LogicalKeyboardKey.buttonR1, +// 2309: LogicalKeyboardKey.buttonL2, +// 2310: LogicalKeyboardKey.buttonR2, +// 2311: LogicalKeyboardKey.buttonSelect, +// 2312: LogicalKeyboardKey.buttonStart, +// 2313: LogicalKeyboardKey.buttonMode, +// 2314: LogicalKeyboardKey.buttonThumbl, +// 2315: LogicalKeyboardKey.buttonThumbr, +// 2401: LogicalKeyboardKey.buttonTrigger, +// 2402: LogicalKeyboardKey.buttonThumb, +// 2403: LogicalKeyboardKey.buttonThumb2, +// 2404: LogicalKeyboardKey.buttonTop, +// 2405: LogicalKeyboardKey.buttonTop2, +// 2406: LogicalKeyboardKey.buttonPinkie, +// 2407: LogicalKeyboardKey.buttonBase1, +// 2408: LogicalKeyboardKey.buttonBase2, +// 2409: LogicalKeyboardKey.buttonBase3, +// 2410: LogicalKeyboardKey.buttonBase4, +// 2411: LogicalKeyboardKey.buttonBase5, +// 2412: LogicalKeyboardKey.buttonBase6, +// 2413: LogicalKeyboardKey.buttonBase7, +// 2414: LogicalKeyboardKey.buttonBase8, +// 2415: LogicalKeyboardKey.buttonBase9, +// 2416: LogicalKeyboardKey.buttonDead, + 19: LogicalKeyboardKey.camera, + 40: LogicalKeyboardKey.brightnessUp, + 41: LogicalKeyboardKey.brightnessDown, + 5: LogicalKeyboardKey.clear, +// 7: LogicalKeyboardKey.focus, +// 9: LogicalKeyboardKey.search, + 10: LogicalKeyboardKey.mediaPlayPause, + 11: LogicalKeyboardKey.mediaStop, + 12: LogicalKeyboardKey.mediaTrackNext, + 13: LogicalKeyboardKey.mediaTrackPrevious, + 14: LogicalKeyboardKey.mediaRewind, + 15: LogicalKeyboardKey.mediaFastForward, +// 20: LogicalKeyboardKey.voiceAssistant, +// -1: LogicalKeyboardKey.unknown, + 2600: LogicalKeyboardKey.sleep, + 2601: LogicalKeyboardKey.zenkakuHankaku, +// 2602: LogicalKeyboardKey.102nd, +// 2603: LogicalKeyboardKey.ro, + 2604: LogicalKeyboardKey.katakana, + 2605: LogicalKeyboardKey.hiragana, + 2606: LogicalKeyboardKey.convert, + 2607: LogicalKeyboardKey.hiraganaKatakana, + 2608: LogicalKeyboardKey.nonConvert, +// 2609: LogicalKeyboardKey.linefeed, +// 2610: LogicalKeyboardKey.macro, +// 2611: LogicalKeyboardKey.numpadPlusminus, +// 2612: LogicalKeyboardKey.scale, +// 2613: LogicalKeyboardKey.hanguel, +// 2614: LogicalKeyboardKey.hanja, + 2615: LogicalKeyboardKey.intlYen, +// 2616: LogicalKeyboardKey.stop, + 2617: LogicalKeyboardKey.again, + 2618: LogicalKeyboardKey.props, + 2619: LogicalKeyboardKey.undo, + 2620: LogicalKeyboardKey.copy, + 2621: LogicalKeyboardKey.open, + 2622: LogicalKeyboardKey.paste, + 2623: LogicalKeyboardKey.find, + 2624: LogicalKeyboardKey.cut, + 2625: LogicalKeyboardKey.help, +// 2626: LogicalKeyboardKey.calc, +// 2627: LogicalKeyboardKey.file, + 2628: LogicalKeyboardKey.browserFavorites, +// 12: LogicalKeyboardKey.mediaTrackNext, + 2630: LogicalKeyboardKey.mediaPlayPause, +// 13: LogicalKeyboardKey.mediaTrackPrevious, + 2632: LogicalKeyboardKey.close, + 3: LogicalKeyboardKey.call, +// 2634: LogicalKeyboardKey.config, + 2635: LogicalKeyboardKey.browserRefresh, + 2636: LogicalKeyboardKey.exit, +// 2637: LogicalKeyboardKey.edit, +// 2638: LogicalKeyboardKey.scrollup, +// 2639: LogicalKeyboardKey.scrolldown, +// 2640: LogicalKeyboardKey.new, + 2641: LogicalKeyboardKey.redo, + 2642: LogicalKeyboardKey.close, +// 2644: LogicalKeyboardKey.bassboost, + 2645: LogicalKeyboardKey.print, +// 2646: LogicalKeyboardKey.chat, +// 2647: LogicalKeyboardKey.finance, + 2648: LogicalKeyboardKey.cancel, +// 2649: LogicalKeyboardKey.kbdillumToggle, +// 2650: LogicalKeyboardKey.kbdillumDown, +// 2651: LogicalKeyboardKey.kbdillumUp, +// 2652: LogicalKeyboardKey.send, +// 2653: LogicalKeyboardKey.reply, +// 2654: LogicalKeyboardKey.forwardmail, + 2655: LogicalKeyboardKey.save, +// 2656: LogicalKeyboardKey.documents, +// 2657: LogicalKeyboardKey.videoNext, +// 2658: LogicalKeyboardKey.videoPrev, +// 2659: LogicalKeyboardKey.brightnessCycle, +// 2660: LogicalKeyboardKey.brightnessZero, +// 2661: LogicalKeyboardKey.displayOff, +// 2663: LogicalKeyboardKey.goto, + 2664: LogicalKeyboardKey.info, +// 2665: LogicalKeyboardKey.program, +// 2666: LogicalKeyboardKey.pvr, + 2667: LogicalKeyboardKey.subtitle, +// 2668: LogicalKeyboardKey.fullScreen, +// 2669: LogicalKeyboardKey.keyboard, +// 2670: LogicalKeyboardKey.aspectRatio, +// 2671: LogicalKeyboardKey.pc, + 2672: LogicalKeyboardKey.tv, +// 2673: LogicalKeyboardKey.tv2, +// 2674: LogicalKeyboardKey.vcr, +// 2675: LogicalKeyboardKey.vcr2, +// 2676: LogicalKeyboardKey.sat, +// 2677: LogicalKeyboardKey.cd, +// 2678: LogicalKeyboardKey.tape, +// 2679: LogicalKeyboardKey.tuner, +// 2680: LogicalKeyboardKey.player, +// 2681: LogicalKeyboardKey.dvd, +// 2682: LogicalKeyboardKey.audio, +// 2683: LogicalKeyboardKey.video, +// 2684: LogicalKeyboardKey.memo, +// 2685: LogicalKeyboardKey.calendar, + 2686: LogicalKeyboardKey.colorF0Red, + 2687: LogicalKeyboardKey.colorF1Green, + 2688: LogicalKeyboardKey.colorF2Yellow, + 2689: LogicalKeyboardKey.colorF3Blue, + 2690: LogicalKeyboardKey.channelUp, + 2691: LogicalKeyboardKey.channelDown, +// 2692: LogicalKeyboardKey.last, +// 2693: LogicalKeyboardKey.restart, +// 2694: LogicalKeyboardKey.slow, +// 2695: LogicalKeyboardKey.shuffle, +// 2696: LogicalKeyboardKey.videophone, +// 2697: LogicalKeyboardKey.games, + 2698: LogicalKeyboardKey.zoomIn, + 2699: LogicalKeyboardKey.zoomOut, +// 2700: LogicalKeyboardKey.zoomreset, +// 2701: LogicalKeyboardKey.wordprocessor, +// 2702: LogicalKeyboardKey.editor, +// 2703: LogicalKeyboardKey.spreadsheet, +// 2704: LogicalKeyboardKey.graphicseditor, +// 2705: LogicalKeyboardKey.presentation, +// 2706: LogicalKeyboardKey.database, +// 2707: LogicalKeyboardKey.news, +// 2708: LogicalKeyboardKey.voicemail, +// 2709: LogicalKeyboardKey.addressbook, +// 2710: LogicalKeyboardKey.messenger, +// 2711: LogicalKeyboardKey.brightnessToggle, + 2712: LogicalKeyboardKey.spellCheck, +// 2713: LogicalKeyboardKey.coffee, +// 2714: LogicalKeyboardKey.mediaRepeat, +// 2715: LogicalKeyboardKey.images, +// 2716: LogicalKeyboardKey.buttonconfig, +// 2717: LogicalKeyboardKey.taskmanager, +// 2718: LogicalKeyboardKey.journal, +// 2719: LogicalKeyboardKey.controlpanel, +// 2720: LogicalKeyboardKey.appselect, +// 2721: LogicalKeyboardKey.screensaver, +// 2722: LogicalKeyboardKey.assistant, +// 2723: LogicalKeyboardKey.kbdLayoutNext, +// 2724: LogicalKeyboardKey.brightnessMin, +// 2725: LogicalKeyboardKey.brightnessMax, +// 2726: LogicalKeyboardKey.kbdinputassistPrev, +// 2727: LogicalKeyboardKey.kbdinputassistNext, +// 2728: LogicalKeyboardKey.kbdinputassistPrevgroup, +// 2729: LogicalKeyboardKey.kbdinputassistNextgroup, +// 2730: LogicalKeyboardKey.kbdinputassistAccept, +// 2731: LogicalKeyboardKey.kbdinputassistCancel, +// 2800: LogicalKeyboardKey.front, +// 2801: LogicalKeyboardKey.setup, + 2802: LogicalKeyboardKey.wakeUp, +// 2803: LogicalKeyboardKey.sendfile, +// 2804: LogicalKeyboardKey.deletefile, +// 2805: LogicalKeyboardKey.xfer, +// 2806: LogicalKeyboardKey.prog1, +// 2807: LogicalKeyboardKey.prog2, +// 2808: LogicalKeyboardKey.msdos, +// 2809: LogicalKeyboardKey.screenlock, +// 2810: LogicalKeyboardKey.directionRotateDisplay, +// 2811: LogicalKeyboardKey.cyclewindows, +// 2812: LogicalKeyboardKey.computer, + 2813: LogicalKeyboardKey.eject, +// 2814: LogicalKeyboardKey.iso, +// 2815: LogicalKeyboardKey.move, + 2816: LogicalKeyboardKey.f13, + 2817: LogicalKeyboardKey.f14, + 2818: LogicalKeyboardKey.f15, + 2819: LogicalKeyboardKey.f16, + 2820: LogicalKeyboardKey.f17, + 2821: LogicalKeyboardKey.f18, + 2822: LogicalKeyboardKey.f19, + 2823: LogicalKeyboardKey.f20, + 2824: LogicalKeyboardKey.f21, + 2825: LogicalKeyboardKey.f22, + 2826: LogicalKeyboardKey.f23, + 2827: LogicalKeyboardKey.f24, +// 2828: LogicalKeyboardKey.prog3, +// 2829: LogicalKeyboardKey.prog4, +// 2830: LogicalKeyboardKey.dashboard, + 2831: LogicalKeyboardKey.suspend, +// 2832: LogicalKeyboardKey.hp, +// 2833: LogicalKeyboardKey.sound, + 2834: LogicalKeyboardKey.question, + 2065: LogicalKeyboardKey.at, +// 2836: LogicalKeyboardKey.connect, +// 2837: LogicalKeyboardKey.sport, +// 2838: LogicalKeyboardKey.shop, +// 2839: LogicalKeyboardKey.alterase, + 6: LogicalKeyboardKey.headsetHook, +// 2841: LogicalKeyboardKey.switchvideomode, +// 2842: LogicalKeyboardKey.battery, +// 2843: LogicalKeyboardKey.bluetooth, +// 2844: LogicalKeyboardKey.wlan, +// 2845: LogicalKeyboardKey.uwb, +// 2846: LogicalKeyboardKey.wwanWimax, +// 2847: LogicalKeyboardKey.rfkill, + 23: LogicalKeyboardKey.audioVolumeMute, +// 2848: LogicalKeyboardKey.f26, +// 3001: LogicalKeyboardKey.channel, +// 3100: LogicalKeyboardKey.btn0, +// 3101: LogicalKeyboardKey.btn1, +// 3102: LogicalKeyboardKey.btn2, +// 3103: LogicalKeyboardKey.btn3, +// 3104: LogicalKeyboardKey.btn4, +// 3105: LogicalKeyboardKey.btn5, +// 3106: LogicalKeyboardKey.btn6, +// 3107: LogicalKeyboardKey.btn7, +// 3108: LogicalKeyboardKey.btn8, +// 3109: LogicalKeyboardKey.btn9, +// 3201: LogicalKeyboardKey.brlDot1, +// 3202: LogicalKeyboardKey.brlDot2, +// 3203: LogicalKeyboardKey.brlDot3, +// 3204: LogicalKeyboardKey.brlDot4, +// 3205: LogicalKeyboardKey.brlDot5, +// 3206: LogicalKeyboardKey.brlDot6, +// 3207: LogicalKeyboardKey.brlDot7, +// 3208: LogicalKeyboardKey.brlDot8, +// 3209: LogicalKeyboardKey.brlDot9, +// 3210: LogicalKeyboardKey.brlDot10, + 4: LogicalKeyboardKey.endCall, +// 2629: LogicalKeyboardKey.next, +// 2631: LogicalKeyboardKey.previous, +}; + +/// Maps Ohos-specific scan codes to the matching [PhysicalKeyboardKey]. +const Map kOhosToPhysicalKey = { + 1: PhysicalKeyboardKey.escape, + 2: PhysicalKeyboardKey.digit1, + 3: PhysicalKeyboardKey.digit2, + 4: PhysicalKeyboardKey.digit3, + 5: PhysicalKeyboardKey.digit4, + 6: PhysicalKeyboardKey.digit5, + 7: PhysicalKeyboardKey.digit6, + 8: PhysicalKeyboardKey.digit7, + 9: PhysicalKeyboardKey.digit8, + 10: PhysicalKeyboardKey.digit9, + 11: PhysicalKeyboardKey.digit0, + 12: PhysicalKeyboardKey.minus, + 13: PhysicalKeyboardKey.equal, + 14: PhysicalKeyboardKey.backspace, + 15: PhysicalKeyboardKey.tab, + 16: PhysicalKeyboardKey.keyQ, + 17: PhysicalKeyboardKey.keyW, + 18: PhysicalKeyboardKey.keyE, + 19: PhysicalKeyboardKey.keyR, + 20: PhysicalKeyboardKey.keyT, + 21: PhysicalKeyboardKey.keyY, + 22: PhysicalKeyboardKey.keyU, + 23: PhysicalKeyboardKey.keyI, + 24: PhysicalKeyboardKey.keyO, + 25: PhysicalKeyboardKey.keyP, + 26: PhysicalKeyboardKey.bracketLeft, + 27: PhysicalKeyboardKey.bracketRight, + 28: PhysicalKeyboardKey.enter, + 29: PhysicalKeyboardKey.controlLeft, + 30: PhysicalKeyboardKey.keyA, + 31: PhysicalKeyboardKey.keyS, + 32: PhysicalKeyboardKey.keyD, + 33: PhysicalKeyboardKey.keyF, + 34: PhysicalKeyboardKey.keyG, + 35: PhysicalKeyboardKey.keyH, + 36: PhysicalKeyboardKey.keyJ, + 37: PhysicalKeyboardKey.keyK, + 38: PhysicalKeyboardKey.keyL, + 39: PhysicalKeyboardKey.semicolon, + 40: PhysicalKeyboardKey.quote, + 41: PhysicalKeyboardKey.backquote, + 42: PhysicalKeyboardKey.shiftLeft, + 43: PhysicalKeyboardKey.backslash, + 44: PhysicalKeyboardKey.keyZ, + 45: PhysicalKeyboardKey.keyX, + 46: PhysicalKeyboardKey.keyC, + 47: PhysicalKeyboardKey.keyV, + 48: PhysicalKeyboardKey.keyB, + 49: PhysicalKeyboardKey.keyN, + 50: PhysicalKeyboardKey.keyM, + 51: PhysicalKeyboardKey.comma, + 52: PhysicalKeyboardKey.period, + 53: PhysicalKeyboardKey.slash, + 54: PhysicalKeyboardKey.shiftRight, + 55: PhysicalKeyboardKey.numpadMultiply, + 56: PhysicalKeyboardKey.altLeft, + 57: PhysicalKeyboardKey.space, + 58: PhysicalKeyboardKey.capsLock, + 59: PhysicalKeyboardKey.f1, + 60: PhysicalKeyboardKey.f2, + 61: PhysicalKeyboardKey.f3, + 62: PhysicalKeyboardKey.f4, + 63: PhysicalKeyboardKey.f5, + 64: PhysicalKeyboardKey.f6, + 65: PhysicalKeyboardKey.f7, + 66: PhysicalKeyboardKey.f8, + 67: PhysicalKeyboardKey.f9, + 68: PhysicalKeyboardKey.f10, + 69: PhysicalKeyboardKey.numLock, + 70: PhysicalKeyboardKey.scrollLock, + 71: PhysicalKeyboardKey.numpad7, + 72: PhysicalKeyboardKey.numpad8, + 73: PhysicalKeyboardKey.numpad9, + 74: PhysicalKeyboardKey.numpadSubtract, + 75: PhysicalKeyboardKey.numpad4, + 76: PhysicalKeyboardKey.numpad5, + 77: PhysicalKeyboardKey.numpad6, + 78: PhysicalKeyboardKey.numpadAdd, + 79: PhysicalKeyboardKey.numpad1, + 80: PhysicalKeyboardKey.numpad2, + 81: PhysicalKeyboardKey.numpad3, + 82: PhysicalKeyboardKey.numpad0, + 83: PhysicalKeyboardKey.numpadDecimal, + 86: PhysicalKeyboardKey.backslash, + 87: PhysicalKeyboardKey.f11, + 88: PhysicalKeyboardKey.f12, + 89: PhysicalKeyboardKey.intlRo, + 90: PhysicalKeyboardKey.lang3, + 91: PhysicalKeyboardKey.lang4, + 92: PhysicalKeyboardKey.convert, + 94: PhysicalKeyboardKey.nonConvert, + 95: PhysicalKeyboardKey.numpadComma, + 96: PhysicalKeyboardKey.numpadEnter, + 97: PhysicalKeyboardKey.controlRight, + 98: PhysicalKeyboardKey.numpadDivide, + 99: PhysicalKeyboardKey.printScreen, + 100: PhysicalKeyboardKey.altRight, + 102: PhysicalKeyboardKey.home, + 103: PhysicalKeyboardKey.arrowUp, + 104: PhysicalKeyboardKey.pageUp, + 105: PhysicalKeyboardKey.arrowLeft, + 106: PhysicalKeyboardKey.arrowRight, + 107: PhysicalKeyboardKey.end, + 108: PhysicalKeyboardKey.arrowDown, + 109: PhysicalKeyboardKey.pageDown, + 110: PhysicalKeyboardKey.insert, + 111: PhysicalKeyboardKey.delete, + 113: PhysicalKeyboardKey.audioVolumeMute, + 114: PhysicalKeyboardKey.audioVolumeDown, + 115: PhysicalKeyboardKey.audioVolumeUp, + 116: PhysicalKeyboardKey.power, + 117: PhysicalKeyboardKey.numpadEqual, + 119: PhysicalKeyboardKey.pause, + 121: PhysicalKeyboardKey.numpadComma, + 124: PhysicalKeyboardKey.intlYen, + 125: PhysicalKeyboardKey.metaLeft, + 126: PhysicalKeyboardKey.metaRight, + 127: PhysicalKeyboardKey.contextMenu, + 128: PhysicalKeyboardKey.mediaStop, + 129: PhysicalKeyboardKey.again, + 130: PhysicalKeyboardKey.props, + 131: PhysicalKeyboardKey.undo, + 133: PhysicalKeyboardKey.copy, + 134: PhysicalKeyboardKey.open, + 135: PhysicalKeyboardKey.paste, + 136: PhysicalKeyboardKey.find, + 137: PhysicalKeyboardKey.cut, + 138: PhysicalKeyboardKey.help, + 139: PhysicalKeyboardKey.contextMenu, + 142: PhysicalKeyboardKey.sleep, + 143: PhysicalKeyboardKey.wakeUp, + 152: PhysicalKeyboardKey.power, + 155: PhysicalKeyboardKey.launchMail, + 156: PhysicalKeyboardKey.browserFavorites, + 159: PhysicalKeyboardKey.browserForward, + 160: PhysicalKeyboardKey.close, + 161: PhysicalKeyboardKey.eject, + 162: PhysicalKeyboardKey.eject, + 163: PhysicalKeyboardKey.mediaTrackNext, + 164: PhysicalKeyboardKey.mediaPlayPause, + 165: PhysicalKeyboardKey.mediaTrackPrevious, + 166: PhysicalKeyboardKey.mediaStop, + 167: PhysicalKeyboardKey.mediaRecord, + 168: PhysicalKeyboardKey.mediaRewind, + 174: PhysicalKeyboardKey.exit, + 177: PhysicalKeyboardKey.pageUp, + 178: PhysicalKeyboardKey.pageDown, + 179: PhysicalKeyboardKey.numpadParenLeft, + 180: PhysicalKeyboardKey.numpadParenRight, + 182: PhysicalKeyboardKey.redo, + 183: PhysicalKeyboardKey.f13, + 184: PhysicalKeyboardKey.f14, + 185: PhysicalKeyboardKey.f15, + 186: PhysicalKeyboardKey.f16, + 187: PhysicalKeyboardKey.f17, + 188: PhysicalKeyboardKey.f18, + 189: PhysicalKeyboardKey.f19, + 190: PhysicalKeyboardKey.f20, + 191: PhysicalKeyboardKey.f21, + 192: PhysicalKeyboardKey.f22, + 193: PhysicalKeyboardKey.f23, + 194: PhysicalKeyboardKey.f24, + 200: PhysicalKeyboardKey.mediaPlay, + 201: PhysicalKeyboardKey.mediaPause, + 205: PhysicalKeyboardKey.suspend, + 206: PhysicalKeyboardKey.close, + 207: PhysicalKeyboardKey.mediaPlay, + 208: PhysicalKeyboardKey.mediaFastForward, + 209: PhysicalKeyboardKey.bassBoost, + 210: PhysicalKeyboardKey.print, + 215: PhysicalKeyboardKey.launchMail, + 217: PhysicalKeyboardKey.browserSearch, + 224: PhysicalKeyboardKey.brightnessDown, + 225: PhysicalKeyboardKey.brightnessUp, + 256: PhysicalKeyboardKey.gameButton1, + 257: PhysicalKeyboardKey.gameButton2, + 258: PhysicalKeyboardKey.gameButton3, + 259: PhysicalKeyboardKey.gameButton4, + 260: PhysicalKeyboardKey.gameButton5, + 261: PhysicalKeyboardKey.gameButton6, + 262: PhysicalKeyboardKey.gameButton7, + 263: PhysicalKeyboardKey.gameButton8, + 264: PhysicalKeyboardKey.gameButton9, + 265: PhysicalKeyboardKey.gameButton10, + 266: PhysicalKeyboardKey.gameButton11, + 267: PhysicalKeyboardKey.gameButton12, + 268: PhysicalKeyboardKey.gameButton13, + 269: PhysicalKeyboardKey.gameButton14, + 270: PhysicalKeyboardKey.gameButton15, + 271: PhysicalKeyboardKey.gameButton16, + 288: PhysicalKeyboardKey.gameButton1, + 289: PhysicalKeyboardKey.gameButton2, + 290: PhysicalKeyboardKey.gameButton3, + 291: PhysicalKeyboardKey.gameButton4, + 292: PhysicalKeyboardKey.gameButton5, + 293: PhysicalKeyboardKey.gameButton6, + 294: PhysicalKeyboardKey.gameButton7, + 295: PhysicalKeyboardKey.gameButton8, + 296: PhysicalKeyboardKey.gameButton9, + 297: PhysicalKeyboardKey.gameButton10, + 298: PhysicalKeyboardKey.gameButton11, + 299: PhysicalKeyboardKey.gameButton12, + 300: PhysicalKeyboardKey.gameButton13, + 301: PhysicalKeyboardKey.gameButton14, + 302: PhysicalKeyboardKey.gameButton15, + 303: PhysicalKeyboardKey.gameButton16, + 304: PhysicalKeyboardKey.gameButtonA, + 305: PhysicalKeyboardKey.gameButtonB, + 306: PhysicalKeyboardKey.gameButtonC, + 307: PhysicalKeyboardKey.gameButtonX, + 308: PhysicalKeyboardKey.gameButtonY, + 309: PhysicalKeyboardKey.gameButtonZ, + 310: PhysicalKeyboardKey.gameButtonLeft1, + 311: PhysicalKeyboardKey.gameButtonRight1, + 312: PhysicalKeyboardKey.gameButtonLeft2, + 313: PhysicalKeyboardKey.gameButtonRight2, + 314: PhysicalKeyboardKey.gameButtonSelect, + 315: PhysicalKeyboardKey.gameButtonStart, + 316: PhysicalKeyboardKey.gameButtonMode, + 317: PhysicalKeyboardKey.gameButtonThumbLeft, + 318: PhysicalKeyboardKey.gameButtonThumbRight, + 353: PhysicalKeyboardKey.select, + 358: PhysicalKeyboardKey.info, + 370: PhysicalKeyboardKey.closedCaptionToggle, + 397: PhysicalKeyboardKey.launchCalendar, + 402: PhysicalKeyboardKey.channelUp, + 403: PhysicalKeyboardKey.channelDown, + 405: PhysicalKeyboardKey.mediaLast, + 411: PhysicalKeyboardKey.pause, + 429: PhysicalKeyboardKey.launchContacts, + 464: PhysicalKeyboardKey.fn, + 583: PhysicalKeyboardKey.launchAssistant, +}; + + +/// A map of Ohos key codes which have printable representations, but appear +/// on the number pad. Used to provide different key objects for keys like +/// KEY_EQUALS and NUMPAD_EQUALS. +const Map kOhosNumPadMap = { + 2103: LogicalKeyboardKey.numpad0, + 2104: LogicalKeyboardKey.numpad1, + 2105: LogicalKeyboardKey.numpad2, + 2106: LogicalKeyboardKey.numpad3, + 2107: LogicalKeyboardKey.numpad4, + 2108: LogicalKeyboardKey.numpad5, + 2109: LogicalKeyboardKey.numpad6, + 2110: LogicalKeyboardKey.numpad7, + 2111: LogicalKeyboardKey.numpad8, + 2112: LogicalKeyboardKey.numpad9, + 2113: LogicalKeyboardKey.numpadDivide, + 2114: LogicalKeyboardKey.numpadMultiply, + 2115: LogicalKeyboardKey.numpadSubtract, + 2116: LogicalKeyboardKey.numpadAdd, + 2117: LogicalKeyboardKey.numpadDecimal, + 2118: LogicalKeyboardKey.numpadComma, + 2119: LogicalKeyboardKey.numpadEnter, + 2120: LogicalKeyboardKey.numpadEqual, + 2121: LogicalKeyboardKey.numpadParenLeft, + 2122: LogicalKeyboardKey.numpadParenRight, +}; + /// Maps Fuchsia-specific IDs to the matching [LogicalKeyboardKey]. const Map kFuchsiaToLogicalKey = { 0x1200000010: LogicalKeyboardKey.hyper, diff --git a/packages/flutter/lib/src/services/platform_views.dart b/packages/flutter/lib/src/services/platform_views.dart index 5dbc2008f522d7806a26d2e2a326f46b7083aa97..d188041d3af7d9941cf06022e62ef4873e1705a4 100644 --- a/packages/flutter/lib/src/services/platform_views.dart +++ b/packages/flutter/lib/src/services/platform_views.dart @@ -146,6 +146,31 @@ class PlatformViewsService { return controller; } + static OhosViewController initOhosView({ + required int id, + required String viewType, + required TextDirection layoutDirection, + dynamic creationParams, + MessageCodec? creationParamsCodec, + VoidCallback? onFocus, + }) { + assert(id != null); + assert(viewType != null); + assert(layoutDirection != null); + assert(creationParams == null || creationParamsCodec != null); + + final TextureOhosViewController controller = TextureOhosViewController._( + viewId: id, + viewType: viewType, + layoutDirection: layoutDirection, + creationParams: creationParams, + creationParamsCodec: creationParamsCodec, + ); + + _instance._focusCallbacks[id] = onFocus ?? () {}; + return controller; + } + /// {@macro flutter.services.PlatformViewsService.initAndroidView} /// /// This attempts to use the newest and most efficient platform view @@ -173,6 +198,30 @@ class PlatformViewsService { return controller; } + static SurfaceOhosViewController initSurfaceOhosView({ + required int id, + required String viewType, + required TextDirection layoutDirection, + dynamic creationParams, + MessageCodec? creationParamsCodec, + VoidCallback? onFocus, + }) { + assert(id != null); + assert(viewType != null); + assert(layoutDirection != null); + assert(creationParams == null || creationParamsCodec != null); + + final SurfaceOhosViewController controller = SurfaceOhosViewController._( + viewId: id, + viewType: viewType, + layoutDirection: layoutDirection, + creationParams: creationParams, + creationParamsCodec: creationParamsCodec, + ); + _instance._focusCallbacks[id] = onFocus ?? () {}; + return controller; + } + /// {@macro flutter.services.PlatformViewsService.initAndroidView} /// /// When this factory is used, the Android view and Flutter widgets are @@ -201,6 +250,26 @@ class PlatformViewsService { return controller; } + static ExpensiveOhosViewController initExpensiveOhosView({ + required int id, + required String viewType, + required TextDirection layoutDirection, + dynamic creationParams, + MessageCodec? creationParamsCodec, + VoidCallback? onFocus, + }) { + final ExpensiveOhosViewController controller = ExpensiveOhosViewController._( + viewId: id, + viewType: viewType, + layoutDirection: layoutDirection, + creationParams: creationParams, + creationParamsCodec: creationParamsCodec, + ); + + _instance._focusCallbacks[id] = onFocus ?? () {}; + return controller; + } + /// Factory method to create a `UiKitView`. /// /// The `id` parameter is an unused unique identifier generated with @@ -334,6 +403,44 @@ class AndroidPointerProperties { } } +class OhosPointerProperties { + /// Creates an [OhosPointerProperties] object. + /// + /// All parameters must not be null. + const OhosPointerProperties({ + required this.id, + required this.toolType, + }) : assert(id != null), + assert(toolType != null); + + final int id; + + /// The type of tool used to make contact such as a finger or stylus, if known. + final int toolType; + + /// Value for `toolType` when the tool type is unknown. + static const int kToolTypeUnknown = 0; + + /// Value for `toolType` when the tool type is a finger. + static const int kToolTypeFinger = 1; + + /// Value for `toolType` when the tool type is a stylus. + static const int kToolTypeStylus = 2; + + /// Value for `toolType` when the tool type is a mouse. + static const int kToolTypeMouse = 3; + + /// Value for `toolType` when the tool type is an eraser. + static const int kToolTypeEraser = 4; + + List _asList() => [id, toolType]; + + @override + String toString() { + return '${objectRuntimeType(this, 'OhosPointerProperties')}(id: $id, toolType: $toolType)'; + } +} + /// Position information for an Android pointer. /// /// A Dart version of Android's [MotionEvent.PointerCoords](https://developer.android.com/reference/android/view/MotionEvent.PointerCoords). @@ -408,6 +515,73 @@ class AndroidPointerCoords { } } +class OhosPointerCoords { + /// Creates an OhosPointerCoords. + /// + /// All parameters must not be null. + const OhosPointerCoords({ + required this.orientation, + required this.pressure, + required this.size, + required this.toolMajor, + required this.toolMinor, + required this.touchMajor, + required this.touchMinor, + required this.x, + required this.y, + }) : assert(orientation != null), + assert(pressure != null), + assert(size != null), + assert(toolMajor != null), + assert(toolMinor != null), + assert(touchMajor != null), + assert(touchMinor != null), + assert(x != null), + assert(y != null); + + /// The orientation of the touch area and tool area in radians clockwise from vertical. + final double orientation; + + /// A normalized value that describes the pressure applied to the device by a finger or other tool. + final double pressure; + + /// A normalized value that describes the approximate size of the pointer touch area in relation to the maximum detectable size of the device. + final double size; + + final double toolMajor; + + final double toolMinor; + + final double touchMajor; + + final double touchMinor; + + /// The X component of the pointer movement. + final double x; + + /// The Y component of the pointer movement. + final double y; + + List _asList() { + return [ + orientation, + pressure, + size, + toolMajor, + toolMinor, + touchMajor, + touchMinor, + x, + y, + ]; + } + + @override + String toString() { + return '${objectRuntimeType(this, 'OhosPointerCoords')}(orientation: $orientation, pressure: $pressure, size: $size, toolMajor: $toolMajor, toolMinor: $toolMinor, touchMajor: $touchMajor, touchMinor: $touchMinor, x: $x, y: $y)'; + } +} + /// A Dart version of Android's [MotionEvent](https://developer.android.com/reference/android/view/MotionEvent). /// /// This is used by [AndroidViewController] to describe pointer events that are forwarded to a platform view @@ -532,6 +706,114 @@ class AndroidMotionEvent { } } +class OhosMotionEvent { + /// Creates an OhosMotionEvent. + /// + /// All parameters must not be null. + OhosMotionEvent({ + required this.downTime, + required this.eventTime, + required this.action, + required this.pointerCount, + required this.pointerProperties, + required this.pointerCoords, + required this.metaState, + required this.buttonState, + required this.xPrecision, + required this.yPrecision, + required this.deviceId, + required this.edgeFlags, + required this.source, + required this.flags, + required this.motionEventId, + }) : assert(downTime != null), + assert(eventTime != null), + assert(action != null), + assert(pointerCount != null), + assert(pointerProperties != null), + assert(pointerCoords != null), + assert(metaState != null), + assert(buttonState != null), + assert(xPrecision != null), + assert(yPrecision != null), + assert(deviceId != null), + assert(edgeFlags != null), + assert(source != null), + assert(flags != null), + assert(pointerProperties.length == pointerCount), + assert(pointerCoords.length == pointerCount); + + /// The time (in ms) when the user originally pressed down to start a stream of position events, + /// relative to an arbitrary timeline. + final int downTime; + + /// The time this event occurred, relative to an arbitrary timeline. + final int eventTime; + + /// A value representing the kind of action being performed. + final int action; + + /// The number of pointers that are part of this event. + /// This must be equivalent to the length of `pointerProperties` and `pointerCoords`. + final int pointerCount; + + /// List of [OhosPointerProperties] for each pointer that is part of this event. + final List pointerProperties; + + /// List of [OhosPointerCoords] for each pointer that is part of this event. + final List pointerCoords; + + /// The state of any meta / modifier keys that were in effect when the event was generated. + final int metaState; + + /// The state of all buttons that are pressed such as a mouse or stylus button. + final int buttonState; + + /// The precision of the X coordinates being reported, in physical pixels. + final double xPrecision; + + /// The precision of the Y coordinates being reported, in physical pixels. + final double yPrecision; + + final int deviceId; + + /// A bit field indicating which edges, if any, were touched by this MotionEvent. + final int edgeFlags; + + /// The source of this event (e.g a touchpad or stylus). + final int source; + + final int flags; + + final int motionEventId; + + List _asList(int viewId) { + return [ + viewId, + downTime, + eventTime, + action, + pointerCount, + pointerProperties.map>((OhosPointerProperties p) => p._asList()).toList(), + pointerCoords.map>((OhosPointerCoords p) => p._asList()).toList(), + metaState, + buttonState, + xPrecision, + yPrecision, + deviceId, + edgeFlags, + source, + flags, + motionEventId, + ]; + } + + @override + String toString() { + return 'OhosPointerEvent(downTime: $downTime, eventTime: $eventTime, action: $action, pointerCount: $pointerCount, pointerProperties: $pointerProperties, pointerCoords: $pointerCoords, metaState: $metaState, buttonState: $buttonState, xPrecision: $xPrecision, yPrecision: $yPrecision, deviceId: $deviceId, edgeFlags: $edgeFlags, source: $source, flags: $flags, motionEventId: $motionEventId)'; + } +} + enum _AndroidViewState { waitingForSize, creating, @@ -539,6 +821,13 @@ enum _AndroidViewState { disposed, } +enum _OhosViewState { + waitingForSize, + creating, + created, + disposed, +} + // Helper for converting PointerEvents into AndroidMotionEvents. class _AndroidMotionEventConverter { _AndroidMotionEventConverter(); @@ -1372,6 +1661,791 @@ class _HybridAndroidViewControllerInternals extends _AndroidViewControllerIntern } } +class _OhosMotionEventConverter { + _OhosMotionEventConverter(); + + final Map pointerPositions = + {}; + final Map pointerProperties = + {}; + final Set usedOhosPointerIds = {}; + + PointTransformer get pointTransformer => _pointTransformer; + late PointTransformer _pointTransformer; + set pointTransformer(PointTransformer transformer) { + assert(transformer != null); + _pointTransformer = transformer; + } + + int? downTimeMillis; + + void handlePointerDownEvent(PointerDownEvent event) { + if (pointerProperties.isEmpty) { + downTimeMillis = event.timeStamp.inMilliseconds; + } + int ohosPointerId = 0; + while (usedOhosPointerIds.contains(ohosPointerId)) { + ohosPointerId++; + } + usedOhosPointerIds.add(ohosPointerId); + pointerProperties[event.pointer] = propertiesFor(event, ohosPointerId); + } + + void updatePointerPositions(PointerEvent event) { + final Offset position = _pointTransformer(event.position); + pointerPositions[event.pointer] = OhosPointerCoords( + orientation: event.orientation, + pressure: event.pressure, + size: event.size, + toolMajor: event.radiusMajor, + toolMinor: event.radiusMinor, + touchMajor: event.radiusMajor, + touchMinor: event.radiusMinor, + x: position.dx, + y: position.dy, + ); + } + + void _remove(int pointer) { + pointerPositions.remove(pointer); + usedOhosPointerIds.remove(pointerProperties[pointer]!.id); + pointerProperties.remove(pointer); + if (pointerProperties.isEmpty) { + downTimeMillis = null; + } + } + + void handlePointerUpEvent(PointerUpEvent event) { + _remove(event.pointer); + } + + void handlePointerCancelEvent(PointerCancelEvent event) { + // The pointer cancel event is handled like pointer up. Normally, + // the difference is that pointer cancel doesn't perform any action, + // but in this case neither up or cancel perform any action. + _remove(event.pointer); + } + + OhosMotionEvent? toOhosMotionEvent(PointerEvent event) { + final List pointers = pointerPositions.keys.toList(); + final int pointerIdx = pointers.indexOf(event.pointer); + final int numPointers = pointers.length; + + // This value must match the value in engine's FlutterView.java. + // This flag indicates whether the original Ohos pointer events were batched together. + const int kPointerDataFlagBatched = 1; + + // Ohos MotionEvent objects can batch information on multiple pointers. + // Flutter breaks these such batched events into multiple PointerEvent objects. + // When there are multiple active pointers we accumulate the information for all pointers + // as we get PointerEvents, and only send it to the embedded Ohos view when + // we see the last pointer. This way we achieve the same batching as Ohos. + if (event.platformData == kPointerDataFlagBatched || + (isSinglePointerAction(event) && pointerIdx < numPointers - 1)) { + return null; + } + + final int action; + if (event is PointerDownEvent) { + action = numPointers == 1 + ? OhosViewController.kActionDown + : OhosViewController.pointerAction(pointerIdx, OhosViewController.kActionPointerDown); + } else if (event is PointerUpEvent) { + action = numPointers == 1 + ? OhosViewController.kActionUp + : OhosViewController.pointerAction(pointerIdx, OhosViewController.kActionPointerUp); + } else if (event is PointerMoveEvent) { + action = OhosViewController.kActionMove; + } else if (event is PointerCancelEvent) { + action = OhosViewController.kActionCancel; + } else { + return null; + } + + return OhosMotionEvent( + downTime: downTimeMillis!, + eventTime: event.timeStamp.inMilliseconds, + action: action, + pointerCount: pointerPositions.length, + pointerProperties: pointers + .map((int i) => pointerProperties[i]!) + .toList(), + pointerCoords: pointers + .map((int i) => pointerPositions[i]!) + .toList(), + metaState: 0, + buttonState: 0, + xPrecision: 1.0, + yPrecision: 1.0, + deviceId: 0, + edgeFlags: 0, + source: 0, + flags: 0, + motionEventId: event.embedderId, + ); + } + + OhosPointerProperties propertiesFor(PointerEvent event, int pointerId) { + int toolType = OhosPointerProperties.kToolTypeUnknown; + switch (event.kind) { + case PointerDeviceKind.touch: + case PointerDeviceKind.trackpad: + toolType = OhosPointerProperties.kToolTypeFinger; + break; + case PointerDeviceKind.mouse: + toolType = OhosPointerProperties.kToolTypeMouse; + break; + case PointerDeviceKind.stylus: + toolType = OhosPointerProperties.kToolTypeStylus; + break; + case PointerDeviceKind.invertedStylus: + toolType = OhosPointerProperties.kToolTypeEraser; + break; + case PointerDeviceKind.unknown: + toolType = OhosPointerProperties.kToolTypeUnknown; + break; + } + return OhosPointerProperties(id: pointerId, toolType: toolType); + } + + bool isSinglePointerAction(PointerEvent event) => + event is! PointerDownEvent && event is! PointerUpEvent; +} + +abstract class OhosViewController extends PlatformViewController { + OhosViewController._({ + required this.viewId, + required String viewType, + required TextDirection layoutDirection, + dynamic creationParams, + MessageCodec? creationParamsCodec, + }) : assert(viewId != null), + assert(viewType != null), + assert(layoutDirection != null), + assert(creationParams == null || creationParamsCodec != null), + _viewType = viewType, + _layoutDirection = layoutDirection, + _creationParams = creationParams == null ? null : _CreationParams(creationParams, creationParamsCodec!); + + /// Action code for when a primary pointer touched the screen. + /// + static const int kActionDown = 0; + + /// Action code for when a primary pointer stopped touching the screen. + /// + static const int kActionUp = 1; + + /// Action code for when the event only includes information about pointer movement. + /// + static const int kActionMove = 2; + + /// Action code for when a motion event has been canceled. + /// + static const int kActionCancel = 3; + + /// Action code for when a secondary pointer touched the screen. + /// + static const int kActionPointerDown = 5; + + /// Action code for when a secondary pointer stopped touching the screen. + /// + static const int kActionPointerUp = 6; + + static const int kOhosLayoutDirectionLtr = 0; + + static const int kOhosLayoutDirectionRtl = 1; + + /// The unique identifier of the Ohos view controlled by this controller. + @override + final int viewId; + + final String _viewType; + + // Helps convert PointerEvents to OhosMotionEvents. + final _OhosMotionEventConverter _motionEventConverter = + _OhosMotionEventConverter(); + + TextDirection _layoutDirection; + + _OhosViewState _state = _OhosViewState.waitingForSize; + + final _CreationParams? _creationParams; + + final List _platformViewCreatedCallbacks = + []; + + static int _getOhosDirection(TextDirection direction) { + assert(direction != null); + switch (direction) { + case TextDirection.ltr: + return kOhosLayoutDirectionLtr; + case TextDirection.rtl: + return kOhosLayoutDirectionRtl; + } + } + + /// Creates a masked Ohos MotionEvent action value for an indexed pointer. + static int pointerAction(int pointerId, int action) { + return ((pointerId << 8) & 0xff00) | (action & 0xff); + } + + /// Sends the message to dispose the platform view. + Future _sendDisposeMessage(); + + /// True if [_sendCreateMessage] can only be called with a non-null size. + bool get _createRequiresSize; + + /// Sends the message to create the platform view with an initial [size]. + /// + /// If [_createRequiresSize] is true, `size` is non-nullable, and the call + /// should instead be deferred until the size is available. + Future _sendCreateMessage({required covariant Size? size, Offset? position}); + + /// Sends the message to resize the platform view to [size]. + Future _sendResizeMessage(Size size); + + @override + bool get awaitingCreation => _state == _OhosViewState.waitingForSize; + + @override + Future create({Size? size, Offset? position}) async { + assert(_state != _OhosViewState.disposed, 'trying to create a disposed Ohos view'); + assert(_state == _OhosViewState.waitingForSize, 'Ohos view is already sized. View id: $viewId'); + + if (_createRequiresSize && size == null) { + // Wait for a setSize call. + return; + } + + _state = _OhosViewState.creating; + await _sendCreateMessage(size: size, position: position); + _state = _OhosViewState.created; + + for (final PlatformViewCreatedCallback callback in _platformViewCreatedCallbacks) { + callback(viewId); + } + } + + /// Sizes the Ohos View. + /// + /// [size] is the view's new size in logical pixel, it must not be null and must + /// be bigger than zero. + /// + /// The first time a size is set triggers the creation of the Ohos view. + /// + /// Returns the buffer size in logical pixel that backs the texture where the platform + /// view pixels are written to. + /// + /// The buffer size may or may not be the same as [size]. + /// + /// As a result, consumers are expected to clip the texture using [size], while using + /// the return value to size the texture. + Future setSize(Size size) async { + assert(_state != _OhosViewState.disposed, 'Ohos view is disposed. View id: $viewId'); + if (_state == _OhosViewState.waitingForSize) { + // Either `create` hasn't been called, or it couldn't run due to missing + // size information, so create the view now. + await create(size: size); + return size; + } else { + return _sendResizeMessage(size); + } + } + + /// Sets the offset of the platform view. + /// + /// [off] is the view's new offset in logical pixel. + /// + /// On Ohos, this allows the Ohos native view to draw the a11y highlights in the same + /// location on the screen as the platform view widget in the Flutter framework. + Future setOffset(Offset off); + + /// Returns the texture entry id that the Ohos view is rendering into. + /// + /// Returns null if the Ohos view has not been successfully created, if it has been + /// disposed, or if the implementation does not use textures. + int? get textureId; + + /// True if the view requires native view composition rather than using a + /// texture to render. + /// + /// This value may change during [create], but will not change after that + /// call's future has completed. + bool get requiresViewComposition => false; + + /// for description of the parameters. + /// + /// See [OhosViewController.dispatchPointerEvent] for sending a + /// [PointerEvent]. + Future sendMotionEvent(OhosMotionEvent event) async { + await SystemChannels.platform_views.invokeMethod( + 'touch', + event._asList(viewId), + ); + } + + /// Converts a given point from the global coordinate system in logical pixels + /// to the local coordinate system for this box. + /// + /// This is required to convert a [PointerEvent] to an [OhosMotionEvent]. + /// It is typically provided by using [RenderBox.globalToLocal]. + PointTransformer get pointTransformer => _motionEventConverter._pointTransformer; + set pointTransformer(PointTransformer transformer) { + assert(transformer != null); + _motionEventConverter._pointTransformer = transformer; + } + + /// Whether the platform view has already been created. + bool get isCreated => _state == _OhosViewState.created; + + /// Adds a callback that will get invoke after the platform view has been + /// created. + void addOnPlatformViewCreatedListener(PlatformViewCreatedCallback listener) { + assert(listener != null); + assert(_state != _OhosViewState.disposed); + _platformViewCreatedCallbacks.add(listener); + } + + /// Removes a callback added with [addOnPlatformViewCreatedListener]. + void removeOnPlatformViewCreatedListener(PlatformViewCreatedCallback listener) { + assert(listener != null); + assert(_state != _OhosViewState.disposed); + _platformViewCreatedCallbacks.remove(listener); + } + + /// The created callbacks that are invoked after the platform view has been + /// created. + @visibleForTesting + List get createdCallbacks => _platformViewCreatedCallbacks; + + /// Sets the layout direction for the Ohos view. + Future setLayoutDirection(TextDirection layoutDirection) async { + assert( + _state != _OhosViewState.disposed, + 'trying to set a layout direction for a disposed UIView. View id: $viewId', + ); + + if (layoutDirection == _layoutDirection) { + return; + } + + assert(layoutDirection != null); + _layoutDirection = layoutDirection; + + // If the view was not yet created we just update _layoutDirection and return, as the new + // direction will be used in _create. + if (_state == _OhosViewState.waitingForSize) { + return; + } + + await SystemChannels.platform_views + .invokeMethod('setDirection', { + 'id': viewId, + 'direction': _getOhosDirection(layoutDirection), + }); + } + + /// to the view. + /// + /// This method can only be used if a [PointTransformer] is provided to + /// [OhosViewController.pointTransformer]. Otherwise, an [AssertionError] + /// is thrown. See [OhosViewController.sendMotionEvent] for sending a + /// `MotionEvent` without a [PointTransformer]. + /// + + /// for description of the parameters. + @override + Future dispatchPointerEvent(PointerEvent event) async { + if (event is PointerHoverEvent) { + return; + } + + if (event is PointerDownEvent) { + _motionEventConverter.handlePointerDownEvent(event); + } + + _motionEventConverter.updatePointerPositions(event); + + final OhosMotionEvent? ohosEvent = + _motionEventConverter.toOhosMotionEvent(event); + + if (event is PointerUpEvent) { + _motionEventConverter.handlePointerUpEvent(event); + } else if (event is PointerCancelEvent) { + _motionEventConverter.handlePointerCancelEvent(event); + } + + if (ohosEvent != null) { + await sendMotionEvent(ohosEvent); + } + } + + /// Clears the focus from the Ohos View if it is focused. + @override + Future clearFocus() { + if (_state != _OhosViewState.created) { + return Future.value(); + } + return SystemChannels.platform_views.invokeMethod('clearFocus', viewId); + } + + /// Disposes the Ohos view. + /// + /// The [OhosViewController] object is unusable after calling this. + /// The identifier of the platform view cannot be reused after the view is + /// disposed. + @override + Future dispose() async { + if (_state == _OhosViewState.creating || _state == _OhosViewState.created) { + await _sendDisposeMessage(); + } + _platformViewCreatedCallbacks.clear(); + _state = _OhosViewState.disposed; + PlatformViewsService._instance._focusCallbacks.remove(viewId); + } +} + +class SurfaceOhosViewController extends OhosViewController { + SurfaceOhosViewController._({ + required super.viewId, + required super.viewType, + required super.layoutDirection, + super.creationParams, + super.creationParamsCodec, + }) : super._(); + + // By default, assume the implementation will be texture-based. + _OhosViewControllerInternals _internals = _TextureOhosViewControllerInternals(); + + @override + bool get _createRequiresSize => true; + + @override + Future _sendCreateMessage({required Size size, Offset? position}) async { + assert(!size.isEmpty, 'trying to create $TextureAndroidViewController without setting a valid size.'); + + final dynamic response = await _OhosViewControllerInternals.sendCreateMessage( + viewId: viewId, + viewType: _viewType, + hybrid: false, + hybridFallback: true, + layoutDirection: _layoutDirection, + creationParams: _creationParams, + size: size, + position: position, + ); + if (response is int) { + (_internals as _TextureOhosViewControllerInternals).textureId = response; + } else { + // A null response indicates fallback to Hybrid Composition, so swap out + // the implementation. + _internals = _HybridOhosViewControllerInternals(); + } + return true; + } + + @override + int? get textureId { + return _internals.textureId; + } + + @override + bool get requiresViewComposition { + return _internals.requiresViewComposition; + } + + @override + Future _sendDisposeMessage() { + return _internals.sendDisposeMessage(viewId: viewId); + } + + @override + Future _sendResizeMessage(Size size) { + return _internals.setSize(size, viewId: viewId, viewState: _state); + } + + @override + Future setOffset(Offset off) { + return _internals.setOffset(off, viewId: viewId, viewState: _state); + } +} + +class ExpensiveOhosViewController extends OhosViewController { + ExpensiveOhosViewController._({ + required super.viewId, + required super.viewType, + required super.layoutDirection, + super.creationParams, + super.creationParamsCodec, + }) : super._(); + + final _OhosViewControllerInternals _internals = _HybridOhosViewControllerInternals(); + + @override + bool get _createRequiresSize => false; + + @override + Future _sendCreateMessage({required Size? size, Offset? position}) async { + await _OhosViewControllerInternals.sendCreateMessage( + viewId: viewId, + viewType: _viewType, + hybrid: true, + layoutDirection: _layoutDirection, + creationParams: _creationParams, + position: position, + ); + } + + @override + int? get textureId { + return _internals.textureId; + } + + @override + bool get requiresViewComposition { + return _internals.requiresViewComposition; + } + + @override + Future _sendDisposeMessage() { + return _internals.sendDisposeMessage(viewId: viewId); + } + + @override + Future _sendResizeMessage(Size size) { + return _internals.setSize(size, viewId: viewId, viewState: _state); + } + + @override + Future setOffset(Offset off) { + return _internals.setOffset(off, viewId: viewId, viewState: _state); + } +} + +class TextureOhosViewController extends OhosViewController { + TextureOhosViewController._({ + required super.viewId, + required super.viewType, + required super.layoutDirection, + super.creationParams, + super.creationParamsCodec, + }) : super._(); + + final _TextureOhosViewControllerInternals _internals = _TextureOhosViewControllerInternals(); + + @override + bool get _createRequiresSize => true; + + @override + Future _sendCreateMessage({required Size size, Offset? position}) async { + assert(!size.isEmpty, 'trying to create $TextureOhosViewController without setting a valid size.'); + + _internals.textureId = await _OhosViewControllerInternals.sendCreateMessage( + viewId: viewId, + viewType: _viewType, + hybrid: false, + layoutDirection: _layoutDirection, + creationParams: _creationParams, + size: size, + position: position, + ) as int; + } + + @override + int? get textureId { + return _internals.textureId; + } + + @override + bool get requiresViewComposition { + return _internals.requiresViewComposition; + } + + @override + Future _sendDisposeMessage() { + return _internals.sendDisposeMessage(viewId: viewId); + } + + @override + Future _sendResizeMessage(Size size) { + return _internals.setSize(size, viewId: viewId, viewState: _state); + } + + @override + Future setOffset(Offset off) { + return _internals.setOffset(off, viewId: viewId, viewState: _state); + } +} + +abstract class _OhosViewControllerInternals { + // Sends a create message with the given parameters, and returns the result + // if any. + // + // This uses a dynamic return because depending on the mode that is selected + // on the native side, the return type is different. Callers should cast + // depending on the possible return types for their arguments. + static Future sendCreateMessage({ + required int viewId, + required String viewType, + required TextDirection layoutDirection, + required bool hybrid, + bool hybridFallback = false, + _CreationParams? creationParams, + Size? size, + Offset? position}) { + final Map args = { + 'id': viewId, + 'viewType': viewType, + 'direction': OhosViewController._getOhosDirection(layoutDirection), + if (hybrid == true) 'hybrid': hybrid, + if (size != null) 'width': size.width, + if (size != null) 'height': size.height, + if (hybridFallback == true) 'hybridFallback': hybridFallback, + if (position != null) 'left': position.dx, + if (position != null) 'top': position.dy, + }; + if (creationParams != null) { + final ByteData paramsByteData = creationParams.codec.encodeMessage(creationParams.data)!; + args['params'] = Uint8List.view( + paramsByteData.buffer, + 0, + paramsByteData.lengthInBytes, + ); + } + return SystemChannels.platform_views.invokeMethod('create', args); + } + + int? get textureId; + + bool get requiresViewComposition; + + Future setSize( + Size size, { + required int viewId, + required _OhosViewState viewState, + }); + + Future setOffset( + Offset offset, { + required int viewId, + required _OhosViewState viewState, + }); + + Future sendDisposeMessage({required int viewId}); +} + +class _TextureOhosViewControllerInternals extends _OhosViewControllerInternals { + _TextureOhosViewControllerInternals(); + + /// The current offset of the platform view. + Offset _offset = Offset.zero; + + @override + int? textureId; + + @override + bool get requiresViewComposition => false; + + @override + Future setSize( + Size size, { + required int viewId, + required _OhosViewState viewState, + }) async { + assert(viewState != _OhosViewState.waitingForSize, 'Ohos view must have an initial size. View id: $viewId'); + assert(!size.isEmpty); + + final Map? meta = await SystemChannels.platform_views.invokeMapMethod( + 'resize', + { + 'id': viewId, + 'width': size.width, + 'height': size.height, + }, + ); + assert(meta != null); + assert(meta!.containsKey('width')); + assert(meta!.containsKey('height')); + int width = meta!['width']! as int; + int height = meta!['height']! as int; + return Size(width.toDouble(), height.toDouble()); + } + + @override + Future setOffset( + Offset offset, { + required int viewId, + required _OhosViewState viewState, + }) async { + if (offset == _offset) { + return; + } + + // Don't set the offset unless the Android view has been created. + // The implementation of this method channel throws if the Android view for this viewId + // isn't addressable. + if (viewState != _OhosViewState.created) { + return; + } + + _offset = offset; + + await SystemChannels.platform_views.invokeMethod( + 'offset', + { + 'id': viewId, + 'top': offset.dy, + 'left': offset.dx, + }, + ); + } + + @override + Future sendDisposeMessage({required int viewId}) { + return SystemChannels + .platform_views.invokeMethod('dispose', { + 'id': viewId, + 'hybrid': false, + }); + } +} + +class _HybridOhosViewControllerInternals extends _OhosViewControllerInternals { + @override + int get textureId { + throw UnimplementedError('Not supported for hybrid composition.'); + } + + @override + bool get requiresViewComposition => true; + + @override + Future setSize( + Size size, { + required int viewId, + required _OhosViewState viewState, + }) { + throw UnimplementedError('Not supported for hybrid composition.'); + } + + @override + Future setOffset( + Offset offset, { + required int viewId, + required _OhosViewState viewState, + }) { + throw UnimplementedError('Not supported for hybrid composition.'); + } + + @override + Future sendDisposeMessage({required int viewId}) { + return SystemChannels.platform_views.invokeMethod('dispose', { + 'id': viewId, + 'hybrid': true, + }); + } +} + /// Base class for iOS and macOS view controllers. /// /// View controllers are used to create and interact with the UIView or NSView diff --git a/packages/flutter/lib/src/services/raw_keyboard.dart b/packages/flutter/lib/src/services/raw_keyboard.dart index 348fe3df419039c41992935ebc2465c824f5b23f..944e5799c609793cedd0785f5958c2abaae16fd8 100644 --- a/packages/flutter/lib/src/services/raw_keyboard.dart +++ b/packages/flutter/lib/src/services/raw_keyboard.dart @@ -13,6 +13,7 @@ import 'raw_keyboard_fuchsia.dart'; import 'raw_keyboard_ios.dart'; import 'raw_keyboard_linux.dart'; import 'raw_keyboard_macos.dart'; +import 'raw_keyboard_ohos.dart'; import 'raw_keyboard_web.dart'; import 'raw_keyboard_windows.dart'; import 'system_channels.dart'; @@ -404,6 +405,17 @@ abstract class RawKeyEvent with Diagnosticable { } else { final String keymap = message['keymap']! as String; switch (keymap) { + case 'ohos': + data = RawKeyEventDataOhos( + message['type'] as String? ?? KeyType.keydown.toString(), + message['keyCode'] as int? ?? 0, + message['deviceId'] as int? ?? 0, + message['character'] as String? ?? '', + ); + if (message.containsKey('character')) { + character = message['character'] as String?; + } + break; case 'android': data = RawKeyEventDataAndroid( flags: message['flags'] as int? ?? 0, diff --git a/packages/flutter/lib/src/services/raw_keyboard_ohos.dart b/packages/flutter/lib/src/services/raw_keyboard_ohos.dart new file mode 100644 index 0000000000000000000000000000000000000000..b54201d0a626235b6285b1b578c0bbb50932dd18 --- /dev/null +++ b/packages/flutter/lib/src/services/raw_keyboard_ohos.dart @@ -0,0 +1,161 @@ +/* +* Copyright (c) 2024 Hunan OpenValley Digital Industry Development 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 'keyboard_maps.g.dart'; +import 'raw_keyboard.dart'; + +/// 左Alt +const int KEYCODE_ALT_LEFT = 2045; + +/// 右Alt +const int KEYCODE_ALT_RIGHT = 2046; + +/// 左shift +const int KEYCODE_SHIFT_LEFT = 2047; + +/// 右shift +const int KEYCODE_SHIFT_RIGHT = 2048; + +/// 左ctrl +const int KEYCODE_CTRL_LEFT = 2072; + +/// 右ctrl +const int KEYCODE_CTRL_RIGHT = 2073; + +/// 功能键 +const int KEYCODE_FUNCTION = 2078; + +/// 滚动键锁定 +const int KEYCODE_SCROLL_LOCK = 2075; + +/// 大小写锁定 +const int KEYCODE_CAPS_LOCK = 2074; + +/// 小键盘锁 +const int KEYCODE_NUM_LOCK = 2102; + +/// mate left +const int KEYCODE_MATE_LEFT = 2076; + +/// mate right +const int KEYCODE_MATE_RIGHT = 2077; + +/// 按键类型 +enum KeyType { + /// 按键松开 + keyup, + + /// 按键按下 + keydown +} + +/// RawKeyEventData for OpenHarmony platform +class RawKeyEventDataOhos extends RawKeyEventData { + /// Constructor + const RawKeyEventDataOhos( + this._type, this._keyCode, this._deviceId, this._character); + + //按键类型,keyup/keydown + final String _type; + + // 按键编号 + final int _keyCode; + + // 设备id + final int _deviceId; + + // 按键键值 + final String _character; + + bool get _isKeyDown => _type == KeyType.keydown.toString(); + + @override + KeyboardSide? getModifierSide(ModifierKey key) { + KeyboardSide? findSide(int leftMask, int rightMask) { + if (_keyCode == leftMask) { + return KeyboardSide.left; + } else if (_keyCode == rightMask) { + return KeyboardSide.right; + } + return KeyboardSide.all; + } + + switch (key) { + case ModifierKey.controlModifier: + return findSide(KEYCODE_CTRL_LEFT, KEYCODE_CTRL_RIGHT); + case ModifierKey.shiftModifier: + return findSide(KEYCODE_SHIFT_LEFT, KEYCODE_SHIFT_RIGHT); + case ModifierKey.altModifier: + return findSide(KEYCODE_ALT_LEFT, KEYCODE_ALT_RIGHT); + case ModifierKey.metaModifier: + return findSide(KEYCODE_MATE_LEFT, KEYCODE_MATE_RIGHT); + case ModifierKey.capsLockModifier: + return (_keyCode == KEYCODE_CAPS_LOCK) ? KeyboardSide.all : null; + case ModifierKey.numLockModifier: + case ModifierKey.scrollLockModifier: + case ModifierKey.functionModifier: + case ModifierKey.symbolModifier: + return KeyboardSide.all; + } + } + + @override + bool isModifierPressed(ModifierKey key, + {KeyboardSide side = KeyboardSide.any}) { + if (!_isKeyDown) { + return false; + } + switch (key) { + case ModifierKey.controlModifier: + return _keyCode == KEYCODE_CTRL_LEFT || _keyCode == KEYCODE_CTRL_RIGHT; + case ModifierKey.shiftModifier: + return _keyCode == KEYCODE_SHIFT_LEFT || + _keyCode == KEYCODE_SHIFT_RIGHT; + case ModifierKey.altModifier: + return _keyCode == KEYCODE_ALT_LEFT || _keyCode == KEYCODE_ALT_RIGHT; + case ModifierKey.metaModifier: + return _keyCode == KEYCODE_MATE_LEFT || _keyCode == KEYCODE_MATE_RIGHT; + case ModifierKey.capsLockModifier: + return _keyCode == KEYCODE_CAPS_LOCK; + case ModifierKey.numLockModifier: + return _keyCode == KEYCODE_NUM_LOCK; + case ModifierKey.scrollLockModifier: + return _keyCode == KEYCODE_SCROLL_LOCK; + case ModifierKey.functionModifier: + return _keyCode == KEYCODE_FUNCTION; + case ModifierKey.symbolModifier: + return false; + } + } + + @override + String get keyLabel => _character; + + @override + LogicalKeyboardKey get logicalKey { + if (kOhosToLogicalKey.containsKey(_keyCode)) { + return kOhosToLogicalKey[_keyCode]!; + } + return LogicalKeyboardKey(_keyCode | LogicalKeyboardKey.ohosPlane); + } + + @override + PhysicalKeyboardKey get physicalKey { + if (kOhosToPhysicalKey.containsKey(_keyCode)) { + return kOhosToPhysicalKey[_keyCode]!; + } + return PhysicalKeyboardKey(_keyCode + LogicalKeyboardKey.ohosPlane); + } +} diff --git a/packages/flutter/lib/src/services/system_navigator.dart b/packages/flutter/lib/src/services/system_navigator.dart index 1ea16f921ac91bc37f178c8c38c3cf51b9225387..726920a3851cd8cb372aaf2e282a95752414655a 100644 --- a/packages/flutter/lib/src/services/system_navigator.dart +++ b/packages/flutter/lib/src/services/system_navigator.dart @@ -31,6 +31,7 @@ abstract final class SystemNavigator { case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: return; case TargetPlatform.android: return SystemChannels.platform.invokeMethod( diff --git a/packages/flutter/lib/src/services/text_formatter.dart b/packages/flutter/lib/src/services/text_formatter.dart index daaeef1f370d1427499ae8bd759b9409a6197e20..c1b578548f15dd9d9066c933c315936e1118cd69 100644 --- a/packages/flutter/lib/src/services/text_formatter.dart +++ b/packages/flutter/lib/src/services/text_formatter.dart @@ -534,6 +534,7 @@ class LengthLimitingTextInputFormatter extends TextInputFormatter { case TargetPlatform.macOS: case TargetPlatform.linux: case TargetPlatform.fuchsia: + case TargetPlatform.ohos: return MaxLengthEnforcement.truncateAfterCompositionEnds; } } diff --git a/packages/flutter/lib/src/widgets/app.dart b/packages/flutter/lib/src/widgets/app.dart index bea2378a29298a7293b280690af5ff99d764a277..2c747fd75b299780b21964ba7586b7eb8b1ac952 100644 --- a/packages/flutter/lib/src/widgets/app.dart +++ b/packages/flutter/lib/src/widgets/app.dart @@ -1326,6 +1326,7 @@ class WidgetsApp extends StatefulWidget { switch (defaultTargetPlatform) { case TargetPlatform.android: + case TargetPlatform.ohos: case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: diff --git a/packages/flutter/lib/src/widgets/default_text_editing_shortcuts.dart b/packages/flutter/lib/src/widgets/default_text_editing_shortcuts.dart index 46ec7c22594ae9112d965e64d0a4f65d7ab625da..3ffc0d2c83aeff318ffdf7289d0be33d46d8f0ae 100644 --- a/packages/flutter/lib/src/widgets/default_text_editing_shortcuts.dart +++ b/packages/flutter/lib/src/widgets/default_text_editing_shortcuts.dart @@ -253,6 +253,8 @@ class DefaultTextEditingShortcuts extends StatelessWidget { static final Map _fuchsiaShortcuts = _androidShortcuts; + static final Map _ohosShortcuts = _androidShortcuts; + static final Map _linuxNumpadShortcuts = { // When numLock is on, numpad keys shortcuts require shift to be pressed too. const SingleActivator(LogicalKeyboardKey.numpad6, shift: true, numLock: LockState.locked): const ExtendSelectionByCharacterIntent(forward: true, collapseSelection: false), @@ -524,6 +526,7 @@ class DefaultTextEditingShortcuts extends StatelessWidget { TargetPlatform.linux => _linuxShortcuts, TargetPlatform.macOS => _macShortcuts, TargetPlatform.windows => _windowsShortcuts, + TargetPlatform.ohos => _ohosShortcuts, }; } @@ -536,6 +539,7 @@ class DefaultTextEditingShortcuts extends StatelessWidget { case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: return null; case TargetPlatform.iOS: return _iOSDisablingTextShortcuts; diff --git a/packages/flutter/lib/src/widgets/editable_text.dart b/packages/flutter/lib/src/widgets/editable_text.dart index f139488a68bda82c10bb4f89723b99a69c6f85cd..6d2e749c97e76234b1efbbfdae917c89075d2836 100644 --- a/packages/flutter/lib/src/widgets/editable_text.dart +++ b/packages/flutter/lib/src/widgets/editable_text.dart @@ -2085,6 +2085,7 @@ class EditableText extends StatefulWidget { case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: break; } } @@ -2373,6 +2374,7 @@ class EditableTextState extends State with AutomaticKeepAliveClien case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: return textEditingValue.text.isNotEmpty && !(textEditingValue.selection.start == 0 && textEditingValue.selection.end == textEditingValue.text.length); @@ -2412,6 +2414,7 @@ class EditableTextState extends State with AutomaticKeepAliveClien case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: return false; } } @@ -2463,6 +2466,7 @@ class EditableTextState extends State with AutomaticKeepAliveClien case TargetPlatform.linux: case TargetPlatform.windows: break; + case TargetPlatform.ohos: case TargetPlatform.android: case TargetPlatform.fuchsia: // Collapse the selection and hide the toolbar and handles. @@ -2570,6 +2574,7 @@ class EditableTextState extends State with AutomaticKeepAliveClien case TargetPlatform.android: case TargetPlatform.iOS: case TargetPlatform.fuchsia: + case TargetPlatform.ohos: break; case TargetPlatform.macOS: case TargetPlatform.linux: @@ -2581,6 +2586,7 @@ class EditableTextState extends State with AutomaticKeepAliveClien case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: bringIntoView(textEditingValue.selection.extent); case TargetPlatform.macOS: case TargetPlatform.iOS: @@ -2990,7 +2996,7 @@ class EditableTextState extends State with AutomaticKeepAliveClien } } - if (defaultTargetPlatform != TargetPlatform.iOS && defaultTargetPlatform != TargetPlatform.android) { + if (defaultTargetPlatform != TargetPlatform.iOS && defaultTargetPlatform != TargetPlatform.android ) { return; } @@ -3746,7 +3752,8 @@ class EditableTextState extends State with AutomaticKeepAliveClien TargetPlatform.fuchsia || TargetPlatform.linux || TargetPlatform.macOS || - TargetPlatform.windows => false, + TargetPlatform.windows || + TargetPlatform.ohos => false, }; bool _isInternalScrollableNotification(BuildContext? notificationContext) { @@ -4212,6 +4219,7 @@ class EditableTextState extends State with AutomaticKeepAliveClien case TargetPlatform.windows: case TargetPlatform.fuchsia: case TargetPlatform.android: + case TargetPlatform.ohos: if (cause == SelectionChangedCause.drag) { if (oldSelection.baseOffset != newSelection.baseOffset) { bringIntoView(newSelection.base); @@ -5087,6 +5095,7 @@ class EditableTextState extends State with AutomaticKeepAliveClien case TargetPlatform.android: case TargetPlatform.iOS: case TargetPlatform.fuchsia: + case TargetPlatform.ohos: // On mobile platforms, we don't unfocus on touch events unless they're // in the web browser, but we do unfocus for all other kinds of events. switch (event.kind) { @@ -5094,6 +5103,7 @@ class EditableTextState extends State with AutomaticKeepAliveClien if (kIsWeb) { widget.focusNode.unfocus(); } + break; case ui.PointerDeviceKind.mouse: case ui.PointerDeviceKind.stylus: case ui.PointerDeviceKind.invertedStylus: @@ -5194,6 +5204,7 @@ class EditableTextState extends State with AutomaticKeepAliveClien return false; } case TargetPlatform.android: + case TargetPlatform.ohos: // Gboard on Android puts non-CJK words in composing regions. Coalesce // composing text in order to allow the saving of partial words in that // case. @@ -5323,7 +5334,7 @@ class EditableTextState extends State with AutomaticKeepAliveClien // Newer versions of iOS (iOS 15+) no longer reveal the most recently // entered character. const Set mobilePlatforms = { - TargetPlatform.android, TargetPlatform.fuchsia, + TargetPlatform.android, TargetPlatform.fuchsia, TargetPlatform.ohos }; final bool brieflyShowPassword = WidgetsBinding.instance.platformDispatcher.brieflyShowPassword && mobilePlatforms.contains(defaultTargetPlatform); @@ -6023,6 +6034,277 @@ class _CopySelectionAction extends ContextAction { bool get isActionEnabled => state._value.selection.isValid && !state._value.selection.isCollapsed; } +/// A void function that takes a [TextEditingValue]. +@visibleForTesting +typedef TextEditingValueCallback = void Function(TextEditingValue value); + +/// Provides undo/redo capabilities for text editing. +/// +/// Listens to [controller] as a [ValueNotifier] and saves relevant values for +/// undoing/redoing. The cadence at which values are saved is a best +/// approximation of the native behaviors of a hardware keyboard on Flutter's +/// desktop platforms, as there are subtle differences between each of these +/// platforms. +/// +/// Listens to keyboard undo/redo shortcuts and calls [onTriggered] when a +/// shortcut is triggered that would affect the state of the [controller]. +class _TextEditingHistory extends StatefulWidget { + /// Creates an instance of [_TextEditingHistory]. + const _TextEditingHistory({ + required this.child, + required this.controller, + required this.onTriggered, + }); + + /// The child widget of [_TextEditingHistory]. + final Widget child; + + /// The [TextEditingController] to save the state of over time. + final TextEditingController controller; + + /// Called when an undo or redo causes a state change. + /// + /// If the state would still be the same before and after the undo/redo, this + /// will not be called. For example, receiving a redo when there is nothing + /// to redo will not call this method. + /// + /// It is also not called when the controller is changed for reasons other + /// than undo/redo. + final TextEditingValueCallback onTriggered; + + @override + State<_TextEditingHistory> createState() => _TextEditingHistoryState(); +} + +class _TextEditingHistoryState extends State<_TextEditingHistory> { + final _UndoStack _stack = _UndoStack(); + late final _Throttled _throttledPush; + Timer? _throttleTimer; + + // This duration was chosen as a best fit for the behavior of Mac, Linux, + // and Windows undo/redo state save durations, but it is not perfect for any + // of them. + static const Duration _kThrottleDuration = Duration(milliseconds: 500); + + void _undo(UndoTextIntent intent) { + _update(_stack.undo()); + } + + void _redo(RedoTextIntent intent) { + _update(_stack.redo()); + } + + void _update(TextEditingValue? nextValue) { + if (nextValue == null) { + return; + } + if (nextValue.text == widget.controller.text) { + return; + } + widget.onTriggered(widget.controller.value.copyWith( + text: nextValue.text, + selection: nextValue.selection, + )); + } + + void _push() { + if (widget.controller.value == TextEditingValue.empty) { + return; + } + + switch (defaultTargetPlatform) { + case TargetPlatform.iOS: + case TargetPlatform.macOS: + case TargetPlatform.fuchsia: + case TargetPlatform.linux: + case TargetPlatform.windows: + // Composing text is not counted in history coalescing. + if (!widget.controller.value.composing.isCollapsed) { + return; + } + break; + case TargetPlatform.android: + case TargetPlatform.ohos: + // Gboard on Android puts non-CJK words in composing regions. Coalesce + // composing text in order to allow the saving of partial words in that + // case. + break; + } + + _throttleTimer = _throttledPush(widget.controller.value); + } + + @override + void initState() { + super.initState(); + _throttledPush = _throttle( + duration: _kThrottleDuration, + function: _stack.push, + ); + _push(); + widget.controller.addListener(_push); + } + + @override + void didUpdateWidget(_TextEditingHistory oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.controller != oldWidget.controller) { + _stack.clear(); + oldWidget.controller.removeListener(_push); + widget.controller.addListener(_push); + } + } + + @override + void dispose() { + widget.controller.removeListener(_push); + _throttleTimer?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Actions( + actions: > { + UndoTextIntent: Action.overridable(context: context, defaultAction: CallbackAction(onInvoke: _undo)), + RedoTextIntent: Action.overridable(context: context, defaultAction: CallbackAction(onInvoke: _redo)), + }, + child: widget.child, + ); + } +} + +/// A data structure representing a chronological list of states that can be +/// undone and redone. +class _UndoStack { + /// Creates an instance of [_UndoStack]. + _UndoStack(); + + final List _list = []; + + // The index of the current value, or null if the list is emtpy. + late int _index; + + /// Returns the current value of the stack. + T? get currentValue => _list.isEmpty ? null : _list[_index]; + + /// Add a new state change to the stack. + /// + /// Pushing identical objects will not create multiple entries. + void push(T value) { + if (_list.isEmpty) { + _index = 0; + _list.add(value); + return; + } + + assert(_index < _list.length && _index >= 0); + + if (value == currentValue) { + return; + } + + // If anything has been undone in this stack, remove those irrelevant states + // before adding the new one. + if (_index != null && _index != _list.length - 1) { + _list.removeRange(_index + 1, _list.length); + } + _list.add(value); + _index = _list.length - 1; + } + + /// Returns the current value after an undo operation. + /// + /// An undo operation moves the current value to the previously pushed value, + /// if any. + /// + /// Iff the stack is completely empty, then returns null. + T? undo() { + if (_list.isEmpty) { + return null; + } + + assert(_index < _list.length && _index >= 0); + + if (_index != 0) { + _index = _index - 1; + } + + return currentValue; + } + + /// Returns the current value after a redo operation. + /// + /// A redo operation moves the current value to the value that was last + /// undone, if any. + /// + /// Iff the stack is completely empty, then returns null. + T? redo() { + if (_list.isEmpty) { + return null; + } + + assert(_index < _list.length && _index >= 0); + + if (_index < _list.length - 1) { + _index = _index + 1; + } + + return currentValue; + } + + /// Remove everything from the stack. + void clear() { + _list.clear(); + _index = -1; + } + + @override + String toString() { + return '_UndoStack $_list'; + } +} + +/// A function that can be throttled with the throttle function. +typedef _Throttleable = void Function(T currentArg); + +/// A function that has been throttled by [_throttle]. +typedef _Throttled = Timer Function(T currentArg); + +/// Returns a _Throttled that will call through to the given function only a +/// maximum of once per duration. +/// +/// Only works for functions that take exactly one argument and return void. +_Throttled _throttle({ + required Duration duration, + required _Throttleable function, + // If true, calls at the start of the timer. + bool leadingEdge = false, +}) { + Timer? timer; + bool calledDuringTimer = false; + late T arg; + + return (T currentArg) { + arg = currentArg; + if (timer != null) { + calledDuringTimer = true; + return timer!; + } + if (leadingEdge) { + function(arg); + } + calledDuringTimer = false; + timer = Timer(duration, () { + if (!leadingEdge || calledDuringTimer) { + function(arg); + } + timer = null; + }); + return timer!; + }; +} + /// The start and end glyph heights of some range of text. @immutable class _GlyphHeights { diff --git a/packages/flutter/lib/src/widgets/focus_manager.dart b/packages/flutter/lib/src/widgets/focus_manager.dart index 19e6294a5aeb1df907cb91d40a6cea5776cdf905..a02479a5ea55159e4fcea8c4437a00d41514045e 100644 --- a/packages/flutter/lib/src/widgets/focus_manager.dart +++ b/packages/flutter/lib/src/widgets/focus_manager.dart @@ -1521,13 +1521,15 @@ class FocusManager with DiagnosticableTreeMixin, ChangeNotifier { if (kFlutterMemoryAllocationsEnabled) { ChangeNotifier.maybeDispatchObjectCreation(this); } - if (kIsWeb || defaultTargetPlatform != TargetPlatform.android) { + if (kIsWeb || (defaultTargetPlatform != TargetPlatform.android && defaultTargetPlatform != TargetPlatform.ohos)) { // It appears that some Android keyboard implementations can cause // app lifecycle state changes: adding this listener would cause the // text field to unfocus as the user is trying to type. // // Until this is resolved, we won't be adding the listener to Android apps. // https://github.com/flutter/flutter/pull/142930#issuecomment-1981750069 + // On the OHOS platform, any operations on the keyboard are ineffective when the app loses focus, + // making it unsuitable to listen to the application lifecycle. _appLifecycleListener = _AppLifecycleListener(_appLifecycleChange); WidgetsBinding.instance.addObserver(_appLifecycleListener!); } @@ -2166,6 +2168,7 @@ class _HighlightModeManager { case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.iOS: + case TargetPlatform.ohos: if (WidgetsBinding.instance.mouseTracker.mouseIsConnected) { return FocusHighlightMode.traditional; } diff --git a/packages/flutter/lib/src/widgets/modal_barrier.dart b/packages/flutter/lib/src/widgets/modal_barrier.dart index f2a247a7d3b582b7104fe7a8744ab19034131a2e..7c7f215057457e5b09dccdc8f9d8908fd6a73976 100644 --- a/packages/flutter/lib/src/widgets/modal_barrier.dart +++ b/packages/flutter/lib/src/widgets/modal_barrier.dart @@ -211,6 +211,7 @@ class ModalBarrier extends StatelessWidget { case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: platformSupportsDismissingBarrier = false; case TargetPlatform.android: case TargetPlatform.iOS: diff --git a/packages/flutter/lib/src/widgets/platform_menu_bar.dart b/packages/flutter/lib/src/widgets/platform_menu_bar.dart index 687894d37805c3c5c7fa26260d6fa96eaa7bfc4c..91de8350fee4f289fe183a1562788aee23740bd3 100644 --- a/packages/flutter/lib/src/widgets/platform_menu_bar.dart +++ b/packages/flutter/lib/src/widgets/platform_menu_bar.dart @@ -862,6 +862,7 @@ class PlatformProvidedMenuItem extends PlatformMenuItem { case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: return false; case TargetPlatform.macOS: return const { diff --git a/packages/flutter/lib/src/widgets/platform_view.dart b/packages/flutter/lib/src/widgets/platform_view.dart index ae24e58757fec8b7aa13ca0ea842514b9d58a1f9..44ffdd48bd3a76360c8150ee9e4f763dbca4d22e 100644 --- a/packages/flutter/lib/src/widgets/platform_view.dart +++ b/packages/flutter/lib/src/widgets/platform_view.dart @@ -197,6 +197,143 @@ class AndroidView extends StatefulWidget { State createState() => _AndroidViewState(); } +class OhosView extends StatefulWidget { + /// Creates a widget that embeds an Ohos view. + /// + /// {@template flutter.widgets.OhosView.constructorArgs} + /// The `viewType` and `hitTestBehavior` parameters must not be null. + /// If `creationParams` is not null then `creationParamsCodec` must not be null. + /// {@endtemplate} + const OhosView({ + super.key, + required this.viewType, + this.onPlatformViewCreated, + this.hitTestBehavior = PlatformViewHitTestBehavior.opaque, + this.layoutDirection, + this.gestureRecognizers, + this.creationParams, + this.creationParamsCodec, + this.clipBehavior = Clip.hardEdge, + }) : assert(viewType != null), + assert(hitTestBehavior != null), + assert(creationParams == null || creationParamsCodec != null), + assert(clipBehavior != null); + + /// The unique identifier for Ohos view type to be embedded by this widget. + /// + /// A [PlatformViewFactory](/javadoc/io/flutter/plugin/platform/PlatformViewFactory.html) + /// for this type must have been registered. + /// + /// See also: + /// + /// * [OhosView] for an example of registering a platform view factory. + final String viewType; + + /// {@template flutter.widgets.OhosView.onPlatformViewCreated} + /// Callback to invoke after the platform view has been created. + /// + /// May be null. + /// {@endtemplate} + final PlatformViewCreatedCallback? onPlatformViewCreated; + + /// {@template flutter.widgets.OhosView.hitTestBehavior} + /// How this widget should behave during hit testing. + /// + /// This defaults to [PlatformViewHitTestBehavior.opaque]. + /// {@endtemplate} + final PlatformViewHitTestBehavior hitTestBehavior; + + /// {@template flutter.widgets.OhosView.layoutDirection} + /// The text direction to use for the embedded view. + /// + /// If this is null, the ambient [Directionality] is used instead. + /// {@endtemplate} + final TextDirection? layoutDirection; + + /// Which gestures should be forwarded to the Ohos view. + /// + /// {@template flutter.widgets.OhosView.gestureRecognizers.descHead} + /// The gesture recognizers built by factories in this set participate in the gesture arena for + /// each pointer that was put down on the widget. If any of these recognizers win the + /// gesture arena, the entire pointer event sequence starting from the pointer down event + /// will be dispatched to the platform view. + /// + /// When null, an empty set of gesture recognizer factories is used, in which case a pointer event sequence + /// will only be dispatched to the platform view if no other member of the arena claimed it. + /// {@endtemplate} + /// + /// For example, with the following setup vertical drags will not be dispatched to the Ohos + /// view as the vertical drag gesture is claimed by the parent [GestureDetector]. + /// + /// ```dart + /// GestureDetector( + /// onVerticalDragStart: (DragStartDetails d) {}, + /// child: const OhosView( + /// viewType: 'webview', + /// ), + /// ) + /// ``` + /// + /// To get the [OhosView] to claim the vertical drag gestures we can pass a vertical drag + /// gesture recognizer factory in [gestureRecognizers] e.g: + /// + /// ```dart + /// GestureDetector( + /// onVerticalDragStart: (DragStartDetails details) {}, + /// child: SizedBox( + /// width: 200.0, + /// height: 100.0, + /// child: OhosView( + /// viewType: 'webview', + /// gestureRecognizers: >{ + /// Factory( + /// () => EagerGestureRecognizer(), + /// ), + /// }, + /// ), + /// ), + /// ) + /// ``` + /// + /// {@template flutter.widgets.OhosView.gestureRecognizers.descFoot} + /// A platform view can be configured to consume all pointers that were put + /// down in its bounds by passing a factory for an [EagerGestureRecognizer] in + /// [gestureRecognizers]. [EagerGestureRecognizer] is a special gesture + /// recognizer that immediately claims the gesture after a pointer down event. + /// + /// The [gestureRecognizers] property must not contain more than one factory + /// with the same [Factory.type]. + /// + /// Changing [gestureRecognizers] results in rejection of any active gesture + /// arenas (if the platform view is actively participating in an arena). + /// {@endtemplate} + // We use OneSequenceGestureRecognizers as they support gesture arena teams. + // TODO(amirh): get a list of GestureRecognizers here. + // https://github.com/flutter/flutter/issues/20953 + final Set>? gestureRecognizers; + + /// Passed as the args argument of [PlatformViewFactory#create](/javadoc/io/flutter/plugin/platform/PlatformViewFactory.html#create-ohos.content.Context-int-java.lang.Object-) + /// + /// This can be used by plugins to pass constructor parameters to the embedded Ohos view. + final dynamic creationParams; + + /// The codec used to encode `creationParams` before sending it to the + /// platform side. It should match the codec passed to the constructor of [PlatformViewFactory](/javadoc/io/flutter/plugin/platform/PlatformViewFactory.html#PlatformViewFactory-io.flutter.plugin.common.MessageCodec-). + /// + /// This is typically one of: [StandardMessageCodec], [JSONMessageCodec], [StringCodec], or [BinaryCodec]. + /// + /// This must not be null if [creationParams] is not null. + final MessageCodec? creationParamsCodec; + + /// {@macro flutter.material.Material.clipBehavior} + /// + /// Defaults to [Clip.hardEdge], and must not be null. + final Clip clipBehavior; + + @override + State createState() => _OhosViewState(); +} + /// Common superclass for iOS and macOS platform views. /// /// Platform views are used to embed native views in the widget hierarchy, with @@ -828,6 +965,138 @@ class _AndroidViewState extends State { } } +class _OhosViewState extends State { + int? _id; + late OhosViewController _controller; + TextDirection? _layoutDirection; + bool _initialized = false; + FocusNode? _focusNode; + + static final Set> _emptyRecognizersSet = + >{}; + + @override + Widget build(BuildContext context) { + return Focus( + focusNode: _focusNode, + onFocusChange: _onFocusChange, + child: _OhosPlatformView( + controller: _controller, + hitTestBehavior: widget.hitTestBehavior, + gestureRecognizers: widget.gestureRecognizers ?? _emptyRecognizersSet, + clipBehavior: widget.clipBehavior, + ), + ); + } + + void _initializeOnce() { + if (_initialized) { + return; + } + _initialized = true; + _createNewOhosView(); + _focusNode = FocusNode(debugLabel: 'OhosView(id: $_id)'); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + final TextDirection newLayoutDirection = _findLayoutDirection(); + final bool didChangeLayoutDirection = _layoutDirection != newLayoutDirection; + _layoutDirection = newLayoutDirection; + + _initializeOnce(); + if (didChangeLayoutDirection) { + // The native view will update asynchronously, in the meantime we don't want + // to block the framework. (so this is intentionally not awaiting). + _controller.setLayoutDirection(_layoutDirection!); + } + } + + @override + void didUpdateWidget(OhosView oldWidget) { + super.didUpdateWidget(oldWidget); + + final TextDirection newLayoutDirection = _findLayoutDirection(); + final bool didChangeLayoutDirection = _layoutDirection != newLayoutDirection; + _layoutDirection = newLayoutDirection; + + if (widget.viewType != oldWidget.viewType) { + _controller.dispose(); + _createNewOhosView(); + return; + } + + if (didChangeLayoutDirection) { + _controller.setLayoutDirection(_layoutDirection!); + } + } + + TextDirection _findLayoutDirection() { + assert(widget.layoutDirection != null || debugCheckHasDirectionality(context)); + return widget.layoutDirection ?? Directionality.of(context); + } + + @override + void dispose() { + _controller.dispose(); + _focusNode?.dispose(); + _focusNode = null; + super.dispose(); + } + + void _createNewOhosView() { + _id = platformViewsRegistry.getNextPlatformViewId(); + _controller = PlatformViewsService.initOhosView( + id: _id!, + viewType: widget.viewType, + layoutDirection: _layoutDirection!, + creationParams: widget.creationParams, + creationParamsCodec: widget.creationParamsCodec, + onFocus: () { + _focusNode!.requestFocus(); + }, + ); + if (widget.onPlatformViewCreated != null) { + _controller.addOnPlatformViewCreatedListener(widget.onPlatformViewCreated!); + } + } + + void _onFocusChange(bool isFocused) { + if (!_controller.isCreated) { + return; + } + if (!isFocused) { + _controller.clearFocus().catchError((dynamic e) { + if (e is MissingPluginException) { + // We land the framework part of Ohos platform views keyboard + // support before the engine part. There will be a commit range where + // clearFocus isn't implemented in the engine. When that happens we + // just swallow the error here. Once the engine part is rolled to the + // framework I'll remove this. + // TODO(amirh): remove this once the engine's clearFocus is rolled. + return; + } + }); + return; + } + SystemChannels.textInput.invokeMethod( + 'TextInput.setPlatformViewClient', + {'platformViewId': _id}, + ).catchError((dynamic e) { + if (e is MissingPluginException) { + // We land the framework part of Ohos platform views keyboard + // support before the engine part. There will be a commit range where + // setPlatformViewClient isn't implemented in the engine. When that + // happens we just swallow the error here. Once the engine part is + // rolled to the framework I'll remove this. + // TODO(amirh): remove this once the engine's clearFocus is rolled. + return; + } + }); + } +} + abstract class _DarwinViewState, ViewT extends _DarwinPlatformView> extends State { ControllerT? _controller; TextDirection? _layoutDirection; @@ -1026,6 +1295,40 @@ class _AndroidPlatformView extends LeafRenderObjectWidget { } } +class _OhosPlatformView extends LeafRenderObjectWidget { + const _OhosPlatformView({ + required this.controller, + required this.hitTestBehavior, + required this.gestureRecognizers, + this.clipBehavior = Clip.hardEdge, + }) : assert(controller != null), + assert(hitTestBehavior != null), + assert(gestureRecognizers != null), + assert(clipBehavior != null); + + final OhosViewController controller; + final PlatformViewHitTestBehavior hitTestBehavior; + final Set> gestureRecognizers; + final Clip clipBehavior; + + @override + RenderObject createRenderObject(BuildContext context) => + RenderOhosView( + viewController: controller, + hitTestBehavior: hitTestBehavior, + gestureRecognizers: gestureRecognizers, + clipBehavior: clipBehavior, + ); + + @override + void updateRenderObject(BuildContext context, RenderOhosView renderObject) { + renderObject.controller = controller; + renderObject.hitTestBehavior = hitTestBehavior; + renderObject.updateGestureRecognizers(gestureRecognizers); + renderObject.clipBehavior = clipBehavior; + } +} + abstract class _DarwinPlatformView> extends LeafRenderObjectWidget { const _DarwinPlatformView({ required this.controller, @@ -1421,6 +1724,36 @@ class AndroidViewSurface extends StatefulWidget { } } +class OhosViewSurface extends StatefulWidget { + /// Construct an `OhosPlatformViewSurface`. + const OhosViewSurface({ + super.key, + required this.controller, + required this.hitTestBehavior, + required this.gestureRecognizers, + }) : assert(controller != null), + assert(hitTestBehavior != null), + assert(gestureRecognizers != null); + + /// The controller for the platform view integrated by this [OhosViewSurface]. + /// + /// See [PlatformViewSurface.controller] for details. + final OhosViewController controller; + + /// Which gestures should be forwarded to the PlatformView. + /// + /// See [PlatformViewSurface.gestureRecognizers] for details. + final Set> gestureRecognizers; + + /// {@macro flutter.widgets.AndroidView.hitTestBehavior} + final PlatformViewHitTestBehavior hitTestBehavior; + + @override + State createState() { + return _OhosViewSurfaceState(); + } +} + class _AndroidViewSurfaceState extends State { @override void initState() { @@ -1461,6 +1794,46 @@ class _AndroidViewSurfaceState extends State { } } +class _OhosViewSurfaceState extends State { + @override + void initState() { + super.initState(); + if (!widget.controller.isCreated) { + // Schedule a rebuild once creation is complete and the final dislay + // type is known. + widget.controller.addOnPlatformViewCreatedListener(_onPlatformViewCreated); + } + } + + @override + void dispose() { + widget.controller.removeOnPlatformViewCreatedListener(_onPlatformViewCreated); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + if (widget.controller.requiresViewComposition) { + return _PlatformLayerBasedOhosViewSurface( + controller: widget.controller, + hitTestBehavior: widget.hitTestBehavior, + gestureRecognizers: widget.gestureRecognizers, + ); + } else { + return _TextureBasedOhosViewSurface( + controller: widget.controller, + hitTestBehavior: widget.hitTestBehavior, + gestureRecognizers: widget.gestureRecognizers, + ); + } + } + + void _onPlatformViewCreated(int _) { + // Trigger a re-build based on the current controller state. + setState(() {}); + } +} + // Displays an Android platform view via GL texture. class _TextureBasedAndroidViewSurface extends PlatformViewSurface { const _TextureBasedAndroidViewSurface({ @@ -1485,6 +1858,31 @@ class _TextureBasedAndroidViewSurface extends PlatformViewSurface { } } +class _TextureBasedOhosViewSurface extends PlatformViewSurface { + const _TextureBasedOhosViewSurface({ + required OhosViewController super.controller, + required super.hitTestBehavior, + required super.gestureRecognizers, + }) : assert(controller != null), + assert(hitTestBehavior != null), + assert(gestureRecognizers != null); + + @override + RenderObject createRenderObject(BuildContext context) { + final OhosViewController viewController = controller as OhosViewController; + // Use GL texture based composition. + // App should use GL texture unless they require to embed a SurfaceView. + final RenderOhosView renderBox = RenderOhosView( + viewController: viewController, + gestureRecognizers: gestureRecognizers, + hitTestBehavior: hitTestBehavior, + ); + viewController.pointTransformer = + (Offset position) => renderBox.globalToLocal(position); + return renderBox; + } +} + class _PlatformLayerBasedAndroidViewSurface extends PlatformViewSurface { const _PlatformLayerBasedAndroidViewSurface({ required AndroidViewController super.controller, @@ -1503,6 +1901,26 @@ class _PlatformLayerBasedAndroidViewSurface extends PlatformViewSurface { } } +class _PlatformLayerBasedOhosViewSurface extends PlatformViewSurface { + const _PlatformLayerBasedOhosViewSurface({ + required OhosViewController super.controller, + required super.hitTestBehavior, + required super.gestureRecognizers, + }) : assert(controller != null), + assert(hitTestBehavior != null), + assert(gestureRecognizers != null); + + @override + RenderObject createRenderObject(BuildContext context) { + final OhosViewController viewController = controller as OhosViewController; + final PlatformViewRenderBox renderBox = + super.createRenderObject(context) as PlatformViewRenderBox; + viewController.pointTransformer = + (Offset position) => renderBox.globalToLocal(position); + return renderBox; + } +} + /// A callback used to notify the size of the platform view placeholder. /// This size is the initial size of the platform view. typedef _OnLayoutCallback = void Function(Size size, Offset position); diff --git a/packages/flutter/lib/src/widgets/primary_scroll_controller.dart b/packages/flutter/lib/src/widgets/primary_scroll_controller.dart index dea998277de273e5c311ade958f4cd2b6a4d2bb6..fbdcce042e9683a715fd839b1df94801f78783f0 100644 --- a/packages/flutter/lib/src/widgets/primary_scroll_controller.dart +++ b/packages/flutter/lib/src/widgets/primary_scroll_controller.dart @@ -13,6 +13,7 @@ const Set _kMobilePlatforms = { TargetPlatform.android, TargetPlatform.iOS, TargetPlatform.fuchsia, + TargetPlatform.ohos, }; /// Associates a [ScrollController] with a subtree. diff --git a/packages/flutter/lib/src/widgets/scroll_configuration.dart b/packages/flutter/lib/src/widgets/scroll_configuration.dart index a2b6bd4e0ec05a74c7af3a8fdbb6d9fce4575675..807cd8bb94df2b4383d0f82805c9017df431b6df 100644 --- a/packages/flutter/lib/src/widgets/scroll_configuration.dart +++ b/packages/flutter/lib/src/widgets/scroll_configuration.dart @@ -121,6 +121,7 @@ class ScrollBehavior { case TargetPlatform.windows: case TargetPlatform.android: case TargetPlatform.fuchsia: + case TargetPlatform.ohos: return MultitouchDragStrategy.latestPointer; } } @@ -160,6 +161,7 @@ class ScrollBehavior { case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.iOS: + case TargetPlatform.ohos: return child; } } @@ -174,6 +176,7 @@ class ScrollBehavior { case TargetPlatform.linux: case TargetPlatform.macOS: case TargetPlatform.windows: + case TargetPlatform.ohos: return child; case TargetPlatform.android: case TargetPlatform.fuchsia: @@ -208,6 +211,7 @@ class ScrollBehavior { case TargetPlatform.macOS: return (PointerEvent event) => MacOSScrollViewFlingVelocityTracker(event.kind); case TargetPlatform.android: + case TargetPlatform.ohos: case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: @@ -239,6 +243,7 @@ class ScrollBehavior { case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: return _clampingPhysics; } } diff --git a/packages/flutter/lib/src/widgets/scroll_view.dart b/packages/flutter/lib/src/widgets/scroll_view.dart index ad8dcd813d9e5715eda3ac5358344e6f04972d1d..dc90777a0b2de8768a5f83b4d4b675d34011b7fc 100644 --- a/packages/flutter/lib/src/widgets/scroll_view.dart +++ b/packages/flutter/lib/src/widgets/scroll_view.dart @@ -4,6 +4,7 @@ import 'dart:math' as math; +import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/rendering.dart'; diff --git a/packages/flutter/lib/src/widgets/scrollbar.dart b/packages/flutter/lib/src/widgets/scrollbar.dart index bb63d5561dfc80585e43d03bf37857e4ef247250..c9685845cf779710d19dd4bb4a4d681628f095c5 100644 --- a/packages/flutter/lib/src/widgets/scrollbar.dart +++ b/packages/flutter/lib/src/widgets/scrollbar.dart @@ -1610,6 +1610,7 @@ class RawScrollbarState extends State with TickerProv newPosition = clampDouble(newPosition, position.minScrollExtent, position.maxScrollExtent); case TargetPlatform.iOS: case TargetPlatform.android: + case TargetPlatform.ohos: // We can only drag the scrollbar into overscroll on mobile // platforms, and only then if the physics allow it. break; diff --git a/packages/flutter/lib/src/widgets/selectable_region.dart b/packages/flutter/lib/src/widgets/selectable_region.dart index 519489c66bded24adaf9b0ec93b11e6185f8be4f..77528d70e42e21cf3a5dd56e69c660c4b6ddf2fb 100644 --- a/packages/flutter/lib/src/widgets/selectable_region.dart +++ b/packages/flutter/lib/src/widgets/selectable_region.dart @@ -280,6 +280,7 @@ class SelectableRegion extends StatefulWidget { || TargetPlatform.fuchsia || TargetPlatform.linux || TargetPlatform.windows + || TargetPlatform.ohos => false, // TODO(bleroux): the share button should be shown on iOS but the share // functionality requires some changes on the engine side because, on iPad, @@ -414,6 +415,7 @@ class SelectableRegionState extends State with TextSelectionDe case TargetPlatform.android: case TargetPlatform.iOS: break; + case TargetPlatform.ohos: case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.macOS: @@ -491,6 +493,7 @@ class SelectableRegionState extends State with TextSelectionDe case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.linux: + case TargetPlatform.ohos: // From observation, these platforms reset their tap count to 0 when // the number of consecutive taps exceeds the max consecutive tap supported. // For example on Debian Linux with GTK, when going past a triple click, @@ -546,6 +549,7 @@ class SelectableRegionState extends State with TextSelectionDe case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.iOS: + case TargetPlatform.ohos: // On mobile platforms the selection is set on tap up. break; case TargetPlatform.macOS: @@ -589,6 +593,7 @@ class SelectableRegionState extends State with TextSelectionDe case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.iOS: + case TargetPlatform.ohos: _collapseSelectionAt(offset: details.globalPosition); case TargetPlatform.macOS: case TargetPlatform.linux: @@ -654,6 +659,7 @@ class SelectableRegionState extends State with TextSelectionDe case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.windows: + case TargetPlatform.ohos: // If lastSecondaryTapDownPosition is within the current selection then // keep the current selection, if not then collapse it. final bool lastSecondaryTapDownPositionWasOnActiveSelection = _positionIsOnActiveSelection(globalPosition: details.globalPosition); @@ -1231,6 +1237,7 @@ class SelectableRegionState extends State with TextSelectionDe switch (defaultTargetPlatform) { case TargetPlatform.android: case TargetPlatform.fuchsia: + case TargetPlatform.ohos: _clearSelection(); case TargetPlatform.iOS: hideToolbar(false); @@ -1245,6 +1252,7 @@ class SelectableRegionState extends State with TextSelectionDe case TargetPlatform.android: case TargetPlatform.iOS: case TargetPlatform.fuchsia: + case TargetPlatform.ohos: selectAll(SelectionChangedCause.toolbar); case TargetPlatform.linux: case TargetPlatform.macOS: @@ -1260,6 +1268,7 @@ class SelectableRegionState extends State with TextSelectionDe switch (defaultTargetPlatform) { case TargetPlatform.android: case TargetPlatform.fuchsia: + case TargetPlatform.ohos: _clearSelection(); case TargetPlatform.iOS: hideToolbar(false); diff --git a/packages/flutter/lib/src/widgets/text_selection.dart b/packages/flutter/lib/src/widgets/text_selection.dart index 5398787540c76a6acd3a9895fb416dbe243fa3fb..95e834db80bf89c210d04776e9cab5f8168cac4c 100644 --- a/packages/flutter/lib/src/widgets/text_selection.dart +++ b/packages/flutter/lib/src/widgets/text_selection.dart @@ -765,6 +765,7 @@ class TextSelectionOverlay { case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: newSelection = TextSelection( baseOffset: _selection.baseOffset, extentOffset: position.offset, @@ -859,6 +860,7 @@ class TextSelectionOverlay { case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: newSelection = TextSelection( baseOffset: position.offset, extentOffset: _selection.extentOffset, @@ -1251,6 +1253,7 @@ class SelectionOverlay { case TargetPlatform.linux: case TargetPlatform.macOS: case TargetPlatform.windows: + case TargetPlatform.ohos: break; } } @@ -1975,6 +1978,7 @@ class TextSelectionGestureDetectorBuilder { case TargetPlatform.linux: case TargetPlatform.macOS: case TargetPlatform.windows: + case TargetPlatform.ohos: } } @@ -1988,6 +1992,7 @@ class TextSelectionGestureDetectorBuilder { case TargetPlatform.linux: case TargetPlatform.macOS: case TargetPlatform.windows: + case TargetPlatform.ohos: } } @@ -2228,6 +2233,7 @@ class TextSelectionGestureDetectorBuilder { case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.iOS: + case TargetPlatform.ohos: // On mobile platforms the selection is set on tap up. break; case TargetPlatform.macOS: @@ -2347,7 +2353,7 @@ class TextSelectionGestureDetectorBuilder { case TargetPlatform.macOS: case TargetPlatform.windows: break; - // On desktop platforms the selection is set on tap down. + case TargetPlatform.ohos: case TargetPlatform.android: editableText.hideToolbar(false); if (isShiftPressedValid) { @@ -2483,6 +2489,7 @@ class TextSelectionGestureDetectorBuilder { case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.linux: + case TargetPlatform.ohos: case TargetPlatform.windows: renderEditable.selectWord(cause: SelectionChangedCause.longPress); } @@ -2541,6 +2548,7 @@ class TextSelectionGestureDetectorBuilder { case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: renderEditable.selectWordsInRange( from: details.globalPosition - details.offsetFromOrigin - editableOffset - scrollableOffset, to: details.globalPosition, @@ -2599,6 +2607,7 @@ class TextSelectionGestureDetectorBuilder { case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.linux: + case TargetPlatform.ohos: case TargetPlatform.windows: if (!renderEditable.hasFocus) { renderEditable.selectPosition(cause: SelectionChangedCause.tap); @@ -2723,6 +2732,7 @@ class TextSelectionGestureDetectorBuilder { case TargetPlatform.iOS: case TargetPlatform.macOS: case TargetPlatform.windows: + case TargetPlatform.ohos: _selectParagraphsInRange(from: details.globalPosition, cause: SelectionChangedCause.tap); case TargetPlatform.linux: _selectLinesInRange(from: details.globalPosition, cause: SelectionChangedCause.tap); @@ -2770,6 +2780,7 @@ class TextSelectionGestureDetectorBuilder { case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: _extendSelection(details.globalPosition, SelectionChangedCause.drag); } } else { @@ -2800,6 +2811,7 @@ class TextSelectionGestureDetectorBuilder { } case TargetPlatform.android: case TargetPlatform.fuchsia: + case TargetPlatform.ohos: switch (details.kind) { case PointerDeviceKind.mouse: case PointerDeviceKind.trackpad: @@ -2888,6 +2900,7 @@ class TextSelectionGestureDetectorBuilder { case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.iOS: + case TargetPlatform.ohos: switch (details.kind) { case PointerDeviceKind.mouse: case PointerDeviceKind.trackpad: @@ -2959,6 +2972,7 @@ class TextSelectionGestureDetectorBuilder { return; case TargetPlatform.android: case TargetPlatform.fuchsia: + case TargetPlatform.ohos: // With a precise pointer device, such as a mouse, trackpad, or stylus, // the drag will select the text spanning the origin of the drag to the end of the drag. // With a touch device, the cursor should move with the drag. @@ -3245,6 +3259,7 @@ class _TextSelectionGestureDetectorState extends State( () => TapAndHorizontalDragGestureRecognizer(debugOwner: this), (TapAndHorizontalDragGestureRecognizer instance) { diff --git a/packages/flutter/test/cupertino/adaptive_text_selection_toolbar_test.dart b/packages/flutter/test/cupertino/adaptive_text_selection_toolbar_test.dart index 0fc08af596cacd863167d45900133b3a6abe11fc..ba20a41ac30e774e68d04a2f5e7843c391301940 100644 --- a/packages/flutter/test/cupertino/adaptive_text_selection_toolbar_test.dart +++ b/packages/flutter/test/cupertino/adaptive_text_selection_toolbar_test.dart @@ -59,6 +59,7 @@ void main() { case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.iOS: + case TargetPlatform.ohos: expect(find.byType(CupertinoTextSelectionToolbar), findsOneWidget); expect(find.byType(CupertinoDesktopTextSelectionToolbar), findsNothing); case TargetPlatform.macOS: @@ -147,6 +148,7 @@ void main() { case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.iOS: + case TargetPlatform.ohos: expect(find.byType(CupertinoTextSelectionToolbarButton), findsOneWidget); case TargetPlatform.macOS: case TargetPlatform.linux: @@ -202,6 +204,7 @@ void main() { case TargetPlatform.fuchsia: case TargetPlatform.iOS: + case TargetPlatform.ohos: expect(find.byType(CupertinoTextSelectionToolbarButton), findsNWidgets(6)); expect(find.text('Cut'), findsOneWidget); expect(find.text('Copy'), findsOneWidget); @@ -268,6 +271,7 @@ void main() { case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.iOS: + case TargetPlatform.ohos: expect(find.byType(CupertinoTextSelectionToolbarButton), findsOneWidget); expect(find.byType(CupertinoDesktopTextSelectionToolbarButton), findsNothing); case TargetPlatform.macOS: diff --git a/packages/flutter/test/cupertino/app_test.dart b/packages/flutter/test/cupertino/app_test.dart index df359b9d8dc10bf5a84265ba7550d36d47b47c70..169fd2ac2dcc67560ba5975b6278c32f9591a474 100644 --- a/packages/flutter/test/cupertino/app_test.dart +++ b/packages/flutter/test/cupertino/app_test.dart @@ -529,6 +529,7 @@ void main() { case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.iOS: + case TargetPlatform.ohos: // Does not throw if we aren't using it. defaultBehavior.buildScrollbar(capturedContext, child, details); case TargetPlatform.linux: diff --git a/packages/flutter/test/cupertino/text_field_test.dart b/packages/flutter/test/cupertino/text_field_test.dart index d443e546aaf05b1cf3425eef4a1c491f66cb9ca3..c6552a755dd88b0411de90989f30f0284ae21f0f 100644 --- a/packages/flutter/test/cupertino/text_field_test.dart +++ b/packages/flutter/test/cupertino/text_field_test.dart @@ -6782,6 +6782,7 @@ void main() { // On Apple platforms, dragging the base handle makes it the extent. expect(controller.selection.baseOffset, testValue.length); expect(controller.selection.extentOffset, toOffset); + case TargetPlatform.ohos: case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.linux: @@ -8570,6 +8571,7 @@ void main() { expect(controller.selection.baseOffset, 0); // Other platforms start from the previous selection. + case TargetPlatform.ohos: case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.linux: @@ -8692,7 +8694,7 @@ void main() { ); addTearDown(controller.dispose); final bool isTargetPlatformMobile = defaultTargetPlatform == TargetPlatform.android - || defaultTargetPlatform == TargetPlatform.fuchsia; + || defaultTargetPlatform == TargetPlatform.fuchsia || defaultTargetPlatform == TargetPlatform.ohos; await tester.pumpWidget( CupertinoApp( home: Center( @@ -8908,7 +8910,7 @@ void main() { ); addTearDown(controller.dispose); final bool isTargetPlatformMobile = defaultTargetPlatform == TargetPlatform.android - || defaultTargetPlatform == TargetPlatform.fuchsia; + || defaultTargetPlatform == TargetPlatform.fuchsia || defaultTargetPlatform == TargetPlatform.ohos; await tester.pumpWidget( CupertinoApp( home: Center( @@ -9050,7 +9052,7 @@ void main() { expect(find.text('Cut'), findsOneWidget); expect(find.text('Copy'), findsOneWidget); expect(find.text('Paste'), findsOneWidget); - + case TargetPlatform.ohos: case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.linux: @@ -9074,7 +9076,7 @@ void main() { expect(find.text('Cut'), findsOneWidget); expect(find.text('Copy'), findsOneWidget); expect(find.text('Paste'), findsOneWidget); - + case TargetPlatform.ohos: case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.linux: @@ -9374,7 +9376,7 @@ void main() { {TargetPlatform.iOS, TargetPlatform.android})); }); - testWidgets('should build nothing on all platforms but iOS and Android', (WidgetTester tester) async { + testWidgets('should build nothing on all platforms but iOS and Android、OpenHarmony', (WidgetTester tester) async { await tester.pumpWidget(const CupertinoApp( home: CupertinoTextField(), )); @@ -9393,7 +9395,7 @@ void main() { isNull); }, variant: TargetPlatformVariant.all( - excluding: {TargetPlatform.iOS, TargetPlatform.android})); + excluding: {TargetPlatform.iOS, TargetPlatform.android, TargetPlatform.ohos })); }); testWidgets('Can drag handles to show, unshow, and update magnifier', diff --git a/packages/flutter/test/material/about_test.dart b/packages/flutter/test/material/about_test.dart index 49d1a2b3f649ad54283b177a80976eb972fcdce4..e562813720b20a4d6a74ec73e975c7919d8919cd 100644 --- a/packages/flutter/test/material/about_test.dart +++ b/packages/flutter/test/material/about_test.dart @@ -1267,6 +1267,7 @@ void main() { case TargetPlatform.linux: case TargetPlatform.macOS: case TargetPlatform.windows: + case TargetPlatform.ohos: expect(find.byType(CupertinoScrollbar), findsNothing); case TargetPlatform.iOS: expect(find.byType(CupertinoScrollbar), findsOneWidget); diff --git a/packages/flutter/test/material/adaptive_text_selection_toolbar_test.dart b/packages/flutter/test/material/adaptive_text_selection_toolbar_test.dart index 58bddbbc4010b6a517fb812b737472afc3847ae4..64ed6c8de32b91159299d02ed3f923ee0ef9d0b6 100644 --- a/packages/flutter/test/material/adaptive_text_selection_toolbar_test.dart +++ b/packages/flutter/test/material/adaptive_text_selection_toolbar_test.dart @@ -53,6 +53,7 @@ void main() { expect(find.text(buttonText), findsOneWidget); switch (defaultTargetPlatform) { + case TargetPlatform.ohos: case TargetPlatform.android: expect(find.byType(TextSelectionToolbar), findsOneWidget); expect(find.byType(CupertinoTextSelectionToolbar), findsNothing); @@ -154,6 +155,7 @@ void main() { expect(find.text('Paste'), findsOneWidget); switch (defaultTargetPlatform) { + case TargetPlatform.ohos: case TargetPlatform.android: case TargetPlatform.fuchsia: expect(find.byType(TextSelectionToolbarTextButton), findsOneWidget); @@ -201,6 +203,7 @@ void main() { expect(find.byKey(key), findsOneWidget); switch (defaultTargetPlatform) { + case TargetPlatform.ohos: case TargetPlatform.android: expect(find.byType(TextSelectionToolbarTextButton), findsNWidgets(6)); expect(find.text('Cut'), findsOneWidget); @@ -332,6 +335,7 @@ void main() { expect(buttonTypes, contains(ContextMenuButtonType.paste)); switch (defaultTargetPlatform) { + case TargetPlatform.ohos: case TargetPlatform.android: case TargetPlatform.iOS: case TargetPlatform.fuchsia: @@ -356,6 +360,7 @@ void main() { expect(buttonTypes, contains(ContextMenuButtonType.paste)); switch (defaultTargetPlatform) { + case TargetPlatform.ohos: case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.linux: @@ -405,6 +410,7 @@ void main() { expect(find.text(buttonText), findsOneWidget); switch (defaultTargetPlatform) { + case TargetPlatform.ohos: case TargetPlatform.fuchsia: case TargetPlatform.android: expect(find.byType(TextSelectionToolbarTextButton), findsOneWidget); diff --git a/packages/flutter/test/material/app_test.dart b/packages/flutter/test/material/app_test.dart index d6b549abca3b0f90cdb43fabb9791f5357644866..f9fa710abcfc9f88fbfb2a0e26c100ea61e1a837 100644 --- a/packages/flutter/test/material/app_test.dart +++ b/packages/flutter/test/material/app_test.dart @@ -1518,6 +1518,7 @@ void main() { case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.iOS: + case TargetPlatform.ohos: // Does not throw if we aren't using it. defaultBehavior.buildScrollbar(capturedContext, child, details); case TargetPlatform.linux: @@ -1567,6 +1568,7 @@ void main() { case TargetPlatform.linux: case TargetPlatform.macOS: case TargetPlatform.windows: + case TargetPlatform.ohos: // Does not throw if we aren't using it. // Horizontal axis gets no scrollbars for all platforms. defaultBehavior.buildScrollbar(capturedContext, child, details); diff --git a/packages/flutter/test/material/back_button_test.dart b/packages/flutter/test/material/back_button_test.dart index afdba078a123ea5268c33886d165b456402e0ed8..d486eb86a370b1f99b35b2e933b3083669c29e11 100644 --- a/packages/flutter/test/material/back_button_test.dart +++ b/packages/flutter/test/material/back_button_test.dart @@ -206,6 +206,7 @@ void main() { case TargetPlatform.linux: case TargetPlatform.macOS: case TargetPlatform.windows: + case TargetPlatform.ohos: expectedLabel = null; } expect(tester.getSemantics(find.byType(BackButton)), matchesSemantics( @@ -249,6 +250,7 @@ void main() { case TargetPlatform.linux: case TargetPlatform.macOS: case TargetPlatform.windows: + case TargetPlatform.ohos: expectedLabel = null; } expect(tester.getSemantics(find.byType(CloseButton)), matchesSemantics( diff --git a/packages/flutter/test/material/drawer_button_test.dart b/packages/flutter/test/material/drawer_button_test.dart index bb565c8c753324565d0233c7950f1de6148e593c..dd37e169194c871cfe99ee3f0003c2ebcff528e1 100644 --- a/packages/flutter/test/material/drawer_button_test.dart +++ b/packages/flutter/test/material/drawer_button_test.dart @@ -173,6 +173,7 @@ void main() { case TargetPlatform.linux: case TargetPlatform.macOS: case TargetPlatform.windows: + case TargetPlatform.ohos: expectedLabel = null; } expect(tester.getSemantics(find.byType(DrawerButton)), matchesSemantics( @@ -230,6 +231,7 @@ void main() { case TargetPlatform.linux: case TargetPlatform.macOS: case TargetPlatform.windows: + case TargetPlatform.ohos: expectedLabel = null; } expect(tester.getSemantics(find.byType(EndDrawerButton)), matchesSemantics( diff --git a/packages/flutter/test/material/magnifier_test.dart b/packages/flutter/test/material/magnifier_test.dart index b1e3555735de0b05da23af352c5c35ccb20f74f0..be6910a6091408c91465e10a0517654016c40b56 100644 --- a/packages/flutter/test/material/magnifier_test.dart +++ b/packages/flutter/test/material/magnifier_test.dart @@ -90,7 +90,7 @@ void main() { expect(builtWidget, isA()); }, variant: TargetPlatformVariant.only(TargetPlatform.iOS)); - testWidgets('should return null on all platforms not Android, iOS', + testWidgets('should return null on all platforms not Android, iOS, OpenHarmony', (WidgetTester tester) async { await tester.pumpWidget(const MaterialApp( home: Placeholder(), @@ -112,7 +112,8 @@ void main() { variant: TargetPlatformVariant.all( excluding: { TargetPlatform.iOS, - TargetPlatform.android + TargetPlatform.android, + TargetPlatform.ohos, }), ); }); diff --git a/packages/flutter/test/material/menu_anchor_test.dart b/packages/flutter/test/material/menu_anchor_test.dart index 8e411e54acfebfed9f7f1b4422b82a2b5ef35d9f..04bf26b70f768a486d74d485be841b2626303905 100644 --- a/packages/flutter/test/material/menu_anchor_test.dart +++ b/packages/flutter/test/material/menu_anchor_test.dart @@ -2105,6 +2105,7 @@ void main() { Text mnemonic3; switch (defaultTargetPlatform) { + case TargetPlatform.ohos: case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.linux: @@ -3091,6 +3092,7 @@ void main() { String expectedSeparator; String expectedShift; switch (defaultTargetPlatform) { + case TargetPlatform.ohos: case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.linux: @@ -3122,6 +3124,7 @@ void main() { case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: allExpected = [expectedAlt, expectedCtrl, expectedMeta, expectedShift, 'A'].join(expectedSeparator); case TargetPlatform.iOS: case TargetPlatform.macOS: diff --git a/packages/flutter/test/material/navigation_drawer_test.dart b/packages/flutter/test/material/navigation_drawer_test.dart index 849ce1fe60d2077e9ce0da6ac5842cf1735b8c1a..8fc0845fbe3204b619ace06c6acf2548ce75d686 100644 --- a/packages/flutter/test/material/navigation_drawer_test.dart +++ b/packages/flutter/test/material/navigation_drawer_test.dart @@ -289,6 +289,94 @@ void main() { expect(tester.getBottomRight(find.widgetWithText(NavigationDrawerDestination,'Label4')).dy, viewHeight); }); + testWidgets('Navigation drawer is scrollable', (WidgetTester tester) async { + final GlobalKey scaffoldKey = GlobalKey(); + widgetSetup(tester, 500, windowHeight: 300); + await tester.pumpWidget( + _buildWidget( + scaffoldKey, + NavigationDrawer( + children: [ + for(int i = 0; i < 100; i++) + NavigationDrawerDestination( + icon: const Icon(Icons.ac_unit), + label: Text('Label$i'), + ), + ], + onDestinationSelected: (int i) {}, + ), + ), + ); + scaffoldKey.currentState!.openDrawer(); + await tester.pump(const Duration(seconds: 1)); + + expect(find.text('Label0'), findsOneWidget); + expect(find.text('Label1'), findsOneWidget); + expect(find.text('Label2'), findsOneWidget); + expect(find.text('Label3'), findsOneWidget); + expect(find.text('Label4'), findsOneWidget); + expect(find.text('Label5'), findsOneWidget); + expect(find.text('Label6'), findsNothing); + expect(find.text('Label7'), findsNothing); + expect(find.text('Label8'), findsNothing); + + await tester.dragFrom(const Offset(0, 200), const Offset(0.0, -200)); + await tester.pump(); + + expect(find.text('Label0'), findsNothing); + expect(find.text('Label1'), findsNothing); + expect(find.text('Label2'), findsNothing); + expect(find.text('Label3'), findsOneWidget); + expect(find.text('Label4'), findsOneWidget); + expect(find.text('Label5'), findsOneWidget); + expect(find.text('Label6'), findsOneWidget); + expect(find.text('Label7'), findsOneWidget); + expect(find.text('Label8'), findsOneWidget); + expect(find.text('Label9'), findsNothing); + expect(find.text('Label10'), findsNothing); + }); + + testWidgets('Safe Area test', (WidgetTester tester) async { + final GlobalKey scaffoldKey = GlobalKey(); + const double windowHeight = 300; + widgetSetup(tester, 500, windowHeight: windowHeight); + await tester.pumpWidget( + MediaQuery( + data: const MediaQueryData(padding: EdgeInsets.all(20.0)), + child: MaterialApp( + useInheritedMediaQuery: true, + theme: ThemeData.light(), + home: Scaffold( + key: scaffoldKey, + drawer: NavigationDrawer( + children: [ + for(int i = 0; i < 10; i++) + NavigationDrawerDestination( + icon: const Icon(Icons.ac_unit), + label: Text('Label$i'), + ), + ], + onDestinationSelected: (int i) {}, + ), + body: Container(), + ), + ), + ), + ); + scaffoldKey.currentState!.openDrawer(); + await tester.pump(); + await tester.pump(const Duration(seconds: 1)); + + // Safe area padding on the top and sides. + expect( + tester.getTopLeft(find.widgetWithText(NavigationDrawerDestination,'Label0')), + const Offset(20.0, 20.0), + ); + + // No Safe area padding at the bottom. + expect(tester.getBottomRight(find.widgetWithText(NavigationDrawerDestination,'Label4')).dy, windowHeight); + }); + testWidgets('Navigation drawer semantics', (WidgetTester tester) async { final GlobalKey scaffoldKey = GlobalKey(); final ThemeData theme = ThemeData(); diff --git a/packages/flutter/test/material/page_transitions_theme_test.dart b/packages/flutter/test/material/page_transitions_theme_test.dart index 275c310774209ab89e03349eb0540dd51c2f02dd..0a37b9ccc8058266fcc8fbb3a91da61088a1896b 100644 --- a/packages/flutter/test/material/page_transitions_theme_test.dart +++ b/packages/flutter/test/material/page_transitions_theme_test.dart @@ -21,6 +21,7 @@ void main() { case TargetPlatform.android: case TargetPlatform.iOS: case TargetPlatform.macOS: + case TargetPlatform.ohos: expect( theme.builders[platform], isNotNull, diff --git a/packages/flutter/test/material/selection_area_test.dart b/packages/flutter/test/material/selection_area_test.dart index 9967576c88cffc1b099a12195e2f21ad2127451b..c0efb5dd4853a60e8606091ba17a5183b489c172 100644 --- a/packages/flutter/test/material/selection_area_test.dart +++ b/packages/flutter/test/material/selection_area_test.dart @@ -28,6 +28,7 @@ void main() { final SelectableRegion region = tester.widget(find.byType(SelectableRegion)); switch (defaultTargetPlatform) { + case TargetPlatform.ohos: case TargetPlatform.android: case TargetPlatform.fuchsia: expect(region.selectionControls, materialTextSelectionHandleControls); diff --git a/packages/flutter/test/material/switch_test.dart b/packages/flutter/test/material/switch_test.dart index 3e690c12d54d0fb973002349e6417f235de2e90f..f8b9d4961550541323918584671b0cc6a7387dbe 100644 --- a/packages/flutter/test/material/switch_test.dart +++ b/packages/flutter/test/material/switch_test.dart @@ -4200,6 +4200,7 @@ class _SwitchThemeAdaptation extends Adaptation { case TargetPlatform.fuchsia: case TargetPlatform.windows: case TargetPlatform.linux: + case TargetPlatform.ohos: return defaultValue; case TargetPlatform.iOS: case TargetPlatform.macOS: diff --git a/packages/flutter/test/material/text_field_test.dart b/packages/flutter/test/material/text_field_test.dart index 19c187ece188f676f5dfb4989fdbef7be9ece291..0aefdabfd47303a395dfbcb1be03b87c3101906a 100644 --- a/packages/flutter/test/material/text_field_test.dart +++ b/packages/flutter/test/material/text_field_test.dart @@ -2936,6 +2936,7 @@ void main() { case TargetPlatform.macOS: expect(controller.selection.baseOffset, 11); expect(controller.selection.extentOffset, 2); + case TargetPlatform.ohos: case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.linux: @@ -2964,6 +2965,7 @@ void main() { // The left handle was already the extent, and it remains so. expect(controller.selection.baseOffset, 11); expect(controller.selection.extentOffset, 0); + case TargetPlatform.ohos: case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.linux: @@ -3041,6 +3043,7 @@ void main() { case TargetPlatform.macOS: expect(controller.selection.baseOffset, 11); expect(controller.selection.extentOffset, 2); + case TargetPlatform.ohos: case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.linux: @@ -3069,6 +3072,7 @@ void main() { // The left handle was already the extent, and it remains so. expect(controller.selection.baseOffset, 11); expect(controller.selection.extentOffset, 0); + case TargetPlatform.ohos: case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.linux: @@ -3159,6 +3163,7 @@ void main() { case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: expect(controller.selection.baseOffset, toOffset); expect(controller.selection.extentOffset, testValue.length); } @@ -15995,6 +16000,7 @@ void main() { expect(controller.selection.baseOffset, 0); // Other platforms start from the previous selection. + case TargetPlatform.ohos: case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.linux: @@ -16468,7 +16474,7 @@ void main() { expect(find.text('Cut'), findsOneWidget); expect(find.text('Copy'), findsOneWidget); expect(find.text('Paste'), findsOneWidget); - + case TargetPlatform.ohos: case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.linux: @@ -16493,7 +16499,7 @@ void main() { expect(find.text('Cut'), findsOneWidget); expect(find.text('Copy'), findsOneWidget); expect(find.text('Paste'), findsOneWidget); - + case TargetPlatform.ohos: case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.linux: @@ -16921,7 +16927,7 @@ void main() { isA()); }, variant: TargetPlatformVariant.only(TargetPlatform.iOS)); - testWidgets('should build nothing on Android and iOS', + testWidgets('should build nothing on Android and iOS,OpenHarmony', (WidgetTester tester) async { await tester.pumpWidget(const MaterialApp( home: Scaffold(body: TextField())) @@ -16942,7 +16948,8 @@ void main() { }, variant: TargetPlatformVariant.all(excluding: { TargetPlatform.iOS, - TargetPlatform.android + TargetPlatform.android, + TargetPlatform.ohos, })); }); }); @@ -17432,6 +17439,7 @@ void main() { switch (pointerDeviceKind) { case PointerDeviceKind.touch: switch (defaultTargetPlatform) { + case TargetPlatform.ohos: case TargetPlatform.iOS: case TargetPlatform.android: case TargetPlatform.fuchsia: @@ -17500,6 +17508,7 @@ void main() { case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: expect(spellCheckToolbar, isA()); } }, variant: const TargetPlatformVariant({ TargetPlatform.android, TargetPlatform.iOS })); @@ -17522,6 +17531,7 @@ void main() { case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: expectedConfiguration = SpellCheckConfiguration( misspelledTextStyle: TextField.materialMisspelledTextStyle, spellCheckService: DefaultSpellCheckService(), diff --git a/packages/flutter/test/material/text_form_field_test.dart b/packages/flutter/test/material/text_form_field_test.dart index 99b739d56dd16400d78107e8f810c3a057c1146e..b7eded8cb8a39b01d98d8c45fb69d0b03e2764ed 100644 --- a/packages/flutter/test/material/text_form_field_test.dart +++ b/packages/flutter/test/material/text_form_field_test.dart @@ -1197,7 +1197,7 @@ void main() { expect(find.text('Cut'), findsOneWidget); expect(find.text('Copy'), findsOneWidget); expect(find.text('Paste'), findsOneWidget); - + case TargetPlatform.ohos: case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.linux: @@ -1222,7 +1222,7 @@ void main() { expect(find.text('Cut'), findsOneWidget); expect(find.text('Copy'), findsOneWidget); expect(find.text('Paste'), findsOneWidget); - + case TargetPlatform.ohos: case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.linux: diff --git a/packages/flutter/test/material/theme_data_test.dart b/packages/flutter/test/material/theme_data_test.dart index ba2acd84827d52c2e8740c25d38669c216bf033f..c443d70b3607ed871ac5fb237944b3ac926c8fd0 100644 --- a/packages/flutter/test/material/theme_data_test.dart +++ b/packages/flutter/test/material/theme_data_test.dart @@ -86,6 +86,7 @@ void main() { testWidgets('Defaults to MaterialTapTargetBehavior.padded on mobile platforms and MaterialTapTargetBehavior.shrinkWrap on desktop', (WidgetTester tester) async { final ThemeData themeData = ThemeData(platform: defaultTargetPlatform); switch (defaultTargetPlatform) { + case TargetPlatform.ohos: case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.iOS: @@ -493,6 +494,7 @@ void main() { } else { expect(theme.splashFactory, equals(InkSparkle.splashFactory)); } + case TargetPlatform.ohos: case TargetPlatform.iOS: case TargetPlatform.fuchsia: case TargetPlatform.linux: @@ -506,6 +508,7 @@ void main() { final ThemeData theme = ThemeData(useMaterial3: false); switch (debugDefaultTargetPlatformOverride!) { + case TargetPlatform.ohos: case TargetPlatform.android: case TargetPlatform.iOS: case TargetPlatform.fuchsia: @@ -518,6 +521,7 @@ void main() { testWidgets('VisualDensity.adaptivePlatformDensity returns adaptive values', (WidgetTester tester) async { switch (debugDefaultTargetPlatformOverride!) { + case TargetPlatform.ohos: case TargetPlatform.android: case TargetPlatform.iOS: case TargetPlatform.fuchsia: @@ -545,6 +549,7 @@ void main() { testWidgets('VisualDensity in ThemeData defaults to "compact" on desktop and "standard" on mobile', (WidgetTester tester) async { final ThemeData themeData = ThemeData(); switch (debugDefaultTargetPlatformOverride!) { + case TargetPlatform.ohos: case TargetPlatform.android: case TargetPlatform.iOS: case TargetPlatform.fuchsia: diff --git a/packages/flutter/test/widgets/editable_text_test.dart b/packages/flutter/test/widgets/editable_text_test.dart index 3c2bb0d21690c6508c24ab59379cb85370f3c750..63a9f40491716e7ae6572dfbd1dd70b0badf94a8 100644 --- a/packages/flutter/test/widgets/editable_text_test.dart +++ b/packages/flutter/test/widgets/editable_text_test.dart @@ -6803,6 +6803,7 @@ void main() { switch (defaultTargetPlatform) { // These platforms extend by line. + case TargetPlatform.ohos: case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.linux: @@ -6975,6 +6976,7 @@ void main() { switch (defaultTargetPlatform) { // Extend selection. + case TargetPlatform.ohos: case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.linux: @@ -7780,6 +7782,7 @@ void main() { switch (defaultTargetPlatform) { // These platforms don't move the selection with home/end at all. + case TargetPlatform.ohos: case TargetPlatform.android: case TargetPlatform.iOS: case TargetPlatform.fuchsia: @@ -7820,6 +7823,7 @@ void main() { switch (defaultTargetPlatform) { // These platforms don't move the selection with home/end at all. + case TargetPlatform.ohos: case TargetPlatform.android: case TargetPlatform.iOS: case TargetPlatform.fuchsia: @@ -7927,6 +7931,7 @@ void main() { switch (defaultTargetPlatform) { // These platforms don't move the selection with home/end at all. + case TargetPlatform.ohos: case TargetPlatform.android: case TargetPlatform.iOS: case TargetPlatform.fuchsia: @@ -7967,6 +7972,7 @@ void main() { switch (defaultTargetPlatform) { // These platforms don't move the selection with home/end at all still. + case TargetPlatform.ohos: case TargetPlatform.android: case TargetPlatform.iOS: case TargetPlatform.fuchsia: @@ -8083,6 +8089,7 @@ void main() { switch (defaultTargetPlatform) { // These platforms don't move the selection with home/end at all. + case TargetPlatform.ohos: case TargetPlatform.android: case TargetPlatform.iOS: case TargetPlatform.fuchsia: @@ -8123,6 +8130,7 @@ void main() { switch (defaultTargetPlatform) { // These platforms don't move the selection with home/end at all still. + case TargetPlatform.ohos: case TargetPlatform.android: case TargetPlatform.iOS: case TargetPlatform.fuchsia: @@ -8262,6 +8270,7 @@ void main() { switch (defaultTargetPlatform) { // These platforms don't handle shift + home/end at all. + case TargetPlatform.ohos: case TargetPlatform.android: case TargetPlatform.fuchsia: expect( @@ -8608,6 +8617,7 @@ void main() { switch (defaultTargetPlatform) { // These platforms don't move the selection with shift + home/end at all. + case TargetPlatform.ohos: case TargetPlatform.android: case TargetPlatform.fuchsia: expect( @@ -8662,6 +8672,7 @@ void main() { switch (defaultTargetPlatform) { // These platforms don't move the selection with home/end at all still. + case TargetPlatform.ohos: case TargetPlatform.android: case TargetPlatform.fuchsia: expect( @@ -8793,6 +8804,7 @@ void main() { switch (defaultTargetPlatform) { // These platforms don't move the selection with home/end at all. + case TargetPlatform.ohos: case TargetPlatform.android: case TargetPlatform.fuchsia: expect( @@ -8847,6 +8859,7 @@ void main() { switch (defaultTargetPlatform) { // These platforms don't move the selection with home/end at all still. + case TargetPlatform.ohos: case TargetPlatform.android: case TargetPlatform.fuchsia: expect( @@ -12052,6 +12065,7 @@ void main() { expect(controller.selection.isCollapsed, false); switch (defaultTargetPlatform) { // These platforms extend by line. + case TargetPlatform.ohos: case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.linux: @@ -13757,7 +13771,7 @@ void main() { ); }, variant: TargetPlatformVariant.all(), skip: kIsWeb); // [intended] - testWidgets('does not save composing changes (except Android)', (WidgetTester tester) async { + testWidgets('does not save composing changes (except Android, OpenHarmony)', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: EditableText( @@ -13914,9 +13928,9 @@ void main() { ); // On web, these keyboard shortcuts are handled by the browser. - }, variant: TargetPlatformVariant.all(excluding: { TargetPlatform.android }), skip: kIsWeb); // [intended] + }, variant: TargetPlatformVariant.all(excluding: { TargetPlatform.android , TargetPlatform.ohos }), skip: kIsWeb); // [intended] - testWidgets('does save composing changes on Android', (WidgetTester tester) async { + testWidgets('does save composing changes on Android', OpenHarmony', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( home: EditableText( @@ -14138,7 +14152,7 @@ void main() { ); // On web, these keyboard shortcuts are handled by the browser. - }, variant: TargetPlatformVariant.only(TargetPlatform.android), skip: kIsWeb); // [intended] + }, variant: const TargetPlatformVariant({ TargetPlatform.android, TargetPlatform.ohos }), skip: kIsWeb); // [intended] testWidgets('saves right up to composing change even when throttled', (WidgetTester tester) async { await tester.pumpWidget( @@ -14240,6 +14254,7 @@ void main() { switch (defaultTargetPlatform) { // Android includes composing changes. case TargetPlatform.android: + case TargetPlatform.ohos: expect( controller.value, emptyComposingOnAndroid( @@ -14298,6 +14313,7 @@ void main() { await sendRedo(tester); switch (defaultTargetPlatform) { // Android includes composing changes. + case TargetPlatform.ohos: case TargetPlatform.android: expect( controller.value, diff --git a/packages/flutter/test/widgets/focus_manager_test.dart b/packages/flutter/test/widgets/focus_manager_test.dart index c5fd810e7439505f3d4c364d8b75bd0e9ba6ceb6..bdf757f263e1df87531d3fd95b36cb2820b65c87 100644 --- a/packages/flutter/test/widgets/focus_manager_test.dart +++ b/packages/flutter/test/widgets/focus_manager_test.dart @@ -1386,6 +1386,7 @@ void main() { testWidgets('Initial highlight mode guesses correctly.', (WidgetTester tester) async { FocusManager.instance.highlightStrategy = FocusHighlightStrategy.automatic; switch (defaultTargetPlatform) { + case TargetPlatform.ohos: case TargetPlatform.fuchsia: case TargetPlatform.android: case TargetPlatform.iOS: diff --git a/packages/flutter/test/widgets/scroll_behavior_test.dart b/packages/flutter/test/widgets/scroll_behavior_test.dart index 3cca9d2e9f0380bdeb2de952cbdac381e8ccb423..2246ef36d6fe33f298a75f1a626b41911ee4011c 100644 --- a/packages/flutter/test/widgets/scroll_behavior_test.dart +++ b/packages/flutter/test/widgets/scroll_behavior_test.dart @@ -57,6 +57,7 @@ void main() { case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.iOS: + case TargetPlatform.ohos: // Does not throw if we aren't using it. defaultBehavior.buildScrollbar(capturedContext, child, details); case TargetPlatform.linux: diff --git a/packages/flutter/test/widgets/selectable_region_test.dart b/packages/flutter/test/widgets/selectable_region_test.dart index bc8908c17311aec650ee5d87706ae7d6928e2807..85b7f07c9003627cc23c13b3a101247e6c44e334 100644 --- a/packages/flutter/test/widgets/selectable_region_test.dart +++ b/packages/flutter/test/widgets/selectable_region_test.dart @@ -537,6 +537,7 @@ void main() { log.last, isMethodCall('HapticFeedback.vibrate', arguments: 'HapticFeedbackType.selectionClick'), ); + case TargetPlatform.ohos: case TargetPlatform.fuchsia: case TargetPlatform.iOS: case TargetPlatform.linux: @@ -2736,6 +2737,7 @@ void main() { final bool alt; final bool control; switch (defaultTargetPlatform) { + case TargetPlatform.ohos: case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.linux: @@ -2851,6 +2853,7 @@ void main() { final bool alt; final bool meta; switch (defaultTargetPlatform) { + case TargetPlatform.ohos: case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.linux: @@ -2949,6 +2952,7 @@ void main() { case TargetPlatform.fuchsia: case TargetPlatform.linux: case TargetPlatform.windows: + case TargetPlatform.ohos: meta = false; alt = true; case TargetPlatform.iOS: @@ -3253,6 +3257,7 @@ void main() { switch (defaultTargetPlatform) { case TargetPlatform.android: case TargetPlatform.fuchsia: + case TargetPlatform.ohos: expect(regionState.selectionOverlay, isNull); expect(regionState.selectionOverlay?.startHandleLayerLink, isNull); expect(regionState.selectionOverlay?.endHandleLayerLink, isNull); @@ -3310,6 +3315,7 @@ void main() { case TargetPlatform.linux: case TargetPlatform.macOS: case TargetPlatform.windows: + case TargetPlatform.ohos: expect(buttonItems[1].type, ContextMenuButtonType.selectAll); selectAllButton = buttonItems[1]; } @@ -3323,6 +3329,7 @@ void main() { case TargetPlatform.android: case TargetPlatform.iOS: case TargetPlatform.fuchsia: + case TargetPlatform.ohos: expect(regionState.selectionOverlay, isNotNull); expect(regionState.selectionOverlay?.startHandleLayerLink, isNotNull); expect(regionState.selectionOverlay?.endHandleLayerLink, isNotNull); @@ -3449,6 +3456,7 @@ void main() { case TargetPlatform.linux: case TargetPlatform.macOS: case TargetPlatform.windows: + case TargetPlatform.ohos: expect(buttonItems.length, 2); expect(buttonItems[0].type, ContextMenuButtonType.copy); expect(buttonItems[1].type, ContextMenuButtonType.selectAll); diff --git a/packages/flutter/test/widgets/semantics_debugger_test.dart b/packages/flutter/test/widgets/semantics_debugger_test.dart index 3b4e45a88b08f366f70f559ca9d1c6f6e3465689..60a077e3a201c972d779c3087810c209aaad1b03 100644 --- a/packages/flutter/test/widgets/semantics_debugger_test.dart +++ b/packages/flutter/test/widgets/semantics_debugger_test.dart @@ -333,6 +333,7 @@ void main() { case TargetPlatform.windows: case TargetPlatform.android: case TargetPlatform.fuchsia: + case TargetPlatform.ohos: expect(value, equals(0.70)); } }, variant: TargetPlatformVariant.all()); diff --git a/packages/flutter/test/widgets/text_selection_test.dart b/packages/flutter/test/widgets/text_selection_test.dart index 9e21a0133d77c907e463867b966833e3fd15255f..da8ac9208c210de386c8e76b906c2a91076e3c3d 100644 --- a/packages/flutter/test/widgets/text_selection_test.dart +++ b/packages/flutter/test/widgets/text_selection_test.dart @@ -519,7 +519,7 @@ void main() { expect(state.showToolbarCalled, isTrue); expect(renderEditable.selectWordCalled, isTrue); expect(renderEditable.lastCause, SelectionChangedCause.longPress); - }, variant: TargetPlatformVariant.all(excluding: { TargetPlatform.iOS, TargetPlatform.macOS })); + }, variant: TargetPlatformVariant.all(excluding: { TargetPlatform.iOS, TargetPlatform.macOS TargetPlatform.ohos})); testWidgets('TextSelectionGestureDetectorBuilder right click Apple platforms', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/80119 @@ -633,6 +633,7 @@ void main() { case TargetPlatform.iOS: expect(renderEditable.selectWordEdgeCalled, isTrue); expect(renderEditable.lastCause, SelectionChangedCause.tap); + case TargetPlatform.ohos: case TargetPlatform.macOS: case TargetPlatform.android: case TargetPlatform.fuchsia: @@ -641,7 +642,7 @@ void main() { expect(renderEditable.selectPositionAtCalled, isTrue); expect(renderEditable.lastCause, SelectionChangedCause.tap); } - }, variant: TargetPlatformVariant.all()); + }, variant: TargetPlatformVariant.all(excluding: { TargetPlatform.ohos })); testWidgets('test TextSelectionGestureDetectorBuilder toggles toolbar on single tap on previous selection iOS', (WidgetTester tester) async { await pumpTextSelectionGestureDetectorBuilder(tester); @@ -664,6 +665,7 @@ void main() { case TargetPlatform.iOS: expect(renderEditable.selectWordEdgeCalled, isFalse); expect(state.toggleToolbarCalled, isTrue); + case TargetPlatform.ohos: case TargetPlatform.macOS: case TargetPlatform.android: case TargetPlatform.fuchsia: @@ -672,7 +674,7 @@ void main() { expect(renderEditable.selectPositionAtCalled, isTrue); expect(renderEditable.lastCause, SelectionChangedCause.tap); } - }, variant: TargetPlatformVariant.all()); + }, variant: TargetPlatformVariant.all(excluding: { TargetPlatform.ohos })); testWidgets('test TextSelectionGestureDetectorBuilder shows spell check toolbar on single tap on Android', (WidgetTester tester) async { diff --git a/packages/flutter/test/widgets/text_selection_toolbar_utils.dart b/packages/flutter/test/widgets/text_selection_toolbar_utils.dart index 23a9830bc5f426d072d45366a874c501c62fc916..db91c7db0f3b58e1ff5d7a4e6b1d3be1a1e41c0f 100644 --- a/packages/flutter/test/widgets/text_selection_toolbar_utils.dart +++ b/packages/flutter/test/widgets/text_selection_toolbar_utils.dart @@ -40,6 +40,7 @@ void expectCupertinoToolbarForPartialSelection() { switch (defaultTargetPlatform) { case TargetPlatform.android: + case TargetPlatform.ohos: expect(find.byType(CupertinoButton), findsNWidgets(5)); expect(find.text('Cut'), findsOneWidget); expect(find.text('Copy'), findsOneWidget); @@ -80,6 +81,7 @@ void expectCupertinoToolbarForFullSelection() { switch (defaultTargetPlatform) { case TargetPlatform.android: + case TargetPlatform.ohos: expect(find.byType(CupertinoButton), findsNWidgets(4)); expect(find.text('Cut'), findsOneWidget); expect(find.text('Copy'), findsOneWidget); @@ -113,6 +115,7 @@ void expectCupertinoToolbarForCollapsedSelection() { switch (defaultTargetPlatform) { case TargetPlatform.android: + case TargetPlatform.ohos: expect(find.byType(CupertinoButton), findsNWidgets(4)); expect(find.text('Cut'), findsOneWidget); expect(find.text('Copy'), findsOneWidget); @@ -145,6 +148,7 @@ void expectMaterialToolbarForPartialSelection() { switch (defaultTargetPlatform) { case TargetPlatform.android: + case TargetPlatform.ohos: expect(find.byType(TextButton), findsNWidgets(5)); expect(find.text('Cut'), findsOneWidget); expect(find.text('Copy'), findsOneWidget); @@ -174,6 +178,7 @@ void expectMaterialToolbarForFullSelection() { switch (defaultTargetPlatform) { case TargetPlatform.android: + case TargetPlatform.ohos: expect(find.byType(TextButton), findsNWidgets(4)); expect(find.text('Cut'), findsOneWidget); expect(find.text('Copy'), findsOneWidget); diff --git a/packages/flutter/test/widgets/two_dimensional_scroll_view_test.dart b/packages/flutter/test/widgets/two_dimensional_scroll_view_test.dart index 0b29d547f60f37ab7984d6a6a7f445ab24b8efc7..e118754157513e3b892b607967c6f65cdd21ee49 100644 --- a/packages/flutter/test/widgets/two_dimensional_scroll_view_test.dart +++ b/packages/flutter/test/widgets/two_dimensional_scroll_view_test.dart @@ -164,6 +164,7 @@ void main() { case TargetPlatform.linux: case TargetPlatform.macOS: case TargetPlatform.windows: + case TargetPlatform.ohos: expect(controller.hasClients, isFalse); } @@ -184,6 +185,7 @@ void main() { case TargetPlatform.linux: case TargetPlatform.macOS: case TargetPlatform.windows: + case TargetPlatform.ohos: expect(controller.hasClients, isTrue); expect(controller.position.axis, Axis.horizontal); } @@ -205,6 +207,7 @@ void main() { case TargetPlatform.linux: case TargetPlatform.macOS: case TargetPlatform.windows: + case TargetPlatform.ohos: expect(controller.hasClients, isFalse); } @@ -220,6 +223,7 @@ void main() { case TargetPlatform.android: case TargetPlatform.fuchsia: case TargetPlatform.iOS: + case TargetPlatform.ohos: expect(controller.hasClients, isTrue); expect(controller.position.axis, Axis.vertical); case TargetPlatform.linux: @@ -244,6 +248,7 @@ void main() { case TargetPlatform.linux: case TargetPlatform.macOS: case TargetPlatform.windows: + case TargetPlatform.ohos: expect(controller.hasClients, isTrue); expect(controller.position.axis, Axis.vertical); } @@ -264,6 +269,7 @@ void main() { case TargetPlatform.linux: case TargetPlatform.macOS: case TargetPlatform.windows: + case TargetPlatform.ohos: expect(controller.hasClients, isFalse); } diff --git a/packages/flutter/test/widgets/two_dimensional_viewport_test.dart b/packages/flutter/test/widgets/two_dimensional_viewport_test.dart index a3caa62125016b1ad7e722acd6a5b64aa284c420..a44bf49ce99dcdb610c628ec30d192d94b787279 100644 --- a/packages/flutter/test/widgets/two_dimensional_viewport_test.dart +++ b/packages/flutter/test/widgets/two_dimensional_viewport_test.dart @@ -36,6 +36,7 @@ void main() { switch (defaultTargetPlatform) { case TargetPlatform.android: case TargetPlatform.fuchsia: + case TargetPlatform.ohos: expect(find.byType(RepaintBoundary), findsNWidgets(7)); case TargetPlatform.iOS: case TargetPlatform.linux: @@ -67,6 +68,7 @@ void main() { switch (defaultTargetPlatform) { case TargetPlatform.android: case TargetPlatform.fuchsia: + case TargetPlatform.ohos: expect(find.byType(RepaintBoundary), findsNWidgets(6)); case TargetPlatform.iOS: case TargetPlatform.linux: @@ -551,6 +553,7 @@ void main() { case TargetPlatform.linux: case TargetPlatform.macOS: case TargetPlatform.windows: + case TargetPlatform.ohos: expectModalRoute(); expect(find.byType(RepaintBoundary), findsNWidgets(3)); } @@ -578,6 +581,7 @@ void main() { case TargetPlatform.linux: case TargetPlatform.macOS: case TargetPlatform.windows: + case TargetPlatform.ohos: expectModalRoute(); expect(find.byType(RepaintBoundary), findsNWidgets(2)); } diff --git a/packages/flutter/test_reporter.json b/packages/flutter/test_reporter.json new file mode 100644 index 0000000000000000000000000000000000000000..1696c55de0e9a578f1aba589fbf73964937dae07 Binary files /dev/null and b/packages/flutter/test_reporter.json differ diff --git a/packages/flutter_test/lib/src/event_simulation.dart b/packages/flutter_test/lib/src/event_simulation.dart index 47c6004820489b3945cc0dbe53963a4ea16a5355..5633cda12f7ac8ba45ff79ef615943896590a9aa 100644 --- a/packages/flutter_test/lib/src/event_simulation.dart +++ b/packages/flutter_test/lib/src/event_simulation.dart @@ -65,6 +65,7 @@ abstract final class KeyEventSimulator { case 'web': case 'ios': case 'windows': + case 'ohos': return true; } return false; @@ -74,6 +75,9 @@ abstract final class KeyEventSimulator { assert(_osIsSupported(platform), 'Platform $platform not supported for key simulation'); late Map map; switch (platform) { + case 'ohos': + map = kOhosToPhysicalKey; + break; case 'android': map = kAndroidToPhysicalKey; case 'fuchsia': @@ -110,6 +114,9 @@ abstract final class KeyEventSimulator { } else { late Map map; switch (platform) { + case 'ohos': + map = kOhosToLogicalKey; + break; case 'android': map = kAndroidToLogicalKey; case 'fuchsia': @@ -196,6 +203,9 @@ abstract final class KeyEventSimulator { map = kWebToPhysicalKey; } else { switch (platform) { + case 'ohos': + map = kOhosToPhysicalKey; + break; case 'android': map = kAndroidToPhysicalKey; case 'fuchsia': @@ -262,6 +272,15 @@ abstract final class KeyEventSimulator { final int scanCode = _getScanCode(physicalKey, platform); switch (platform) { + case 'ohos': + result['keyCode'] = keyCode; + result['deviceId'] = 1; + if (resultCharacter.isNotEmpty) { + result['character'] = resultCharacter; + }else { + result['character'] = ''; + } + break; case 'android': result['keyCode'] = keyCode; if (resultCharacter.isNotEmpty) { diff --git a/packages/flutter_test/lib/src/widget_tester.dart b/packages/flutter_test/lib/src/widget_tester.dart index 032c1d894c87d7a02ef52d9944aa8a0ec8f5eff0..15a8d2d5915a4ec6b73939ff65ef37cc07660f35 100644 --- a/packages/flutter_test/lib/src/widget_tester.dart +++ b/packages/flutter_test/lib/src/widget_tester.dart @@ -287,6 +287,7 @@ class TargetPlatformVariant extends TestVariant { TargetPlatform.android, TargetPlatform.iOS, TargetPlatform.fuchsia, + TargetPlatform.ohos, }; /// Creates a [TargetPlatformVariant] that tests only the given value of diff --git a/packages/flutter_test/test/event_simulation_test.dart b/packages/flutter_test/test/event_simulation_test.dart index 1d3a356e99245b6a6ceff897031113b9c3cf10c1..48097bedd155646f1b35e14671fa435692aa4073 100644 --- a/packages/flutter_test/test/event_simulation_test.dart +++ b/packages/flutter_test/test/event_simulation_test.dart @@ -386,6 +386,7 @@ void main() { TargetPlatform.linux => RawKeyEventDataLinux, TargetPlatform.macOS => RawKeyEventDataMacOs, TargetPlatform.windows => RawKeyEventDataWindows, + TargetPlatform.ohos => RawKeyEventDataOhos, }; expect(events.first.data.runtimeType, expectedType); }, variant: TargetPlatformVariant.all()); diff --git a/packages/flutter_tools/bin/podhelper.rb b/packages/flutter_tools/bin/podhelper.rb index 3d64737ae691a38f5904885688e52b8049ed3006..00d7f06f15742af16ccf0497463bfeba1c645a13 100644 --- a/packages/flutter_tools/bin/podhelper.rb +++ b/packages/flutter_tools/bin/podhelper.rb @@ -51,6 +51,9 @@ def flutter_additional_ios_build_settings(target) # ARC code targeting iOS 8 does not build on Xcode 14.3. force_to_arc_supported_min = target.deployment_target[/\d+/].to_i < 9 + # ARC code targeting iOS 8 does not build on Xcode 14.3. + force_to_arc_supported_min = target.deployment_target[/\d+/].to_i < 9 + # This podhelper script is at $FLUTTER_ROOT/packages/flutter_tools/bin. # Add search paths from $FLUTTER_ROOT/bin/cache/artifacts/engine. artifacts_dir = File.join('..', '..', '..', '..', 'bin', 'cache', 'artifacts', 'engine') diff --git a/packages/flutter_tools/lib/executable.dart b/packages/flutter_tools/lib/executable.dart index 26907596cf58bfec127507294a8865f8f8a0d123..e917236b72420107922f737dd01fd5ad5d45251a 100644 --- a/packages/flutter_tools/lib/executable.dart +++ b/packages/flutter_tools/lib/executable.dart @@ -232,7 +232,7 @@ List generateCommands({ ), MakeHostAppEditableCommand(), PackagesCommand(), - PrecacheCommand( + PrecacheCommand( verboseHelp: verboseHelp, cache: globals.cache, logger: globals.logger, diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart index 86fdaace7025974f19e3d0a4d8aa5b039b198e2d..6145d9b475796bb6bffb46390f91ac8114fc97c8 100644 --- a/packages/flutter_tools/lib/src/android/android_device.dart +++ b/packages/flutter_tools/lib/src/android/android_device.dart @@ -235,6 +235,10 @@ class AndroidDevice extends Device { case TargetPlatform.web_javascript: case TargetPlatform.windows_x64: case TargetPlatform.windows_arm64: + case TargetPlatform.ohos: + case TargetPlatform.ohos_arm: + case TargetPlatform.ohos_arm64: + case TargetPlatform.ohos_x64: throw UnsupportedError('Invalid target platform for Android'); } } @@ -573,6 +577,10 @@ class AndroidDevice extends Device { case TargetPlatform.web_javascript: case TargetPlatform.windows_arm64: case TargetPlatform.windows_x64: + case TargetPlatform.ohos: + case TargetPlatform.ohos_arm: + case TargetPlatform.ohos_arm64: + case TargetPlatform.ohos_x64: _logger.printError('Android platforms are only supported.'); return LaunchResult.failed(); } diff --git a/packages/flutter_tools/lib/src/artifacts.dart b/packages/flutter_tools/lib/src/artifacts.dart index ba3703b86ef6005f6ee11d62437f41cccd3c88ba..275a1312c2c22425f178a3860ed0b22a101819bd 100644 --- a/packages/flutter_tools/lib/src/artifacts.dart +++ b/packages/flutter_tools/lib/src/artifacts.dart @@ -15,6 +15,7 @@ import 'base/utils.dart'; import 'build_info.dart'; import 'cache.dart'; import 'globals.dart' as globals; +import 'reporting/reporting.dart'; enum Artifact { /// The tool which compiles a dart kernel file into native code. @@ -66,6 +67,8 @@ enum Artifact { fontSubset, constFinder, + /// the flutter engine runtime + flutterEngineHar, /// The location of file generators. flutterToolsFileGenerators, @@ -153,6 +156,8 @@ TargetPlatform? _mapTargetPlatform(TargetPlatform? targetPlatform) { switch (targetPlatform) { case TargetPlatform.android: return TargetPlatform.android_arm64; + case TargetPlatform.ohos: + return TargetPlatform.ohos_arm64; case TargetPlatform.ios: case TargetPlatform.darwin: case TargetPlatform.linux_x64: @@ -167,6 +172,9 @@ TargetPlatform? _mapTargetPlatform(TargetPlatform? targetPlatform) { case TargetPlatform.android_arm64: case TargetPlatform.android_x64: case TargetPlatform.android_x86: + case TargetPlatform.ohos_arm: + case TargetPlatform.ohos_arm64: + case TargetPlatform.ohos_x64: case null: return targetPlatform; } @@ -232,6 +240,10 @@ String? _artifactToFileName(Artifact artifact, Platform hostPlatform, [ BuildMod return 'font-subset$exe'; case Artifact.constFinder: return 'const_finder.dart.snapshot'; + case Artifact.flutterEngineHar: + return 'flutter.har'; + case Artifact.engineDartBinary: + return 'dart$exe'; case Artifact.flutterToolsFileGenerators: return ''; case Artifact.flutterPreviewDevice: @@ -582,6 +594,11 @@ class CachedArtifacts implements Artifacts { case TargetPlatform.fuchsia_arm64: case TargetPlatform.fuchsia_x64: return _getFuchsiaArtifactPath(artifact, platform!, mode!); + case TargetPlatform.ohos: + case TargetPlatform.ohos_arm: + case TargetPlatform.ohos_arm64: + case TargetPlatform.ohos_x64: + return _getOhosArtifactPath(artifact, platform ?? _currentHostPlatform(_platform, _operatingSystemUtils), mode!); case TargetPlatform.tester: case TargetPlatform.web_javascript: case null: @@ -613,7 +630,7 @@ class CachedArtifacts implements Artifacts { switch (artifact) { case Artifact.genSnapshot: assert(mode != BuildMode.debug, 'Artifact $artifact only available in non-debug mode.'); - final String hostPlatform = getNameForHostPlatform(getCurrentHostPlatform()); + final String hostPlatform = getNameForHostPlatform(globals.platform.isMacOS ? HostPlatform.darwin_x64 : getCurrentHostPlatform()); return _fileSystem.path.join(engineDir, hostPlatform, _artifactToFileName(artifact, _platform)); case Artifact.engineDartSdkPath: case Artifact.engineDartBinary: @@ -641,6 +658,7 @@ class CachedArtifacts implements Artifacts { case Artifact.vmSnapshotData: case Artifact.windowsCppClientWrapper: case Artifact.windowsDesktopPath: + case Artifact.flutterEngineHar: case Artifact.flutterToolsFileGenerators: case Artifact.flutterPreviewDevice: return _getHostArtifactPath(artifact, platform, mode); @@ -683,6 +701,7 @@ class CachedArtifacts implements Artifacts { case Artifact.windowsDesktopPath: case Artifact.flutterToolsFileGenerators: case Artifact.flutterPreviewDevice: + case Artifact.flutterEngineHar: return _getHostArtifactPath(artifact, platform, mode); } } @@ -735,10 +754,15 @@ class CachedArtifacts implements Artifacts { case Artifact.windowsDesktopPath: case Artifact.flutterToolsFileGenerators: case Artifact.flutterPreviewDevice: + case Artifact.flutterEngineHar: return _getHostArtifactPath(artifact, platform, mode); } } + String _getOhosArtifactPath(Artifact artifact, TargetPlatform platform, BuildMode mode) { + return _getHostArtifactPath(artifact, platform, mode); + } + String _getFlutterPatchedSdkPath(BuildMode? mode) { final String engineArtifactsPath = _cache.getArtifactDirectory('engine').path; return _fileSystem.path.join(engineArtifactsPath, 'common', @@ -818,6 +842,10 @@ class CachedArtifacts implements Artifacts { case Artifact.fuchsiaFlutterRunner: case Artifact.fuchsiaKernelCompiler: throw StateError('Artifact $artifact not available for platform $platform.'); + case Artifact.flutterEngineHar: + return _fileSystem.path.join( + _getEngineArtifactsPath(platform, mode)!, + _artifactToFileName(artifact, _platform, mode)); case Artifact.flutterToolsFileGenerators: return _getFileGeneratorsPath(); case Artifact.flutterPreviewDevice: @@ -854,12 +882,22 @@ class CachedArtifacts implements Artifacts { case TargetPlatform.android_arm64: case TargetPlatform.android_x64: case TargetPlatform.android_x86: + case TargetPlatform.ohos: + case TargetPlatform.ohos_arm: + case TargetPlatform.ohos_arm64: + case TargetPlatform.ohos_x64: assert(mode != null, 'Need to specify a build mode for platform $platform.'); final String suffix = mode != BuildMode.debug ? '-${snakeCase(mode!.cliName, '-')}' : ''; return _fileSystem.path.join(engineDir, platformName + suffix); case TargetPlatform.android: assert(false, 'cannot use TargetPlatform.android to look up artifacts'); return null; + case TargetPlatform.ohos: + case TargetPlatform.ohos_arm: + case TargetPlatform.ohos_arm64: + case TargetPlatform.ohos_x64: + assert(false, 'cannot use TargetPlatform.ohos_(xx) to look up artifacts'); + return null; } } @@ -980,10 +1018,22 @@ class CachedLocalEngineArtifacts implements Artifacts { final OperatingSystemUtils _operatingSystemUtils; final Artifacts _backupCache; + /// this list hostArtifact will execute by the backup engine ,because local engine arch not match . + final List hostArtifactList = [ + HostArtifact.impellerc, + ]; + + bool isOhosLocalEngine(){ + return _fileSystem.path.basename(localEngineInfo.targetOutPath).contains('ohos'); + } + @override FileSystemEntity getHostArtifact( HostArtifact artifact, ) { + if(isOhosLocalEngine() && hostArtifactList.contains(artifact)){ + return _backupCache.getHostArtifact(artifact); + } switch (artifact) { case HostArtifact.flutterWebSdk: final String path = _getFlutterWebSdkPath(); @@ -1090,7 +1140,7 @@ class CachedLocalEngineArtifacts implements Artifacts { final String? artifactFileName = isDirectoryArtifact ? null : _artifactToFileName(artifact, _platform, mode); switch (artifact) { case Artifact.genSnapshot: - return _genSnapshotPath(); + return _genSnapshotPath(platform); case Artifact.flutterTester: return _flutterTesterPath(platform!); case Artifact.isolateSnapshotData: @@ -1099,6 +1149,7 @@ class CachedLocalEngineArtifacts implements Artifacts { case Artifact.icuData: case Artifact.flutterXcframework: case Artifact.flutterMacOSXcframework: + case Artifact.flutterEngineHar: return _fileSystem.path.join(localEngineInfo.targetOutPath, artifactFileName); case Artifact.platformKernelDill: if (platform == TargetPlatform.fuchsia_x64 || platform == TargetPlatform.fuchsia_arm64) { @@ -1134,6 +1185,9 @@ class CachedLocalEngineArtifacts implements Artifacts { final String productOrNo = mode.isRelease ? '_product' : ''; return _fileSystem.path.join(localEngineInfo.targetOutPath, 'flutter$jitOrAot${productOrNo}_runner-0.far'); case Artifact.fontSubset: + if (isOhosLocalEngine()) { + return _backupCache.getArtifactPath(artifact); + } return _fileSystem.path.join(_hostEngineOutPath, artifactFileName); case Artifact.constFinder: return _fileSystem.path.join(_hostEngineOutPath, 'gen', artifactFileName); @@ -1216,6 +1270,10 @@ class CachedLocalEngineArtifacts implements Artifacts { case TargetPlatform.fuchsia_x64: case TargetPlatform.web_javascript: case TargetPlatform.tester: + case TargetPlatform.ohos: + case TargetPlatform.ohos_arm: + case TargetPlatform.ohos_arm64: + case TargetPlatform.ohos_x64: throwToolExit('Unsupported host platform: $hostPlatform'); } } @@ -1224,8 +1282,14 @@ class CachedLocalEngineArtifacts implements Artifacts { return _fileSystem.path.join(localEngineInfo.targetOutPath, 'flutter_web_sdk'); } - String _genSnapshotPath() { - const List clangDirs = ['.', 'clang_x64', 'clang_x86', 'clang_i386', 'clang_arm64']; + String _genSnapshotPath(TargetPlatform? platform) { + late List clangDirs; + if (isOhosPlatform(platform)) { + // on ohos platform, clang_x64 has compatibility first + clangDirs = ['clang_x64', 'clang_arm64', '.', 'clang_x86', 'clang_i386']; + } else { + clangDirs = ['.', 'clang_x64', 'clang_x86', 'clang_i386', 'clang_arm64']; + } final String genSnapshotName = _artifactToFileName(Artifact.genSnapshot, _platform)!; for (final String clangDir in clangDirs) { final String genSnapshotPath = _fileSystem.path.join(localEngineInfo.targetOutPath, clangDir, genSnapshotName); @@ -1237,14 +1301,20 @@ class CachedLocalEngineArtifacts implements Artifacts { } String _flutterTesterPath(TargetPlatform platform) { - if (_platform.isLinux) { - return _fileSystem.path.join(localEngineInfo.targetOutPath, _artifactToFileName(Artifact.flutterTester, _platform)); - } else if (_platform.isMacOS) { - return _fileSystem.path.join(localEngineInfo.targetOutPath, 'flutter_tester'); - } else if (_platform.isWindows) { - return _fileSystem.path.join(localEngineInfo.targetOutPath, 'flutter_tester.exe'); + late List clangDirs; + clangDirs = ['clang_x64', 'clang_arm64', '.', 'clang_x86', 'clang_i386']; + final String testerName = _artifactToFileName(Artifact.flutterTester, _platform)!; + if (_platform.isLinux || _platform.isMacOS || _platform.isWindows) { + for (final String clangDir in clangDirs) { + final String testerPath = _fileSystem.path.join(localEngineInfo.targetOutPath, clangDir, testerName); + if (_processManager.canRun(testerPath)) { + return testerPath; + } + } + } else { + throw Exception('Unsupported platform $platform.'); } - throw Exception('Unsupported platform $platform.'); + throw Exception('Unable to find $testerName'); } @override @@ -1311,6 +1381,7 @@ class CachedLocalWebSdkArtifacts implements Artifacts { case Artifact.constFinder: case Artifact.flutterToolsFileGenerators: case Artifact.flutterPreviewDevice: + case Artifact.flutterEngineHar: break; } } @@ -1441,6 +1512,10 @@ class CachedLocalWebSdkArtifacts implements Artifacts { case TargetPlatform.fuchsia_x64: case TargetPlatform.web_javascript: case TargetPlatform.tester: + case TargetPlatform.ohos: + case TargetPlatform.ohos_arm: + case TargetPlatform.ohos_arm64: + case TargetPlatform.ohos_x64: throwToolExit('Unsupported host platform: $hostPlatform'); } } diff --git a/packages/flutter_tools/lib/src/asset.dart b/packages/flutter_tools/lib/src/asset.dart index 5fa7e7d0e01b806a7a8c498dfc21290cc6cb1830..68c22742153bb3fa6a0f8000287d5ad2c743432b 100644 --- a/packages/flutter_tools/lib/src/asset.dart +++ b/packages/flutter_tools/lib/src/asset.dart @@ -477,7 +477,8 @@ class ManifestAssetBundle implements AssetBundle { // For all platforms, include the shaders unconditionally. They are // small, and whether they're used is determined only by the app source // code and not by the Flutter manifest. - ..._getMaterialShaders(), + if (targetPlatform != TargetPlatform.web_javascript) + ..._getMaterialShaders(), ]; for (final _Asset asset in materialAssets) { final File assetFile = asset.lookupAssetFile(_fileSystem); diff --git a/packages/flutter_tools/lib/src/base/build.dart b/packages/flutter_tools/lib/src/base/build.dart index 60be3c0e1d7dafebdc18ce81a7205637f03b947f..2cb5cc4a04776eacfc343a4d82352cca15d917f8 100644 --- a/packages/flutter_tools/lib/src/base/build.dart +++ b/packages/flutter_tools/lib/src/base/build.dart @@ -348,6 +348,9 @@ class AOTSnapshotter { TargetPlatform.linux_arm64, TargetPlatform.windows_x64, TargetPlatform.windows_arm64, + TargetPlatform.ohos_arm64, + TargetPlatform.ohos_arm, + TargetPlatform.ohos_x64, ].contains(platform); } } diff --git a/packages/flutter_tools/lib/src/base/platform.dart b/packages/flutter_tools/lib/src/base/platform.dart index 45da89ad4c67e8c0a212d56a157f5c8f397a1e37..4a52b50a7d9d44c3f2595835f2b9dd08fd322133 100644 --- a/packages/flutter_tools/lib/src/base/platform.dart +++ b/packages/flutter_tools/lib/src/base/platform.dart @@ -46,6 +46,9 @@ abstract class Platform { /// True if the operating system is Fuchsia. bool get isFuchsia => operatingSystem == 'fuchsia'; + /// True if the operating system is Ohos. + bool get isOhos => operatingSystem == 'ohos'; + /// The environment for this process. /// /// The returned environment is an unmodifiable map whose content is diff --git a/packages/flutter_tools/lib/src/base/user_messages.dart b/packages/flutter_tools/lib/src/base/user_messages.dart index 26a8278ce75adc62ab349482d34f80b1ae1750a6..bad9281874103dab1598aef5c208d72b0d34bc79 100644 --- a/packages/flutter_tools/lib/src/base/user_messages.dart +++ b/packages/flutter_tools/lib/src/base/user_messages.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import '../ohos/ohos_sdk.dart'; import 'platform.dart'; /// Class containing some message strings that can be produced by Flutter tools. @@ -161,6 +162,58 @@ class UserMessages { 'Android Studio not found; download from https://developer.android.com/studio/index.html\n' '(or visit ${androidSdkInstallUrl(platform)} for detailed instructions).'; + // Messages used in NoOhosSdkValidator + String ohosSdkMissing(String location) => + 'OHOS_SDK_HOME = $location\n' + 'but OpenHarmony Sdk not found at this location'; + String hosSdkMissing(String location) => + 'HOS_SDK_HOME = $location\n' + 'but nHarmonyOS Sdk not found at this location'; + String ohosSdkInstallation() => + 'OpenHarmony Sdk not found; \n' + 'please do that, first: download from https://developer.harmonyos.com/cn/develop/deveco-studio#download_cli ;\n' + 'second: follow this document: https://developer.harmonyos.com/cn/docs/documentation/doc-guides-V3/ide-command-line-ohsdkmgr-0000001545647965-V3 to install OpenHarmony sdk with ohsdkmgr; \n' + 'If the Ohos SDK has been installed to a custom location, please use\n' + '`flutter config --ohos-sdk` to update to that location.\n'; + String hosSdkInstallation() => + 'HarmonyOS Sdk not found; \n' + 'please do that, first: download from https://developer.harmonyos.com/cn/develop/deveco-studio#download_cli ;\n' + 'second: follow this document: https://developer.harmonyos.com/cn/docs/documentation/doc-guides-V3/ide-command-line-ohsdkmgr-0000001545647965-V3 to install OpenHarmony sdk with ohsdkmgr; \n' + 'If the Ohos SDK has been installed to a custom location, please use\n' + '`flutter config --ohos-sdk` to update to that location.\n'; + + String ohosSdkVersion(HarmonySdk ohosSdk) => + 'OpenHarmony Sdk at ${ohosSdk.sdkPath}, available api versions has ${ohosSdk.apiAvailable}'; + String hosSdkVersion(HarmonySdk hosSdk) => + 'HarmonyOS Sdk at ${hosSdk.sdkPath}, available api versions has ${hosSdk.apiAvailable}'; + + String hdcMissing() => + 'hdc is missing ,please download from https://developer.harmonyos.com/cn/develop/deveco-studio#download_cli;\n' + 'and set environment HDC_HOME also set to PATH'; + String hdcVersion(String ohpmVersion) => + 'hdc version $ohpmVersion'; + + String ohpmMissing() => + 'Ohpm is missing, please configure "ohpm" to the environment variable PATH.'; + String ohpmVersion(String version) => 'Ohpm version $version'; + + String nodeMissing() => + 'Node is missing, please configure "node" to the environment variable PATH.'; + String nodeVersion(String version) => 'Node version $version'; + + String hvigorwMissing() => + 'Hvigorw is missing, please configure "hvigorw" to the environment variable PATH.'; + String hvigorwPath(String path) => 'Hvigorw binary at $path'; + + String signToolMissing() => + 'signTool is missing ,please download from https://gitee.com/openharmony/developtools_hapsigner;\n' + 'and follow ReadMe to generate hap-sign-tool.jar, set environment SIGN_TOOL_HOME = /developtools_hapsigner/autosign such as /home/xxx/sdk/developtools_hapsigner/autosign\n' + 'If the signTool has been installed to a custom location, please use\n' + '`flutter config --signTool-home` to update to that location.\n'; + String signToolVersion(String signToolHome) => + 'signTool location:$signToolHome'; + + // Messages used in XcodeValidator String xcodeLocation(String location) => 'Xcode at $location'; diff --git a/packages/flutter_tools/lib/src/build_info.dart b/packages/flutter_tools/lib/src/build_info.dart index 5d3c0d56f93680b6bac3a9e5ff9f44dea0f8302a..b5e72567af1708fc8c41cb5cca73203eadec9c10 100644 --- a/packages/flutter_tools/lib/src/build_info.dart +++ b/packages/flutter_tools/lib/src/build_info.dart @@ -355,6 +355,29 @@ class AndroidBuildInfo { final bool fastStart; } +/// Information about an Ohos build to be performed or used. +class OhosBuildInfo { + const OhosBuildInfo( + this.buildInfo, { + this.targetArchs = const [ + OhosArch.armeabi_v7a, + OhosArch.arm64_v8a, + OhosArch.x86_64, + ], + this.enableImpellerFlag = null, + }); + + // The build info containing the mode and flavor. + final BuildInfo buildInfo; + + /// The target platforms for the build. + final Iterable targetArchs; + + // enable impeller option, default is true + final bool? enableImpellerFlag; +} + + /// A summary of the compilation strategy used for Dart. enum BuildMode { /// Built in JIT mode with no optimizations, enabled asserts, and a VM service. @@ -452,6 +475,24 @@ String? validatedBuildNumberForPlatform(TargetPlatform targetPlatform, String? b } return tmpBuildNumberStr; } + if (targetPlatform == TargetPlatform.ohos || + targetPlatform == TargetPlatform.ohos_arm || + targetPlatform == TargetPlatform.ohos_arm64 || + targetPlatform == TargetPlatform.ohos_x64) { + final RegExp disallowed = RegExp(r'[^\d]'); + String tmpBuildNumberStr = buildNumber.replaceAll(disallowed, ''); + int tmpBuildNumberInt = int.tryParse(tmpBuildNumberStr) ?? 0; + if (tmpBuildNumberInt < 1) { + tmpBuildNumberInt = 1; + } + tmpBuildNumberStr = tmpBuildNumberInt.toString(); + if (tmpBuildNumberStr != buildNumber) { + logger.printTrace( + 'Invalid build-number: $buildNumber for Ohos, overridden by $tmpBuildNumberStr.\n' + 'See versionCode at https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/app-configuration-file-V5'); + } + return tmpBuildNumberStr; + } return buildNumber; } @@ -489,6 +530,29 @@ String? validatedBuildNameForPlatform(TargetPlatform targetPlatform, String? bui // See versionName at https://developer.android.com/studio/publish/versioning return buildName; } + if (targetPlatform == TargetPlatform.ohos || + targetPlatform == TargetPlatform.ohos_arm || + targetPlatform == TargetPlatform.ohos_arm64 || + targetPlatform == TargetPlatform.ohos_x64) { + final RegExp disallowed = RegExp(r'[^\d\.]'); + String tmpBuildName = buildName.replaceAll(disallowed, ''); + if (tmpBuildName.isEmpty) { + return null; + } + final List segments = tmpBuildName + .split('.') + .where((String segment) => segment.isNotEmpty) + .toList(); + while (segments.length < 3) { + segments.add('0'); + } + tmpBuildName = segments.join('.'); + if (tmpBuildName != buildName) { + logger.printTrace('Invalid build-name: $buildName for Ohos, overridden by $tmpBuildName.\n' + 'See versionName at https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/app-configuration-file-V5'); + } + return tmpBuildName; + } return buildName; } @@ -525,7 +589,12 @@ enum TargetPlatform { android_arm, android_arm64, android_x64, - android_x86; + android_x86, + //ohos platform + ohos, + ohos_arm, + ohos_arm64, + ohos_x64; String get fuchsiaArchForTargetPlatform { switch (this) { @@ -546,6 +615,10 @@ enum TargetPlatform { case TargetPlatform.web_javascript: case TargetPlatform.windows_x64: case TargetPlatform.windows_arm64: + case TargetPlatform.ohos: + case TargetPlatform.ohos_arm: + case TargetPlatform.ohos_arm64: + case TargetPlatform.ohos_x64: throw UnsupportedError('Unexpected Fuchsia platform $this'); } } @@ -569,6 +642,10 @@ enum TargetPlatform { case TargetPlatform.ios: case TargetPlatform.tester: case TargetPlatform.web_javascript: + case TargetPlatform.ohos: + case TargetPlatform.ohos_arm: + case TargetPlatform.ohos_arm64: + case TargetPlatform.ohos_x64: throw UnsupportedError('Unexpected target platform $this'); } } @@ -623,6 +700,23 @@ enum AndroidArch { } } +enum OhosArch { + armeabi_v7a, + arm64_v8a, + x86_64, +} + + +bool isOhosPlatform(TargetPlatform? targetPlatform) { + if (targetPlatform == TargetPlatform.ohos || + targetPlatform == TargetPlatform.ohos_arm || + targetPlatform == TargetPlatform.ohos_arm64 || + targetPlatform == TargetPlatform.ohos_x64) { + return true; + } + return false; +} + /// The default set of iOS device architectures to build for. List defaultIOSArchsForEnvironment( EnvironmentType environmentType, @@ -728,6 +822,14 @@ String getNameForTargetPlatform(TargetPlatform platform, {DarwinArch? darwinArch return 'web-javascript'; case TargetPlatform.android: return 'android'; + case TargetPlatform.ohos: + return 'ohos'; + case TargetPlatform.ohos_arm: + return 'ohos-arm'; + case TargetPlatform.ohos_arm64: + return 'ohos-arm64'; + case TargetPlatform.ohos_x64: + return 'ohos-x86'; } } @@ -757,7 +859,7 @@ TargetPlatform getTargetPlatformForName(String platform) { return TargetPlatform.darwin; case 'linux-x64': return TargetPlatform.linux_x64; - case 'linux-arm64': + case 'linux-arm64': return TargetPlatform.linux_arm64; case 'windows-x64': return TargetPlatform.windows_x64; @@ -765,6 +867,14 @@ TargetPlatform getTargetPlatformForName(String platform) { return TargetPlatform.windows_arm64; case 'web-javascript': return TargetPlatform.web_javascript; + case 'ohos': + return TargetPlatform.ohos; + case 'ohos-arm': + return TargetPlatform.ohos_arm; + case 'ohos-arm64': + return TargetPlatform.ohos_arm64; + case 'ohos-x64': + return TargetPlatform.ohos_x64; case 'flutter-tester': return TargetPlatform.tester; } @@ -785,11 +895,42 @@ AndroidArch getAndroidArchForName(String platform) { throw Exception('Unsupported Android arch name "$platform"'); } -HostPlatform getCurrentHostPlatform() { - if (globals.platform.isMacOS) { - return HostPlatform.darwin_x64; +OhosArch getOhosArchForName(String platform) { + switch (platform) { + case 'ohos-arm': + return OhosArch.armeabi_v7a; + case 'ohos-arm64': + return OhosArch.arm64_v8a; + case 'ohos-x86': + return OhosArch.x86_64; + } + throw Exception('Unsupported Ohos arch name "$platform"'); +} + +String getNameForOhosArch(OhosArch arch) { + switch (arch) { + case OhosArch.armeabi_v7a: + return 'armeabi-v7a'; + case OhosArch.arm64_v8a: + return 'arm64-v8a'; + case OhosArch.x86_64: + return 'x86_64'; } - if (globals.platform.isLinux) { +} + +String getPlatformNameForOhosArch(OhosArch arch) { + switch (arch) { + case OhosArch.armeabi_v7a: + return 'ohos-arm'; + case OhosArch.arm64_v8a: + return 'ohos-arm64'; + case OhosArch.x86_64: + return 'ohos-x86'; + } +} + +HostPlatform getCurrentHostPlatform() { + if (globals.platform.isLinux || globals.platform.isMacOS) { // support x64 and arm64 architecture. return globals.os.hostPlatform; } @@ -850,6 +991,11 @@ String getWebBuildDirectory() { return globals.fs.path.join(getBuildDirectory(), 'web'); } +/// Returns the ohos build output directory. +String getOhosBuildDirectory(){ + return globals.fs.path.join(getBuildDirectory(), 'ohos'); +} + /// Returns the Linux build output directory. String getLinuxBuildDirectory([TargetPlatform? targetPlatform]) { final String arch = (targetPlatform == null) ? diff --git a/packages/flutter_tools/lib/src/build_system/targets/common.dart b/packages/flutter_tools/lib/src/build_system/targets/common.dart index ba03270c526d5055cb02f60ba2dc4b4a529912d0..34980aebfb4449ec5862b78adb9eac1eaaef5764 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/common.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/common.dart @@ -219,6 +219,10 @@ class KernelSnapshot extends Target { case TargetPlatform.linux_arm64: case TargetPlatform.tester: case TargetPlatform.web_javascript: + case TargetPlatform.ohos: + case TargetPlatform.ohos_arm: + case TargetPlatform.ohos_arm64: + case TargetPlatform.ohos_x64: forceLinkPlatform = false; } @@ -234,6 +238,7 @@ class KernelSnapshot extends Target { TargetPlatform.ios => 'ios', TargetPlatform.linux_arm64 || TargetPlatform.linux_x64 => 'linux', TargetPlatform.windows_arm64 || TargetPlatform.windows_x64 => 'windows', + TargetPlatform.ohos || TargetPlatform.ohos_arm || TargetPlatform.ohos_arm64 || TargetPlatform.ohos_x64 => 'ohos', TargetPlatform.tester || TargetPlatform.web_javascript => null, }; diff --git a/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart b/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart index e2ab35de10906586bffc6f0d0fcd399ec19898af..fb9263226f892d2422b3d6ddb61ec432231a3f2b 100644 --- a/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart +++ b/packages/flutter_tools/lib/src/build_system/targets/native_assets.dart @@ -167,6 +167,14 @@ class NativeAssets extends Target { // Write the file we claim to have in the [outputs]. await writeNativeAssetsYaml(KernelAssets(), environment.buildDir.uri, fileSystem); dependencies = []; + case TargetPlatform.ohos: + case TargetPlatform.ohos_arm: + case TargetPlatform.ohos_arm64: + case TargetPlatform.ohos_x64: + // TODO(gengfei): adapt to ohos + // throw UnimplementedError('This function is not yet implemented'); + await writeNativeAssetsYaml(KernelAssets(), environment.buildDir.uri, fileSystem); + dependencies = []; } } @@ -361,6 +369,10 @@ class NativeAssets extends Target { case TargetPlatform.web_javascript: case TargetPlatform.windows_x64: case TargetPlatform.windows_arm64: + case TargetPlatform.ohos: + case TargetPlatform.ohos_arm: + case TargetPlatform.ohos_arm64: + case TargetPlatform.ohos_x64: throwToolExit('Unsupported Android target platform: $targetPlatform.'); } } diff --git a/packages/flutter_tools/lib/src/build_system/targets/ohos.dart b/packages/flutter_tools/lib/src/build_system/targets/ohos.dart new file mode 100644 index 0000000000000000000000000000000000000000..8d4e40dcd7b55176425d51acf206c556149a84dd --- /dev/null +++ b/packages/flutter_tools/lib/src/build_system/targets/ohos.dart @@ -0,0 +1,367 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development 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 'package:file/file.dart'; + +import '../../artifacts.dart'; +import '../../base/build.dart'; +import '../../base/deferred_component.dart'; +import '../../build_info.dart'; +import '../../globals.dart' as globals show xcode; +import '../build_system.dart'; +import '../depfile.dart'; +import '../exceptions.dart'; +import 'assets.dart'; +import 'common.dart'; +import '../tools/shader_compiler.dart'; + +class DebugOhosApplication extends OhosAssetBundle { + const DebugOhosApplication(); + + @override + String get name => 'debug_ohos_application'; + + @override + List get inputs => [ + ...super.inputs, + const Source.artifact(Artifact.vmSnapshotData, mode: BuildMode.debug), + const Source.artifact(Artifact.isolateSnapshotData, + mode: BuildMode.debug), + ]; + + @override + List get outputs => [ + ...super.outputs, + const Source.pattern('{OUTPUT_DIR}/flutter_assets/vm_snapshot_data'), + const Source.pattern( + '{OUTPUT_DIR}/flutter_assets/isolate_snapshot_data'), + const Source.pattern('{OUTPUT_DIR}/flutter_assets/kernel_blob.bin'), + ]; +} + +/// ohos release targets +const OhosAotBundle ohosArmReleaseBundle = OhosAotBundle(ohosArmRelease); +const OhosAotBundle ohosArm64ReleaseBundle = OhosAotBundle(ohosArm64Release); +const OhosAotBundle ohosX64ReleaseBundle = OhosAotBundle(ohosX64Release); +const OhosAotBundle ohosArmProfileBundle = OhosAotBundle(OhosAot(TargetPlatform.ohos_arm, BuildMode.profile)); +const OhosAotBundle ohosArm64ProfileBundle = OhosAotBundle(OhosAot(TargetPlatform.ohos_arm64, BuildMode.profile)); +const OhosAotBundle ohosX64ProfileBundle = OhosAotBundle(OhosAot(TargetPlatform.ohos_x64, BuildMode.profile)); + +List ohosTargets = [ + ohosArmReleaseBundle, + ohosArm64ReleaseBundle, + ohosX64ReleaseBundle, + ohosArmProfileBundle, + ohosArm64ProfileBundle, + ohosX64ProfileBundle, + const DebugOhosApplication(), +]; + +/// A rule paired with [OhosAot] that copies the produced so file and manifest.json (if present) into the output directory. +class OhosAotBundle extends Target { + /// Create an [OhosAotBundle] implementation for a given [targetPlatform] and [buildMode]. + const OhosAotBundle(this.dependency); + + /// The [OhosAot] instance this bundle rule depends on. + final OhosAot dependency; + + /// The name of the produced Ohos ABI. + String get _ohosAbiName { + return getNameForOhosArch(getOhosArchForName( + getNameForTargetPlatform(dependency.targetPlatform))); + } + + @override + String get name => + 'ohos_aot_bundle_${dependency.buildMode.cliName}_' + '${getNameForTargetPlatform(dependency.targetPlatform)}'; + + TargetPlatform get targetPlatform => dependency.targetPlatform; + + /// The selected build mode. + /// + /// This is restricted to [BuildMode.profile] or [BuildMode.release]. + BuildMode get buildMode => dependency.buildMode; + + @override + List get inputs => [ + Source.pattern('{BUILD_DIR}/$_ohosAbiName/app.so'), + ]; + + // flutter.gradle has been updated to correctly consume it. + @override + List get outputs => [ + Source.pattern('{OUTPUT_DIR}/$_ohosAbiName/app.so'), + ]; + + @override + List get depfiles => [ + 'flutter_$name.d', + ]; + + @override + List get dependencies => [ + dependency, + const AotOhosAssetBundle(), + ]; + + @override + Future build(Environment environment) async { + final Directory buildDir = + environment.buildDir.childDirectory(_ohosAbiName); + final Directory outputDirectory = + environment.outputDir.childDirectory(_ohosAbiName); + if (!outputDirectory.existsSync()) { + outputDirectory.createSync(recursive: true); + } + final File outputLibFile = buildDir.childFile('app.so'); + outputLibFile.copySync(outputDirectory.childFile('app.so').path); + + final List inputs = []; + final List outputs = []; + final File manifestFile = buildDir.childFile('manifest.json'); + if (manifestFile.existsSync()) { + final File destinationFile = outputDirectory.childFile('manifest.json'); + manifestFile.copySync(destinationFile.path); + inputs.add(manifestFile); + outputs.add(destinationFile); + } + final DepfileService depfileService = DepfileService( + fileSystem: environment.fileSystem, + logger: environment.logger, + ); + depfileService.writeToFile( + Depfile(inputs, outputs), + environment.buildDir.childFile('flutter_$name.d'), + writeEmpty: true, + ); + } +} + +const OhosAot ohosArmRelease = + OhosAot(TargetPlatform.ohos_arm, BuildMode.release); +const OhosAot ohosArm64Release = + OhosAot(TargetPlatform.ohos_arm64, BuildMode.release); +const OhosAot ohosX64Release = + OhosAot(TargetPlatform.ohos_x64, BuildMode.release); + +class OhosAot extends AotElfBase { + /// Create an [OhosAot] implementation for a given [targetPlatform] and [buildMode]. + const OhosAot(this.targetPlatform, this.buildMode); + + /// The name of the produced Ohos ABI. + String get _ohosAbiName { + return getNameForOhosArch( + getOhosArchForName(getNameForTargetPlatform(targetPlatform))); + } + + @override + String get name => 'ohos_aot_${buildMode.cliName}_' + '${getNameForTargetPlatform(targetPlatform)}'; + + /// The specific Ohos ABI we are building for. + final TargetPlatform targetPlatform; + + /// The selected build mode. + /// + /// Build mode is restricted to [BuildMode.profile] or [BuildMode.release] for AOT builds. + final BuildMode buildMode; + + @override + List get inputs => [ + const Source.pattern( + '{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/ohos.dart'), + const Source.pattern('{BUILD_DIR}/app.dill'), + const Source.artifact(Artifact.skyEnginePath), + Source.artifact( + Artifact.genSnapshot, + mode: buildMode, + platform: targetPlatform, + ), + ]; + + @override + List get outputs => [ + Source.pattern('{BUILD_DIR}/$_ohosAbiName/app.so'), + ]; + + @override + List get depfiles => [ + 'flutter_$name.d', + ]; + + @override + List get dependencies => const [ + KernelSnapshot(), + ]; + + @override + Future build(Environment environment) async { + final AOTSnapshotter snapshotter = AOTSnapshotter( + fileSystem: environment.fileSystem, + logger: environment.logger, + xcode: globals.xcode!, + processManager: environment.processManager, + artifacts: environment.artifacts, + ); + final Directory output = environment.buildDir.childDirectory(_ohosAbiName); + final String? buildModeEnvironment = environment.defines[kBuildMode]; + if (buildModeEnvironment == null) { + throw MissingDefineException(kBuildMode, 'aot_elf'); + } + if (!output.existsSync()) { + output.createSync(recursive: true); + } + final List extraGenSnapshotOptions = + decodeCommaSeparated(environment.defines, kExtraGenSnapshotOptions); + final List outputs = []; // outputs for the depfile + final String manifestPath = + '${output.path}${environment.platform.pathSeparator}manifest.json'; + if (environment.defines[kDeferredComponents] == 'true') { + extraGenSnapshotOptions.add('--loading_unit_manifest=$manifestPath'); + outputs.add(environment.fileSystem.file(manifestPath)); + } + final BuildMode buildMode = BuildMode.fromCliName(buildModeEnvironment); + final bool dartObfuscation = + environment.defines[kDartObfuscation] == 'true'; + final String? codeSizeDirectory = environment.defines[kCodeSizeDirectory]; + + if (codeSizeDirectory != null) { + final File codeSizeFile = environment.fileSystem + .directory(codeSizeDirectory) + .childFile('snapshot.$_ohosAbiName.json'); + final File precompilerTraceFile = environment.fileSystem + .directory(codeSizeDirectory) + .childFile('trace.$_ohosAbiName.json'); + extraGenSnapshotOptions + .add('--write-v8-snapshot-profile-to=${codeSizeFile.path}'); + extraGenSnapshotOptions + .add('--trace-precompiler-to=${precompilerTraceFile.path}'); + } + + final String? splitDebugInfo = environment.defines[kSplitDebugInfo]; + final int snapshotExitCode = await snapshotter.build( + platform: targetPlatform, + buildMode: buildMode, + mainPath: environment.buildDir.childFile('app.dill').path, + outputPath: output.path, + extraGenSnapshotOptions: extraGenSnapshotOptions, + splitDebugInfo: splitDebugInfo, + dartObfuscation: dartObfuscation, + ); + if (snapshotExitCode != 0) { + throw Exception('AOT snapshotter exited with code $snapshotExitCode'); + } + if (environment.defines[kDeferredComponents] == 'true') { + // Parse the manifest for .so paths + final List loadingUnits = + LoadingUnit.parseLoadingUnitManifest( + environment.fileSystem.file(manifestPath), environment.logger); + for (final LoadingUnit unit in loadingUnits) { + outputs.add(environment.fileSystem.file(unit.path)); + } + } + final DepfileService depfileService = DepfileService( + fileSystem: environment.fileSystem, + logger: environment.logger, + ); + depfileService.writeToFile( + Depfile([], outputs), + environment.buildDir.childFile('flutter_$name.d'), + writeEmpty: true, + ); + } +} + +/// An implementation of [OhosAssetBundle] that only includes assets. +class AotOhosAssetBundle extends OhosAssetBundle { + const AotOhosAssetBundle(); + + @override + String get name => 'aot_ohos_asset_bundle'; +} + +/// Prepares the asset bundle in the format expected by flutter.gradle. +/// +/// The vm_snapshot_data, isolate_snapshot_data, and kernel_blob.bin are +/// expected to be in the root output directory. +/// +/// All assets and manifests are included from flutter_assets/**. +abstract class OhosAssetBundle extends Target { + const OhosAssetBundle(); + + @override + List get inputs => const [ + Source.pattern('{BUILD_DIR}/app.dill'), + // ...IconTreeShaker.inputs, + ]; + + @override + List get outputs => const []; + + @override + List get depfiles => [ + 'flutter_assets.d', + ]; + + @override + Future build(Environment environment) async { + final String? buildModeEnvironment = environment.defines[kBuildMode]; + if (buildModeEnvironment == null) { + throw MissingDefineException(kBuildMode, name); + } + final BuildMode buildMode = BuildMode.fromCliName(buildModeEnvironment); + final Directory outputDirectory = environment.outputDir + .childDirectory('flutter_assets') + ..createSync(recursive: true); + + // Only copy the prebuilt runtimes and kernel blob in debug mode. + if (buildMode == BuildMode.debug) { + final String vmSnapshotData = environment.artifacts + .getArtifactPath(Artifact.vmSnapshotData, mode: BuildMode.debug); + final String isolateSnapshotData = environment.artifacts + .getArtifactPath(Artifact.isolateSnapshotData, mode: BuildMode.debug); + environment.buildDir + .childFile('app.dill') + .copySync(outputDirectory.childFile('kernel_blob.bin').path); + environment.fileSystem + .file(vmSnapshotData) + .copySync(outputDirectory.childFile('vm_snapshot_data').path); + environment.fileSystem + .file(isolateSnapshotData) + .copySync(outputDirectory.childFile('isolate_snapshot_data').path); + } + final Depfile assetDepfile = await copyAssets( + environment, + outputDirectory, + targetPlatform: TargetPlatform.ohos, + buildMode: buildMode, + flavor: environment.defines[kFlavor], + ); + final DepfileService depfileService = DepfileService( + fileSystem: environment.fileSystem, + logger: environment.logger, + ); + depfileService.writeToFile( + assetDepfile, + environment.buildDir.childFile('flutter_assets.d'), + ); + } + + @override + List get dependencies => const [ + KernelSnapshot(), + ]; +} diff --git a/packages/flutter_tools/lib/src/build_system/tools/shader_compiler.dart b/packages/flutter_tools/lib/src/build_system/tools/shader_compiler.dart index bef6474cabb94d26cd6ae7f797c48b2389451b41..0cbdd9ee3d674d74541cb709f43658b0684f83eb 100644 --- a/packages/flutter_tools/lib/src/build_system/tools/shader_compiler.dart +++ b/packages/flutter_tools/lib/src/build_system/tools/shader_compiler.dart @@ -115,6 +115,10 @@ class ShaderCompiler { case TargetPlatform.linux_arm64: case TargetPlatform.windows_x64: case TargetPlatform.windows_arm64: + case TargetPlatform.ohos: + case TargetPlatform.ohos_arm: + case TargetPlatform.ohos_arm64: + case TargetPlatform.ohos_x64: return ['--sksl', '--runtime-stage-gles', '--runtime-stage-vulkan']; case TargetPlatform.ios: @@ -175,6 +179,7 @@ class ShaderCompiler { '--spirv=$outputPath.spirv', '--input=${input.path}', '--input-type=frag', + '--remap-samplers', '--include=${input.parent.path}', '--include=$shaderLibPath', ]; diff --git a/packages/flutter_tools/lib/src/cache.dart b/packages/flutter_tools/lib/src/cache.dart index c5296d8a8a2aefc5fe470a54a22da1c74e947d86..e3d3ec063a171d02aea3941a8c19684a30a989fe 100644 --- a/packages/flutter_tools/lib/src/cache.dart +++ b/packages/flutter_tools/lib/src/cache.dart @@ -51,6 +51,10 @@ class DevelopmentArtifact { /// Artifacts required for iOS development. static const DevelopmentArtifact iOS = DevelopmentArtifact._('ios', feature: flutterIOSFeature); + /// Artifacts required for OpenHarmony development. + static const DevelopmentArtifact ohosGenSnapshot = DevelopmentArtifact._('ohos_gen_snapshot', feature: flutterOhosFeature); + static const DevelopmentArtifact ohosInternalBuild = DevelopmentArtifact._('ohos_internal_build', feature: flutterOhosFeature); + /// Artifacts required for web development. static const DevelopmentArtifact web = DevelopmentArtifact._('web', feature: flutterWebFeature); @@ -81,6 +85,8 @@ class DevelopmentArtifact { androidMaven, androidInternalBuild, iOS, + ohosGenSnapshot, + ohosInternalBuild, web, macOS, windows, @@ -190,7 +196,7 @@ class Cache { httpClient: HttpClient(), allowedBaseUrls: [ storageBaseUrl, - realmlessStorageBaseUrl, + ohosStorageBaseUrl, cipdBaseUrl, ], ); @@ -499,6 +505,32 @@ class Cache { : storageBaseUrl.replaceAll('/$storageRealm', ''); } + /// The base for URLs that store Flutter engine ohos artifacts that are fetched + /// during the installation of the Flutter SDK. + /// + /// By default the base URL is https://flutter-ohos.obs.cn-south-1.myhuaweicloud.com. However, if + /// `FLUTTER_OHOS_STORAGE_BASE_URL` environment variable is provided, the + /// environment variable value is returned instead. + /// + /// See also: + /// + /// * [cipdBaseUrl], which determines how CIPD artifacts are fetched. + /// * [Cache] class-level dartdocs that explain how artifact mirrors work. + String get ohosStorageBaseUrl { + final String? overrideUrl = _platform.environment['FLUTTER_OHOS_STORAGE_BASE_URL']; + if (overrideUrl == null) { + return 'https://flutter-ohos.obs.cn-south-1.myhuaweicloud.com'; + } + // verify that this is a valid URI. + try { + Uri.parse(overrideUrl); + } on FormatException catch (err) { + throwToolExit('"FLUTTER_OHOS_STORAGE_BASE_URL" contains an invalid URI:\n$err'); + } + _maybeWarnAboutStorageOverride(overrideUrl); + return overrideUrl; + } + /// The base for URLs that store Flutter engine artifacts in CIPD. /// /// For some platforms, such as Web and Fuchsia, CIPD artifacts are fetched @@ -895,6 +927,8 @@ abstract class EngineCachedArtifact extends CachedArtifact { /// A list of the dart package directories to download. List getPackageDirs(); + String get storageBaseUrl => cache.storageBaseUrl; + @override bool isUpToDateInner(FileSystem fileSystem) { final Directory pkgDir = cache.getCacheDir('pkg'); @@ -927,7 +961,7 @@ abstract class EngineCachedArtifact extends CachedArtifact { FileSystem fileSystem, OperatingSystemUtils operatingSystemUtils, ) async { - final String url = '${cache.storageBaseUrl}/flutter_infra_release/flutter/$version/'; + final String url = '$storageBaseUrl/flutter_infra_release/flutter/$version/'; final Directory pkgDir = cache.getCacheDir('pkg'); for (final String pkgName in getPackageDirs()) { diff --git a/packages/flutter_tools/lib/src/commands/assemble.dart b/packages/flutter_tools/lib/src/commands/assemble.dart index 5ac969e9d86018d2a720d362707ea6d4d62885ad..13bb4cb0dd180fa9351a281c54c456ff412db7ec 100644 --- a/packages/flutter_tools/lib/src/commands/assemble.dart +++ b/packages/flutter_tools/lib/src/commands/assemble.dart @@ -20,6 +20,7 @@ import '../build_system/targets/ios.dart'; import '../build_system/targets/linux.dart'; import '../build_system/targets/macos.dart'; import '../build_system/targets/windows.dart'; +import '../build_system/targets/ohos.dart'; import '../cache.dart'; import '../convert.dart'; import '../globals.dart' as globals; @@ -81,45 +82,49 @@ List _kDefaultTargets = [ const ProfileBundleWindowsAssets(TargetPlatform.windows_arm64), const ReleaseBundleWindowsAssets(TargetPlatform.windows_x64), const ReleaseBundleWindowsAssets(TargetPlatform.windows_arm64), + // Ohos targets + ...ohosTargets, ]; /// Assemble provides a low level API to interact with the flutter tool build /// system. class AssembleCommand extends FlutterCommand { - AssembleCommand({ bool verboseHelp = false, required BuildSystem buildSystem }) - : _buildSystem = buildSystem { + AssembleCommand({bool verboseHelp = false, required BuildSystem buildSystem}) + : _buildSystem = buildSystem { argParser.addMultiOption( 'define', abbr: 'd', valueHelp: 'target=key=value', - help: 'Allows passing configuration to a target, as in "--define=target=key=value".', - ); - argParser.addOption( - 'performance-measurement-file', - help: 'Output individual target performance to a JSON file.' - ); - argParser.addMultiOption( - 'input', - abbr: 'i', - help: 'Allows passing additional inputs with "--input=key=value". Unlike ' - 'defines, additional inputs do not generate a new configuration; instead ' - 'they are treated as dependencies of the targets that use them.' + help: + 'Allows passing configuration to a target, as in "--define=target=key=value".', ); + argParser.addOption('performance-measurement-file', + help: 'Output individual target performance to a JSON file.'); + argParser.addMultiOption('input', + abbr: 'i', + help: + 'Allows passing additional inputs with "--input=key=value". Unlike ' + 'defines, additional inputs do not generate a new configuration; instead ' + 'they are treated as dependencies of the targets that use them.'); argParser.addOption('depfile', - help: 'A file path where a depfile will be written. ' - 'This contains all build inputs and outputs in a Make-style syntax.' - ); - argParser.addOption('build-inputs', help: 'A file path where a newline-separated ' - 'file containing all inputs used will be written after a build. ' - 'This file is not included as a build input or output. This file is not ' - 'written if the build fails for any reason.'); - argParser.addOption('build-outputs', help: 'A file path where a newline-separated ' - 'file containing all outputs created will be written after a build. ' - 'This file is not included as a build input or output. This file is not ' - 'written if the build fails for any reason.'); - argParser.addOption('output', abbr: 'o', help: 'A directory where output ' - 'files will be written. Must be either absolute or relative from the ' - 'root of the current Flutter project.', + help: 'A file path where a depfile will be written. ' + 'This contains all build inputs and outputs in a Make-style syntax.'); + argParser.addOption('build-inputs', + help: 'A file path where a newline-separated ' + 'file containing all inputs used will be written after a build. ' + 'This file is not included as a build input or output. This file is not ' + 'written if the build fails for any reason.'); + argParser.addOption('build-outputs', + help: 'A file path where a newline-separated ' + 'file containing all outputs created will be written after a build. ' + 'This file is not included as a build input or output. This file is not ' + 'written if the build fails for any reason.'); + argParser.addOption( + 'output', + abbr: 'o', + help: 'A directory where output ' + 'files will be written. Must be either absolute or relative from the ' + 'root of the current Flutter project.', ); usesExtraDartFlagOptions(verboseHelp: verboseHelp); usesDartDefineOption(); @@ -164,7 +169,8 @@ class AssembleCommand extends FlutterCommand { } final TargetPlatform targetPlatform = getTargetPlatformForName(platform); - final DevelopmentArtifact? artifact = artifactFromTargetPlatform(targetPlatform); + final DevelopmentArtifact? artifact = + artifactFromTargetPlatform(targetPlatform); if (artifact != null) { return {artifact}; } @@ -179,13 +185,11 @@ class AssembleCommand extends FlutterCommand { } final String name = argumentResults.rest.first; final Map targetMap = { - for (final Target target in _kDefaultTargets) - target.name: target, + for (final Target target in _kDefaultTargets) target.name: target, }; final List results = [ for (final String targetName in argumentResults.rest) - if (targetMap.containsKey(targetName)) - targetMap[targetName]!, + if (targetMap.containsKey(targetName)) targetMap[targetName]!, ]; if (results.isEmpty) { throwToolExit('No target named "$name" defined.'); @@ -242,8 +246,8 @@ class AssembleCommand extends FlutterCommand { analytics: globals.analytics, platform: globals.platform, engineVersion: artifacts.isLocalEngine - ? null - : globals.flutterVersion.engineRevision, + ? null + : globals.flutterVersion.engineRevision, generateDartPluginRegistry: true, ); return result; @@ -262,7 +266,10 @@ class AssembleCommand extends FlutterCommand { } final ArgResults argumentResults = argResults!; if (argumentResults.wasParsed(FlutterOptions.kExtraGenSnapshotOptions)) { - results[kExtraGenSnapshotOptions] = (argumentResults[FlutterOptions.kExtraGenSnapshotOptions] as List).join(','); + results[kExtraGenSnapshotOptions] = + (argumentResults[FlutterOptions.kExtraGenSnapshotOptions] + as List) + .join(','); } final Map defineConfigJsonMap = extractDartDefineConfigJsonMap(); @@ -276,7 +283,10 @@ class AssembleCommand extends FlutterCommand { results[kDeferredComponents] = 'true'; } if (argumentResults.wasParsed(FlutterOptions.kExtraFrontEndOptions)) { - results[kExtraFrontEndOptions] = (argumentResults[FlutterOptions.kExtraFrontEndOptions] as List).join(','); + results[kExtraFrontEndOptions] = + (argumentResults[FlutterOptions.kExtraFrontEndOptions] + as List) + .join(','); } return results; } @@ -299,9 +309,8 @@ class AssembleCommand extends FlutterCommand { decodedDefines = decodeDartDefines(_environment.defines, kDartDefines); } on FormatException { throwToolExit( - 'Error parsing assemble command: your generated configuration may be out of date. ' - "Try re-running 'flutter build ios' or the appropriate build command." - ); + 'Error parsing assemble command: your generated configuration may be out of date. ' + "Try re-running 'flutter build ios' or the appropriate build command."); } if (_flutterProject.manifest.deferredComponents != null && decodedDefines.contains('validate-deferred-components=true') @@ -309,7 +318,8 @@ class AssembleCommand extends FlutterCommand { && !isDebug()) { // Add deferred components validation target that require loading units. target = DeferredComponentsGenSnapshotValidatorTarget( - deferredComponentsDependencies: deferredTargets.cast(), + deferredComponentsDependencies: + deferredTargets.cast(), nonDeferredComponentsDependencies: nonDeferredTargets, title: 'Deferred components gen_snapshot validation', ); @@ -331,8 +341,10 @@ class AssembleCommand extends FlutterCommand { if (!result.success) { for (final ExceptionMeasurement measurement in result.exceptions.values) { if (measurement.fatal || globals.logger.isVerbose) { - globals.printError('Target ${measurement.target} failed: ${measurement.exception}', - stackTrace: globals.logger.isVerbose ? measurement.stackTrace : null, + globals.printError( + 'Target ${measurement.target} failed: ${measurement.exception}', + stackTrace: + globals.logger.isVerbose ? measurement.stackTrace : null, ); } } @@ -347,7 +359,8 @@ class AssembleCommand extends FlutterCommand { writeListIfChanged(result.outputFiles, stringArg('build-outputs')!); } if (argumentResults.wasParsed('performance-measurement-file')) { - final File outFile = globals.fs.file(argumentResults['performance-measurement-file']); + final File outFile = + globals.fs.file(argumentResults['performance-measurement-file']); writePerformanceData(result.performance.values, outFile); } if (argumentResults.wasParsed('depfile')) { @@ -379,7 +392,8 @@ void writeListIfChanged(List files, String path) { /// Output performance measurement data in [outFile]. @visibleForTesting -void writePerformanceData(Iterable measurements, File outFile) { +void writePerformanceData( + Iterable measurements, File outFile) { final Map jsonData = { 'targets': [ for (final PerformanceMeasurement measurement in measurements) diff --git a/packages/flutter_tools/lib/src/commands/build.dart b/packages/flutter_tools/lib/src/commands/build.dart index 8ae6ec3baf97ba1caf0047e227323bb24daa9e57..3abedd6bccf5154df67879189f29d7d35ea4e511 100644 --- a/packages/flutter_tools/lib/src/commands/build.dart +++ b/packages/flutter_tools/lib/src/commands/build.dart @@ -19,8 +19,12 @@ import '../commands/build_windows.dart'; import '../runner/flutter_command.dart'; import 'build_aar.dart'; import 'build_apk.dart'; +import 'build_app.dart'; import 'build_appbundle.dart'; import 'build_bundle.dart'; +import 'build_hap.dart'; +import 'build_har.dart'; +import 'build_hsp.dart'; import 'build_ios.dart'; import 'build_ios_framework.dart'; import 'build_macos_framework.dart'; @@ -85,6 +89,10 @@ class BuildCommand extends FlutterCommand { processUtils: processUtils, verboseHelp: verboseHelp, )); + _addSubcommand(BuildHapCommand(logger: logger, verboseHelp: verboseHelp)); + _addSubcommand(BuildHarCommand(logger: logger, verboseHelp: verboseHelp)); + _addSubcommand(BuildHspCommand(logger: logger, verboseHelp: verboseHelp)); + _addSubcommand(BuildAppCommand(logger: logger, verboseHelp: verboseHelp)); } void _addSubcommand(BuildSubCommand command) { diff --git a/packages/flutter_tools/lib/src/commands/build_app.dart b/packages/flutter_tools/lib/src/commands/build_app.dart new file mode 100644 index 0000000000000000000000000000000000000000..7233fe78bf991821fcf6de0eecadbd1fe50cfb5b --- /dev/null +++ b/packages/flutter_tools/lib/src/commands/build_app.dart @@ -0,0 +1,87 @@ +/* +* Copyright (c) 2024 Hunan OpenValley Digital Industry Development 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 '../build_info.dart'; +import '../globals.dart' as globals; +import '../ohos/hvigor_utils.dart'; +import '../ohos/ohos_builder.dart'; +import '../project.dart'; +import '../runner/flutter_command.dart'; +import 'build.dart'; + +class BuildAppCommand extends BuildSubCommand { + BuildAppCommand({required super.logger, bool verboseHelp = false}) + : super(verboseHelp: verboseHelp) { + addTreeShakeIconsFlag(); + usesTargetOption(); + addBuildModeFlags(verboseHelp: verboseHelp); + usesFlavorOption(); + usesPubOption(); + usesBuildNumberOption(); + usesBuildNameOption(); + addShrinkingFlag(verboseHelp: verboseHelp); + addSplitDebugInfoOption(); + addDartObfuscationOption(); + usesDartDefineOption(); + usesExtraDartFlagOptions(verboseHelp: verboseHelp); + addBundleSkSLPathOption(hide: !verboseHelp); + addEnableExperimentation(hide: !verboseHelp); + addBuildPerformanceFile(hide: !verboseHelp); + addNullSafetyModeOptions(hide: !verboseHelp); + usesAnalyzeSizeFlag(); + addIgnoreDeprecationOption(); + usesTrackWidgetCreation(verboseHelp: verboseHelp); + + argParser.addMultiOption( + 'target-platform', + defaultsTo: const ['ohos-arm64'], + allowed: ['ohos-arm64', 'ohos-arm', 'ohos-x86'], + help: 'The target platform for which the app is compiled.', + ); + } + + @override + final String description = 'Build an Ohos App file from your app.\n\n'; + + @override + String get name => 'app'; + + @override + bool get reportNullSafety => false; + + @override + Future> get requiredArtifacts async => { + DevelopmentArtifact.ohosGenSnapshot, + DevelopmentArtifact.ohosInternalBuild, + }; + + @override + Future runCommand() async { + if (globals.hmosSdk == null) { + exitWithNoSdkMessage(); + } + final BuildInfo buildInfo = await getBuildInfo(); + final OhosBuildInfo ohosBuildInfo = OhosBuildInfo( + buildInfo, + targetArchs: stringsArg('target-platform').map(getOhosArchForName), + ); + await ohosBuilder?.buildApp( + project: FlutterProject.current(), + ohosBuildInfo: ohosBuildInfo, + target: targetFile, + ); + return FlutterCommandResult.success(); + } +} diff --git a/packages/flutter_tools/lib/src/commands/build_bundle.dart b/packages/flutter_tools/lib/src/commands/build_bundle.dart index 82470cf6071a3d3f7f01ecf700950932b683bb77..305b0facc512f116fa8290b53fcc00f05f47e1fa 100644 --- a/packages/flutter_tools/lib/src/commands/build_bundle.dart +++ b/packages/flutter_tools/lib/src/commands/build_bundle.dart @@ -136,6 +136,10 @@ class BuildBundleCommand extends BuildSubCommand { case TargetPlatform.ios: case TargetPlatform.tester: case TargetPlatform.web_javascript: + case TargetPlatform.ohos: + case TargetPlatform.ohos_arm: + case TargetPlatform.ohos_arm64: + case TargetPlatform.ohos_x64: break; } diff --git a/packages/flutter_tools/lib/src/commands/build_hap.dart b/packages/flutter_tools/lib/src/commands/build_hap.dart new file mode 100644 index 0000000000000000000000000000000000000000..effd90361169e8f7c96e4ef2ac1f4b5262e2d400 --- /dev/null +++ b/packages/flutter_tools/lib/src/commands/build_hap.dart @@ -0,0 +1,87 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development 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 '../build_info.dart'; +import '../globals.dart' as globals; +import '../ohos/hvigor_utils.dart'; +import '../ohos/ohos_builder.dart'; +import '../project.dart'; +import '../runner/flutter_command.dart'; +import 'build.dart'; + +class BuildHapCommand extends BuildSubCommand { + BuildHapCommand({required super.logger, bool verboseHelp = false}) + : super(verboseHelp: verboseHelp) { + addTreeShakeIconsFlag(); + usesTargetOption(); + addBuildModeFlags(verboseHelp: verboseHelp); + usesFlavorOption(); + usesPubOption(); + usesBuildNumberOption(); + usesBuildNameOption(); + addShrinkingFlag(verboseHelp: verboseHelp); + addSplitDebugInfoOption(); + addDartObfuscationOption(); + usesDartDefineOption(); + usesExtraDartFlagOptions(verboseHelp: verboseHelp); + addBundleSkSLPathOption(hide: !verboseHelp); + addEnableExperimentation(hide: !verboseHelp); + addBuildPerformanceFile(hide: !verboseHelp); + addNullSafetyModeOptions(hide: !verboseHelp); + usesAnalyzeSizeFlag(); + addIgnoreDeprecationOption(); + usesTrackWidgetCreation(verboseHelp: verboseHelp); + + argParser.addMultiOption( + 'target-platform', + defaultsTo: const ['ohos-arm64'], + allowed: ['ohos-arm64', 'ohos-arm', 'ohos-x86'], + help: 'The target platform for which the app is compiled.', + ); + } + + @override + final String description = 'Build an Ohos Hap file from your app.\n\n'; + + @override + String get name => 'hap'; + + @override + bool get reportNullSafety => false; + + @override + Future> get requiredArtifacts async => { + DevelopmentArtifact.ohosGenSnapshot, + DevelopmentArtifact.ohosInternalBuild, + }; + + @override + Future runCommand() async { + if (globals.hmosSdk == null) { + exitWithNoSdkMessage(); + } + final BuildInfo buildInfo = await getBuildInfo(); + final OhosBuildInfo ohosBuildInfo = OhosBuildInfo( + buildInfo, + targetArchs: stringsArg('target-platform').map(getOhosArchForName), + ); + await ohosBuilder?.buildHap( + project: FlutterProject.current(), + ohosBuildInfo: ohosBuildInfo, + target: targetFile, + ); + return FlutterCommandResult.success(); + } +} diff --git a/packages/flutter_tools/lib/src/commands/build_har.dart b/packages/flutter_tools/lib/src/commands/build_har.dart new file mode 100644 index 0000000000000000000000000000000000000000..b9445beb43f1588c3d31c12763f08ce5a4bcd461 --- /dev/null +++ b/packages/flutter_tools/lib/src/commands/build_har.dart @@ -0,0 +1,85 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development 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 '../build_info.dart'; +import '../globals.dart' as globals; +import '../ohos/hvigor_utils.dart'; +import '../ohos/ohos_builder.dart'; +import '../project.dart'; +import '../runner/flutter_command.dart'; +import 'build.dart'; + +class BuildHarCommand extends BuildSubCommand { + BuildHarCommand({required super.logger, bool verboseHelp = false}) + : super(verboseHelp: verboseHelp) { + addTreeShakeIconsFlag(); + usesTargetOption(); + addBuildModeFlags(verboseHelp: verboseHelp); + usesFlavorOption(); + usesPubOption(); + addShrinkingFlag(verboseHelp: verboseHelp); + addSplitDebugInfoOption(); + addDartObfuscationOption(); + usesDartDefineOption(); + usesExtraDartFlagOptions(verboseHelp: verboseHelp); + addBundleSkSLPathOption(hide: !verboseHelp); + addEnableExperimentation(hide: !verboseHelp); + addBuildPerformanceFile(hide: !verboseHelp); + addNullSafetyModeOptions(hide: !verboseHelp); + usesAnalyzeSizeFlag(); + addIgnoreDeprecationOption(); + usesTrackWidgetCreation(verboseHelp: verboseHelp); + + argParser.addMultiOption( + 'target-platform', + defaultsTo: const ['ohos-arm64'], + allowed: ['ohos-arm64', 'ohos-arm', 'ohos-x86'], + help: 'The target platform for which the app is compiled.', + ); + } + + @override + final String description = 'Build an Ohos har file from your app.\n\n'; + + @override + String get name => 'har'; + + @override + bool get reportNullSafety => false; + + @override + Future> get requiredArtifacts async => { + DevelopmentArtifact.ohosGenSnapshot, + DevelopmentArtifact.ohosInternalBuild, + }; + + @override + Future runCommand() async { + if (globals.hmosSdk == null) { + exitWithNoSdkMessage(); + } + final BuildInfo buildInfo = await getBuildInfo(); + final OhosBuildInfo ohosBuildInfo = OhosBuildInfo( + buildInfo, + targetArchs: stringsArg('target-platform').map(getOhosArchForName), + ); + await ohosBuilder?.buildHar( + project: FlutterProject.current(), + ohosBuildInfo: ohosBuildInfo, + target: targetFile, + ); + return FlutterCommandResult.success(); + } +} diff --git a/packages/flutter_tools/lib/src/commands/build_hsp.dart b/packages/flutter_tools/lib/src/commands/build_hsp.dart new file mode 100644 index 0000000000000000000000000000000000000000..6fcaedb72a9546f8b31731c98ab92e4700a3dd23 --- /dev/null +++ b/packages/flutter_tools/lib/src/commands/build_hsp.dart @@ -0,0 +1,87 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development 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 '../build_info.dart'; +import '../globals.dart' as globals; +import '../ohos/hvigor_utils.dart'; +import '../ohos/ohos_builder.dart'; +import '../project.dart'; +import '../runner/flutter_command.dart'; +import 'build.dart'; + +class BuildHspCommand extends BuildSubCommand { + BuildHspCommand({required super.logger, bool verboseHelp = false}) + : super(verboseHelp: verboseHelp) { + addTreeShakeIconsFlag(); + usesTargetOption(); + addBuildModeFlags(verboseHelp: verboseHelp); + usesFlavorOption(); + usesPubOption(); + usesBuildNumberOption(); + usesBuildNameOption(); + addShrinkingFlag(verboseHelp: verboseHelp); + addSplitDebugInfoOption(); + addDartObfuscationOption(); + usesDartDefineOption(); + usesExtraDartFlagOptions(verboseHelp: verboseHelp); + addBundleSkSLPathOption(hide: !verboseHelp); + addEnableExperimentation(hide: !verboseHelp); + addBuildPerformanceFile(hide: !verboseHelp); + addNullSafetyModeOptions(hide: !verboseHelp); + usesAnalyzeSizeFlag(); + addIgnoreDeprecationOption(); + usesTrackWidgetCreation(verboseHelp: verboseHelp); + + argParser.addMultiOption( + 'target-platform', + defaultsTo: const ['ohos-arm64'], + allowed: ['ohos-arm64', 'ohos-arm', 'ohos-x86'], + help: 'The target platform for which the app is compiled.', + ); + } + + @override + final String description = 'Build an Ohos hsp file from your app.\n\n'; + + @override + String get name => 'hsp'; + + @override + bool get reportNullSafety => false; + + @override + Future> get requiredArtifacts async => { + DevelopmentArtifact.ohosGenSnapshot, + DevelopmentArtifact.ohosInternalBuild, + }; + + @override + Future runCommand() async { + if (globals.hmosSdk == null) { + exitWithNoSdkMessage(); + } + final BuildInfo buildInfo = await getBuildInfo(); + final OhosBuildInfo ohosBuildInfo = OhosBuildInfo( + buildInfo, + targetArchs: stringsArg('target-platform').map(getOhosArchForName), + ); + await ohosBuilder?.buildHsp( + project: FlutterProject.current(), + ohosBuildInfo: ohosBuildInfo, + target: targetFile, + ); + return FlutterCommandResult.success(); + } +} diff --git a/packages/flutter_tools/lib/src/commands/channel.dart b/packages/flutter_tools/lib/src/commands/channel.dart index 1c54fa1513b3bce197c81209debd942119444ce8..71521cff972c30de6344104ac20c2b9844719baa 100644 --- a/packages/flutter_tools/lib/src/commands/channel.dart +++ b/packages/flutter_tools/lib/src/commands/channel.dart @@ -45,6 +45,7 @@ class ChannelCommand extends FlutterCommand { @override Future runCommand() async { + throwToolExit('It will be supported later.', exitCode: 1); final List rest = argResults?.rest ?? []; switch (rest.length) { case 0: diff --git a/packages/flutter_tools/lib/src/commands/clean.dart b/packages/flutter_tools/lib/src/commands/clean.dart index 3cd5d74f3a67687652ed8882ccb6f869a2a5964f..52d0f2a9506685f085708a99ce01f54fd1a994a5 100644 --- a/packages/flutter_tools/lib/src/commands/clean.dart +++ b/packages/flutter_tools/lib/src/commands/clean.dart @@ -72,6 +72,9 @@ class CleanCommand extends FlutterCommand { deleteFile(flutterProject.flutterPluginsDependenciesFile); deleteFile(flutterProject.flutterPluginsFile); + flutterProject.ohos.deleteOhModulesCache(); + deleteFile(flutterProject.ohos.ephemeralDirectory); + return const FlutterCommandResult(ExitStatus.success); } diff --git a/packages/flutter_tools/lib/src/commands/config.dart b/packages/flutter_tools/lib/src/commands/config.dart index 81ca66fadb3db2922ef0e8ea9c85f7fc50fca1a3..05ccdb99190704e31b34e7e9c03753ad6aec8516 100644 --- a/packages/flutter_tools/lib/src/commands/config.dart +++ b/packages/flutter_tools/lib/src/commands/config.dart @@ -35,6 +35,9 @@ class ConfigCommand extends FlutterCommand { ' 1) the JDK bundled with the latest installation of Android Studio,\n' ' 2) the JDK found at the directory found in the JAVA_HOME environment variable, and\n' " 3) the directory containing the java binary found in the user's path."); + argParser.addOption('ohos-sdk', help: 'The OpenHarmony SDK directory.'); + argParser.addOption('ohpm-home', help: 'The ohpm tool directory.'); + argParser.addOption('signTool-home', help: 'The sign tool directory.'); argParser.addOption('build-dir', help: 'The relative path to override a projects build directory.', valueHelp: 'out/'); argParser.addFlag('machine', @@ -150,6 +153,18 @@ class ConfigCommand extends FlutterCommand { _updateConfig('jdk-dir', stringArg('jdk-dir')!); } + if (argResults!.wasParsed('ohos-sdk')) { + _updateConfig('ohos-sdk', stringArg('ohos-sdk')!); + } + + if (argResults!.wasParsed('ohpm-home')) { + _updateConfig('ohpm-home', stringArg('ohpm-home')!); + } + + if (argResults!.wasParsed('signTool-home')) { + _updateConfig('signTool-home', stringArg('signTool-home')!); + } + if (argResults!.wasParsed('clear-ios-signing-cert')) { _updateConfig('ios-signing-cert', ''); } diff --git a/packages/flutter_tools/lib/src/commands/create.dart b/packages/flutter_tools/lib/src/commands/create.dart index e470fa222dce840b4d528b996a10e181b7dd96cc..0dd89c16afa29bb152c924a0517eb2d49e50d74d 100644 --- a/packages/flutter_tools/lib/src/commands/create.dart +++ b/packages/flutter_tools/lib/src/commands/create.dart @@ -21,6 +21,7 @@ import '../flutter_manifest.dart'; import '../flutter_project_metadata.dart'; import '../globals.dart' as globals; import '../ios/code_signing.dart'; +import '../ohos/hvigor_utils.dart' as hvigor; import '../project.dart'; import '../reporting/reporting.dart'; import '../runner/flutter_command.dart'; @@ -278,14 +279,16 @@ class CreateCommand extends CreateBase { final bool includeLinux; final bool includeMacos; final bool includeWindows; + final bool includeOhos; if (template == FlutterProjectType.module) { - // The module template only supports iOS and Android. + // The module template only supports iOS 、Android And OpenHarmony includeIos = true; includeAndroid = true; includeWeb = false; includeLinux = false; includeMacos = false; includeWindows = false; + includeOhos = true; } else if (template == FlutterProjectType.package) { // The package template does not supports any platform. includeIos = false; @@ -294,6 +297,7 @@ class CreateCommand extends CreateBase { includeLinux = false; includeMacos = false; includeWindows = false; + includeOhos = false; } else { includeIos = featureFlags.isIOSEnabled && platforms.contains('ios'); includeAndroid = featureFlags.isAndroidEnabled && platforms.contains('android'); @@ -301,6 +305,7 @@ class CreateCommand extends CreateBase { includeLinux = featureFlags.isLinuxEnabled && platforms.contains('linux'); includeMacos = featureFlags.isMacOSEnabled && platforms.contains('macos'); includeWindows = featureFlags.isWindowsEnabled && platforms.contains('windows'); + includeOhos = featureFlags.isOhosEnabled && platforms.contains('ohos'); } String? developmentTeam; @@ -336,6 +341,7 @@ class CreateCommand extends CreateBase { linux: includeLinux, macos: includeMacos, windows: includeWindows, + ohos: includeOhos, dartSdkVersionBounds: "'>=$dartSdk <4.0.0'", implementationTests: boolArg('implementation-tests'), agpVersion: gradle.templateAndroidGradlePluginVersion, @@ -445,6 +451,7 @@ class CreateCommand extends CreateBase { macOSPlatform: includeMacos, windowsPlatform: includeWindows, webPlatform: includeWeb, + ohosPlatform: includeOhos, ); } } @@ -618,12 +625,18 @@ Your $application code is in $relativeAppMain. project: project, requireAndroidSdk: false); } + final bool generateOhos = templateContext['ohos'] == true; + if (generateOhos) { + hvigor.updateLocalProperties(project: project); + } + final String? projectName = templateContext['projectName'] as String?; final String organization = templateContext['organization']! as String; // Required to make the context. final String? androidPluginIdentifier = templateContext['androidIdentifier'] as String?; final String exampleProjectName = '${projectName}_example'; templateContext['projectName'] = exampleProjectName; templateContext['androidIdentifier'] = CreateBase.createAndroidIdentifier(organization, exampleProjectName); + templateContext['ohosIdentifier'] = CreateBase.createAndroidIdentifier(organization, exampleProjectName); templateContext['iosIdentifier'] = CreateBase.createUTIIdentifier(organization, exampleProjectName); templateContext['macosIdentifier'] = CreateBase.createUTIIdentifier(organization, exampleProjectName); templateContext['windowsIdentifier'] = CreateBase.createWindowsIdentifier(organization, exampleProjectName); diff --git a/packages/flutter_tools/lib/src/commands/create_base.dart b/packages/flutter_tools/lib/src/commands/create_base.dart index 93c9050c9b08c3b1a2e4f2ac62c4a63f0d4b2cc2..258cfaa20fd103dd032af1b58baaba755e12b4f0 100644 --- a/packages/flutter_tools/lib/src/commands/create_base.dart +++ b/packages/flutter_tools/lib/src/commands/create_base.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:io'; + import 'package:meta/meta.dart'; import 'package:uuid/uuid.dart'; @@ -29,6 +31,7 @@ const List _kAvailablePlatforms = [ 'linux', 'macos', 'web', + 'ohos', ]; /// A list of all possible create platforms, even those that may not be enabled @@ -40,6 +43,7 @@ const List kAllCreatePlatforms = [ 'linux', 'macos', 'web', + 'ohos', ]; const String _kDefaultPlatformArgumentHelp = @@ -360,6 +364,7 @@ abstract class CreateBase extends FlutterCommand { bool linux = false, bool macos = false, bool windows = false, + bool ohos = false, bool implementationTests = false, }) { final String pluginDartClass = _createPluginClassName(projectName); @@ -381,6 +386,13 @@ abstract class CreateBase extends FlutterCommand { // https://developer.gnome.org/gio/stable/GApplication.html#g-application-id-is-valid final String linuxIdentifier = androidIdentifier; + // Ohos uses the same scheme as the Android identifier. + final String ohosIdentifier = androidIdentifier; + // locating ohos sdk from environment + final String? ohosSdkHome = globals.ohosSdk?.sdkPath; + final String? hosSdkHome = globals.hmosSdk?.sdkPath; + final String? nodeHome = Platform.environment['NODE_HOME']; + return { 'organization': organization, 'projectName': projectName, @@ -390,6 +402,10 @@ abstract class CreateBase extends FlutterCommand { 'macosIdentifier': appleIdentifier, 'linuxIdentifier': linuxIdentifier, 'windowsIdentifier': windowsIdentifier, + 'ohosIdentifier':ohosIdentifier, + 'ohosSdkHome':ohosSdkHome, + 'hosSdkHome':hosSdkHome, + 'nodeHome':nodeHome, 'description': projectDescription, 'dartSdk': '$flutterRoot/bin/cache/dart-sdk', 'androidMinApiLevel': android_common.minApiLevel, @@ -418,6 +434,7 @@ abstract class CreateBase extends FlutterCommand { 'linux': linux, 'macos': macos, 'windows': windows, + 'ohos': ohos, 'year': DateTime.now().year, 'dartSdkVersionBounds': dartSdkVersionBounds, 'implementationTests': implementationTests, @@ -521,6 +538,7 @@ abstract class CreateBase extends FlutterCommand { final bool macOSPlatform = templateContext['macos'] as bool? ?? false; final bool windowsPlatform = templateContext['windows'] as bool? ?? false; final bool webPlatform = templateContext['web'] as bool? ?? false; + final bool ohosPlatform = templateContext['ohos'] as bool ? ?? false; if (boolArg('pub')) { final Environment environment = Environment( @@ -567,6 +585,9 @@ abstract class CreateBase extends FlutterCommand { if (windowsPlatform) { platformsForMigrateConfig.add(SupportedPlatform.windows); } + if (ohosPlatform) { + platformsForMigrateConfig.add(SupportedPlatform.ohos); + } if (templateContext['fuchsia'] == true) { platformsForMigrateConfig.add(SupportedPlatform.fuchsia); } diff --git a/packages/flutter_tools/lib/src/commands/custom_devices.dart b/packages/flutter_tools/lib/src/commands/custom_devices.dart index 3f07e7ff734fe397813681cb3cca856afb643581..7b2bd1dcbf8464f170587d3f7f4e0009a1036162 100644 --- a/packages/flutter_tools/lib/src/commands/custom_devices.dart +++ b/packages/flutter_tools/lib/src/commands/custom_devices.dart @@ -31,6 +31,22 @@ import '../runner/flutter_command_runner.dart'; /// The Object arg may be null. typedef PrintFn = void Function(Object); +class CustomDevicesNotSupportCommand extends FlutterCommand { + @override + String get description => 'It will be supported later.'; + + @override + String get name => 'custom-devices'; + + @override + Future runCommand() async { + throwToolExit('It will be supported later.'); + } + + @override + String get usage => description; +} + class CustomDevicesCommand extends FlutterCommand { factory CustomDevicesCommand({ required CustomDevicesConfig customDevicesConfig, diff --git a/packages/flutter_tools/lib/src/commands/daemon.dart b/packages/flutter_tools/lib/src/commands/daemon.dart index d74f0ae73cd5caa59511fdec48703a5e0e5e330c..0def47af8725849681c85bcaffce2dfb7c1ea7e3 100644 --- a/packages/flutter_tools/lib/src/commands/daemon.dart +++ b/packages/flutter_tools/lib/src/commands/daemon.dart @@ -486,6 +486,21 @@ class DaemonDomain extends Domain { 'fixCode': _ReasonCode.create.name, }); } + case PlatformType.ohos: + if (!featureFlags.isOhosEnabled) { + reasons.add({ + 'reasonText': 'the Ohos feature is not enabled', + 'fixText': 'Run "flutter config --enable-ohos"', + 'fixCode': _ReasonCode.config.name, + }); + } + if (!supportedPlatforms.contains(SupportedPlatform.ohos)) { + reasons.add({ + 'reasonText': 'the Ohos platform is not enabled for this project', + 'fixText': 'Run "flutter create --platforms=ohos ." in your application directory', + 'fixCode': _ReasonCode.create.name, + }); + } case PlatformType.ios: if (!featureFlags.isIOSEnabled) { reasons.add({ diff --git a/packages/flutter_tools/lib/src/commands/downgrade.dart b/packages/flutter_tools/lib/src/commands/downgrade.dart index a58b75c0098620ebf6d9688138e38dc3e4ebf882..794f0bcdcf4ccbdac949d8d9d915675f608fc400 100644 --- a/packages/flutter_tools/lib/src/commands/downgrade.dart +++ b/packages/flutter_tools/lib/src/commands/downgrade.dart @@ -79,6 +79,7 @@ class DowngradeCommand extends FlutterCommand { @override Future runCommand() async { + throwToolExit('It will be supported later.', exitCode: 1); // Commands do not necessarily have access to the correct zone injected // values when being created. Fields must be lazily instantiated in runCommand, // at least until the zone injection is refactored. diff --git a/packages/flutter_tools/lib/src/commands/emulators.dart b/packages/flutter_tools/lib/src/commands/emulators.dart index b991bccba4f116213e6b93cde47eae5cb6de23fa..7cd232e9e4f677fc283299ef864beabeb279742b 100644 --- a/packages/flutter_tools/lib/src/commands/emulators.dart +++ b/packages/flutter_tools/lib/src/commands/emulators.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:io'; + import 'package:args/args.dart'; import '../base/common.dart'; @@ -11,6 +13,8 @@ import '../emulator.dart'; import '../globals.dart' as globals; import '../runner/flutter_command.dart'; +const String kOhosSdkEmulatorPath = 'OHOS_EMULATOR_HOME'; + class EmulatorsCommand extends FlutterCommand { EmulatorsCommand() { argParser.addOption('launch', @@ -23,6 +27,10 @@ class EmulatorsCommand extends FlutterCommand { negatable: false); argParser.addOption('name', help: 'Used with the "--create" flag. Specifies a name for the emulator being created.'); + if (globals.platform.isWindows) { + argParser.addFlag('launch-ohos-emulator', + help: 'Launch boot the emulator instance (Ohos only).'); + } } @override @@ -51,6 +59,8 @@ class EmulatorsCommand extends FlutterCommand { await _launchEmulator(stringArg('launch')!, coldBoot: coldBoot); } else if (argumentResults.wasParsed('create')) { await _createEmulator(name: stringArg('name')); + } else if (argumentResults.wasParsed('launch-ohos-emulator')) { + _launchOhosEmulator(); } else { final String? searchText = argumentResults.rest.isNotEmpty @@ -78,6 +88,34 @@ class EmulatorsCommand extends FlutterCommand { } } + void _launchOhosEmulator() { + if (!globals.platform.isWindows) { + return; + } + String? emulatorDirectory = globals.platform.environment[kOhosSdkEmulatorPath]; + + if (emulatorDirectory == null) { + globals.printStatus("Please set OHOS_EMULATOR_HOME.\n"); + return; + } + + final Directory emulatorPath = Directory(emulatorDirectory); + if (!emulatorPath.existsSync()) { + globals.printStatus("ohos emulator cannot found.\n"); + return; + } + final List cmd = []; + cmd.add(globals.fs.path.join(emulatorDirectory, 'emulator', 'Emulator.exe')); + cmd.add('-hvd'); + cmd.add('x86'); + cmd.add('-path'); + cmd.add(globals.fs.path.join(emulatorDirectory, 'hvd')); + + globals.processManager.start(cmd, workingDirectory: emulatorDirectory); + return; + } + + Future _createEmulator({ String? name }) async { final CreateEmulatorResult createResult = await emulatorManager!.createEmulator(name: name); diff --git a/packages/flutter_tools/lib/src/commands/packages.dart b/packages/flutter_tools/lib/src/commands/packages.dart index c6c3eab196b0812707c4c7b52438d9eeddc7bde7..058e0bf67cbb4d927c2c8c4849d63b467ae0b716 100644 --- a/packages/flutter_tools/lib/src/commands/packages.dart +++ b/packages/flutter_tools/lib/src/commands/packages.dart @@ -81,11 +81,11 @@ class PackagesTestCommand extends FlutterCommand { @override String get description { return 'Run the "test" package.\n' - 'This is similar to "flutter test", but instead of hosting the tests in the ' - 'flutter environment it hosts the tests in a pure Dart environment. The main ' - 'differences are that the "dart:ui" library is not available and that tests ' - 'run faster. This is helpful for testing libraries that do not depend on any ' - 'packages from the Flutter SDK. It is equivalent to "pub run test".'; + 'This is similar to "flutter test", but instead of hosting the tests in the ' + 'flutter environment it hosts the tests in a pure Dart environment. The main ' + 'differences are that the "dart:ui" library is not available and that tests ' + 'run faster. This is helpful for testing libraries that do not depend on any ' + 'packages from the Flutter SDK. It is equivalent to "pub run test".'; } @override @@ -121,7 +121,7 @@ class PackagesForwardCommand extends FlutterCommand { @override String get description { return '$_description\n' - 'This runs the "pub" tool in a Flutter context.'; + 'This runs the "pub" tool in a Flutter context.'; } @override @@ -152,7 +152,7 @@ class PackagesPassthroughCommand extends FlutterCommand { @override String get description { return 'Pass the remaining arguments to Dart\'s "pub" tool.\n' - 'This runs the "pub" tool in a Flutter context.'; + 'This runs the "pub" tool in a Flutter context.'; } @override @@ -192,7 +192,7 @@ class PackagesGetCommand extends FlutterCommand { @override String get description { return '$_description\n' - 'This runs the "pub" tool in a Flutter context.'; + 'This runs the "pub" tool in a Flutter context.'; } @override diff --git a/packages/flutter_tools/lib/src/commands/precache.dart b/packages/flutter_tools/lib/src/commands/precache.dart index a5d305eb130c5c13e70b41b04d208e1e5d456a23..e74f7d626ed6dc29863897c816f1537261724445 100644 --- a/packages/flutter_tools/lib/src/commands/precache.dart +++ b/packages/flutter_tools/lib/src/commands/precache.dart @@ -43,6 +43,14 @@ class PrecacheCommand extends FlutterCommand { hide: !verboseHelp); argParser.addFlag('ios', help: 'Precache artifacts for iOS development.'); + argParser.addFlag('ohos', + help: 'Precache artifacts for ohos development.'); + argParser.addFlag('ohos_gen_snapshot', + help: 'Precache gen_snapshot for ohos development.', + hide: !verboseHelp); + argParser.addFlag('ohos_internal_build', + help: 'Precache dependencies for internal ohos development.', + hide: !verboseHelp); argParser.addFlag('web', help: 'Precache artifacts for web development.'); argParser.addFlag('linux', @@ -87,6 +95,10 @@ class PrecacheCommand extends FlutterCommand { 'android_maven', 'android_internal_build', ], + 'ohos': [ + 'ohos_gen_snapshot', + 'ohos_internal_build', + ], }; /// Returns a reverse mapping of _expandedArtifacts, from child artifact name diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart index 88909281f3628fc2a3d5fa0dedf8fa38491e9ca6..ba03a87ce715c6b2040464e40194fcf5b45e5463 100644 --- a/packages/flutter_tools/lib/src/commands/run.dart +++ b/packages/flutter_tools/lib/src/commands/run.dart @@ -641,7 +641,7 @@ class RunCommand extends RunCommandBase { .every((Device device) => device.supportsFlavors); if (flavor != null && !flavorsSupportedOnEveryDevice) { globals.printWarning( - '--flavor is only supported for Android, macOS, and iOS devices. ' + '--flavor is only supported for Android, macOS, iOS and ohos devices. ' 'Flavor-related features may not function properly and could ' 'behave differently in a future release.' ); diff --git a/packages/flutter_tools/lib/src/commands/shell_completion.dart b/packages/flutter_tools/lib/src/commands/shell_completion.dart index 5fe8de30133d8b206ab652b49b97664c98c25ae2..fc5eaba31e802752cc59e13d46f63d7d7ade5dba 100644 --- a/packages/flutter_tools/lib/src/commands/shell_completion.dart +++ b/packages/flutter_tools/lib/src/commands/shell_completion.dart @@ -57,7 +57,7 @@ class ShellCompletionCommand extends FlutterCommand { if (outputFile.existsSync() && !boolArg('overwrite')) { throwToolExit( 'Output file ${outputFile.path} already exists, will not overwrite. ' - 'Use --overwrite to force overwriting existing output file.', + 'Use --overwrite to force overwriting existing output file.', exitCode: 1, ); } diff --git a/packages/flutter_tools/lib/src/commands/upgrade.dart b/packages/flutter_tools/lib/src/commands/upgrade.dart index d9918e385efdc20aa9170f3459e8586de7c78374..c1d1c27f849938f49da1b3ef39948ff0e4a9a19b 100644 --- a/packages/flutter_tools/lib/src/commands/upgrade.dart +++ b/packages/flutter_tools/lib/src/commands/upgrade.dart @@ -72,6 +72,7 @@ class UpgradeCommand extends FlutterCommand { @override Future runCommand() { + throwToolExit('It will be supported later.', exitCode: 1); _commandRunner.workingDirectory = stringArg('working-directory') ?? Cache.flutterRoot!; return _commandRunner.runCommand( force: boolArg('force'), diff --git a/packages/flutter_tools/lib/src/compile.dart b/packages/flutter_tools/lib/src/compile.dart index 78db064093842e29d8300b53aad3c4dac269ff20..276661cb2f21124b6f762e37d56117fab4974639 100644 --- a/packages/flutter_tools/lib/src/compile.dart +++ b/packages/flutter_tools/lib/src/compile.dart @@ -15,9 +15,11 @@ import 'base/common.dart'; import 'base/file_system.dart'; import 'base/io.dart'; import 'base/logger.dart'; +import 'base/os.dart'; import 'base/platform.dart'; import 'build_info.dart'; import 'convert.dart'; +import 'globals.dart' as globals; /// Opt-in changes to the dart compilers. const List kDartCompilerExperiments = [ diff --git a/packages/flutter_tools/lib/src/context_runner.dart b/packages/flutter_tools/lib/src/context_runner.dart index 0e0e12ecf40592783657fead70adfe7d951b5bc7..1c8e505408b426ce9f94ca0c237d05a836b1cae8 100644 --- a/packages/flutter_tools/lib/src/context_runner.dart +++ b/packages/flutter_tools/lib/src/context_runner.dart @@ -55,6 +55,12 @@ import 'macos/macos_workflow.dart'; import 'macos/xcdevice.dart'; import 'macos/xcode.dart'; import 'mdns_discovery.dart'; +import 'ohos/hvigor.dart'; +import 'ohos/hvigor_utils.dart'; +import 'ohos/ohos_builder.dart'; +import 'ohos/ohos_doctor.dart'; +import 'ohos/ohos_sdk.dart'; +import 'ohos/ohos_workflow.dart'; import 'persistent_tool_state.dart'; import 'reporting/crash_reporting.dart'; import 'reporting/first_run.dart'; @@ -118,6 +124,18 @@ Future runInContext( stdio: globals.stdio, ), AndroidSdk: AndroidSdk.locateAndroidSdk, + OhosSdk: OhosSdk.localOhosSdk, + HmosSdk: HmosSdk.localHmosSdk, + HarmonySdk: HarmonySdk.locateHarmonySdk, + OhosBuilder:()=> OhosHvigorBuilder( + logger: globals.logger, + processManager: globals.processManager, + fileSystem: globals.fs, + artifacts: globals.artifacts!, + usage: globals.flutterUsage, + hvigorUtils: globals.hvigorUtils!, + platform: globals.platform, + ), AndroidStudio: AndroidStudio.latestValid, AndroidValidator: () => AndroidValidator( java: globals.java, @@ -126,6 +144,17 @@ Future runInContext( platform: globals.platform, userMessages: globals.userMessages, ), + OhosValidator: () => OhosValidator( + ohosSdk: globals.harmonySdk, + fileSystem: globals.fs, + logger: globals.logger, + platform: globals.platform, + processManager: globals.processManager, + userMessages: globals.userMessages), + OhosWorkflow: () => OhosWorkflow( + ohosSdk: globals.harmonySdk, + featureFlags: featureFlags, + ), AndroidWorkflow: () => AndroidWorkflow( androidSdk: globals.androidSdk, featureFlags: featureFlags, @@ -136,6 +165,7 @@ Future runInContext( logger: globals.logger, fileSystem: globals.fs, androidSdk: globals.androidSdk, + ohosSdk: globals.harmonySdk, ), Artifacts: () => CachedArtifacts( fileSystem: globals.fs, @@ -197,12 +227,14 @@ Future runInContext( processManager: globals.processManager, platform: globals.platform, androidSdk: globals.androidSdk, + ohosSdk: globals.harmonySdk, iosSimulatorUtils: globals.iosSimulatorUtils!, featureFlags: featureFlags, fileSystem: globals.fs, iosWorkflow: globals.iosWorkflow!, artifacts: globals.artifacts!, flutterVersion: globals.flutterVersion, + ohosWorkflow: ohosWorkflow!, androidWorkflow: androidWorkflow!, fuchsiaWorkflow: fuchsiaWorkflow!, xcDevice: globals.xcdevice!, @@ -258,6 +290,7 @@ Future runInContext( platform: globals.platform, cache: globals.cache, ), + HvigorUtils:() => HvigorUtils(), HotRunnerConfig: () => HotRunnerConfig(), IOSSimulatorUtils: () => IOSSimulatorUtils( logger: globals.logger, diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart index 630abe646c9a6023aaa4da773ec9d95fa1d2f272..7a87f8bfc93bf8651cf3ced0938db0c83829162c 100644 --- a/packages/flutter_tools/lib/src/device.dart +++ b/packages/flutter_tools/lib/src/device.dart @@ -53,6 +53,7 @@ enum PlatformType { macos, windows, fuchsia, + ohos, custom, windowsPreview; diff --git a/packages/flutter_tools/lib/src/doctor.dart b/packages/flutter_tools/lib/src/doctor.dart index cef3c7190447511c61e744e84bbfdf60f5970606..a1154ca10d8eb49c5781fbc928bc7d668ad733dc 100644 --- a/packages/flutter_tools/lib/src/doctor.dart +++ b/packages/flutter_tools/lib/src/doctor.dart @@ -36,6 +36,7 @@ import 'linux/linux_doctor.dart'; import 'linux/linux_workflow.dart'; import 'macos/macos_workflow.dart'; import 'macos/xcode_validator.dart'; +import 'ohos/ohos_doctor.dart'; import 'proxy_validator.dart'; import 'reporting/reporting.dart'; import 'tester/flutter_tester.dart'; @@ -139,6 +140,7 @@ class _DefaultDoctorValidatorsProvider implements DoctorValidatorsProvider { flutterRoot: () => Cache.flutterRoot!, operatingSystemUtils: globals.os, ), + ohosValidator!, //OhosValidator if (platform.isWindows) WindowsVersionValidator( operatingSystemUtils: globals.os, diff --git a/packages/flutter_tools/lib/src/features.dart b/packages/flutter_tools/lib/src/features.dart index dae623f31dc2de8f1ffa6a1e600f72551c184a79..e11c1a4d75ed4103d4e1ea892ae5bcfb35f78d0d 100644 --- a/packages/flutter_tools/lib/src/features.dart +++ b/packages/flutter_tools/lib/src/features.dart @@ -18,6 +18,9 @@ abstract class FeatureFlags { /// const constructor so that subclasses can be const. const FeatureFlags(); + /// Whether flutter desktop for ohos is enabled. + bool get isOhosEnabled => true; + /// Whether flutter desktop for linux is enabled. bool get isLinuxEnabled => false; @@ -59,6 +62,7 @@ abstract class FeatureFlags { /// All current Flutter feature flags. const List allFeatures = [ + flutterOhosFeature, flutterWebFeature, flutterLinuxDesktopFeature, flutterMacOSDesktopFeature, @@ -105,6 +109,24 @@ const Feature flutterWindowsDesktopFeature = Feature.fullyEnabled( environmentOverride: 'FLUTTER_WINDOWS', ); +/// The [Feature] for Ohos devices. +const Feature flutterOhosFeature = Feature( + name: 'Flutter for Ohos', + configSetting: 'enable-ohos', + master: FeatureChannelSetting( + available: true, + enabledByDefault: true, + ), + beta: FeatureChannelSetting( + available: true, + enabledByDefault: true, + ), + stable: FeatureChannelSetting( + available: true, + enabledByDefault: true, + ), +); + /// The [Feature] for Android devices. const Feature flutterAndroidFeature = Feature.fullyEnabled( name: 'Flutter for Android', @@ -185,15 +207,14 @@ const Feature previewDevice = Feature( /// settings. class Feature { /// Creates a [Feature]. - const Feature({ - required this.name, - this.environmentOverride, - this.configSetting, - this.extraHelpText, - this.master = const FeatureChannelSetting(), - this.beta = const FeatureChannelSetting(), - this.stable = const FeatureChannelSetting() - }); + const Feature( + {required this.name, + this.environmentOverride, + this.configSetting, + this.extraHelpText, + this.master = const FeatureChannelSetting(), + this.beta = const FeatureChannelSetting(), + this.stable = const FeatureChannelSetting()}); /// Creates a [Feature] that is fully enabled across channels. const Feature.fullyEnabled( diff --git a/packages/flutter_tools/lib/src/flutter_application_package.dart b/packages/flutter_tools/lib/src/flutter_application_package.dart index f605c19575dc6183ac86a78d6095e1d90a47ab0e..6a7573c12a874c4dd02a51c8807c34cbc4da3be3 100644 --- a/packages/flutter_tools/lib/src/flutter_application_package.dart +++ b/packages/flutter_tools/lib/src/flutter_application_package.dart @@ -17,6 +17,8 @@ import 'globals.dart' as globals; import 'ios/application_package.dart'; import 'linux/application_package.dart'; import 'macos/application_package.dart'; +import 'ohos/application_package.dart'; +import 'ohos/ohos_sdk.dart'; import 'project.dart'; import 'tester/flutter_tester.dart'; import 'web/web_device.dart'; @@ -26,19 +28,22 @@ import 'windows/application_package.dart'; class FlutterApplicationPackageFactory extends ApplicationPackageFactory { FlutterApplicationPackageFactory({ required AndroidSdk? androidSdk, + required HarmonySdk? ohosSdk, required ProcessManager processManager, required Logger logger, required UserMessages userMessages, required FileSystem fileSystem, - }) : _androidSdk = androidSdk, - _processManager = processManager, - _logger = logger, - _userMessages = userMessages, - _fileSystem = fileSystem, - _processUtils = ProcessUtils(logger: logger, processManager: processManager); - + }) : _androidSdk = androidSdk, + _ohosSdk = ohosSdk, + _processManager = processManager, + _logger = logger, + _userMessages = userMessages, + _fileSystem = fileSystem, + _processUtils = + ProcessUtils(logger: logger, processManager: processManager); final AndroidSdk? _androidSdk; + final HarmonySdk? _ohosSdk; final ProcessManager _processManager; final Logger _logger; final ProcessUtils _processUtils; @@ -79,7 +84,8 @@ class FlutterApplicationPackageFactory extends ApplicationPackageFactory { ); case TargetPlatform.ios: return applicationBinary == null - ? await IOSApp.fromIosProject(FlutterProject.current().ios, buildInfo) + ? await IOSApp.fromIosProject( + FlutterProject.current().ios, buildInfo) : IOSApp.fromPrebuiltApp(applicationBinary); case TargetPlatform.tester: return FlutterTesterApp.fromCurrentDirectory(globals.fs); @@ -107,6 +113,30 @@ class FlutterApplicationPackageFactory extends ApplicationPackageFactory { return applicationBinary == null ? FuchsiaApp.fromFuchsiaProject(FlutterProject.current().fuchsia) : FuchsiaApp.fromPrebuiltApp(applicationBinary); + case TargetPlatform.ohos: + case TargetPlatform.ohos_arm64: + case TargetPlatform.ohos_arm: + case TargetPlatform.ohos_x64: + if (applicationBinary == null) { + return OhosHap.fromOhosProject( + FlutterProject.current().ohos, + processManager: _processManager, + processUtils: _processUtils, + logger: _logger, + ohosSdk: _ohosSdk, + userMessages: _userMessages, + fileSystem: _fileSystem, + buildInfo: buildInfo, + ); + } + return OhosHap.fromHap( + applicationBinary, + ohosSdk: _ohosSdk!, + processManager: _processManager, + logger: _logger, + userMessages: _userMessages, + processUtils: _processUtils, + ); } } } diff --git a/packages/flutter_tools/lib/src/flutter_cache.dart b/packages/flutter_tools/lib/src/flutter_cache.dart index 9b2c51be5f5660b642dfdbd4771d4378cf2ce212..d073bfc331a8f78b18367a6722840300d0723710 100644 --- a/packages/flutter_tools/lib/src/flutter_cache.dart +++ b/packages/flutter_tools/lib/src/flutter_cache.dart @@ -37,6 +37,8 @@ class FlutterCache extends Cache { registerArtifact(AndroidGenSnapshotArtifacts(this, platform: platform)); registerArtifact(AndroidInternalBuildArtifacts(this)); registerArtifact(IOSEngineArtifacts(this, platform: platform)); + registerArtifact(OHOSGenSnapshotArtifacts(this, platform: platform)); + registerArtifact(OHOSInternalBuildArtifacts(this)); registerArtifact(FlutterWebSdk(this)); registerArtifact(LegacyCanvasKitRemover(this)); registerArtifact(FlutterSdk(this, platform: platform)); @@ -510,6 +512,79 @@ class IOSEngineArtifacts extends EngineCachedArtifact { } } +/// The artifact used to generate snapshots for Ohos builds. +class OHOSGenSnapshotArtifacts extends EngineCachedArtifact { + OHOSGenSnapshotArtifacts(Cache cache, { + required Platform platform, + }) : _platform = platform, + super( + 'ohos-sdk', + cache, + DevelopmentArtifact.ohosGenSnapshot, + ); + + final Platform _platform; + + @override + List getPackageDirs() => const []; + + @override + List> getBinaryDirs() { + return >[ + if (cache.includeAllPlatforms) ...>[ + ..._osxBinaryDirsForOhos, + ..._linuxBinaryDirsForOhos, + ..._windowsBinaryDirsForOhos, + ..._dartSdks + ] else if (_platform.isWindows) + ..._windowsBinaryDirsForOhos + else if (_platform.isMacOS) + ..._osxBinaryDirsForOhos + else if (_platform.isLinux) + ..._linuxBinaryDirsForOhos, + ..._ohosBinaryDirs, + ]; + } + + @override + List getLicenseDirs() { + return []; + } + + @override + String? get version => cache.getVersionFor('engine.ohos'); + + @override + String get storageBaseUrl => cache.ohosStorageBaseUrl; +} + +class OHOSInternalBuildArtifacts extends EngineCachedArtifact { + OHOSInternalBuildArtifacts(Cache cache) : super( + 'ohos-internal-build-artifacts', + cache, + DevelopmentArtifact.ohosInternalBuild, + ); + + @override + List getPackageDirs() => const []; + + @override + List> getBinaryDirs() { + return _ohosBinaryDirs; + } + + @override + List getLicenseDirs() { + return []; + } + + @override + String get storageBaseUrl => cache.ohosStorageBaseUrl; + + @override + String? get version => cache.getVersionFor('engine.ohos'); +} + /// A cached artifact containing Gradle Wrapper scripts and binaries. /// /// While this is only required for Android, we need to always download it due @@ -915,6 +990,30 @@ const List> _androidBinaryDirs = >[ ['android-x86-jit-release', 'android-x86-jit-release/artifacts.zip'], ]; +const List> _osxBinaryDirsForOhos = >[ + ['ohos-arm64-profile/darwin-x64', 'ohos-arm64-profile/darwin-x64.zip'], + ['ohos-arm64-release/darwin-x64', 'ohos-arm64-release/darwin-x64.zip'], +]; + +const List> _linuxBinaryDirsForOhos = >[ + ['ohos-arm64-profile/linux-x64', 'ohos-arm64-profile/linux-x64.zip'], + ['ohos-arm64-release/linux-x64', 'ohos-arm64-release/linux-x64.zip'], +]; + +const List> _windowsBinaryDirsForOhos = >[ + ['ohos-arm64-profile/windows-x64', 'ohos-arm64-profile/windows-x64.zip'], + ['ohos-arm64-release/windows-x64', 'ohos-arm64-release/windows-x64.zip'], +]; + +const List> _ohosBinaryDirs = >[ + ['ohos-arm64', 'ohos-arm64/artifacts.zip'], + ['ohos-arm64-profile', 'ohos-arm64-profile/artifacts.zip'], + ['ohos-arm64-release', 'ohos-arm64-release/artifacts.zip'], + // ['ohos-x64', 'ohos-x64/artifacts.zip'], + // ['ohos-x64-profile', 'ohos-x64-profile/artifacts.zip'], + // ['ohos-x64-release', 'ohos-x64-release/artifacts.zip'], +]; + const List> _dartSdks = > [ ['darwin-x64', 'dart-sdk-darwin-x64.zip'], ['linux-x64', 'dart-sdk-linux-x64.zip'], diff --git a/packages/flutter_tools/lib/src/flutter_device_manager.dart b/packages/flutter_tools/lib/src/flutter_device_manager.dart index 3b9558e53d0f5a46c9ccffb4d50ce16047143e8f..92bb0f2319847ffd031fd99d97ac6af5e942e85e 100644 --- a/packages/flutter_tools/lib/src/flutter_device_manager.dart +++ b/packages/flutter_tools/lib/src/flutter_device_manager.dart @@ -28,6 +28,9 @@ import 'macos/macos_ipad_device.dart'; import 'macos/macos_workflow.dart'; import 'macos/xcdevice.dart'; import 'preview_device.dart'; +import 'ohos/ohos_device_discovery.dart'; +import 'ohos/ohos_sdk.dart'; +import 'ohos/ohos_workflow.dart'; import 'tester/flutter_tester.dart'; import 'version.dart'; import 'web/web_device.dart'; @@ -43,10 +46,12 @@ class FlutterDeviceManager extends DeviceManager { required ProcessManager processManager, required FileSystem fileSystem, required AndroidSdk? androidSdk, + required HarmonySdk? ohosSdk, required FeatureFlags featureFlags, required IOSSimulatorUtils iosSimulatorUtils, required XCDevice xcDevice, required AndroidWorkflow androidWorkflow, + required OhosWorkflow ohosWorkflow, required IOSWorkflow iosWorkflow, required FuchsiaWorkflow fuchsiaWorkflow, required FlutterVersion flutterVersion, @@ -67,6 +72,15 @@ class FlutterDeviceManager extends DeviceManager { platform: platform, userMessages: userMessages, ), + OhosDevices( + logger: logger, + ohosSdk: ohosSdk, + processManager: processManager, + fileSystem: fileSystem, + platform: platform, + userMessages: userMessages, + ohosWorkflow: ohosWorkflow, + ), IOSDevices( platform: platform, xcdevice: xcDevice, diff --git a/packages/flutter_tools/lib/src/flutter_features.dart b/packages/flutter_tools/lib/src/flutter_features.dart index f9e961c219a59663e769a15f505d4684504b081f..e0fa6888b136304d9b9390e0d78a8f0a80877746 100644 --- a/packages/flutter_tools/lib/src/flutter_features.dart +++ b/packages/flutter_tools/lib/src/flutter_features.dart @@ -12,14 +12,17 @@ class FlutterFeatureFlags implements FeatureFlags { required FlutterVersion flutterVersion, required Config config, required Platform platform, - }) : _flutterVersion = flutterVersion, - _config = config, - _platform = platform; + }) : _flutterVersion = flutterVersion, + _config = config, + _platform = platform; final FlutterVersion _flutterVersion; final Config _config; final Platform _platform; + @override + bool get isOhosEnabled => isEnabled(flutterOhosFeature); + @override bool get isLinuxEnabled => isEnabled(flutterLinuxDesktopFeature); @@ -61,19 +64,22 @@ class FlutterFeatureFlags implements FeatureFlags { @override bool isEnabled(Feature feature) { final String currentChannel = _flutterVersion.channel; - final FeatureChannelSetting featureSetting = feature.getSettingForChannel(currentChannel); + final FeatureChannelSetting featureSetting = + feature.getSettingForChannel(currentChannel); if (!featureSetting.available) { return false; } bool isEnabled = featureSetting.enabledByDefault; if (feature.configSetting != null) { - final bool? configOverride = _config.getValue(feature.configSetting!) as bool?; + final bool? configOverride = + _config.getValue(feature.configSetting!) as bool?; if (configOverride != null) { isEnabled = configOverride; } } if (feature.environmentOverride != null) { - if (_platform.environment[feature.environmentOverride]?.toLowerCase() == 'true') { + if (_platform.environment[feature.environmentOverride]?.toLowerCase() == + 'true') { isEnabled = true; } } diff --git a/packages/flutter_tools/lib/src/flutter_manifest.dart b/packages/flutter_tools/lib/src/flutter_manifest.dart index 736e00ba02eaa95b008e83b77bd2cc3a3fc10fc6..25e42b12708a915d24496fdd90551fc260044315 100644 --- a/packages/flutter_tools/lib/src/flutter_manifest.dart +++ b/packages/flutter_tools/lib/src/flutter_manifest.dart @@ -17,7 +17,7 @@ import 'plugins.dart'; const bool kIs3dSceneSupported = true; const Set _kValidPluginPlatforms = { - 'android', 'ios', 'web', 'windows', 'linux', 'macos', + 'android', 'ios', 'web', 'windows', 'linux', 'macos','ohos', }; /// A wrapper around the `flutter` section in the `pubspec.yaml` file. @@ -214,6 +214,34 @@ class FlutterManifest { return null; } + /// Returns the OpenHarmony package declared by this manifest in its + /// module or plugin descriptor. Returns null, if there is no + /// such declaration. + String? get ohosPackage { + if (isModule) { + final Object? module = _flutterDescriptor['module']; + if (module is YamlMap) { + return module['ohosPackage'] as String?; + } + } + final Map? platforms = supportedPlatforms; + if (platforms == null) { + // Pre-multi-platform plugin format + if (isPlugin) { + final YamlMap? plugin = _flutterDescriptor['plugin'] as YamlMap?; + return plugin?['ohosPackage'] as String?; + } + return null; + } + if (platforms.containsKey('ohos')) { + final Object? ohos = platforms['ohos']; + if (ohos is YamlMap) { + return ohos['package'] as String?; + } + } + return null; + } + /// Returns the deferred components configuration if declared. Returns /// null if no deferred components are declared. late final List? deferredComponents = computeDeferredComponents(); diff --git a/packages/flutter_tools/lib/src/flutter_plugins.dart b/packages/flutter_tools/lib/src/flutter_plugins.dart index 693ec77a603b24b9e9d28d6d07cf8b239c75fe28..f94c75a67366f6fceafab9a9ed395ab792f67937 100644 --- a/packages/flutter_tools/lib/src/flutter_plugins.dart +++ b/packages/flutter_tools/lib/src/flutter_plugins.dart @@ -22,6 +22,7 @@ import 'dart/language_version.dart'; import 'dart/package_map.dart'; import 'features.dart'; import 'globals.dart' as globals; +import 'ohos/ohos_plugins_manager.dart'; import 'platform_plugins.dart'; import 'plugins.dart'; import 'project.dart'; @@ -133,7 +134,8 @@ const String _kFlutterPluginsSharedDarwinSource = 'shared_darwin_source'; /// "macos": [], /// "linux": [], /// "windows": [], -/// "web": [] +/// "web": [], +/// "ohos": [] /// }, /// "dependencyGraph": [ /// { @@ -173,6 +175,7 @@ bool _writeFlutterPluginsList(FlutterProject project, List plugins) { project.linux.pluginConfigKey, project.windows.pluginConfigKey, project.web.pluginConfigKey, + project.ohos.pluginConfigKey, ]; final Map pluginsMap = {}; @@ -419,6 +422,39 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { } '''; +const String _arktsPluginRegistryTemplate = ''' +import { FlutterEngine, Log } from '@ohos/flutter_ohos'; +{{#methodChannelPlugins}} +import {{class}} from '{{name}}'; +{{/methodChannelPlugins}} + +/** + * 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 { + {{#methodChannelPlugins}} + flutterEngine.getPlugins()?.add(new {{class}}()); + {{/methodChannelPlugins}} + } catch (e) { + Log.e( + TAG, + "Tried to register plugins with FlutterEngine (" + + flutterEngine + + ") failed."); + Log.e(TAG, "Received exception while registering", e); + } + } +} +'''; + const String _pluginRegistrantPodspecTemplate = ''' # # Generated file, do not edit. @@ -629,6 +665,9 @@ import 'package:{{pluginName}}/{{pluginName}}.dart'; {{#windows}} import 'package:{{pluginName}}/{{pluginName}}.dart'; {{/windows}} +{{#ohos}} +import 'package:{{pluginName}}/{{pluginName}}.dart'; +{{/ohos}} @pragma('vm:entry-point') class _PluginRegistrant { @@ -655,6 +694,10 @@ $_dartPluginRegisterWith {{#windows}} $_dartPluginRegisterWith {{/windows}} + } else if (Platform.operatingSystem == 'ohos') { + {{#ohos}} +$_dartPluginRegisterWith + {{/ohos}} } } } @@ -749,6 +792,24 @@ Future _writePluginCmakefile(File destinationFile, Map tem ); } +Future _writeOhosPluginRegistrant(FlutterProject project, List plugins) async{ + /// 检查依赖 + await checkOhosPluginsDependencies(project); + final List methodChannelPlugins = _filterMethodChannelPlugins(plugins, OhosPlugin.kConfigKey); + final List> ohosMethodChannelPlugins = _extractPlatformMaps(methodChannelPlugins, OhosPlugin.kConfigKey); + final Map context = { + 'os': 'ohos', + 'methodChannelPlugins': ohosMethodChannelPlugins, + }; + await _renderTemplateToFile( + _arktsPluginRegistryTemplate, + context, + project.ohos.managedDirectory.childFile('GeneratedPluginRegistrant.ets'), + globals.templateRenderer, + ); +} + + Future _writeMacOSPluginRegistrant(FlutterProject project, List plugins) async { final List methodChannelPlugins = _filterMethodChannelPlugins(plugins, MacOSPlugin.kConfigKey); final List> macosMethodChannelPlugins = _extractPlatformMaps(methodChannelPlugins, MacOSPlugin.kConfigKey); @@ -1069,6 +1130,7 @@ Future injectPlugins( bool macOSPlatform = false, bool windowsPlatform = false, Iterable? allowedPlugins, + bool ohosPlatfrom = false, }) async { final List plugins = await findPlugins(project); // Sort the plugins by name to keep ordering stable in generated files. @@ -1088,6 +1150,9 @@ Future injectPlugins( if (windowsPlatform) { await writeWindowsPluginFiles(project, plugins, globals.templateRenderer, allowedPlugins: allowedPlugins); } + if (ohosPlatfrom) { + await _writeOhosPluginRegistrant(project, plugins); + } if (!project.isModule) { final List darwinProjects = [ if (iosPlatform) project.ios, @@ -1136,6 +1201,7 @@ List resolvePlatformImplementation( LinuxPlugin.kConfigKey, MacOSPlugin.kConfigKey, WindowsPlugin.kConfigKey, + OhosPlugin.kConfigKey, ]; final Map> possibleResolutions = >{}; @@ -1296,6 +1362,7 @@ Future generateMainDartWithPluginRegistrant( LinuxPlugin.kConfigKey: [], MacOSPlugin.kConfigKey: [], WindowsPlugin.kConfigKey: [], + OhosPlugin.kConfigKey: [], }; final File newMainDart = rootProject.dartPluginRegistrant; if (resolutions.isEmpty) { diff --git a/packages/flutter_tools/lib/src/globals.dart b/packages/flutter_tools/lib/src/globals.dart index bb39d9b14d2add3853e67ea440064558e8a70cf8..45c0fc9cce75eb254541c6bc4856760188f41b1d 100644 --- a/packages/flutter_tools/lib/src/globals.dart +++ b/packages/flutter_tools/lib/src/globals.dart @@ -41,6 +41,8 @@ import 'macos/cocoapods.dart'; import 'macos/cocoapods_validator.dart'; import 'macos/xcdevice.dart'; import 'macos/xcode.dart'; +import 'ohos/hvigor_utils.dart'; +import 'ohos/ohos_sdk.dart'; import 'persistent_tool_state.dart'; import 'pre_run_validator.dart'; import 'project.dart'; @@ -69,6 +71,9 @@ OperatingSystemUtils get os => context.get()!; Signals get signals => context.get() ?? LocalSignals.instance; AndroidStudio? get androidStudio => context.get(); AndroidSdk? get androidSdk => context.get(); +OhosSdk? get ohosSdk => context.get(); +HmosSdk? get hmosSdk => context.get(); +HarmonySdk? get harmonySdk => context.get(); FlutterVersion get flutterVersion => context.get()!; FuchsiaArtifacts? get fuchsiaArtifacts => context.get(); FuchsiaSdk? get fuchsiaSdk => context.get(); @@ -279,6 +284,8 @@ LocalFileSystem get localFileSystem => _instance ??= LocalFileSystem( /// Gradle utils in the current [AppContext]. GradleUtils? get gradleUtils => context.get(); +HvigorUtils? get hvigorUtils => context.get(); + CocoaPods? get cocoaPods => context.get(); FlutterProjectFactory get projectFactory { diff --git a/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart b/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart index f644246fda581c524454321a97e52d1c704e2edc..98c26d52e9df8d53b3faf7b871c5e588c46f997c 100644 --- a/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart +++ b/packages/flutter_tools/lib/src/isolated/native_assets/native_assets.dart @@ -428,6 +428,10 @@ Future dryRunNativeAssets({ fileSystem: fileSystem, buildRunner: buildRunner, ); + case build_info.TargetPlatform.ohos: + case build_info.TargetPlatform.ohos_arm: + case build_info.TargetPlatform.ohos_arm64: + case build_info.TargetPlatform.ohos_x64://TODO: adapt ohos case build_info.TargetPlatform.fuchsia_arm64: case build_info.TargetPlatform.fuchsia_x64: case build_info.TargetPlatform.web_javascript: @@ -502,6 +506,7 @@ Future dryRunNativeAssetsMultipleOSes({ projectUri, buildRunner, ), + // TODO: adapt Ohos ]; final Uri nativeAssetsUri = await writeNativeAssetsYaml( KernelAssets(nativeAssetPaths), @@ -724,6 +729,10 @@ Target _getNativeTarget(build_info.TargetPlatform targetPlatform) { case build_info.TargetPlatform.android_arm64: case build_info.TargetPlatform.android_x64: case build_info.TargetPlatform.android_x86: + case build_info.TargetPlatform.ohos: + case build_info.TargetPlatform.ohos_arm: + case build_info.TargetPlatform.ohos_arm64: + case build_info.TargetPlatform.ohos_x64: throw Exception('Unknown targetPlatform: $targetPlatform.'); } } diff --git a/packages/flutter_tools/lib/src/mdns_discovery.dart b/packages/flutter_tools/lib/src/mdns_discovery.dart index a2f0decf7fb22cac32e5cc1128e4c9fd17721d7c..d7c849b1a45c794a7d7907eb2bc9291ee585c199 100644 --- a/packages/flutter_tools/lib/src/mdns_discovery.dart +++ b/packages/flutter_tools/lib/src/mdns_discovery.dart @@ -516,6 +516,11 @@ class MDnsVmServiceDiscovery { 'under System Preferences > Network > iPhone USB. ' 'See https://github.com/flutter/flutter/issues/46698 for details.' ); + break; + case TargetPlatform.ohos: + case TargetPlatform.ohos_arm: + case TargetPlatform.ohos_arm64: + case TargetPlatform.ohos_x64: case TargetPlatform.android: case TargetPlatform.android_arm: case TargetPlatform.android_arm64: diff --git a/packages/flutter_tools/lib/src/ohos/application_package.dart b/packages/flutter_tools/lib/src/ohos/application_package.dart new file mode 100644 index 0000000000000000000000000000000000000000..2529a22372c5fd77d2f9ac3af6ba9dd43f0d491b --- /dev/null +++ b/packages/flutter_tools/lib/src/ohos/application_package.dart @@ -0,0 +1,303 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development 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 'package:json5/json5.dart'; +import 'package:process/process.dart'; + +import '../application_package.dart'; +import '../base/common.dart'; +import '../base/file_system.dart'; +import '../base/logger.dart'; +import '../base/process.dart'; +import '../base/user_messages.dart'; +import '../build_info.dart'; +import '../globals.dart' as globals; +import '../project.dart'; +import 'hvigor_utils.dart'; +import 'ohos_plugins_manager.dart'; +import 'ohos_sdk.dart'; + +const String OHOS_ENTRY_DEFAULT = 'entry'; +const int OHOS_SDK_INT_DEFAULT = 11; + +/// An application package created from an already built Ohos HAP. +class OhosHap extends ApplicationPackage implements PrebuiltApplicationPackage { + OhosHap({ + required super.id, + required this.applicationPackage, + required this.ohosBuildData, + }) : assert(applicationPackage != null), + assert(ohosBuildData != null); + + @override + final FileSystemEntity applicationPackage; + + OhosBuildData ohosBuildData; + + @override + String? get name => applicationPackage.basename; + + /// Creates a new OhosHap based on the information in the Ohos build-profile. + static Future fromOhosProject( + OhosProject ohosProject, { + required HarmonySdk? ohosSdk, + required ProcessManager processManager, + required UserMessages userMessages, + required ProcessUtils processUtils, + required Logger logger, + required FileSystem fileSystem, + BuildInfo? buildInfo, + }) async { + /// parse the build data + final OhosBuildData ohosBuildData = + OhosBuildData.parseOhosBuildData(ohosProject, logger); + final String flavor = getFlavor(ohosProject.getBuildProfileFile(), buildInfo?.flavor); + String bundleName = ohosBuildData.appInfo!.bundleName; + final List? products = ohosBuildData.products; + if (products != null) { + for (final dynamic item in products) { + final Map productItem = item as Map; + if (flavor == productItem['name'] && productItem['bundleName'] != null) { + bundleName = productItem['bundleName'] as String; + ohosBuildData.appInfo!.bundleName = bundleName; + break; + } + } + } + for (final OhosModule element in ohosBuildData.moduleInfo.moduleList) { + element.flavor = flavor; + } + return OhosHap( + id: bundleName, + applicationPackage: ohosProject.getSignedHapFile(flavor), + ohosBuildData: ohosBuildData); + } + + static Future fromHap( + File hap, { + required HarmonySdk ohosSdk, + required ProcessManager processManager, + required UserMessages userMessages, + required Logger logger, + required ProcessUtils processUtils, + }) async { + // TODO(xc) parse build data from hap file + return null; + } +} + +/// OpenHarmony的构建信息 +class OhosBuildData { + OhosBuildData(this.appInfo, this.moduleInfo, this.apiVersion, this.products); + + late AppInfo? appInfo; + late ModuleInfo moduleInfo; + late int apiVersion; + List? products; + + bool get hasEntryModule => false; + + List get harModules { + return moduleInfo.moduleList + .where((OhosModule e) => e.type == OhosModuleType.har) + .toList(); + } + + static OhosBuildData parseOhosBuildData( + OhosProject ohosProject, Logger? logger) { + late AppInfo appInfo; + late ModuleInfo moduleInfo; + late int apiVersion; + List? products; + try { + final File appJson = ohosProject.getAppJsonFile(); + if (appJson.existsSync()) { + final String json = appJson.readAsStringSync(); + final dynamic obj = JSON5.parse(json); + appInfo = AppInfo.getAppInfo(obj); + } else { + appInfo = AppInfo('', 1, ''); + } + } on Exception catch (err) { + throwToolExit('Parse ohos app.json5 error: $err'); + } + + try { + moduleInfo = ModuleInfo.getModuleInfo(ohosProject); + } on Exception catch(err) { + throwToolExit('Parse ohos module.json5 error: $err'); + } + + try { + final File buildProfileFile = ohosProject.getBuildProfileFile(); + if (buildProfileFile.existsSync()) { + final String buildProfileConfig = buildProfileFile.readAsStringSync(); + final dynamic obj = JSON5.parse(buildProfileConfig); + apiVersion = getApiVersion(obj); + // ignore: avoid_dynamic_calls + if (obj['app'] != null && obj['app']['products'] != null) { + // ignore: avoid_dynamic_calls + products = obj['app']['products'] as List; + } + } else { + apiVersion = OHOS_SDK_INT_DEFAULT; + } + } on Exception catch (err) { + throwToolExit('Parse ohos build-profile.json5 error: $err'); + } + return OhosBuildData(appInfo, moduleInfo, apiVersion, products); + } +} + +int getApiVersion(dynamic obj) { + // ignore: avoid_dynamic_calls + dynamic sdkObj = obj['app']?['compatibleSdkVersion']; + // ignore: avoid_dynamic_calls + sdkObj ??= obj['app']?['products'][0]['compatibleSdkVersion']; + if (sdkObj is int) { + return sdkObj; + } else if (sdkObj is String && sdkObj != null) { // 4.1.0(11) + String? str = RegExp(r'\(\d+\)').stringMatch(sdkObj); + if (str != null) { + str = str.substring(1, str.length - 1); + return int.parse(str); + } + } + return OHOS_SDK_INT_DEFAULT; +} + +class AppInfo { + AppInfo(this.bundleName, this.versionCode, this.versionName); + + late String bundleName; + late int versionCode; + late String versionName; + + static AppInfo getAppInfo(dynamic app) { + final String bundleName = app['app']['bundleName'] as String; + final int versionCode = app['app']['versionCode'] as int; + final String versionName = app['app']['versionName'] as String; + return AppInfo(bundleName, versionCode, versionName); + } +} + +class ModuleInfo { + ModuleInfo(this.moduleList); + + List moduleList; + + bool get hasEntryModule => + moduleList.any((OhosModule element) => element.isEntry); + + OhosModule? get entryModule => hasEntryModule + ? moduleList.firstWhere((OhosModule element) => element.isEntry) + : null; + + String? get mainElement => entryModule?.mainElement; + + /// 获取主要的module名,如果存在entry,返回entry类型的module,否则返回第一个module + String get mainModuleName => + entryModule?.name ?? + (moduleList.isNotEmpty ? moduleList.first.name : OHOS_ENTRY_DEFAULT); + + /// 获取主要的module路径,如果存在entry,返回entry类型的module,否则返回第一个module + String get mainModuleSrcPath => + entryModule?.srcPath ?? + (moduleList.isNotEmpty ? moduleList.first.srcPath : OHOS_ENTRY_DEFAULT); + + static ModuleInfo getModuleInfo(OhosProject ohosProject) { + return ModuleInfo(OhosModule.fromOhosProject(ohosProject)); + } +} + +enum OhosModuleType { + entry, + har, + shared, + unknown; + + static OhosModuleType fromName(String name) { + return OhosModuleType.values.firstWhere( + (OhosModuleType element) => element.name == name, + orElse: () => OhosModuleType.unknown); + } +} + +class OhosModule { + OhosModule({ + required this.name, + required this.srcPath, + required this.isEntry, + required this.mainElement, + required this.type, + required this.flavor, + }); + + final String name; + final bool isEntry; + final String? mainElement; + final OhosModuleType type; + final String srcPath; + String flavor; + + static List fromOhosProject(OhosProject ohosProject) { + final File buildProfileFile = ohosProject.ohosRoot.childFile('build-profile.json5'); + if (!buildProfileFile.existsSync()) { + return []; + } + final Map buildProfile = JSON5.parse(buildProfileFile.readAsStringSync()) as Map; + if (!buildProfile.containsKey('modules')) { + return []; + } + final List modules = buildProfile['modules'] as List; + return modules.map((dynamic e) { + final Map module = e as Map; + final String srcPath = module['srcPath'] as String; + return OhosModule.fromModulePath( + modulePath: globals.fs.path.join(ohosProject.ohosRoot.path, srcPath)); + }).toList(); + } + + static OhosModule fromModulePath({ + required String modulePath, + String? flavor, + }) { + modulePath = globals.fs.path.normalize(globals.fs.file(modulePath).resolveSymbolicLinksSync()); + final String moduleJsonPath = globals.fs.path.join(modulePath, 'src', 'main', 'module.json5'); + final File moduleJsonFile = globals.fs.file(moduleJsonPath); + if (!moduleJsonFile.existsSync()) { + throwToolExit('Can not found module.json5 at $moduleJsonPath . \n' + ' You need to update the Flutter plugin project structure. \n' + ' See https://gitee.com/openharmony-sig/flutter_samples/tree/master/ohos/docs/09_specifications/update_flutter_plugin_structure.md'); + } + try { + final Map moduleJson = JSON5.parse(moduleJsonFile.readAsStringSync()) as Map; + final Map module = (moduleJson['module'] as Map).cast(); + final String name = module['name'] as String; + final String type = module['type'] as String; + final bool isEntry = type == OhosModuleType.entry.name; + return OhosModule( + name: name, + srcPath: modulePath, + isEntry: isEntry, + mainElement: isEntry ? module['mainElement'] as String : null, + type: OhosModuleType.fromName(type), + flavor: flavor ?? FLAVOR_DEFAULT); + + } on Exception catch (e) { + throwToolExit('parse module.json5 error , $moduleJsonPath . error: $e'); + } + } +} diff --git a/packages/flutter_tools/lib/src/ohos/build_env.dart b/packages/flutter_tools/lib/src/ohos/build_env.dart new file mode 100644 index 0000000000000000000000000000000000000000..e38dc143cd884a8ca1f6047eca54ec2cc782c850 --- /dev/null +++ b/packages/flutter_tools/lib/src/ohos/build_env.dart @@ -0,0 +1,22 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development 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. +*/ + + +class BuildEnv { + BuildEnv(this.ohpmHome, this.localEngine); + + final String ohpmHome; + final String localEngine; +} diff --git a/packages/flutter_tools/lib/src/ohos/hdc_server.dart b/packages/flutter_tools/lib/src/ohos/hdc_server.dart new file mode 100644 index 0000000000000000000000000000000000000000..5748f09e086b0982d3f755701f93ab5f1e8cb997 --- /dev/null +++ b/packages/flutter_tools/lib/src/ohos/hdc_server.dart @@ -0,0 +1,60 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development 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 'dart:io'; + +import 'ohos_sdk.dart'; + +const String HDC_SERVER_KEY = 'HDC_SERVER'; +const String HDC_SERVER_PORT_KEY = 'HDC_SERVER_PORT'; + +/// +/// return the hdc server config in environment , like 192.168.18.67:8710 +/// +String? getHdcServer() { + final String? hdcServer = Platform.environment[HDC_SERVER_KEY]; + if (hdcServer == null) { + return null; + } + final String? hdcServerPort = Platform.environment[HDC_SERVER_PORT_KEY]; + if (hdcServerPort == null) { + return null; + } + return '$hdcServer:$hdcServerPort'; +} + +String? getHdcServerHost() { + final String? hdcServer = Platform.environment[HDC_SERVER_KEY]; + if (hdcServer == null) { + return null; + } + return hdcServer; +} + +String? getHdcServerPort() { + final String? hdcServerPort = Platform.environment[HDC_SERVER_PORT_KEY]; + if (hdcServerPort == null) { + return null; + } + return hdcServerPort; +} + +List getHdcCommandCompat( + HarmonySdk ohosSdk, String id, List args) { + final String? hdcServer = getHdcServer(); + final List hdcServerCommand = + hdcServer == null ? ['-t', id] : ['-s', hdcServer]; + return [ohosSdk.hdcPath!, ...hdcServerCommand, ...args]; +} diff --git a/packages/flutter_tools/lib/src/ohos/hvigor.dart b/packages/flutter_tools/lib/src/ohos/hvigor.dart new file mode 100644 index 0000000000000000000000000000000000000000..7a365c6ca6475bf0db7ed647573a45510e2eeb13 --- /dev/null +++ b/packages/flutter_tools/lib/src/ohos/hvigor.dart @@ -0,0 +1,821 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development 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 'dart:io'; + +import 'package:json5/json5.dart'; +import 'package:process/process.dart'; + +import '../artifacts.dart'; +import '../base/common.dart'; +import '../base/file_system.dart'; +import '../base/logger.dart'; +import '../base/os.dart'; +import '../base/platform.dart' as base_platform; +import '../base/process.dart'; +import '../base/terminal.dart'; +import '../base/utils.dart'; +import '../build_info.dart'; +import '../build_system/build_system.dart'; +import '../build_system/targets/ohos.dart'; +import '../cache.dart'; +import '../compile.dart'; +import '../convert.dart'; +import '../flutter_plugins.dart'; +import '../globals.dart' as globals; +import '../project.dart'; +import '../reporting/reporting.dart'; +import 'application_package.dart'; +import 'hvigor_utils.dart'; +import 'ohos_builder.dart'; +import 'ohos_plugins_manager.dart'; + +const String FLUTTER_ASSETS_PATH = 'flutter_assets'; + +const String APP_SO_ORIGIN = 'app.so'; + +const String APP_SO = 'libapp.so'; + +const String HAR_FILE_NAME = 'flutter.har'; + +const String BUILD_INFO_JSON_PATH = 'src/main/resources/base/profile/buildinfo.json5'; +const String BUILD_INFO_JSON_DES_PATH = 'src/main/resources/rawfile/buildinfo.json5'; + +final bool isWindows = globals.platform.isWindows; + +String getHvigorwFile() => isWindows ? 'hvigorw.bat' : 'hvigorw'; + +void checkPlatformEnvironment(String environment, Logger? logger) { + final String? environmentConfig = Platform.environment[environment]; + if (environmentConfig == null) { + throwToolExit( + 'error:current platform environment $environment have not set'); + } else { + logger?.printStatus( + 'current platform environment $environment = $environmentConfig'); + } +} + +void copyFlutterAssets(String orgPath, String desPath, Logger? logger) { + logger?.printTrace('copy from "$orgPath" to "$desPath"'); + final LocalFileSystem localFileSystem = globals.localFileSystem; + copyDirectory( + localFileSystem.directory(orgPath), localFileSystem.directory(desPath)); +} + +Future copyFlutterBuildInfoFile(OhosProject ohosProject) async { + final String rawfilePath = globals.fs.path.join(ohosProject.flutterModuleDirectory.path, + 'src/main/resources/rawfile'); + final Directory rawfileDirectory = globals.localFileSystem.directory(rawfilePath); + if (!await rawfileDirectory.exists()) { + return; + } + + final String buildinfoFilePath = globals.fs.path.join(ohosProject.flutterModuleDirectory.path, BUILD_INFO_JSON_PATH); + final File sourceFile = globals.localFileSystem.file(buildinfoFilePath); + final String fileName = globals.fs.path.basename(buildinfoFilePath); + final String destinationFilePath = globals.fs.path.join(rawfilePath, fileName); + final File destinationFile = globals.localFileSystem.file(destinationFilePath); + + if (!await sourceFile.exists()) { + return; + } + + if (!await destinationFile.exists()) { + await sourceFile.copy(destinationFilePath); + } else { + return; + } + // delete sourceFile + await sourceFile.delete(); +} + +Future setImpellerEnableFlag(OhosProject ohosProject, OhosBuildInfo ohosBuildInfo) async { + final String buildinfoFilePath = globals.fs.path.join(ohosProject.flutterModuleDirectory.path, BUILD_INFO_JSON_DES_PATH); + + final File file = globals.localFileSystem.file(buildinfoFilePath); + + if (!await file.exists()) { + throw Exception('Failed to find buildinfo.json5 file: $buildinfoFilePath'); + } + + final String content = await file.readAsString(); + + final Map json = jsonDecode(content) as Map; + + // find "enable_impeller" in json file + final List stringList = json['string'] as List; + final Map? enableImpellerItem = stringList.firstWhere( + (dynamic item) => (item as Map)['name'] == 'enable_impeller', + orElse: () => null, + ) as Map?; + + if (enableImpellerItem != null) { + enableImpellerItem['value'] = ohosBuildInfo.enableImpellerFlag?.toString(); + } + + final String updatedContent = const JsonEncoder.withIndent(' ').convert(json); + + // save setting + await file.writeAsString(updatedContent); +} + +/// eg:entry/src/main/resources/rawfile +String getProjectAssetsPath(String ohosRootPath, OhosProject ohosProject) { + return globals.fs.path.join(ohosProject.flutterModuleDirectory.path, + 'src/main/resources/rawfile', FLUTTER_ASSETS_PATH); +} + +/// eg:entry/libs/arm64-v8a/libapp.so +String getAppSoPath( + String ohosRootPath, OhosArch ohosArch, OhosProject ohosProject) { + return globals.fs.path.join(ohosProject.flutterModuleDirectory.path, 'libs', + getNameForOhosArch(ohosArch), APP_SO); +} + +String getHvigorwPath(String ohosRootPath, {bool checkMod = false}) { + final String hvigorwPath = + globals.fs.path.join(ohosRootPath, getHvigorwFile()); + if (globals.fs.file(hvigorwPath).existsSync()) { + if (checkMod) { + final OperatingSystemUtils operatingSystemUtils = globals.os; + final File file = globals.localFileSystem.file(hvigorwPath); + operatingSystemUtils.chmod(file, '755'); + } + return hvigorwPath; + } else { + return 'hvigorw'; + } +} + +String getAbsolutePath(FlutterProject flutterProject, String path) { + if (globals.fs.path.isRelative(path)) { + return globals.fs.path.join(flutterProject.directory.path, path); + } + return path; +} + + +/// ohpm should init first +Future ohpmInstall( + {required ProcessUtils processUtils, + required String workingDirectory, + Logger? logger}) async { + final List cleanCmd = ['ohpm', 'clean']; + final List installCmd = ['ohpm', 'install', '--all']; + processUtils.runSync(cleanCmd, + workingDirectory: workingDirectory, throwOnError: true); + processUtils.runSync(installCmd, + workingDirectory: workingDirectory, throwOnError: true); +} + +/// 根据来源,替换关键字,输出target文件 +void replaceKey(File file, File target, String key, String value) { + String content = file.readAsStringSync(); + content = content.replaceAll(key, value); + target.writeAsStringSync(content); +} + +///hvigorw任务 +Future hvigorwTask(List taskCommand, + {required ProcessUtils processUtils, + required String workPath, + required String hvigorwPath, + Logger? logger}) async { + final RunResult result = processUtils.runSync(taskCommand, + workingDirectory: workPath, throwOnError: true); + return result.exitCode; +} + +Future assembleHap( + {required ProcessUtils processUtils, + required String ohosRootPath, + required String hvigorwPath, + required String flavor, + required String buildMode, + Logger? logger}) async { + final List command = [ + hvigorwPath, + // 'clean', + 'assembleHap', + '-p', + 'product=$flavor', + '-p', + 'buildMode=$buildMode', + '--no-daemon', + ]; + return hvigorwTask(command, + processUtils: processUtils, + workPath: ohosRootPath, + hvigorwPath: hvigorwPath, + logger: logger); +} + +Future assembleApp( + {required ProcessUtils processUtils, + required String ohosRootPath, + required String hvigorwPath, + required String flavor, + required String buildMode, + Logger? logger}) async { + final List command = [ + hvigorwPath, + // 'clean', + 'assembleApp', + '-p', + 'product=$flavor', + '-p', + 'buildMode=$buildMode', + '--no-daemon', + ]; + return hvigorwTask(command, + processUtils: processUtils, + workPath: ohosRootPath, + hvigorwPath: hvigorwPath, + logger: logger); +} + + +Future assembleHar( + {required ProcessUtils processUtils, + required String workPath, + required String hvigorwPath, + required String moduleName, + required String buildMode, + String product = 'default', + Logger? logger}) async { + final List command = [ + hvigorwPath, + // 'clean', + '--mode', + 'module', + '-p', + 'module=$moduleName', + '-p', + 'product=$product', + 'assembleHar', + '--no-daemon', + ]; + return hvigorwTask(command, + processUtils: processUtils, + workPath: workPath, + hvigorwPath: hvigorwPath, + logger: logger); +} + +Future assembleHsp( + {required ProcessUtils processUtils, + required String workPath, + required String hvigorwPath, + required String moduleName, + required String flavor, + required String buildMode, + Logger? logger}) async { + final List command = [ + hvigorwPath, + // 'clean', + '--mode', + 'module', + '-p', + 'module=$moduleName', + '-p', + 'product=$flavor', + '-p', + 'buildMode=$buildMode', + 'assembleHsp', + '--no-daemon', + ]; + return hvigorwTask(command, + processUtils: processUtils, + workPath: workPath, + hvigorwPath: hvigorwPath, + logger: logger); +} + +/// flutter构建 +Future flutterAssemble(FlutterProject flutterProject, + OhosBuildInfo ohosBuildInfo, String targetFile) async { + late String targetName; + if (ohosBuildInfo.buildInfo.isDebug) { + targetName = 'debug_ohos_application'; + } else if (ohosBuildInfo.buildInfo.isProfile) { + // eg:ohos_aot_bundle_profile_ohos-arm64 + targetName = + 'ohos_aot_bundle_profile_${getPlatformNameForOhosArch(ohosBuildInfo.targetArchs.first)}'; + } else { + // eg:ohos_aot_bundle_release_ohos-arm64 + targetName = + 'ohos_aot_bundle_release_${getPlatformNameForOhosArch(ohosBuildInfo.targetArchs.first)}'; + } + final List selectTarget = + ohosTargets.where((Target e) => targetName == e.name).toList(); + if (selectTarget.isEmpty) { + throwToolExit('do not found compare target.'); + } else if (selectTarget.length > 1) { + throwToolExit('more than one target match.'); + } + final Target target = selectTarget[0]; + + final Status status = + globals.logger.startProgress('Compiling $targetName for the Ohos...'); + String output = globals.fs.directory(getOhosBuildDirectory()).path; + // If path is relative, make it absolute from flutter project. + output = getAbsolutePath(flutterProject, output); + try { + final BuildResult result = await globals.buildSystem.build( + target, + Environment( + projectDir: globals.fs.currentDirectory, + outputDir: globals.fs.directory(output), + buildDir: flutterProject.directory + .childDirectory('.dart_tool') + .childDirectory('flutter_build'), + defines: { + ...ohosBuildInfo.buildInfo.toBuildSystemEnvironment(), + kTargetFile: targetFile, + kTargetPlatform: getNameForTargetPlatform(TargetPlatform.ohos), + }, + artifacts: globals.artifacts!, + fileSystem: globals.fs, + logger: globals.logger, + processManager: globals.processManager, + analytics: globals.analytics, + platform: globals.platform, + usage: globals.flutterUsage, + cacheDir: globals.cache.getRoot(), + engineVersion: globals.artifacts!.isLocalEngine + ? null + : globals.flutterVersion.engineRevision, + flutterRootDir: globals.fs.directory(Cache.flutterRoot), + generateDartPluginRegistry: true, + )); + if (!result.success) { + for (final ExceptionMeasurement measurement in result.exceptions.values) { + globals.printError( + 'Target ${measurement.target} failed: ${measurement.exception}', + stackTrace: measurement.fatal ? measurement.stackTrace : null, + ); + } + throwToolExit('Failed to compile application for the Ohos.'); + } else { + return output; + } + } on Exception catch (err) { + throwToolExit(err.toString()); + } finally { + status.stop(); + } +} + +/// 清理和拷贝flutter产物和资源 +Future cleanAndCopyFlutterAsset( + OhosProject ohosProject, + OhosBuildInfo ohosBuildInfo, + Logger? logger, + String ohosRootPath, + String output) async { + logger?.printTrace('copy flutter assets to project start'); + // clean flutter assets + final String desFlutterAssetsPath = + getProjectAssetsPath(ohosRootPath, ohosProject); + final Directory desAssets = globals.fs.directory(desFlutterAssetsPath); + if (desAssets.existsSync()) { + desAssets.deleteSync(recursive: true); + } + + /// copy flutter assets + copyFlutterAssets(globals.fs.path.join(output, FLUTTER_ASSETS_PATH), + desFlutterAssetsPath, logger); + await copyFlutterBuildInfoFile(ohosProject); + + if (ohosBuildInfo.enableImpellerFlag != null) { + await setImpellerEnableFlag(ohosProject, ohosBuildInfo); + } + + final String desAppSoPath = + getAppSoPath(ohosRootPath, ohosBuildInfo.targetArchs.first, ohosProject); + if (ohosBuildInfo.buildInfo.isRelease || ohosBuildInfo.buildInfo.isProfile) { + // copy app.so + final String appSoPath = globals.fs.path.join(output, + getNameForOhosArch(ohosBuildInfo.targetArchs.first), APP_SO_ORIGIN); + final File appSoFile = globals.localFileSystem.file(appSoPath); + ensureParentExists(desAppSoPath); + appSoFile.copySync(desAppSoPath); + } else { + final File appSo = globals.fs.file(desAppSoPath); + if (appSo.existsSync()) { + appSo.deleteSync(); + } + } + logger?.printTrace('copy flutter assets to project end'); +} + +/// 清理和拷贝flutter运行时 +void cleanAndCopyFlutterRuntime( + OhosProject ohosProject, + OhosBuildInfo ohosBuildInfo, + Logger? logger, + String ohosRootPath, + OhosBuildData ohosBuildData) { + logger?.printTrace('copy flutter runtime to project start'); + // 复制 flutter.har + final String localEngineHarPath = globals.artifacts!.getArtifactPath( + Artifact.flutterEngineHar, + platform: getTargetPlatformForName( + getPlatformNameForOhosArch(ohosBuildInfo.targetArchs.first)), + mode: ohosBuildInfo.buildInfo.mode, + ); + final String desHarPath = + globals.fs.path.join(ohosRootPath, 'har', HAR_FILE_NAME); + ensureParentExists(desHarPath); + final File originHarFile = globals.localFileSystem.file(localEngineHarPath); + originHarFile.copySync(desHarPath); + logger?.printTrace('copy from "$localEngineHarPath" to "$desHarPath"'); + logger?.printTrace('copy flutter runtime to project end'); +} + +void ensureParentExists(String path) { + final Directory directory = globals.localFileSystem.file(path).parent; + if (!directory.existsSync()) { + directory.createSync(recursive: true); + } +} + +class OhosHvigorBuilder implements OhosBuilder { + OhosHvigorBuilder({ + required Logger logger, + required ProcessManager processManager, + required FileSystem fileSystem, + required Artifacts artifacts, + required Usage usage, + required HvigorUtils hvigorUtils, + required base_platform.Platform platform, + }) : _logger = logger, + _fileSystem = fileSystem, + _artifacts = artifacts, + _usage = usage, + _hvigorUtils = hvigorUtils, + _fileSystemUtils = + FileSystemUtils(fileSystem: fileSystem, platform: platform), + _processUtils = + ProcessUtils(logger: logger, processManager: processManager); + + final Logger _logger; + final ProcessUtils _processUtils; + final FileSystem _fileSystem; + final Artifacts _artifacts; + final Usage _usage; + final HvigorUtils _hvigorUtils; + final FileSystemUtils _fileSystemUtils; + + late OhosProject ohosProject; + late String ohosRootPath; + late OhosBuildData ohosBuildData; + + void parseData(FlutterProject flutterProject, Logger? logger) { + ohosProject = flutterProject.ohos; + ohosRootPath = ohosProject.ohosRoot.path; + ohosBuildData = OhosBuildData.parseOhosBuildData(ohosProject, logger); + } + + /// build hap + @override + Future buildHap({ + required FlutterProject project, + required OhosBuildInfo ohosBuildInfo, + required String target, + }) async { + _logger.printStatus('start hap build...'); + + if (!project.ohos.ohosBuildData.moduleInfo.hasEntryModule) { + throwToolExit( + "this ohos project don't have a entry module, can't build to a hap file."); + } + final Status status = _logger.startProgress( + 'Running Hvigor task assembleHap...', + ); + + updateProjectVersion(project, ohosBuildInfo.buildInfo); + await addPluginsModules(project); + await addFlutterModuleAndPluginsSrcOverrides(project); + + await buildApplicationPipeLine(project, ohosBuildInfo, target: target); + + final String hvigorwPath = getHvigorwPath(ohosRootPath, checkMod: true); + + /// 生成所有 plugin 的 har + await assembleHars(_processUtils, project, ohosBuildInfo, _logger); + await assembleHsps(_processUtils, project, ohosBuildInfo, _logger); + + await removePluginsModules(project); + await addFlutterModuleAndPluginsOverrides(project); + // ohosProject.deleteOhModulesCache(); + await ohpmInstall( + processUtils: _processUtils, + workingDirectory: ohosRootPath, + logger: _logger, + ); + + /// invoke hvigow task generate hap file. + final int errorCode = await assembleHap( + processUtils: _processUtils, + ohosRootPath: ohosRootPath, + hvigorwPath: hvigorwPath, + flavor: getFlavor( + ohosProject.getBuildProfileFile(), ohosBuildInfo.buildInfo.flavor), + buildMode: ohosBuildInfo.buildInfo.modeName, + logger: _logger); + status.stop(); + if (errorCode != 0) { + throwToolExit('assembleHap error! please check log.'); + } + + final File buildProfile = project.ohos.getBuildProfileFile(); + final String buildProfileConfig = buildProfile.readAsStringSync(); + final dynamic obj = JSON5.parse(buildProfileConfig); + final dynamic signingConfigs = obj['app']?['signingConfigs']; + if (signingConfigs is List && signingConfigs.isEmpty) { + _logger.printError( + '请通过DevEco Studio打开ohos工程后配置调试签名(File -> Project Structure -> Signing Configs 勾选Automatically generate signature)'); + } else { + final BuildInfo buildInfo = ohosBuildInfo.buildInfo; + final File bundleFile = OhosProject.getSignedFile( + modulePath: ohosProject.mainModuleDirectory.path, + moduleName: ohosProject.mainModuleName, + flavor: getFlavor(ohosProject.getBuildProfileFile(), buildInfo.flavor), + throwOnMissing: true, + ); + final String appSize = (buildInfo.mode == BuildMode.debug) + ? '' // Don't display the size when building a debug variant. + : ' (${getSizeAsPlatformMB(bundleFile.lengthSync())})'; + _logger.printStatus( + '${_logger.terminal.successMark} Built ${_fileSystem.path.relative(bundleFile.path)}$appSize.', + color: TerminalColor.green, + ); + } + } + + Future flutterBuildPre(FlutterProject flutterProject, OhosBuildInfo ohosBuildInfo, String target) async { + /** + * 1. execute flutter assemble + * 2. copy flutter asset to flutter module + * 3. copy flutter runtime + * 4. ohpm install + */ + + final String output = await flutterAssemble(flutterProject, ohosBuildInfo, target); + + await cleanAndCopyFlutterAsset(ohosProject, ohosBuildInfo, _logger, ohosRootPath, output); + + cleanAndCopyFlutterRuntime(ohosProject, ohosBuildInfo, _logger, ohosRootPath, ohosBuildData); + + // ohpm install for all modules + // ohosProject.deleteOhModulesCache(); + await ohpmInstall( + processUtils: _processUtils, + workingDirectory: ohosRootPath, + logger: _logger, + ); + } + + @override + Future buildHar({ + required FlutterProject project, + required OhosBuildInfo ohosBuildInfo, + required String target, + }) async { + if (!project.isModule || + !project.ohos.flutterModuleDirectory.existsSync()) { + throwToolExit('current project is not module or has not pub get'); + } + + final Status status = _logger.startProgress( + 'Running Hvigor task assembleHar...', + ); + + await addPluginsModules(project); + await addFlutterModuleAndPluginsSrcOverrides(project); + + parseData(project, _logger); + + await flutterBuildPre(project, ohosBuildInfo, target); + + final String hvigorwPath = getHvigorwPath(ohosRootPath, checkMod: true); + final List harModules = ohosBuildData.harModules; + + /// 生成 module 和所有 plugin 的 har + await assembleHars(_processUtils, project, ohosBuildInfo, _logger); + await assembleHsps(_processUtils, project, ohosBuildInfo, _logger); + + await removePluginsModules(project); + await addFlutterModuleAndPluginsOverrides(project); + status.stop(); + printHowToConsumeHar(logger: _logger); + } + + + + /// Prints how to consume the har from a host app. + void printHowToConsumeHar({ + Logger? logger, + }) { + logger?.printStatus('\nConsuming the Module', emphasis: true); + logger?.printStatus(''' + 1. Open ${globals.fs.path.join('', 'oh-package.json5')} + 2. Add flutter_module to the dependencies list: + + "dependencies": { + "@ohos/flutter_module": "file:path/to/har/flutter_module.har" + } + + 3. Override flutter and plugins dependencies: + + "overrides" { + "@ohos/flutter_ohos": "file:path/to/har/flutter.har", + } + '''); + } + + @override + Future buildHsp({ + required FlutterProject project, + required OhosBuildInfo ohosBuildInfo, + required String target, + }) { + // TODO: implement buildHsp + throw UnimplementedError(); + } + + @override + Future buildApp({ + required FlutterProject project, + required OhosBuildInfo ohosBuildInfo, + required String target, + }) async { + final Status status = _logger.startProgress( + 'Running Hvigor task assembleApp...', + ); + updateProjectVersion(project, ohosBuildInfo.buildInfo); + await buildApplicationPipeLine(project, ohosBuildInfo, target: target); + + final String hvigorwPath = getHvigorwPath(ohosRootPath, checkMod: true); + + /// invoke hvigow task generate hap file. + final int errorCode1 = await assembleApp( + processUtils: _processUtils, + ohosRootPath: ohosRootPath, + flavor: getFlavor( + ohosProject.getBuildProfileFile(), ohosBuildInfo.buildInfo.flavor), + hvigorwPath: hvigorwPath, + buildMode: ohosBuildInfo.buildInfo.modeName, + logger: _logger); + status.stop(); + if (errorCode1 != 0) { + throwToolExit('assembleHap error! please check log.'); + } + final BuildInfo buildInfo = ohosBuildInfo.buildInfo; + final File bundleFile = OhosProject.getSignedFile( + modulePath: ohosProject.mainModuleDirectory.path, + moduleName: ohosProject.mainModuleName, + flavor: getFlavor(ohosProject.getBuildProfileFile(), buildInfo.flavor), + type: OhosFileType.app, + throwOnMissing: true, + ); + final String appSize = (buildInfo.mode == BuildMode.debug) + ? '' // Don't display the size when building a debug variant. + : ' (${getSizeAsPlatformMB(bundleFile.lengthSync())})'; + _logger.printStatus( + '${_logger.terminal.successMark} Built ${_fileSystem.path.relative(bundleFile.path)}$appSize.', + color: TerminalColor.green, + ); + } + + Future buildApplicationPipeLine(FlutterProject flutterProject, OhosBuildInfo ohosBuildInfo, {required String target}) async { + if (!flutterProject.ohos.ohosBuildData.moduleInfo.hasEntryModule) { + throwToolExit( + "this ohos project don't have a entry module , can't build to a application."); + } + + parseData(flutterProject, _logger); + + /// 检查plugin的har构建 + await checkOhosPluginsDependencies(flutterProject); + + await flutterBuildPre(flutterProject, ohosBuildInfo, target); + + if (ohosProject.isRunWithModuleHar) { + await assembleHars(_processUtils, flutterProject, ohosBuildInfo, _logger); + await assembleHsps(_processUtils, flutterProject, ohosBuildInfo, _logger); + + /// har文件拷贝后,需要重新install + // ohosProject.deleteOhModulesCache(); + await ohpmInstall( + processUtils: _processUtils, + workingDirectory: ohosProject.mainModuleDirectory.path, + logger: _logger); + } + } + + String _moduleNameWithFlavor(List modules, String? flavor) { + return modules + .map((OhosModule module) => OhosModule.fromModulePath( + modulePath: module.srcPath, + flavor: getFlavor( + globals.fs.file(globals.fs.path + .join(module.srcPath, 'build-profile.json5')), + flavor, + ), + )) + .map((OhosModule module) => '${module.name}@${module.flavor}') + .join(','); + } + + /// 生成所有 plugin 的 har + Future assembleHars( + ProcessUtils processUtils, + FlutterProject project, + OhosBuildInfo ohosBuildInfo, + Logger? logger, + ) async { + final String ohosProjectPath = project.ohos.ohosRoot.path; + final List modules = ohosBuildData.harModules; + if (modules.isEmpty) { + return; + } + + // compile hars. parallel compilation. + final String hvigorwPath = getHvigorwPath(ohosProjectPath, checkMod: true); + final String moduleName = + _moduleNameWithFlavor(modules, ohosBuildInfo.buildInfo.flavor); + final int errorCode = await assembleHar( + processUtils: processUtils, + workPath: ohosProjectPath, + moduleName: moduleName, + hvigorwPath: hvigorwPath, + buildMode: ohosBuildInfo.buildInfo.modeName, + logger: logger); + if (errorCode != 0) { + throwToolExit('Oops! assembleHars failed! please check log.'); + } + + // copy hars + for (final OhosModule module in modules) { + final File originHar = globals.fs.file(globals.fs.path.join( + module.srcPath, + 'build', + 'default', + 'outputs', + module.flavor, + '${module.name}.har')); + if (!originHar.existsSync()) { + throwToolExit('Oops! Failed to find: ${originHar.path}'); + } + final String desPath = globals.fs.path + .join(ohosRootPath, 'har', '${module.name}.har'); + ensureParentExists(desPath); + originHar.copySync(desPath); + } + } + + Future assembleHsps( + ProcessUtils processUtils, + FlutterProject project, + OhosBuildInfo ohosBuildInfo, + Logger? logger, + ) async { + final String ohosProjectPath = project.ohos.ohosRoot.path; + final List modules = ohosBuildData.moduleInfo.moduleList + .where((OhosModule element) => element.type == OhosModuleType.shared) + .toList(); + if (modules.isEmpty) { + return; + } + final String hvigorwPath = getHvigorwPath(ohosProjectPath, checkMod: true); + final String moduleName = + _moduleNameWithFlavor(modules, ohosBuildInfo.buildInfo.flavor); + final int errorCode = await assembleHsp( + processUtils: processUtils, + workPath: ohosProjectPath, + moduleName: moduleName, + hvigorwPath: hvigorwPath, + flavor: getFlavor( + project.ohos.getBuildProfileFile(), ohosBuildInfo.buildInfo.flavor), + buildMode: ohosBuildInfo.buildInfo.modeName, + logger: logger); + if (errorCode != 0) { + throwToolExit('Oops! assembleHsps failed! please check log.'); + } + } +} diff --git a/packages/flutter_tools/lib/src/ohos/hvigor_utils.dart b/packages/flutter_tools/lib/src/ohos/hvigor_utils.dart new file mode 100644 index 0000000000000000000000000000000000000000..ea3fa101aff10e4c421bfe19093d6487981d2206 --- /dev/null +++ b/packages/flutter_tools/lib/src/ohos/hvigor_utils.dart @@ -0,0 +1,175 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development 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 'dart:convert'; + +import 'package:json5/json5.dart'; + +import '../base/common.dart'; +import '../base/file_system.dart'; + +import '../base/utils.dart'; +import '../build_info.dart'; +import '../globals.dart' as globals; +import '../project.dart'; +import '../reporting/reporting.dart'; +import 'ohos_sdk.dart'; + +const String FLAVOR_DEFAULT = 'default'; +const String BUILD_NUMBER_DEFAULT = '1000000'; +const String BUILD_NAME_DEFAULT = '1.0.0'; + +class HvigorUtils {} + +/// Overwrite local.properties in the specified Flutter project's Harmony +/// sub-project, if needed. +/// +/// If [requireHarmonySdk] is true (the default) and no Harmony SDK is found, +/// this will fail with a [ToolExit]. +void updateLocalProperties({ + required FlutterProject project, + BuildInfo? buildInfo, + bool requireHarmonySdk = true, +}) { + if (requireHarmonySdk && globals.hmosSdk == null) { + exitWithNoSdkMessage(); + } + final File localProperties = project.ohos.localPropertiesFile; + bool changed = false; + + SettingsFile settings; + if (localProperties.existsSync()) { + settings = SettingsFile.parseFromFile(localProperties); + } else { + settings = SettingsFile(); + changed = true; + } + + void changeIfNecessary(String key, String? value) { + if (settings.values[key] == value) { + return; + } + if (value == null) { + settings.values.remove(key); + } else { + settings.values[key] = value; + } + changed = true; + } + + final HmosSdk? hmosSdk = globals.hmosSdk; + if (hmosSdk != null) { + changeIfNecessary('hwsdk.dir', globals.fsUtils.escapePath(hmosSdk.sdkPath)); + } + final String? nodeHome = globals.platform.environment['NODE_HOME']; + if (nodeHome != null) { + changeIfNecessary('nodejs.dir', globals.fsUtils.escapePath(nodeHome)); + } + + if (changed) { + settings.writeContents(localProperties); + } + if (project.ohos.isRunWithModuleHar) { + settings.writeContents(project.ohos.ephemeralLocalPropertiesFile); + } +} + +/// Writes standard Ohos local properties to the specified [properties] file. +/// +/// Writes the path to the Ohos SDK, if known. +void writeLocalProperties(File properties) { + final SettingsFile settings = SettingsFile(); + final HmosSdk? hmosSdk = globals.hmosSdk; + if (hmosSdk != null) { + settings.values['hwsdk.dir'] = globals.fsUtils.escapePath(hmosSdk.sdkPath); + } + final String? nodeHome = globals.platform.environment['NODE_HOME']; + if (nodeHome != null) { + settings.values['nodejs.dir'] = nodeHome; + } + settings.writeContents(properties); +} + +void exitWithNoSdkMessage() { + BuildEvent('unsupported-project', + type: 'hvigor', + eventError: 'hos-sdk-not-found', + flutterUsage: globals.flutterUsage) + .send(); + throwToolExit('${globals.logger.terminal.warningMark} No Hmos SDK found. ' + 'Try setting the HOS_SDK_HOME environment variable.'); +} + +String getFlavor(File buildProfileFile, String? flavor) { + if (flavor == null) { + return FLAVOR_DEFAULT; + } + if (buildProfileFile.existsSync()) { + final Map config = JSON5 + .parse(buildProfileFile.readAsStringSync()) as Map; + final List targetList; + // ignore: avoid_dynamic_calls + if (config['app'] != null && config['app']['products'] != null) { + // ohos/build-profile.json5 + // ignore: avoid_dynamic_calls + targetList = config['app']['products'] as List; + } else if (config['targets'] != null) { + // ohos/entry/build-profile.json5 + targetList = config['targets'] as List; + } else { + // ignore: always_specify_types + targetList = []; + } + for (final dynamic item in targetList) { + final Map map = item as Map; + if (flavor == (map['name'] as String)) { + return flavor; + } + } + } + globals.logger.printWarning( + 'Flavor "$flavor" not exists in file ${buildProfileFile.path}, use "$FLAVOR_DEFAULT" instead.'); + return FLAVOR_DEFAULT; +} + +void updateProjectVersion(FlutterProject project, BuildInfo? buildInfo) { + final File targetFile = project.ohos.getAppJsonFile(); + if (targetFile.existsSync()) { + final String? buildNumber = validatedBuildNumberForPlatform( + TargetPlatform.ohos_arm, + buildInfo?.buildNumber ?? project.manifest.buildNumber, + globals.logger, + ); + final String? buildName = validatedBuildNameForPlatform( + TargetPlatform.ohos_arm, + buildInfo?.buildName ?? project.manifest.buildName, + globals.logger, + ); + + final Map config = + JSON5.parse(targetFile.readAsStringSync()) as Map; + if (config['app'] != null) { + final Map map = config['app'] as Map; + if (buildNumber != null) { + map['versionCode'] = int.parse(buildNumber); + } + if (buildName != null) { + map['versionName'] = buildName; + } + final String configNew = + const JsonEncoder.withIndent(' ').convert(config); + targetFile.writeAsStringSync(configNew, flush: true); + } + } +} diff --git a/packages/flutter_tools/lib/src/ohos/ohos_builder.dart b/packages/flutter_tools/lib/src/ohos/ohos_builder.dart new file mode 100644 index 0000000000000000000000000000000000000000..0e36e5036de3f871ebff12f5dff56cdecf317258 --- /dev/null +++ b/packages/flutter_tools/lib/src/ohos/ohos_builder.dart @@ -0,0 +1,55 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development 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 '../base/context.dart'; +import '../build_info.dart'; +import '../project.dart'; + +/// The builder in the current context. +OhosBuilder? get ohosBuilder { + return context.get(); +} + +abstract class OhosBuilder { + const OhosBuilder(); + + /// build hap + Future buildHap({ + required FlutterProject project, + required OhosBuildInfo ohosBuildInfo, + required String target, + }); + + /// build har + Future buildHar({ + required FlutterProject project, + required OhosBuildInfo ohosBuildInfo, + required String target, + }); + + /// build app + Future buildApp({ + required FlutterProject project, + required OhosBuildInfo ohosBuildInfo, + required String target, + }); + + /// build hsp + Future buildHsp({ + required FlutterProject project, + required OhosBuildInfo ohosBuildInfo, + required String target, + }); +} diff --git a/packages/flutter_tools/lib/src/ohos/ohos_device.dart b/packages/flutter_tools/lib/src/ohos/ohos_device.dart new file mode 100644 index 0000000000000000000000000000000000000000..f8765a473cb45548302bfbfa29293a1fa0e7c03b --- /dev/null +++ b/packages/flutter_tools/lib/src/ohos/ohos_device.dart @@ -0,0 +1,790 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development 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 'dart:async'; +import 'dart:math'; + +import 'package:process/process.dart'; + +import '../application_package.dart'; +import '../base/common.dart'; +import '../base/file_system.dart'; +import '../base/io.dart'; +import '../base/logger.dart'; +import '../base/platform.dart'; +import '../base/process.dart'; +import '../build_info.dart'; +import '../convert.dart'; +import '../device.dart'; +import '../device_port_forwarder.dart'; +import '../globals.dart' as globals; +import '../project.dart'; +import '../protocol_discovery.dart'; +import 'application_package.dart'; +import 'hdc_server.dart'; +import 'ohos_builder.dart'; +import 'ohos_sdk.dart'; + +class OhosDevice extends Device { + OhosDevice( + super.id, { + this.deviceCodeName, + required Logger logger, + required ProcessManager processManager, + required Platform platform, + required HarmonySdk ohosSdk, + required FileSystem fileSystem, + required String? hdcServer, + }) : _logger = logger, + _processManager = processManager, + _ohosSdk = ohosSdk, + _platform = platform, + _fileSystem = fileSystem, + _processUtils = + ProcessUtils(logger: logger, processManager: processManager), + _hdcServer = hdcServer, + super( + category: Category.mobile, + platformType: PlatformType.ohos, + ephemeral: true, + ); + + final Logger _logger; + final ProcessManager _processManager; + final HarmonySdk _ohosSdk; + final Platform _platform; + final FileSystem _fileSystem; + final ProcessUtils _processUtils; + final String? _hdcServer; + final String? deviceCodeName; + + @override + void clearLogs() { + // clear hilog history + _processUtils + .runSync(hdcCommandForDevice(['shell', 'hilog', '-r'])); + } + + @override + Future dispose() async { + _logReader?._stop(); + _pastLogReader?._stop(); + } + + @override + Future get emulatorId async => id; + + HdcLogReader? _logReader; + HdcLogReader? _pastLogReader; + + @override + FutureOr getLogReader({ + ApplicationPackage? app, + bool includePastLogs = false, + }) async { + if (includePastLogs) { + return _pastLogReader ??= await HdcLogReader.createLogReader( + this, + _processManager, + includePastLogs: true, + ); + } else { + return _logReader ??= await HdcLogReader.createLogReader( + this, + _processManager, + ); + } + } + + @override + bool get supportsScreenshot => true; + + @override + Future takeScreenshot(File outputFile) async { + const String remotePath = '/data/local/tmp/flutter_screenshot.jpeg'; + await runHdcCheckedAsync( + ['shell', 'snapshot_display', '-f', remotePath]); + await _processUtils.run( + hdcCommandForDevice(['file recv', remotePath, outputFile.path]), + throwOnError: true, + ); + await runHdcCheckedAsync(['shell', 'rm', remotePath]); + } + + @override + bool get supportsFlavors => true; + + Future _installApp(covariant ApplicationPackage app, + {String? userIdentifier}) async { + if (app is! OhosHap) { + throwToolExit('this project or file is not contain(a) Hap file'); + } + final OhosHap hap = app; + final File file = globals.fs.file(hap.applicationPackage.path); + if (!file.existsSync()) { + throwToolExit('Failed to get the hap file: ${file.path}'); + } + + _logger.printStatus('installing hap. bundleName: ${app.id} '); + const String targetPath = 'data/local/tmp/flutterInstallTemp'; + final List> hspCmds = app.ohosBuildData.moduleInfo.moduleList + .where((OhosModule module) => module.type == OhosModuleType.shared) + .map((OhosModule module) => OhosProject.getSignedFile( + modulePath: module.srcPath, + moduleName: module.name, + flavor: module.flavor, + type: OhosFileType.hsp, + )) + .map((File file) => ['file', 'send', file.path, targetPath]) + .toList(); + final List> cmds = >[ + ['shell', 'rm', '-rf', targetPath], + ['shell', 'mkdir', targetPath], + ...hspCmds, + ['file', 'send', hap.applicationPackage.path, targetPath], + ['shell', 'bm', 'install', '-p', targetPath], + ['shell', 'rm', '-rf', targetPath], + ].map((List cmd) => hdcCommandForDevice(cmd)).toList(); + + RunResult? result; + for (final List cmd in cmds) { + result = _processUtils.runSync(cmd, throwOnError: true); + if (result.exitCode != 0 || result.stdout.contains('error')) { + _logger.printError('_installApp: cmd=$cmd\n code=${result.exitCode}, stdout=${result.stdout}, stderr=${result.stderr}'); + return false; + } + } + return result != null && result.exitCode == 0; + } + + @override + Future isAppInstalled(covariant ApplicationPackage app, + {String? userIdentifier}) async { + final String bundleName = app.id; + final List propCommand = + hdcCommandForDevice(['shell', '"bm dump -n $bundleName"']); + _logger.printTrace(propCommand.join(' ')); + final RunResult result = _processUtils.runSync(propCommand); + if (result.exitCode == 0) { + final String cmdResult = result.stdout; + if (cmdResult.contains(bundleName)) { + return true; + } else if (cmdResult.contains('error: failed to get information')) { + return false; + } else { + throw ToolExit('unknown result for bm dump.'); + } + } + return false; + } + + @override + Future isLatestBuildInstalled(covariant ApplicationPackage app) async { + return false; + } + + @override + late final Future isLocalEmulator = () async { + return false; + }(); + + @override + bool isSupported() { + return true; + } + + @override + bool isSupportedForProject(FlutterProject flutterProject) { + return flutterProject.ohos.existsSync(); + } + + @override + String get name => deviceCodeName ?? 'unknown'; + + @override + late final DevicePortForwarder? portForwarder = () { + final String? hdcPath = _ohosSdk.hdcPath; + if (hdcPath == null) { + return null; + } + return OhosDevicePortForwarder( + processManager: _processManager, + logger: _logger, + deviceId: id, + hdcPath: hdcPath, + ohosDevice: this, + ); + }(); + + @override + Future get sdkNameAndVersion async => + 'Ohos ${await _sdkVersion} (API ${await apiVersion})'; + + Future get _sdkVersion => _getProperty('const.ohos.fullname'); + + Future get apiVersion => _getProperty('const.ohos.apiversion'); + + OhosHap? _package; + + @override + Future startApp(covariant ApplicationPackage? package, + {String? mainPath, + String? route, + required DebuggingOptions debuggingOptions, + Map platformArgs = const {}, + bool prebuiltApplication = false, + bool ipv6 = false, + String? userIdentifier}) async { + /// + /// 1. check build status + /// 2. build or not + /// 3. install or not + /// 4. start app command + /// + + final TargetPlatform devicePlatform = await targetPlatform; + OhosHap? builtPackage = package as OhosHap?; + OhosArch ohosArch; + switch (devicePlatform) { + case TargetPlatform.ohos_arm64: + ohosArch = OhosArch.arm64_v8a; + case TargetPlatform.ohos_arm: + ohosArch = OhosArch.armeabi_v7a; + case TargetPlatform.ohos_x64: + ohosArch = OhosArch.x86_64; + case TargetPlatform.ohos: + case TargetPlatform.android: + case TargetPlatform.darwin: + case TargetPlatform.fuchsia_arm64: + case TargetPlatform.fuchsia_x64: + case TargetPlatform.ios: + case TargetPlatform.linux_arm64: + case TargetPlatform.linux_x64: + case TargetPlatform.tester: + case TargetPlatform.web_javascript: + case TargetPlatform.windows_x64: + case TargetPlatform.android_arm: + case TargetPlatform.android_arm64: + case TargetPlatform.android_x64: + case TargetPlatform.android_x86: + case TargetPlatform.windows_arm64: + _logger.printError('Ohos platforms are only supported.'); + return LaunchResult.failed(); + } + + if (!prebuiltApplication) { + _logger.printTrace('Building Hap'); + final FlutterProject project = FlutterProject.current(); + await ohosBuilder?.buildHap( + project: project, + ohosBuildInfo: OhosBuildInfo( + debuggingOptions.buildInfo, + targetArchs: [ohosArch], + enableImpellerFlag: debuggingOptions.enableImpeller.asBool, + ), + target: mainPath ?? 'lib/main.dart', + ); + + builtPackage = await ApplicationPackageFactory.instance! + .getPackageForPlatform(devicePlatform, + buildInfo: debuggingOptions.buildInfo) as OhosHap?; + } + // There was a failure parsing the android project information. + if (builtPackage == null) { + throwToolExit('Problem building Ohos application: see above error(s).'); + } + + _logger.printTrace("Stopping app '${builtPackage.name}' on $name."); + await stopApp(builtPackage, userIdentifier: userIdentifier); + + if (!await installApp(builtPackage, userIdentifier: userIdentifier)) { + return LaunchResult.failed(); + } + + final bool traceStartup = platformArgs['trace-startup'] as bool? ?? false; + ProtocolDiscovery? observatoryDiscovery; + + if (debuggingOptions.debuggingEnabled) { + observatoryDiscovery = ProtocolDiscovery.vmService( + // Avoid using getLogReader, which returns a singleton instance, because the + // observatory discovery will dipose at the end. creating a new logger here allows + // logs to be surfaced normally during `flutter drive`. + await HdcLogReader.createLogReader( + this, + _processManager, + ), + portForwarder: portForwarder, + hostPort: debuggingOptions.hostVmServicePort, + devicePort: debuggingOptions.deviceVmServicePort, + ipv6: ipv6, + logger: _logger, + ); + } + final List cmd = [ + 'shell', + 'aa', + 'start', + '-a', + builtPackage.ohosBuildData.moduleInfo.mainElement!, + '-b', + builtPackage.ohosBuildData.appInfo!.bundleName, + ]; + final String result = (await runHdcCheckedAsync(cmd)).stdout; + // This invocation returns 0 even when it fails. + if (result.toLowerCase().contains('error')) { + _logger.printError(result.trim(), wrap: false); + return LaunchResult.failed(); + } + + _package = builtPackage; + if (!debuggingOptions.debuggingEnabled) { + return LaunchResult.succeeded(); + } + + // Wait for the service protocol port here. This will complete once the + // device has printed "Observatory is listening on...". + _logger.printTrace('Waiting for observatory port to be available...'); + try { + Uri? observatoryUri; + if (debuggingOptions.buildInfo.isDebug || + debuggingOptions.buildInfo.isProfile) { + observatoryUri = await observatoryDiscovery?.uri; + _logger.printWarning('waiting for a debug connection: $observatoryUri'); + if (observatoryUri == null) { + _logger.printError( + 'Error waiting for a debug connection: ' + 'The log reader stopped unexpectedly', + ); + return LaunchResult.failed(); + } + } + return LaunchResult.succeeded(observatoryUri: observatoryUri); + } on Exception catch (error) { + _logger.printError('Error waiting for a debug connection: $error'); + return LaunchResult.failed(); + } finally { + await observatoryDiscovery?.cancel(); + } + } + + @override + Future installApp(covariant ApplicationPackage app, + {String? userIdentifier}) async { + final bool wasInstalled = + await isAppInstalled(app, userIdentifier: userIdentifier); + _logger.printTrace('Installing Hap.'); + if (await _installApp(app, userIdentifier: userIdentifier)) { + return true; + } + _logger.printTrace('Warning: Failed to install Hap.'); + if (!wasInstalled) { + return false; + } + _logger.printStatus('Uninstalling old version...'); + if (!await uninstallApp(app, userIdentifier: userIdentifier)) { + _logger.printError('Error: Uninstalling old version failed.'); + return false; + } + if (!await _installApp(app, userIdentifier: userIdentifier)) { + _logger.printError('Error: Failed to install Hap again.'); + return false; + } + return true; + } + + @override + Future stopApp(covariant ApplicationPackage? app, + {String? userIdentifier}) async { + if (app == null) { + return false; + } + final RunResult result = _processUtils.runSync( + hdcCommandForDevice(['shell', 'aa', 'force-stop', app.id]), + ); + return result.exitCode == 0; + } + + @override + late final Future targetPlatform = () async { + // const.product.cpu.abilist = arm64-v8a + final String? abilist = await _getProperty('const.product.cpu.abilist'); + if (abilist == null) { + return TargetPlatform.ohos_arm64; + } else if (abilist.contains('arm64-v8a')) { + return TargetPlatform.ohos_arm64; + } else if (abilist.contains('x64')) { + return TargetPlatform.ohos_x64; + } else { + return TargetPlatform.ohos_arm64; + } + }(); + + @override + Future uninstallApp(covariant ApplicationPackage app, + {String? userIdentifier}) async { + final List uninstallCommand = + hdcCommandForDevice(['uninstall', app.id]); + final RunResult result = + _processUtils.runSync(uninstallCommand, throwOnError: true); + return result.exitCode == 0; + } + + Future _getProperty(String name) async { + return (await _properties)[name]; + } + + List hdcCommandForDevice(List args) { + return getHdcCommandCompat(_ohosSdk, id, args); + } + + Future runHdcCheckedAsync( + List params, { + String? workingDirectory, + bool allowReentrantFlutter = false, + }) async { + return _processUtils.run( + hdcCommandForDevice(params), + throwOnError: true, + workingDirectory: workingDirectory, + allowReentrantFlutter: allowReentrantFlutter, + ); + } + + late final Future> _properties = () async { + Map properties = {}; + + final List propCommand = + hdcCommandForDevice(['shell', 'param', 'get']); + _logger.printTrace(propCommand.join(' ')); + final RunResult result = + _processUtils.runSync(propCommand, throwOnError: true); + + if (result.exitCode == 0) { + properties = parseHdcDeviceProperties(result.stdout); + } + return properties; + }(); + + Map parseHdcDeviceProperties(String str) { + final Map properties = {}; + final List split = str.split('\r\n'); + for (final String line in split) { + // some properties value may contain '=' char,but key not + final int indexOf = line.indexOf('='); + if (indexOf == -1) { + continue; + } + final String key = line.substring(0, indexOf).trim(); + final String value = line.substring(indexOf + 1).trim(); + properties[key] = value; + } + return properties; + } +} + +/// A [DevicePortForwarder] implemented for Ohos devices that uses hdc. +class OhosDevicePortForwarder extends DevicePortForwarder { + OhosDevicePortForwarder({ + required ProcessManager processManager, + required Logger logger, + required String deviceId, + required String hdcPath, + required OhosDevice ohosDevice, + }) : _deviceId = deviceId, + _hdcPath = hdcPath, + _logger = logger, + _processUtils = + ProcessUtils(logger: logger, processManager: processManager), + _ohosDevice = ohosDevice; + + final String _deviceId; + final String _hdcPath; + final Logger _logger; + final ProcessUtils _processUtils; + final OhosDevice _ohosDevice; + + static int? _extractPort(String portString) { + return int.tryParse(portString.trim()); + } + + @override + List get forwardedPorts { + final List ports = []; + + String stdout; + try { + stdout = _processUtils + .runSync( + _ohosDevice.hdcCommandForDevice([ + 'fport', + 'ls', + ]), + throwOnError: true, + ) + .stdout + .trim(); + } on ProcessException catch (error) { + _logger.printError('Failed to list forwarded ports: $error.'); + return ports; + } + + final List lines = LineSplitter.split(stdout).toList(); + for (final String line in lines) { + if (!line.startsWith(_deviceId)) { + continue; + } + final List splitLine = line.split('tcp:'); + + // Sanity check splitLine. + if (splitLine.length != 3) { + continue; + } + + // Attempt to extract ports. + final int? hostPort = _extractPort(splitLine[1]); + final int? devicePort = _extractPort(splitLine[2]); + + // Failed, skip. + if (hostPort == null || devicePort == null) { + continue; + } + + ports.add(ForwardedPort(hostPort, devicePort)); + } + + return ports; + } + + /// return one random port num , between [50000 ~ 65535] + int getOneRandomPort() { + const int min = 50000; + const int max = 65535; + return min + Random().nextInt(max - min); + } + + @override + Future forward(int devicePort, {int? hostPort}) async { + /// if host port == 0 , hdc will tip '[Fail]Forward parament failed' , so give a random port + hostPort ??= getOneRandomPort(); + + final RunResult process = await _processUtils.run( + _ohosDevice.hdcCommandForDevice([ + 'fport', + 'tcp:$hostPort', + 'tcp:$devicePort', + ]), + throwOnError: true, + ); + + if (process.stderr.isNotEmpty) { + process.throwException('hdc returned error:\n${process.stderr}'); + } + + if (process.exitCode != 0) { + if (process.stdout.isNotEmpty) { + process.throwException('hdc returned error:\n${process.stdout}'); + } + process.throwException('hdc failed without a message'); + } + + if (hostPort == 0) { + if (process.stdout.isEmpty) { + process.throwException('hdc did not report forwarded port'); + } + hostPort = int.tryParse(process.stdout); + if (hostPort == null) { + process.throwException('hdc forward error:\n${process.stdout}'); + } + } else { + if (process.stdout.isNotEmpty && !process.stdout.trim().contains('OK')) { + process.throwException('hdc returned error:\n${process.stdout}'); + } + } + + return hostPort!; + } + + @override + Future unforward(ForwardedPort forwardedPort) async { + final RunResult runResult = await _processUtils.run( + _ohosDevice.hdcCommandForDevice([ + 'fport', + 'rm', + 'tcp:${forwardedPort.hostPort}', + 'tcp:${forwardedPort.devicePort}', + ]), + ); + if (runResult.exitCode == 0) { + return; + } + _logger.printError('Failed to unforward port: $runResult'); + } + + @override + Future dispose() async { + for (final ForwardedPort port in forwardedPorts) { + await unforward(port); + } + } +} + +/// A log reader that logs from `hdc hilog`. +class HdcLogReader extends DeviceLogReader { + HdcLogReader._(this._hdcProcess, this.name); + + /// Create a new [HdcLogReader] from an [OhosDevice] instance. + static Future createLogReader( + OhosDevice device, + ProcessManager processManager, { + bool includePastLogs = false, + }) async { + final List args = [ + 'shell', + 'hilog', + ]; + + // If past logs are included then filter for 'flutter' logs only. + if (includePastLogs) { + args.addAll(['-T', 'flutter']); + } else { + // if not includePastLogs, execute clear log. + device.clearLogs(); + } + final Process process = + await processManager.start(device.hdcCommandForDevice(args)); + return HdcLogReader._(process, device.name); + } + + final Process _hdcProcess; + + @override + final String name; + + late final StreamController _linesController = + StreamController.broadcast( + onListen: _start, + onCancel: _stop, + ); + + @override + Stream get logLines => _linesController.stream; + + void _start() { + const Utf8Decoder decoder = Utf8Decoder(reportErrors: false); + _hdcProcess.stdout + .transform(decoder) + .transform(const LineSplitter()) + .listen(_onLine); + _hdcProcess.stderr + .transform(decoder) + .transform(const LineSplitter()) + .listen(_onLine); + unawaited(_hdcProcess.exitCode.whenComplete(() { + if (_linesController.hasListener) { + _linesController.close(); + } + })); + } + + // 10-27 19:57:53.779 1195 2885 I Thread:528202332952 [INFO:ohos_main.cpp(140)] flutter The Dart VM service is listening on http://0.0.0.0:34063/nBIFd7ZPwk0=/ + static final RegExp _logFormat = RegExp(r'^[\d-:. ]{30,40}[VDIWEF][^:]+:'); + + static final List _allowedTags = [ + RegExp(r'^[\d-:. ]{30,40}[VDIWEF]\s[^:]+Flutter[^:]+:\sflutter\s'), + RegExp(r'^[\d-:. ]{30,40}[IE].*Dart VM\s+'), + RegExp(r'^[WEF]\/System\.err:\s+'), + RegExp(r'^[F]\/[\S^:]+:\s+'), + ]; + + // 'F/libc(pid): Fatal signal 11' + static final RegExp _fatalLog = + RegExp(r'^F\/libc\s*\(\s*\d+\):\sFatal signal (\d+)'); + + // 'I/DEBUG(pid): ...' + static final RegExp _tombstoneLine = + RegExp(r'^[IF]\/DEBUG\s*\(\s*\d+\):\s(.+)$'); + + // 'I/DEBUG(pid): Tombstone written to: ' + static final RegExp _tombstoneTerminator = + RegExp(r'^Tombstone written to:\s'); + + // we default to true in case none of the log lines match + bool _acceptedLastLine = true; + + // Whether a fatal crash is happening or not. + // During a fatal crash only lines from the crash are accepted, the rest are + // dropped. + bool _fatalCrash = false; + + // The format of the line is controlled by the '-v' parameter passed to + // adb logcat. We are currently passing 'time', which has the format: + // mm-dd hh:mm:ss.milliseconds Priority/Tag( PID): .... + void _onLine(String line) { + // This line might be processed after the subscription is closed but before + // adb stops streaming logs. + if (_linesController.isClosed) { + return; + } + final Match? logMatch = _logFormat.firstMatch(line); + if (logMatch != null) { + bool acceptLine = false; + + if (_fatalCrash) { + // While a fatal crash is going on, only accept lines from the crash + // Otherwise the crash log in the console may get interrupted + final Match? fatalMatch = _tombstoneLine.firstMatch(line); + + if (fatalMatch != null) { + acceptLine = true; + + line = fatalMatch[1]!; + + if (_tombstoneTerminator.hasMatch(line)) { + // Hit crash terminator, stop logging the crash info + _fatalCrash = false; + } + } + } else { + // Filter on approved names and levels. + acceptLine = _allowedTags.any((RegExp re) => re.hasMatch(line)); + } + + if (acceptLine) { + _acceptedLastLine = true; + _linesController.add(line); + return; + } + _acceptedLastLine = false; + } else if (line == '--------- beginning of system' || + line == '--------- beginning of main') { + _acceptedLastLine = false; + } else { + if (_acceptedLastLine) { + _linesController.add(line); + return; + } + } + } + + void _stop() { + _linesController.close(); + _hdcProcess.kill(); + } + + @override + void dispose() { + _stop(); + } +} diff --git a/packages/flutter_tools/lib/src/ohos/ohos_device_discovery.dart b/packages/flutter_tools/lib/src/ohos/ohos_device_discovery.dart new file mode 100644 index 0000000000000000000000000000000000000000..a410af1f48b01635ac4baf54eefba4585835bf67 --- /dev/null +++ b/packages/flutter_tools/lib/src/ohos/ohos_device_discovery.dart @@ -0,0 +1,134 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development 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 'package:process/process.dart'; + +import '../base/common.dart'; +import '../base/file_system.dart'; +import '../base/io.dart'; +import '../base/logger.dart'; +import '../base/platform.dart'; +import '../base/process.dart'; +import '../base/user_messages.dart'; +import '../device.dart'; +import 'hdc_server.dart'; +import 'ohos_device.dart'; +import 'ohos_sdk.dart'; +import 'ohos_workflow.dart'; + +class OhosDevices extends PollingDeviceDiscovery { + OhosDevices({ + required OhosWorkflow ohosWorkflow, + required ProcessManager processManager, + required Logger logger, + HarmonySdk? ohosSdk, + required FileSystem fileSystem, + required Platform platform, + required UserMessages userMessages, + }) : _ohosWorkflow = ohosWorkflow, + _processUtils = ProcessUtils( + logger: logger, + processManager: processManager, + ), + _ohosSdk = ohosSdk, + _processManager = processManager, + _logger = logger, + _fileSystem = fileSystem, + _platform = platform, + _userMessages = userMessages, + super('HarmonyOS devices'); + + final OhosWorkflow _ohosWorkflow; + final ProcessUtils _processUtils; + final ProcessManager _processManager; + final Logger _logger; + final FileSystem _fileSystem; + final Platform _platform; + final UserMessages _userMessages; + final HarmonySdk? _ohosSdk; + + bool _doesNotHaveHdc() { + return _ohosSdk == null || + _ohosSdk?.hdcPath == null || + !_processManager.canRun(_ohosSdk!.hdcPath); + } + + @override + Future> pollingGetDevices({Duration? timeout}) async { + if (_doesNotHaveHdc()) { + return []; + } + String text; + + final List cmd = getHdcCommandCompat(_ohosSdk!, '', ['list', 'targets']); + + try { + text = (await _processUtils.run( + cmd, + throwOnError: true, + )) + .stdout + .trim(); + // _logger.printStatus('hdc list result:\n$text'); + } on ProcessException catch (exception) { + throwToolExit( + 'Unable to run "hdc", check your Ohos SDK installation and ' + '$kOhosSdkRoot environment variable: ${exception.executable}', + ); + } + final List devices = []; + _parseHdcDeviceOutput( + text, + devices: devices, + ); + return devices; + } + + @override + bool get supportsPlatform => _ohosWorkflow.appliesToHostPlatform; + + @override + bool get canListAnything => _ohosWorkflow.canListDevices; + + void _parseHdcDeviceOutput( + String text, { + List? devices, + List? diagnostics, + String? hdcServer, + }) { + // return empty if do not discovery any devices + if (text.contains('[Empty]') || text.contains('connect failed')) { + diagnostics?.add(text); + return; + } + + for (final String line in text.trim().split('\n')) { + final String deviceId = line.trim(); + devices?.add(OhosDevice( + deviceId, + deviceCodeName: deviceId, + ohosSdk: _ohosSdk!, + fileSystem: _fileSystem, + logger: _logger, + platform: _platform, + processManager: _processManager, + hdcServer: hdcServer, + )); + } + } + + @override + List get wellKnownIds => const []; +} diff --git a/packages/flutter_tools/lib/src/ohos/ohos_doctor.dart b/packages/flutter_tools/lib/src/ohos/ohos_doctor.dart new file mode 100644 index 0000000000000000000000000000000000000000..4091d89fa8b860326afe5b0bef65ca561531e060 --- /dev/null +++ b/packages/flutter_tools/lib/src/ohos/ohos_doctor.dart @@ -0,0 +1,161 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development 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 'package:process/process.dart'; + +import '../base/context.dart'; +import '../base/file_system.dart'; +import '../base/io.dart'; +import '../base/logger.dart'; +import '../base/os.dart'; +import '../base/platform.dart'; +import '../base/user_messages.dart'; +import '../base/version.dart'; +import '../doctor_validator.dart'; +import '../globals.dart' as globals; +import 'ohos_sdk.dart'; + +OhosValidator? get ohosValidator => context.get(); + +/// A combination of version description and parsed version number. +class _VersionInfo { + /// Constructs a VersionInfo from a version description string. + /// + /// This should contain a version number. For example: + /// "clang version 9.0.1-6+build1" + _VersionInfo(this.description) { + final String? versionString = RegExp(r'[0-9]+\.[0-9]+(?:\.[0-9]+)?') + .firstMatch(description) + ?.group(0); + number = Version.parse(versionString); + } + + // The full info string reported by the binary. + String description; + + // The parsed Version. + Version? number; +} + +class OhosValidator extends DoctorValidator { + OhosValidator({ + required HarmonySdk? ohosSdk, + required FileSystem fileSystem, + required Logger logger, + required Platform platform, + required ProcessManager processManager, + required UserMessages userMessages, + }) : _hosSdk = ohosSdk, + _fileSystem = fileSystem, + _logger = logger, + _operatingSystemUtils = OperatingSystemUtils( + fileSystem: fileSystem, + logger: logger, + platform: platform, + processManager: processManager, + ), + _platform = platform, + _processManager = processManager, + _userMessages = userMessages, + super('HarmonyOS toolchain - develop for HarmonyOS devices'); + + final HarmonySdk? _hosSdk; + final FileSystem _fileSystem; + final Logger _logger; + final OperatingSystemUtils _operatingSystemUtils; + final Platform _platform; + final ProcessManager _processManager; + final UserMessages _userMessages; + + final bool isWindows = globals.platform.isWindows; + + @override + Future validate() async { + ValidationType validationType = ValidationType.success; + final List messages = []; + + /// check ohos sdk exist and version correct + if (HarmonySdk.locateHarmonySdk() != null ) { + messages.add(ValidationMessage(_userMessages.ohosSdkVersion(_hosSdk!))); + } else { + validationType = ValidationType.missing; + if (_hosSdk != null) { + messages.add(ValidationMessage.error( + _userMessages.ohosSdkMissing((_hosSdk?.sdkPath) ?? ''))); + } + messages + .add(ValidationMessage.error(_userMessages.hosSdkInstallation())); + } + + /// check ohpm + final _VersionInfo? ohpmVersion = await _getBinaryVersion(['ohpm', '--version']); + final String? ohpmVersionString = ohpmVersion?.description; + if (ohpmVersionString == null) { + validationType = ValidationType.missing; + messages.add(ValidationMessage.error(_userMessages.ohpmMissing())); + } else { + messages + .add(ValidationMessage(_userMessages.ohpmVersion(ohpmVersionString))); + } + + /// check node + final _VersionInfo? nodeVersion = await _getBinaryVersion(['node', '--version']); + final String? nodeVersionString = nodeVersion?.description; + if (nodeVersionString == null) { + validationType = ValidationType.missing; + messages.add(ValidationMessage.error(_userMessages.nodeMissing())); + } else { + messages + .add(ValidationMessage(_userMessages.nodeVersion(nodeVersionString))); + } + + /// check hvigorw + final _VersionInfo? hvigorPath; + if (_platform.isWindows) { + hvigorPath = await _getBinaryVersion(['where', 'hvigorw']); + } else { + hvigorPath = await _getBinaryVersion(['which', 'hvigorw']); + } + final String? hvigorPathString = hvigorPath?.description; + if (hvigorPathString == null) { + validationType = ValidationType.missing; + messages.add(ValidationMessage.error(_userMessages.hvigorwMissing())); + } else { + messages + .add(ValidationMessage(_userMessages.hvigorwPath(hvigorPathString))); + } + return ValidationResult(validationType, messages); + } + + /// Returns the installed version of [binary], or null if it's not installed. + /// + /// Requires tha [binary] take a '--version' flag, and print a version of the + /// form x.y.z somewhere on the first line of output. + Future<_VersionInfo?> _getBinaryVersion(List commands) async { + ProcessResult? result; + try { + result = await _processManager.run(commands); + } on ArgumentError { + // ignore error. + } on ProcessException { + // ignore error. + } + if (result == null || result.exitCode != 0) { + return null; + } + final String firstLine = (result.stdout as String).split('\n').first.trim(); + return _VersionInfo(firstLine); + } +} diff --git a/packages/flutter_tools/lib/src/ohos/ohos_plugins_manager.dart b/packages/flutter_tools/lib/src/ohos/ohos_plugins_manager.dart new file mode 100644 index 0000000000000000000000000000000000000000..574df4264618bf99f0995a7162e06d68ab749362 --- /dev/null +++ b/packages/flutter_tools/lib/src/ohos/ohos_plugins_manager.dart @@ -0,0 +1,212 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development 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 'dart:convert'; + +import 'package:json5/json5.dart'; + +import '../base/common.dart'; +import '../base/file_system.dart'; +import '../base/utils.dart'; +import '../flutter_plugins.dart'; +import '../globals.dart' as globals; +import '../platform_plugins.dart'; +import '../plugins.dart'; +import '../project.dart'; + +const String kUseAbsolutePathOfHar = 'useAbsolutePathOfHar'; + +/// 检查 ohos plugin 依赖 +Future checkOhosPluginsDependencies(FlutterProject flutterProject) async { + final List plugins = (await findPlugins(flutterProject)) + .where((Plugin p) => p.platforms.containsKey(OhosPlugin.kConfigKey)) + .toList(); + final File packageFile = flutterProject.ohos.flutterModulePackageFile; + if (!packageFile.existsSync()) { + globals.logger.printTrace('check if oh-package.json5 file:($packageFile) exist ?'); + return; + } + + final SettingsFile settings = flutterProject.ohos.settings; + final bool useAbsolutePathOfHar = settings.values[kUseAbsolutePathOfHar] == 'true'; + + final String packageConfig = packageFile.readAsStringSync(); + final Map config = JSON5.parse(packageConfig) as Map; + final Map dependencies = + config['dependencies'] as Map; + final List removeList = []; + for (final Plugin plugin in plugins) { + for (final String key in dependencies.keys) { + if (key.startsWith('@ohos') && key.contains(plugin.name)) { + removeList.add(key); + } + } + final String absolutePath = globals.fs.path.join(flutterProject.ohos.ohosRoot.path, 'har/${plugin.name}.har'); + if (useAbsolutePathOfHar && flutterProject.isModule) { + dependencies[plugin.name] = 'file:$absolutePath'; + } else { + final String relativePath = _relative(absolutePath, globals.fs.path.dirname(packageFile.path)); + dependencies[plugin.name] = 'file:$relativePath'; + } + } + for (final String key in removeList) { + globals.printStatus( + 'OhosDependenciesManager: deprecated plugin dependencies "$key" has been removed.'); + dependencies.remove(key); + } + final String configNew = const JsonEncoder.withIndent(' ').convert(config); + packageFile.writeAsStringSync(configNew, flush: true); +} + +/// 添加到工程级 build-profile.json5 的 modules 中 +Future addPluginsModules(FlutterProject flutterProject) async { + final List plugins = (await findPlugins(flutterProject)) + .where((Plugin p) => p.platforms.containsKey(OhosPlugin.kConfigKey)) + .toList(); + if (plugins.isEmpty) { + return; + } + final File buildProfileFile = flutterProject.ohos.getBuildProfileFile(); + if (!buildProfileFile.existsSync()) { + throwToolExit('check if build-profile.json5 file:($buildProfileFile) exist ?'); + } + final String packageConfig = buildProfileFile.readAsStringSync(); + final Map buildProfile = JSON5.parse(packageConfig) as Map; + final List> modules = (buildProfile['modules'] as List).cast(); + final Map modulesMap = Map.fromEntries(modules.map((e) => MapEntry(e['name'] as String, e))); + for (final Plugin plugin in plugins) { + if (modulesMap.containsKey(plugin.name)) { + continue; + } + modules.add({ + 'name': plugin.name, + 'srcPath': _relative( + globals.fs.path.join(plugin.path, OhosPlugin.kConfigKey), + flutterProject.ohos.ohosRoot.path, + ), + 'targets': >[ + { + 'name': 'default', + 'applyToProducts': ['default'] + } + ], + }); + } + final String buildProfileNew = const JsonEncoder.withIndent(' ').convert(buildProfile); + buildProfileFile.writeAsStringSync(buildProfileNew, flush: true); +} + +/// 在工程级的的oh-package.json5里添加flutter_module以及plugins的配置 +Future addFlutterModuleAndPluginsSrcOverrides(FlutterProject flutterProject) async { + final List plugins = (await findPlugins(flutterProject)) + .where((Plugin p) => p.platforms.containsKey(OhosPlugin.kConfigKey)) + .toList(); + if (plugins.isEmpty) { + return; + } + final File packageFile = flutterProject.ohos.ohosRoot.childFile('oh-package.json5'); + if (!packageFile.existsSync()) { + globals.logger.printTrace('check if oh-package.json5 file:($packageFile) exist ?'); + return; + } + final String packageConfig = packageFile.readAsStringSync(); + final Map config = JSON5.parse(packageConfig) as Map; + final Map overrides = config['overrides'] as Map? ?? {}; + + for (final Plugin plugin in plugins) { + overrides[plugin.name] = _relative( + globals.fs.path.join(plugin.path, OhosPlugin.kConfigKey), + flutterProject.ohos.ohosRoot.path, + ); + } + final String relativePath = _relative(flutterProject.ohos.flutterModuleDirectory.path, flutterProject.ohos.ohosRoot.path); + overrides['@ohos/flutter_module'] = 'file:./$relativePath'; + overrides['@ohos/flutter_ohos'] = 'file:./har/flutter.har'; + final String configNew = const JsonEncoder.withIndent(' ').convert(config); + packageFile.writeAsStringSync(configNew, flush: true); +} + +/// 添加到工程级 build-profile.json5 的 modules 中 +Future removePluginsModules(FlutterProject flutterProject) async { + final List plugins = (await findPlugins(flutterProject)) + .where((Plugin p) => p.platforms.containsKey(OhosPlugin.kConfigKey)) + .toList(); + if (plugins.isEmpty) { + return; + } + final Map pluginsMap = Map.fromEntries( + plugins.map((Plugin e) => MapEntry(e.name, e)) + ); + final File buildProfileFile = flutterProject.ohos.getBuildProfileFile(); + if (!buildProfileFile.existsSync()) { + globals.logger.printTrace('check if build-profile.json5 file:($buildProfileFile) exist ?'); + return; + } + final String packageConfig = buildProfileFile.readAsStringSync(); + final Map buildProfile = JSON5.parse(packageConfig) as Map; + final List> modules = (buildProfile['modules'] as List).cast(); + final List> newModules = >[]; + + for (final Map module in modules) { + if (pluginsMap.containsKey(module['name'])) { + continue; + } else { + newModules.add(module); + } + } + buildProfile['modules'] = newModules; + final String buildProfileNew = const JsonEncoder.withIndent(' ').convert(buildProfile); + buildProfileFile.writeAsStringSync(buildProfileNew, flush: true); +} + +/// 把flutter_module跟plugins的依赖写入工程级oh-package.json5里的overrides +Future addFlutterModuleAndPluginsOverrides(FlutterProject flutterProject) async { + final List plugins = (await findPlugins(flutterProject)) + .where((Plugin p) => p.platforms.containsKey(OhosPlugin.kConfigKey)) + .toList(); + if (plugins.isEmpty) { + return; + } + final File packageFile = flutterProject.ohos.ohosRoot.childFile('oh-package.json5'); + if (!packageFile.existsSync()) { + globals.logger.printTrace('check if oh-package.json5 file:($packageFile) exist ?'); + return; + } + final String packageConfig = packageFile.readAsStringSync(); + final Map config = JSON5.parse(packageConfig) as Map; + final Map overrides = config['overrides'] as Map? ?? {}; + final SettingsFile settings = flutterProject.ohos.settings; + final bool useAbsolutePathOfHar = settings.values[kUseAbsolutePathOfHar] == 'true'; + + for (final Plugin plugin in plugins) { + final String absolutePath = globals.fs.path.join(flutterProject.ohos.ohosRoot.path, 'har/${plugin.name}.har'); + if (useAbsolutePathOfHar && flutterProject.isModule) { + overrides[plugin.name] = 'file:$absolutePath'; + } else { + overrides[plugin.name] = 'file:./har/${plugin.name}.har'; + } + } + final String configNew = const JsonEncoder.withIndent(' ').convert(config); + packageFile.writeAsStringSync(configNew, flush: true); +} + +String _relative(String path, String from) { + final String realPath = path.endsWith('.har') + ? path + : globals.fs.file(path).resolveSymbolicLinksSync(); + final String realFrom = globals.fs.file(from).resolveSymbolicLinksSync(); + final String result = globals.fs.path.relative(realPath, from: realFrom).replaceAll(r'\', '/'); + return result; +} diff --git a/packages/flutter_tools/lib/src/ohos/ohos_sdk.dart b/packages/flutter_tools/lib/src/ohos/ohos_sdk.dart new file mode 100644 index 0000000000000000000000000000000000000000..0b737851bb81f750fcb07e9c09bc8e1cc44c4998 --- /dev/null +++ b/packages/flutter_tools/lib/src/ohos/ohos_sdk.dart @@ -0,0 +1,332 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development 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 'dart:collection'; +import 'package:json5/json5.dart'; +import '../base/file_system.dart'; +import '../globals.dart' as globals; + +// OpenHarmony SDK +const String kOhosHome = 'OHOS_HOME'; +const String kOhosSdkRoot = 'OHOS_SDK_HOME'; +// HarmonyOS SDK +const String kHmosHome = 'HOS_SDK_HOME'; +const String kDevecoSdk = 'DEVECO_SDK_HOME'; + +// for api11 developer preview +SplayTreeMap sdkVersionMap = SplayTreeMap((a, b) => b.compareTo(a)); + +// find first hdc in sdkPath +String? _getHdcPath(String sdkPath) { + final bool isWindows = globals.platform.isWindows; + final String hdcName = isWindows ? 'hdc.exe' : 'hdc'; + for (final int api in sdkVersionMap.keys) { + final String sdkVersion = sdkVersionMap[api]!; + final List findList = [ + globals.fs.path.join(sdkPath, sdkVersion, 'openharmony', 'toolchains', hdcName), + globals.fs.path.join(sdkPath, sdkVersion, 'base', 'toolchains', hdcName), + globals.fs.path.join(sdkPath, api.toString(), 'toolchains', hdcName), + ]; + for (final String path in findList) { + if (globals.fs.file(path).existsSync()) { + return path; + } + } + } + return null; +} + +abstract class HarmonySdk { + // name + String get name; + // sdk path + String get sdkPath; + // hdc path + String? get hdcPath; + // available api list + List get apiAvailable; + // is valid sdk + bool get isValidDirectory; + + static HarmonySdk? locateHarmonySdk() { + final OhosSdk? ohosSdk = OhosSdk.localOhosSdk(); + final HmosSdk? hmosSdk = HmosSdk.localHmosSdk(); + if (ohosSdk != null) { + return ohosSdk; + } else if (hmosSdk != null) { + return hmosSdk; + } else { + return null; + } + } + + static bool isNumeric(String str) { + return double.tryParse(str) != null; + } +} + +class OhosSdk implements HarmonySdk { + OhosSdk(this._sdkDir); + + final Directory _sdkDir; + + @override + String get name => 'OpenHarmonySDK'; + + @override + String get sdkPath => _sdkDir.path; + + @override + String? get hdcPath => _getHdcPath(_sdkDir.path); + + @override + List get apiAvailable => getAvailableApi(); + + @override + bool get isValidDirectory => validSdkDirectory(_sdkDir.path); + + static OhosSdk? localOhosSdk() { + String? findOhosHomeDir() { + String? ohosHomeDir; + if (globals.config.containsKey('ohos-sdk')) { + ohosHomeDir = globals.config.getValue('ohos-sdk') as String?; + } else if (globals.platform.environment.containsKey(kOhosHome)) { + ohosHomeDir = globals.platform.environment[kOhosHome]; + } else if (globals.platform.environment.containsKey(kOhosSdkRoot)) { + ohosHomeDir = globals.platform.environment[kOhosSdkRoot]; + } + + if (ohosHomeDir != null) { + initSdkVersionMap(ohosHomeDir); + + if (validSdkDirectory(ohosHomeDir)) { + return ohosHomeDir; + } + if (validSdkDirectory(globals.fs.path.join(ohosHomeDir, 'sdk'))) { + return globals.fs.path.join(ohosHomeDir, 'sdk'); + } + } + + //openharmony/11/toolchains/hdc + final List hdcBins = globals.os.whichAll(globals.platform.isWindows ? 'hdc.exe' : 'hdc'); + for (File hdcBin in hdcBins) { + // Make sure we're using the hdc from the SDK. + hdcBin = globals.fs.file(hdcBin.resolveSymbolicLinksSync()); + final String dir = hdcBin.parent.parent.parent.path; + Directory directory = globals.fs.directory(dir); + if (directory.existsSync()) { + initSdkVersionMap(dir); + if (validSdkDirectory(dir)) { + return dir; + } + } + } + + return null; + } + + final String? ohosHomeDir = findOhosHomeDir(); + if (ohosHomeDir == null) { + // No dice. + globals.printTrace('Unable to locate an OpenHarmony SDK.'); + return null; + } + + return OhosSdk(globals.fs.directory(ohosHomeDir)); + } + + // int sdkVersionMap + static void initSdkVersionMap(String sdkPath) { + final Directory directory = globals.fs.directory(sdkPath); + if (directory.existsSync()) { + for (FileSystemEntity element in directory.listSync()) { + if (element is Directory) { + Directory dir = globals.fs.directory(element).childDirectory('toolchains'); + if (dir.existsSync() && HarmonySdk.isNumeric(element.basename)) { + sdkVersionMap.addAll({int.parse(element.basename): element.basename}); + } + } + } + } + } + + static bool validSdkDirectory(String dir) { + return hdcExists(dir); + } + + static bool hdcExists(String dir) { + return _getHdcPath(dir) != null; + } + + List getAvailableApi() { + final List list = []; + // for api11 developer preview + for (final int api in sdkVersionMap.keys) { + final Directory directory = + globals.fs.directory(globals.fs.path.join(sdkPath, sdkVersionMap[api])); + if (directory.existsSync()) { + list.add('$api:${sdkVersionMap[api]!}'); + } + } + // if not found, find it in previous version + if (list.isEmpty) { + for (final int folder in sdkVersionMap.keys) { + final Directory directory = + globals.fs.directory(globals.fs.path.join(sdkPath, folder.toString())); + if (directory.existsSync()) { + list.add(folder.toString()); + } + } + } + return list; + } + +} + +class HmosSdk implements HarmonySdk { + HmosSdk(this._sdkDir); + + final Directory _sdkDir; + + @override + String get name => 'HarmonyOSSDK'; + + @override + String? get hdcPath => _getHdcPath(_sdkDir.path); + + @override + String get sdkPath => _sdkDir.path; + + @override + List get apiAvailable => getAvailableApi(); + + @override + bool get isValidDirectory => validSdkDirectory(sdkPath); + + List getAvailableApi() { + final List list = []; + // for api11 developer preview + for (final int api in sdkVersionMap.keys) { + final Directory directory = + globals.fs.directory(globals.fs.path.join(sdkPath, sdkVersionMap[api])); + if (directory.existsSync()) { + list.add('$api:${sdkVersionMap[api]!}'); + } + } + // if not found, find it in previous version + if (list.isEmpty) { + for (final int folder in sdkVersionMap.keys) { + final Directory directory = + globals.fs.directory(globals.fs.path.join(sdkPath, folder.toString())); + if (directory.existsSync()) { + list.add(folder.toString()); + } + } + } + return list; + } + + static HmosSdk? localHmosSdk() { + String? findHmosHomeDir() { + String? hmosHomeDir; + if (globals.config.containsKey('ohos-sdk')) { + hmosHomeDir = globals.config.getValue('ohos-sdk') as String?; + } else if (globals.platform.environment.containsKey(kDevecoSdk)) { + hmosHomeDir = globals.platform.environment[kDevecoSdk]; + } else if (globals.platform.environment.containsKey(kHmosHome)) { + hmosHomeDir = globals.platform.environment[kHmosHome]; + } + + if (hmosHomeDir != null) { + initSdkVersionMap(hmosHomeDir); + + if (validSdkDirectory(hmosHomeDir)) { + return hmosHomeDir; + } + } + //sdk/HarmonyOS-NEXT-DP1/base/toolchains/hdc + final List hdcBins = globals.os.whichAll(globals.platform.isWindows ? 'hdc.exe' : 'hdc'); + for (File hdcBin in hdcBins) { + // Make sure we're using the hdc from the SDK. + hdcBin = globals.fs.file(hdcBin.resolveSymbolicLinksSync()); + final String dir = hdcBin.parent.parent.parent.parent.path; + Directory directory = globals.fs.directory(dir); + if (directory.existsSync()) { + initSdkVersionMap(dir); + if (validSdkDirectory(dir)) { + return dir; + } + } + } + + return null; + } + + final String? hmosHomeDir = findHmosHomeDir(); + if (hmosHomeDir == null) { + // No dice. + // globals.printError('Unable to locate an HarmonyOS SDK.'); + return null; + } + + return HmosSdk(globals.fs.directory(hmosHomeDir)); + } + + // int sdkVersionMap + static void initSdkVersionMap(String sdkPath) { + final Directory directory = globals.fs.directory(sdkPath); + if (directory.existsSync()) { + for (FileSystemEntity element in directory.listSync()) { + if (element is Directory) { + // read apiVersion from sdk-pkg.json + File sdkPkgJson = globals.fs.directory(element).childFile('sdk-pkg.json'); + if (sdkPkgJson.existsSync()) { + dynamic sdk_pkg = JSON5.parse(sdkPkgJson.readAsStringSync()); + if (sdk_pkg['data'] != null && sdk_pkg['data']['apiVersion'] != null + && HarmonySdk.isNumeric(sdk_pkg['data']['apiVersion'] as String)) { + sdkVersionMap.addAll({int.parse(sdk_pkg['data']['apiVersion'] as String): element.basename}); + } + } + } + } + } + } + + //harmonyOsSdk,包含目录hmscore和openharmony + static bool validSdkDirectory(String hmosHomeDir) { + return validApi10SdkDirectory(hmosHomeDir) || + validApi11SdkDirectory(hmosHomeDir); + } + + static bool validApi10SdkDirectory(String hmosHomeDir) { + final Directory directory = globals.fs.directory(hmosHomeDir); + return directory.childDirectory('hmscore').existsSync() && + directory.childDirectory('openharmony').existsSync(); + } + + static bool validApi11SdkDirectory(String hmosHomeDir) { + if (sdkVersionMap.length == 0) { + return false; + } + for (String sdkName in sdkVersionMap.values) { + Directory sdkDir = globals.fs.directory( + globals.fs.path.join(hmosHomeDir, sdkName)); + if (!sdkDir.existsSync()) { + return false; + } + } + return true; + } +} diff --git a/packages/flutter_tools/lib/src/ohos/ohos_workflow.dart b/packages/flutter_tools/lib/src/ohos/ohos_workflow.dart new file mode 100644 index 0000000000000000000000000000000000000000..f372aff99363109052afd3a9e5a2408fa7edce5e --- /dev/null +++ b/packages/flutter_tools/lib/src/ohos/ohos_workflow.dart @@ -0,0 +1,47 @@ +/* +* Copyright (c) 2023 Hunan OpenValley Digital Industry Development 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 '../base/context.dart'; +import '../doctor_validator.dart'; +import '../features.dart'; +import 'ohos_sdk.dart'; + +OhosWorkflow? get ohosWorkflow => context.get(); + +class OhosWorkflow implements Workflow { + OhosWorkflow({ + required HarmonySdk? ohosSdk, + required FeatureFlags featureFlags, + }) : _ohosSdk = ohosSdk, + _featureFlags = featureFlags; + + final HarmonySdk? _ohosSdk; + final FeatureFlags _featureFlags; + + @override + bool get appliesToHostPlatform => _featureFlags.isOhosEnabled; + + @override + bool get canListDevices => + appliesToHostPlatform && _ohosSdk != null && _ohosSdk?.hdcPath != null; + + @override + bool get canLaunchDevices => + appliesToHostPlatform && _ohosSdk != null && _ohosSdk?.hdcPath != null; + + @override + bool get canListEmulators => canListDevices; + //&& _ohosSdk?.emulatorPath != null; +} diff --git a/packages/flutter_tools/lib/src/platform_plugins.dart b/packages/flutter_tools/lib/src/platform_plugins.dart index 5efa3e0e3a4e6e1264847b481b9851d074def51b..e64ae1df6c3ca839765ec3c271aed6dd681d7104 100644 --- a/packages/flutter_tools/lib/src/platform_plugins.dart +++ b/packages/flutter_tools/lib/src/platform_plugins.dart @@ -140,9 +140,9 @@ class AndroidPlugin extends PluginPlatform implements NativeOrDartPlugin { 'name': name, if (package != null) 'package': package, if (pluginClass != null) 'class': pluginClass, - if (dartPluginClass != null) kDartPluginClass : dartPluginClass, + if (dartPluginClass != null) kDartPluginClass: dartPluginClass, if (ffiPlugin) kFfiPlugin: true, - if (defaultPackage != null) kDefaultPackage : defaultPackage, + if (defaultPackage != null) kDefaultPackage: defaultPackage, // Mustache doesn't support complex types. 'supportsEmbeddingV1': _supportedEmbeddings.contains('1'), 'supportsEmbeddingV2': _supportedEmbeddings.contains('2'), @@ -195,11 +195,10 @@ class AndroidPlugin extends PluginPlatform implements NativeOrDartPlugin { if (mainPluginClass == null || !mainClassFound) { assert(mainClassCandidates.length <= 2); throwToolExit( - "The plugin `$name` doesn't have a main class defined in ${mainClassCandidates.join(' or ')}. " - "This is likely to due to an incorrect `androidPackage: $package` or `mainClass` entry in the plugin's pubspec.yaml.\n" - 'If you are the author of this plugin, fix the `androidPackage` entry or move the main class to any of locations used above. ' - 'Otherwise, please contact the author of this plugin and consider using a different plugin in the meanwhile. ' - ); + "The plugin `$name` doesn't have a main class defined in ${mainClassCandidates.join(' or ')}. " + "This is likely to due to an incorrect `androidPackage: $package` or `mainClass` entry in the plugin's pubspec.yaml.\n" + 'If you are the author of this plugin, fix the `androidPackage` entry or move the main class to any of locations used above. ' + 'Otherwise, please contact the author of this plugin and consider using a different plugin in the meanwhile. '); } final String mainClassContent = mainPluginClass.readAsStringSync(); @@ -209,8 +208,8 @@ class AndroidPlugin extends PluginPlatform implements NativeOrDartPlugin { } else { supportedEmbeddings.add('1'); } - if (mainClassContent.contains('PluginRegistry') - && mainClassContent.contains('registerWith')) { + if (mainClassContent.contains('PluginRegistry') && + mainClassContent.contains('registerWith')) { supportedEmbeddings.add('1'); } return supportedEmbeddings; @@ -288,7 +287,7 @@ class IOSPlugin extends PluginPlatform implements NativeOrDartPlugin, DarwinPlug 'name': name, 'prefix': classPrefix, if (pluginClass != null) 'class': pluginClass, - if (dartPluginClass != null) kDartPluginClass : dartPluginClass, + if (dartPluginClass != null) kDartPluginClass: dartPluginClass, if (ffiPlugin) kFfiPlugin: true, if (sharedDarwinSource) kSharedDarwinSource: true, if (defaultPackage != null) kDefaultPackage : defaultPackage, @@ -378,7 +377,9 @@ class WindowsPlugin extends PluginPlatform this.defaultPackage, this.variants = const {}, }) : ffiPlugin = ffiPlugin ?? false, - assert(pluginClass != null || dartPluginClass != null || defaultPackage != null); + assert(pluginClass != null || + dartPluginClass != null || + defaultPackage != null); factory WindowsPlugin.fromYaml(String name, YamlMap yaml) { assert(validate(yaml)); @@ -393,7 +394,8 @@ class WindowsPlugin extends PluginPlatform // If no variant list is provided assume Win32 for backward compatibility. variants.add(PluginPlatformVariant.win32); } else { - const Map variantByName = { + const Map variantByName = + { 'win32': PluginPlatformVariant.win32, }; for (final String variantName in variantList.cast()) { @@ -468,7 +470,10 @@ class LinuxPlugin extends PluginPlatform implements NativeOrDartPlugin { bool? ffiPlugin, this.defaultPackage, }) : ffiPlugin = ffiPlugin ?? false, - assert(pluginClass != null || dartPluginClass != null || (ffiPlugin ?? false) || defaultPackage != null); + assert(pluginClass != null || + dartPluginClass != null || + (ffiPlugin ?? false) || + defaultPackage != null); LinuxPlugin.fromYaml(this.name, YamlMap yaml) : assert(validate(yaml)), @@ -564,10 +569,93 @@ class WebPlugin extends PluginPlatform { } } +class OhosPlugin extends PluginPlatform implements NativeOrDartPlugin { + OhosPlugin({ + required this.name, + required this.pluginPath, + this.package, + this.pluginClass, + this.dartPluginClass, + bool? ffiPlugin, + this.defaultPackage, + required FileSystem fileSystem, + }) : _fileSystem = fileSystem, + ffiPlugin = ffiPlugin ?? false; + + factory OhosPlugin.fromYaml( + String name, YamlMap yaml, String pluginPath, FileSystem fileSystem) { + assert(validate(yaml)); + return OhosPlugin( + name: name, + package: yaml['package'] as String?, + pluginClass: yaml[kPluginClass] as String?, + dartPluginClass: yaml[kDartPluginClass] as String?, + ffiPlugin: yaml[kFfiPlugin] as bool?, + defaultPackage: yaml[kDefaultPackage] as String?, + pluginPath: pluginPath, + fileSystem: fileSystem, + ); + } + + final FileSystem _fileSystem; + + @override + bool hasMethodChannel() => pluginClass != null; + + @override + bool hasFfi() => ffiPlugin; + + @override + bool hasDart() => dartPluginClass != null; + + static bool validate(YamlMap yaml) { + if (yaml == null) { + return false; + } + return yaml[kPluginClass] is String || + yaml[kDartPluginClass] is String || + yaml[kFfiPlugin] == true || + yaml[kDefaultPackage] is String; + } + + static const String kConfigKey = 'ohos'; + + /// The plugin name defined in pubspec.yaml. + final String name; + + /// The plugin package name defined in pubspec.yaml. + final String? package; + + /// The native plugin main class defined in pubspec.yaml, if any. + final String? pluginClass; + + /// The Dart plugin main class defined in pubspec.yaml, if any. + final String? dartPluginClass; + + /// Is FFI plugin defined in pubspec.yaml. + final bool ffiPlugin; + + /// The default implementation package defined in pubspec.yaml, if any. + final String? defaultPackage; + + /// The absolute path to the plugin in the pub cache. + final String pluginPath; + + @override + Map toMap() { + return { + 'name': name, + if (package != null) 'package': package, + if (pluginClass != null) 'class': pluginClass, + if (dartPluginClass != null) kDartPluginClass: dartPluginClass, + if (ffiPlugin) kFfiPlugin: true, + if (defaultPackage != null) kDefaultPackage: defaultPackage, + }; + } +} + final RegExp _internalCapitalLetterRegex = RegExp(r'(?=(?!^)[A-Z])'); String _filenameForCppClass(String className) { - return className.splitMapJoin( - _internalCapitalLetterRegex, - onMatch: (_) => '_', - onNonMatch: (String n) => n.toLowerCase()); + return className.splitMapJoin(_internalCapitalLetterRegex, + onMatch: (_) => '_', onNonMatch: (String n) => n.toLowerCase()); } diff --git a/packages/flutter_tools/lib/src/plugins.dart b/packages/flutter_tools/lib/src/plugins.dart index f43309a4e7c86310c22f0f72efa4136001b62811..d5c9c633ee95e627583e107adba06911e1f40d9d 100644 --- a/packages/flutter_tools/lib/src/plugins.dart +++ b/packages/flutter_tools/lib/src/plugins.dart @@ -141,6 +141,15 @@ class Plugin { WindowsPlugin.fromYaml(name, platformsYaml[WindowsPlugin.kConfigKey] as YamlMap); } + if (_providesImplementationForPlatform(platformsYaml, OhosPlugin.kConfigKey)) { + platforms[OhosPlugin.kConfigKey] = OhosPlugin.fromYaml( + name, + platformsYaml[OhosPlugin.kConfigKey] as YamlMap, + path, + fileSystem, + ); + } + // TODO(stuartmorgan): Consider merging web into this common handling; the // fact that its implementation of Dart-only plugins and default packages // are separate is legacy. @@ -150,6 +159,7 @@ class Plugin { LinuxPlugin.kConfigKey, MacOSPlugin.kConfigKey, WindowsPlugin.kConfigKey, + OhosPlugin.kConfigKey, ]; final Map defaultPackages = {}; final Map dartPluginClasses = {}; @@ -314,6 +324,9 @@ class Plugin { if (isInvalid(WindowsPlugin.kConfigKey, WindowsPlugin.validate)) { errors.add('Invalid "windows" plugin specification.'); } + if (isInvalid(OhosPlugin.kConfigKey, OhosPlugin.validate)) { + errors.add('Invalid "ohos" plugin specification.'); + } return errors; } diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart index b4c2e69399084ec3145d1702af2bb4615c63cf3e..b211ae38841aee2dc06085bd90aa08b288d7ac8e 100644 --- a/packages/flutter_tools/lib/src/project.dart +++ b/packages/flutter_tools/lib/src/project.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:io' as io; +import 'package:json5/json5.dart'; import 'package:meta/meta.dart'; import 'package:xml/xml.dart'; import 'package:yaml/yaml.dart'; @@ -9,6 +11,9 @@ import 'package:yaml/yaml.dart'; import '../src/convert.dart'; import 'android/android_builder.dart'; import 'android/gradle_utils.dart' as gradle; +import 'base/io.dart'; +import 'ohos/application_package.dart'; +import 'ohos/hvigor_utils.dart' as hvigor; import 'base/common.dart'; import 'base/error_handling_io.dart'; import 'base/file_system.dart'; @@ -21,6 +26,7 @@ import 'features.dart'; import 'flutter_manifest.dart'; import 'flutter_plugins.dart'; import 'globals.dart' as globals; +import 'ohos/hvigor_utils.dart'; import 'platform_plugins.dart'; import 'project_validator_result.dart'; import 'template.dart'; @@ -31,6 +37,7 @@ export 'xcode_project.dart'; /// Enum for each officially supported platform. enum SupportedPlatform { + ohos, android, ios, linux, @@ -45,15 +52,14 @@ class FlutterProjectFactory { FlutterProjectFactory({ required Logger logger, required FileSystem fileSystem, - }) : _logger = logger, - _fileSystem = fileSystem; + }) : _logger = logger, + _fileSystem = fileSystem; final Logger _logger; final FileSystem _fileSystem; @visibleForTesting - final Map projects = - {}; + final Map projects = {}; /// Returns a [FlutterProject] view of the given directory or a ToolExit error, /// if `pubspec.yaml` or `example/pubspec.yaml` is invalid. @@ -91,15 +97,18 @@ class FlutterProject { /// Returns a [FlutterProject] view of the given directory or a ToolExit error, /// if `pubspec.yaml` or `example/pubspec.yaml` is invalid. - static FlutterProject fromDirectory(Directory directory) => globals.projectFactory.fromDirectory(directory); + static FlutterProject fromDirectory(Directory directory) => + globals.projectFactory.fromDirectory(directory); /// Returns a [FlutterProject] view of the current directory or a ToolExit error, /// if `pubspec.yaml` or `example/pubspec.yaml` is invalid. - static FlutterProject current() => globals.projectFactory.fromDirectory(globals.fs.currentDirectory); + static FlutterProject current() => + globals.projectFactory.fromDirectory(globals.fs.currentDirectory); /// Create a [FlutterProject] and bypass the project caching. @visibleForTesting - static FlutterProject fromDirectoryTest(Directory directory, [Logger? logger]) { + static FlutterProject fromDirectoryTest(Directory directory, + [Logger? logger]) { final FileSystem fileSystem = directory.fileSystem; logger ??= BufferLogger.test(); final FlutterManifest manifest = FlutterProject._readManifest( @@ -109,8 +118,8 @@ class FlutterProject { ); final FlutterManifest exampleManifest = FlutterProject._readManifest( FlutterProject._exampleDirectory(directory) - .childFile(bundle.defaultManifestPath) - .path, + .childFile(bundle.defaultManifestPath) + .path, logger: logger, fileSystem: fileSystem, ); @@ -140,7 +149,8 @@ class FlutterProject { // used during create as best-effort, use the // default target bundle identifier. try { - final String? bundleIdentifier = await ios.productBundleIdentifier(null); + final String? bundleIdentifier = + await ios.productBundleIdentifier(null); if (bundleIdentifier != null) { candidates.add(bundleIdentifier); } @@ -155,10 +165,8 @@ class FlutterProject { final String? applicationId = android.applicationId; final String? group = android.group; candidates.addAll([ - if (applicationId != null) - applicationId, - if (group != null) - group, + if (applicationId != null) applicationId, + if (group != null) group, ]); } if (example.android.existsSync()) { @@ -168,12 +176,15 @@ class FlutterProject { } } if (example.ios.existsSync()) { - final String? bundleIdentifier = await example.ios.productBundleIdentifier(null); + final String? bundleIdentifier = + await example.ios.productBundleIdentifier(null); if (bundleIdentifier != null) { candidates.add(bundleIdentifier); } } - return Set.of(candidates.map(_organizationNameFromPackageName).whereType()); + return Set.of(candidates + .map(_organizationNameFromPackageName) + .whereType()); } String? _organizationNameFromPackageName(String packageName) { @@ -204,6 +215,9 @@ class FlutterProject { /// The Fuchsia sub project of this project. late final FuchsiaProject fuchsia = FuchsiaProject._(this); + /// The Ohos sub project of this project. + late final OhosProject ohos = OhosProject._(this); + /// The `pubspec.yaml` file of this project. File get pubspecFile => directory.childFile('pubspec.yaml'); @@ -214,7 +228,8 @@ class FlutterProject { /// /// This is the replacement for .packages which contains language /// version information. - File get packageConfigFile => directory.childDirectory('.dart_tool').childFile('package_config.json'); + File get packageConfigFile => + directory.childDirectory('.dart_tool').childFile('package_config.json'); /// The `.metadata` file of this project. File get metadataFile => directory.childFile('.metadata'); @@ -224,30 +239,30 @@ class FlutterProject { /// The `.flutter-plugins-dependencies` file of this project, /// which contains the dependencies each plugin depends on. - File get flutterPluginsDependenciesFile => directory.childFile('.flutter-plugins-dependencies'); + File get flutterPluginsDependenciesFile => + directory.childFile('.flutter-plugins-dependencies'); /// The `.dart-tool` directory of this project. Directory get dartTool => directory.childDirectory('.dart_tool'); /// The directory containing the generated code for this project. - Directory get generated => directory - .absolute - .childDirectory('.dart_tool') - .childDirectory('build') - .childDirectory('generated') - .childDirectory(manifest.appName); + Directory get generated => directory.absolute + .childDirectory('.dart_tool') + .childDirectory('build') + .childDirectory('generated') + .childDirectory(manifest.appName); /// The generated Dart plugin registrant for non-web platforms. File get dartPluginRegistrant => dartTool - .childDirectory('flutter_build') - .childFile('dart_plugin_registrant.dart'); + .childDirectory('flutter_build') + .childFile('dart_plugin_registrant.dart'); /// The example sub-project of this project. FlutterProject get example => FlutterProject( - _exampleDirectory(directory), - _exampleManifest, - FlutterManifest.empty(logger: globals.logger), - ); + _exampleDirectory(directory), + _exampleManifest, + FlutterManifest.empty(logger: globals.logger), + ); /// True if this project is a Flutter module project. bool get isModule => manifest.isModule; @@ -263,7 +278,9 @@ class FlutterProject { /// Returns a list of platform names that are supported by the project. List getSupportedPlatforms({bool includeRoot = false}) { - final List platforms = includeRoot ? [SupportedPlatform.root] : []; + final List platforms = includeRoot + ? [SupportedPlatform.root] + : []; if (android.existsSync()) { platforms.add(SupportedPlatform.android); } @@ -285,18 +302,23 @@ class FlutterProject { if (fuchsia.existsSync()) { platforms.add(SupportedPlatform.fuchsia); } + if (ohos.existsSync()) { + platforms.add(SupportedPlatform.ohos); + } return platforms; } /// The directory that will contain the example if an example exists. - static Directory _exampleDirectory(Directory directory) => directory.childDirectory('example'); + static Directory _exampleDirectory(Directory directory) => + directory.childDirectory('example'); /// Reads and validates the `pubspec.yaml` file at [path], asynchronously /// returning a [FlutterManifest] representation of the contents. /// /// Completes with an empty [FlutterManifest], if the file does not exist. /// Completes with a ToolExit on validation error. - static FlutterManifest _readManifest(String path, { + static FlutterManifest _readManifest( + String path, { required Logger logger, required FileSystem fileSystem, }) { @@ -311,10 +333,12 @@ class FlutterProject { logger.printStatus('Error detected in pubspec.yaml:', emphasis: true); logger.printError('$e'); } on FormatException catch (e) { - logger.printError('Error detected while parsing pubspec.yaml:', emphasis: true); + logger.printError('Error detected while parsing pubspec.yaml:', + emphasis: true); logger.printError('$e'); } on FileSystemException catch (e) { - logger.printError('Error detected while reading pubspec.yaml:', emphasis: true); + logger.printError('Error detected while reading pubspec.yaml:', + emphasis: true); logger.printError('$e'); } if (manifest == null) { @@ -344,6 +368,7 @@ class FlutterProject { macOSPlatform: featureFlags.isMacOSEnabled && macos.existsSync(), windowsPlatform: featureFlags.isWindowsEnabled && windows.existsSync(), webPlatform: featureFlags.isWebEnabled && web.existsSync(), + ohosPlatform: featureFlags.isOhosEnabled && ohos.existsSync(), deprecationBehavior: deprecationBehavior, allowedPlugins: allowedPlugins, ); @@ -358,15 +383,18 @@ class FlutterProject { bool macOSPlatform = false, bool windowsPlatform = false, bool webPlatform = false, + bool ohosPlatform = false, DeprecationBehavior deprecationBehavior = DeprecationBehavior.none, Iterable? allowedPlugins, }) async { if (!directory.existsSync() || isPlugin) { return; } - await refreshPluginsList(this, iosPlatform: iosPlatform, macOSPlatform: macOSPlatform); + await refreshPluginsList(this, + iosPlatform: iosPlatform, macOSPlatform: macOSPlatform); if (androidPlatform) { - await android.ensureReadyForPlatformSpecificTooling(deprecationBehavior: deprecationBehavior); + await android.ensureReadyForPlatformSpecificTooling( + deprecationBehavior: deprecationBehavior); } if (iosPlatform) { await ios.ensureReadyForPlatformSpecificTooling(); @@ -383,6 +411,9 @@ class FlutterProject { if (webPlatform) { await web.ensureReadyForPlatformSpecificTooling(); } + if (ohosPlatform) { + await ohos.ensureReadyForPlatformSpecificTooling(); + } await injectPlugins( this, androidPlatform: androidPlatform, @@ -391,25 +422,25 @@ class FlutterProject { macOSPlatform: macOSPlatform, windowsPlatform: windowsPlatform, allowedPlugins: allowedPlugins, + ohosPlatfrom: ohosPlatform, ); } - void checkForDeprecation({DeprecationBehavior deprecationBehavior = DeprecationBehavior.none}) { + void checkForDeprecation( + {DeprecationBehavior deprecationBehavior = DeprecationBehavior.none}) { if (android.existsSync() && pubspecFile.existsSync()) { android.checkForDeprecation(deprecationBehavior: deprecationBehavior); } } /// Returns a json encoded string containing the [appName], [version], and [buildNumber] that is used to generate version.json - String getVersionInfo() { + String getVersionInfo() { final String? buildName = manifest.buildName; final String? buildNumber = manifest.buildNumber; final Map versionFileJson = { 'app_name': manifest.appName, - if (buildName != null) - 'version': buildName, - if (buildNumber != null) - 'build_number': buildNumber, + if (buildName != null) 'version': buildName, + if (buildNumber != null) 'build_number': buildNumber, 'package_name': manifest.appName, }; return jsonEncode(versionFileJson); @@ -418,7 +449,6 @@ class FlutterProject { /// Base class for projects per platform. abstract class FlutterProjectPlatform { - /// Plugin's platform config key, e.g., "macos", "ios". String get pluginConfigKey; @@ -476,10 +506,14 @@ class AndroidProject extends FlutterProjectPlatform { /// The Gradle root directory of the Android wrapping of Flutter and plugins. /// This is the same as [hostAppGradleRoot] except when the project is /// a Flutter module with an editable host app. - Directory get _flutterLibGradleRoot => isModule ? ephemeralDirectory : _editableHostAppDirectory; + Directory get _flutterLibGradleRoot => + isModule ? ephemeralDirectory : _editableHostAppDirectory; - Directory get ephemeralDirectory => parent.directory.childDirectory('.android'); - Directory get _editableHostAppDirectory => parent.directory.childDirectory('android'); + Directory get ephemeralDirectory => + parent.directory.childDirectory('.android'); + + Directory get _editableHostAppDirectory => + parent.directory.childDirectory('android'); /// True if the parent Flutter project is a module. bool get isModule => parent.isModule; @@ -515,8 +549,8 @@ class AndroidProject extends FlutterProjectPlatform { bool _computeSupportedVersion() { final FileSystem fileSystem = hostAppGradleRoot.fileSystem; - final File plugin = hostAppGradleRoot.childFile( - fileSystem.path.join('buildSrc', 'src', 'main', 'groovy', 'FlutterPlugin.groovy')); + final File plugin = hostAppGradleRoot.childFile(fileSystem.path + .join('buildSrc', 'src', 'main', 'groovy', 'FlutterPlugin.groovy')); if (plugin.existsSync()) { return false; } @@ -615,10 +649,12 @@ class AndroidProject extends FlutterProjectPlatform { return hostAppGradleRoot.childFile('AndroidManifest.xml'); } - File get gradleAppOutV1File => gradleAppOutV1Directory.childFile('app-debug.apk'); + File get gradleAppOutV1File => + gradleAppOutV1Directory.childFile('app-debug.apk'); Directory get gradleAppOutV1Directory { - return globals.fs.directory(globals.fs.path.join(hostAppGradleRoot.path, 'app', 'build', 'outputs', 'apk')); + return globals.fs.directory(globals.fs.path + .join(hostAppGradleRoot.path, 'app', 'build', 'outputs', 'apk')); } /// Whether the current flutter project has an Android sub-project. @@ -725,13 +761,19 @@ $javaGradleCompatUrl return parent.buildDirectory; } - Future ensureReadyForPlatformSpecificTooling({DeprecationBehavior deprecationBehavior = DeprecationBehavior.none}) async { + Future ensureReadyForPlatformSpecificTooling( + {DeprecationBehavior deprecationBehavior = + DeprecationBehavior.none}) async { if (isModule && _shouldRegenerateFromTemplate()) { await _regenerateLibrary(); // Add ephemeral host app, if an editable host app does not already exist. if (!_editableHostAppDirectory.existsSync()) { - await _overwriteFromTemplate(globals.fs.path.join('module', 'android', 'host_app_common'), ephemeralDirectory); - await _overwriteFromTemplate(globals.fs.path.join('module', 'android', 'host_app_ephemeral'), ephemeralDirectory); + await _overwriteFromTemplate( + globals.fs.path.join('module', 'android', 'host_app_common'), + ephemeralDirectory); + await _overwriteFromTemplate( + globals.fs.path.join('module', 'android', 'host_app_ephemeral'), + ephemeralDirectory); } } if (!hostAppGradleRoot.existsSync()) { @@ -742,14 +784,17 @@ $javaGradleCompatUrl bool _shouldRegenerateFromTemplate() { return globals.fsUtils.isOlderThanReference( - entity: ephemeralDirectory, - referenceFile: parent.pubspecFile, - ) || globals.cache.isOlderThanToolsStamp(ephemeralDirectory); + entity: ephemeralDirectory, + referenceFile: parent.pubspecFile, + ) || + globals.cache.isOlderThanToolsStamp(ephemeralDirectory); } - File get localPropertiesFile => _flutterLibGradleRoot.childFile('local.properties'); + File get localPropertiesFile => + _flutterLibGradleRoot.childFile('local.properties'); - Directory get pluginRegistrantHost => _flutterLibGradleRoot.childDirectory(isModule ? 'Flutter' : 'app'); + Directory get pluginRegistrantHost => + _flutterLibGradleRoot.childDirectory(isModule ? 'Flutter' : 'app'); Future _regenerateLibrary() async { ErrorHandlingFileSystem.deleteIfExists(ephemeralDirectory, recursive: true); @@ -775,7 +820,8 @@ $javaGradleCompatUrl logger: globals.logger, templateRenderer: globals.templateRenderer, ); - final String androidIdentifier = parent.manifest.androidPackage ?? 'com.example.${parent.manifest.appName}'; + final String androidIdentifier = parent.manifest.androidPackage ?? + 'com.example.${parent.manifest.appName}'; template.render( target, { @@ -796,7 +842,8 @@ $javaGradleCompatUrl ); } - void checkForDeprecation({DeprecationBehavior deprecationBehavior = DeprecationBehavior.none}) { + void checkForDeprecation( + {DeprecationBehavior deprecationBehavior = DeprecationBehavior.none}) { if (deprecationBehavior == DeprecationBehavior.none) { return; } @@ -827,14 +874,16 @@ $javaGradleCompatUrl if (isModule) { // A module type's Android project is used in add-to-app scenarios and // only supports the V2 embedding. - return AndroidEmbeddingVersionResult(AndroidEmbeddingVersion.v2, 'Is add-to-app module'); + return AndroidEmbeddingVersionResult( + AndroidEmbeddingVersion.v2, 'Is add-to-app module'); } if (isPlugin) { // Plugins do not use an appManifest, so we stop here. // // TODO(garyq): This method does not currently check for code references to // the v1 embedding, we should check for this once removal is further along. - return AndroidEmbeddingVersionResult(AndroidEmbeddingVersion.v2, 'Is plugin'); + return AndroidEmbeddingVersionResult( + AndroidEmbeddingVersion.v2, 'Is plugin'); } if (!appManifestFile.existsSync()) { return AndroidEmbeddingVersionResult(AndroidEmbeddingVersion.v1, 'No `${appManifestFile.absolute.path}` file'); @@ -844,30 +893,36 @@ $javaGradleCompatUrl document = XmlDocument.parse(appManifestFile.readAsStringSync()); } on XmlException { throwToolExit('Error parsing $appManifestFile ' - 'Please ensure that the android manifest is a valid XML document and try again.'); + 'Please ensure that the android manifest is a valid XML document and try again.'); } on FileSystemException { throwToolExit('Error reading $appManifestFile even though it exists. ' - 'Please ensure that you have read permission to this file and try again.'); + 'Please ensure that you have read permission to this file and try again.'); } - for (final XmlElement application in document.findAllElements('application')) { + for (final XmlElement application + in document.findAllElements('application')) { final String? applicationName = application.getAttribute('android:name'); if (applicationName == 'io.flutter.app.FlutterApplication') { - return AndroidEmbeddingVersionResult(AndroidEmbeddingVersion.v1, '${appManifestFile.absolute.path} uses `android:name="io.flutter.app.FlutterApplication"`'); + return AndroidEmbeddingVersionResult(AndroidEmbeddingVersion.v1, + '${appManifestFile.absolute.path} uses `android:name="io.flutter.app.FlutterApplication"`'); } } for (final XmlElement metaData in document.findAllElements('meta-data')) { final String? name = metaData.getAttribute('android:name'); if (name == 'flutterEmbedding') { - final String? embeddingVersionString = metaData.getAttribute('android:value'); + final String? embeddingVersionString = + metaData.getAttribute('android:value'); if (embeddingVersionString == '1') { - return AndroidEmbeddingVersionResult(AndroidEmbeddingVersion.v1, '${appManifestFile.absolute.path} `` in ${appManifestFile.absolute.path}'); + return AndroidEmbeddingVersionResult(AndroidEmbeddingVersion.v1, + 'No `` in ${appManifestFile.absolute.path}'); } } @@ -875,6 +930,7 @@ $javaGradleCompatUrl enum AndroidEmbeddingVersion { /// V1 APIs based on io.flutter.app.FlutterActivity. v1, + /// V2 APIs based on io.flutter.embedding.android.FlutterActivity. v2, } @@ -914,8 +970,8 @@ class WebProject extends FlutterProjectPlatform { /// Whether this flutter project has a web sub-project. @override bool existsSync() { - return parent.directory.childDirectory('web').existsSync() - && indexFile.existsSync(); + return parent.directory.childDirectory('web').existsSync() && + indexFile.existsSync(); } /// The 'lib' directory for the application. @@ -925,14 +981,12 @@ class WebProject extends FlutterProjectPlatform { Directory get directory => parent.directory.childDirectory('web'); /// The html file used to host the flutter web application. - File get indexFile => parent.directory - .childDirectory('web') - .childFile('index.html'); + File get indexFile => + parent.directory.childDirectory('web').childFile('index.html'); /// The .dart_tool/dartpad directory - Directory get dartpadToolDirectory => parent.directory - .childDirectory('.dart_tool') - .childDirectory('dartpad'); + Directory get dartpadToolDirectory => + parent.directory.childDirectory('.dart_tool').childDirectory('dartpad'); Future ensureReadyForPlatformSpecificTooling() async { /// Create .dart_tool/dartpad/web_plugin_registrant.dart. @@ -952,12 +1006,14 @@ class FuchsiaProject { final FlutterProject project; Directory? _editableHostAppDirectory; + Directory get editableHostAppDirectory => _editableHostAppDirectory ??= project.directory.childDirectory('fuchsia'); bool existsSync() => editableHostAppDirectory.existsSync(); Directory? _meta; + Directory get meta => _meta ??= editableHostAppDirectory.childDirectory('meta'); } @@ -979,3 +1035,305 @@ String? versionToParsableString(Version? version) { return '${version.major}.${version.minor}.${version.patch}'; } + +/// The Ohos sub project. +class OhosProject extends FlutterProjectPlatform { + OhosProject._(this.parent); + + OhosBuildData get _initOhosBuildData { + _ohosBuildDataIns = OhosBuildData.parseOhosBuildData(this, globals.logger); + return _ohosBuildDataIns!; + } + + static const String kBuildProfileName = 'build-profile.json5'; + static const String kFlutterModuleName = 'flutter_module'; + + final FlutterProject parent; + + OhosBuildData? _ohosBuildDataIns; + + OhosBuildData get ohosBuildData => _ohosBuildDataIns ?? _initOhosBuildData; + + /// True if the parent Flutter project is a module. + bool get isModule => parent.isModule; + + /// True if the parent Flutter project is a plugin. + bool get isPlugin => parent.isPlugin; + + /// The directory in the project that is managed by Flutter. As much as + /// possible, files that are edited by Flutter tooling after initial project + /// creation should live here. + Directory get managedDirectory => + flutterModuleDirectory.childDirectory('src/main/ets/plugins'); + + /// 是否先编译.ohos/module下har,再运行hap + bool get isRunWithModuleHar => + isModule && editableHostAppDirectory.existsSync(); + + /// Whether this flutter project has a ohos sub-project. + @override + bool existsSync() { + return parent.isModule || editableHostAppDirectory.existsSync(); + } + + @override + String get pluginConfigKey => OhosPlugin.kConfigKey; + + Directory get ohosRoot { + if (!isModule || editableHostAppDirectory.existsSync()) { + return editableHostAppDirectory; + } + return ephemeralDirectory; + } + + /// flutter运行时资源拷贝来源路径 + Directory get flutterRuntimeAssertOriginPath => + isModule ? ephemeralDirectory : editableHostAppDirectory; + + Directory get ephemeralDirectory => parent.directory.childDirectory('.ohos'); + + Directory get editableHostAppDirectory => + parent.directory.childDirectory('ohos'); + + /// flutter资源和运行环境,生成和打包的module + String get flutterModuleName => + isModule ? kFlutterModuleName : mainModuleName; + + /// 主module,entry存在的话,是entryModuleName,否则是其他module + String get mainModuleName => ohosBuildData.moduleInfo.mainModuleName; + + Directory get flutterModuleDirectory { + if (isModule) { + final File buildProfileFile = + ephemeralDirectory.childFile(kBuildProfileName); + final Map buildProfile = JSON5 + .parse(buildProfileFile.readAsStringSync()) as Map; + final List modules = buildProfile['modules'] as List; + Map? module = modules.firstWhere((item) { + final Map module = item as Map; + return module['name'] as String == kFlutterModuleName; + }, orElse: () => null) as Map?; + + if (module == null) { + module = { + 'name': 'flutter_module', + 'srcPath': './flutter_module', + }; + final List modules = buildProfile['modules'] as List; + modules.add(module); + final String buildProfileNew = + const JsonEncoder.withIndent(' ').convert(buildProfile); + buildProfileFile.writeAsStringSync(buildProfileNew, flush: true); + } + + final String srcPath = module['srcPath'] as String; + return globals.fs + .directory(globals.fs.path.join(ephemeralDirectory.path, + globals.platform.isWindows ? srcPath.replaceAll(r'./', r'') : srcPath)); + } + return editableHostAppDirectory.childDirectory(mainModuleName); + } + + Directory get mainModuleDirectory { + return globals.fs.directory(globals.fs.path + .join(ohosRoot.path, ohosBuildData.moduleInfo.mainModuleSrcPath)); + } + + List get moduleDirectorys { + final List list = ohosBuildData.moduleInfo.moduleList + .map((OhosModule e) => globals.fs.path.join(ohosRoot.path, e.srcPath)) + .map((String path) => globals.fs.directory(path)) + .toList(); + return list; + } + + List get ohModulesCacheDirectorys { + const String OH_MODULES_NAME = 'oh_modules'; + // 先删除build,再删除oh_modules + final List list = moduleDirectorys + .map((Directory e) => e.childDirectory('build')) + .toList(); + list.add(ohosRoot.childDirectory('build')); + list.addAll(moduleDirectorys + .map((Directory e) => e.childDirectory(OH_MODULES_NAME))); + list.add(ohosRoot.childDirectory(OH_MODULES_NAME)); + return list; + } + + /// 删除ohModules文件夹缓存 + Future deleteOhModulesCache() async { + for (final Directory element in ohModulesCacheDirectorys) { + await deleteDirectory(element); + } + } + + Future deleteDirectory(Directory dir) async { + if (dir.existsSync()) { + if (globals.platform.isWindows) { + final Process process = + await Process.start('cmd', ['rmdir', '/s/q', dir.path]); + if (await process.exitCode != 0) { + throwToolExit('Unable to remove directory ${dir.path}', exitCode: 1); + } + } else { + dir.deleteSync(recursive: true); + } + } + } + + File getAppJsonFile() => + ohosRoot.childDirectory('AppScope').childFile('app.json5'); + + File getBuildProfileFile() => ohosRoot.childFile(kBuildProfileName); + + // entry/src/main/module.json5 配置,主要获取启动ability名 + File getModuleJsonFile() => mainModuleDirectory + .childDirectory('src') + .childDirectory('main') + .childFile('module.json5'); + + // macos: entry/build/{flavor}/outputs/{flavor}/entry-{flavor}-signed.hap + // windows: entry/build/default/outputs/{flavor}/entry-{flavor}-signed.hap + File getSignedHapFile(String flavor) { + return OhosProject.getSignedFile( + modulePath: mainModuleDirectory.path, + moduleName: mainModuleName, + flavor: flavor, + ); + } + + static File getSignedFile({ + required String modulePath, + String moduleName = 'entry', + String flavor = 'default', + OhosFileType type = OhosFileType.hap, + bool throwOnMissing = false, + }) { + final Directory moduleDir = globals.fs.directory(modulePath); + final List findFiles = [ + moduleDir + .childDirectory('build') + .childDirectory(flavor) + .childDirectory('outputs') + .childDirectory(flavor) + .childFile('$moduleName-$flavor-signed.${type.name}'), + moduleDir + .childDirectory('build') + .childDirectory('default') + .childDirectory('outputs') + .childDirectory(flavor) + .childFile('$moduleName-$flavor-signed.${type.name}'), + ]; + if (type == OhosFileType.app) { + findFiles.add(moduleDir + .childDirectory('build') + .childDirectory(flavor) + .childDirectory('outputs') + .childDirectory(flavor) + .childDirectory('app') + .childFile('$moduleName-$flavor.hap')); + } + for (final File file in findFiles) { + if (file.existsSync()) { + return file; + } + } + + if (throwOnMissing) { + throwToolExit('Hvigor build failed to produce an ${type.name} file. ' + "It's likely that this file was generated under $modulePath, " + "but the tool couldn't find it."); + } + return findFiles[0]; + } + + File get flutterModulePackageFile => + flutterModuleDirectory.childFile('oh-package.json5'); + + File get localPropertiesFile => ohosRoot.childFile('local.properties'); + + File get ephemeralLocalPropertiesFile => + ephemeralDirectory.childFile('local.properties'); + + SettingsFile get settings => isModule + ? (ephemeralLocalPropertiesFile.existsSync() + ? SettingsFile.parseFromFile(ephemeralLocalPropertiesFile) + : SettingsFile()) + : (localPropertiesFile.existsSync() + ? SettingsFile.parseFromFile(localPropertiesFile) + : SettingsFile()); + + bool hasSignedHapBuild(flavor) { + return getSignedHapFile(flavor).existsSync(); + } + + Future ensureReadyForPlatformSpecificTooling( + {DeprecationBehavior deprecationBehavior = + DeprecationBehavior.none}) async { + if (isModule && _shouldRegenerateFromTemplate()) { + await _regenerateLibrary(); + // Add ephemeral host app, if an editable host app does not already exist. + if (!editableHostAppDirectory.existsSync()) { + await _overwriteFromTemplate( + globals.fs.path.join('module', 'ohos', 'host_app_common'), + ephemeralDirectory); + } + } + hvigor.updateLocalProperties(project: parent); + } + + Future _regenerateLibrary() async { + ErrorHandlingFileSystem.deleteIfExists(ephemeralDirectory, recursive: true); + await _overwriteFromTemplate( + globals.fs.path.join('module', 'ohos', 'module_library'), + ephemeralDirectory); + await _overwriteFromTemplate( + globals.fs.path.join('module', 'ohos', 'hvigor_plugin'), + ephemeralDirectory); + await _overwriteFromTemplate( + globals.fs.path.join('module', 'ohos', 'host_config'), + ephemeralDirectory); + } + + bool _shouldRegenerateFromTemplate() { + // Do not re-generate .ohos when it already exists and is a symbolic link. + if (ephemeralDirectory.existsSync() && + io.FileSystemEntity.isLinkSync(ephemeralDirectory.path)) { + return false; + } + + return globals.fsUtils.isOlderThanReference( + entity: ephemeralDirectory, + referenceFile: parent.pubspecFile, + ) || + globals.cache.isOlderThanToolsStamp(ephemeralDirectory); + } + + Future _overwriteFromTemplate(String path, Directory target) async { + final Template template = await Template.fromName( + path, + fileSystem: globals.fs, + templateManifest: null, + logger: globals.logger, + templateRenderer: globals.templateRenderer, + ); + final String ohosIdentifier = + parent.manifest.ohosPackage ?? 'com.example.${parent.manifest.appName}'; + template.render( + target, + { + 'ohosIdentifier': ohosIdentifier, + 'projectName': parent.manifest.appName, + 'ohosSdk': ohosIdentifier, + }, + printStatusWhenWriting: false, + ); + } +} + +enum OhosFileType { + app, + hap, + har, + hsp, +} diff --git a/packages/flutter_tools/lib/src/project_validator.dart b/packages/flutter_tools/lib/src/project_validator.dart index 5749d009c17ea9c09159acfd80094695c8d17823..10cd5e1171784027cec5f18bbbab8f9fd281f470 100644 --- a/packages/flutter_tools/lib/src/project_validator.dart +++ b/packages/flutter_tools/lib/src/project_validator.dart @@ -177,6 +177,11 @@ class VariableDumpMachineProjectValidator extends MachineProjectValidator { value: _toJsonValue(platform.isFuchsia), status: StatusProjectValidator.info, )); + result.add(ProjectValidatorResult( + name: 'Platform.isOhos', + value: _toJsonValue(platform.isOhos), + status: StatusProjectValidator.info, + )); result.add(ProjectValidatorResult( name: 'Platform.pathSeparator', value: _toJsonValue(platform.pathSeparator), diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart index 87a2e513ecdc331b09805eb1f11531ca1fc370bc..ea3813bc64d16c840b1db46f9def395706a5f68c 100644 --- a/packages/flutter_tools/lib/src/resident_runner.dart +++ b/packages/flutter_tools/lib/src/resident_runner.dart @@ -35,6 +35,7 @@ import 'device.dart'; import 'globals.dart' as globals; import 'ios/application_package.dart'; import 'ios/devices.dart'; +import 'ohos/hdc_server.dart'; import 'project.dart'; import 'resident_devtools_handler.dart'; import 'run_cold.dart'; @@ -265,6 +266,13 @@ class FlutterDevice { bool isWaitingForVm = false; subscription = vmServiceUris!.listen((Uri? vmServiceUri) async { + // when on hdc server mode,the host is not local,change to hdc server + final String? hdcServerHost = getHdcServerHost(); + if(hdcServerHost!=null){ + const String localHost = '127.0.0.1'; + vmServiceUri = Uri.parse(vmServiceUri?.toString()?.replaceAll(localHost, hdcServerHost)??localHost); + } + // FYI, this message is used as a sentinel in tests. globals.printTrace('Connecting to service protocol: $vmServiceUri'); isWaitingForVm = true; @@ -1641,6 +1649,10 @@ Future getMissingPackageHintForPlatform(TargetPlatform platform) async case TargetPlatform.web_javascript: case TargetPlatform.windows_x64: case TargetPlatform.windows_arm64: + case TargetPlatform.ohos: + case TargetPlatform.ohos_arm: + case TargetPlatform.ohos_arm64: + case TargetPlatform.ohos_x64: return null; } } diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart index 87f2f0e40de008f1de17cc98faa5441c464f22ab..ed5feb12e3c1e4212605b0686f434725c137887f 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart @@ -1940,6 +1940,11 @@ DevelopmentArtifact? artifactFromTargetPlatform(TargetPlatform targetPlatform) { return DevelopmentArtifact.web; case TargetPlatform.ios: return DevelopmentArtifact.iOS; + case TargetPlatform.ohos: + case TargetPlatform.ohos_arm: + case TargetPlatform.ohos_arm64: + case TargetPlatform.ohos_x64: + return DevelopmentArtifact.ohosGenSnapshot; case TargetPlatform.darwin: if (featureFlags.isMacOSEnabled) { return DevelopmentArtifact.macOS; diff --git a/packages/flutter_tools/lib/src/sksl_writer.dart b/packages/flutter_tools/lib/src/sksl_writer.dart index 706cfe19763c02aba2d89bd1bebb8b5d79eccd28..fdd8716d088cfddb0e31c65f40be32ebe13e9154 100644 --- a/packages/flutter_tools/lib/src/sksl_writer.dart +++ b/packages/flutter_tools/lib/src/sksl_writer.dart @@ -55,6 +55,10 @@ Future sharedSkSlWriter(Device device, Map? data, { case TargetPlatform.web_javascript: case TargetPlatform.windows_x64: case TargetPlatform.windows_arm64: + case TargetPlatform.ohos: + case TargetPlatform.ohos_arm: + case TargetPlatform.ohos_arm64: + case TargetPlatform.ohos_x64: break; } final Map manifest = { diff --git a/packages/flutter_tools/lib/src/template.dart b/packages/flutter_tools/lib/src/template.dart index a6bc9291ca42c5045618ce2568fe31f0400248df..d963590b458c6f41c8c4bd4ca5861e4bf21a7a6f 100644 --- a/packages/flutter_tools/lib/src/template.dart +++ b/packages/flutter_tools/lib/src/template.dart @@ -248,6 +248,12 @@ class Template { return null; } + //Only build a ohos project if explicitly asked. + final bool ohos = (context['ohos'] as bool?) ?? false; + if (relativeDestinationPath.startsWith('ohos.tmpl') && !ohos) { + return null; + } + final String? projectName = context['projectName'] as String?; final String? androidIdentifier = context['androidIdentifier'] as String?; final String? pluginClass = context['pluginClass'] as String?; diff --git a/packages/flutter_tools/lib/src/test/flutter_platform.dart b/packages/flutter_tools/lib/src/test/flutter_platform.dart index 26b48832755d718fa00cd0a6e8b59f69cca34611..403691f040902f5e7ffd4c50d4047d000e6116d5 100644 --- a/packages/flutter_tools/lib/src/test/flutter_platform.dart +++ b/packages/flutter_tools/lib/src/test/flutter_platform.dart @@ -515,7 +515,6 @@ class FlutterPlatform extends PlatformPlugin { initializeExpressionCompiler(mainDart); } } - globals.printTrace('test $ourTestCount: starting test device'); final TestDevice testDevice = _createTestDevice(ourTestCount); final Stopwatch? testTimeRecorderStopwatch = testTimeRecorder?.start(TestTimePhases.Run); diff --git a/packages/flutter_tools/lib/src/test/integration_test_device.dart b/packages/flutter_tools/lib/src/test/integration_test_device.dart index 598bcc7423c805628494882fe638604ff8ab0570..0f463d67ac6b4d2c04d9ec5f21ff073f47d8dc6f 100644 --- a/packages/flutter_tools/lib/src/test/integration_test_device.dart +++ b/packages/flutter_tools/lib/src/test/integration_test_device.dart @@ -86,7 +86,6 @@ class IntegrationTestTestDevice implements TestDevice { final vm_service.IsolateRef isolateRef = await vmService.findExtensionIsolate( kIntegrationTestMethod, ); - await vmService.service.streamListen(vm_service.EventStreams.kExtension); final Stream remoteMessages = vmService.service.onExtensionEvent .where((vm_service.Event e) => e.extensionKind == kIntegrationTestExtension) diff --git a/packages/flutter_tools/lib/src/version.dart b/packages/flutter_tools/lib/src/version.dart index 9801183052729b758ff53db3bf67d2a799af35f4..80c6b90df93b792a017b75795cc08e550347a434 100644 --- a/packages/flutter_tools/lib/src/version.dart +++ b/packages/flutter_tools/lib/src/version.dart @@ -31,6 +31,7 @@ const String kUserBranch = '[user-branch]'; /// users to the "beta" channel. const Map kObsoleteBranches = { 'dev': 'beta', + 'oh-3.22.0': 'beta', }; /// The names of each channel/branch in order of increasing stability. @@ -986,6 +987,13 @@ class GitTagVersion { } } + final RegExp ohosTagPattern = RegExp(r'^\d+\.\d+\.\d+-ohos$'); + for (final String tag in tags) { + if (ohosTagPattern.hasMatch(tag.trim())) { + return parse(tag); + } + } + // If we're not currently on a tag, use git describe to find the most // recent tag and number of commits past. return parse( @@ -1040,6 +1048,34 @@ class GitTagVersion { ); } + static GitTagVersion parseOhosVersion(String version) { + final RegExp versionPattern = RegExp( + r'^(\d+)\.(\d+)\.(\d+)(-ohos(-\d+\.\d+\.\d+)?)?(?:-(\d+)-g([a-f0-9]+))?$'); + final Match? match = versionPattern.firstMatch(version.trim()); + if (match == null) { + return const GitTagVersion.unknown(); + } + + final List matchGroups = match.groups([1, 2, 3, 4, 5, 6, 7]); + final int? x = matchGroups[0] == null ? null : int.tryParse(matchGroups[0]!); + final int? y = matchGroups[1] == null ? null : int.tryParse(matchGroups[1]!); + final int? z = matchGroups[2] == null ? null : int.tryParse(matchGroups[2]!); + final String? devString = matchGroups[3]; + + // count of commits past last tagged version + final int? commits = matchGroups[5] == null ? 0 : int.tryParse(matchGroups[5]!); + final String hash = matchGroups[6] ?? ''; + + return GitTagVersion( + x: x, + y: y, + z: z, + commits: commits, + hash: hash, + gitTag: '$x.$y.$z${devString ?? ''}', // e.g. 3.7.12-ohos-1.0.0 + ); + } + static GitTagVersion parse(String version) { GitTagVersion gitTagVersion; @@ -1047,6 +1083,12 @@ class GitTagVersion { if (gitTagVersion != const GitTagVersion.unknown()) { return gitTagVersion; } + + gitTagVersion = parseOhosVersion(version); + if (gitTagVersion != const GitTagVersion.unknown()) { + return gitTagVersion; + } + globals.printTrace('Could not interpret results of "git describe": $version'); return const GitTagVersion.unknown(); } @@ -1058,6 +1100,9 @@ class GitTagVersion { if (commits == 0 && gitTag != null) { return gitTag!; } + if (gitTag != null && gitTag!.contains('ohos')) { + return gitTag!; + } if (hotfix != null) { // This is an unexpected state where untagged commits exist past a hotfix return '$x.$y.$z+hotfix.${hotfix! + 1}.pre.$commits'; diff --git a/packages/flutter_tools/lib/src/vmservice.dart b/packages/flutter_tools/lib/src/vmservice.dart index 67587c55161a9db6c48da3a10e71c0ca98531e88..c253d0d2d3eb1f866420878098d1abe6d549022d 100644 --- a/packages/flutter_tools/lib/src/vmservice.dart +++ b/packages/flutter_tools/lib/src/vmservice.dart @@ -17,6 +17,7 @@ import 'convert.dart'; import 'device.dart'; import 'globals.dart' as globals; import 'project.dart'; +import 'ohos/hdc_server.dart'; import 'version.dart'; const String kResultType = 'type'; @@ -166,6 +167,12 @@ Future _defaultOpenChannel(String url, { while (socket == null) { attempts += 1; try { + final String? hdcServerHost = getHdcServerHost(); + // when on hdc server mode,the host is not local,change to hdc server + if(hdcServerHost!=null){ + url = url.replaceAll('127.0.0.1', hdcServerHost); + logger.printStatus('io.WebSocket.connect change url to $url'); + } socket = await constructor(url, compression: compression, logger: logger); } on io.WebSocketException catch (e) { await handleError(e); diff --git a/packages/flutter_tools/lib/src/web/file_generators/flutter_js.dart b/packages/flutter_tools/lib/src/web/file_generators/flutter_js.dart new file mode 100644 index 0000000000000000000000000000000000000000..81a611dd301c9c511832bbc2492403dd1e0d479f --- /dev/null +++ b/packages/flutter_tools/lib/src/web/file_generators/flutter_js.dart @@ -0,0 +1,389 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + + +/// Generates the flutter.js file. +/// +/// flutter.js should be completely static, so **do not use any parameter or +/// environment variable to generate this file**. +String generateFlutterJsFile() { + return r''' +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +if (!_flutter) { + var _flutter = {}; +} +_flutter.loader = null; + +(function () { + "use strict"; + + const baseUri = ensureTrailingSlash(getBaseURI()); + + function getBaseURI() { + const base = document.querySelector("base"); + return (base && base.getAttribute("href")) || ""; + } + + function ensureTrailingSlash(uri) { + if (uri == "") { + return uri; + } + return uri.endsWith("/") ? uri : `${uri}/`; + } + + /** + * Wraps `promise` in a timeout of the given `duration` in ms. + * + * Resolves/rejects with whatever the original `promises` does, or rejects + * if `promise` takes longer to complete than `duration`. In that case, + * `debugName` is used to compose a legible error message. + * + * If `duration` is < 0, the original `promise` is returned unchanged. + * @param {Promise} promise + * @param {number} duration + * @param {string} debugName + * @returns {Promise} a wrapped promise. + */ + async function timeout(promise, duration, debugName) { + if (duration < 0) { + return promise; + } + let timeoutId; + const _clock = new Promise((_, reject) => { + timeoutId = setTimeout(() => { + reject( + new Error( + `${debugName} took more than ${duration}ms to resolve. Moving on.`, + { + cause: timeout, + } + ) + ); + }, duration); + }); + + return Promise.race([promise, _clock]).finally(() => { + clearTimeout(timeoutId); + }); + } + + /** + * Handles the creation of a TrustedTypes `policy` that validates URLs based + * on an (optional) incoming array of RegExes. + */ + class FlutterTrustedTypesPolicy { + /** + * Constructs the policy. + * @param {[RegExp]} validPatterns the patterns to test URLs + * @param {String} policyName the policy name (optional) + */ + constructor(validPatterns, policyName = "flutter-js") { + const patterns = validPatterns || [ + /\.dart\.js$/, + /^flutter_service_worker.js$/ + ]; + if (window.trustedTypes) { + this.policy = trustedTypes.createPolicy(policyName, { + createScriptURL: function(url) { + const parsed = new URL(url, window.location); + const file = parsed.pathname.split("/").pop(); + const matches = patterns.some((pattern) => pattern.test(file)); + if (matches) { + return parsed.toString(); + } + console.error( + "URL rejected by TrustedTypes policy", + policyName, ":", url, "(download prevented)"); + } + }); + } + } + } + + /** + * Handles loading/reloading Flutter's service worker, if configured. + * + * @see: https://developers.google.com/web/fundamentals/primers/service-workers + */ + class FlutterServiceWorkerLoader { + /** + * Injects a TrustedTypesPolicy (or undefined if the feature is not supported). + * @param {TrustedTypesPolicy | undefined} policy + */ + setTrustedTypesPolicy(policy) { + this._ttPolicy = policy; + } + + /** + * Returns a Promise that resolves when the latest Flutter service worker, + * configured by `settings` has been loaded and activated. + * + * Otherwise, the promise is rejected with an error message. + * @param {*} settings Service worker settings + * @returns {Promise} that resolves when the latest serviceWorker is ready. + */ + loadServiceWorker(settings) { + if (!("serviceWorker" in navigator) || settings == null) { + // In the future, settings = null -> uninstall service worker? + return Promise.reject( + new Error("Service worker not supported (or configured).") + ); + } + const { + serviceWorkerVersion, + serviceWorkerUrl = `${baseUri}flutter_service_worker.js?v=${serviceWorkerVersion}`, + timeoutMillis = 4000, + } = settings; + + // Apply the TrustedTypes policy, if present. + let url = serviceWorkerUrl; + if (this._ttPolicy != null) { + url = this._ttPolicy.createScriptURL(url); + } + + const serviceWorkerActivation = navigator.serviceWorker + .register(url) + .then(this._getNewServiceWorker) + .then(this._waitForServiceWorkerActivation); + + // Timeout race promise + return timeout( + serviceWorkerActivation, + timeoutMillis, + "prepareServiceWorker" + ); + } + + /** + * Returns the latest service worker for the given `serviceWorkerRegistrationPromise`. + * + * This might return the current service worker, if there's no new service worker + * awaiting to be installed/updated. + * + * @param {Promise} serviceWorkerRegistrationPromise + * @returns {Promise} + */ + async _getNewServiceWorker(serviceWorkerRegistrationPromise) { + const reg = await serviceWorkerRegistrationPromise; + + if (!reg.active && (reg.installing || reg.waiting)) { + // No active web worker and we have installed or are installing + // one for the first time. Simply wait for it to activate. + console.debug("Installing/Activating first service worker."); + return reg.installing || reg.waiting; + } else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) { + // When the app updates the serviceWorkerVersion changes, so we + // need to ask the service worker to update. + return reg.update().then((newReg) => { + console.debug("Updating service worker."); + return newReg.installing || newReg.waiting || newReg.active; + }); + } else { + console.debug("Loading from existing service worker."); + return reg.active; + } + } + + /** + * Returns a Promise that resolves when the `latestServiceWorker` changes its + * state to "activated". + * + * @param {Promise} latestServiceWorkerPromise + * @returns {Promise} + */ + async _waitForServiceWorkerActivation(latestServiceWorkerPromise) { + const serviceWorker = await latestServiceWorkerPromise; + + if (!serviceWorker || serviceWorker.state == "activated") { + if (!serviceWorker) { + return Promise.reject( + new Error("Cannot activate a null service worker!") + ); + } else { + console.debug("Service worker already active."); + return Promise.resolve(); + } + } + return new Promise((resolve, _) => { + serviceWorker.addEventListener("statechange", () => { + if (serviceWorker.state == "activated") { + console.debug("Activated new service worker."); + resolve(); + } + }); + }); + } + } + + /** + * Handles injecting the main Flutter web entrypoint (main.dart.js), and notifying + * the user when Flutter is ready, through `didCreateEngineInitializer`. + * + * @see https://docs.flutter.dev/development/platform-integration/web/initialization + */ + class FlutterEntrypointLoader { + /** + * Creates a FlutterEntrypointLoader. + */ + constructor() { + // Watchdog to prevent injecting the main entrypoint multiple times. + this._scriptLoaded = false; + } + + /** + * Injects a TrustedTypesPolicy (or undefined if the feature is not supported). + * @param {TrustedTypesPolicy | undefined} policy + */ + setTrustedTypesPolicy(policy) { + this._ttPolicy = policy; + } + + /** + * Loads flutter main entrypoint, specified by `entrypointUrl`, and calls a + * user-specified `onEntrypointLoaded` callback with an EngineInitializer + * object when it's done. + * + * @param {*} options + * @returns {Promise | undefined} that will eventually resolve with an + * EngineInitializer, or will be rejected with the error caused by the loader. + * Returns undefined when an `onEntrypointLoaded` callback is supplied in `options`. + */ + async loadEntrypoint(options) { + const { entrypointUrl = `${baseUri}main.dart.js`, onEntrypointLoaded } = + options || {}; + + return this._loadEntrypoint(entrypointUrl, onEntrypointLoaded); + } + + /** + * Resolves the promise created by loadEntrypoint, and calls the `onEntrypointLoaded` + * function supplied by the user (if needed). + * + * Called by Flutter through `_flutter.loader.didCreateEngineInitializer` method, + * which is bound to the correct instance of the FlutterEntrypointLoader by + * the FlutterLoader object. + * + * @param {Function} engineInitializer @see https://github.com/flutter/engine/blob/main/lib/web_ui/lib/src/engine/js_interop/js_loader.dart#L42 + */ + didCreateEngineInitializer(engineInitializer) { + if (typeof this._didCreateEngineInitializerResolve === "function") { + this._didCreateEngineInitializerResolve(engineInitializer); + // Remove the resolver after the first time, so Flutter Web can hot restart. + this._didCreateEngineInitializerResolve = null; + // Make the engine revert to "auto" initialization on hot restart. + delete _flutter.loader.didCreateEngineInitializer; + } + if (typeof this._onEntrypointLoaded === "function") { + this._onEntrypointLoaded(engineInitializer); + } + } + + /** + * Injects a script tag into the DOM, and configures this loader to be able to + * handle the "entrypoint loaded" notifications received from Flutter web. + * + * @param {string} entrypointUrl the URL of the script that will initialize + * Flutter. + * @param {Function} onEntrypointLoaded a callback that will be called when + * Flutter web notifies this object that the entrypoint is + * loaded. + * @returns {Promise | undefined} a Promise that resolves when the entrypoint + * is loaded, or undefined if `onEntrypointLoaded` + * is a function. + */ + _loadEntrypoint(entrypointUrl, onEntrypointLoaded) { + const useCallback = typeof onEntrypointLoaded === "function"; + + if (!this._scriptLoaded) { + this._scriptLoaded = true; + const scriptTag = this._createScriptTag(entrypointUrl); + if (useCallback) { + // Just inject the script tag, and return nothing; Flutter will call + // `didCreateEngineInitializer` when it's done. + console.debug("Injecting