├── .github └── workflows │ ├── publish.yml │ └── rust.yml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── ndk-sys ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── generate_bindings.sh ├── src │ ├── ffi_aarch64.rs │ ├── ffi_arm.rs │ ├── ffi_i686.rs │ ├── ffi_x86_64.rs │ └── lib.rs └── wrapper.h ├── ndk ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml └── src │ ├── asset.rs │ ├── audio.rs │ ├── bitmap.rs │ ├── configuration.rs │ ├── data_space.rs │ ├── event.rs │ ├── font.rs │ ├── hardware_buffer.rs │ ├── hardware_buffer_format.rs │ ├── input_queue.rs │ ├── lib.rs │ ├── looper.rs │ ├── media │ ├── image_reader.rs │ ├── media_codec.rs │ ├── media_format.rs │ └── mod.rs │ ├── media_error.rs │ ├── native_activity.rs │ ├── native_window.rs │ ├── performance_hint.rs │ ├── shared_memory.rs │ ├── surface_texture.rs │ ├── sync.rs │ ├── trace.rs │ └── utils.rs └── rustfmt.toml /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | paths: "**/Cargo.toml" 7 | 8 | jobs: 9 | Publish: 10 | if: github.repository_owner == 'rust-mobile' 11 | runs-on: ubuntu-latest 12 | strategy: 13 | max-parallel: 1 # ensure crate order 14 | fail-fast: false 15 | matrix: 16 | crate: 17 | - { name: "ndk-sys", target: "armv7-linux-androideabi" } 18 | - { name: "ndk", target: "armv7-linux-androideabi" } 19 | steps: 20 | - uses: actions/checkout@v4 21 | - uses: dtolnay/rust-toolchain@stable 22 | with: 23 | target: ${{ matrix.crate.target }} 24 | - name: Publish ${{ matrix.crate.name }} 25 | continue-on-error: true 26 | run: cargo publish --manifest-path ${{ matrix.crate.name }}/Cargo.toml --target ${{ matrix.crate.target }} --token ${{ secrets.cratesio_token }} 27 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | formatting: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | 11 | - name: Format 12 | run: cargo fmt --all -- --check 13 | 14 | clippy: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - uses: dtolnay/rust-toolchain@stable 20 | with: 21 | target: aarch64-linux-android 22 | 23 | - name: Clippy 24 | # Use a cross-compilation target (that's typical for Android) to lint everything 25 | run: cargo clippy --all --all-targets --all-features --target aarch64-linux-android -- -Dwarnings 26 | 27 | check_ndk_sys_msrv: 28 | name: Check ndk-sys MSRV (1.60.0) 29 | runs-on: ubuntu-latest 30 | steps: 31 | - uses: actions/checkout@v4 32 | 33 | - uses: dtolnay/rust-toolchain@1.60.0 34 | with: 35 | target: aarch64-linux-android 36 | 37 | - name: cargo check 38 | run: cargo check -p ndk-sys --all-targets --all-features --target aarch64-linux-android 39 | 40 | check_msrv: 41 | strategy: 42 | matrix: 43 | minimal-versions: [true, false] 44 | name: Check overall MSRV (1.66.0) 45 | runs-on: ubuntu-latest 46 | steps: 47 | - uses: actions/checkout@v4 48 | 49 | - uses: dtolnay/rust-toolchain@nightly 50 | if: ${{ matrix.minimal-versions }} 51 | 52 | - name: Generate lockfile with minimal dependency versions 53 | run: cargo +nightly generate-lockfile -Zminimal-versions 54 | if: ${{ matrix.minimal-versions }} 55 | 56 | - uses: dtolnay/rust-toolchain@1.66.0 57 | with: 58 | target: aarch64-linux-android 59 | 60 | - name: cargo check 61 | run: > 62 | cargo check --workspace --all-targets --all-features --target aarch64-linux-android 63 | || (echo "::warning::MSRV test failed for minimal-versions: ${{ matrix.minimal-versions }}"; false) 64 | continue-on-error: ${{ !matrix.minimal-versions }} 65 | 66 | build: 67 | strategy: 68 | fail-fast: false 69 | matrix: 70 | os: [ubuntu-latest] 71 | rust-channel: [stable, nightly] 72 | rust-target: 73 | - armv7-linux-androideabi 74 | - aarch64-linux-android 75 | - i686-linux-android 76 | - x86_64-linux-android 77 | include: 78 | - os: windows-latest 79 | rust-channel: stable 80 | rust-target: aarch64-linux-android 81 | - os: windows-latest 82 | rust-channel: stable 83 | rust-target: x86_64-linux-android 84 | 85 | runs-on: ${{ matrix.os }} 86 | name: Cross-compile 87 | 88 | steps: 89 | - uses: actions/checkout@v4 90 | 91 | - name: Installing Rust ${{ matrix.rust-channel }} w/ ${{ matrix.rust-target }} 92 | uses: dtolnay/rust-toolchain@master 93 | with: 94 | target: ${{ matrix.rust-target }} 95 | toolchain: ${{ matrix.rust-channel }} 96 | 97 | - name: Compile for ${{ matrix.rust-target }} 98 | run: cargo build --target ${{ matrix.rust-target }} 99 | 100 | build-host: 101 | strategy: 102 | fail-fast: false 103 | matrix: 104 | os: [ubuntu-latest] 105 | rust-channel: [stable, nightly] 106 | 107 | runs-on: ${{ matrix.os }} 108 | name: Host-side tests 109 | 110 | steps: 111 | - uses: actions/checkout@v4 112 | 113 | - name: Installing Rust ${{ matrix.rust-channel }} 114 | uses: dtolnay/rust-toolchain@master 115 | with: 116 | toolchain: ${{ matrix.rust-channel }} 117 | 118 | - name: Test ndk-sys 119 | run: cargo test -p ndk-sys --all-features 120 | 121 | - name: Test ndk 122 | run: cargo test -p ndk --all-features 123 | 124 | docs: 125 | strategy: 126 | fail-fast: false 127 | matrix: 128 | os: [ubuntu-latest] 129 | rust-channel: [stable, nightly] 130 | 131 | runs-on: ${{ matrix.os }} 132 | name: Build-test docs 133 | 134 | steps: 135 | - uses: actions/checkout@v4 136 | 137 | - name: Installing Rust ${{ matrix.rust-channel }} 138 | uses: dtolnay/rust-toolchain@master 139 | with: 140 | toolchain: ${{ matrix.rust-channel }} 141 | 142 | - name: Document all crates 143 | env: 144 | RUSTDOCFLAGS: -Dwarnings 145 | run: cargo doc --all --all-features 146 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | "ndk", 5 | "ndk-sys", 6 | ] 7 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![ci](https://github.com/rust-mobile/ndk/actions/workflows/rust.yml/badge.svg)](https://github.com/rust-mobile/ndk/actions/workflows/rust.yml) ![MIT license](https://img.shields.io/badge/License-MIT-green.svg) ![APACHE2 license](https://img.shields.io/badge/License-APACHE2-green.svg) 2 | 3 | Rust bindings to the [Android NDK](https://developer.android.com/ndk) 4 | 5 | Name | Description | Badges 6 | --- | --- | --- 7 | [`ndk-sys`](./ndk-sys) | Raw FFI bindings to the NDK | [![crates.io](https://img.shields.io/crates/v/ndk-sys.svg)](https://crates.io/crates/ndk-sys) [![Docs](https://docs.rs/ndk-sys/badge.svg)](https://docs.rs/ndk-sys) [![MSRV](https://img.shields.io/badge/rustc-1.60.0+-ab6000.svg)](https://blog.rust-lang.org/2022/04/07/Rust-1.60.0.html) 8 | [`ndk`](./ndk) | Safe abstraction of the bindings | [![crates.io](https://img.shields.io/crates/v/ndk.svg)](https://crates.io/crates/ndk) [![Docs](https://docs.rs/ndk/badge.svg)](https://docs.rs/ndk) [![MSRV](https://img.shields.io/badge/rustc-1.64.0+-ab6000.svg)](https://blog.rust-lang.org/2022/09/22/Rust-1.64.0.html) 9 | 10 | See these [`ndk-examples`](https://github.com/rust-mobile/cargo-apk/tree/main/examples/examples) and these [`rust-android-examples`](https://github.com/rust-mobile/rust-android-examples) for examples using the NDK. 11 | 12 | > [!IMPORTANT] 13 | > This repository was recently [modularized](https://github.com/rust-mobile/ndk/issues/372) and the following crates were split into separate repositories: 14 | > 15 | > Crate | New Location | Notes 16 | > ------|--------------|------ 17 | > ndk-context | https://github.com/rust-mobile/ndk-context | 18 | > ndk-glue | https://github.com/rust-mobile/ndk-glue | ⛔ _deprecated_ - see [android-activity](https://github.com/rust-mobile/android-activity) 19 | > ndk-macro | https://github.com/rust-mobile/ndk-glue | ⛔ _deprecated_ - see [android-activity](https://github.com/rust-mobile/android-activity) 20 | > ndk-build | https://github.com/rust-mobile/cargo-apk | ⛔ _deprecated_ - see [xbuild](https://github.com/rust-mobile/xbuild) 21 | > cargo-apk | https://github.com/rust-mobile/cargo-apk | ⛔ _deprecated_ - see [xbuild](https://github.com/rust-mobile/xbuild) 22 | -------------------------------------------------------------------------------- /ndk-sys/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /ndk-sys/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Unreleased 2 | 3 | - Regenerate bindings with `bindgen 0.71.1`. (#487) 4 | 5 | # 0.6.0 (2024-04-26) 6 | 7 | - Generate against upstream NDK build `11769913`. (#471) 8 | - Add `nativewindow` feature to link against `libnativewindow`. (#465) 9 | 10 | # 0.5.0 (2023-10-15) 11 | 12 | - **Breaking:** Regenerate against NDK `25.2.9519653` with `rust-bindgen 0.66.0`. (#324, #370) 13 | - Add `font`, `font_matcher`, `system_fonts` bindings. (#397) 14 | - Add `sync` feature for linking against `libsync.so`. (#423) 15 | 16 | # 0.4.1 (2022-11-23) 17 | 18 | - Re-release of `0.4.0` to combat a faulty `0.4.0+25.0.8775105` publish. Now also includes `+23.1.7779620` version metadata. 19 | 20 | # 0.4.0 (2022-07-24) 21 | 22 | - **Breaking:** Turn `enum` type aliases into newtype wrappers (and regenerate with `rust-bindgen 0.59.2`). (#245, #315) 23 | 24 | # 0.3.0 (2022-01-05) 25 | 26 | - **Breaking:** Use `jni-sys` for low-level JNI types instead of those autogenerated by `bindgen` based on the header. 27 | These JNI types are _not_ publicly (re)exported anymore. (#209) 28 | - Regenerate against NDK 23.1.7779620 with `rust-bindgen 0.59.1`, now including all Android-related headers. (#201) 29 | 30 | # 0.2.2 (2021-11-22) 31 | 32 | - Regenerate against NDK 23.0.7599858. (#178) 33 | 34 | # 0.2.1 (2020-10-15) 35 | 36 | - Fix documentation build on docs.rs 37 | 38 | # 0.2.0 (2020-09-15) 39 | 40 | - **Breaking:** `onSaveInstanceState` signature corrected to take `outSize` as a `*mut size_t` instead of `*mut usize`. 41 | - Add `media` bindings 42 | - Add `bitmap` and `hardware_buffer` bindings 43 | - Add `aaudio` bindings 44 | 45 | # 0.1.0 (2020-04-22) 46 | 47 | - Initial release! 🎉 48 | -------------------------------------------------------------------------------- /ndk-sys/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ndk-sys" 3 | version = "0.6.0+11769913" 4 | authors = ["The Rust Windowing contributors"] 5 | edition = "2021" 6 | description = "FFI bindings for the Android NDK" 7 | license = "MIT OR Apache-2.0" 8 | keywords = ["android", "ndk"] 9 | readme = "../README.md" 10 | documentation = "https://docs.rs/ndk-sys" 11 | homepage = "https://github.com/rust-mobile/ndk" 12 | repository = "https://github.com/rust-mobile/ndk" 13 | rust-version = "1.60" 14 | categories = ["os", "os::android-apis", "external-ffi-bindings"] 15 | 16 | [dependencies] 17 | jni-sys = "0.3.0" 18 | 19 | [features] 20 | test = [] 21 | audio = [] 22 | bitmap = [] 23 | media = [] 24 | nativewindow = [] 25 | sync = [] 26 | 27 | [package.metadata.docs.rs] 28 | rustdoc-args = ["--cfg", "docsrs"] 29 | targets = [ 30 | "aarch64-linux-android", 31 | "armv7-linux-androideabi", 32 | "i686-linux-android", 33 | "x86_64-linux-android", 34 | ] 35 | -------------------------------------------------------------------------------- /ndk-sys/generate_bindings.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -ex 4 | 5 | os=$(uname -s) 6 | 7 | if [[ "$os" == "Darwin" ]]; then 8 | host_tag="darwin-x86_64" 9 | elif [[ "$os" == "CYGWIN"* ]]; then 10 | host_tag="windows-x86_64" 11 | else 12 | host_tag="linux-x86_64" 13 | fi 14 | 15 | # Builds can be found at https://ci.android.com for the `ndk` component 16 | build_id=$(grep -oP 'version = "\d\.\d.\d\+\K\d+' ./Cargo.toml) 17 | # From https://cs.android.com/android/platform/superproject/main/+/main:development/python-packages/fetchartifact/fetchartifact/__init__.py 18 | target="ndk" 19 | artifact_name="ndk_platform.tar.bz2" 20 | url="https://androidbuildinternal.googleapis.com/android/internal/build/v3/builds/$build_id/$target/attempts/latest/artifacts/$artifact_name/url" 21 | echo "Downloading sysroot $build_id from $url" 22 | curl -L $url -o "$artifact_name" 23 | tar xvf "$artifact_name" "ndk/sysroot/usr/include" 24 | sysroot="$PWD/ndk/sysroot/" 25 | [ ! -d "$sysroot" ] && echo "Android sysroot $sysroot does not exist!" && exit 1 26 | 27 | while read ARCH && read TARGET ; do 28 | bindgen wrapper.h -o src/ffi_$ARCH.rs \ 29 | --rust-target 1.60 \ 30 | --blocklist-item 'JNI\w+' \ 31 | --blocklist-item 'C?_?JNIEnv' \ 32 | --blocklist-item '_?JavaVM' \ 33 | --blocklist-item '_?j\w+' \ 34 | --newtype-enum '\w+_(result|status)_t' \ 35 | --newtype-enum 'ACameraDevice_request_template' \ 36 | --newtype-enum 'ADataSpace' \ 37 | --newtype-enum 'AHardwareBuffer_Format' \ 38 | --newtype-enum 'AHardwareBuffer_UsageFlags' \ 39 | --newtype-enum 'AHdrMetadataType' \ 40 | --newtype-enum 'AIMAGE_FORMATS' \ 41 | --newtype-enum 'AMediaDrmEventType' \ 42 | --newtype-enum 'AMediaDrmKeyRequestType' \ 43 | --newtype-enum 'AMediaDrmKeyType' \ 44 | --newtype-enum 'AMediaKeyStatusType' \ 45 | --newtype-enum 'AMidiDevice_Protocol' \ 46 | --newtype-enum 'AMotionClassification' \ 47 | --newtype-enum 'ANativeWindowTransform' \ 48 | --newtype-enum 'ANativeWindow_ChangeFrameRateStrategy' \ 49 | --newtype-enum 'ANativeWindow_FrameRateCompatibility' \ 50 | --newtype-enum 'ANativeWindow_LegacyFormat' \ 51 | --newtype-enum 'AndroidBitmapCompressFormat' \ 52 | --newtype-enum 'AndroidBitmapFormat' \ 53 | --newtype-enum 'AppendMode' \ 54 | --newtype-enum 'DeviceTypeCode' \ 55 | --newtype-enum 'DurationCode' \ 56 | --newtype-enum 'FeatureLevelCode' \ 57 | --newtype-enum 'FuseCode' \ 58 | --newtype-enum 'HeapTaggingLevel' \ 59 | --newtype-enum 'OperandCode' \ 60 | --newtype-enum 'OperationCode' \ 61 | --newtype-enum 'OutputFormat' \ 62 | --newtype-enum 'PaddingCode' \ 63 | --newtype-enum 'PreferenceCode' \ 64 | --newtype-enum 'PriorityCode' \ 65 | --newtype-enum 'ResNsendFlags' \ 66 | --newtype-enum 'ResultCode' \ 67 | --newtype-enum 'SeekMode' \ 68 | --newtype-enum 'acamera_\w+' \ 69 | --newtype-enum 'android_LogPriority' \ 70 | --newtype-enum 'android_fdsan_error_level' \ 71 | --newtype-enum 'android_fdsan_owner_type' \ 72 | --newtype-enum 'cryptoinfo_mode_t' \ 73 | --newtype-enum 'log_id' \ 74 | -- \ 75 | --sysroot="$sysroot" --target=$TARGET 76 | done << EOF 77 | arm 78 | arm-linux-androideabi 79 | aarch64 80 | aarch64-linux-android 81 | i686 82 | i686-linux-android 83 | x86_64 84 | x86_64-linux-android 85 | EOF 86 | -------------------------------------------------------------------------------- /ndk-sys/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Raw FFI bindings to the Android NDK. 2 | //! 3 | //! The bindings are pre-generated and the right one for the platform is selected at compile time. 4 | 5 | // Bindgen lints 6 | #![allow(non_upper_case_globals)] 7 | #![allow(non_camel_case_types)] 8 | #![allow(non_snake_case)] 9 | #![allow(improper_ctypes)] 10 | #![allow(clippy::all)] 11 | // Temporarily allow UB nullptr dereference in bindgen layout tests until fixed upstream: 12 | // https://github.com/rust-lang/rust-bindgen/pull/2055 13 | // https://github.com/rust-lang/rust-bindgen/pull/2064 14 | #![allow(deref_nullptr)] 15 | // Test setup lints 16 | #![cfg_attr(test, allow(dead_code))] 17 | #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] 18 | 19 | use jni_sys::*; 20 | 21 | #[cfg(not(any(target_os = "android", feature = "test")))] 22 | compile_error!("ndk-sys only supports compiling for Android"); 23 | 24 | #[cfg(all(any(target_os = "android", feature = "test"), target_arch = "arm"))] 25 | include!("ffi_arm.rs"); 26 | 27 | #[cfg(all(any(target_os = "android", feature = "test"), target_arch = "aarch64"))] 28 | include!("ffi_aarch64.rs"); 29 | 30 | #[cfg(all(any(target_os = "android", feature = "test"), target_arch = "x86"))] 31 | include!("ffi_i686.rs"); 32 | 33 | #[cfg(all(any(target_os = "android", feature = "test"), target_arch = "x86_64"))] 34 | include!("ffi_x86_64.rs"); 35 | 36 | #[cfg(target_os = "android")] 37 | #[link(name = "android")] 38 | extern "C" {} 39 | 40 | #[cfg(all(feature = "nativewindow", target_os = "android"))] 41 | #[link(name = "nativewindow")] 42 | extern "C" {} 43 | 44 | #[cfg(all(feature = "media", target_os = "android"))] 45 | #[link(name = "mediandk")] 46 | extern "C" {} 47 | 48 | #[cfg(all(feature = "bitmap", target_os = "android"))] 49 | #[link(name = "jnigraphics")] 50 | extern "C" {} 51 | 52 | #[cfg(all(feature = "audio", target_os = "android"))] 53 | #[link(name = "aaudio")] 54 | extern "C" {} 55 | 56 | #[cfg(all(feature = "sync", target_os = "android"))] 57 | #[link(name = "sync")] 58 | extern "C" {} 59 | -------------------------------------------------------------------------------- /ndk-sys/wrapper.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | // #include 5 | // #include 6 | // #include 7 | // #include 8 | // #include 9 | // #include 10 | // #include 11 | // #include 12 | // #include 13 | // #include 14 | // #include 15 | // #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | // #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | // #include 33 | // #include 34 | // #include 35 | // #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | // #include 41 | #include 42 | #include 43 | // Not available in nightly NDK CI builds 44 | // #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | #include 50 | // #include 51 | // #include 52 | #include 53 | #include 54 | #include 55 | #include 56 | #include 57 | #include 58 | // #include 59 | // #include 60 | #include 61 | #include 62 | #include 63 | #include 64 | // #include 65 | #include 66 | #include 67 | #include 68 | 69 | #include 70 | 71 | #include 72 | 73 | #include 74 | #include 75 | #include 76 | #include 77 | #include 78 | #include 79 | #include 80 | #include 81 | 82 | #include 83 | #include 84 | #include 85 | #include 86 | #include 87 | #include 88 | #include 89 | #include 90 | #include 91 | #include 92 | -------------------------------------------------------------------------------- /ndk/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | Cargo.lock 4 | -------------------------------------------------------------------------------- /ndk/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Unreleased 2 | 3 | - image_reader: Add `ImageReader::new_with_data_space()` constructor and `ImageReader::data_space()` getter from API level 34. (#474) 4 | - Add bindings for Performance Hint manager (`APerformanceHintManager`, `APerformanceHintSession`, `AWorkDuration`). (#480) 5 | 6 | # 0.9.0 (2024-04-26) 7 | 8 | - Move `MediaFormat` from `media::media_codec` to its own `media::media_format` module. (#442) 9 | - media_format: Expose `MediaFormat::copy()` and `MediaFormat::clear()` from API level 29. (#449) 10 | - **Breaking:** media_format: Mark all `fn set_*()` and `fn str()` as taking `self` by `&mut`. (#452) 11 | - **Breaking:** Require all `dyn Fn*` types to implement `Send` when the FFI implementation invokes them on a separate thread: (#455) 12 | - `audio::AudioStreamDataCallback`; 13 | - `audio::AudioStreamErrorCallback`; 14 | - `media::image_reader::BufferRemovedListener`; 15 | - `media::image_reader::ImageListener`; 16 | - `media::media_codec::ErrorCallback`; 17 | - `media::media_codec::FormatChangedCallback`; 18 | - `media::media_codec::InputAvailableCallback`; 19 | - `media::media_codec::OutputAvailableCallback`. 20 | - Drop previous `Box`ed callbacks _after_ registering new ones, instead of before. (#455) 21 | - input_queue: Add `from_java()` constructor, available since API level 33. (#456) 22 | - event: Add `from_java()` constructors to `KeyEvent` and `MotionEvent`, available since API level 31. (#456) 23 | - **Breaking:** image_reader: Special-case return statuses in `Image`-acquire functions. (#457) 24 | - **Breaking:** image_reader: Mark `ImageReader::acquire_latest_image_async()` `unsafe` to match the safety requirements on `ImageReader::acquire_next_image_async()`. (#457) 25 | - event: Implement `SourceClass` `bitflag` and provide `Source::class()` getter. (#458) 26 | - Ensure all `bitflags` implementations consider all (including unknown) bits in negation and `all()`. (#458) 27 | - **Breaking:** Mark all enums as `non_exhaustive` and fix `repr` types. (#459) 28 | - **Breaking:** native_window: Remove redundant `TRANSFORM_` prefix from `NativeWindowTransform` variants. (#460) 29 | - **Breaking:** hardware_buffer: Convert `HardwareBufferUsage` to `bitflags`. (#461) 30 | - bitmap: Guard `BitmapCompressError` behind missing `api-level-30` feature. (#462) 31 | - native_window: Require linking against `libnativewindow` for most API >= 26 functions. (#465) 32 | - **Breaking:** audio: Merge `AudioResult` variant enum into `AudioError`. (#467) 33 | - data_space: Add missing `DataSpaceRange::Unspecified` variant. (#468) 34 | - **Breaking:** looper: Require `Send` marker when adding fd event callbacks on `ForeignLooper`. (#469) 35 | - **Breaking:** Upgrade to [`ndk-sys 0.6.0`](../ndk-sys/CHANGELOG.md#060-2024-04-26). (#472) 36 | 37 | # 0.8.0 (2023-10-15) 38 | 39 | - event: Add `tool_type` getter for `Pointer`. (#323) 40 | - input_queue: Allow any non-zero return code from `pre_dispatch()` again, as per documentation. (#325) 41 | - asset: Use entire asset length when mapping buffer. (#387) 42 | - Bump MSRV to 1.66 for `raw-window-handle 0.5.1`, `num_enum`'s `catch_all` with arbitrary enum discriminants. (#388, #431) 43 | - Bump optional `jni` dependency for doctest example from `0.19` to `0.21`. (#390) 44 | - **Breaking:** Upgrade to [`ndk-sys 0.5.0`](../ndk-sys/CHANGELOG.md#050-2023-10-15). (#370) 45 | - **Breaking:** Upgrade `bitflags` crate from `1` to `2`. (#394) 46 | - bitmap: Add `try_format()` to `AndroidBitmapInfo` to handle unexpected formats without panicking. (#395) 47 | - Add `Font` bindings. (#397) 48 | - **Breaking:** Upgrade `num_enum` crate from `0.5.1` to `0.7`. (#398, #419) 49 | - **Breaking:** Renamed, moved and flattened "`media`" error types and helpers to a new `media_error` module. (#399, #432) 50 | - **Breaking:** media_codec: Wrap common dequeued-buffer status codes in enum. (#401) 51 | - **Breaking:** media_codec: Return `MaybeUninit` bytes in `buffer_mut()`. (#403) 52 | - native_window: Add `lock()` to blit raw pixel data. (#404) 53 | - hardware_buffer_format: Add `YCbCr_P010` and `R8_UNORM` variants. (#405) 54 | - **Breaking:** hardware_buffer_format: Add catch-all variant. (#407) 55 | - asset: Add missing `is_allocated()` and `open_file_descriptor()` methods. (#409) 56 | - **Breaking:** media_codec: Add support for asynchronous notification callbacks. (#410) 57 | - Add panic guards to callbacks. (#412) 58 | - looper: Add `remove_fd()` to unregister events/callbacks for a file descriptor. (#416) 59 | - **Breaking:** Use `BorrowedFd` and `OwnedFd` to clarify possible ownership transitions. (#417) 60 | - **Breaking:** Upgrade to [`ndk-sys 0.5.0`](../ndk-sys/CHANGELOG.md#050-2023-10-15). (#420) 61 | - Add bindings for `sync.h`. (#423) 62 | - **Breaking:** bitmap: Provide detailed implementation for `AndroidBitmapInfoFlags`. (#424) 63 | - native_window: Add `set_buffers_transform()`, `try_allocate_buffers()` and `set_frame_rate*()`. (#425) 64 | - Add bindings for `ASharedMemory`. (#427) 65 | - hardware_buffer: Add `id()` to retrieve a system-wide unique identifier for a `HardwareBuffer`. (#428) 66 | - **Breaking:** bitmap: Strip `Android` prefix from structs and enums, and `Bitmap` from `Result`. (#430) 67 | - **Breaking:** `raw-window-handle 0.5` support is now behind an _optional_ `rwh_05` crate feature and `raw-window-handle` `0.4` and `0.6` support is provided via the new `rwh_04` and (default-enabled) `rwh_06` crate features. (#434) 68 | - **Breaking:** looper: Provide `event` value to file descriptor poll callback. (#435) 69 | - **Breaking:** `HardwareBufferFormat` is no longer exported from `hardware_buffer` and `native_window`, and can only be reached through the `hardware_buffer_format` module. (#436) 70 | - **Breaking:** `get_` prefixes have been removed from all public functions in light of the [C-GETTER](https://rust-lang.github.io/api-guidelines/naming.html#getter-names-follow-rust-convention-c-getter) convention. (#437) 71 | - Add `DataSpace` type and relevant functions on `Bitmap` and `NativeWindow`. (#438) 72 | - bitmap: Add `Bitmap::compress()` and `Bitmap::compress_raw()` functions. (#440) 73 | - **Breaking:** Turn `BitmapError` into a `non_exhaustive` `enum`. (#440) 74 | - **Breaking:** audio: Rename `AudioErrorResult` to `AudioResult` and turn into a `non_exhaustive` `enum`. (#441) 75 | 76 | # 0.7.0 (2022-07-24) 77 | 78 | - hardware_buffer: Make `HardwareBuffer::as_ptr()` public for interop with Vulkan. (#213) 79 | - **Breaking:** `Configuration::country()` now returns `None` when the country is unset (akin to `Configuration::language()`). (#220) 80 | - Add `MediaCodec` and `MediaFormat` bindings. (#216) 81 | - **Breaking:** Upgrade to [`ndk-sys 0.4.0`](../ndk-sys/CHANGELOG.md#040-2022-07-24) and use new `enum` newtype wrappers. (#245) 82 | - native_window: Use `release`/`acquire` for `Drop` and `Clone` respectively. (#207) 83 | - **Breaking:** audio: Rename from `aaudio` to `audio` and drop `A` prefix. (#273) 84 | - Implement `HasRawWindowHandle` directly on `NativeWindow`. (#274, #319) 85 | - **Breaking:** native_activity: Replace `CStr` return types with `Path`. (#279) 86 | - native_window: Add `format()` getter and `set_buffers_geometry()` setter. (#276) 87 | - native_activity: Add `set_window_format()` setter. (#277) 88 | - native_activity: Add `set_window_flags()` to change window behavior. (#278) 89 | - Add `SurfaceTexture` bindings. (#267) 90 | - Improve library and structure documentation, linking back to the NDK docs more rigorously. (#290) 91 | - **Breaking:** input_queue: `get_event()` now returns a `Result` with `std::io::Error`; `InputQueueError` has been removed. (#292) 92 | - **Breaking:** input_queue: `has_events()` now returns a `bool` directly without being wrapped in `Result`. (#294) 93 | - **Breaking:** hardware_buffer: `HardwareBufferError` has been removed and replaced with `std::io::Error` in return types. (#295) 94 | - Fixed `HardwareBuffer` leak on buffers returned from `AndroidBitmap::get_hardware_buffer()`. (#296) 95 | - Bump optional `jni` dependency for doctest example from `0.18` to `0.19`. (#300) 96 | - hardware_buffer: Made `HardwareBufferDesc` fields `pub`. (#313) 97 | - **Breaking:** Remove `hardware_buffer` and `trace` features in favour of using `api-level-26` or `api-level-23` directly. (#320) 98 | 99 | # 0.6.0 (2022-01-05) 100 | 101 | - **Breaking:** Upgrade to [`ndk-sys 0.3.0`](../ndk-sys/CHANGELOG.md#030-2022-01-05) and migrate to `jni-sys` types that it now directly uses in its bindings. (#209 / #214) 102 | 103 | # 0.5.0 (2021-11-22) 104 | 105 | - **Breaking:** Replace `add_fd_with_callback` `ident` with constant value `ALOOPER_POLL_CALLBACK`, 106 | as per . 107 | - **Breaking:** Accept unboxed closure in `add_fd_with_callback`. 108 | - aaudio: Replace "Added in" comments with missing `#[cfg(feature)]`. 109 | - aaudio: Add missing `fn get_allowed_capture_policy()`. 110 | - configuration: Add missing `api-level-30` feature to `fn screen_round()`. 111 | 112 | # 0.4.0 (2021-08-02) 113 | 114 | - **Breaking:** Model looper file descriptor events integer as `bitflags`. 115 | 116 | # 0.3.0 (2021-01-30) 117 | 118 | - **Breaking:** Looper `ident` not passed in `data` pointer anymore. 119 | `attach_looper` now only sets the `ident` field when attaching an 120 | `InputQueue` to a `ForeignLooper`. 121 | If you are relying on `Poll::Event::data` to tell event fd and 122 | input queue apart, please use `Poll::Event::ident` and the new 123 | constants introduced in `ndk-glue`! 124 | 125 | # 0.2.1 (2020-10-15) 126 | 127 | - Fix documentation build on docs.rs 128 | 129 | # 0.2.0 (2020-09-15) 130 | 131 | - **Breaking:** Updated to use [ndk-sys 0.2.0](../ndk-sys/CHANGELOG.md#020-2020-09-15) 132 | - Added `media` bindings 133 | - Added `bitmap` and `hardware_buffer` bindings 134 | - Added `aaudio` bindings 135 | - Fixed assets directory path to be relative to the manifest 136 | - Added `trace` feature for native tracing 137 | 138 | # 0.1.0 (2020-04-22) 139 | 140 | - Initial release! 🎉 141 | -------------------------------------------------------------------------------- /ndk/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ndk" 3 | version = "0.9.0" 4 | authors = ["The Rust Mobile contributors"] 5 | edition = "2021" 6 | description = "Safe Rust bindings to the Android NDK" 7 | license = "MIT OR Apache-2.0" 8 | keywords = ["android", "ndk"] 9 | readme = "../README.md" 10 | documentation = "https://docs.rs/ndk" 11 | homepage = "https://github.com/rust-mobile/ndk" 12 | repository = "https://github.com/rust-mobile/ndk" 13 | rust-version = "1.66" 14 | categories = ["os", "os::android-apis", "api-bindings"] 15 | 16 | [features] 17 | default = ["rwh_06"] 18 | all = ["audio", "bitmap", "media", "nativewindow", "sync", "api-level-34", "rwh_04", "rwh_05", "rwh_06"] 19 | 20 | audio = ["ffi/audio", "api-level-26"] 21 | bitmap = ["ffi/bitmap"] 22 | media = ["ffi/media"] 23 | nativewindow = ["ffi/nativewindow"] 24 | sync = ["ffi/sync", "api-level-26"] 25 | 26 | api-level-23 = [] 27 | api-level-24 = ["api-level-23"] 28 | api-level-25 = ["api-level-24"] 29 | api-level-26 = ["api-level-25"] 30 | api-level-27 = ["api-level-26"] 31 | api-level-28 = ["api-level-27"] 32 | api-level-29 = ["api-level-28"] 33 | api-level-30 = ["api-level-29"] 34 | api-level-31 = ["api-level-30"] 35 | api-level-32 = ["api-level-31"] 36 | api-level-33 = ["api-level-32"] 37 | api-level-34 = ["api-level-33"] 38 | api-level-35 = ["api-level-34"] 39 | 40 | test = ["ffi/test", "jni", "all"] 41 | 42 | [dependencies] 43 | bitflags = "2.4" # At least 2.4.0 for `const _ = !0` 44 | jni-sys = "0.3" 45 | log = "0.4.6" 46 | num_enum = "0.7" 47 | rwh_04 = { package = "raw-window-handle", version = "0.4", optional = true } 48 | rwh_05 = { package = "raw-window-handle", version = "0.5", optional = true } 49 | rwh_06 = { package = "raw-window-handle", version = "0.6", optional = true } 50 | thiserror = "1.0.23" 51 | 52 | [dependencies.jni] 53 | version = "0.21" 54 | optional = true 55 | 56 | [dependencies.ffi] 57 | package = "ndk-sys" 58 | path = "../ndk-sys" 59 | version = "0.6.0" 60 | 61 | [dev-dependencies] 62 | # Only for use in documentation and doc-tests 63 | libc = "0.2.3" 64 | 65 | [package.metadata.docs.rs] 66 | features = ["jni", "all"] 67 | rustdoc-args = ["--cfg", "docsrs"] 68 | targets = [ 69 | "aarch64-linux-android", 70 | "armv7-linux-androideabi", 71 | "i686-linux-android", 72 | "x86_64-linux-android", 73 | ] 74 | -------------------------------------------------------------------------------- /ndk/src/asset.rs: -------------------------------------------------------------------------------- 1 | //! Bindings for [`AAsset`], [`AAssetDir`] and [`AAssetManager`] 2 | //! 3 | //! [`AAsset`]: https://developer.android.com/ndk/reference/group/asset#aasset 4 | //! [`AAssetDir`]: https://developer.android.com/ndk/reference/group/asset#aassetdir 5 | //! [`AAssetManager`]: https://developer.android.com/ndk/reference/group/asset#aassetmanager 6 | 7 | use std::{ 8 | ffi::{CStr, CString}, 9 | io, 10 | os::fd::{FromRawFd, OwnedFd}, 11 | ptr::NonNull, 12 | }; 13 | 14 | /// A native [`AAssetManager *`] 15 | /// 16 | /// [`AAssetManager *`]: https://developer.android.com/ndk/reference/group/asset#aassetmanager 17 | #[derive(Debug)] 18 | #[doc(alias = "AAssetManager")] 19 | pub struct AssetManager { 20 | ptr: NonNull, 21 | } 22 | 23 | // AAssetManager is thread safe. 24 | // See https://developer.android.com/ndk/reference/group/asset#aassetmanager 25 | unsafe impl Send for AssetManager {} 26 | unsafe impl Sync for AssetManager {} 27 | 28 | impl AssetManager { 29 | /// Create an `AssetManager` from a pointer 30 | /// 31 | /// # Safety 32 | /// By calling this function, you assert that the pointer is a valid pointer to a native 33 | /// `AAssetManager`. 34 | pub unsafe fn from_ptr(ptr: NonNull) -> Self { 35 | Self { ptr } 36 | } 37 | 38 | /// Returns the pointer to the native `AAssetManager`. 39 | pub fn ptr(&self) -> NonNull { 40 | self.ptr 41 | } 42 | 43 | /// Open the asset. Returns [`None`] if opening the asset fails. 44 | /// 45 | /// This currently always opens the asset in the streaming mode. 46 | #[doc(alias = "AAssetManager_open")] 47 | pub fn open(&self, filename: &CStr) -> Option { 48 | unsafe { 49 | let ptr = ffi::AAssetManager_open( 50 | self.ptr.as_ptr(), 51 | filename.as_ptr(), 52 | ffi::AASSET_MODE_STREAMING as i32, 53 | ); 54 | Some(Asset::from_ptr(NonNull::new(ptr)?)) 55 | } 56 | } 57 | 58 | /// Open an asset directory. Returns [`None`] if opening the directory fails. 59 | #[doc(alias = "AAssetManager_openDir")] 60 | pub fn open_dir(&self, filename: &CStr) -> Option { 61 | unsafe { 62 | let ptr = ffi::AAssetManager_openDir(self.ptr.as_ptr(), filename.as_ptr()); 63 | Some(AssetDir::from_ptr(NonNull::new(ptr)?)) 64 | } 65 | } 66 | } 67 | 68 | /// A native [`AAssetDir *`] 69 | /// 70 | /// ```no_run 71 | /// # use std::ffi::CString; 72 | /// # use ndk::asset::AssetManager; 73 | /// # let asset_manager: AssetManager = unimplemented!(); 74 | /// use std::io::Read; 75 | /// 76 | /// let mut my_dir = asset_manager 77 | /// .open_dir(&CString::new("my_dir").unwrap()) 78 | /// .expect("Could not open directory"); 79 | /// 80 | /// // Use it as an iterator 81 | /// let all_files = my_dir.collect::>(); 82 | /// 83 | /// // Reset the iterator 84 | /// my_dir.rewind(); 85 | /// 86 | /// // Use .with_next() to iterate without allocating `CString`s 87 | /// while let Some(asset) = my_dir.with_next(|cstr| asset_manager.open(cstr).unwrap()) { 88 | /// let mut text = String::new(); 89 | /// asset.read_to_string(&mut text); 90 | /// // ... 91 | /// } 92 | /// ``` 93 | /// 94 | /// [`AAssetDir *`]: https://developer.android.com/ndk/reference/group/asset#aassetdir 95 | #[derive(Debug)] 96 | #[doc(alias = "AAssetDir")] 97 | pub struct AssetDir { 98 | ptr: NonNull, 99 | } 100 | 101 | // It's unclear if AAssetDir is thread safe. 102 | // However, AAsset is not, so there's a good chance that AAssetDir is not either. 103 | 104 | impl Drop for AssetDir { 105 | #[doc(alias = "AAssetDir_close")] 106 | fn drop(&mut self) { 107 | unsafe { ffi::AAssetDir_close(self.ptr.as_ptr()) } 108 | } 109 | } 110 | 111 | impl AssetDir { 112 | /// Construct an `AssetDir` from the native `AAssetDir *`. This gives ownership of the 113 | /// `AAssetDir *` to the `AssetDir`, which will handle closing the asset. Avoid using 114 | /// the pointer after calling this function. 115 | /// 116 | /// # Safety 117 | /// By calling this function, you assert that it points to a valid native `AAssetDir`. 118 | pub unsafe fn from_ptr(ptr: NonNull) -> Self { 119 | Self { ptr } 120 | } 121 | 122 | /// The corresponding native `AAssetDir *` 123 | pub fn ptr(&self) -> NonNull { 124 | self.ptr 125 | } 126 | 127 | /// Get the next filename, if any, and process it. Like [`Iterator::next()`], but performs 128 | /// no additional allocation. 129 | /// 130 | /// The filenames are in the correct format to be passed to [`AssetManager::open()`]. 131 | #[doc(alias = "AAssetDir_getNextFileName")] 132 | pub fn with_next(&mut self, f: impl for<'a> FnOnce(&'a CStr) -> T) -> Option { 133 | unsafe { 134 | let next_name = ffi::AAssetDir_getNextFileName(self.ptr.as_ptr()); 135 | if next_name.is_null() { 136 | None 137 | } else { 138 | Some(f(CStr::from_ptr(next_name))) 139 | } 140 | } 141 | } 142 | 143 | /// Reset the iteration state 144 | #[doc(alias = "AAssetDir_rewind")] 145 | pub fn rewind(&mut self) { 146 | unsafe { 147 | ffi::AAssetDir_rewind(self.ptr.as_ptr()); 148 | } 149 | } 150 | } 151 | 152 | impl Iterator for AssetDir { 153 | type Item = CString; 154 | 155 | fn next(&mut self) -> Option { 156 | self.with_next(|cstr| cstr.to_owned()) 157 | } 158 | } 159 | 160 | /// A native [`AAsset *`], opened in streaming mode 161 | /// 162 | /// ```no_run 163 | /// # use std::ffi::CString; 164 | /// # use ndk::asset::AssetManager; 165 | /// # let asset_manager: AssetManager = unimplemented!(); 166 | /// use std::io::Read; 167 | /// 168 | /// let asset = asset_manager 169 | /// .open(&CString::new("path/to/asset").unwrap()) 170 | /// .expect("Could not open asset"); 171 | /// 172 | /// let mut data = vec![]; 173 | /// asset.read_to_end(&mut data); 174 | /// // ... use data ... 175 | /// ``` 176 | /// 177 | /// [`AAsset *`]: https://developer.android.com/ndk/reference/group/asset#aasset 178 | #[derive(Debug)] 179 | #[doc(alias = "AAsset")] 180 | pub struct Asset { 181 | ptr: NonNull, 182 | } 183 | 184 | // AAsset is *not* thread safe. 185 | // See https://developer.android.com/ndk/reference/group/asset#aasset 186 | 187 | impl Drop for Asset { 188 | #[doc(alias = "AAsset_close")] 189 | fn drop(&mut self) { 190 | unsafe { ffi::AAsset_close(self.ptr.as_ptr()) } 191 | } 192 | } 193 | 194 | impl Asset { 195 | /// Construct an `Asset` from the native `AAsset *`. This gives ownership of the `AAsset *` to 196 | /// the `Asset`, which will handle closing the asset. Avoid using the pointer after calling 197 | /// this function. 198 | /// 199 | /// # Safety 200 | /// By calling this function, you assert that it points to a valid native `AAsset`, open 201 | /// in the streaming mode. 202 | pub unsafe fn from_ptr(ptr: NonNull) -> Self { 203 | Self { ptr } 204 | } 205 | 206 | /// The corresponding native `AAsset *` 207 | pub fn ptr(&self) -> NonNull { 208 | self.ptr 209 | } 210 | 211 | /// Returns the total length of the asset, in bytes 212 | #[doc(alias = "AAsset_getLength64")] 213 | pub fn length(&self) -> usize { 214 | unsafe { ffi::AAsset_getLength64(self.ptr.as_ptr()) as usize } 215 | } 216 | 217 | /// Returns the remaining length of the asset, in bytes 218 | #[doc(alias = "AAsset_getRemainingLength64")] 219 | pub fn remaining_length(&self) -> usize { 220 | unsafe { ffi::AAsset_getRemainingLength64(self.ptr.as_ptr()) as usize } 221 | } 222 | 223 | /// Maps all data into a buffer and returns it 224 | #[doc(alias = "AAsset_getBuffer")] 225 | pub fn buffer(&mut self) -> io::Result<&[u8]> { 226 | unsafe { 227 | let buf_ptr = ffi::AAsset_getBuffer(self.ptr.as_ptr()); 228 | if buf_ptr.is_null() { 229 | Err(io::Error::new( 230 | io::ErrorKind::Other, 231 | "Android Asset error creating buffer", 232 | )) 233 | } else { 234 | Ok(std::slice::from_raw_parts( 235 | buf_ptr as *const u8, 236 | self.length(), 237 | )) 238 | } 239 | } 240 | } 241 | 242 | /// Returns whether this asset's internal buffer is allocated in ordinary RAM (i.e. not `mmap`ped). 243 | #[doc(alias = "AAsset_isAllocated")] 244 | pub fn is_allocated(&self) -> bool { 245 | unsafe { ffi::AAsset_isAllocated(self.ptr.as_ptr()) != 0 } 246 | } 247 | 248 | /// Open a new file descriptor that can be used to read the asset data. 249 | /// 250 | /// Returns an error if direct fd access is not possible (for example, if the asset is compressed). 251 | #[doc(alias = "AAsset_openFileDescriptor64")] 252 | pub fn open_file_descriptor(&self) -> io::Result { 253 | let mut offset = 0; 254 | let mut size = 0; 255 | let res = 256 | unsafe { ffi::AAsset_openFileDescriptor64(self.ptr.as_ptr(), &mut offset, &mut size) }; 257 | if res >= 0 { 258 | Ok(OpenedFileDescriptor { 259 | fd: unsafe { OwnedFd::from_raw_fd(res) }, 260 | offset: offset as usize, 261 | size: size as usize, 262 | }) 263 | } else { 264 | Err(io::Error::new( 265 | io::ErrorKind::Other, 266 | "Android Asset openFileDescriptor error", 267 | )) 268 | } 269 | } 270 | } 271 | 272 | impl io::Read for Asset { 273 | #[doc(alias = "AAsset_read")] 274 | fn read(&mut self, buf: &mut [u8]) -> io::Result { 275 | unsafe { 276 | let res = ffi::AAsset_read(self.ptr.as_ptr(), buf.as_mut_ptr() as *mut _, buf.len()); 277 | if res >= 0 { 278 | Ok(res as usize) 279 | } else { 280 | Err(io::Error::new( 281 | io::ErrorKind::Other, 282 | "Android Asset read error", 283 | )) 284 | } 285 | } 286 | } 287 | } 288 | 289 | impl io::Seek for Asset { 290 | #[doc(alias = "AAsset_seek64")] 291 | fn seek(&mut self, seek: io::SeekFrom) -> io::Result { 292 | unsafe { 293 | let res = match seek { 294 | io::SeekFrom::Start(x) => { 295 | ffi::AAsset_seek64(self.ptr.as_ptr(), x as i64, ffi::SEEK_SET as i32) 296 | } 297 | io::SeekFrom::Current(x) => { 298 | ffi::AAsset_seek64(self.ptr.as_ptr(), x, ffi::SEEK_CUR as i32) 299 | } 300 | io::SeekFrom::End(x) => { 301 | ffi::AAsset_seek64(self.ptr.as_ptr(), x, ffi::SEEK_END as i32) 302 | } 303 | }; 304 | if res < 0 { 305 | Err(io::Error::new( 306 | io::ErrorKind::Other, 307 | "Android Asset seek error", 308 | )) 309 | } else { 310 | Ok(res as u64) 311 | } 312 | } 313 | } 314 | } 315 | 316 | /// Contains the opened file descriptor returned by [`Asset::open_file_descriptor()`], together 317 | /// with the offset and size of the given asset within that file descriptor. 318 | #[derive(Debug)] 319 | pub struct OpenedFileDescriptor { 320 | pub fd: OwnedFd, 321 | pub offset: usize, 322 | pub size: usize, 323 | } 324 | -------------------------------------------------------------------------------- /ndk/src/bitmap.rs: -------------------------------------------------------------------------------- 1 | //! Bindings for [`AndroidBitmap`] functions 2 | //! 3 | //! These functions operate directly on a JNI [`android.graphics.Bitmap`] instance. 4 | //! 5 | //! [`AndroidBitmap`]: https://developer.android.com/ndk/reference/group/bitmap 6 | //! [`android.graphics.Bitmap`]: https://developer.android.com/reference/android/graphics/Bitmap 7 | #![cfg(feature = "bitmap")] 8 | 9 | use jni_sys::{jobject, JNIEnv}; 10 | use num_enum::{FromPrimitive, IntoPrimitive}; 11 | use std::{error, fmt, mem::MaybeUninit}; 12 | 13 | #[cfg(feature = "api-level-30")] 14 | use crate::data_space::DataSpace; 15 | #[cfg(feature = "api-level-30")] 16 | use crate::hardware_buffer::HardwareBufferRef; 17 | 18 | #[repr(i32)] 19 | #[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, IntoPrimitive)] 20 | #[non_exhaustive] 21 | pub enum BitmapError { 22 | #[doc(alias = "ANDROID_BITMAP_RESULT_ALLOCATION_FAILED")] 23 | AllocationFailed = ffi::ANDROID_BITMAP_RESULT_ALLOCATION_FAILED, 24 | #[doc(alias = "ANDROID_BITMAP_RESULT_BAD_PARAMETER")] 25 | BadParameter = ffi::ANDROID_BITMAP_RESULT_BAD_PARAMETER, 26 | #[doc(alias = "ANDROID_BITMAP_RESULT_JNI_EXCEPTION")] 27 | JniException = ffi::ANDROID_BITMAP_RESULT_JNI_EXCEPTION, 28 | 29 | // Use the SUCCESS discriminant, as no-one will be able to call `as i32` and only has access to 30 | // the constants via `From` provided by `IntoPrimitive` which reads the contained value. 31 | // An autogenerated ` + 1` discriminant is normally fine, except that the 32 | // previous variant is negative and `+ 1` would match the variant before that. 33 | #[doc(hidden)] 34 | #[num_enum(catch_all)] 35 | __Unknown(i32) = ffi::ANDROID_BITMAP_RESULT_SUCCESS, 36 | } 37 | 38 | impl fmt::Display for BitmapError { 39 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 40 | write!(f, "{:?}", self) 41 | } 42 | } 43 | 44 | impl error::Error for BitmapError {} 45 | 46 | pub type Result = std::result::Result; 47 | 48 | impl BitmapError { 49 | pub(crate) fn from_status(status: i32) -> Result<()> { 50 | match status { 51 | ffi::ANDROID_BITMAP_RESULT_SUCCESS => Ok(()), 52 | x => Err(Self::from(x)), 53 | } 54 | } 55 | } 56 | 57 | fn construct(with_ptr: impl FnOnce(*mut T) -> i32) -> Result { 58 | let mut result = MaybeUninit::uninit(); 59 | let status = with_ptr(result.as_mut_ptr()); 60 | BitmapError::from_status(status).map(|()| unsafe { result.assume_init() }) 61 | } 62 | 63 | #[repr(i32)] 64 | #[derive(Clone, Copy, Debug, PartialEq, Eq, IntoPrimitive, FromPrimitive)] 65 | #[allow(non_camel_case_types)] 66 | #[doc(alias = "AndroidBitmapFormat")] 67 | #[non_exhaustive] 68 | pub enum BitmapFormat { 69 | #[doc(alias = "ANDROID_BITMAP_FORMAT_NONE")] 70 | NONE = ffi::AndroidBitmapFormat::ANDROID_BITMAP_FORMAT_NONE.0 as i32, 71 | #[doc(alias = "ANDROID_BITMAP_FORMAT_RGBA_8888")] 72 | RGBA_8888 = ffi::AndroidBitmapFormat::ANDROID_BITMAP_FORMAT_RGBA_8888.0 as i32, 73 | #[doc(alias = "ANDROID_BITMAP_FORMAT_RGB_565")] 74 | RGB_565 = ffi::AndroidBitmapFormat::ANDROID_BITMAP_FORMAT_RGB_565.0 as i32, 75 | #[deprecated = "Deprecated in API level 13. Because of the poor quality of this configuration, it is advised to use ARGB_8888 instead."] 76 | #[doc(alias = "ANDROID_BITMAP_FORMAT_RGBA_4444")] 77 | RGBA_4444 = ffi::AndroidBitmapFormat::ANDROID_BITMAP_FORMAT_RGBA_4444.0 as i32, 78 | #[doc(alias = "ANDROID_BITMAP_FORMAT_A_8")] 79 | A_8 = ffi::AndroidBitmapFormat::ANDROID_BITMAP_FORMAT_A_8.0 as i32, 80 | #[doc(alias = "ANDROID_BITMAP_FORMAT_RGBA_F16")] 81 | RGBA_F16 = ffi::AndroidBitmapFormat::ANDROID_BITMAP_FORMAT_RGBA_F16.0 as i32, 82 | #[doc(alias = "ANDROID_BITMAP_FORMAT_RGBA_1010102")] 83 | RGBA_1010102 = ffi::AndroidBitmapFormat::ANDROID_BITMAP_FORMAT_RGBA_1010102.0 as i32, 84 | 85 | #[doc(hidden)] 86 | #[num_enum(catch_all)] 87 | __Unknown(i32), 88 | } 89 | 90 | /// An immediate wrapper over [`android.graphics.Bitmap`] 91 | /// 92 | /// [`android.graphics.Bitmap`]: https://developer.android.com/reference/android/graphics/Bitmap 93 | #[derive(Debug)] 94 | pub struct Bitmap { 95 | env: *mut JNIEnv, 96 | inner: jobject, 97 | } 98 | 99 | impl Bitmap { 100 | /// Create a [`Bitmap`] wrapper from JNI pointers 101 | /// 102 | /// # Safety 103 | /// This function should be called with a healthy JVM pointer and with a non-null 104 | /// [`android.graphics.Bitmap`], which must be kept alive on the Java/Kotlin side. 105 | /// 106 | /// [`android.graphics.Bitmap`]: https://developer.android.com/reference/android/graphics/Bitmap 107 | pub unsafe fn from_jni(env: *mut JNIEnv, bitmap: jobject) -> Self { 108 | Self { env, inner: bitmap } 109 | } 110 | 111 | /// Fills out and returns the [`BitmapInfo`] struct for the given Java bitmap object. 112 | #[doc(alias = "AndroidBitmap_getInfo")] 113 | pub fn info(&self) -> Result { 114 | let inner = 115 | construct(|res| unsafe { ffi::AndroidBitmap_getInfo(self.env, self.inner, res) })?; 116 | 117 | Ok(BitmapInfo { inner }) 118 | } 119 | 120 | /// Returns the [`DataSpace`] of this [`Bitmap`]. 121 | /// 122 | /// Note that [`DataSpace`] only exposes a few values. This may return [`DataSpace::Unknown`], 123 | /// even for Named ColorSpaces, if they have no corresponding [`DataSpace`]. 124 | #[cfg(feature = "api-level-30")] 125 | #[doc(alias = "AndroidBitmap_getDataSpace")] 126 | pub fn data_space(&self) -> DataSpace { 127 | let value = unsafe { ffi::AndroidBitmap_getDataSpace(self.env, self.inner) }; 128 | value.into() 129 | } 130 | 131 | /// Attempt to lock the pixel address. 132 | /// 133 | /// Locking will ensure that the memory for the pixels will not move until the 134 | /// [`Bitmap::unlock_pixels()`] call, and ensure that, if the pixels had been previously purged, 135 | /// they will have been restored. 136 | /// 137 | /// If this call succeeds, it must be balanced by a call to [`Bitmap::unlock_pixels()`], after 138 | /// which time the address of the pixels should no longer be used. 139 | #[doc(alias = "AndroidBitmap_lockPixels")] 140 | pub fn lock_pixels(&self) -> Result<*mut std::os::raw::c_void> { 141 | construct(|res| unsafe { ffi::AndroidBitmap_lockPixels(self.env, self.inner, res) }) 142 | } 143 | 144 | /// Call this to balance a successful call to [`Bitmap::lock_pixels()`]. 145 | #[doc(alias = "AndroidBitmap_unlockPixels")] 146 | pub fn unlock_pixels(&self) -> Result<()> { 147 | let status = unsafe { ffi::AndroidBitmap_unlockPixels(self.env, self.inner) }; 148 | BitmapError::from_status(status) 149 | } 150 | 151 | /// Retrieve the native object associated with an [`ffi::ANDROID_BITMAP_FLAGS_IS_HARDWARE`] 152 | /// [`Bitmap`] (requires [`BitmapInfoFlags::is_hardware()`] on [`BitmapInfo::flags()`] to return 153 | /// [`true`]). 154 | /// 155 | /// Client must not modify it while a [`Bitmap`] is wrapping it. 156 | #[cfg(feature = "api-level-30")] 157 | #[doc(alias = "AndroidBitmap_getHardwareBuffer")] 158 | pub fn hardware_buffer(&self) -> Result { 159 | unsafe { 160 | let result = 161 | construct(|res| ffi::AndroidBitmap_getHardwareBuffer(self.env, self.inner, res))?; 162 | let non_null = if cfg!(debug_assertions) { 163 | std::ptr::NonNull::new(result).expect("result should never be null") 164 | } else { 165 | std::ptr::NonNull::new_unchecked(result) 166 | }; 167 | Ok(HardwareBufferRef::from_ptr(non_null)) 168 | } 169 | } 170 | 171 | /// [Lock] the pixels in `self` and compress them as described by [`info()`]. 172 | /// 173 | /// Unlike [`compress_raw()`] this requires a [`Bitmap`] object (as `self`) backed by a 174 | /// [`jobject`]. 175 | /// 176 | /// # Parameters 177 | /// - `format`: [`BitmapCompressFormat`] to compress to. 178 | /// - `quality`: Hint to the compressor, `0-100`. The value is interpreted differently 179 | /// depending on [`BitmapCompressFormat`]. 180 | /// - `compress_callback`: Closure that writes the compressed data. Will be called on the 181 | /// current thread, each time the compressor has compressed more data that is ready to be 182 | /// written. May be called more than once for each call to this method. 183 | /// 184 | /// [Lock]: Self::lock_pixels() 185 | /// [`info()`]: Self::info() 186 | /// [`compress_raw()`]: Self::compress_raw() 187 | #[cfg(feature = "api-level-30")] 188 | #[doc(alias = "AndroidBitmap_compress")] 189 | pub fn compress Result<(), ()>>( 190 | &self, 191 | format: BitmapCompressFormat, 192 | quality: i32, 193 | compress_callback: F, 194 | ) -> Result<(), BitmapCompressError> { 195 | let info = self.info()?; 196 | let data_space = self.data_space(); 197 | let pixels = self.lock_pixels()?; 198 | // SAFETY: When lock_pixels() succeeds, assume it returns a valid pointer that stays 199 | // valid until we call unlock_pixels(). 200 | let result = unsafe { 201 | Self::compress_raw( 202 | &info, 203 | data_space, 204 | pixels, 205 | format, 206 | quality, 207 | compress_callback, 208 | ) 209 | }; 210 | self.unlock_pixels()?; 211 | result 212 | } 213 | 214 | /// Compress `pixels` as described by `info`. 215 | /// 216 | /// Unlike [`compress()`] this takes a raw pointer to pixels and does not need a [`Bitmap`] 217 | /// object backed by a [`jobject`]. 218 | /// 219 | /// # Parameters 220 | /// - `info`: Description of the pixels to compress. 221 | /// - `data_space`: [`DataSpace`] describing the color space of the pixels. Should _not_ be 222 | /// [`DataSpace::Unknown`] [^1]. 223 | /// - `pixels`: Pointer to pixels to compress. 224 | /// - `format`: [`BitmapCompressFormat`] to compress to. 225 | /// - `quality`: Hint to the compressor, `0-100`. The value is interpreted differently 226 | /// depending on [`BitmapCompressFormat`]. 227 | /// - `compress_callback`: Closure that writes the compressed data. Will be called on the 228 | /// current thread, each time the compressor has compressed more data that is ready to be 229 | /// written. May be called more than once for each call to this method. 230 | /// 231 | /// # Safety 232 | /// `pixels` must point to a valid buffer that matches the size, stride and format in `info`. 233 | /// 234 | /// [`compress()`]: Self::compress() 235 | /// [^1]: 236 | #[cfg(feature = "api-level-30")] 237 | #[doc(alias = "AndroidBitmap_compress")] 238 | pub unsafe fn compress_raw Result<(), ()>>( 239 | info: &BitmapInfo, 240 | data_space: DataSpace, 241 | pixels: *const std::ffi::c_void, 242 | format: BitmapCompressFormat, 243 | quality: i32, 244 | compress_callback: F, 245 | ) -> Result<(), BitmapCompressError> { 246 | if data_space == DataSpace::Unknown { 247 | return Err(BitmapCompressError::DataSpaceUnknown); 248 | } 249 | 250 | use std::{any::Any, ffi::c_void, panic::AssertUnwindSafe}; 251 | struct CallbackState Result<(), ()>> { 252 | callback: F, 253 | panic: Option>, 254 | } 255 | let mut cb_state = CallbackState:: { 256 | callback: compress_callback, 257 | panic: None, 258 | }; 259 | 260 | extern "C" fn compress_cb Result<(), ()>>( 261 | context: *mut c_void, 262 | data: *const c_void, 263 | size: usize, 264 | ) -> bool { 265 | // SAFETY: This callback will only be called serially on a single thread. Both the 266 | // panic state and the FnMut context need to be available mutably. 267 | let cb_state = unsafe { context.cast::>().as_mut() }.unwrap(); 268 | let data = unsafe { std::slice::from_raw_parts(data.cast(), size) }; 269 | let panic = std::panic::catch_unwind(AssertUnwindSafe(|| (cb_state.callback)(data))); 270 | match panic { 271 | Ok(r) => r.is_ok(), 272 | Err(e) => { 273 | cb_state.panic = Some(e); 274 | false 275 | } 276 | } 277 | } 278 | 279 | let status = unsafe { 280 | ffi::AndroidBitmap_compress( 281 | &info.inner, 282 | data_space.into(), 283 | pixels, 284 | format.into(), 285 | quality, 286 | <*mut _>::cast(&mut cb_state), 287 | Some(compress_cb::), 288 | ) 289 | }; 290 | 291 | if let Some(panic) = cb_state.panic { 292 | std::panic::resume_unwind(panic) 293 | } 294 | 295 | Ok(BitmapError::from_status(status)?) 296 | } 297 | } 298 | 299 | /// Possible values for [`ffi::ANDROID_BITMAP_FLAGS_ALPHA_MASK`] within [`BitmapInfoFlags`] 300 | #[repr(u32)] 301 | #[cfg(feature = "api-level-30")] 302 | #[derive(Clone, Copy, Debug, IntoPrimitive, FromPrimitive)] 303 | #[doc(alias = "ANDROID_BITMAP_FLAGS_ALPHA_MASK")] 304 | #[non_exhaustive] 305 | pub enum BitmapInfoFlagsAlpha { 306 | /// Pixel components are premultiplied by alpha. 307 | #[doc(alias = "ANDROID_BITMAP_FLAGS_ALPHA_PREMUL")] 308 | Premultiplied = ffi::ANDROID_BITMAP_FLAGS_ALPHA_PREMUL, 309 | /// Pixels are opaque. 310 | #[doc(alias = "ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE")] 311 | Opaque = ffi::ANDROID_BITMAP_FLAGS_ALPHA_OPAQUE, 312 | /// Pixel components are independent of alpha. 313 | #[doc(alias = "ANDROID_BITMAP_FLAGS_ALPHA_UNPREMUL")] 314 | Unpremultiplied = ffi::ANDROID_BITMAP_FLAGS_ALPHA_UNPREMUL, 315 | 316 | #[doc(hidden)] 317 | #[num_enum(catch_all)] 318 | __Unknown(u32), 319 | } 320 | 321 | /// Bitfield containing information about the bitmap. 322 | #[cfg(feature = "api-level-30")] 323 | #[repr(transparent)] 324 | #[derive(Clone, Copy, Hash, PartialEq, Eq)] 325 | pub struct BitmapInfoFlags(u32); 326 | 327 | #[cfg(feature = "api-level-30")] 328 | impl fmt::Debug for BitmapInfoFlags { 329 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 330 | write!( 331 | f, 332 | "BitmapInfoFlags({:#x}, alpha: {:?}, is_hardware: {})", 333 | self.0, 334 | self.alpha(), 335 | self.is_hardware() 336 | ) 337 | } 338 | } 339 | 340 | #[cfg(feature = "api-level-30")] 341 | impl BitmapInfoFlags { 342 | /// Returns the alpha value contained in the [`ffi::ANDROID_BITMAP_FLAGS_ALPHA_MASK`] bit range 343 | #[doc(alias = "ANDROID_BITMAP_FLAGS_ALPHA_MASK")] 344 | pub fn alpha(self) -> BitmapInfoFlagsAlpha { 345 | // Note that ffi::ANDROID_BITMAP_FLAGS_ALPHA_SHIFT is 0 and hence irrelevant. 346 | (self.0 & ffi::ANDROID_BITMAP_FLAGS_ALPHA_MASK).into() 347 | } 348 | 349 | /// Returns [`true`] when [`ffi::ANDROID_BITMAP_FLAGS_IS_HARDWARE`] is set, meaning this 350 | /// [`Bitmap`] uses "HARDWARE Config" and its [`HardwareBufferRef`] can be retrieved via 351 | /// [`Bitmap::hardware_buffer()`]. 352 | #[doc(alias = "ANDROID_BITMAP_FLAGS_IS_HARDWARE")] 353 | pub fn is_hardware(self) -> bool { 354 | // This constant is defined in a separate anonymous enum which bindgen treats as i32. 355 | (self.0 & ffi::ANDROID_BITMAP_FLAGS_IS_HARDWARE as u32) != 0 356 | } 357 | } 358 | 359 | /// A native [`AndroidBitmapInfo`] 360 | /// 361 | /// [`AndroidBitmapInfo`]: https://developer.android.com/ndk/reference/struct/android-bitmap-info#struct_android_bitmap_info 362 | #[derive(Clone, Copy)] 363 | #[doc(alias = "AndroidBitmapInfo")] 364 | pub struct BitmapInfo { 365 | inner: ffi::AndroidBitmapInfo, 366 | } 367 | 368 | impl fmt::Debug for BitmapInfo { 369 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 370 | let mut f = f.debug_struct("BitmapInfo"); 371 | f.field("width", &self.width()) 372 | .field("height", &self.height()) 373 | .field("stride", &self.stride()) 374 | .field("format", &self.format()); 375 | 376 | #[cfg(feature = "api-level-30")] 377 | f.field("flags", &self.flags()); 378 | 379 | f.finish() 380 | } 381 | } 382 | 383 | impl BitmapInfo { 384 | pub fn new(width: u32, height: u32, stride: u32, format: BitmapFormat) -> Self { 385 | Self { 386 | inner: ffi::AndroidBitmapInfo { 387 | width, 388 | height, 389 | stride, 390 | format: format.into(), 391 | flags: 0, 392 | }, 393 | } 394 | } 395 | 396 | #[cfg(feature = "api-level-30")] 397 | pub fn new_with_flags( 398 | width: u32, 399 | height: u32, 400 | stride: u32, 401 | format: BitmapFormat, 402 | flags: BitmapInfoFlags, 403 | ) -> Self { 404 | Self { 405 | inner: ffi::AndroidBitmapInfo { 406 | flags: flags.0, 407 | ..Self::new(width, height, stride, format).inner 408 | }, 409 | } 410 | } 411 | 412 | /// The bitmap width in pixels. 413 | pub fn width(&self) -> u32 { 414 | self.inner.width 415 | } 416 | 417 | /// The bitmap height in pixels. 418 | pub fn height(&self) -> u32 { 419 | self.inner.height 420 | } 421 | 422 | /// The number of byte per row. 423 | pub fn stride(&self) -> u32 { 424 | self.inner.stride 425 | } 426 | 427 | /// Convert the internal, native [`ffi::AndroidBitmapInfo::format`] into a [`BitmapFormat`]. 428 | pub fn format(&self) -> BitmapFormat { 429 | self.inner.format.into() 430 | } 431 | 432 | /// Bitfield containing information about the bitmap. 433 | #[cfg(feature = "api-level-30")] 434 | pub fn flags(&self) -> BitmapInfoFlags { 435 | BitmapInfoFlags(self.inner.flags) 436 | } 437 | } 438 | 439 | /// Specifies the formats that can be compressed to with [`Bitmap::compress()`] and 440 | /// [`Bitmap::compress_raw()`]. 441 | #[cfg(feature = "api-level-30")] 442 | #[repr(i32)] 443 | #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, FromPrimitive, IntoPrimitive)] 444 | #[doc(alias = "AndroidBitmapCompressFormat")] 445 | #[non_exhaustive] 446 | pub enum BitmapCompressFormat { 447 | /// Compress to the JPEG format. 448 | /// 449 | /// quality of `0` means compress for the smallest size. `100` means compress for max visual 450 | /// quality. 451 | #[doc(alias = "ANDROID_BITMAP_COMPRESS_FORMAT_JPEG")] 452 | Jpeg = ffi::AndroidBitmapCompressFormat::ANDROID_BITMAP_COMPRESS_FORMAT_JPEG.0 as i32, 453 | /// Compress to the PNG format. 454 | /// 455 | /// PNG is lossless, so quality is ignored. 456 | #[doc(alias = "ANDROID_BITMAP_COMPRESS_FORMAT_PNG")] 457 | Png = ffi::AndroidBitmapCompressFormat::ANDROID_BITMAP_COMPRESS_FORMAT_PNG.0 as i32, 458 | /// Compress to the WEBP lossless format. 459 | /// 460 | /// quality refers to how much effort to put into compression. A value of `0` means to 461 | /// compress quickly, resulting in a relatively large file size. `100` means to spend more time 462 | /// compressing, resulting in a smaller file. 463 | #[doc(alias = "ANDROID_BITMAP_COMPRESS_FORMAT_WEBP_LOSSY")] 464 | WebPLossy = 465 | ffi::AndroidBitmapCompressFormat::ANDROID_BITMAP_COMPRESS_FORMAT_WEBP_LOSSY.0 as i32, 466 | /// Compress to the WEBP lossy format. 467 | /// 468 | /// quality of `0` means compress for the smallest size. `100` means compress for max visual quality. 469 | #[doc(alias = "ANDROID_BITMAP_COMPRESS_FORMAT_WEBP_LOSSLESS")] 470 | WebPLossless = 471 | ffi::AndroidBitmapCompressFormat::ANDROID_BITMAP_COMPRESS_FORMAT_WEBP_LOSSLESS.0 as i32, 472 | 473 | #[doc(hidden)] 474 | #[num_enum(catch_all)] 475 | __Unknown(i32), 476 | } 477 | 478 | /// Encapsulates possible errors returned by [`Bitmap::compress()`] or [`Bitmap::compress_raw()`]. 479 | #[cfg(feature = "api-level-30")] 480 | #[derive(Debug, thiserror::Error)] 481 | pub enum BitmapCompressError { 482 | #[error(transparent)] 483 | BitmapError(#[from] BitmapError), 484 | /// [`Bitmap`] compression requires a known [`DataSpace`]. [`DataSpace::Unknown`] is invalid 485 | /// even though it is typically treated as `sRGB`, for that [`DataSpace::Srgb`] has to be passed 486 | /// explicitly. 487 | #[error("The dataspace for this Bitmap is Unknown")] 488 | DataSpaceUnknown, 489 | } 490 | -------------------------------------------------------------------------------- /ndk/src/hardware_buffer_format.rs: -------------------------------------------------------------------------------- 1 | //! Bindings for [`AHardwareBuffer_Format`] 2 | //! 3 | //! [`AHardwareBuffer_Format`]: https://developer.android.com/ndk/reference/group/a-hardware-buffer#ahardwarebuffer_format 4 | 5 | use num_enum::{FromPrimitive, IntoPrimitive}; 6 | 7 | /// Buffer pixel formats. 8 | #[repr(i32)] 9 | #[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, IntoPrimitive)] 10 | #[allow(non_camel_case_types)] 11 | #[non_exhaustive] 12 | pub enum HardwareBufferFormat { 13 | /// Matches deprecated [`ffi::ANativeWindow_LegacyFormat::WINDOW_FORMAT_RGBA_8888`].0. 14 | #[doc(alias = "AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM")] 15 | R8G8B8A8_UNORM = ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM.0 as i32, 16 | /// Matches deprecated [`ffi::ANativeWindow_LegacyFormat::WINDOW_FORMAT_RGBX_8888`].0. 17 | #[doc(alias = "AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM")] 18 | R8G8B8X8_UNORM = ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8X8_UNORM.0 as i32, 19 | #[cfg(feature = "api-level-26")] 20 | #[doc(alias = "AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM")] 21 | R8G8B8_UNORM = ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8G8B8_UNORM.0 as i32, 22 | /// Matches deprecated [`ffi::ANativeWindow_LegacyFormat::WINDOW_FORMAT_RGB_565`].0. 23 | #[doc(alias = "AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM")] 24 | R5G6B5_UNORM = ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM.0 as i32, 25 | #[cfg(feature = "api-level-26")] 26 | #[doc(alias = "AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT")] 27 | R16G16B16A16_FLOAT = 28 | ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R16G16B16A16_FLOAT.0 as i32, 29 | #[cfg(feature = "api-level-26")] 30 | #[doc(alias = "AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM")] 31 | R10G10B10A2_UNORM = 32 | ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R10G10B10A2_UNORM.0 as i32, 33 | #[cfg(feature = "api-level-26")] 34 | #[doc(alias = "AHARDWAREBUFFER_FORMAT_BLOB")] 35 | BLOB = ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_BLOB.0 as i32, 36 | #[cfg(feature = "api-level-26")] 37 | #[doc(alias = "AHARDWAREBUFFER_FORMAT_D16_UNORM")] 38 | D16_UNORM = ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_D16_UNORM.0 as i32, 39 | #[cfg(feature = "api-level-26")] 40 | #[doc(alias = "AHARDWAREBUFFER_FORMAT_D24_UNORM")] 41 | D24_UNORM = ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_D24_UNORM.0 as i32, 42 | #[cfg(feature = "api-level-26")] 43 | #[doc(alias = "AHARDWAREBUFFER_FORMAT_D24_UNORM_S8_UINT")] 44 | D24_UNORM_S8_UINT = 45 | ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_D24_UNORM_S8_UINT.0 as i32, 46 | #[cfg(feature = "api-level-26")] 47 | #[doc(alias = "AHARDWAREBUFFER_FORMAT_D32_FLOAT")] 48 | D32_FLOAT = ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_D32_FLOAT.0 as i32, 49 | #[cfg(feature = "api-level-26")] 50 | #[doc(alias = "AHARDWAREBUFFER_FORMAT_D32_FLOAT_S8_UINT")] 51 | D32_FLOAT_S8_UINT = 52 | ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_D32_FLOAT_S8_UINT.0 as i32, 53 | #[cfg(feature = "api-level-26")] 54 | #[doc(alias = "AHARDWAREBUFFER_FORMAT_S8_UINT")] 55 | S8_UINT = ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_S8_UINT.0 as i32, 56 | #[cfg(feature = "api-level-26")] 57 | #[doc(alias = "AHARDWAREBUFFER_FORMAT_Y8Cb8Cr8_420")] 58 | Y8Cb8Cr8_420 = ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_Y8Cb8Cr8_420.0 as i32, 59 | #[cfg(feature = "api-level-26")] 60 | #[doc(alias = "AHARDWAREBUFFER_FORMAT_YCbCr_P010")] 61 | YCbCr_P010 = ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_YCbCr_P010.0 as i32, 62 | #[cfg(feature = "api-level-26")] 63 | R8_UNORM = ffi::AHardwareBuffer_Format::AHARDWAREBUFFER_FORMAT_R8_UNORM.0 as i32, 64 | 65 | #[doc(hidden)] 66 | #[num_enum(catch_all)] 67 | __Unknown(i32), 68 | } 69 | 70 | impl HardwareBufferFormat { 71 | /// Returns [`None`] when there is no immediate byte size available for this format, for 72 | /// example on planar buffer formats. 73 | pub fn bytes_per_pixel(self) -> Option { 74 | Some(match self { 75 | Self::R8G8B8A8_UNORM | Self::R8G8B8X8_UNORM => 4, 76 | #[cfg(feature = "api-level-26")] 77 | Self::R8G8B8_UNORM => 3, 78 | Self::R5G6B5_UNORM => 2, 79 | #[cfg(feature = "api-level-26")] 80 | Self::R16G16B16A16_FLOAT => 8, 81 | #[cfg(feature = "api-level-26")] 82 | Self::R10G10B10A2_UNORM => 4, 83 | #[cfg(feature = "api-level-26")] 84 | Self::BLOB => 1, 85 | #[cfg(feature = "api-level-26")] 86 | Self::D16_UNORM => 2, 87 | #[cfg(feature = "api-level-26")] 88 | Self::D24_UNORM => 3, 89 | #[cfg(feature = "api-level-26")] 90 | Self::D24_UNORM_S8_UINT => 4, 91 | #[cfg(feature = "api-level-26")] 92 | Self::D32_FLOAT => 4, 93 | #[cfg(feature = "api-level-26")] 94 | Self::D32_FLOAT_S8_UINT => 5, 95 | #[cfg(feature = "api-level-26")] 96 | Self::S8_UINT => 1, 97 | #[cfg(feature = "api-level-26")] 98 | Self::Y8Cb8Cr8_420 => 3, 99 | #[cfg(feature = "api-level-26")] 100 | Self::YCbCr_P010 => return None, 101 | #[cfg(feature = "api-level-26")] 102 | Self::R8_UNORM => 1, 103 | Self::__Unknown(_) => return None, 104 | }) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /ndk/src/input_queue.rs: -------------------------------------------------------------------------------- 1 | //! Bindings for [`AInputQueue`] 2 | //! 3 | //! [`AInputQueue`]: https://developer.android.com/ndk/reference/group/input#ainputqueue 4 | 5 | use std::io::Result; 6 | use std::os::raw::c_int; 7 | use std::ptr::{self, NonNull}; 8 | 9 | #[cfg(feature = "api-level-33")] 10 | use jni_sys::{jobject, JNIEnv}; 11 | 12 | use crate::event::InputEvent; 13 | #[cfg(doc)] 14 | use crate::event::KeyEvent; 15 | use crate::looper::ForeignLooper; 16 | use crate::utils::status_to_io_result; 17 | 18 | /// A native [`AInputQueue *`] 19 | /// 20 | /// An input queue is the facility through which you retrieve input events. 21 | /// 22 | /// [`AInputQueue *`]: https://developer.android.com/ndk/reference/group/input#ainputqueue 23 | #[derive(Debug)] 24 | pub struct InputQueue { 25 | ptr: NonNull, 26 | } 27 | 28 | // It gets shared between threads in `ndk-glue` 29 | unsafe impl Send for InputQueue {} 30 | unsafe impl Sync for InputQueue {} 31 | 32 | impl InputQueue { 33 | /// Construct an [`InputQueue`] from the native pointer. 34 | /// 35 | /// # Safety 36 | /// By calling this function, you assert that the pointer is a valid pointer to an NDK [`ffi::AInputQueue`]. 37 | pub unsafe fn from_ptr(ptr: NonNull) -> Self { 38 | Self { ptr } 39 | } 40 | 41 | /// Returns the [`InputQueue`] object associated with the supplied 42 | /// [Java `InputQueue`][`android.view.InputQueue`] object. 43 | /// 44 | /// # Safety 45 | /// 46 | /// This function should be called with a healthy JVM pointer and with a non-null 47 | /// [`android.view.InputQueue`], which must be kept alive on the Java/Kotlin side. 48 | /// 49 | /// The returned native object holds a weak reference to the Java object, and is only valid as 50 | /// long as the Java object has not yet been disposed. You should ensure that there is a strong 51 | /// reference to the Java object and that it has not been disposed before using the returned 52 | /// object. 53 | /// 54 | /// [`android.view.InputQueue`]: https://developer.android.com/reference/android/view/InputQueue 55 | #[cfg(feature = "api-level-33")] 56 | #[doc(alias = "AInputQueue_fromJava")] 57 | pub unsafe fn from_java(env: *mut JNIEnv, input_queue: jobject) -> Option { 58 | let ptr = unsafe { ffi::AInputQueue_fromJava(env, input_queue) }; 59 | Some(Self::from_ptr(NonNull::new(ptr)?)) 60 | } 61 | 62 | pub fn ptr(&self) -> NonNull { 63 | self.ptr 64 | } 65 | 66 | /// Returns the next available [`InputEvent`] from the queue. 67 | /// 68 | /// Returns [`None`] if no event is available. 69 | #[doc(alias = "AInputQueue_getEvent")] 70 | pub fn event(&self) -> Result> { 71 | let mut out_event = ptr::null_mut(); 72 | let status = unsafe { ffi::AInputQueue_getEvent(self.ptr.as_ptr(), &mut out_event) }; 73 | match status_to_io_result(status) { 74 | Ok(()) => { 75 | debug_assert!(!out_event.is_null()); 76 | Ok(Some(unsafe { 77 | InputEvent::from_ptr(NonNull::new_unchecked(out_event)) 78 | })) 79 | } 80 | Err(e) if e.kind() == std::io::ErrorKind::WouldBlock => Ok(None), 81 | Err(e) => Err(e), 82 | } 83 | } 84 | 85 | /// Returns [`true`] if there are one or more events available in the input queue. 86 | #[doc(alias = "AInputQueue_hasEvents")] 87 | pub fn has_events(&self) -> bool { 88 | match unsafe { ffi::AInputQueue_hasEvents(self.ptr.as_ptr()) } { 89 | 0 => false, 90 | 1 => true, 91 | r => unreachable!("AInputQueue_hasEvents returned non-boolean {}", r), 92 | } 93 | } 94 | 95 | /// Sends the key for standard pre-dispatching that is, possibly deliver it to the current IME 96 | /// to be consumed before the app. 97 | /// 98 | /// Returns [`Some`] if it was not pre-dispatched, meaning you can process it right now. If 99 | /// [`None`] is returned, you must abandon the current event processing and allow the event to 100 | /// appear again in the event queue (if it does not get consumed during pre-dispatching). 101 | /// 102 | /// Also returns [`None`] if `event` is not a [`KeyEvent`]. 103 | #[doc(alias = "AInputQueue_preDispatchEvent")] 104 | pub fn pre_dispatch(&self, event: InputEvent) -> Option { 105 | match unsafe { ffi::AInputQueue_preDispatchEvent(self.ptr.as_ptr(), event.ptr().as_ptr()) } 106 | { 107 | 0 => Some(event), 108 | _ => None, 109 | } 110 | } 111 | 112 | /// Report that dispatching has finished with the given [`InputEvent`]. 113 | /// 114 | /// This must be called after receiving an event with [`InputQueue::event()`]. 115 | #[doc(alias = "AInputQueue_finishEvent")] 116 | pub fn finish_event(&self, event: InputEvent, handled: bool) { 117 | unsafe { 118 | ffi::AInputQueue_finishEvent(self.ptr.as_ptr(), event.ptr().as_ptr(), handled as c_int) 119 | } 120 | } 121 | 122 | /// Add this input queue to a [`ForeignLooper`] for processing. 123 | /// 124 | /// See [`ForeignLooper::add_fd()`] for information on the `ident`, `callback`, and `data` params. 125 | #[doc(alias = "AInputQueue_attachLooper")] 126 | pub fn attach_looper(&self, looper: &ForeignLooper, id: i32) { 127 | unsafe { 128 | ffi::AInputQueue_attachLooper( 129 | self.ptr.as_ptr(), 130 | looper.ptr().as_ptr(), 131 | id, 132 | None, 133 | ptr::null_mut(), 134 | ) 135 | } 136 | } 137 | 138 | /// Remove this input queue from the [`ForeignLooper`] it is currently attached to. 139 | #[doc(alias = "AInputQueue_detachLooper")] 140 | pub fn detach_looper(&self) { 141 | unsafe { ffi::AInputQueue_detachLooper(self.ptr.as_ptr()) } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /ndk/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! # Android NDK 2 | //! 3 | //! Bindings to the [Android NDK]. 4 | //! 5 | //! [Android NDK]: https://developer.android.com/ndk/reference 6 | #![warn( 7 | missing_debug_implementations, 8 | rust_2018_idioms, 9 | trivial_casts, 10 | unused_qualifications 11 | )] 12 | #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] 13 | 14 | pub mod asset; 15 | pub mod audio; 16 | pub mod bitmap; 17 | pub mod configuration; 18 | pub mod data_space; 19 | pub mod event; 20 | pub mod font; 21 | pub mod hardware_buffer; 22 | pub mod hardware_buffer_format; 23 | pub mod input_queue; 24 | pub mod looper; 25 | pub mod media; 26 | pub mod media_error; 27 | pub mod native_activity; 28 | pub mod native_window; 29 | pub mod performance_hint; 30 | pub mod shared_memory; 31 | pub mod surface_texture; 32 | pub mod sync; 33 | pub mod trace; 34 | mod utils; 35 | -------------------------------------------------------------------------------- /ndk/src/looper.rs: -------------------------------------------------------------------------------- 1 | //! Bindings for [`ALooper`] 2 | //! 3 | //! In Android, [`ALooper`]s are inherently thread-local. Due to this, there are two different 4 | //! [`ALooper`] interfaces exposed in this module: 5 | //! 6 | //! * [`ThreadLooper`], which has methods for the operations performable with a looper in one's own 7 | //! thread; and 8 | //! * [`ForeignLooper`], which has methods for the operations performable with any thread's looper. 9 | //! 10 | //! [`ALooper`]: https://developer.android.com/ndk/reference/group/looper#alooper 11 | 12 | use std::mem::ManuallyDrop; 13 | use std::os::{ 14 | fd::{AsRawFd, BorrowedFd, RawFd}, 15 | raw::c_void, 16 | }; 17 | use std::ptr; 18 | use std::time::Duration; 19 | use thiserror::Error; 20 | 21 | use crate::utils::abort_on_panic; 22 | 23 | /// A thread-local native [`ALooper *`]. This promises that there is a looper associated with the 24 | /// current thread. 25 | /// 26 | /// [`ALooper *`]: https://developer.android.com/ndk/reference/group/looper#alooper 27 | #[derive(Debug)] 28 | pub struct ThreadLooper { 29 | _marker: std::marker::PhantomData<*mut ()>, // Not send or sync 30 | foreign: ForeignLooper, 31 | } 32 | 33 | bitflags::bitflags! { 34 | /// Flags for file descriptor events that a looper can monitor. 35 | /// 36 | /// These flag bits can be combined to monitor multiple events at once. 37 | #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] 38 | pub struct FdEvent : u32 { 39 | /// The file descriptor is available for read operations. 40 | #[doc(alias = "ALOOPER_EVENT_INPUT")] 41 | const INPUT = ffi::ALOOPER_EVENT_INPUT; 42 | /// The file descriptor is available for write operations. 43 | #[doc(alias = "ALOOPER_EVENT_OUTPUT")] 44 | const OUTPUT = ffi::ALOOPER_EVENT_OUTPUT; 45 | /// The file descriptor has encountered an error condition. 46 | /// 47 | /// The looper always sends notifications about errors; it is not necessary to specify this 48 | /// event flag in the requested event set. 49 | #[doc(alias = "ALOOPER_EVENT_ERROR")] 50 | const ERROR = ffi::ALOOPER_EVENT_ERROR; 51 | /// The file descriptor was hung up. 52 | /// 53 | /// For example, indicates that the remote end of a pipe or socket was closed. 54 | /// 55 | /// The looper always sends notifications about hangups; it is not necessary to specify this 56 | /// event flag in the requested event set. 57 | #[doc(alias = "ALOOPER_EVENT_HANGUP")] 58 | const HANGUP = ffi::ALOOPER_EVENT_HANGUP; 59 | /// The file descriptor is invalid. 60 | /// 61 | /// For example, the file descriptor was closed prematurely. 62 | /// 63 | /// The looper always sends notifications about invalid file descriptors; it is not 64 | /// necessary to specify this event flag in the requested event set. 65 | #[doc(alias = "ALOOPER_EVENT_INVALID")] 66 | const INVALID = ffi::ALOOPER_EVENT_INVALID; 67 | 68 | // https://docs.rs/bitflags/latest/bitflags/#externally-defined-flags 69 | const _ = !0; 70 | } 71 | } 72 | 73 | /// The poll result from a [`ThreadLooper`]. 74 | #[derive(Debug)] 75 | pub enum Poll<'fd> { 76 | /// This looper was woken using [`ForeignLooper::wake()`] 77 | Wake, 78 | /// For [`ThreadLooper::poll_once*()`][ThreadLooper::poll_once()], an event was received and processed using a callback. 79 | Callback, 80 | /// For [`ThreadLooper::poll_*_timeout()`][ThreadLooper::poll_once_timeout()], the requested timeout was reached before any events. 81 | Timeout, 82 | /// An event was received 83 | Event { 84 | ident: i32, 85 | /// # Safety 86 | /// The caller should guarantee that this file descriptor remains open after it was added 87 | /// via [`ForeignLooper::add_fd()`] or [`ForeignLooper::add_fd_with_callback()`]. 88 | fd: BorrowedFd<'fd>, 89 | events: FdEvent, 90 | data: *mut c_void, 91 | }, 92 | } 93 | 94 | #[derive(Debug, Copy, Clone, Error)] 95 | #[error("Android Looper error")] 96 | pub struct LooperError; 97 | 98 | impl ThreadLooper { 99 | /// Prepares a looper for the current thread and returns it 100 | pub fn prepare() -> Self { 101 | unsafe { 102 | let ptr = ffi::ALooper_prepare(ffi::ALOOPER_PREPARE_ALLOW_NON_CALLBACKS as _); 103 | let foreign = ForeignLooper::from_ptr(ptr::NonNull::new(ptr).expect("looper non null")); 104 | Self { 105 | _marker: std::marker::PhantomData, 106 | foreign, 107 | } 108 | } 109 | } 110 | 111 | /// Returns the looper associated with the current thread, if any. 112 | pub fn for_thread() -> Option { 113 | Some(Self { 114 | _marker: std::marker::PhantomData, 115 | foreign: ForeignLooper::for_thread()?, 116 | }) 117 | } 118 | 119 | /// Polls the looper, blocking on processing an event, but with a timeout in milliseconds. 120 | /// Give a timeout of `0` to make this non-blocking. 121 | fn poll_once_ms(&self, ms: i32) -> Result, LooperError> { 122 | let mut fd = -1; 123 | let mut events = -1; 124 | let mut data: *mut c_void = ptr::null_mut(); 125 | match unsafe { ffi::ALooper_pollOnce(ms, &mut fd, &mut events, &mut data) } { 126 | ffi::ALOOPER_POLL_WAKE => Ok(Poll::Wake), 127 | ffi::ALOOPER_POLL_CALLBACK => Ok(Poll::Callback), 128 | ffi::ALOOPER_POLL_TIMEOUT => Ok(Poll::Timeout), 129 | ffi::ALOOPER_POLL_ERROR => Err(LooperError), 130 | ident if ident >= 0 => Ok(Poll::Event { 131 | ident, 132 | // SAFETY: Even though this FD at least shouldn't outlive self, a user could have 133 | // closed it after calling add_fd or add_fd_with_callback. 134 | fd: unsafe { BorrowedFd::borrow_raw(fd) }, 135 | events: FdEvent::from_bits(events as u32) 136 | .expect("poll event contains unknown bits"), 137 | data, 138 | }), 139 | _ => unreachable!(), 140 | } 141 | } 142 | 143 | /// Polls the looper, blocking on processing an event. 144 | #[inline] 145 | pub fn poll_once(&self) -> Result, LooperError> { 146 | self.poll_once_ms(-1) 147 | } 148 | 149 | /// Polls the looper, blocking on processing an event, but with a timeout. Give a timeout of 150 | /// [`Duration::ZERO`] to make this non-blocking. 151 | /// 152 | /// It panics if the timeout is larger than expressible as an [`i32`] of milliseconds (roughly 25 153 | /// days). 154 | #[inline] 155 | pub fn poll_once_timeout(&self, timeout: Duration) -> Result, LooperError> { 156 | self.poll_once_ms( 157 | timeout 158 | .as_millis() 159 | .try_into() 160 | .expect("Supplied timeout is too large"), 161 | ) 162 | } 163 | 164 | /// Repeatedly polls the looper, blocking on processing an event, but with a timeout in 165 | /// milliseconds. Give a timeout of `0` to make this non-blocking. 166 | /// 167 | /// This function will never return [`Poll::Callback`]. 168 | fn poll_all_ms(&self, ms: i32) -> Result, LooperError> { 169 | let mut fd = -1; 170 | let mut events = -1; 171 | let mut data: *mut c_void = ptr::null_mut(); 172 | match unsafe { ffi::ALooper_pollAll(ms, &mut fd, &mut events, &mut data) } { 173 | ffi::ALOOPER_POLL_WAKE => Ok(Poll::Wake), 174 | ffi::ALOOPER_POLL_TIMEOUT => Ok(Poll::Timeout), 175 | ffi::ALOOPER_POLL_ERROR => Err(LooperError), 176 | ident if ident >= 0 => Ok(Poll::Event { 177 | ident, 178 | // SAFETY: Even though this FD at least shouldn't outlive self, a user could have 179 | // closed it after calling add_fd or add_fd_with_callback. 180 | fd: unsafe { BorrowedFd::borrow_raw(fd) }, 181 | events: FdEvent::from_bits(events as u32) 182 | .expect("poll event contains unknown bits"), 183 | data, 184 | }), 185 | _ => unreachable!(), 186 | } 187 | } 188 | 189 | /// Repeatedly polls the looper, blocking on processing an event. 190 | /// 191 | /// This function will never return [`Poll::Callback`]. 192 | #[inline] 193 | pub fn poll_all(&self) -> Result, LooperError> { 194 | self.poll_all_ms(-1) 195 | } 196 | 197 | /// Repeatedly polls the looper, blocking on processing an event, but with a timeout. Give a 198 | /// timeout of [`Duration::ZERO`] to make this non-blocking. 199 | /// 200 | /// This function will never return [`Poll::Callback`]. 201 | /// 202 | /// It panics if the timeout is larger than expressible as an [`i32`] of milliseconds (roughly 25 203 | /// days). 204 | #[inline] 205 | pub fn poll_all_timeout(&self, timeout: Duration) -> Result, LooperError> { 206 | self.poll_all_ms( 207 | timeout 208 | .as_millis() 209 | .try_into() 210 | .expect("Supplied timeout is too large"), 211 | ) 212 | } 213 | 214 | /// Adds a file descriptor to be polled, with a callback that is invoked when any of the 215 | /// [`FdEvent`]s described in `events` is triggered. 216 | /// 217 | /// The callback receives the file descriptor it is associated with and a bitmask of the poll 218 | /// events that were triggered (typically [`FdEvent::INPUT`]). It should return [`true`] to 219 | /// continue receiving callbacks, or [`false`] to have the callback unregistered. 220 | /// 221 | /// See also [the NDK 222 | /// docs](https://developer.android.com/ndk/reference/group/looper.html#alooper_addfd). 223 | /// 224 | /// Note that this will leak a [`Box`] unless the callback returns [`false`] to unregister 225 | /// itself. 226 | /// 227 | /// # Threading 228 | /// This function will be called on the current thread when this [`ThreadLooper`] is 229 | /// polled. A callback can also be registered from other threads via the equivalent 230 | /// [`ForeignLooper::add_fd_with_callback()`] function, which requires a [`Send`] bound. 231 | /// 232 | /// # Safety 233 | /// The caller should guarantee that this file descriptor stays open until it is removed via 234 | /// [`remove_fd()`][ForeignLooper::remove_fd()] or by returning [`false`] from the callback, 235 | /// and for however long the caller wishes to use this file descriptor inside and after the 236 | /// callback. 237 | #[doc(alias = "ALooper_addFd")] 238 | pub fn add_fd_with_callback, FdEvent) -> bool>( 239 | &self, 240 | fd: BorrowedFd<'_>, 241 | events: FdEvent, 242 | callback: F, 243 | ) -> Result<(), LooperError> { 244 | unsafe { 245 | self.foreign 246 | .add_fd_with_callback_assume_send(fd, events, callback) 247 | } 248 | } 249 | 250 | /// Returns a reference to the [`ForeignLooper`] that is associated with the current thread. 251 | pub fn as_foreign(&self) -> &ForeignLooper { 252 | &self.foreign 253 | } 254 | 255 | pub fn into_foreign(self) -> ForeignLooper { 256 | self.foreign 257 | } 258 | } 259 | 260 | /// A native [`ALooper *`], not necessarily allocated with the current thread. 261 | /// 262 | /// [`ALooper *`]: https://developer.android.com/ndk/reference/group/looper#alooper 263 | #[derive(Debug)] 264 | pub struct ForeignLooper { 265 | ptr: ptr::NonNull, 266 | } 267 | 268 | unsafe impl Send for ForeignLooper {} 269 | unsafe impl Sync for ForeignLooper {} 270 | 271 | impl Drop for ForeignLooper { 272 | fn drop(&mut self) { 273 | unsafe { ffi::ALooper_release(self.ptr.as_ptr()) } 274 | } 275 | } 276 | 277 | impl Clone for ForeignLooper { 278 | fn clone(&self) -> Self { 279 | unsafe { 280 | ffi::ALooper_acquire(self.ptr.as_ptr()); 281 | Self { ptr: self.ptr } 282 | } 283 | } 284 | } 285 | 286 | impl ForeignLooper { 287 | /// Returns the looper associated with the current thread, if any. 288 | #[inline] 289 | pub fn for_thread() -> Option { 290 | ptr::NonNull::new(unsafe { ffi::ALooper_forThread() }) 291 | .map(|ptr| unsafe { Self::from_ptr(ptr) }) 292 | } 293 | 294 | /// Construct a [`ForeignLooper`] object from the given pointer. 295 | /// 296 | /// # Safety 297 | /// By calling this function, you guarantee that the pointer is a valid, non-null pointer to an 298 | /// NDK [`ffi::ALooper`]. 299 | #[inline] 300 | pub unsafe fn from_ptr(ptr: ptr::NonNull) -> Self { 301 | ffi::ALooper_acquire(ptr.as_ptr()); 302 | Self { ptr } 303 | } 304 | 305 | /// Returns a pointer to the NDK `ALooper` object. 306 | #[inline] 307 | pub fn ptr(&self) -> ptr::NonNull { 308 | self.ptr 309 | } 310 | 311 | /// Wakes the looper. An event of [`Poll::Wake`] will be sent. 312 | pub fn wake(&self) { 313 | unsafe { ffi::ALooper_wake(self.ptr.as_ptr()) } 314 | } 315 | 316 | /// Adds a file descriptor to be polled, without a callback. 317 | /// 318 | /// See also [the NDK 319 | /// docs](https://developer.android.com/ndk/reference/group/looper.html#alooper_addfd). 320 | /// 321 | /// # Safety 322 | /// The caller should guarantee that this file descriptor stays open until it is removed via 323 | /// [`remove_fd()`][Self::remove_fd()], and for however long the caller wishes to use this file 324 | /// descriptor when it is returned in [`Poll::Event::fd`]. 325 | // 326 | // `ALooper_addFd` won't dereference `data`; it will only pass it on to the event. 327 | // Optionally dereferencing it there already enforces `unsafe` context. 328 | #[allow(clippy::not_unsafe_ptr_arg_deref)] 329 | pub fn add_fd( 330 | &self, 331 | fd: BorrowedFd<'_>, 332 | ident: i32, 333 | events: FdEvent, 334 | data: *mut c_void, 335 | ) -> Result<(), LooperError> { 336 | match unsafe { 337 | ffi::ALooper_addFd( 338 | self.ptr.as_ptr(), 339 | fd.as_raw_fd(), 340 | ident, 341 | events.bits() as i32, 342 | None, 343 | data, 344 | ) 345 | } { 346 | 1 => Ok(()), 347 | -1 => Err(LooperError), 348 | _ => unreachable!(), 349 | } 350 | } 351 | 352 | /// Adds a file descriptor to be polled, with a callback that is invoked when any of the 353 | /// [`FdEvent`]s described in `events` is triggered. 354 | /// 355 | /// The callback receives the file descriptor it is associated with and a bitmask of the poll 356 | /// events that were triggered (typically [`FdEvent::INPUT`]). It should return [`true`] to 357 | /// continue receiving callbacks, or [`false`] to have the callback unregistered. 358 | /// 359 | /// See also [the NDK 360 | /// docs](https://developer.android.com/ndk/reference/group/looper.html#alooper_addfd). 361 | /// 362 | /// Note that this will leak a [`Box`] unless the callback returns [`false`] to unregister 363 | /// itself. 364 | /// 365 | /// # Threading 366 | /// This function will be called on the looper thread where and when it is polled. 367 | /// For registering callbacks without [`Send`] requirement, call the equivalent 368 | /// [`ThreadLooper::add_fd_with_callback()`] function on the Looper thread. 369 | /// 370 | /// # Safety 371 | /// The caller should guarantee that this file descriptor stays open until it is removed via 372 | /// [`remove_fd()`][Self::remove_fd()] or by returning [`false`] from the callback, and for 373 | /// however long the caller wishes to use this file descriptor inside and after the callback. 374 | #[doc(alias = "ALooper_addFd")] 375 | pub fn add_fd_with_callback, FdEvent) -> bool + Send>( 376 | &self, 377 | fd: BorrowedFd<'_>, 378 | events: FdEvent, 379 | callback: F, 380 | ) -> Result<(), LooperError> { 381 | unsafe { self.add_fd_with_callback_assume_send(fd, events, callback) } 382 | } 383 | 384 | /// Private helper to deduplicate/commonize the implementation behind 385 | /// [`ForeignLooper::add_fd_with_callback()`] and [`ThreadLooper::add_fd_with_callback()`], 386 | /// as both have their own way of guaranteeing thread-safety. The former, [`ForeignLooper`], 387 | /// requires the closure to be [`Send`]. The latter, [`ThreadLooper`], can only exist on the 388 | /// thread where polling happens and where the closure will end up being invoked, and does not 389 | /// require [`Send`]. 390 | /// 391 | /// # Safety 392 | /// The caller must guarantee that `F` is [`Send`] or that `F` will only run on the current 393 | /// thread. See the explanation above about why this function exists. 394 | unsafe fn add_fd_with_callback_assume_send, FdEvent) -> bool>( 395 | &self, 396 | fd: BorrowedFd<'_>, 397 | events: FdEvent, 398 | callback: F, 399 | ) -> Result<(), LooperError> { 400 | extern "C" fn cb_handler, FdEvent) -> bool>( 401 | fd: RawFd, 402 | events: i32, 403 | data: *mut c_void, 404 | ) -> i32 { 405 | abort_on_panic(|| unsafe { 406 | let mut cb = ManuallyDrop::new(Box::::from_raw(data as *mut _)); 407 | let events = FdEvent::from_bits_retain( 408 | events.try_into().expect("Unexpected sign bit in `events`"), 409 | ); 410 | let keep_registered = cb(BorrowedFd::borrow_raw(fd), events); 411 | if !keep_registered { 412 | ManuallyDrop::into_inner(cb); 413 | } 414 | keep_registered as i32 415 | }) 416 | } 417 | let data = Box::into_raw(Box::new(callback)) as *mut _; 418 | match unsafe { 419 | ffi::ALooper_addFd( 420 | self.ptr.as_ptr(), 421 | fd.as_raw_fd(), 422 | ffi::ALOOPER_POLL_CALLBACK, 423 | events.bits() as i32, 424 | Some(cb_handler::), 425 | data, 426 | ) 427 | } { 428 | 1 => Ok(()), 429 | -1 => Err(LooperError), 430 | _ => unreachable!(), 431 | } 432 | } 433 | 434 | /// Removes a previously added file descriptor from the looper. 435 | /// 436 | /// Returns [`true`] if the file descriptor was removed, [`false`] if it was not previously 437 | /// registered. 438 | /// 439 | /// # Safety 440 | /// When this method returns, it is safe to close the file descriptor since the looper will no 441 | /// longer have a reference to it. However, it is possible for the callback to already be 442 | /// running or for it to run one last time if the file descriptor was already signalled. 443 | /// Calling code is responsible for ensuring that this case is safely handled. For example, if 444 | /// the callback takes care of removing itself during its own execution either by returning `0` 445 | /// or by calling this method, then it can be guaranteed to not be invoked again at any later 446 | /// time unless registered anew. 447 | /// 448 | /// Note that unregistering a file descriptor with callback will leak a [`Box`] created in 449 | /// [`add_fd_with_callback()`][Self::add_fd_with_callback()]. Consider returning [`false`] 450 | /// from the callback instead to drop it. 451 | pub fn remove_fd(&self, fd: BorrowedFd<'_>) -> Result { 452 | match unsafe { ffi::ALooper_removeFd(self.ptr.as_ptr(), fd.as_raw_fd()) } { 453 | 1 => Ok(true), 454 | 0 => Ok(false), 455 | -1 => Err(LooperError), 456 | _ => unreachable!(), 457 | } 458 | } 459 | } 460 | -------------------------------------------------------------------------------- /ndk/src/media/image_reader.rs: -------------------------------------------------------------------------------- 1 | //! Bindings for [`AImageReader`] and [`AImage`] 2 | //! 3 | //! [`AImageReader`]: https://developer.android.com/ndk/reference/group/media#aimagereader 4 | //! [`AImage`]: https://developer.android.com/ndk/reference/group/media#aimage 5 | #![cfg(feature = "api-level-24")] 6 | 7 | #[cfg(feature = "api-level-26")] 8 | use std::os::fd::{FromRawFd, IntoRawFd, OwnedFd}; 9 | use std::{ffi::c_void, fmt, mem::MaybeUninit, ptr::NonNull}; 10 | 11 | use num_enum::{FromPrimitive, IntoPrimitive}; 12 | 13 | #[cfg(feature = "api-level-26")] 14 | use crate::hardware_buffer::{HardwareBuffer, HardwareBufferUsage}; 15 | use crate::media_error::{construct, construct_never_null, MediaError, Result}; 16 | use crate::native_window::NativeWindow; 17 | use crate::utils::abort_on_panic; 18 | #[cfg(feature = "api-level-34")] 19 | use crate::{data_space::DataSpace, hardware_buffer_format::HardwareBufferFormat}; 20 | 21 | #[repr(i32)] 22 | #[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, IntoPrimitive)] 23 | #[allow(non_camel_case_types)] 24 | #[non_exhaustive] 25 | #[doc(alias = "AIMAGE_FORMATS")] 26 | pub enum ImageFormat { 27 | RGBA_8888 = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_RGBA_8888.0 as i32, 28 | RGBX_8888 = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_RGBX_8888.0 as i32, 29 | RGB_888 = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_RGB_888.0 as i32, 30 | RGB_565 = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_RGB_565.0 as i32, 31 | RGBA_FP16 = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_RGBA_FP16.0 as i32, 32 | YUV_420_888 = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_YUV_420_888.0 as i32, 33 | JPEG = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_JPEG.0 as i32, 34 | RAW16 = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_RAW16.0 as i32, 35 | RAW_PRIVATE = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_RAW_PRIVATE.0 as i32, 36 | RAW10 = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_RAW10.0 as i32, 37 | RAW12 = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_RAW12.0 as i32, 38 | DEPTH16 = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_DEPTH16.0 as i32, 39 | DEPTH_POINT_CLOUD = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_DEPTH_POINT_CLOUD.0 as i32, 40 | PRIVATE = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_PRIVATE.0 as i32, 41 | Y8 = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_Y8.0 as i32, 42 | HEIC = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_HEIC.0 as i32, 43 | DEPTH_JPEG = ffi::AIMAGE_FORMATS::AIMAGE_FORMAT_DEPTH_JPEG.0 as i32, 44 | 45 | #[doc(hidden)] 46 | #[num_enum(catch_all)] 47 | __Unknown(i32), 48 | } 49 | 50 | #[doc(alias = "AImageReader_ImageCallback")] 51 | #[doc(alias = "AImageReader_ImageListener")] 52 | pub type ImageListener = Box; 53 | 54 | #[cfg(feature = "api-level-26")] 55 | #[doc(alias = "AImageReader_BufferRemovedCallback")] 56 | #[doc(alias = "AImageReader_BufferRemovedListener")] 57 | pub type BufferRemovedListener = Box; 58 | 59 | /// Result returned by: 60 | /// - [`ImageReader::acquire_next_image()`]` 61 | /// - [`ImageReader::acquire_next_image_async()`]` 62 | /// - [`ImageReader::acquire_latest_image()`]` 63 | /// - [`ImageReader::acquire_latest_image_async()`]` 64 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] 65 | pub enum AcquireResult { 66 | /// Returned if there is no buffers currently available in the reader queue. 67 | #[doc(alias = "AMEDIA_IMGREADER_NO_BUFFER_AVAILABLE")] 68 | NoBufferAvailable, 69 | /// Returned if the number of concurrently acquired images has reached the limit. 70 | #[doc(alias = "AMEDIA_IMGREADER_MAX_IMAGES_ACQUIRED")] 71 | MaxImagesAcquired, 72 | 73 | /// Returned if an [`Image`] (optionally with fence) was successfully acquired. 74 | Image(T), 75 | } 76 | 77 | impl AcquireResult { 78 | fn map(self, f: impl FnOnce(T) -> U) -> AcquireResult { 79 | match self { 80 | AcquireResult::Image(img) => AcquireResult::Image(f(img)), 81 | AcquireResult::NoBufferAvailable => AcquireResult::NoBufferAvailable, 82 | AcquireResult::MaxImagesAcquired => AcquireResult::MaxImagesAcquired, 83 | } 84 | } 85 | } 86 | 87 | impl AcquireResult { 88 | /// Inlined version of [`construct_never_null()`] with IMGREADER-specific result mapping. 89 | fn construct_never_null( 90 | with_ptr: impl FnOnce(*mut *mut ffi::AImage) -> ffi::media_status_t, 91 | ) -> Result { 92 | let mut result = MaybeUninit::uninit(); 93 | let status = with_ptr(result.as_mut_ptr()); 94 | match status { 95 | ffi::media_status_t::AMEDIA_IMGREADER_NO_BUFFER_AVAILABLE => { 96 | Ok(Self::NoBufferAvailable) 97 | } 98 | ffi::media_status_t::AMEDIA_IMGREADER_MAX_IMAGES_ACQUIRED => { 99 | Ok(Self::MaxImagesAcquired) 100 | } 101 | status => MediaError::from_status(status).map(|()| { 102 | let result = unsafe { result.assume_init() }; 103 | Self::Image(Image { 104 | inner: if cfg!(debug_assertions) { 105 | NonNull::new(result).expect("result should never be null") 106 | } else { 107 | unsafe { NonNull::new_unchecked(result) } 108 | }, 109 | }) 110 | }), 111 | } 112 | } 113 | } 114 | 115 | /// A native [`AImageReader *`] 116 | /// 117 | /// [`AImageReader *`]: https://developer.android.com/ndk/reference/group/media#aimagereader 118 | #[doc(alias = "AImageReader")] 119 | pub struct ImageReader { 120 | inner: NonNull, 121 | image_cb: Option>, 122 | #[cfg(feature = "api-level-26")] 123 | buffer_removed_cb: Option>, 124 | } 125 | 126 | impl fmt::Debug for ImageReader { 127 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 128 | f.debug_struct("ImageReader") 129 | .field("inner", &self.inner) 130 | .field( 131 | "image_cb", 132 | match &self.image_cb { 133 | Some(_) => &"Some(_)", 134 | None => &"None", 135 | }, 136 | ) 137 | .finish() 138 | } 139 | } 140 | 141 | impl ImageReader { 142 | fn from_ptr(inner: NonNull) -> Self { 143 | Self { 144 | inner, 145 | image_cb: None, 146 | #[cfg(feature = "api-level-26")] 147 | buffer_removed_cb: None, 148 | } 149 | } 150 | 151 | fn as_ptr(&self) -> *mut ffi::AImageReader { 152 | self.inner.as_ptr() 153 | } 154 | 155 | #[doc(alias = "AImageReader_new")] 156 | pub fn new(width: i32, height: i32, format: ImageFormat, max_images: i32) -> Result { 157 | let inner = construct_never_null(|res| unsafe { 158 | ffi::AImageReader_new(width, height, format.into(), max_images, res) 159 | })?; 160 | 161 | Ok(Self::from_ptr(inner)) 162 | } 163 | 164 | #[cfg(feature = "api-level-26")] 165 | #[doc(alias = "AImageReader_newWithUsage")] 166 | pub fn new_with_usage( 167 | width: i32, 168 | height: i32, 169 | format: ImageFormat, 170 | usage: HardwareBufferUsage, 171 | max_images: i32, 172 | ) -> Result { 173 | let inner = construct_never_null(|res| unsafe { 174 | ffi::AImageReader_newWithUsage( 175 | width, 176 | height, 177 | format.into(), 178 | usage.bits(), 179 | max_images, 180 | res, 181 | ) 182 | })?; 183 | 184 | Ok(Self::from_ptr(inner)) 185 | } 186 | 187 | #[cfg(feature = "api-level-34")] 188 | #[doc(alias = "AImageReader_newWithDataSpace")] 189 | pub fn new_with_data_space( 190 | width: i32, 191 | height: i32, 192 | usage: HardwareBufferUsage, 193 | max_images: i32, 194 | format: HardwareBufferFormat, 195 | data_space: DataSpace, 196 | ) -> Result { 197 | let inner = construct_never_null(|res| unsafe { 198 | ffi::AImageReader_newWithDataSpace( 199 | width, 200 | height, 201 | usage.bits(), 202 | max_images, 203 | i32::from(format) 204 | .try_into() 205 | .expect("Unexpected sign bit in `format`"), 206 | data_space.into(), 207 | res, 208 | ) 209 | })?; 210 | 211 | Ok(Self::from_ptr(inner)) 212 | } 213 | 214 | #[doc(alias = "AImageReader_setImageListener")] 215 | pub fn set_image_listener(&mut self, listener: ImageListener) -> Result<()> { 216 | let mut boxed = Box::new(listener); 217 | let ptr: *mut ImageListener = &mut *boxed; 218 | 219 | unsafe extern "C" fn on_image_available( 220 | context: *mut c_void, 221 | reader: *mut ffi::AImageReader, 222 | ) { 223 | abort_on_panic(|| { 224 | let reader = ImageReader::from_ptr(NonNull::new_unchecked(reader)); 225 | let listener: *mut ImageListener = context.cast(); 226 | (*listener)(&reader); 227 | std::mem::forget(reader); 228 | }) 229 | } 230 | 231 | let mut listener = ffi::AImageReader_ImageListener { 232 | context: ptr as _, 233 | onImageAvailable: Some(on_image_available), 234 | }; 235 | let status = unsafe { ffi::AImageReader_setImageListener(self.as_ptr(), &mut listener) }; 236 | 237 | // keep listener alive until Drop or new listener is assigned 238 | self.image_cb = Some(boxed); 239 | 240 | MediaError::from_status(status) 241 | } 242 | 243 | #[cfg(feature = "api-level-26")] 244 | #[doc(alias = "AImageReader_setBufferRemovedListener")] 245 | pub fn set_buffer_removed_listener(&mut self, listener: BufferRemovedListener) -> Result<()> { 246 | let mut boxed = Box::new(listener); 247 | let ptr: *mut BufferRemovedListener = &mut *boxed; 248 | 249 | unsafe extern "C" fn on_buffer_removed( 250 | context: *mut c_void, 251 | reader: *mut ffi::AImageReader, 252 | buffer: *mut ffi::AHardwareBuffer, 253 | ) { 254 | abort_on_panic(|| { 255 | let reader = ImageReader::from_ptr(NonNull::new_unchecked(reader)); 256 | let buffer = HardwareBuffer::from_ptr(NonNull::new_unchecked(buffer)); 257 | let listener: *mut BufferRemovedListener = context.cast(); 258 | (*listener)(&reader, &buffer); 259 | std::mem::forget(reader); 260 | }) 261 | } 262 | 263 | let mut listener = ffi::AImageReader_BufferRemovedListener { 264 | context: ptr as _, 265 | onBufferRemoved: Some(on_buffer_removed), 266 | }; 267 | let status = 268 | unsafe { ffi::AImageReader_setBufferRemovedListener(self.as_ptr(), &mut listener) }; 269 | 270 | // keep listener alive until Drop or new listener is assigned 271 | self.buffer_removed_cb = Some(boxed); 272 | 273 | MediaError::from_status(status) 274 | } 275 | 276 | /// Get a [`NativeWindow`] that can be used to produce [`Image`]s for this [`ImageReader`]. 277 | /// 278 | /// 279 | #[doc(alias = "AImageReader_getWindow")] 280 | pub fn window(&self) -> Result { 281 | unsafe { 282 | let ptr = construct_never_null(|res| ffi::AImageReader_getWindow(self.as_ptr(), res))?; 283 | Ok(NativeWindow::clone_from_ptr(ptr)) 284 | } 285 | } 286 | 287 | #[doc(alias = "AImageReader_getWidth")] 288 | pub fn width(&self) -> Result { 289 | construct(|res| unsafe { ffi::AImageReader_getWidth(self.as_ptr(), res) }) 290 | } 291 | 292 | #[doc(alias = "AImageReader_getHeight")] 293 | pub fn height(&self) -> Result { 294 | construct(|res| unsafe { ffi::AImageReader_getHeight(self.as_ptr(), res) }) 295 | } 296 | 297 | #[doc(alias = "AImageReader_getFormat")] 298 | pub fn format(&self) -> Result { 299 | let format = construct(|res| unsafe { ffi::AImageReader_getFormat(self.as_ptr(), res) })?; 300 | Ok(format.into()) 301 | } 302 | 303 | #[doc(alias = "AImageReader_getMaxImages")] 304 | pub fn max_images(&self) -> Result { 305 | construct(|res| unsafe { ffi::AImageReader_getMaxImages(self.as_ptr(), res) }) 306 | } 307 | 308 | #[doc(alias = "AImageReader_acquireNextImage")] 309 | pub fn acquire_next_image(&self) -> Result> { 310 | AcquireResult::construct_never_null(|res| unsafe { 311 | ffi::AImageReader_acquireNextImage(self.as_ptr(), res) 312 | }) 313 | } 314 | 315 | /// Acquire the next [`Image`] from the image reader's queue asynchronously. 316 | /// 317 | /// # Safety 318 | /// If the returned file descriptor is not [`None`], it must be awaited before attempting to 319 | /// access the [`Image`] returned. 320 | /// 321 | /// 322 | #[cfg(feature = "api-level-26")] 323 | #[doc(alias = "AImageReader_acquireNextImageAsync")] 324 | pub unsafe fn acquire_next_image_async( 325 | &self, 326 | ) -> Result)>> { 327 | let mut fence = MaybeUninit::uninit(); 328 | AcquireResult::construct_never_null(|res| { 329 | ffi::AImageReader_acquireNextImageAsync(self.as_ptr(), res, fence.as_mut_ptr()) 330 | }) 331 | .map(|result| { 332 | result.map(|image| match fence.assume_init() { 333 | -1 => (image, None), 334 | fence => (image, Some(unsafe { OwnedFd::from_raw_fd(fence) })), 335 | }) 336 | }) 337 | } 338 | 339 | #[doc(alias = "AImageReader_acquireLatestImage")] 340 | pub fn acquire_latest_image(&self) -> Result> { 341 | AcquireResult::construct_never_null(|res| unsafe { 342 | ffi::AImageReader_acquireLatestImage(self.as_ptr(), res) 343 | }) 344 | } 345 | 346 | /// Acquire the latest [`Image`] from the image reader's queue asynchronously, dropping older images. 347 | /// 348 | /// # Safety 349 | /// If the returned file descriptor is not [`None`], it must be awaited before attempting to 350 | /// access the [`Image`] returned. 351 | /// 352 | /// 353 | #[cfg(feature = "api-level-26")] 354 | #[doc(alias = "AImageReader_acquireLatestImageAsync")] 355 | pub unsafe fn acquire_latest_image_async( 356 | &self, 357 | ) -> Result)>> { 358 | let mut fence = MaybeUninit::uninit(); 359 | AcquireResult::construct_never_null(|res| { 360 | ffi::AImageReader_acquireLatestImageAsync(self.as_ptr(), res, fence.as_mut_ptr()) 361 | }) 362 | .map(|result| { 363 | result.map(|image| match fence.assume_init() { 364 | -1 => (image, None), 365 | fence => (image, Some(unsafe { OwnedFd::from_raw_fd(fence) })), 366 | }) 367 | }) 368 | } 369 | } 370 | 371 | impl Drop for ImageReader { 372 | #[doc(alias = "AImageReader_delete")] 373 | fn drop(&mut self) { 374 | unsafe { ffi::AImageReader_delete(self.as_ptr()) }; 375 | } 376 | } 377 | 378 | /// A native [`AImage *`] 379 | /// 380 | /// [`AImage *`]: https://developer.android.com/ndk/reference/group/media#aimage 381 | #[derive(Debug)] 382 | #[doc(alias = "AImage")] 383 | pub struct Image { 384 | inner: NonNull, 385 | } 386 | 387 | #[doc(alias = "AImageCropRect")] 388 | pub type CropRect = ffi::AImageCropRect; 389 | 390 | impl Image { 391 | fn as_ptr(&self) -> *mut ffi::AImage { 392 | self.inner.as_ptr() 393 | } 394 | 395 | #[doc(alias = "AImage_getPlaneData")] 396 | pub fn plane_data(&self, plane_idx: i32) -> Result<&[u8]> { 397 | let mut result_ptr = MaybeUninit::uninit(); 398 | let mut result_len = MaybeUninit::uninit(); 399 | let status = unsafe { 400 | ffi::AImage_getPlaneData( 401 | self.as_ptr(), 402 | plane_idx, 403 | result_ptr.as_mut_ptr(), 404 | result_len.as_mut_ptr(), 405 | ) 406 | }; 407 | 408 | MediaError::from_status(status).map(|()| unsafe { 409 | std::slice::from_raw_parts(result_ptr.assume_init(), result_len.assume_init() as _) 410 | }) 411 | } 412 | 413 | #[doc(alias = "AImage_getPlanePixelStride")] 414 | pub fn plane_pixel_stride(&self, plane_idx: i32) -> Result { 415 | construct(|res| unsafe { ffi::AImage_getPlanePixelStride(self.as_ptr(), plane_idx, res) }) 416 | } 417 | 418 | #[doc(alias = "AImage_getPlaneRowStride")] 419 | pub fn plane_row_stride(&self, plane_idx: i32) -> Result { 420 | construct(|res| unsafe { ffi::AImage_getPlaneRowStride(self.as_ptr(), plane_idx, res) }) 421 | } 422 | 423 | #[doc(alias = "AImage_getCropRect")] 424 | pub fn crop_rect(&self) -> Result { 425 | construct(|res| unsafe { ffi::AImage_getCropRect(self.as_ptr(), res) }) 426 | } 427 | 428 | #[doc(alias = "AImage_getWidth")] 429 | pub fn width(&self) -> Result { 430 | construct(|res| unsafe { ffi::AImage_getWidth(self.as_ptr(), res) }) 431 | } 432 | 433 | #[doc(alias = "AImage_getHeight")] 434 | pub fn height(&self) -> Result { 435 | construct(|res| unsafe { ffi::AImage_getHeight(self.as_ptr(), res) }) 436 | } 437 | 438 | #[doc(alias = "AImage_getFormat")] 439 | pub fn format(&self) -> Result { 440 | let format = construct(|res| unsafe { ffi::AImage_getFormat(self.as_ptr(), res) })?; 441 | Ok(format.into()) 442 | } 443 | 444 | #[doc(alias = "AImage_getTimestamp")] 445 | pub fn timestamp(&self) -> Result { 446 | construct(|res| unsafe { ffi::AImage_getTimestamp(self.as_ptr(), res) }) 447 | } 448 | 449 | #[doc(alias = "AImage_getNumberOfPlanes")] 450 | pub fn number_of_planes(&self) -> Result { 451 | construct(|res| unsafe { ffi::AImage_getNumberOfPlanes(self.as_ptr(), res) }) 452 | } 453 | 454 | /// Get the hardware buffer handle of the input image intended for GPU and/or hardware access. 455 | /// 456 | /// Note that no reference on the returned [`HardwareBuffer`] handle is acquired automatically. 457 | /// Once the [`Image`] or the parent [`ImageReader`] is deleted, the [`HardwareBuffer`] handle 458 | /// from previous [`Image::hardware_buffer()`] becomes invalid. 459 | /// 460 | /// If the caller ever needs to hold on a reference to the [`HardwareBuffer`] handle after the 461 | /// [`Image`] or the parent [`ImageReader`] is deleted, it must call 462 | /// [`HardwareBuffer::acquire()`] to acquire an extra reference, and [`drop()`] it when 463 | /// finished using it in order to properly deallocate the underlying memory managed by 464 | /// [`HardwareBuffer`]. If the caller has acquired an extra reference on a [`HardwareBuffer`] 465 | /// returned from this function, it must also register a listener using 466 | /// [`ImageReader::set_buffer_removed_listener()`] to be notified when the buffer is no longer 467 | /// used by [`ImageReader`]. 468 | #[cfg(feature = "api-level-26")] 469 | #[doc(alias = "AImage_getHardwareBuffer")] 470 | pub fn hardware_buffer(&self) -> Result { 471 | unsafe { 472 | let ptr = 473 | construct_never_null(|res| ffi::AImage_getHardwareBuffer(self.as_ptr(), res))?; 474 | Ok(HardwareBuffer::from_ptr(ptr)) 475 | } 476 | } 477 | 478 | #[cfg(feature = "api-level-26")] 479 | #[doc(alias = "AImage_deleteAsync")] 480 | pub fn delete_async(self, release_fence_fd: OwnedFd) { 481 | unsafe { ffi::AImage_deleteAsync(self.as_ptr(), release_fence_fd.into_raw_fd()) }; 482 | std::mem::forget(self); 483 | } 484 | 485 | #[cfg(feature = "api-level-34")] 486 | #[doc(alias = "AImage_getDataSpace")] 487 | pub fn data_space(&self) -> Result { 488 | construct(|res| unsafe { ffi::AImage_getDataSpace(self.as_ptr(), res) }) 489 | .map(DataSpace::from) 490 | } 491 | } 492 | 493 | impl Drop for Image { 494 | #[doc(alias = "AImage_delete")] 495 | fn drop(&mut self) { 496 | unsafe { ffi::AImage_delete(self.as_ptr()) }; 497 | } 498 | } 499 | -------------------------------------------------------------------------------- /ndk/src/media/media_format.rs: -------------------------------------------------------------------------------- 1 | //! Bindings for [`AMediaFormat`] 2 | //! 3 | //! [`AMediaFormat`]: https://developer.android.com/ndk/reference/group/media#amediaformat 4 | 5 | use std::{ 6 | ffi::{CStr, CString}, 7 | fmt, 8 | ptr::{self, NonNull}, 9 | slice, 10 | }; 11 | 12 | use crate::media_error::{MediaError, Result}; 13 | 14 | /// A native [`AMediaFormat *`] 15 | /// 16 | /// [`AMediaFormat *`]: https://developer.android.com/ndk/reference/group/media#amediaformat 17 | #[doc(alias = "AMediaFormat")] 18 | pub struct MediaFormat { 19 | inner: NonNull, 20 | } 21 | 22 | impl fmt::Display for MediaFormat { 23 | /// Human readable representation of the format. 24 | #[doc(alias = "AMediaFormat_toString")] 25 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 26 | let c_str = unsafe { CStr::from_ptr(ffi::AMediaFormat_toString(self.as_ptr())) }; 27 | f.write_str(c_str.to_str().unwrap()) 28 | } 29 | } 30 | 31 | impl fmt::Debug for MediaFormat { 32 | #[doc(alias = "AMediaFormat_toString")] 33 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 34 | write!(f, "MediaFormat({:?}: {})", self.inner, self) 35 | } 36 | } 37 | 38 | impl Default for MediaFormat { 39 | #[doc(alias = "AMediaFormat_new")] 40 | fn default() -> Self { 41 | Self::new() 42 | } 43 | } 44 | 45 | impl MediaFormat { 46 | /// Assumes ownership of `ptr` 47 | /// 48 | /// # Safety 49 | /// `ptr` must be a valid pointer to an Android [`ffi::AMediaFormat`]. 50 | pub unsafe fn from_ptr(ptr: NonNull) -> Self { 51 | Self { inner: ptr } 52 | } 53 | 54 | pub fn as_ptr(&self) -> *mut ffi::AMediaFormat { 55 | self.inner.as_ptr() 56 | } 57 | 58 | #[doc(alias = "AMediaFormat_new")] 59 | pub fn new() -> Self { 60 | Self { 61 | inner: NonNull::new(unsafe { ffi::AMediaFormat_new() }).unwrap(), 62 | } 63 | } 64 | 65 | #[doc(alias = "AMediaFormat_getInt32")] 66 | pub fn i32(&self, key: &str) -> Option { 67 | let name = CString::new(key).unwrap(); 68 | let mut out = 0; 69 | if unsafe { ffi::AMediaFormat_getInt32(self.as_ptr(), name.as_ptr(), &mut out) } { 70 | Some(out) 71 | } else { 72 | None 73 | } 74 | } 75 | 76 | #[doc(alias = "AMediaFormat_getInt64")] 77 | pub fn i64(&self, key: &str) -> Option { 78 | let name = CString::new(key).unwrap(); 79 | let mut out = 0; 80 | if unsafe { ffi::AMediaFormat_getInt64(self.as_ptr(), name.as_ptr(), &mut out) } { 81 | Some(out) 82 | } else { 83 | None 84 | } 85 | } 86 | 87 | #[doc(alias = "AMediaFormat_getFloat")] 88 | pub fn f32(&self, key: &str) -> Option { 89 | let name = CString::new(key).unwrap(); 90 | let mut out = 0.0; 91 | if unsafe { ffi::AMediaFormat_getFloat(self.as_ptr(), name.as_ptr(), &mut out) } { 92 | Some(out) 93 | } else { 94 | None 95 | } 96 | } 97 | 98 | #[doc(alias = "AMediaFormat_getSize")] 99 | pub fn usize(&self, key: &str) -> Option { 100 | let name = CString::new(key).unwrap(); 101 | let mut out = 0; 102 | if unsafe { ffi::AMediaFormat_getSize(self.as_ptr(), name.as_ptr(), &mut out) } { 103 | Some(out) 104 | } else { 105 | None 106 | } 107 | } 108 | 109 | #[doc(alias = "AMediaFormat_getBuffer")] 110 | pub fn buffer(&self, key: &str) -> Option<&[u8]> { 111 | let name = CString::new(key).unwrap(); 112 | let mut out_buffer = ptr::null_mut(); 113 | let mut out_size = 0; 114 | unsafe { 115 | ffi::AMediaFormat_getBuffer( 116 | self.as_ptr(), 117 | name.as_ptr(), 118 | &mut out_buffer, 119 | &mut out_size, 120 | ) 121 | } 122 | .then(|| unsafe { slice::from_raw_parts(out_buffer.cast(), out_size) }) 123 | } 124 | 125 | /// The returned `&str` borrow is only valid until the next call to [`MediaFormat::str()`] for 126 | /// the same key. 127 | #[doc(alias = "AMediaFormat_getString")] 128 | pub fn str(&mut self, key: &str) -> Option<&str> { 129 | let name = CString::new(key).unwrap(); 130 | let mut out = ptr::null(); 131 | unsafe { ffi::AMediaFormat_getString(self.as_ptr(), name.as_ptr(), &mut out) } 132 | .then(|| unsafe { CStr::from_ptr(out) }.to_str().unwrap()) 133 | } 134 | 135 | #[doc(alias = "AMediaFormat_setInt32")] 136 | pub fn set_i32(&mut self, key: &str, value: i32) { 137 | let name = CString::new(key).unwrap(); 138 | unsafe { ffi::AMediaFormat_setInt32(self.as_ptr(), name.as_ptr(), value) } 139 | } 140 | 141 | #[doc(alias = "AMediaFormat_setInt64")] 142 | pub fn set_i64(&mut self, key: &str, value: i64) { 143 | let name = CString::new(key).unwrap(); 144 | unsafe { ffi::AMediaFormat_setInt64(self.as_ptr(), name.as_ptr(), value) } 145 | } 146 | 147 | #[doc(alias = "AMediaFormat_setFloat")] 148 | pub fn set_f32(&mut self, key: &str, value: f32) { 149 | let name = CString::new(key).unwrap(); 150 | unsafe { ffi::AMediaFormat_setFloat(self.as_ptr(), name.as_ptr(), value) } 151 | } 152 | 153 | #[doc(alias = "AMediaFormat_setString")] 154 | pub fn set_str(&mut self, key: &str, value: &str) { 155 | let name = CString::new(key).unwrap(); 156 | let c_string = CString::new(value).unwrap(); 157 | unsafe { ffi::AMediaFormat_setString(self.as_ptr(), name.as_ptr(), c_string.as_ptr()) } 158 | } 159 | 160 | #[doc(alias = "AMediaFormat_setBuffer")] 161 | pub fn set_buffer(&mut self, key: &str, value: &[u8]) { 162 | let name = CString::new(key).unwrap(); 163 | unsafe { 164 | ffi::AMediaFormat_setBuffer( 165 | self.as_ptr(), 166 | name.as_ptr(), 167 | value.as_ptr().cast(), 168 | value.len(), 169 | ) 170 | } 171 | } 172 | 173 | #[cfg(feature = "api-level-28")] 174 | #[doc(alias = "AMediaFormat_getDouble")] 175 | pub fn f64(&self, key: &str) -> Option { 176 | let name = CString::new(key).unwrap(); 177 | let mut out = 0.0; 178 | if unsafe { ffi::AMediaFormat_getDouble(self.as_ptr(), name.as_ptr(), &mut out) } { 179 | Some(out) 180 | } else { 181 | None 182 | } 183 | } 184 | 185 | /// Returns (left, top, right, bottom) 186 | #[cfg(feature = "api-level-28")] 187 | #[doc(alias = "AMediaFormat_getRect")] 188 | pub fn rect(&self, key: &str) -> Option<(i32, i32, i32, i32)> { 189 | let name = CString::new(key).unwrap(); 190 | let mut left = 0; 191 | let mut top = 0; 192 | let mut right = 0; 193 | let mut bottom = 0; 194 | if unsafe { 195 | ffi::AMediaFormat_getRect( 196 | self.as_ptr(), 197 | name.as_ptr(), 198 | &mut left, 199 | &mut top, 200 | &mut right, 201 | &mut bottom, 202 | ) 203 | } { 204 | Some((left, top, right, bottom)) 205 | } else { 206 | None 207 | } 208 | } 209 | 210 | #[cfg(feature = "api-level-28")] 211 | #[doc(alias = "AMediaFormat_setDouble")] 212 | pub fn set_f64(&mut self, key: &str, value: f64) { 213 | let name = CString::new(key).unwrap(); 214 | unsafe { ffi::AMediaFormat_setDouble(self.as_ptr(), name.as_ptr(), value) } 215 | } 216 | 217 | #[cfg(feature = "api-level-28")] 218 | #[doc(alias = "AMediaFormat_setRect")] 219 | pub fn set_rect(&mut self, key: &str, left: i32, top: i32, right: i32, bottom: i32) { 220 | let name = CString::new(key).unwrap(); 221 | unsafe { ffi::AMediaFormat_setRect(self.as_ptr(), name.as_ptr(), left, top, right, bottom) } 222 | } 223 | 224 | #[cfg(feature = "api-level-28")] 225 | #[doc(alias = "AMediaFormat_setSize")] 226 | pub fn set_usize(&mut self, key: &str, value: usize) { 227 | let name = CString::new(key).unwrap(); 228 | unsafe { ffi::AMediaFormat_setSize(self.as_ptr(), name.as_ptr(), value) } 229 | } 230 | 231 | /// Copy one [`MediaFormat`] to another. 232 | #[cfg(feature = "api-level-29")] 233 | #[doc(alias = "AMediaFormat_copy")] 234 | pub fn copy(&self, to: &mut Self) -> Result<()> { 235 | let status = unsafe { ffi::AMediaFormat_copy(to.as_ptr(), self.as_ptr()) }; 236 | MediaError::from_status(status) 237 | } 238 | 239 | /// Clones this [`MediaFormat`] into a [`MediaFormat::new()`] object using 240 | /// [`MediaFormat::copy()`]. 241 | #[cfg(feature = "api-level-29")] 242 | #[doc(alias = "AMediaFormat_new")] 243 | #[doc(alias = "AMediaFormat_copy")] 244 | pub fn try_clone(&self) -> Result { 245 | let mut copy = Self::new(); 246 | self.copy(&mut copy)?; 247 | Ok(copy) 248 | } 249 | 250 | /// Remove all key/value pairs from this [`MediaFormat`]. 251 | #[cfg(feature = "api-level-29")] 252 | #[doc(alias = "AMediaFormat_clear")] 253 | pub fn clear(&mut self) { 254 | unsafe { ffi::AMediaFormat_clear(self.as_ptr()) } 255 | } 256 | } 257 | 258 | impl Drop for MediaFormat { 259 | #[doc(alias = "AMediaFormat_delete")] 260 | fn drop(&mut self) { 261 | let status = unsafe { ffi::AMediaFormat_delete(self.as_ptr()) }; 262 | MediaError::from_status(status).unwrap() 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /ndk/src/media/mod.rs: -------------------------------------------------------------------------------- 1 | //! Bindings for the NDK media classes. 2 | //! 3 | //! See also [the NDK docs](https://developer.android.com/ndk/reference/group/media) 4 | #![cfg(feature = "media")] 5 | 6 | pub mod image_reader; 7 | pub mod media_codec; 8 | pub mod media_format; 9 | -------------------------------------------------------------------------------- /ndk/src/media_error.rs: -------------------------------------------------------------------------------- 1 | //! Bindings for NDK media status codes. 2 | //! 3 | //! Also used outside of `libmediandk.so` in `libamidi.so` for example. 4 | #![cfg(feature = "media")] 5 | // The cfg(feature) bounds for some pub(crate) fn uses are non-trivial and will become even more 6 | // complex going forward. Allow them to be unused when compiling with certain feature combinations. 7 | #![allow(dead_code)] 8 | 9 | use std::{fmt, mem::MaybeUninit, ptr::NonNull}; 10 | 11 | use num_enum::{FromPrimitive, IntoPrimitive}; 12 | 13 | pub type Result = std::result::Result; 14 | 15 | /// Media Status codes for [`media_status_t`](https://developer.android.com/ndk/reference/group/media#group___media_1ga009a49041fe39f7bdc6d8b5cddbe760c) 16 | #[repr(i32)] 17 | #[derive(Copy, Clone, Debug, PartialEq, Eq, FromPrimitive, IntoPrimitive)] 18 | #[doc(alias = "media_status_t")] 19 | #[non_exhaustive] 20 | pub enum MediaError { 21 | #[doc(alias = "AMEDIACODEC_ERROR_INSUFFICIENT_RESOURCE")] 22 | CodecErrorInsufficientResource = ffi::media_status_t::AMEDIACODEC_ERROR_INSUFFICIENT_RESOURCE.0, 23 | #[doc(alias = "AMEDIACODEC_ERROR_RECLAIMED")] 24 | CodecErrorReclaimed = ffi::media_status_t::AMEDIACODEC_ERROR_RECLAIMED.0, 25 | #[doc(alias = "AMEDIA_ERROR_UNKNOWN")] 26 | ErrorUnknown = ffi::media_status_t::AMEDIA_ERROR_UNKNOWN.0, 27 | #[doc(alias = "AMEDIA_ERROR_MALFORMED")] 28 | ErrorMalformed = ffi::media_status_t::AMEDIA_ERROR_MALFORMED.0, 29 | #[doc(alias = "AMEDIA_ERROR_UNSUPPORTED")] 30 | ErrorUnsupported = ffi::media_status_t::AMEDIA_ERROR_UNSUPPORTED.0, 31 | #[doc(alias = "AMEDIA_ERROR_INVALID_OBJECT")] 32 | ErrorInvalidObject = ffi::media_status_t::AMEDIA_ERROR_INVALID_OBJECT.0, 33 | #[doc(alias = "AMEDIA_ERROR_INVALID_PARAMETER")] 34 | ErrorInvalidParameter = ffi::media_status_t::AMEDIA_ERROR_INVALID_PARAMETER.0, 35 | #[doc(alias = "AMEDIA_ERROR_INVALID_OPERATION")] 36 | ErrorInvalidOperation = ffi::media_status_t::AMEDIA_ERROR_INVALID_OPERATION.0, 37 | #[doc(alias = "AMEDIA_ERROR_END_OF_STREAM")] 38 | ErrorEndOfStream = ffi::media_status_t::AMEDIA_ERROR_END_OF_STREAM.0, 39 | #[doc(alias = "AMEDIA_ERROR_IO")] 40 | ErrorIo = ffi::media_status_t::AMEDIA_ERROR_IO.0, 41 | #[doc(alias = "AMEDIA_ERROR_WOULD_BLOCK")] 42 | ErrorWouldBlock = ffi::media_status_t::AMEDIA_ERROR_WOULD_BLOCK.0, 43 | #[doc(alias = "AMEDIA_DRM_ERROR_BASE")] 44 | DrmErrorBase = ffi::media_status_t::AMEDIA_DRM_ERROR_BASE.0, 45 | #[doc(alias = "AMEDIA_DRM_NOT_PROVISIONED")] 46 | DrmNotProvisioned = ffi::media_status_t::AMEDIA_DRM_NOT_PROVISIONED.0, 47 | #[doc(alias = "AMEDIA_DRM_RESOURCE_BUSY")] 48 | DrmResourceBusy = ffi::media_status_t::AMEDIA_DRM_RESOURCE_BUSY.0, 49 | #[doc(alias = "AMEDIA_DRM_DEVICE_REVOKED")] 50 | DrmDeviceRevoked = ffi::media_status_t::AMEDIA_DRM_DEVICE_REVOKED.0, 51 | #[doc(alias = "AMEDIA_DRM_SHORT_BUFFER")] 52 | DrmShortBuffer = ffi::media_status_t::AMEDIA_DRM_SHORT_BUFFER.0, 53 | #[doc(alias = "AMEDIA_DRM_SESSION_NOT_OPENED")] 54 | DrmSessionNotOpened = ffi::media_status_t::AMEDIA_DRM_SESSION_NOT_OPENED.0, 55 | #[doc(alias = "AMEDIA_DRM_TAMPER_DETECTED")] 56 | DrmTamperDetected = ffi::media_status_t::AMEDIA_DRM_TAMPER_DETECTED.0, 57 | #[doc(alias = "AMEDIA_DRM_VERIFY_FAILED")] 58 | DrmVerifyFailed = ffi::media_status_t::AMEDIA_DRM_VERIFY_FAILED.0, 59 | #[doc(alias = "AMEDIA_DRM_NEED_KEY")] 60 | DrmNeedKey = ffi::media_status_t::AMEDIA_DRM_NEED_KEY.0, 61 | #[doc(alias = "AMEDIA_DRM_LICENSE_EXPIRED")] 62 | DrmLicenseExpired = ffi::media_status_t::AMEDIA_DRM_LICENSE_EXPIRED.0, 63 | #[doc(alias = "AMEDIA_IMGREADER_ERROR_BASE")] 64 | ImgreaderErrorBase = ffi::media_status_t::AMEDIA_IMGREADER_ERROR_BASE.0, 65 | #[doc(alias = "AMEDIA_IMGREADER_CANNOT_LOCK_IMAGE")] 66 | ImgreaderCannotLockImage = ffi::media_status_t::AMEDIA_IMGREADER_CANNOT_LOCK_IMAGE.0, 67 | #[doc(alias = "AMEDIA_IMGREADER_CANNOT_UNLOCK_IMAGE")] 68 | ImgreaderCannotUnlockImage = ffi::media_status_t::AMEDIA_IMGREADER_CANNOT_UNLOCK_IMAGE.0, 69 | #[doc(alias = "AMEDIA_IMGREADER_IMAGE_NOT_LOCKED")] 70 | ImgreaderImageNotLocked = ffi::media_status_t::AMEDIA_IMGREADER_IMAGE_NOT_LOCKED.0, 71 | 72 | /// This error code is unknown to the [`ndk`][crate] crate. Please report an issue if you 73 | /// believe this code needs to be added to our mapping. 74 | // Use the OK discriminant, as no-one will be able to call `as i32` and only has access to the 75 | // constants via `From` provided by `IntoPrimitive` which reads the contained value. 76 | // An autogenerated ` + 1` discriminant is normally fine, except that the 77 | // previous variant is negative and `+1` would match the variant before that. 78 | #[doc(hidden)] 79 | #[num_enum(catch_all)] 80 | __Unknown(i32) = ffi::media_status_t::AMEDIA_OK.0, 81 | } 82 | 83 | impl fmt::Display for MediaError { 84 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 85 | write!(f, "{:?}", self) 86 | } 87 | } 88 | 89 | impl std::error::Error for MediaError {} 90 | 91 | impl MediaError { 92 | /// Returns [`Ok`] on [`ffi::media_status_t::AMEDIA_OK`], [`Err`] otherwise (including positive 93 | /// values). 94 | /// 95 | /// Note that some known error codes (currently only for `AMediaCodec`) are positive. 96 | pub(crate) fn from_status(status: ffi::media_status_t) -> Result<()> { 97 | match status { 98 | ffi::media_status_t::AMEDIA_OK => Ok(()), 99 | x => Err(Self::from(x.0)), 100 | } 101 | } 102 | 103 | /// Returns the original value in [`Ok`] if it is not negative, [`Err`] otherwise. 104 | /// 105 | /// Note that some [`ffi::media_status_t`] codes are positive but will never be returned as 106 | /// [`Err`] from this function. As of writing these codes are specific to the `AMediaCodec` API 107 | /// and should not be handled generically. 108 | pub(crate) fn from_status_if_negative + Copy>(value: T) -> Result { 109 | let v = value.into(); 110 | if v >= 0 { 111 | Ok(value) 112 | } else { 113 | Err(Self::from( 114 | i32::try_from(v).expect("Error code out of bounds"), 115 | )) 116 | } 117 | } 118 | } 119 | 120 | /// Calls the `with_ptr` construction function with a pointer to uninitialized stack memory, 121 | /// expecting `with_ptr` to initialize it or otherwise return an error code. 122 | pub(crate) fn construct(with_ptr: impl FnOnce(*mut T) -> ffi::media_status_t) -> Result { 123 | let mut result = MaybeUninit::uninit(); 124 | let status = with_ptr(result.as_mut_ptr()); 125 | MediaError::from_status(status).map(|()| unsafe { result.assume_init() }) 126 | } 127 | 128 | /// Calls the `with_ptr` construction function with a pointer to a pointer, and expects `with_ptr` 129 | /// to initialize the second pointer to a valid address. That address is returned in the form of a 130 | /// [`NonNull`] object. 131 | pub(crate) fn construct_never_null( 132 | with_ptr: impl FnOnce(*mut *mut T) -> ffi::media_status_t, 133 | ) -> Result> { 134 | let result = construct(with_ptr)?; 135 | Ok(if cfg!(debug_assertions) { 136 | NonNull::new(result).expect("result should never be null") 137 | } else { 138 | unsafe { NonNull::new_unchecked(result) } 139 | }) 140 | } 141 | -------------------------------------------------------------------------------- /ndk/src/native_activity.rs: -------------------------------------------------------------------------------- 1 | //! Bindings for [`ANativeActivity`] 2 | //! 3 | //! [`ANativeActivity`]: https://developer.android.com/ndk/reference/group/native-activity#anativeactivity 4 | 5 | use super::hardware_buffer_format::HardwareBufferFormat; 6 | use std::{ 7 | ffi::{CStr, OsStr}, 8 | os::{raw::c_void, unix::prelude::OsStrExt}, 9 | path::Path, 10 | ptr::NonNull, 11 | }; 12 | 13 | bitflags::bitflags! { 14 | /// Window flags, as per the Java API at [`android.view.WindowManager.LayoutParams`]. 15 | /// 16 | /// 17 | /// 18 | /// [`android.view.WindowManager.LayoutParams`]: https://developer.android.com/reference/android/view/WindowManager.LayoutParams 19 | #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)] 20 | pub struct WindowFlags : u32 { 21 | const ALLOW_LOCK_WHILE_SCREEN_ON = ffi::AWINDOW_FLAG_ALLOW_LOCK_WHILE_SCREEN_ON; 22 | const DIM_BEHIND = ffi::AWINDOW_FLAG_DIM_BEHIND; 23 | #[deprecated = "Deprecated. Blurring is no longer supported."] 24 | const BLUR_BEHIND = ffi::AWINDOW_FLAG_BLUR_BEHIND; 25 | const NOT_FOCUSABLE = ffi::AWINDOW_FLAG_NOT_FOCUSABLE; 26 | const NOT_TOUCHABLE = ffi::AWINDOW_FLAG_NOT_TOUCHABLE; 27 | const NOT_TOUCH_MODAL = ffi::AWINDOW_FLAG_NOT_TOUCH_MODAL; 28 | #[deprecated = "This constant was deprecated in API level 20. This flag has no effect."] 29 | const TOUCHABLE_WHEN_WAKING = ffi::AWINDOW_FLAG_TOUCHABLE_WHEN_WAKING; 30 | const KEEP_SCREEN_ON = ffi::AWINDOW_FLAG_KEEP_SCREEN_ON; 31 | const LAYOUT_IN_SCREEN = ffi::AWINDOW_FLAG_LAYOUT_IN_SCREEN; 32 | const LAYOUT_NO_LIMITS = ffi::AWINDOW_FLAG_LAYOUT_NO_LIMITS; 33 | const FULLSCREEN = ffi::AWINDOW_FLAG_FULLSCREEN; 34 | #[cfg_attr(feature = "api-level-30", deprecated = "This constant was deprecated in API level 30. This value became API \"by accident\", and shouldn't be used by 3rd party applications.")] 35 | const FORCE_NOT_FULLSCREEN = ffi::AWINDOW_FLAG_FORCE_NOT_FULLSCREEN; 36 | #[deprecated = "This constant was deprecated in API level 17. This flag is no longer used."] 37 | const DITHER = ffi::AWINDOW_FLAG_DITHER; 38 | const SECURE = ffi::AWINDOW_FLAG_SECURE; 39 | const SCALED = ffi::AWINDOW_FLAG_SCALED; 40 | const IGNORE_CHEEK_PRESSES = ffi::AWINDOW_FLAG_IGNORE_CHEEK_PRESSES; 41 | const LAYOUT_INSET_DECOR = ffi::AWINDOW_FLAG_LAYOUT_INSET_DECOR; 42 | const ALT_FOCUSABLE_IM = ffi::AWINDOW_FLAG_ALT_FOCUSABLE_IM; 43 | const WATCH_OUTSIDE_TOUCH = ffi::AWINDOW_FLAG_WATCH_OUTSIDE_TOUCH; 44 | const SHOW_WHEN_LOCKED = ffi::AWINDOW_FLAG_SHOW_WHEN_LOCKED; 45 | const SHOW_WALLPAPER = ffi::AWINDOW_FLAG_SHOW_WALLPAPER; 46 | const TURN_SCREEN_ON = ffi::AWINDOW_FLAG_TURN_SCREEN_ON; 47 | #[cfg_attr(feature = "api-level-26", deprecated = "This constant was deprecated in API level 26. Use `SHOW_WHEN_LOCKED` instead.")] 48 | const DISMISS_KEYGUARD = ffi::AWINDOW_FLAG_DISMISS_KEYGUARD; 49 | const ATTACHED_IN_DECOR = 0x40000000; 50 | 51 | // https://docs.rs/bitflags/latest/bitflags/#externally-defined-flags 52 | const _ = !0; 53 | } 54 | } 55 | 56 | /// A native [`ANativeActivity *`] 57 | /// 58 | /// This is either provided in [`ffi::ANativeActivity_onCreate()`], or accessible through 59 | /// `ndk_glue::native_activity()`. 60 | /// 61 | /// [`ANativeActivity *`]: https://developer.android.com/ndk/reference/struct/a-native-activity 62 | #[derive(Debug)] 63 | pub struct NativeActivity { 64 | ptr: NonNull, 65 | } 66 | 67 | // It gets shared between threads in `ndk-glue` 68 | unsafe impl Send for NativeActivity {} 69 | unsafe impl Sync for NativeActivity {} 70 | 71 | impl NativeActivity { 72 | /// Create a [`NativeActivity`] from a pointer 73 | /// 74 | /// # Safety 75 | /// By calling this function, you assert that it is a valid pointer to a native 76 | /// [`ffi::ANativeActivity`]. 77 | pub unsafe fn from_ptr(ptr: NonNull) -> Self { 78 | Self { ptr } 79 | } 80 | 81 | /// The pointer to the native `ANativeActivity` 82 | pub fn ptr(&self) -> NonNull { 83 | self.ptr 84 | } 85 | } 86 | 87 | /// Methods that relate to fields of the struct itself 88 | /// 89 | /// The relevant NDK docs can be found 90 | /// [here](https://developer.android.com/ndk/reference/struct/a-native-activity). 91 | impl NativeActivity { 92 | /// The platform's SDK version code 93 | pub fn sdk_version(&self) -> i32 { 94 | unsafe { self.ptr.as_ref().sdkVersion } 95 | } 96 | 97 | /// Path to this application's internal data directory 98 | pub fn internal_data_path(&self) -> &Path { 99 | OsStr::from_bytes(unsafe { CStr::from_ptr(self.ptr.as_ref().internalDataPath) }.to_bytes()) 100 | .as_ref() 101 | } 102 | 103 | /// Path to this application's external (removable, mountable) data directory 104 | pub fn external_data_path(&self) -> &Path { 105 | OsStr::from_bytes(unsafe { CStr::from_ptr(self.ptr.as_ref().externalDataPath) }.to_bytes()) 106 | .as_ref() 107 | } 108 | 109 | /// This app's asset manager, which can be used to access assets from the `.apk` file. 110 | pub fn asset_manager(&self) -> crate::asset::AssetManager { 111 | unsafe { 112 | crate::asset::AssetManager::from_ptr( 113 | NonNull::new(self.ptr.as_ref().assetManager).unwrap(), 114 | ) 115 | } 116 | } 117 | 118 | /// Instance data associated with the activity 119 | pub fn instance(&self) -> *mut c_void { 120 | unsafe { self.ptr.as_ref().instance } 121 | } 122 | 123 | /// Set the instance data associated with the activity 124 | /// 125 | /// # Safety 126 | /// This can invalidate assumptions held by `ndk-glue`, as well as cause data 127 | /// races with concurrent access to the instance data. 128 | pub unsafe fn set_instance(&mut self, data: *mut c_void) { 129 | // FIXME Does this create undefined behavior by creating a mutable reference to what could 130 | // also be accessed immutably at the same time? 131 | // 132 | // I think that as long as we warn the users to avoid concurrent access, and we pass along 133 | // the `unsafe` burden, it's OK. 134 | self.ptr.as_mut().instance = data; 135 | } 136 | 137 | /// This process's `JavaVM` object. 138 | /// 139 | /// Usage with [__jni__](https://crates.io/crates/jni) crate: 140 | /// ```no_run 141 | /// # use ndk::native_activity::NativeActivity; 142 | /// # let native_activity: NativeActivity = unimplemented!(); 143 | /// let vm_ptr = native_activity.vm(); 144 | /// let vm = unsafe { jni::JavaVM::from_raw(vm_ptr) }.unwrap(); 145 | /// let env = vm.attach_current_thread(); 146 | /// // Do JNI with env ... 147 | /// ``` 148 | pub fn vm(&self) -> *mut jni_sys::JavaVM { 149 | unsafe { self.ptr.as_ref() }.vm 150 | } 151 | 152 | /// The [`android.app.NativeActivity`] instance 153 | /// 154 | /// In the JNI, this is named `clazz`; however, as the docs say, "it should really be named 155 | /// 'activity' instead of 'clazz', since it's a reference to the NativeActivity instance". 156 | /// 157 | /// [`android.app.NativeActivity`]: https://developer.android.com/reference/android/app/NativeActivity 158 | pub fn activity(&self) -> jni_sys::jobject { 159 | unsafe { self.ptr.as_ref() }.clazz 160 | } 161 | 162 | /// Path to the directory with the application's OBB files. 163 | /// 164 | /// # Safety 165 | /// Only available as of Honeycomb (Android 3.0+, API level 11+) 166 | pub unsafe fn obb_path(&self) -> &Path { 167 | OsStr::from_bytes(CStr::from_ptr(self.ptr.as_ref().obbPath).to_bytes()).as_ref() 168 | } 169 | } 170 | 171 | /// Methods that relate to `ANativeActivity_*` functions. 172 | /// 173 | /// The relevant NDK docs can be found 174 | /// [here](https://developer.android.com/ndk/reference/group/native-activity). 175 | impl NativeActivity { 176 | /// Sends a destroy event to the activity and stops it. 177 | pub fn finish(&self) { 178 | unsafe { ffi::ANativeActivity_finish(self.ptr.as_ptr()) } 179 | } 180 | 181 | /// Shows the IME (the on-screen keyboard). 182 | /// 183 | /// If `force` is true, the `SHOW_FORCED` flag is used; otherwise, the `SHOW_IMPLICIT` flag is 184 | /// used. Depending on the value of this flag, the `hide_soft_input` method with behave 185 | /// differently. See [the relevant 186 | /// javadoc](https://developer.android.com/reference/android/view/inputmethod/InputMethodManager#constants_2) 187 | /// for more information. 188 | pub fn show_soft_input(&self, force: bool) { 189 | let flag = if force { 190 | ffi::ANATIVEACTIVITY_SHOW_SOFT_INPUT_FORCED 191 | } else { 192 | ffi::ANATIVEACTIVITY_SHOW_SOFT_INPUT_IMPLICIT 193 | }; 194 | unsafe { ffi::ANativeActivity_showSoftInput(self.ptr.as_ptr(), flag) } 195 | } 196 | 197 | /// Hides the IME (the on-screen keyboard). 198 | /// 199 | /// If `not_always` is true, the `HIDE_NOT_ALWAYS` flag is used; otherwise, the 200 | /// `HIDE_IMPLICIT_ONLY` flag is used. Depending on the value of this flag and the way the IME 201 | /// was shown, it may or may not be hidden. See [the relevant 202 | /// javadoc](https://developer.android.com/reference/android/view/inputmethod/InputMethodManager#constants_2) 203 | /// for more information. 204 | pub fn hide_soft_input(&self, not_always: bool) { 205 | let flag = if not_always { 206 | ffi::ANATIVEACTIVITY_HIDE_SOFT_INPUT_NOT_ALWAYS 207 | } else { 208 | ffi::ANATIVEACTIVITY_HIDE_SOFT_INPUT_IMPLICIT_ONLY 209 | }; 210 | unsafe { ffi::ANativeActivity_hideSoftInput(self.ptr.as_ptr(), flag) } 211 | } 212 | 213 | /// Change the window format of the given activity. 214 | /// 215 | /// Calls [`getWindow().setFormat()`] of the given activity. Note that this method can be 216 | /// called from any thread; it will send a message to the main thread of the process where the 217 | /// Java finish call will take place. 218 | /// 219 | /// [`getWindow().setFormat()`]: https://developer.android.com/reference/android/view/Window#setFormat(int) 220 | pub fn set_window_format(&self, format: HardwareBufferFormat) { 221 | unsafe { ffi::ANativeActivity_setWindowFormat(self.ptr.as_ptr(), format.into()) } 222 | } 223 | 224 | /// Change the window flags of the given activity. 225 | /// 226 | /// Calls [`getWindow().setFlags()`] of the given activity. 227 | /// 228 | /// Note that this method can be called from any thread; it will send a message to the main 229 | /// thread of the process where the Java finish call will take place. 230 | /// 231 | /// [`getWindow().setFlags()`]: https://developer.android.com/reference/android/view/Window#setFlags(int,%20int) 232 | pub fn set_window_flags(&self, add_flags: WindowFlags, remove_flags: WindowFlags) { 233 | unsafe { 234 | ffi::ANativeActivity_setWindowFlags( 235 | self.ptr.as_ptr(), 236 | add_flags.bits(), 237 | remove_flags.bits(), 238 | ) 239 | } 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /ndk/src/native_window.rs: -------------------------------------------------------------------------------- 1 | //! Bindings for [`ANativeWindow`] 2 | //! 3 | //! [`ANativeWindow`]: https://developer.android.com/ndk/reference/group/a-native-window#anativewindow 4 | 5 | use std::{ffi::c_void, io, mem::MaybeUninit, ptr::NonNull}; 6 | 7 | use jni_sys::{jobject, JNIEnv}; 8 | 9 | use super::{hardware_buffer_format::HardwareBufferFormat, utils::status_to_io_result}; 10 | #[cfg(all(feature = "nativewindow", feature = "api-level-28"))] 11 | use crate::data_space::DataSpace; 12 | 13 | pub type Rect = ffi::ARect; 14 | 15 | // [`NativeWindow`] represents the producer end of an image queue 16 | /// 17 | /// It is the C counterpart of the [`android.view.Surface`] object in Java, and can be converted 18 | /// both ways. Depending on the consumer, images submitted to [`NativeWindow`] can be shown on the 19 | /// display or sent to other consumers, such as video encoders. 20 | /// 21 | /// [`android.view.Surface`]: https://developer.android.com/reference/android/view/Surface 22 | #[derive(Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 23 | pub struct NativeWindow { 24 | ptr: NonNull, 25 | } 26 | 27 | unsafe impl Send for NativeWindow {} 28 | unsafe impl Sync for NativeWindow {} 29 | 30 | impl Drop for NativeWindow { 31 | fn drop(&mut self) { 32 | unsafe { ffi::ANativeWindow_release(self.ptr.as_ptr()) } 33 | } 34 | } 35 | 36 | impl Clone for NativeWindow { 37 | fn clone(&self) -> Self { 38 | unsafe { ffi::ANativeWindow_acquire(self.ptr.as_ptr()) } 39 | Self { ptr: self.ptr } 40 | } 41 | } 42 | 43 | #[cfg(feature = "rwh_04")] 44 | unsafe impl rwh_04::HasRawWindowHandle for NativeWindow { 45 | fn raw_window_handle(&self) -> rwh_04::RawWindowHandle { 46 | let mut handle = rwh_04::AndroidNdkHandle::empty(); 47 | handle.a_native_window = self.ptr.as_ptr().cast(); 48 | rwh_04::RawWindowHandle::AndroidNdk(handle) 49 | } 50 | } 51 | 52 | #[cfg(feature = "rwh_05")] 53 | unsafe impl rwh_05::HasRawWindowHandle for NativeWindow { 54 | fn raw_window_handle(&self) -> rwh_05::RawWindowHandle { 55 | let mut handle = rwh_05::AndroidNdkWindowHandle::empty(); 56 | handle.a_native_window = self.ptr.as_ptr().cast(); 57 | rwh_05::RawWindowHandle::AndroidNdk(handle) 58 | } 59 | } 60 | 61 | #[cfg(feature = "rwh_06")] 62 | impl rwh_06::HasWindowHandle for NativeWindow { 63 | fn window_handle(&self) -> Result, rwh_06::HandleError> { 64 | let handle = rwh_06::AndroidNdkWindowHandle::new(self.ptr.cast()); 65 | let handle = rwh_06::RawWindowHandle::AndroidNdk(handle); 66 | // SAFETY: All fields of the "raw" `AndroidNdkWindowHandle` struct are filled out. The 67 | // returned pointer is also kept valid by `NativeWindow` (until `Drop`), which is lifetime- 68 | // borrowed in the returned `WindowHandle<'_>` and cannot be outlived. Its value won't 69 | // change throughout the lifetime of this `NativeWindow`. 70 | Ok(unsafe { rwh_06::WindowHandle::borrow_raw(handle) }) 71 | } 72 | } 73 | 74 | impl NativeWindow { 75 | /// Assumes ownership of `ptr` 76 | /// 77 | /// # Safety 78 | /// `ptr` must be a valid pointer to an Android [`ffi::ANativeWindow`]. 79 | pub unsafe fn from_ptr(ptr: NonNull) -> Self { 80 | Self { ptr } 81 | } 82 | 83 | /// Acquires ownership of `ptr` 84 | /// 85 | /// # Safety 86 | /// `ptr` must be a valid pointer to an Android [`ffi::ANativeWindow`]. 87 | pub unsafe fn clone_from_ptr(ptr: NonNull) -> Self { 88 | ffi::ANativeWindow_acquire(ptr.as_ptr()); 89 | Self::from_ptr(ptr) 90 | } 91 | 92 | pub fn ptr(&self) -> NonNull { 93 | self.ptr 94 | } 95 | 96 | pub fn height(&self) -> i32 { 97 | unsafe { ffi::ANativeWindow_getHeight(self.ptr.as_ptr()) } 98 | } 99 | 100 | pub fn width(&self) -> i32 { 101 | unsafe { ffi::ANativeWindow_getWidth(self.ptr.as_ptr()) } 102 | } 103 | 104 | /// Return the current pixel format ([`HardwareBufferFormat`]) of the window surface. 105 | pub fn format(&self) -> HardwareBufferFormat { 106 | let value = unsafe { ffi::ANativeWindow_getFormat(self.ptr.as_ptr()) }; 107 | value.into() 108 | } 109 | 110 | /// Change the format and size of the window buffers. 111 | /// 112 | /// The width and height control the number of pixels in the buffers, not the dimensions of the 113 | /// window on screen. If these are different than the window's physical size, then its buffer 114 | /// will be scaled to match that size when compositing it to the screen. The width and height 115 | /// must be either both zero or both non-zero. 116 | /// 117 | /// For all of these parameters, if `0` or [`None`] is supplied then the window's base value 118 | /// will come back in force. 119 | pub fn set_buffers_geometry( 120 | &self, 121 | width: i32, 122 | height: i32, 123 | format: Option, 124 | ) -> io::Result<()> { 125 | let format = format.map_or(0i32, |f| f.into()); 126 | let status = unsafe { 127 | ffi::ANativeWindow_setBuffersGeometry(self.ptr.as_ptr(), width, height, format) 128 | }; 129 | status_to_io_result(status) 130 | } 131 | 132 | /// Set a transform that will be applied to future buffers posted to the window. 133 | #[cfg(all(feature = "nativewindow", feature = "api-level-26"))] 134 | #[doc(alias = "ANativeWindow_setBuffersTransform")] 135 | pub fn set_buffers_transform(&self, transform: NativeWindowTransform) -> io::Result<()> { 136 | let status = 137 | unsafe { ffi::ANativeWindow_setBuffersTransform(self.ptr.as_ptr(), transform.bits()) }; 138 | status_to_io_result(status) 139 | } 140 | 141 | /// All buffers queued after this call will be associated with the dataSpace parameter 142 | /// specified. 143 | /// 144 | /// `data_space` specifies additional information about the buffer. For example, it can be used 145 | /// to convey the color space of the image data in the buffer, or it can be used to indicate 146 | /// that the buffers contain depth measurement data instead of color images. The default 147 | /// dataSpace is `0`, [`DataSpace::Unknown`], unless it has been overridden by the producer. 148 | #[cfg(all(feature = "nativewindow", feature = "api-level-28"))] 149 | #[doc(alias = "ANativeWindow_setBuffersDataSpace")] 150 | pub fn set_buffers_data_space(&self, data_space: DataSpace) -> io::Result<()> { 151 | let status = 152 | unsafe { ffi::ANativeWindow_setBuffersDataSpace(self.ptr.as_ptr(), data_space.into()) }; 153 | status_to_io_result(status) 154 | } 155 | 156 | /// Get the dataspace of the buffers in this [`NativeWindow`]. 157 | #[cfg(all(feature = "nativewindow", feature = "api-level-28"))] 158 | #[doc(alias = "ANativeWindow_getBuffersDataSpace")] 159 | pub fn buffers_data_space(&self) -> io::Result { 160 | let status = unsafe { ffi::ANativeWindow_getBuffersDataSpace(self.ptr.as_ptr()) }; 161 | if status >= 0 { 162 | Ok(status.into()) 163 | } else { 164 | Err(status_to_io_result(status).unwrap_err()) 165 | } 166 | } 167 | 168 | /// Sets the intended frame rate for this window. 169 | /// 170 | /// Same as [`set_frame_rate_with_change_strategy(window, frame_rate, compatibility, ChangeFrameRateStrategy::OnlyIfSeamless)`][`NativeWindow::set_frame_rate_with_change_strategy()`]. 171 | /// 172 | #[cfg_attr( 173 | not(feature = "api-level-31"), 174 | doc = "[`NativeWindow::set_frame_rate_with_change_strategy()`]: https://developer.android.com/ndk/reference/group/a-native-window#anativewindow_setframeratewithchangestrategy" 175 | )] 176 | #[cfg(all(feature = "nativewindow", feature = "api-level-30"))] 177 | #[doc(alias = "ANativeWindow_setFrameRate")] 178 | pub fn set_frame_rate( 179 | &self, 180 | frame_rate: f32, 181 | compatibility: FrameRateCompatibility, 182 | ) -> io::Result<()> { 183 | let status = unsafe { 184 | ffi::ANativeWindow_setFrameRate(self.ptr.as_ptr(), frame_rate, compatibility as i8) 185 | }; 186 | status_to_io_result(status) 187 | } 188 | 189 | /// Sets the intended frame rate for this window. 190 | /// 191 | /// On devices that are capable of running the display at different refresh rates, the system 192 | /// may choose a display refresh rate to better match this window's frame rate. Usage of this 193 | /// API won't introduce frame rate throttling, or affect other aspects of the application's 194 | /// frame production pipeline. However, because the system may change the display refresh rate, 195 | /// calls to this function may result in changes to Choreographer callback timings, and changes 196 | /// to the time interval at which the system releases buffers back to the application. 197 | /// 198 | /// Note that this only has an effect for windows presented on the display. If this 199 | /// [`NativeWindow`] is consumed by something other than the system compositor, e.g. a media 200 | /// codec, this call has no effect. 201 | /// 202 | /// You can register for changes in the refresh rate using 203 | /// [`ffi::AChoreographer_registerRefreshRateCallback()`]. 204 | /// 205 | /// # Parameters 206 | /// 207 | /// - `frame_rate`: The intended frame rate of this window, in frames per second. `0` is a 208 | /// special value that indicates the app will accept the system's choice for the display 209 | /// frame rate, which is the default behavior if this function isn't called. The `frame_rate` 210 | /// param does not need to be a valid refresh rate for this device's display - e.g., it's 211 | /// fine to pass `30`fps to a device that can only run the display at `60`fps. 212 | /// - `compatibility`: The frame rate compatibility of this window. The compatibility value may 213 | /// influence the system's choice of display refresh rate. See the [`FrameRateCompatibility`] 214 | /// values for more info. This parameter is ignored when `frame_rate` is `0`. 215 | /// - `change_frame_rate_strategy`: Whether display refresh rate transitions caused by this 216 | /// window should be seamless. A seamless transition is one that doesn't have any visual 217 | /// interruptions, such as a black screen for a second or two. See the 218 | /// [`ChangeFrameRateStrategy`] values. This parameter is ignored when `frame_rate` is `0`. 219 | #[cfg(all(feature = "nativewindow", feature = "api-level-31"))] 220 | #[doc(alias = "ANativeWindow_setFrameRateWithChangeStrategy")] 221 | pub fn set_frame_rate_with_change_strategy( 222 | &self, 223 | frame_rate: f32, 224 | compatibility: FrameRateCompatibility, 225 | change_frame_rate_strategy: ChangeFrameRateStrategy, 226 | ) -> io::Result<()> { 227 | let status = unsafe { 228 | ffi::ANativeWindow_setFrameRateWithChangeStrategy( 229 | self.ptr.as_ptr(), 230 | frame_rate, 231 | compatibility as i8, 232 | change_frame_rate_strategy as i8, 233 | ) 234 | }; 235 | status_to_io_result(status) 236 | } 237 | 238 | /// Provides a hint to the window that buffers should be preallocated ahead of time. 239 | /// 240 | /// Note that the window implementation is not guaranteed to preallocate any buffers, for 241 | /// instance if an implementation disallows allocation of new buffers, or if there is 242 | /// insufficient memory in the system to preallocate additional buffers 243 | #[cfg(all(feature = "nativewindow", feature = "api-level-30"))] 244 | pub fn try_allocate_buffers(&self) { 245 | unsafe { ffi::ANativeWindow_tryAllocateBuffers(self.ptr.as_ptr()) } 246 | } 247 | 248 | /// Return the [`NativeWindow`] associated with a JNI [`android.view.Surface`] pointer. 249 | /// 250 | /// # Safety 251 | /// By calling this function, you assert that `env` is a valid pointer to a [`JNIEnv`] and 252 | /// `surface` is a valid pointer to an [`android.view.Surface`]. 253 | /// 254 | /// [`android.view.Surface`]: https://developer.android.com/reference/android/view/Surface 255 | pub unsafe fn from_surface(env: *mut JNIEnv, surface: jobject) -> Option { 256 | let ptr = ffi::ANativeWindow_fromSurface(env, surface); 257 | Some(Self::from_ptr(NonNull::new(ptr)?)) 258 | } 259 | 260 | /// Return a JNI [`android.view.Surface`] pointer derived from this [`NativeWindow`]. 261 | /// 262 | /// # Safety 263 | /// By calling this function, you assert that `env` is a valid pointer to a [`JNIEnv`]. 264 | /// 265 | /// [`android.view.Surface`]: https://developer.android.com/reference/android/view/Surface 266 | #[cfg(feature = "api-level-26")] 267 | pub unsafe fn to_surface(&self, env: *mut JNIEnv) -> jobject { 268 | ffi::ANativeWindow_toSurface(env, self.ptr().as_ptr()) 269 | } 270 | 271 | /// Lock the window's next drawing surface for writing. 272 | /// 273 | /// Optionally pass the region you intend to draw into `dirty_bounds`. When this function 274 | /// returns it is updated (commonly enlarged) with the actual area the caller needs to redraw. 275 | pub fn lock( 276 | &self, 277 | dirty_bounds: Option<&mut Rect>, 278 | ) -> io::Result> { 279 | let dirty_bounds = match dirty_bounds { 280 | Some(dirty_bounds) => dirty_bounds, 281 | None => std::ptr::null_mut(), 282 | }; 283 | let mut buffer = MaybeUninit::uninit(); 284 | let status = unsafe { 285 | ffi::ANativeWindow_lock(self.ptr.as_ptr(), buffer.as_mut_ptr(), dirty_bounds) 286 | }; 287 | status_to_io_result(status)?; 288 | 289 | Ok(NativeWindowBufferLockGuard { 290 | window: self, 291 | buffer: unsafe { buffer.assume_init() }, 292 | }) 293 | } 294 | } 295 | 296 | /// Lock holding the next drawing surface for writing. It is unlocked and posted on [`drop()`]. 297 | #[derive(Debug)] 298 | pub struct NativeWindowBufferLockGuard<'a> { 299 | window: &'a NativeWindow, 300 | buffer: ffi::ANativeWindow_Buffer, 301 | } 302 | 303 | impl NativeWindowBufferLockGuard<'_> { 304 | /// The number of pixels that are shown horizontally. 305 | pub fn width(&self) -> usize { 306 | usize::try_from(self.buffer.width).unwrap() 307 | } 308 | 309 | // The number of pixels that are shown vertically. 310 | pub fn height(&self) -> usize { 311 | usize::try_from(self.buffer.height).unwrap() 312 | } 313 | 314 | /// The number of _pixels_ that a line in the buffer takes in memory. 315 | /// 316 | /// This may be `>= width`. 317 | pub fn stride(&self) -> usize { 318 | usize::try_from(self.buffer.stride).unwrap() 319 | } 320 | 321 | /// The format of the buffer. One of [`HardwareBufferFormat`]. 322 | pub fn format(&self) -> HardwareBufferFormat { 323 | self.buffer.format.into() 324 | } 325 | 326 | /// The actual bits. 327 | /// 328 | /// This points to a memory segment of [`stride()`][Self::stride()] * 329 | /// [`height()`][Self::height()] * [`HardwareBufferFormat::bytes_per_pixel()`] bytes. 330 | /// 331 | /// Only [`width()`][Self::width()] pixels are visible for each [`stride()`][Self::stride()] 332 | /// line of pixels in the buffer. 333 | /// 334 | /// See [`bytes()`][Self::bytes()] for safe access to these bytes. 335 | pub fn bits(&mut self) -> *mut c_void { 336 | self.buffer.bits 337 | } 338 | 339 | /// Safe write access to likely uninitialized pixel buffer data. 340 | /// 341 | /// Returns [`None`] when there is no [`HardwareBufferFormat::bytes_per_pixel()`] size 342 | /// available for this [`format()`][Self::format()]. 343 | /// 344 | /// The returned slice consists of [`stride()`][Self::stride()] * [`height()`][Self::height()] 345 | /// \* [`HardwareBufferFormat::bytes_per_pixel()`] bytes. 346 | /// 347 | /// Only [`width()`][Self::width()] pixels are visible for each [`stride()`][Self::stride()] 348 | /// line of pixels in the buffer. 349 | pub fn bytes(&mut self) -> Option<&mut [MaybeUninit]> { 350 | let num_pixels = self.stride() * self.height(); 351 | let num_bytes = num_pixels * self.format().bytes_per_pixel()?; 352 | Some(unsafe { std::slice::from_raw_parts_mut(self.bits().cast(), num_bytes) }) 353 | } 354 | 355 | /// Returns a slice of bytes for each line of visible pixels in the buffer, ignoring any 356 | /// padding pixels incurred by the stride. 357 | /// 358 | /// See [`bits()`][Self::bits()] and [`bytes()`][Self::bytes()] for contiguous access to the 359 | /// underlying buffer. 360 | pub fn lines(&mut self) -> Option]>> { 361 | let bpp = self.format().bytes_per_pixel()?; 362 | let scanline_bytes = bpp * self.stride(); 363 | let width_bytes = bpp * self.width(); 364 | let bytes = self.bytes()?; 365 | 366 | Some( 367 | bytes 368 | .chunks_exact_mut(scanline_bytes) 369 | .map(move |scanline| &mut scanline[..width_bytes]), 370 | ) 371 | } 372 | } 373 | 374 | impl Drop for NativeWindowBufferLockGuard<'_> { 375 | fn drop(&mut self) { 376 | let ret = unsafe { ffi::ANativeWindow_unlockAndPost(self.window.ptr.as_ptr()) }; 377 | assert_eq!(ret, 0); 378 | } 379 | } 380 | 381 | #[cfg(all(feature = "nativewindow", feature = "api-level-26"))] 382 | bitflags::bitflags! { 383 | /// Transforms that can be applied to buffers as they are displayed to a window. 384 | /// 385 | /// Supported transforms are any combination of horizontal mirror, vertical mirror, and 386 | /// clockwise 90 degree rotation, in that order. Rotations of 180 and 270 degrees are made up 387 | /// of those basic transforms. 388 | #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] 389 | #[doc(alias = "ANativeWindowTransform")] 390 | pub struct NativeWindowTransform : i32 { 391 | #[doc(alias = "ANATIVEWINDOW_TRANSFORM_IDENTITY")] 392 | const IDENTITY = ffi::ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_IDENTITY.0 as i32; 393 | #[doc(alias = "ANATIVEWINDOW_TRANSFORM_MIRROR_HORIZONTAL")] 394 | const MIRROR_HORIZONTAL = ffi::ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_MIRROR_HORIZONTAL.0 as i32; 395 | #[doc(alias = "ANATIVEWINDOW_TRANSFORM_MIRROR_VERTICAL")] 396 | const MIRROR_VERTICAL = ffi::ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_MIRROR_VERTICAL.0 as i32; 397 | #[doc(alias = "ANATIVEWINDOW_TRANSFORM_ROTATE_90")] 398 | const ROTATE_90 = ffi::ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_ROTATE_90.0 as i32; 399 | /// Defined as [`Self::MIRROR_HORIZONTAL`] `|` [`Self::MIRROR_VERTICAL`]. 400 | #[doc(alias = "ANATIVEWINDOW_TRANSFORM_ROTATE_180")] 401 | const ROTATE_180 = ffi::ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_ROTATE_180.0 as i32; 402 | /// Defined as [`Self::ROTATE_180`] `|` [`Self::ROTATE_90`]. 403 | #[doc(alias = "ANATIVEWINDOW_TRANSFORM_ROTATE_270")] 404 | const ROTATE_270 = ffi::ANativeWindowTransform::ANATIVEWINDOW_TRANSFORM_ROTATE_270.0 as i32; 405 | 406 | // https://docs.rs/bitflags/latest/bitflags/#externally-defined-flags 407 | const _ = !0; 408 | } 409 | } 410 | 411 | /// Compatibility value for [`NativeWindow::set_frame_rate()`] 412 | #[cfg_attr( 413 | feature = "api-level-31", 414 | doc = " and [`NativeWindow::set_frame_rate_with_change_strategy()`]" 415 | )] 416 | /// . 417 | #[cfg(all(feature = "nativewindow", feature = "api-level-30"))] 418 | #[repr(i8)] 419 | #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] 420 | #[doc(alias = "ANativeWindow_FrameRateCompatibility")] 421 | #[non_exhaustive] 422 | pub enum FrameRateCompatibility { 423 | /// There are no inherent restrictions on the frame rate of this window. 424 | /// 425 | /// When the system selects a frame rate other than what the app requested, the app will be 426 | /// able to run at the system frame rate without requiring pull down. This value should be used 427 | /// when displaying game content, UIs, and anything that isn't video. 428 | #[doc(alias = "ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT")] 429 | Default = 430 | ffi::ANativeWindow_FrameRateCompatibility::ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_DEFAULT.0 as i8, 431 | /// This window is being used to display content with an inherently fixed frame rate, e.g. a 432 | /// video that has a specific frame rate. 433 | /// 434 | /// When the system selects a frame rate other than what the app requested, the app will need 435 | /// to do pull down or use some other technique to adapt to the system's frame rate. The user 436 | /// experience is likely to be worse (e.g. more frame stuttering) than it would be if the 437 | /// system had chosen the app's requested frame rate. This value should be used for video 438 | /// content. 439 | #[doc(alias = "ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE")] 440 | FixedSource = ffi::ANativeWindow_FrameRateCompatibility::ANATIVEWINDOW_FRAME_RATE_COMPATIBILITY_FIXED_SOURCE.0 as i8, 441 | } 442 | 443 | /// Change frame rate strategy value for [`NativeWindow::set_frame_rate_with_change_strategy()`]. 444 | #[cfg(all(feature = "nativewindow", feature = "api-level-31"))] 445 | #[repr(i8)] 446 | #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)] 447 | #[doc(alias = "ANativeWindow_ChangeFrameRateStrategy")] 448 | #[non_exhaustive] 449 | pub enum ChangeFrameRateStrategy { 450 | /// Change the frame rate only if the transition is going to be seamless. 451 | #[doc(alias = "ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS")] 452 | OnlyIfSeamless = 453 | ffi::ANativeWindow_ChangeFrameRateStrategy::ANATIVEWINDOW_CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS 454 | .0 as i8, 455 | /// Change the frame rate even if the transition is going to be non-seamless, i.e. with visual interruptions for the user. 456 | #[doc(alias = "ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS")] 457 | Always = 458 | ffi::ANativeWindow_ChangeFrameRateStrategy::ANATIVEWINDOW_CHANGE_FRAME_RATE_ALWAYS.0 as i8, 459 | } 460 | -------------------------------------------------------------------------------- /ndk/src/performance_hint.rs: -------------------------------------------------------------------------------- 1 | //! Bindings for [`APerformanceHintManager`] and [`APerformanceHintSession`] 2 | //! 3 | //! `APerformanceHint` allows apps to create performance hint sessions for groups of 4 | //! threads, and provide hints to the system about the workload of those threads, to help the 5 | //! system more accurately allocate power for them. It is the NDK counterpart to the [Java 6 | //! PerformanceHintManager SDK API]. 7 | //! 8 | //! [`APerformanceHintManager`]: https://developer.android.com/ndk/reference/group/a-performance-hint#aperformancehintmanager 9 | //! [`APerformanceHintSession`]: https://developer.android.com/ndk/reference/group/a-performance-hint#aperformancehintsession 10 | //! [Java PerformanceHintManager SDK API]: https://developer.android.com/reference/android/os/PerformanceHintManager 11 | #![cfg(feature = "api-level-33")] 12 | 13 | #[cfg(doc)] 14 | use std::io::ErrorKind; 15 | use std::{io::Result, ptr::NonNull, time::Duration}; 16 | 17 | use crate::utils::status_to_io_result; 18 | 19 | /// An opaque type representing a handle to a performance hint manager. 20 | /// 21 | /// To use: 22 | /// 1. Obtain the performance hint manager instance by calling [`PerformanceHintManager::new()`]. 23 | /// 2. Create a [`PerformanceHintSession`] with [`PerformanceHintManager::new()`]. 24 | /// 3. Get the preferred update rate with [`PerformanceHintManager::preferred_update_rate()`]. 25 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] 26 | #[doc(alias = "APerformanceHintManager")] 27 | pub struct PerformanceHintManager { 28 | ptr: NonNull, 29 | } 30 | 31 | // SAFETY: The NDK stores a per-process global singleton that is accessible from any thread, and the 32 | // only public methods perform thread-safe operations (i.e. the underlying AIBinder API to implement 33 | // `IHintManager->createHintSession()` is thread-safe). 34 | unsafe impl Send for PerformanceHintManager {} 35 | unsafe impl Sync for PerformanceHintManager {} 36 | 37 | impl PerformanceHintManager { 38 | /// Retrieve a reference to the performance hint manager. 39 | /// 40 | /// Returns [`None`] on failure. 41 | #[doc(alias = "APerformanceHint_getManager")] 42 | pub fn new() -> Option { 43 | NonNull::new(unsafe { ffi::APerformanceHint_getManager() }).map(|ptr| Self { ptr }) 44 | } 45 | 46 | /// Creates a session for the given set of threads and sets their initial target work duration. 47 | /// 48 | /// # Parameters 49 | /// - `thread_ids`: The list of threads to be associated with this session. They must be part of 50 | /// this process' thread group. 51 | /// - `initial_target_work_duration`: The target duration for the new session. This must be 52 | /// positive if using the work duration API, or [`Duration::ZERO`] otherwise. 53 | #[doc(alias = "APerformanceHint_createSession")] 54 | pub fn create_session( 55 | &self, 56 | thread_ids: &[i32], 57 | initial_target_work_duration: Duration, 58 | ) -> Option { 59 | NonNull::new(unsafe { 60 | ffi::APerformanceHint_createSession( 61 | self.ptr.as_ptr(), 62 | thread_ids.as_ptr(), 63 | thread_ids.len(), 64 | initial_target_work_duration 65 | .as_nanos() 66 | .try_into() 67 | .expect("Supplied duration is too large"), 68 | ) 69 | }) 70 | .map(|ptr| PerformanceHintSession { ptr }) 71 | } 72 | 73 | /// Get preferred update rate information for this device. 74 | /// 75 | /// Returns the preferred update rate supported by device software. 76 | #[doc(alias = "APerformanceHint_getPreferredUpdateRateNanos")] 77 | pub fn preferred_update_rate(&self) -> Duration { 78 | Duration::from_nanos(unsafe { 79 | ffi::APerformanceHint_getPreferredUpdateRateNanos(self.ptr.as_ptr()) 80 | .try_into() 81 | .expect("getPreferredUpdateRateNanos should not return negative") 82 | }) 83 | } 84 | } 85 | 86 | /// An opaque type representing a handle to a performance hint session. A session can only be 87 | /// acquired from a [`PerformanceHintManager`] with [`PerformanceHintManager::create_session()`]. 88 | /// It will be freed automatically on [`drop()`] after use. 89 | /// 90 | /// A Session represents a group of threads with an inter-related workload such that hints for their 91 | /// performance should be considered as a unit. The threads in a given session should be long-lived 92 | /// and not created or destroyed dynamically. 93 | /// 94 | /// The work duration API can be used with periodic workloads to dynamically adjust 95 | /// thread performance and keep the work on schedule while optimizing the available 96 | /// power budget. When using the work duration API, the starting target duration 97 | /// should be specified while creating the session, and can later be adjusted with 98 | /// [`PerformanceHintSession::update_target_work_duration()`]. While using the work duration API, 99 | /// the client is expected to call [`PerformanceHintSession::report_actual_work_duration()`] each 100 | /// cycle to report the actual time taken to complete to the system. 101 | /// 102 | /// All timings should be from [`ffi::CLOCK_MONOTONIC`]. 103 | #[derive(Debug, PartialEq, Eq, Hash)] 104 | #[doc(alias = "APerformanceHintSession")] 105 | pub struct PerformanceHintSession { 106 | ptr: NonNull, 107 | } 108 | 109 | impl PerformanceHintSession { 110 | /// Updates this session's target duration for each cycle of work. 111 | /// 112 | /// `target_duration` is the new desired duration. 113 | /// 114 | /// # Returns 115 | /// - [`ErrorKind::InvalidInput`] if `target_duration` is not positive. 116 | /// - [`ErrorKind::BrokenPipe`] if communication with the system service has failed. 117 | #[doc(alias = "APerformanceHint_updateTargetWorkDuration")] 118 | pub fn update_target_work_duration(&self, target_duration: Duration) -> Result<()> { 119 | status_to_io_result(unsafe { 120 | ffi::APerformanceHint_updateTargetWorkDuration( 121 | self.ptr.as_ptr(), 122 | target_duration 123 | .as_nanos() 124 | .try_into() 125 | .expect("Supplied duration is too large"), 126 | ) 127 | }) 128 | } 129 | 130 | /// Reports the actual duration for the last cycle of work. 131 | /// 132 | /// The system will attempt to adjust the scheduling and performance of the threads within the 133 | /// thread group to bring the actual duration close to the target duration. 134 | /// 135 | /// `actual_duration` is the duration of time the thread group took to complete its last task. 136 | /// 137 | /// # Returns 138 | /// - [`ErrorKind::InvalidInput`] if `actual_duration` is not positive. 139 | /// - [`ErrorKind::BrokenPipe`] if communication with the system service has failed. 140 | #[doc(alias = "APerformanceHint_reportActualWorkDuration")] 141 | pub fn report_actual_work_duration(&self, actual_duration: Duration) -> Result<()> { 142 | status_to_io_result(unsafe { 143 | ffi::APerformanceHint_reportActualWorkDuration( 144 | self.ptr.as_ptr(), 145 | actual_duration 146 | .as_nanos() 147 | .try_into() 148 | .expect("Supplied duration is too large"), 149 | ) 150 | }) 151 | } 152 | 153 | /// Set a list of threads to the performance hint session. This operation will replace the 154 | /// current list of threads with the given list of threads. 155 | /// 156 | /// `thread_ids` is the list of threads to be associated with this session. They must be part of 157 | /// this app's thread group. 158 | /// 159 | /// # Returns 160 | /// - [`ErrorKind::InvalidInput`] if the list of thread ids is empty or if any of the thread ids 161 | /// are not part of the thread group. 162 | /// - [`ErrorKind::BrokenPipe`] if communication with the system service has failed. 163 | /// - [`ErrorKind::PermissionDenied`] if any thread id doesn't belong to the application. 164 | #[cfg(feature = "api-level-34")] 165 | #[doc(alias = "APerformanceHint_setThreads")] 166 | pub fn set_threads(&self, thread_ids: &[i32]) -> Result<()> { 167 | status_to_io_result(unsafe { 168 | ffi::APerformanceHint_setThreads( 169 | self.ptr.as_ptr(), 170 | thread_ids.as_ptr(), 171 | thread_ids.len(), 172 | ) 173 | }) 174 | } 175 | 176 | /// This tells the session that these threads can be safely scheduled to prefer power efficiency 177 | /// over performance. 178 | /// 179 | /// `enabled` is the flag which sets whether this session will use power-efficient scheduling. 180 | /// 181 | /// # Returns 182 | /// - [`ErrorKind::BrokenPipe`] if communication with the system service has failed. 183 | #[cfg(feature = "api-level-35")] 184 | #[doc(alias = "APerformanceHint_setPreferPowerEfficiency")] 185 | pub fn set_prefer_power_efficiency(&self, enabled: bool) -> Result<()> { 186 | status_to_io_result(unsafe { 187 | ffi::APerformanceHint_setPreferPowerEfficiency(self.ptr.as_ptr(), enabled) 188 | }) 189 | } 190 | 191 | /// Reports the durations for the last cycle of work. 192 | /// 193 | /// The system will attempt to adjust the scheduling and performance of the threads within the 194 | /// thread group to bring the actual duration close to the target duration. 195 | /// 196 | /// # Parameters 197 | /// - `work_duration` is the [`WorkDuration`] structure of times the thread group took to 198 | /// complete its last task breaking down into different components. 199 | /// 200 | /// The work period start timestamp and actual total duration must be greater than zero. 201 | /// 202 | /// The actual CPU and GPU durations must be greater than or equal to [`Duration::ZERO`], and 203 | /// at least one of them must be greater than [`Duration::ZERO`]. When one of them is equal to 204 | /// [`Duration::ZERO`], it means that type of work was not measured for this workload. 205 | /// 206 | /// # Returns 207 | /// - [`ErrorKind::BrokenPipe`] if communication with the system service has failed. 208 | #[cfg(feature = "api-level-35")] 209 | #[doc(alias = "APerformanceHint_reportActualWorkDuration2")] 210 | pub fn report_actual_work_duration2(&self, work_duration: &WorkDuration) -> Result<()> { 211 | status_to_io_result(unsafe { 212 | ffi::APerformanceHint_reportActualWorkDuration2( 213 | self.ptr.as_ptr(), 214 | work_duration.ptr.as_ptr(), 215 | ) 216 | }) 217 | } 218 | } 219 | 220 | impl Drop for PerformanceHintSession { 221 | /// Release the performance hint manager pointer acquired via 222 | /// [`PerformanceHintManager::create_session()`]. 223 | #[doc(alias = "APerformanceHint_closeSession")] 224 | fn drop(&mut self) { 225 | unsafe { ffi::APerformanceHint_closeSession(self.ptr.as_ptr()) } 226 | } 227 | } 228 | 229 | #[cfg(feature = "api-level-35")] 230 | #[derive(Debug, PartialEq, Eq, Hash)] 231 | #[doc(alias = "AWorkDuration")] 232 | pub struct WorkDuration { 233 | ptr: NonNull, 234 | } 235 | 236 | #[cfg(feature = "api-level-35")] 237 | impl WorkDuration { 238 | /// Creates a new [`WorkDuration`]. When the client finishes using [`WorkDuration`], it will 239 | /// automatically be released on [`drop()`]. 240 | #[doc(alias = "AWorkDuration_create")] 241 | pub fn new() -> Self { 242 | Self { 243 | ptr: NonNull::new(unsafe { ffi::AWorkDuration_create() }) 244 | .expect("AWorkDuration_create should not return NULL"), 245 | } 246 | } 247 | 248 | /// Sets the work period start timestamp in nanoseconds. 249 | /// 250 | /// `work_period_start_timestamp` is the work period start timestamp based on 251 | /// [`ffi::CLOCK_MONOTONIC`] about when the work starts. This timestamp must be greater than 252 | /// [`Duration::ZERO`]. 253 | #[doc(alias = "AWorkDuration_setWorkPeriodStartTimestampNanos")] 254 | pub fn set_work_period_start_timestamp(&self, work_period_start_timestamp: Duration) { 255 | unsafe { 256 | ffi::AWorkDuration_setWorkPeriodStartTimestampNanos( 257 | self.ptr.as_ptr(), 258 | work_period_start_timestamp 259 | .as_nanos() 260 | .try_into() 261 | .expect("Supplied timestamp is too large"), 262 | ) 263 | } 264 | } 265 | 266 | /// Sets the actual total work duration in nanoseconds. 267 | /// 268 | /// `actual_total_duration` is the actual total work duration. This number must be greater 269 | /// than [`Duration::ZERO`]. 270 | #[doc(alias = "AWorkDuration_setActualTotalDurationNanos")] 271 | pub fn set_actual_total_duration(&self, actual_total_duration: Duration) { 272 | unsafe { 273 | ffi::AWorkDuration_setActualTotalDurationNanos( 274 | self.ptr.as_ptr(), 275 | actual_total_duration 276 | .as_nanos() 277 | .try_into() 278 | .expect("Supplied duration is too large"), 279 | ) 280 | } 281 | } 282 | 283 | /// Sets the actual CPU work duration in nanoseconds. 284 | /// 285 | /// `actual_cpu_duration` is the actual CPU work duration. If it is equal to 286 | /// [`Duration::ZERO`], that means the CPU was not measured. 287 | #[doc(alias = "AWorkDuration_setActualCpuDurationNanos")] 288 | pub fn set_actual_cpu_duration(&self, actual_cpu_duration: Duration) { 289 | unsafe { 290 | ffi::AWorkDuration_setActualCpuDurationNanos( 291 | self.ptr.as_ptr(), 292 | actual_cpu_duration 293 | .as_nanos() 294 | .try_into() 295 | .expect("Supplied duration is too large"), 296 | ) 297 | } 298 | } 299 | 300 | /// Sets the actual GPU work duration in nanoseconds. 301 | /// 302 | /// `actual_gpu_duration` is the actual GPU work duration. If it is equal to 303 | /// [`Duration::ZERO`], that means the GPU was not measured. 304 | #[doc(alias = "AWorkDuration_setActualGpuDurationNanos")] 305 | pub fn set_actual_gpu_duration(&self, actual_gpu_duration: Duration) { 306 | unsafe { 307 | ffi::AWorkDuration_setActualGpuDurationNanos( 308 | self.ptr.as_ptr(), 309 | actual_gpu_duration 310 | .as_nanos() 311 | .try_into() 312 | .expect("Supplied duration is too large"), 313 | ) 314 | } 315 | } 316 | } 317 | 318 | impl Default for WorkDuration { 319 | #[doc(alias = "AWorkDuration_create")] 320 | fn default() -> Self { 321 | Self::new() 322 | } 323 | } 324 | 325 | impl Drop for WorkDuration { 326 | /// Destroys [`WorkDuration`] and free all resources associated to it. 327 | #[doc(alias = "AWorkDuration_release")] 328 | fn drop(&mut self) { 329 | unsafe { ffi::AWorkDuration_release(self.ptr.as_ptr()) } 330 | } 331 | } 332 | -------------------------------------------------------------------------------- /ndk/src/shared_memory.rs: -------------------------------------------------------------------------------- 1 | //! Bindings for [`ASharedMemory`] 2 | //! 3 | //! [`ASharedMemory`]: https://developer.android.com/ndk/reference/group/memory 4 | #![cfg(feature = "api-level-26")] 5 | 6 | use std::{ 7 | ffi::CStr, 8 | io::{Error, Result}, 9 | // TODO: Import from std::os::fd::{} since Rust 1.66 10 | os::unix::io::{AsFd, AsRawFd, BorrowedFd, FromRawFd, IntoRawFd, OwnedFd, RawFd}, 11 | ptr, 12 | }; 13 | 14 | #[cfg(feature = "api-level-27")] 15 | use jni_sys::{jobject, JNIEnv}; 16 | 17 | /// Enables the creation, mapping, and protection control over anonymous shared memory. 18 | #[derive(Debug)] 19 | #[doc(alias = "ASharedMemory")] 20 | pub struct SharedMemory(OwnedFd); 21 | 22 | impl SharedMemory { 23 | /// Create a shared memory region. 24 | /// 25 | /// Creates shared memory region and returns a file descriptor. The resulting file descriptor 26 | /// can be `mmap`'ed to process memory space with `PROT_READ | PROT_WRITE | PROT_EXEC`. Access 27 | /// to this shared memory region can be restricted with [`set_prot()`][Self::set_prot()]. 28 | /// 29 | /// Use [`android.os.ParcelFileDescriptor`] to pass the file descriptor to another process. 30 | /// File descriptors may also be sent to other processes over a Unix domain socket with 31 | /// `sendmsg` and `SCM_RIGHTS`. See `sendmsg(3)` and `cmsg(3)` man pages for more information. 32 | /// 33 | /// If you intend to share this file descriptor with a child process after calling `exec(3)`, 34 | /// note that you will need to use `fcntl(2)` with `F_SETFD` to clear the `FD_CLOEXEC` flag for 35 | /// this to work on all versions of Android. 36 | /// 37 | /// [`android.os.ParcelFileDescriptor`]: https://developer.android.com/reference/android/os/ParcelFileDescriptor 38 | #[doc(alias = "ASharedMemory_create")] 39 | pub fn create(name: Option<&CStr>, size: usize) -> Result { 40 | let fd = 41 | unsafe { ffi::ASharedMemory_create(name.map_or(ptr::null(), |p| p.as_ptr()), size) }; 42 | if fd < 0 { 43 | Err(Error::last_os_error()) 44 | } else { 45 | Ok(unsafe { Self::from_raw_fd(fd) }) 46 | } 47 | } 48 | 49 | /// Returns a `dup`'d FD from the given Java [`android.os.SharedMemory`] object. 50 | /// 51 | /// The returned file descriptor has all the same properties & capabilities as the FD returned 52 | /// from [`create()`][Self::create()], however the protection flags will be the same as those 53 | /// of the [`android.os.SharedMemory`] object. 54 | /// 55 | /// [`android.os.SharedMemory`]: https://developer.android.com/reference/android/os/SharedMemory 56 | #[cfg(feature = "api-level-27")] 57 | #[doc(alias = "ASharedMemory_dupFromJava")] 58 | #[allow(clippy::not_unsafe_ptr_arg_deref)] 59 | pub fn dup_from_java(env: *mut JNIEnv, shared_memory: jobject) -> Result { 60 | let fd = unsafe { ffi::ASharedMemory_dupFromJava(env, shared_memory) }; 61 | if fd < 0 { 62 | Err(Error::last_os_error()) 63 | } else { 64 | Ok(unsafe { Self::from_raw_fd(fd) }) 65 | } 66 | } 67 | 68 | /// Get the size of the shared memory region. 69 | #[doc(alias = "ASharedMemory_getSize")] 70 | pub fn size(&self) -> usize { 71 | unsafe { ffi::ASharedMemory_getSize(self.as_raw_fd()) } 72 | } 73 | 74 | /// Restrict access of shared memory region. 75 | /// 76 | /// This function restricts access of a shared memory region. Access can only be removed. The 77 | /// effect applies globally to all file descriptors in all processes across the system that 78 | /// refer to this shared memory region. Existing memory mapped regions are not affected. 79 | /// 80 | /// It is a common use case to create a shared memory region, map it read/write locally to 81 | /// initialize content, and then send the shared memory to another process with read only 82 | /// access. Code example as below: 83 | /// 84 | /// ```no_run 85 | /// # use ndk::shared_memory::SharedMemory; 86 | /// # // TODO: Import from std::os::fd::{} since Rust 1.66 87 | /// # use std::os::unix::io::AsRawFd; 88 | /// # use std::ffi::CStr; 89 | /// # unsafe { 90 | /// let mem = SharedMemory::create(Some(CStr::from_bytes_with_nul_unchecked(b"memory\0")), 127).unwrap(); 91 | /// // By default it has PROT_READ | PROT_WRITE | PROT_EXEC. 92 | /// let size = mem.size(); 93 | /// let buffer = libc::mmap( 94 | /// std::ptr::null_mut(), 95 | /// size, 96 | /// libc::PROT_READ | libc::PROT_WRITE, 97 | /// libc::MAP_SHARED, 98 | /// mem.as_raw_fd(), 99 | /// 0, 100 | /// ); 101 | /// let buffer_slice = std::slice::from_raw_parts_mut(buffer.cast(), size); 102 | /// 103 | /// // trivially initialize content 104 | /// buffer_slice[..7].copy_from_slice(b"hello!\0"); 105 | /// 106 | /// // Existing mappings will retain their protection flags (PROT_WRITE here) after set_prod() 107 | /// // unless it is unmapped: 108 | /// libc::munmap(buffer, size); 109 | /// 110 | /// // limit access to read only 111 | /// mem.set_prot(libc::PROT_READ); 112 | /// 113 | /// // share fd with another process here and the other process can only map with PROT_READ. 114 | /// # } 115 | /// ``` 116 | #[doc(alias = "ASharedMemory_setProt")] 117 | pub fn set_prot(&self, prot: i32) -> Result<()> { 118 | let status = unsafe { ffi::ASharedMemory_setProt(self.as_raw_fd(), prot) }; 119 | if status < 0 { 120 | Err(Error::last_os_error()) 121 | } else { 122 | Ok(()) 123 | } 124 | } 125 | } 126 | 127 | impl AsFd for SharedMemory { 128 | fn as_fd(&self) -> BorrowedFd<'_> { 129 | self.0.as_fd() 130 | } 131 | } 132 | 133 | impl AsRawFd for SharedMemory { 134 | fn as_raw_fd(&self) -> RawFd { 135 | self.0.as_raw_fd() 136 | } 137 | } 138 | 139 | impl IntoRawFd for SharedMemory { 140 | fn into_raw_fd(self) -> RawFd { 141 | self.0.into_raw_fd() 142 | } 143 | } 144 | 145 | impl FromRawFd for SharedMemory { 146 | /// # Safety 147 | /// 148 | /// The resource pointed to by `fd` must be open and suitable for assuming 149 | /// ownership. The resource must not require any cleanup other than `close`. 150 | unsafe fn from_raw_fd(fd: RawFd) -> Self { 151 | Self(OwnedFd::from_raw_fd(fd)) 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /ndk/src/surface_texture.rs: -------------------------------------------------------------------------------- 1 | //! Bindings for [`ASurfaceTexture`] 2 | //! 3 | //! See for an architectural overview of 4 | //! [`SurfaceTexture`] internals. 5 | //! 6 | //! [`ASurfaceTexture`]: https://developer.android.com/ndk/reference/group/surface-texture 7 | #![cfg(feature = "api-level-28")] 8 | 9 | use crate::{native_window::NativeWindow, utils::status_to_io_result}; 10 | use jni_sys::{jobject, JNIEnv}; 11 | use std::{io::Result, ptr::NonNull, time::Duration}; 12 | 13 | /// An opaque type to manage [`android.graphics.SurfaceTexture`] from native code 14 | /// 15 | /// [`android.graphics.SurfaceTexture`]: https://developer.android.com/reference/android/graphics/SurfaceTexture 16 | #[derive(Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 17 | pub struct SurfaceTexture { 18 | ptr: NonNull, 19 | } 20 | 21 | unsafe impl Send for SurfaceTexture {} 22 | 23 | impl Drop for SurfaceTexture { 24 | fn drop(&mut self) { 25 | unsafe { ffi::ASurfaceTexture_release(self.ptr.as_ptr()) } 26 | } 27 | } 28 | 29 | impl SurfaceTexture { 30 | /// Assumes ownership of `ptr` 31 | /// 32 | /// # Safety 33 | /// `ptr` must be a valid pointer to an Android [`ffi::ASurfaceTexture`]. 34 | pub unsafe fn from_ptr(ptr: NonNull) -> Self { 35 | Self { ptr } 36 | } 37 | 38 | /// Get a reference to the native [`SurfaceTexture`] from the corresponding Java object. 39 | /// 40 | /// # Safety 41 | /// 42 | /// This function should be called with a healthy JVM pointer and with a non-null 43 | /// [`android.graphics.SurfaceTexture`], which must be kept alive on the Java/Kotlin side. 44 | /// 45 | /// The caller must keep a reference to the Java [`android.graphics.SurfaceTexture`] during the 46 | /// lifetime of the returned [`SurfaceTexture`]. Failing to do so could result in the 47 | /// [`SurfaceTexture`] to stop functioning properly once the Java object gets finalized. 48 | /// However, this will not result in program termination. 49 | /// 50 | /// [`android.graphics.SurfaceTexture`]: https://developer.android.com/reference/android/graphics/SurfaceTexture 51 | pub unsafe fn from_surface_texture(env: *mut JNIEnv, surface_texture: jobject) -> Option { 52 | let a_surface_texture_ptr = ffi::ASurfaceTexture_fromSurfaceTexture(env, surface_texture); 53 | let s = NonNull::new(a_surface_texture_ptr)?; 54 | Some(SurfaceTexture::from_ptr(s)) 55 | } 56 | 57 | /// Returns a pointer to the native [`ffi::ASurfaceTexture`]. 58 | pub fn ptr(&self) -> NonNull { 59 | self.ptr 60 | } 61 | 62 | /// Returns a reference to a [`NativeWindow`] (i.e. the Producer) for this [`SurfaceTexture`]. 63 | /// 64 | /// This is equivalent to Java's: 65 | /// ```java 66 | /// Surface sur = new Surface(surfaceTexture); 67 | /// ``` 68 | pub fn acquire_native_window(&self) -> Option { 69 | let native_window = unsafe { ffi::ASurfaceTexture_acquireANativeWindow(self.ptr.as_ptr()) }; 70 | let n = NonNull::new(native_window)?; 71 | Some(unsafe { NativeWindow::from_ptr(n) }) 72 | } 73 | 74 | /// Attach the [`SurfaceTexture`] to the OpenGL ES context that is current on the calling 75 | /// thread. 76 | /// 77 | /// A new OpenGL ES texture object is created and populated with the [`SurfaceTexture`] image 78 | /// frame that was current at the time of the last call to 79 | /// [`detach_from_gl_context()`][Self::detach_from_gl_context()]. This new texture is bound to 80 | /// the `GL_TEXTURE_EXTERNAL_OES` texture target. 81 | /// 82 | /// This can be used to access the [`SurfaceTexture`] image contents from multiple OpenGL ES 83 | /// contexts. Note, however, that the image contents are only accessible from one OpenGL ES 84 | /// context at a time. 85 | pub fn attach_to_gl_context(&self, tex_name: u32) -> Result<()> { 86 | let status = unsafe { ffi::ASurfaceTexture_attachToGLContext(self.ptr.as_ptr(), tex_name) }; 87 | status_to_io_result(status) 88 | } 89 | 90 | /// Detach the [`SurfaceTexture`] from the OpenGL ES context that owns the OpenGL ES texture 91 | /// object. 92 | /// 93 | /// This call must be made with the OpenGL ES context current on the calling thread. The OpenGL 94 | /// ES texture object will be deleted as a result of this call. After calling this method all 95 | /// calls to [`update_tex_image()`][Self::update_tex_image()] will fail until a successful call 96 | /// to [`attach_to_gl_context()`][Self::attach_to_gl_context()] is made. 97 | /// 98 | /// This can be used to access the [`SurfaceTexture`] image contents from multiple OpenGL ES 99 | /// contexts. Note, however, that the image contents are only accessible from one OpenGL ES 100 | /// context at a time. 101 | pub fn detach_from_gl_context(&self) -> Result<()> { 102 | let status = unsafe { ffi::ASurfaceTexture_detachFromGLContext(self.ptr.as_ptr()) }; 103 | status_to_io_result(status) 104 | } 105 | 106 | /// Retrieve the 4x4 texture coordinate transform matrix associated with the texture image set 107 | /// by the most recent call to [`update_tex_image()`][Self::update_tex_image()]. 108 | /// 109 | /// This transform matrix maps 2D homogeneous texture coordinates of the form `(s, t, 0, 1)` 110 | /// with `s` and `t` in the inclusive range `[0, 1]` to the texture coordinate that should be 111 | /// used to sample that location from the texture. Sampling the texture outside of the range of 112 | /// this transform is undefined. 113 | /// 114 | /// The matrix is stored in column-major order so that it may be passed directly to OpenGL ES 115 | /// via the [`glLoadMatrixf()`] or [`glUniformMatrix4fv()`] functions. 116 | /// 117 | /// [`glLoadMatrixf()`]: https://www.khronos.org/registry/OpenGL-Refpages/gl2.1/xhtml/glLoadMatrix.xml 118 | /// [`gluniformmatrix4fv()`]: https://www.khronos.org/registry/OpenGL-Refpages/es3.1/html/glUniform.xhtml 119 | pub fn transform_matrix(&self) -> [f32; 16] { 120 | let mut r = [0f32; 16]; 121 | unsafe { ffi::ASurfaceTexture_getTransformMatrix(self.ptr.as_ptr(), r.as_mut_ptr()) }; 122 | r 123 | } 124 | 125 | /// Retrieve the timestamp associated with the texture image set by the most recent call to 126 | /// [`update_tex_image()`][Self::update_tex_image()]. 127 | /// 128 | /// This timestamp is in nanoseconds, and is normally monotonically increasing. The timestamp 129 | /// should be unaffected by time-of-day adjustments, and for a camera should be strictly 130 | /// monotonic but for a [`MediaPlayer`] may be reset when the position is set. The specific 131 | /// meaning and zero point of the timestamp depends on the source providing images to the 132 | /// [`SurfaceTexture`]. Unless otherwise specified by the image source, timestamps cannot 133 | /// generally be compared across [`SurfaceTexture`] instances, or across multiple program 134 | /// invocations. It is mostly useful for determining time offsets between subsequent frames. 135 | /// 136 | /// For EGL/Vulkan producers, this timestamp is the desired present time set with the 137 | /// [`EGL_ANDROID_presentation_time`] or [`VK_GOOGLE_display_timing`] extensions. 138 | /// 139 | /// [`MediaPlayer`]: https://developer.android.com/reference/android/media/MediaPlayer 140 | /// [`EGL_ANDROID_presentation_time`]: https://www.khronos.org/registry/EGL/extensions/ANDROID/EGL_ANDROID_presentation_time.txt 141 | /// [`VK_GOOGLE_display_timing`]: https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VK_GOOGLE_display_timing.html 142 | pub fn timestamp(&self) -> Duration { 143 | Duration::from_nanos( 144 | unsafe { ffi::ASurfaceTexture_getTimestamp(self.ptr.as_ptr()) } 145 | .try_into() 146 | .expect("Timestamp cannot be negative"), 147 | ) 148 | } 149 | 150 | /// Update the texture image to the most recent frame from the image stream. 151 | /// 152 | /// This may only be called while the OpenGL ES context that owns the texture is current on the 153 | /// calling thread. It will implicitly bind its texture to the `GL_TEXTURE_EXTERNAL_OES` 154 | /// texture target. 155 | pub fn update_tex_image(&self) -> Result<()> { 156 | let status = unsafe { ffi::ASurfaceTexture_updateTexImage(self.ptr.as_ptr()) }; 157 | status_to_io_result(status) 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /ndk/src/sync.rs: -------------------------------------------------------------------------------- 1 | //! Bindings for [sync functions] 2 | //! 3 | //! [sync functions]: https://developer.android.com/ndk/reference/group/sync 4 | #![cfg(feature = "sync")] 5 | 6 | use std::{ 7 | ffi::CStr, 8 | fmt::Debug, 9 | // TODO: Import from std::os::fd::{} since Rust 1.66 10 | os::unix::io::{AsRawFd, BorrowedFd, FromRawFd, OwnedFd}, 11 | ptr::NonNull, 12 | }; 13 | 14 | #[doc(alias = "sync_file_info")] 15 | #[repr(transparent)] 16 | pub struct SyncFileInfo { 17 | inner: NonNull, 18 | } 19 | 20 | impl Debug for SyncFileInfo { 21 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 22 | f.debug_struct("SyncFileInfo") 23 | .field("name", &self.name()) 24 | .field("status", &self.status()) 25 | .field("flags", &self.flags()) 26 | .field("num_fences", &self.num_fences()) 27 | .field("fence_info", &self.fence_info()) 28 | .finish() 29 | } 30 | } 31 | 32 | impl SyncFileInfo { 33 | /// Retrieve detailed information about a sync file and its fences. 34 | #[doc(alias = "sync_file_info")] 35 | pub fn new(fd: BorrowedFd<'_>) -> Option { 36 | let inner = NonNull::new(unsafe { ffi::sync_file_info(fd.as_raw_fd()) })?; 37 | Some(Self { inner }) 38 | } 39 | 40 | pub fn name(&self) -> &CStr { 41 | let inner = unsafe { self.inner.as_ref() }; 42 | // TODO: Switch to CStr::from_bytes_until_nul (with c_char -> u8 transmute) since MSRV 1.69 43 | // https://github.com/ash-rs/ash/pull/746 44 | unsafe { CStr::from_ptr(inner.name.as_ptr()) } 45 | } 46 | 47 | pub fn status(&self) -> i32 { 48 | let inner = unsafe { self.inner.as_ref() }; 49 | inner.status 50 | } 51 | 52 | pub fn flags(&self) -> u32 { 53 | let inner = unsafe { self.inner.as_ref() }; 54 | inner.flags 55 | } 56 | 57 | pub fn num_fences(&self) -> usize { 58 | let inner = unsafe { self.inner.as_ref() }; 59 | inner.num_fences as usize 60 | } 61 | 62 | /// Get the array of fence infos from the sync file's info. 63 | #[doc(alias = "sync_get_fence_info")] 64 | pub fn fence_info(&self) -> &[SyncFenceInfo] { 65 | let inner = unsafe { self.inner.as_ref() }; 66 | 67 | if inner.num_fences == 0 { 68 | &[] 69 | } else { 70 | let sync_fence_info = NonNull::new(inner.sync_fence_info as *mut _) 71 | .expect("sync_fence_info cannot be null if num_fences > 0"); 72 | unsafe { 73 | std::slice::from_raw_parts(sync_fence_info.as_ptr(), inner.num_fences as usize) 74 | } 75 | } 76 | } 77 | } 78 | 79 | impl Drop for SyncFileInfo { 80 | /// Free a [`struct@ffi::sync_file_info`] structure. 81 | #[doc(alias = "sync_file_info_free")] 82 | fn drop(&mut self) { 83 | unsafe { ffi::sync_file_info_free(self.inner.as_ptr()) } 84 | } 85 | } 86 | 87 | #[doc(alias = "sync_fence_info")] 88 | #[repr(transparent)] 89 | pub struct SyncFenceInfo(ffi::sync_fence_info); 90 | 91 | impl Debug for SyncFenceInfo { 92 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 93 | f.debug_struct("SyncFenceInfo") 94 | .field("obj_name", &self.obj_name()) 95 | .field("driver_name", &self.driver_name()) 96 | .field("status", &self.status()) 97 | .field("flags", &self.flags()) 98 | .field("timestamp_ns", &self.timestamp_ns()) 99 | .finish() 100 | } 101 | } 102 | 103 | impl SyncFenceInfo { 104 | pub fn obj_name(&self) -> &CStr { 105 | // TODO: Switch to CStr::from_bytes_until_nul (with c_char -> u8 transmute) since MSRV 1.69 106 | unsafe { CStr::from_ptr(self.0.obj_name.as_ptr()) } 107 | } 108 | 109 | pub fn driver_name(&self) -> &CStr { 110 | // TODO: Switch to CStr::from_bytes_until_nul (with c_char -> u8 transmute) since MSRV 1.69 111 | unsafe { CStr::from_ptr(self.0.driver_name.as_ptr()) } 112 | } 113 | 114 | pub fn status(&self) -> i32 { 115 | self.0.status 116 | } 117 | 118 | pub fn flags(&self) -> u32 { 119 | self.0.flags 120 | } 121 | 122 | pub fn timestamp_ns(&self) -> u64 { 123 | self.0.timestamp_ns 124 | } 125 | } 126 | 127 | /// Merge two sync files. 128 | /// 129 | /// This produces a new sync file with the given name which has the union of the two original sync 130 | /// file's fences; redundant fences may be removed. 131 | /// 132 | /// If one of the input sync files is signaled or invalid, then this function may behave like 133 | /// `dup()`: the new file descriptor refers to the valid/unsignaled sync file with its original 134 | /// name, rather than a new sync file. 135 | pub fn sync_merge(name: &CStr, fd1: BorrowedFd<'_>, fd2: BorrowedFd<'_>) -> OwnedFd { 136 | unsafe { 137 | OwnedFd::from_raw_fd(ffi::sync_merge( 138 | name.as_ptr(), 139 | fd1.as_raw_fd(), 140 | fd2.as_raw_fd(), 141 | )) 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /ndk/src/trace.rs: -------------------------------------------------------------------------------- 1 | //! Bindings for the NDK tracing API. 2 | //! 3 | //! See also [the NDK docs](https://developer.android.com/ndk/reference/group/tracing) 4 | #![cfg(feature = "api-level-23")] 5 | use std::ffi::{CString, NulError}; 6 | use std::marker::PhantomData; 7 | 8 | pub fn is_trace_enabled() -> bool { 9 | unsafe { ffi::ATrace_isEnabled() } 10 | } 11 | 12 | #[derive(Debug)] 13 | pub struct Section { 14 | // Section is !Sync and !Send 15 | _pd: PhantomData<*mut ()>, 16 | } 17 | 18 | impl Section { 19 | pub fn new(name: &str) -> Result { 20 | let section_name = CString::new(name)?; 21 | unsafe { ffi::ATrace_beginSection(section_name.as_ptr()) }; 22 | 23 | Ok(Self { _pd: PhantomData }) 24 | } 25 | 26 | pub fn end(self) { 27 | drop(self) 28 | } 29 | } 30 | 31 | impl Drop for Section { 32 | fn drop(&mut self) { 33 | unsafe { ffi::ATrace_endSection() }; 34 | } 35 | } 36 | 37 | /// Unique identifier for distinguishing simultaneous events 38 | #[derive(Debug)] 39 | #[cfg(feature = "api-level-29")] 40 | pub struct Cookie(pub i32); 41 | 42 | #[derive(Debug)] 43 | #[cfg(feature = "api-level-29")] 44 | pub struct AsyncSection { 45 | section_name: CString, 46 | cookie: Cookie, 47 | // AsyncSection is !Sync 48 | _pd: PhantomData<&'static ()>, 49 | } 50 | 51 | #[cfg(feature = "api-level-29")] 52 | impl AsyncSection { 53 | pub fn new(name: &str, cookie: Cookie) -> Result { 54 | let section_name = CString::new(name)?; 55 | unsafe { ffi::ATrace_beginAsyncSection(section_name.as_ptr(), cookie.0) }; 56 | 57 | Ok(Self { 58 | section_name, 59 | cookie, 60 | _pd: PhantomData, 61 | }) 62 | } 63 | 64 | pub fn end(self) { 65 | drop(self) 66 | } 67 | } 68 | 69 | #[cfg(feature = "api-level-29")] 70 | impl Drop for AsyncSection { 71 | fn drop(&mut self) { 72 | unsafe { ffi::ATrace_endAsyncSection(self.section_name.as_ptr(), self.cookie.0) }; 73 | } 74 | } 75 | 76 | #[cfg(feature = "api-level-29")] 77 | #[derive(Debug)] 78 | pub struct Counter { 79 | name: CString, 80 | } 81 | 82 | #[cfg(feature = "api-level-29")] 83 | impl Counter { 84 | pub fn new(name: &str) -> Result { 85 | let name = CString::new(name)?; 86 | Ok(Self { name }) 87 | } 88 | 89 | pub fn set_value(&self, value: i64) { 90 | unsafe { ffi::ATrace_setCounter(self.name.as_ptr(), value) } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /ndk/src/utils.rs: -------------------------------------------------------------------------------- 1 | //! Internal utilities 2 | use log::{error, log_enabled, Level}; 3 | use std::ffi::{c_int, CStr, CString}; 4 | use std::io::{Error, Result}; 5 | 6 | /// Turns standard `` status codes - typically rewrapped by Android's [`Errors.h`] - into 7 | /// Rust's [`std::io::Error`]. 8 | /// 9 | /// [`Errors.h`]: https://cs.android.com/android/platform/superproject/+/master:system/core/libutils/include/utils/Errors.h 10 | pub(crate) fn status_to_io_result(status: i32) -> Result<()> { 11 | match status { 12 | 0 => Ok(()), 13 | r if r < 0 => Err(Error::from_raw_os_error(-r)), 14 | r => unreachable!("Status is positive integer {}", r), 15 | } 16 | } 17 | 18 | pub(crate) fn android_log(level: Level, tag: &CStr, msg: &CStr) { 19 | let prio = match level { 20 | Level::Error => ffi::android_LogPriority::ANDROID_LOG_ERROR, 21 | Level::Warn => ffi::android_LogPriority::ANDROID_LOG_WARN, 22 | Level::Info => ffi::android_LogPriority::ANDROID_LOG_INFO, 23 | Level::Debug => ffi::android_LogPriority::ANDROID_LOG_DEBUG, 24 | Level::Trace => ffi::android_LogPriority::ANDROID_LOG_VERBOSE, 25 | }; 26 | unsafe { 27 | ffi::__android_log_write(prio.0 as c_int, tag.as_ptr(), msg.as_ptr()); 28 | } 29 | } 30 | 31 | pub(crate) fn log_panic(panic: Box) { 32 | fn log_panic(panic_str: &str) { 33 | const RUST_PANIC_TAG: &CStr = 34 | unsafe { CStr::from_bytes_with_nul_unchecked(b"RustPanic\0") }; 35 | 36 | let panic_str = CString::new(panic_str).unwrap_or_default(); 37 | 38 | // Use the Rust logger if installed and enabled, otherwise fall back to the Android system 39 | // logger so there is at least some record of the panic 40 | if log_enabled!(Level::Error) { 41 | error!("RustPanic: {}", panic_str.to_string_lossy()); 42 | log::logger().flush(); 43 | } else { 44 | android_log(Level::Error, RUST_PANIC_TAG, &panic_str); 45 | } 46 | } 47 | 48 | match panic.downcast::() { 49 | Ok(panic_string) => log_panic(&panic_string), 50 | Err(panic) => match panic.downcast::<&str>() { 51 | Ok(panic_str) => log_panic(&panic_str), 52 | Err(_) => log_panic("Unknown panic message type"), 53 | }, 54 | } 55 | } 56 | 57 | /// Run a closure and abort the program if it panics. 58 | /// 59 | /// This is generally used to ensure Rust callbacks won't unwind past the FFI boundary, which leads 60 | /// to undefined behaviour. 61 | pub(crate) fn abort_on_panic(f: impl FnOnce() -> R) -> R { 62 | std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)).unwrap_or_else(|panic| { 63 | // Try logging the panic before aborting 64 | // 65 | // Just in case our attempt to log a panic could itself cause a panic we use a 66 | // second catch_unwind here. 67 | let _ = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| log_panic(panic))); 68 | std::process::abort(); 69 | }) 70 | } 71 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | format_code_in_doc_comments = true 2 | --------------------------------------------------------------------------------