From 246b11925c1fa97cf0a1670888a4afe513109bf1 Mon Sep 17 00:00:00 2001 From: Gilles Boccon-Gibod Date: Fri, 6 Oct 2023 12:52:56 -0700 Subject: [PATCH 1/3] add remote hci android app --- docs/mkdocs/mkdocs.yml | 3 + docs/mkdocs/src/extras/android_remote_hci.md | 119 +++ docs/mkdocs/src/extras/index.md | 11 + extras/android/RemoteHCI/.gitignore | 10 + extras/android/RemoteHCI/app/.gitignore | 1 + extras/android/RemoteHCI/app/build.gradle.kts | 73 ++ .../android/RemoteHCI/app/proguard-rules.pro | 21 + .../app/src/main/AndroidManifest.xml | 30 + .../hardware/bluetooth/IBluetoothHci.aidl | 43 + .../bluetooth/IBluetoothHciCallbacks.aidl | 42 + .../android/hardware/bluetooth/Status.aidl | 43 + .../main/hidl/bluetooth/1.1/IBluetoothHci.hal | 44 + .../bluetooth/1.1/IBluetoothHciCallbacks.hal | 31 + .../app/src/main/ic_launcher-playstore.png | Bin 0 -> 44519 bytes .../hardware/bluetooth/IBluetoothHci.java | 259 ++++++ .../bluetooth/IBluetoothHciCallbacks.java | 234 +++++ .../android/hardware/bluetooth/Status.java | 11 + .../bluetooth/V1_0/IBluetoothHci.java | 816 +++++++++++++++++ .../V1_0/IBluetoothHciCallbacks.java | 762 ++++++++++++++++ .../hardware/bluetooth/V1_0/Status.java | 48 + .../bluetooth/V1_1/IBluetoothHci.java | 840 ++++++++++++++++++ .../V1_1/IBluetoothHciCallbacks.java | 774 ++++++++++++++++ .../google/bumble/remotehci/HciHal.java | 260 ++++++ .../bumble/remotehci/HciHalCallback.java | 5 + .../google/bumble/remotehci/HciPacket.java | 69 ++ .../google/bumble/remotehci/HciParser.java | 82 ++ .../google/bumble/remotehci/HciProxy.java | 128 +++ .../google/bumble/remotehci/HciServer.java | 93 ++ .../google/bumble/remotehci/MainActivity.kt | 225 +++++ .../google/bumble/remotehci/ui/theme/Color.kt | 11 + .../google/bumble/remotehci/ui/theme/Theme.kt | 70 ++ .../google/bumble/remotehci/ui/theme/Type.kt | 34 + .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + .../src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 0 -> 2600 bytes .../mipmap-hdpi/ic_launcher_foreground.webp | Bin 0 -> 3472 bytes .../res/mipmap-hdpi/ic_launcher_round.webp | Bin 0 -> 4408 bytes .../src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 0 -> 1624 bytes .../mipmap-mdpi/ic_launcher_foreground.webp | Bin 0 -> 2408 bytes .../res/mipmap-mdpi/ic_launcher_round.webp | Bin 0 -> 2686 bytes .../main/res/mipmap-xhdpi/ic_launcher.webp | Bin 0 -> 3752 bytes .../mipmap-xhdpi/ic_launcher_foreground.webp | Bin 0 -> 4650 bytes .../res/mipmap-xhdpi/ic_launcher_round.webp | Bin 0 -> 6138 bytes .../main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 0 -> 5676 bytes .../mipmap-xxhdpi/ic_launcher_foreground.webp | Bin 0 -> 7088 bytes .../res/mipmap-xxhdpi/ic_launcher_round.webp | Bin 0 -> 9808 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 0 -> 7518 bytes .../ic_launcher_foreground.webp | Bin 0 -> 9474 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 0 -> 13260 bytes .../app/src/main/res/values/colors.xml | 10 + .../res/values/ic_launcher_background.xml | 4 + .../app/src/main/res/values/strings.xml | 3 + .../app/src/main/res/values/themes.xml | 5 + .../app/src/main/res/xml/backup_rules.xml | 13 + .../main/res/xml/data_extraction_rules.xml | 19 + extras/android/RemoteHCI/build.gradle.kts | 8 + extras/android/RemoteHCI/gradle.properties | 23 + .../RemoteHCI/gradle/libs.versions.toml | 36 + .../gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59203 bytes .../gradle/wrapper/gradle-wrapper.properties | 6 + extras/android/RemoteHCI/gradlew | 185 ++++ extras/android/RemoteHCI/gradlew.bat | 89 ++ extras/android/RemoteHCI/lib/.gitignore | 1 + extras/android/RemoteHCI/lib/build.gradle.kts | 39 + .../android/RemoteHCI/lib/consumer-rules.pro | 0 .../android/RemoteHCI/lib/proguard-rules.pro | 21 + .../lib/src/main/AndroidManifest.xml | 4 + .../internal/hidl/base/V1_0/DebugInfo.java | 17 + .../internal/hidl/base/V1_0/IBase.java | 20 + .../src/main/java/android/os/HidlSupport.java | 8 + .../src/main/java/android/os/HwBinder.java | 40 + .../lib/src/main/java/android/os/HwBlob.java | 13 + .../src/main/java/android/os/HwParcel.java | 107 +++ .../src/main/java/android/os/IHwBinder.java | 11 + .../main/java/android/os/IHwInterface.java | 5 + .../main/java/android/os/NativeHandle.java | 4 + .../main/java/android/os/ServiceManager.java | 7 + .../scripts/generate_java_from_aidl.sh | 32 + extras/android/RemoteHCI/settings.gradle.kts | 18 + 79 files changed, 5950 insertions(+) create mode 100644 docs/mkdocs/src/extras/android_remote_hci.md create mode 100644 docs/mkdocs/src/extras/index.md create mode 100644 extras/android/RemoteHCI/.gitignore create mode 100644 extras/android/RemoteHCI/app/.gitignore create mode 100644 extras/android/RemoteHCI/app/build.gradle.kts create mode 100644 extras/android/RemoteHCI/app/proguard-rules.pro create mode 100644 extras/android/RemoteHCI/app/src/main/AndroidManifest.xml create mode 100644 extras/android/RemoteHCI/app/src/main/aidl/android/hardware/bluetooth/IBluetoothHci.aidl create mode 100644 extras/android/RemoteHCI/app/src/main/aidl/android/hardware/bluetooth/IBluetoothHciCallbacks.aidl create mode 100644 extras/android/RemoteHCI/app/src/main/aidl/android/hardware/bluetooth/Status.aidl create mode 100644 extras/android/RemoteHCI/app/src/main/hidl/bluetooth/1.1/IBluetoothHci.hal create mode 100644 extras/android/RemoteHCI/app/src/main/hidl/bluetooth/1.1/IBluetoothHciCallbacks.hal create mode 100644 extras/android/RemoteHCI/app/src/main/ic_launcher-playstore.png create mode 100644 extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/IBluetoothHci.java create mode 100644 extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/IBluetoothHciCallbacks.java create mode 100644 extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/Status.java create mode 100644 extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/V1_0/IBluetoothHci.java create mode 100644 extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/V1_0/IBluetoothHciCallbacks.java create mode 100644 extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/V1_0/Status.java create mode 100644 extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/V1_1/IBluetoothHci.java create mode 100644 extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/V1_1/IBluetoothHciCallbacks.java create mode 100644 extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/HciHal.java create mode 100644 extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/HciHalCallback.java create mode 100644 extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/HciPacket.java create mode 100644 extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/HciParser.java create mode 100644 extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/HciProxy.java create mode 100644 extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/HciServer.java create mode 100644 extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/MainActivity.kt create mode 100644 extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/ui/theme/Color.kt create mode 100644 extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/ui/theme/Theme.kt create mode 100644 extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/ui/theme/Type.kt create mode 100644 extras/android/RemoteHCI/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 extras/android/RemoteHCI/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 extras/android/RemoteHCI/app/src/main/res/mipmap-hdpi/ic_launcher.webp create mode 100644 extras/android/RemoteHCI/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp create mode 100644 extras/android/RemoteHCI/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp create mode 100644 extras/android/RemoteHCI/app/src/main/res/mipmap-mdpi/ic_launcher.webp create mode 100644 extras/android/RemoteHCI/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp create mode 100644 extras/android/RemoteHCI/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp create mode 100644 extras/android/RemoteHCI/app/src/main/res/mipmap-xhdpi/ic_launcher.webp create mode 100644 extras/android/RemoteHCI/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp create mode 100644 extras/android/RemoteHCI/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp create mode 100644 extras/android/RemoteHCI/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp create mode 100644 extras/android/RemoteHCI/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp create mode 100644 extras/android/RemoteHCI/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp create mode 100644 extras/android/RemoteHCI/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp create mode 100644 extras/android/RemoteHCI/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp create mode 100644 extras/android/RemoteHCI/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp create mode 100644 extras/android/RemoteHCI/app/src/main/res/values/colors.xml create mode 100644 extras/android/RemoteHCI/app/src/main/res/values/ic_launcher_background.xml create mode 100644 extras/android/RemoteHCI/app/src/main/res/values/strings.xml create mode 100644 extras/android/RemoteHCI/app/src/main/res/values/themes.xml create mode 100644 extras/android/RemoteHCI/app/src/main/res/xml/backup_rules.xml create mode 100644 extras/android/RemoteHCI/app/src/main/res/xml/data_extraction_rules.xml create mode 100644 extras/android/RemoteHCI/build.gradle.kts create mode 100644 extras/android/RemoteHCI/gradle.properties create mode 100644 extras/android/RemoteHCI/gradle/libs.versions.toml create mode 100644 extras/android/RemoteHCI/gradle/wrapper/gradle-wrapper.jar create mode 100644 extras/android/RemoteHCI/gradle/wrapper/gradle-wrapper.properties create mode 100755 extras/android/RemoteHCI/gradlew create mode 100644 extras/android/RemoteHCI/gradlew.bat create mode 100644 extras/android/RemoteHCI/lib/.gitignore create mode 100644 extras/android/RemoteHCI/lib/build.gradle.kts create mode 100644 extras/android/RemoteHCI/lib/consumer-rules.pro create mode 100644 extras/android/RemoteHCI/lib/proguard-rules.pro create mode 100644 extras/android/RemoteHCI/lib/src/main/AndroidManifest.xml create mode 100644 extras/android/RemoteHCI/lib/src/main/java/android/internal/hidl/base/V1_0/DebugInfo.java create mode 100644 extras/android/RemoteHCI/lib/src/main/java/android/internal/hidl/base/V1_0/IBase.java create mode 100644 extras/android/RemoteHCI/lib/src/main/java/android/os/HidlSupport.java create mode 100644 extras/android/RemoteHCI/lib/src/main/java/android/os/HwBinder.java create mode 100644 extras/android/RemoteHCI/lib/src/main/java/android/os/HwBlob.java create mode 100644 extras/android/RemoteHCI/lib/src/main/java/android/os/HwParcel.java create mode 100644 extras/android/RemoteHCI/lib/src/main/java/android/os/IHwBinder.java create mode 100644 extras/android/RemoteHCI/lib/src/main/java/android/os/IHwInterface.java create mode 100644 extras/android/RemoteHCI/lib/src/main/java/android/os/NativeHandle.java create mode 100644 extras/android/RemoteHCI/lib/src/main/java/android/os/ServiceManager.java create mode 100644 extras/android/RemoteHCI/scripts/generate_java_from_aidl.sh create mode 100644 extras/android/RemoteHCI/settings.gradle.kts diff --git a/docs/mkdocs/mkdocs.yml b/docs/mkdocs/mkdocs.yml index 82a6f419..67bdd7ca 100644 --- a/docs/mkdocs/mkdocs.yml +++ b/docs/mkdocs/mkdocs.yml @@ -67,6 +67,9 @@ nav: - Zephyr: platforms/zephyr.md - Examples: - Overview: examples/index.md + - Extras: + - Overview: extras/index.md + - Android Remote HCI: extras/android_remote_hci.md copyright: Copyright 2021-2023 Google LLC diff --git a/docs/mkdocs/src/extras/android_remote_hci.md b/docs/mkdocs/src/extras/android_remote_hci.md new file mode 100644 index 00000000..706bccf0 --- /dev/null +++ b/docs/mkdocs/src/extras/android_remote_hci.md @@ -0,0 +1,119 @@ +ANDROID REMOTE HCI APP +====================== + +This application allows using an android phone's built-in Bluetooth controller with +a Bumble host stack running outside the phone (typically a development laptop or desktop). +The app runs an HCI proxy between a TCP socket on the "outside" and the Bluetooth HCI HAL +on the "inside". (See [this page](https://source.android.com/docs/core/connect/bluetooth) for a high level +description of the Android Bluetooth HCI HAL). +The HCI packets received on the TCP socket are forwarded to the phone's controller, and the +packets coming from the controller are forwarded to the TCP socket. + + +Building +-------- + +You can build the app by running `./gradlew build` (use `gradlew.bat` on Windows) from the `RemoteHCI` top level directory. +You can also build with Android Studio: open the `RemoteHCI` project. You can build and/or debug from there. + +If the build succeeds, you can find the app APKs (debug and release) at: + + * [Release] ``app/build/outputs/apk/release/app-release-unsigned.apk`` + * [Debug] ``app/build/outputs/apk/debug/app-debug.apk`` + + +Running +------- + +### Preconditions +When the proxy starts (tapping the "Start" button in the app's main activity), it will try to +bind to the Bluetooth HAL. This requires disabling SELinux temporarily, and being the only HAL client. + +#### Disabling SELinux +Binding to the Bluetooth HCI HAL requires certain SELinux permissions that can't simply be changed +on a device without rebuilding its system image. To bypass these restrictions, you will need +to disable SELinux on your phone (please be aware that this is global, not just for the proxy app, +so proceed with caution). +In order to disable SELinux, you need to root the phone (it may be advisable to do this on a +development phone). + +!!! tip "Disabling SELinux Temporarily" + Restart `adb` as root: + ```bash + $ adb root + ``` + + Then disable SELinux + ```bash + $ adb shell setenforce 0 + ``` + + Once you're done using the proxy, you can restore SELinux, if you need to, with + ```bash + $ adb shell setenforce 1 + ``` + + This state will also reset to the normal SELinux enforcement when you reboot. + +#### Stopping the bluetooth process +Since the Bluetooth HAL service can only accept one client, and that in normal conditions +that client is the Android's bluetooth stack, it is required to first shut down the +Android bluetooth stack process. + +!!! tip "Checking if the Bluetooth process is running" + ```bash + $ adb shell "ps -A | grep com.google.android.bluetooth" + ``` + If the process is running, you will get a line like: + ``` + bluetooth 10759 876 17455796 136620 do_epoll_wait 0 S com.google.android.bluetooth + ``` + If you don't, it means that the process is not running and you are clear to proceed. + +Simply turning Bluetooth off from the phone's settings does not ensure that the bluetooth process will exit. +If the bluetooth process is still running after toggling Bluetooth off from the settings, you may try enabling +Airplane Mode, then rebooting. The bluetooth process should, in theory, not restart after the reboot. + +!!! tip "Stopping the bluetooth process with adb" + ```bash + $ adb shell cmd bluetooth_manager disable + ``` + +### Selecting a TCP port +The RemoteHCI app's main activity has a "TCP Port" setting where you can change the port on +which the proxy is accepting connections. If the default value isn't suitable, you can +change it there. + +### Connecting to the proxy +To connect the Bumble stack to the proxy, you need to be able to reach the phone's network +stack. This can be done over the phone's WiFi connection, or, alternatively, using an `adb` +TCP forward (which should be faster than over WiFi). + +!!! tip "Forwarding TCP with `adb`" + To connect to the proxy via an `adb` TCP forward, use: + ```bash + $ adb forward tcp: tcp: + ``` + Where ```` is the port number for a listening socket on your laptop or + desktop machine, and is the TCP port selected in the app's user interface. + Those two ports may be the same, of course. + For example, with the default TCP port 9993: + ```bash + $ adb forward tcp:9993 tcp:9993 + ``` + +Once you've ensured that you can reach the proxy's TCP port on the phone, either directly or +via an `adb` forward, you can then use it as a Bumble transport, using the transport name: +``tcp-client::`` syntax. + +!!! example "Connecting a Bumble client" + Connecting the `bumble-controller-info` app to the phone's controller. + Assuming you have set up an `adb` forward on port 9993: + ```bash + $ bumble-controller-info tcp-client:localhost:9993 + ``` + + Or over WiFi with, in this example, the IP address of the phone being ```192.168.86.27``` + ```bash + $ bumble-controller-info tcp-client:192.168.86.27:9993 + ``` diff --git a/docs/mkdocs/src/extras/index.md b/docs/mkdocs/src/extras/index.md new file mode 100644 index 00000000..ae906c1b --- /dev/null +++ b/docs/mkdocs/src/extras/index.md @@ -0,0 +1,11 @@ +EXTRAS +====== + +A collection of add-ons, apps and tools, to the Bumble project. + +Android Remote HCI +------------------ + +Allows using an Android phone's built-in Bluetooth controller with a Bumble +stack running on a development machine. +See [Android Remote HCI](android_remote_hci.md) for details. \ No newline at end of file diff --git a/extras/android/RemoteHCI/.gitignore b/extras/android/RemoteHCI/.gitignore new file mode 100644 index 00000000..10cfdbfa --- /dev/null +++ b/extras/android/RemoteHCI/.gitignore @@ -0,0 +1,10 @@ +*.iml +.gradle +/local.properties +/.idea +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/extras/android/RemoteHCI/app/.gitignore b/extras/android/RemoteHCI/app/.gitignore new file mode 100644 index 00000000..42afabfd --- /dev/null +++ b/extras/android/RemoteHCI/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/extras/android/RemoteHCI/app/build.gradle.kts b/extras/android/RemoteHCI/app/build.gradle.kts new file mode 100644 index 00000000..2e2df389 --- /dev/null +++ b/extras/android/RemoteHCI/app/build.gradle.kts @@ -0,0 +1,73 @@ +@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed +plugins { + alias(libs.plugins.androidApplication) + alias(libs.plugins.kotlinAndroid) +} + +android { + namespace = "com.github.google.bumble.remotehci" + compileSdk = 33 + + defaultConfig { + applicationId = "com.github.google.bumble.remotehci" + minSdk = 26 + targetSdk = 33 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + vectorDrawables { + useSupportLibrary = true + } + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } + buildFeatures { + compose = true + aidl = false + } + composeOptions { + kotlinCompilerExtensionVersion = "1.4.3" + } + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } +} + +dependencies { + implementation(kotlin("reflect")) + implementation(libs.core.ktx) + implementation(libs.lifecycle.runtime.ktx) + implementation(libs.activity.compose) + implementation(platform(libs.compose.bom)) + implementation(libs.ui) + implementation(libs.ui.graphics) + implementation(libs.ui.tooling.preview) + implementation(libs.material3) + compileOnly(project(":lib")) + testImplementation(libs.junit) + androidTestImplementation(libs.androidx.test.ext.junit) + androidTestImplementation(libs.espresso.core) + androidTestImplementation(platform(libs.compose.bom)) + androidTestImplementation(libs.ui.test.junit4) + debugImplementation(libs.ui.tooling) + debugImplementation(libs.ui.test.manifest) + //compileOnly(files("${project.rootDir.absolutePath}/sdk/framework.jar")) +} \ No newline at end of file diff --git a/extras/android/RemoteHCI/app/proguard-rules.pro b/extras/android/RemoteHCI/app/proguard-rules.pro new file mode 100644 index 00000000..481bb434 --- /dev/null +++ b/extras/android/RemoteHCI/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/extras/android/RemoteHCI/app/src/main/AndroidManifest.xml b/extras/android/RemoteHCI/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..f96a335a --- /dev/null +++ b/extras/android/RemoteHCI/app/src/main/AndroidManifest.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/extras/android/RemoteHCI/app/src/main/aidl/android/hardware/bluetooth/IBluetoothHci.aidl b/extras/android/RemoteHCI/app/src/main/aidl/android/hardware/bluetooth/IBluetoothHci.aidl new file mode 100644 index 00000000..92feaa5e --- /dev/null +++ b/extras/android/RemoteHCI/app/src/main/aidl/android/hardware/bluetooth/IBluetoothHci.aidl @@ -0,0 +1,43 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m -update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.bluetooth; +@VintfStability +interface IBluetoothHci { + void close(); + void initialize(in android.hardware.bluetooth.IBluetoothHciCallbacks callback); + void sendAclData(in byte[] data); + void sendHciCommand(in byte[] command); + void sendIsoData(in byte[] data); + void sendScoData(in byte[] data); +} \ No newline at end of file diff --git a/extras/android/RemoteHCI/app/src/main/aidl/android/hardware/bluetooth/IBluetoothHciCallbacks.aidl b/extras/android/RemoteHCI/app/src/main/aidl/android/hardware/bluetooth/IBluetoothHciCallbacks.aidl new file mode 100644 index 00000000..f0d8c29a --- /dev/null +++ b/extras/android/RemoteHCI/app/src/main/aidl/android/hardware/bluetooth/IBluetoothHciCallbacks.aidl @@ -0,0 +1,42 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m -update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.bluetooth; +@VintfStability +interface IBluetoothHciCallbacks { + void aclDataReceived(in byte[] data); + void hciEventReceived(in byte[] event); + void initializationComplete(in android.hardware.bluetooth.Status status); + void isoDataReceived(in byte[] data); + void scoDataReceived(in byte[] data); +} \ No newline at end of file diff --git a/extras/android/RemoteHCI/app/src/main/aidl/android/hardware/bluetooth/Status.aidl b/extras/android/RemoteHCI/app/src/main/aidl/android/hardware/bluetooth/Status.aidl new file mode 100644 index 00000000..f3ba7921 --- /dev/null +++ b/extras/android/RemoteHCI/app/src/main/aidl/android/hardware/bluetooth/Status.aidl @@ -0,0 +1,43 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m -update-api`. +// +// You must not make a backward incompatible change to any AIDL file built +// with the aidl_interface module type with versions property set. The module +// type is used to build AIDL files in a way that they can be used across +// independently updatable components of the system. If a device is shipped +// with such a backward incompatible change, it has a high risk of breaking +// later when a module using the interface is updated, e.g., Mainline modules. + +package android.hardware.bluetooth; +@Backing(type="int") +@VintfStability +enum Status { + SUCCESS = 0, + ALREADY_INITIALIZED = 1, + UNABLE_TO_OPEN_INTERFACE = 2, + HARDWARE_INITIALIZATION_ERROR = 3, + UNKNOWN = 4, +} \ No newline at end of file diff --git a/extras/android/RemoteHCI/app/src/main/hidl/bluetooth/1.1/IBluetoothHci.hal b/extras/android/RemoteHCI/app/src/main/hidl/bluetooth/1.1/IBluetoothHci.hal new file mode 100644 index 00000000..b7845d5a --- /dev/null +++ b/extras/android/RemoteHCI/app/src/main/hidl/bluetooth/1.1/IBluetoothHci.hal @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.bluetooth@1.1; + +import @1.0::HciPacket; +import @1.0::IBluetoothHci; +import IBluetoothHciCallbacks; + +/** + * The Host Controller Interface (HCI) is the layer defined by the Bluetooth + * specification between the software that runs on the host and the Bluetooth + * controller chip. This boundary is the natural choice for a Hardware + * Abstraction Layer (HAL). Dealing only in HCI packets and events simplifies + * the stack and abstracts away power management, initialization, and other + * implementation-specific details related to the hardware. + */ +interface IBluetoothHci extends @1.0::IBluetoothHci { + /** + * Same as @1.0, but uses 1.1 Callbacks version + */ + initialize_1_1(@1.1::IBluetoothHciCallbacks callback); + + /** + * Send an ISO data packet (as specified in the Bluetooth Core + * Specification v5.2) to the Bluetooth controller. + * Packets must be processed in order. + * @param data HCI data packet to be sent + */ + sendIsoData(HciPacket data); +}; \ No newline at end of file diff --git a/extras/android/RemoteHCI/app/src/main/hidl/bluetooth/1.1/IBluetoothHciCallbacks.hal b/extras/android/RemoteHCI/app/src/main/hidl/bluetooth/1.1/IBluetoothHciCallbacks.hal new file mode 100644 index 00000000..b8d0b8a6 --- /dev/null +++ b/extras/android/RemoteHCI/app/src/main/hidl/bluetooth/1.1/IBluetoothHciCallbacks.hal @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.bluetooth@1.1; + +import @1.0::HciPacket; +import @1.0::IBluetoothHciCallbacks; + +/** + * The interface from the Bluetooth Controller to the stack. + */ +interface IBluetoothHciCallbacks extends @1.0::IBluetoothHciCallbacks { + /** + * Send a ISO data packet form the controller to the host. + * @param data the ISO HCI packet to be passed to the host stack + */ + isoDataReceived(HciPacket data); +}; \ No newline at end of file diff --git a/extras/android/RemoteHCI/app/src/main/ic_launcher-playstore.png b/extras/android/RemoteHCI/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000000000000000000000000000000000000..b0a2f40f4c8573eae360ce6f67d8fff741d68726 GIT binary patch literal 44519 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

tm{E>@4r5Zt2S#^1B?7~O89&~sAPVp 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[])null); + method.invoke(this); + } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { + throw new RuntimeException(e); + } + this.attachInterface(this, DESCRIPTOR); + } + /** + * Cast an IBinder object into an android.hardware.bluetooth.IBluetoothHci interface, + * generating a proxy if needed. + */ + public static android.hardware.bluetooth.IBluetoothHci asInterface(android.os.IBinder obj) + { + if ((obj==null)) { + return null; + } + android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); + if (((iin!=null)&&(iin instanceof android.hardware.bluetooth.IBluetoothHci))) { + return ((android.hardware.bluetooth.IBluetoothHci)iin); + } + return new android.hardware.bluetooth.IBluetoothHci.Stub.Proxy(obj); + } + @Override public android.os.IBinder asBinder() + { + return this; + } + @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException + { + java.lang.String descriptor = DESCRIPTOR; + if (code >= android.os.IBinder.FIRST_CALL_TRANSACTION && code <= android.os.IBinder.LAST_CALL_TRANSACTION) { + data.enforceInterface(descriptor); + } + switch (code) + { + case INTERFACE_TRANSACTION: + { + reply.writeString(descriptor); + return true; + } + } + switch (code) + { + case TRANSACTION_close: + { + this.close(); + reply.writeNoException(); + break; + } + case TRANSACTION_initialize: + { + android.hardware.bluetooth.IBluetoothHciCallbacks _arg0; + _arg0 = android.hardware.bluetooth.IBluetoothHciCallbacks.Stub.asInterface(data.readStrongBinder()); + this.initialize(_arg0); + reply.writeNoException(); + break; + } + case TRANSACTION_sendAclData: + { + byte[] _arg0; + _arg0 = data.createByteArray(); + this.sendAclData(_arg0); + reply.writeNoException(); + break; + } + case TRANSACTION_sendHciCommand: + { + byte[] _arg0; + _arg0 = data.createByteArray(); + this.sendHciCommand(_arg0); + reply.writeNoException(); + break; + } + case TRANSACTION_sendIsoData: + { + byte[] _arg0; + _arg0 = data.createByteArray(); + this.sendIsoData(_arg0); + reply.writeNoException(); + break; + } + case TRANSACTION_sendScoData: + { + byte[] _arg0; + _arg0 = data.createByteArray(); + this.sendScoData(_arg0); + reply.writeNoException(); + break; + } + default: + { + return super.onTransact(code, data, reply, flags); + } + } + return true; + } + private static class Proxy implements android.hardware.bluetooth.IBluetoothHci + { + private android.os.IBinder mRemote; + Proxy(android.os.IBinder remote) + { + mRemote = remote; + } + @Override public android.os.IBinder asBinder() + { + return mRemote; + } + public java.lang.String getInterfaceDescriptor() + { + return DESCRIPTOR; + } + @Override public void close() throws android.os.RemoteException + { + android.os.Parcel _data = android.os.Parcel.obtain(); + android.os.Parcel _reply = android.os.Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + boolean _status = mRemote.transact(Stub.TRANSACTION_close, _data, _reply, 0); + _reply.readException(); + } + finally { + _reply.recycle(); + _data.recycle(); + } + } + @Override public void initialize(android.hardware.bluetooth.IBluetoothHciCallbacks callback) throws android.os.RemoteException + { + android.os.Parcel _data = android.os.Parcel.obtain(); + android.os.Parcel _reply = android.os.Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeStrongInterface(callback); + boolean _status = mRemote.transact(Stub.TRANSACTION_initialize, _data, _reply, 0); + _reply.readException(); + } + finally { + _reply.recycle(); + _data.recycle(); + } + } + @Override public void sendAclData(byte[] data) throws android.os.RemoteException + { + android.os.Parcel _data = android.os.Parcel.obtain(); + android.os.Parcel _reply = android.os.Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeByteArray(data); + boolean _status = mRemote.transact(Stub.TRANSACTION_sendAclData, _data, _reply, 0); + _reply.readException(); + } + finally { + _reply.recycle(); + _data.recycle(); + } + } + @Override public void sendHciCommand(byte[] command) throws android.os.RemoteException + { + android.os.Parcel _data = android.os.Parcel.obtain(); + android.os.Parcel _reply = android.os.Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeByteArray(command); + boolean _status = mRemote.transact(Stub.TRANSACTION_sendHciCommand, _data, _reply, 0); + _reply.readException(); + } + finally { + _reply.recycle(); + _data.recycle(); + } + } + @Override public void sendIsoData(byte[] data) throws android.os.RemoteException + { + android.os.Parcel _data = android.os.Parcel.obtain(); + android.os.Parcel _reply = android.os.Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeByteArray(data); + boolean _status = mRemote.transact(Stub.TRANSACTION_sendIsoData, _data, _reply, 0); + _reply.readException(); + } + finally { + _reply.recycle(); + _data.recycle(); + } + } + @Override public void sendScoData(byte[] data) throws android.os.RemoteException + { + android.os.Parcel _data = android.os.Parcel.obtain(); + android.os.Parcel _reply = android.os.Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeByteArray(data); + boolean _status = mRemote.transact(Stub.TRANSACTION_sendScoData, _data, _reply, 0); + _reply.readException(); + } + finally { + _reply.recycle(); + _data.recycle(); + } + } + } + static final int TRANSACTION_close = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); + static final int TRANSACTION_initialize = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); + static final int TRANSACTION_sendAclData = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2); + static final int TRANSACTION_sendHciCommand = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3); + static final int TRANSACTION_sendIsoData = (android.os.IBinder.FIRST_CALL_TRANSACTION + 4); + static final int TRANSACTION_sendScoData = (android.os.IBinder.FIRST_CALL_TRANSACTION + 5); + } + public static final java.lang.String DESCRIPTOR = "android$hardware$bluetooth$IBluetoothHci".replace('$', '.'); + public void close() throws android.os.RemoteException; + public void initialize(android.hardware.bluetooth.IBluetoothHciCallbacks callback) throws android.os.RemoteException; + public void sendAclData(byte[] data) throws android.os.RemoteException; + public void sendHciCommand(byte[] command) throws android.os.RemoteException; + public void sendIsoData(byte[] data) throws android.os.RemoteException; + public void sendScoData(byte[] data) throws android.os.RemoteException; +} diff --git a/extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/IBluetoothHciCallbacks.java b/extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/IBluetoothHciCallbacks.java new file mode 100644 index 00000000..61a70ad6 --- /dev/null +++ b/extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/IBluetoothHciCallbacks.java @@ -0,0 +1,234 @@ +/* + * This file is auto-generated. DO NOT MODIFY. + */ +package android.hardware.bluetooth; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +public interface IBluetoothHciCallbacks extends android.os.IInterface +{ + /** Default implementation for IBluetoothHciCallbacks. */ + public static class Default implements android.hardware.bluetooth.IBluetoothHciCallbacks + { + @Override public void aclDataReceived(byte[] data) throws android.os.RemoteException + { + } + @Override public void hciEventReceived(byte[] event) throws android.os.RemoteException + { + } + @Override public void initializationComplete(int status) throws android.os.RemoteException + { + } + @Override public void isoDataReceived(byte[] data) throws android.os.RemoteException + { + } + @Override public void scoDataReceived(byte[] data) throws android.os.RemoteException + { + } + @Override + public android.os.IBinder asBinder() { + return null; + } + } + /** Local-side IPC implementation stub class. */ + public static abstract class Stub extends android.os.Binder implements android.hardware.bluetooth.IBluetoothHciCallbacks + { + /** Construct the stub at attach it to the interface. */ + public Stub() + { + //this.markVintfStability(); + try { + Method method = this.getClass().getMethod("markVintfStability", (Class[])null); + method.invoke(this); + } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { + throw new RuntimeException(e); + } + this.attachInterface(this, DESCRIPTOR); + } + /** + * Cast an IBinder object into an android.hardware.bluetooth.IBluetoothHciCallbacks interface, + * generating a proxy if needed. + */ + public static android.hardware.bluetooth.IBluetoothHciCallbacks asInterface(android.os.IBinder obj) + { + if ((obj==null)) { + return null; + } + android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); + if (((iin!=null)&&(iin instanceof android.hardware.bluetooth.IBluetoothHciCallbacks))) { + return ((android.hardware.bluetooth.IBluetoothHciCallbacks)iin); + } + return new android.hardware.bluetooth.IBluetoothHciCallbacks.Stub.Proxy(obj); + } + @Override public android.os.IBinder asBinder() + { + return this; + } + @Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException + { + java.lang.String descriptor = DESCRIPTOR; + if (code >= android.os.IBinder.FIRST_CALL_TRANSACTION && code <= android.os.IBinder.LAST_CALL_TRANSACTION) { + data.enforceInterface(descriptor); + } + switch (code) + { + case INTERFACE_TRANSACTION: + { + reply.writeString(descriptor); + return true; + } + } + switch (code) + { + case TRANSACTION_aclDataReceived: + { + byte[] _arg0; + _arg0 = data.createByteArray(); + this.aclDataReceived(_arg0); + reply.writeNoException(); + break; + } + case TRANSACTION_hciEventReceived: + { + byte[] _arg0; + _arg0 = data.createByteArray(); + this.hciEventReceived(_arg0); + reply.writeNoException(); + break; + } + case TRANSACTION_initializationComplete: + { + int _arg0; + _arg0 = data.readInt(); + this.initializationComplete(_arg0); + reply.writeNoException(); + break; + } + case TRANSACTION_isoDataReceived: + { + byte[] _arg0; + _arg0 = data.createByteArray(); + this.isoDataReceived(_arg0); + reply.writeNoException(); + break; + } + case TRANSACTION_scoDataReceived: + { + byte[] _arg0; + _arg0 = data.createByteArray(); + this.scoDataReceived(_arg0); + reply.writeNoException(); + break; + } + default: + { + return super.onTransact(code, data, reply, flags); + } + } + return true; + } + private static class Proxy implements android.hardware.bluetooth.IBluetoothHciCallbacks + { + private android.os.IBinder mRemote; + Proxy(android.os.IBinder remote) + { + mRemote = remote; + } + @Override public android.os.IBinder asBinder() + { + return mRemote; + } + public java.lang.String getInterfaceDescriptor() + { + return DESCRIPTOR; + } + @Override public void aclDataReceived(byte[] data) throws android.os.RemoteException + { + android.os.Parcel _data = android.os.Parcel.obtain(); + android.os.Parcel _reply = android.os.Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeByteArray(data); + boolean _status = mRemote.transact(Stub.TRANSACTION_aclDataReceived, _data, _reply, 0); + _reply.readException(); + } + finally { + _reply.recycle(); + _data.recycle(); + } + } + @Override public void hciEventReceived(byte[] event) throws android.os.RemoteException + { + android.os.Parcel _data = android.os.Parcel.obtain(); + android.os.Parcel _reply = android.os.Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeByteArray(event); + boolean _status = mRemote.transact(Stub.TRANSACTION_hciEventReceived, _data, _reply, 0); + _reply.readException(); + } + finally { + _reply.recycle(); + _data.recycle(); + } + } + @Override public void initializationComplete(int status) throws android.os.RemoteException + { + android.os.Parcel _data = android.os.Parcel.obtain(); + android.os.Parcel _reply = android.os.Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeInt(status); + boolean _status = mRemote.transact(Stub.TRANSACTION_initializationComplete, _data, _reply, 0); + _reply.readException(); + } + finally { + _reply.recycle(); + _data.recycle(); + } + } + @Override public void isoDataReceived(byte[] data) throws android.os.RemoteException + { + android.os.Parcel _data = android.os.Parcel.obtain(); + android.os.Parcel _reply = android.os.Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeByteArray(data); + boolean _status = mRemote.transact(Stub.TRANSACTION_isoDataReceived, _data, _reply, 0); + _reply.readException(); + } + finally { + _reply.recycle(); + _data.recycle(); + } + } + @Override public void scoDataReceived(byte[] data) throws android.os.RemoteException + { + android.os.Parcel _data = android.os.Parcel.obtain(); + android.os.Parcel _reply = android.os.Parcel.obtain(); + try { + _data.writeInterfaceToken(DESCRIPTOR); + _data.writeByteArray(data); + boolean _status = mRemote.transact(Stub.TRANSACTION_scoDataReceived, _data, _reply, 0); + _reply.readException(); + } + finally { + _reply.recycle(); + _data.recycle(); + } + } + } + static final int TRANSACTION_aclDataReceived = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); + static final int TRANSACTION_hciEventReceived = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1); + static final int TRANSACTION_initializationComplete = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2); + static final int TRANSACTION_isoDataReceived = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3); + static final int TRANSACTION_scoDataReceived = (android.os.IBinder.FIRST_CALL_TRANSACTION + 4); + } + public static final java.lang.String DESCRIPTOR = "android$hardware$bluetooth$IBluetoothHciCallbacks".replace('$', '.'); + public void aclDataReceived(byte[] data) throws android.os.RemoteException; + public void hciEventReceived(byte[] event) throws android.os.RemoteException; + public void initializationComplete(int status) throws android.os.RemoteException; + public void isoDataReceived(byte[] data) throws android.os.RemoteException; + public void scoDataReceived(byte[] data) throws android.os.RemoteException; +} diff --git a/extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/Status.java b/extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/Status.java new file mode 100644 index 00000000..710d6c98 --- /dev/null +++ b/extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/Status.java @@ -0,0 +1,11 @@ +/* + * This file is auto-generated. DO NOT MODIFY. + */ +package android.hardware.bluetooth; +public @interface Status { + public static final int SUCCESS = 0; + public static final int ALREADY_INITIALIZED = 1; + public static final int UNABLE_TO_OPEN_INTERFACE = 2; + public static final int HARDWARE_INITIALIZATION_ERROR = 3; + public static final int UNKNOWN = 4; +} diff --git a/extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/V1_0/IBluetoothHci.java b/extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/V1_0/IBluetoothHci.java new file mode 100644 index 00000000..e7cd5771 --- /dev/null +++ b/extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/V1_0/IBluetoothHci.java @@ -0,0 +1,816 @@ +package android.hardware.bluetooth.V1_0; + +import android.os.HidlSupport; +import android.os.HwBinder; +import android.os.IHwBinder; +import android.os.HwBlob; +import android.os.HwParcel; +import android.os.IHwInterface; +import android.os.NativeHandle; + +/** + * The Host Controller Interface (HCI) is the layer defined by the Bluetooth + * specification between the software that runs on the host and the Bluetooth + * controller chip. This boundary is the natural choice for a Hardware + * Abstraction Layer (HAL). Dealing only in HCI packets and events simplifies + * the stack and abstracts away power management, initialization, and other + * implementation-specific details related to the hardware. + */ +public interface IBluetoothHci extends android.internal.hidl.base.V1_0.IBase { + /** + * Fully-qualified interface name for this interface. + */ + public static final String kInterfaceName = "android.hardware.bluetooth@1.0::IBluetoothHci"; + + /** + * Does a checked conversion from a binder to this class. + */ + /* package private */ static IBluetoothHci asInterface(IHwBinder binder) { + if (binder == null) { + return null; + } + + IHwInterface iface = + binder.queryLocalInterface(kInterfaceName); + + if ((iface != null) && (iface instanceof IBluetoothHci)) { + return (IBluetoothHci)iface; + } + + IBluetoothHci proxy = new IBluetoothHci.Proxy(binder); + + try { + for (String descriptor : proxy.interfaceChain()) { + if (descriptor.equals(kInterfaceName)) { + return proxy; + } + } + } catch (android.os.RemoteException e) { + } + + return null; + } + + /** + * Does a checked conversion from any interface to this class. + */ + public static IBluetoothHci castFrom(IHwInterface iface) { + return (iface == null) ? null : IBluetoothHci.asInterface(iface.asBinder()); + } + + @Override + public IHwBinder asBinder(); + + /** + * This will invoke the equivalent of the C++ getService(std::string) if retry is + * true or tryGetService(std::string) if retry is false. If the service is + * available on the device and retry is true, this will wait for the service to + * start. + * + */ + public static IBluetoothHci getService(String serviceName, boolean retry) throws android.os.RemoteException { + return IBluetoothHci.asInterface(HwBinder.getService("android.hardware.bluetooth@1.0::IBluetoothHci", serviceName, retry)); + } + + /** + * Calls getService("default",retry). + */ + public static IBluetoothHci getService(boolean retry) throws android.os.RemoteException { + return getService("default", retry); + } + + /** + * @deprecated this will not wait for the interface to come up if it hasn't yet + * started. See getService(String,boolean) instead. + */ + @Deprecated + public static IBluetoothHci getService(String serviceName) throws android.os.RemoteException { + return IBluetoothHci.asInterface(HwBinder.getService("android.hardware.bluetooth@1.0::IBluetoothHci", serviceName)); + } + + /** + * @deprecated this will not wait for the interface to come up if it hasn't yet + * started. See getService(boolean) instead. + */ + @Deprecated + public static IBluetoothHci getService() throws android.os.RemoteException { + return getService("default"); + } + + /** + * Initialize the underlying HCI interface. + * + * This method should be used to initialize any hardware interfaces + * required to communicate with the Bluetooth hardware in the + * device. + * + * The |oninitializationComplete| callback must be invoked in response + * to this function to indicate success before any other function + * (sendHciCommand, sendAclData, * sendScoData) is invoked on this + * interface. + * + * @param callback implements IBluetoothHciCallbacks which will + * receive callbacks when incoming HCI packets are received + * from the controller to be sent to the host. + */ + void initialize(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks callback) + throws android.os.RemoteException; + /** + * Send an HCI command (as specified in the Bluetooth Specification + * V4.2, Vol 2, Part 5, Section 5.4.1) to the Bluetooth controller. + * Commands must be executed in order. + * + * @param command is the HCI command to be sent + */ + void sendHciCommand(java.util.ArrayList command) + throws android.os.RemoteException; + /** + * Send an HCI ACL data packet (as specified in the Bluetooth Specification + * V4.2, Vol 2, Part 5, Section 5.4.2) to the Bluetooth controller. + * Packets must be processed in order. + * @param data HCI data packet to be sent + */ + void sendAclData(java.util.ArrayList data) + throws android.os.RemoteException; + /** + * Send an SCO data packet (as specified in the Bluetooth Specification + * V4.2, Vol 2, Part 5, Section 5.4.3) to the Bluetooth controller. + * Packets must be processed in order. + * @param data HCI data packet to be sent + */ + void sendScoData(java.util.ArrayList data) + throws android.os.RemoteException; + /** + * Close the HCI interface + */ + void close() + throws android.os.RemoteException; + /* + * Provides run-time type information for this object. + * For example, for the following interface definition: + * package android.hardware.foo@1.0; + * interface IParent {}; + * interface IChild extends IParent {}; + * Calling interfaceChain on an IChild object must yield the following: + * ["android.hardware.foo@1.0::IChild", + * "android.hardware.foo@1.0::IParent" + * "android.internal.hidl.base@1.0::IBase"] + * + * @return descriptors a vector of descriptors of the run-time type of the + * object. + */ + java.util.ArrayList interfaceChain() + throws android.os.RemoteException; + /* + * Emit diagnostic information to the given file. + * + * Optionally overriden. + * + * @param fd File descriptor to dump data to. + * Must only be used for the duration of this call. + * @param options Arguments for debugging. + * Must support empty for default debug information. + */ + void debug(NativeHandle fd, java.util.ArrayList options) + throws android.os.RemoteException; + /* + * Provides run-time type information for this object. + * For example, for the following interface definition: + * package android.hardware.foo@1.0; + * interface IParent {}; + * interface IChild extends IParent {}; + * Calling interfaceDescriptor on an IChild object must yield + * "android.hardware.foo@1.0::IChild" + * + * @return descriptor a descriptor of the run-time type of the + * object (the first element of the vector returned by + * interfaceChain()) + */ + String interfaceDescriptor() + throws android.os.RemoteException; + /* + * Returns hashes of the source HAL files that define the interfaces of the + * runtime type information on the object. + * For example, for the following interface definition: + * package android.hardware.foo@1.0; + * interface IParent {}; + * interface IChild extends IParent {}; + * Calling interfaceChain on an IChild object must yield the following: + * [(hash of IChild.hal), + * (hash of IParent.hal) + * (hash of IBase.hal)]. + * + * SHA-256 is used as the hashing algorithm. Each hash has 32 bytes + * according to SHA-256 standard. + * + * @return hashchain a vector of SHA-1 digests + */ + java.util.ArrayList getHashChain() + throws android.os.RemoteException; + /* + * This method trigger the interface to enable/disable instrumentation based + * on system property hal.instrumentation.enable. + */ + void setHALInstrumentation() + throws android.os.RemoteException; + /* + * Registers a death recipient, to be called when the process hosting this + * interface dies. + * + * @param recipient a hidl_death_recipient callback object + * @param cookie a cookie that must be returned with the callback + * @return success whether the death recipient was registered successfully. + */ + boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie) + throws android.os.RemoteException; + /* + * Provides way to determine if interface is running without requesting + * any functionality. + */ + void ping() + throws android.os.RemoteException; + /* + * Get debug information on references on this interface. + * @return info debugging information. See comments of DebugInfo. + */ + android.internal.hidl.base.V1_0.DebugInfo getDebugInfo() + throws android.os.RemoteException; + /* + * This method notifies the interface that one or more system properties + * have changed. The default implementation calls + * (C++) report_sysprop_change() in libcutils or + * (Java) android.os.SystemProperties.reportSyspropChanged, + * which in turn calls a set of registered callbacks (eg to update trace + * tags). + */ + void notifySyspropsChanged() + throws android.os.RemoteException; + /* + * Unregisters the registered death recipient. If this service was registered + * multiple times with the same exact death recipient, this unlinks the most + * recently registered one. + * + * @param recipient a previously registered hidl_death_recipient callback + * @return success whether the death recipient was unregistered successfully. + */ + boolean unlinkToDeath(IHwBinder.DeathRecipient recipient) + throws android.os.RemoteException; + + public static final class Proxy implements IBluetoothHci { + private IHwBinder mRemote; + + public Proxy(IHwBinder remote) { + mRemote = java.util.Objects.requireNonNull(remote); + } + + @Override + public IHwBinder asBinder() { + return mRemote; + } + + @Override + public String toString() { + try { + return this.interfaceDescriptor() + "@Proxy"; + } catch (android.os.RemoteException ex) { + /* ignored; handled below. */ + } + return "[class or subclass of " + IBluetoothHci.kInterfaceName + "]@Proxy"; + } + + @Override + public final boolean equals(java.lang.Object other) { + return HidlSupport.interfacesEqual(this, other); + } + + @Override + public final int hashCode() { + return this.asBinder().hashCode(); + } + + // Methods from ::android::hardware::bluetooth::V1_0::IBluetoothHci follow. + @Override + public void initialize(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks callback) + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName); + _hidl_request.writeStrongBinder(callback == null ? null : callback.asBinder()); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(1 /* initialize */, _hidl_request, _hidl_reply, 0 /* flags */); + _hidl_reply.verifySuccess(); + _hidl_request.releaseTemporaryStorage(); + } finally { + _hidl_reply.release(); + } + } + + @Override + public void sendHciCommand(java.util.ArrayList command) + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName); + _hidl_request.writeInt8Vector(command); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(2 /* sendHciCommand */, _hidl_request, _hidl_reply, 0 /* flags */); + _hidl_reply.verifySuccess(); + _hidl_request.releaseTemporaryStorage(); + } finally { + _hidl_reply.release(); + } + } + + @Override + public void sendAclData(java.util.ArrayList data) + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName); + _hidl_request.writeInt8Vector(data); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(3 /* sendAclData */, _hidl_request, _hidl_reply, 0 /* flags */); + _hidl_reply.verifySuccess(); + _hidl_request.releaseTemporaryStorage(); + } finally { + _hidl_reply.release(); + } + } + + @Override + public void sendScoData(java.util.ArrayList data) + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName); + _hidl_request.writeInt8Vector(data); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(4 /* sendScoData */, _hidl_request, _hidl_reply, 0 /* flags */); + _hidl_reply.verifySuccess(); + _hidl_request.releaseTemporaryStorage(); + } finally { + _hidl_reply.release(); + } + } + + @Override + public void close() + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(5 /* close */, _hidl_request, _hidl_reply, 0 /* flags */); + _hidl_reply.verifySuccess(); + _hidl_request.releaseTemporaryStorage(); + } finally { + _hidl_reply.release(); + } + } + + // Methods from ::android::hidl::base::V1_0::IBase follow. + @Override + public java.util.ArrayList interfaceChain() + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(256067662 /* interfaceChain */, _hidl_request, _hidl_reply, 0 /* flags */); + _hidl_reply.verifySuccess(); + _hidl_request.releaseTemporaryStorage(); + + java.util.ArrayList _hidl_out_descriptors = _hidl_reply.readStringVector(); + return _hidl_out_descriptors; + } finally { + _hidl_reply.release(); + } + } + + @Override + public void debug(NativeHandle fd, java.util.ArrayList options) + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + _hidl_request.writeNativeHandle(fd); + _hidl_request.writeStringVector(options); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(256131655 /* debug */, _hidl_request, _hidl_reply, 0 /* flags */); + _hidl_reply.verifySuccess(); + _hidl_request.releaseTemporaryStorage(); + } finally { + _hidl_reply.release(); + } + } + + @Override + public String interfaceDescriptor() + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(256136003 /* interfaceDescriptor */, _hidl_request, _hidl_reply, 0 /* flags */); + _hidl_reply.verifySuccess(); + _hidl_request.releaseTemporaryStorage(); + + String _hidl_out_descriptor = _hidl_reply.readString(); + return _hidl_out_descriptor; + } finally { + _hidl_reply.release(); + } + } + + @Override + public java.util.ArrayList getHashChain() + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(256398152 /* getHashChain */, _hidl_request, _hidl_reply, 0 /* flags */); + _hidl_reply.verifySuccess(); + _hidl_request.releaseTemporaryStorage(); + + java.util.ArrayList _hidl_out_hashchain = new java.util.ArrayList(); + { + HwBlob _hidl_blob = _hidl_reply.readBuffer(16 /* size */); + { + int _hidl_vec_size = _hidl_blob.getInt32(0 /* offset */ + 8 /* offsetof(hidl_vec, mSize) */); + HwBlob childBlob = _hidl_reply.readEmbeddedBuffer( + _hidl_vec_size * 32,_hidl_blob.handle(), + 0 /* offset */ + 0 /* offsetof(hidl_vec, mBuffer) */,true /* nullable */); + + ((java.util.ArrayList) _hidl_out_hashchain).clear(); + for (int _hidl_index_0 = 0; _hidl_index_0 < _hidl_vec_size; ++_hidl_index_0) { + byte[/* 32 */] _hidl_vec_element = new byte[32]; + { + long _hidl_array_offset_1 = _hidl_index_0 * 32; + childBlob.copyToInt8Array(_hidl_array_offset_1, (byte[/* 32 */]) _hidl_vec_element, 32 /* size */); + _hidl_array_offset_1 += 32 * 1; + } + ((java.util.ArrayList) _hidl_out_hashchain).add(_hidl_vec_element); + } + } + } + return _hidl_out_hashchain; + } finally { + _hidl_reply.release(); + } + } + + @Override + public void setHALInstrumentation() + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(256462420 /* setHALInstrumentation */, _hidl_request, _hidl_reply, 1 /* oneway */); + _hidl_request.releaseTemporaryStorage(); + } finally { + _hidl_reply.release(); + } + } + + @Override + public boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie) + throws android.os.RemoteException { + return mRemote.linkToDeath(recipient, cookie); + } + @Override + public void ping() + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(256921159 /* ping */, _hidl_request, _hidl_reply, 0 /* flags */); + _hidl_reply.verifySuccess(); + _hidl_request.releaseTemporaryStorage(); + } finally { + _hidl_reply.release(); + } + } + + @Override + public android.internal.hidl.base.V1_0.DebugInfo getDebugInfo() + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(257049926 /* getDebugInfo */, _hidl_request, _hidl_reply, 0 /* flags */); + _hidl_reply.verifySuccess(); + _hidl_request.releaseTemporaryStorage(); + + android.internal.hidl.base.V1_0.DebugInfo _hidl_out_info = new android.internal.hidl.base.V1_0.DebugInfo(); + ((android.internal.hidl.base.V1_0.DebugInfo) _hidl_out_info).readFromParcel(_hidl_reply); + return _hidl_out_info; + } finally { + _hidl_reply.release(); + } + } + + @Override + public void notifySyspropsChanged() + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(257120595 /* notifySyspropsChanged */, _hidl_request, _hidl_reply, 1 /* oneway */); + _hidl_request.releaseTemporaryStorage(); + } finally { + _hidl_reply.release(); + } + } + + @Override + public boolean unlinkToDeath(IHwBinder.DeathRecipient recipient) + throws android.os.RemoteException { + return mRemote.unlinkToDeath(recipient); + } + } + + public static abstract class Stub extends HwBinder implements IBluetoothHci { + @Override + public IHwBinder asBinder() { + return this; + } + + @Override + public final java.util.ArrayList interfaceChain() { + return new java.util.ArrayList(java.util.Arrays.asList( + android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName, + android.internal.hidl.base.V1_0.IBase.kInterfaceName)); + + } + + @Override + public void debug(NativeHandle fd, java.util.ArrayList options) { + return; + + } + + @Override + public final String interfaceDescriptor() { + return android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName; + + } + + @Override + public final java.util.ArrayList getHashChain() { + return new java.util.ArrayList(java.util.Arrays.asList( + new byte[/* 32 */]{52,124,-25,70,-127,86,7,86,127,95,59,83,-28,-128,9,-104,-54,90,-71,53,81,65,-16,-120,15,-64,-49,12,31,-59,-61,85} /* 347ce746815607567f5f3b53e4800998ca5ab9355141f0880fc0cf0c1fc5c355 */, + new byte[/* 32 */]{-20,127,-41,-98,-48,45,-6,-123,-68,73,-108,38,-83,-82,62,-66,35,-17,5,36,-13,-51,105,87,19,-109,36,-72,59,24,-54,76} /* ec7fd79ed02dfa85bc499426adae3ebe23ef0524f3cd6957139324b83b18ca4c */)); + + } + + @Override + public final void setHALInstrumentation() { + + } + + @Override + public final boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie) { + return true; + + } + + @Override + public final void ping() { + return; + + } + + @Override + public final android.internal.hidl.base.V1_0.DebugInfo getDebugInfo() { + android.internal.hidl.base.V1_0.DebugInfo info = new android.internal.hidl.base.V1_0.DebugInfo(); + info.pid = HidlSupport.getPidIfSharable(); + info.ptr = 0; + info.arch = android.internal.hidl.base.V1_0.DebugInfo.Architecture.UNKNOWN; + return info; + + } + + @Override + public final void notifySyspropsChanged() { + HwBinder.enableInstrumentation(); + + } + + @Override + public final boolean unlinkToDeath(IHwBinder.DeathRecipient recipient) { + return true; + + } + + @Override + public IHwInterface queryLocalInterface(String descriptor) { + if (kInterfaceName.equals(descriptor)) { + return this; + } + return null; + } + + public void registerAsService(String serviceName) throws android.os.RemoteException { + registerService(serviceName); + } + + @Override + public String toString() { + return this.interfaceDescriptor() + "@Stub"; + } + + //@Override + public void onTransact(int _hidl_code, HwParcel _hidl_request, final HwParcel _hidl_reply, int _hidl_flags) + throws android.os.RemoteException { + switch (_hidl_code) { + case 1 /* initialize */: + { + _hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName); + + android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks callback = android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.asInterface(_hidl_request.readStrongBinder()); + initialize(callback); + _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS); + _hidl_reply.send(); + break; + } + + case 2 /* sendHciCommand */: + { + _hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName); + + java.util.ArrayList command = _hidl_request.readInt8Vector(); + sendHciCommand(command); + _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS); + _hidl_reply.send(); + break; + } + + case 3 /* sendAclData */: + { + _hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName); + + java.util.ArrayList data = _hidl_request.readInt8Vector(); + sendAclData(data); + _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS); + _hidl_reply.send(); + break; + } + + case 4 /* sendScoData */: + { + _hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName); + + java.util.ArrayList data = _hidl_request.readInt8Vector(); + sendScoData(data); + _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS); + _hidl_reply.send(); + break; + } + + case 5 /* close */: + { + _hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName); + + close(); + _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS); + _hidl_reply.send(); + break; + } + + case 256067662 /* interfaceChain */: + { + _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + java.util.ArrayList _hidl_out_descriptors = interfaceChain(); + _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS); + _hidl_reply.writeStringVector(_hidl_out_descriptors); + _hidl_reply.send(); + break; + } + + case 256131655 /* debug */: + { + _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + NativeHandle fd = _hidl_request.readNativeHandle(); + java.util.ArrayList options = _hidl_request.readStringVector(); + debug(fd, options); + _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS); + _hidl_reply.send(); + break; + } + + case 256136003 /* interfaceDescriptor */: + { + _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + String _hidl_out_descriptor = interfaceDescriptor(); + _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS); + _hidl_reply.writeString(_hidl_out_descriptor); + _hidl_reply.send(); + break; + } + + case 256398152 /* getHashChain */: + { + _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + java.util.ArrayList _hidl_out_hashchain = getHashChain(); + _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS); + { + HwBlob _hidl_blob = new HwBlob(16 /* size */); + { + int _hidl_vec_size = _hidl_out_hashchain.size(); + _hidl_blob.putInt32(0 /* offset */ + 8 /* offsetof(hidl_vec, mSize) */, _hidl_vec_size); + _hidl_blob.putBool(0 /* offset */ + 12 /* offsetof(hidl_vec, mOwnsBuffer) */, false); + HwBlob childBlob = new HwBlob((int)(_hidl_vec_size * 32)); + for (int _hidl_index_0 = 0; _hidl_index_0 < _hidl_vec_size; ++_hidl_index_0) { + { + long _hidl_array_offset_1 = _hidl_index_0 * 32; + byte[] _hidl_array_item_1 = (byte[/* 32 */]) _hidl_out_hashchain.get(_hidl_index_0); + + if (_hidl_array_item_1 == null || _hidl_array_item_1.length != 32) { + throw new IllegalArgumentException("Array element is not of the expected length"); + } + + childBlob.putInt8Array(_hidl_array_offset_1, _hidl_array_item_1); + _hidl_array_offset_1 += 32 * 1; + } + } + _hidl_blob.putBlob(0 /* offset */ + 0 /* offsetof(hidl_vec, mBuffer) */, childBlob); + } + _hidl_reply.writeBuffer(_hidl_blob); + } + _hidl_reply.send(); + break; + } + + case 256462420 /* setHALInstrumentation */: + { + _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + setHALInstrumentation(); + break; + } + + case 256660548 /* linkToDeath */: + { + break; + } + + case 256921159 /* ping */: + { + _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + ping(); + _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS); + _hidl_reply.send(); + break; + } + + case 257049926 /* getDebugInfo */: + { + _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + android.internal.hidl.base.V1_0.DebugInfo _hidl_out_info = getDebugInfo(); + _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS); + ((android.internal.hidl.base.V1_0.DebugInfo) _hidl_out_info).writeToParcel(_hidl_reply); + _hidl_reply.send(); + break; + } + + case 257120595 /* notifySyspropsChanged */: + { + _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + notifySyspropsChanged(); + break; + } + + case 257250372 /* unlinkToDeath */: + { + break; + } + + } + } + } +} diff --git a/extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/V1_0/IBluetoothHciCallbacks.java b/extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/V1_0/IBluetoothHciCallbacks.java new file mode 100644 index 00000000..694fb723 --- /dev/null +++ b/extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/V1_0/IBluetoothHciCallbacks.java @@ -0,0 +1,762 @@ +package android.hardware.bluetooth.V1_0; + +import android.os.HidlSupport; +import android.os.HwBinder; +import android.os.IHwBinder; +import android.os.HwBlob; +import android.os.HwParcel; +import android.os.IHwInterface; +import android.os.NativeHandle; + +/** + * The interface from the Bluetooth Controller to the stack. + */ +public interface IBluetoothHciCallbacks extends android.internal.hidl.base.V1_0.IBase { + /** + * Fully-qualified interface name for this interface. + */ + public static final String kInterfaceName = "android.hardware.bluetooth@1.0::IBluetoothHciCallbacks"; + + /** + * Does a checked conversion from a binder to this class. + */ + /* package private */ static IBluetoothHciCallbacks asInterface(IHwBinder binder) { + if (binder == null) { + return null; + } + + IHwInterface iface = + binder.queryLocalInterface(kInterfaceName); + + if ((iface != null) && (iface instanceof IBluetoothHciCallbacks)) { + return (IBluetoothHciCallbacks)iface; + } + + IBluetoothHciCallbacks proxy = new IBluetoothHciCallbacks.Proxy(binder); + + try { + for (String descriptor : proxy.interfaceChain()) { + if (descriptor.equals(kInterfaceName)) { + return proxy; + } + } + } catch (android.os.RemoteException e) { + } + + return null; + } + + /** + * Does a checked conversion from any interface to this class. + */ + public static IBluetoothHciCallbacks castFrom(IHwInterface iface) { + return (iface == null) ? null : IBluetoothHciCallbacks.asInterface(iface.asBinder()); + } + + @Override + public IHwBinder asBinder(); + + /** + * This will invoke the equivalent of the C++ getService(std::string) if retry is + * true or tryGetService(std::string) if retry is false. If the service is + * available on the device and retry is true, this will wait for the service to + * start. + * + */ + public static IBluetoothHciCallbacks getService(String serviceName, boolean retry) throws android.os.RemoteException { + return IBluetoothHciCallbacks.asInterface(HwBinder.getService("android.hardware.bluetooth@1.0::IBluetoothHciCallbacks", serviceName, retry)); + } + + /** + * Calls getService("default",retry). + */ + public static IBluetoothHciCallbacks getService(boolean retry) throws android.os.RemoteException { + return getService("default", retry); + } + + /** + * @deprecated this will not wait for the interface to come up if it hasn't yet + * started. See getService(String,boolean) instead. + */ + @Deprecated + public static IBluetoothHciCallbacks getService(String serviceName) throws android.os.RemoteException { + return IBluetoothHciCallbacks.asInterface(HwBinder.getService("android.hardware.bluetooth@1.0::IBluetoothHciCallbacks", serviceName)); + } + + /** + * @deprecated this will not wait for the interface to come up if it hasn't yet + * started. See getService(boolean) instead. + */ + @Deprecated + public static IBluetoothHciCallbacks getService() throws android.os.RemoteException { + return getService("default"); + } + + /** + * Invoked when the Bluetooth controller initialization has been + * completed. + */ + void initializationComplete(int status) + throws android.os.RemoteException; + /** + * This function is invoked when an HCI event is received from the + * Bluetooth controller to be forwarded to the Bluetooth stack. + * @param event is the HCI event to be sent to the Bluetooth stack. + */ + void hciEventReceived(java.util.ArrayList event) + throws android.os.RemoteException; + /** + * Send an ACL data packet form the controller to the host. + * @param data the ACL HCI packet to be passed to the host stack + */ + void aclDataReceived(java.util.ArrayList data) + throws android.os.RemoteException; + /** + * Send a SCO data packet form the controller to the host. + * @param data the SCO HCI packet to be passed to the host stack + */ + void scoDataReceived(java.util.ArrayList data) + throws android.os.RemoteException; + /* + * Provides run-time type information for this object. + * For example, for the following interface definition: + * package android.hardware.foo@1.0; + * interface IParent {}; + * interface IChild extends IParent {}; + * Calling interfaceChain on an IChild object must yield the following: + * ["android.hardware.foo@1.0::IChild", + * "android.hardware.foo@1.0::IParent" + * "android.internal.hidl.base@1.0::IBase"] + * + * @return descriptors a vector of descriptors of the run-time type of the + * object. + */ + java.util.ArrayList interfaceChain() + throws android.os.RemoteException; + /* + * Emit diagnostic information to the given file. + * + * Optionally overriden. + * + * @param fd File descriptor to dump data to. + * Must only be used for the duration of this call. + * @param options Arguments for debugging. + * Must support empty for default debug information. + */ + void debug(NativeHandle fd, java.util.ArrayList options) + throws android.os.RemoteException; + /* + * Provides run-time type information for this object. + * For example, for the following interface definition: + * package android.hardware.foo@1.0; + * interface IParent {}; + * interface IChild extends IParent {}; + * Calling interfaceDescriptor on an IChild object must yield + * "android.hardware.foo@1.0::IChild" + * + * @return descriptor a descriptor of the run-time type of the + * object (the first element of the vector returned by + * interfaceChain()) + */ + String interfaceDescriptor() + throws android.os.RemoteException; + /* + * Returns hashes of the source HAL files that define the interfaces of the + * runtime type information on the object. + * For example, for the following interface definition: + * package android.hardware.foo@1.0; + * interface IParent {}; + * interface IChild extends IParent {}; + * Calling interfaceChain on an IChild object must yield the following: + * [(hash of IChild.hal), + * (hash of IParent.hal) + * (hash of IBase.hal)]. + * + * SHA-256 is used as the hashing algorithm. Each hash has 32 bytes + * according to SHA-256 standard. + * + * @return hashchain a vector of SHA-1 digests + */ + java.util.ArrayList getHashChain() + throws android.os.RemoteException; + /* + * This method trigger the interface to enable/disable instrumentation based + * on system property hal.instrumentation.enable. + */ + void setHALInstrumentation() + throws android.os.RemoteException; + /* + * Registers a death recipient, to be called when the process hosting this + * interface dies. + * + * @param recipient a hidl_death_recipient callback object + * @param cookie a cookie that must be returned with the callback + * @return success whether the death recipient was registered successfully. + */ + boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie) + throws android.os.RemoteException; + /* + * Provides way to determine if interface is running without requesting + * any functionality. + */ + void ping() + throws android.os.RemoteException; + /* + * Get debug information on references on this interface. + * @return info debugging information. See comments of DebugInfo. + */ + android.internal.hidl.base.V1_0.DebugInfo getDebugInfo() + throws android.os.RemoteException; + /* + * This method notifies the interface that one or more system properties + * have changed. The default implementation calls + * (C++) report_sysprop_change() in libcutils or + * (Java) android.os.SystemProperties.reportSyspropChanged, + * which in turn calls a set of registered callbacks (eg to update trace + * tags). + */ + void notifySyspropsChanged() + throws android.os.RemoteException; + /* + * Unregisters the registered death recipient. If this service was registered + * multiple times with the same exact death recipient, this unlinks the most + * recently registered one. + * + * @param recipient a previously registered hidl_death_recipient callback + * @return success whether the death recipient was unregistered successfully. + */ + boolean unlinkToDeath(IHwBinder.DeathRecipient recipient) + throws android.os.RemoteException; + + public static final class Proxy implements IBluetoothHciCallbacks { + private IHwBinder mRemote; + + public Proxy(IHwBinder remote) { + mRemote = java.util.Objects.requireNonNull(remote); + } + + @Override + public IHwBinder asBinder() { + return mRemote; + } + + @Override + public String toString() { + try { + return this.interfaceDescriptor() + "@Proxy"; + } catch (android.os.RemoteException ex) { + /* ignored; handled below. */ + } + return "[class or subclass of " + IBluetoothHciCallbacks.kInterfaceName + "]@Proxy"; + } + + @Override + public final boolean equals(java.lang.Object other) { + return HidlSupport.interfacesEqual(this, other); + } + + @Override + public final int hashCode() { + return this.asBinder().hashCode(); + } + + // Methods from ::android::hardware::bluetooth::V1_0::IBluetoothHciCallbacks follow. + @Override + public void initializationComplete(int status) + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName); + _hidl_request.writeInt32(status); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(1 /* initializationComplete */, _hidl_request, _hidl_reply, 0 /* flags */); + _hidl_reply.verifySuccess(); + _hidl_request.releaseTemporaryStorage(); + } finally { + _hidl_reply.release(); + } + } + + @Override + public void hciEventReceived(java.util.ArrayList event) + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName); + _hidl_request.writeInt8Vector(event); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(2 /* hciEventReceived */, _hidl_request, _hidl_reply, 0 /* flags */); + _hidl_reply.verifySuccess(); + _hidl_request.releaseTemporaryStorage(); + } finally { + _hidl_reply.release(); + } + } + + @Override + public void aclDataReceived(java.util.ArrayList data) + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName); + _hidl_request.writeInt8Vector(data); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(3 /* aclDataReceived */, _hidl_request, _hidl_reply, 0 /* flags */); + _hidl_reply.verifySuccess(); + _hidl_request.releaseTemporaryStorage(); + } finally { + _hidl_reply.release(); + } + } + + @Override + public void scoDataReceived(java.util.ArrayList data) + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName); + _hidl_request.writeInt8Vector(data); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(4 /* scoDataReceived */, _hidl_request, _hidl_reply, 0 /* flags */); + _hidl_reply.verifySuccess(); + _hidl_request.releaseTemporaryStorage(); + } finally { + _hidl_reply.release(); + } + } + + // Methods from ::android::hidl::base::V1_0::IBase follow. + @Override + public java.util.ArrayList interfaceChain() + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(256067662 /* interfaceChain */, _hidl_request, _hidl_reply, 0 /* flags */); + _hidl_reply.verifySuccess(); + _hidl_request.releaseTemporaryStorage(); + + java.util.ArrayList _hidl_out_descriptors = _hidl_reply.readStringVector(); + return _hidl_out_descriptors; + } finally { + _hidl_reply.release(); + } + } + + @Override + public void debug(NativeHandle fd, java.util.ArrayList options) + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + _hidl_request.writeNativeHandle(fd); + _hidl_request.writeStringVector(options); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(256131655 /* debug */, _hidl_request, _hidl_reply, 0 /* flags */); + _hidl_reply.verifySuccess(); + _hidl_request.releaseTemporaryStorage(); + } finally { + _hidl_reply.release(); + } + } + + @Override + public String interfaceDescriptor() + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(256136003 /* interfaceDescriptor */, _hidl_request, _hidl_reply, 0 /* flags */); + _hidl_reply.verifySuccess(); + _hidl_request.releaseTemporaryStorage(); + + String _hidl_out_descriptor = _hidl_reply.readString(); + return _hidl_out_descriptor; + } finally { + _hidl_reply.release(); + } + } + + @Override + public java.util.ArrayList getHashChain() + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(256398152 /* getHashChain */, _hidl_request, _hidl_reply, 0 /* flags */); + _hidl_reply.verifySuccess(); + _hidl_request.releaseTemporaryStorage(); + + java.util.ArrayList _hidl_out_hashchain = new java.util.ArrayList(); + { + HwBlob _hidl_blob = _hidl_reply.readBuffer(16 /* size */); + { + int _hidl_vec_size = _hidl_blob.getInt32(0 /* offset */ + 8 /* offsetof(hidl_vec, mSize) */); + HwBlob childBlob = _hidl_reply.readEmbeddedBuffer( + _hidl_vec_size * 32,_hidl_blob.handle(), + 0 /* offset */ + 0 /* offsetof(hidl_vec, mBuffer) */,true /* nullable */); + + ((java.util.ArrayList) _hidl_out_hashchain).clear(); + for (int _hidl_index_0 = 0; _hidl_index_0 < _hidl_vec_size; ++_hidl_index_0) { + byte[/* 32 */] _hidl_vec_element = new byte[32]; + { + long _hidl_array_offset_1 = _hidl_index_0 * 32; + childBlob.copyToInt8Array(_hidl_array_offset_1, (byte[/* 32 */]) _hidl_vec_element, 32 /* size */); + _hidl_array_offset_1 += 32 * 1; + } + ((java.util.ArrayList) _hidl_out_hashchain).add(_hidl_vec_element); + } + } + } + return _hidl_out_hashchain; + } finally { + _hidl_reply.release(); + } + } + + @Override + public void setHALInstrumentation() + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(256462420 /* setHALInstrumentation */, _hidl_request, _hidl_reply, 1 /* oneway */); + _hidl_request.releaseTemporaryStorage(); + } finally { + _hidl_reply.release(); + } + } + + @Override + public boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie) + throws android.os.RemoteException { + return mRemote.linkToDeath(recipient, cookie); + } + @Override + public void ping() + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(256921159 /* ping */, _hidl_request, _hidl_reply, 0 /* flags */); + _hidl_reply.verifySuccess(); + _hidl_request.releaseTemporaryStorage(); + } finally { + _hidl_reply.release(); + } + } + + @Override + public android.internal.hidl.base.V1_0.DebugInfo getDebugInfo() + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(257049926 /* getDebugInfo */, _hidl_request, _hidl_reply, 0 /* flags */); + _hidl_reply.verifySuccess(); + _hidl_request.releaseTemporaryStorage(); + + android.internal.hidl.base.V1_0.DebugInfo _hidl_out_info = new android.internal.hidl.base.V1_0.DebugInfo(); + ((android.internal.hidl.base.V1_0.DebugInfo) _hidl_out_info).readFromParcel(_hidl_reply); + return _hidl_out_info; + } finally { + _hidl_reply.release(); + } + } + + @Override + public void notifySyspropsChanged() + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(257120595 /* notifySyspropsChanged */, _hidl_request, _hidl_reply, 1 /* oneway */); + _hidl_request.releaseTemporaryStorage(); + } finally { + _hidl_reply.release(); + } + } + + @Override + public boolean unlinkToDeath(IHwBinder.DeathRecipient recipient) + throws android.os.RemoteException { + return mRemote.unlinkToDeath(recipient); + } + } + + public static abstract class Stub extends HwBinder implements IBluetoothHciCallbacks { + @Override + public IHwBinder asBinder() { + return this; + } + + @Override + public final java.util.ArrayList interfaceChain() { + return new java.util.ArrayList(java.util.Arrays.asList( + android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName, + android.internal.hidl.base.V1_0.IBase.kInterfaceName)); + + } + + @Override + public void debug(NativeHandle fd, java.util.ArrayList options) { + return; + + } + + @Override + public final String interfaceDescriptor() { + return android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName; + + } + + @Override + public final java.util.ArrayList getHashChain() { + return new java.util.ArrayList(java.util.Arrays.asList( + new byte[/* 32 */]{-125,95,65,-66,34,-127,-65,-78,47,62,51,-58,-6,-121,11,-34,123,-62,30,55,-27,-49,-70,-7,-93,111,-1,23,6,50,-9,84} /* 835f41be2281bfb22f3e33c6fa870bde7bc21e37e5cfbaf9a36fff170632f754 */, + new byte[/* 32 */]{-20,127,-41,-98,-48,45,-6,-123,-68,73,-108,38,-83,-82,62,-66,35,-17,5,36,-13,-51,105,87,19,-109,36,-72,59,24,-54,76} /* ec7fd79ed02dfa85bc499426adae3ebe23ef0524f3cd6957139324b83b18ca4c */)); + + } + + @Override + public final void setHALInstrumentation() { + + } + + @Override + public final boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie) { + return true; + + } + + @Override + public final void ping() { + return; + + } + + @Override + public final android.internal.hidl.base.V1_0.DebugInfo getDebugInfo() { + android.internal.hidl.base.V1_0.DebugInfo info = new android.internal.hidl.base.V1_0.DebugInfo(); + info.pid = HidlSupport.getPidIfSharable(); + info.ptr = 0; + info.arch = android.internal.hidl.base.V1_0.DebugInfo.Architecture.UNKNOWN; + return info; + + } + + @Override + public final void notifySyspropsChanged() { + HwBinder.enableInstrumentation(); + + } + + @Override + public final boolean unlinkToDeath(IHwBinder.DeathRecipient recipient) { + return true; + + } + + @Override + public IHwInterface queryLocalInterface(String descriptor) { + if (kInterfaceName.equals(descriptor)) { + return this; + } + return null; + } + + public void registerAsService(String serviceName) throws android.os.RemoteException { + registerService(serviceName); + } + + @Override + public String toString() { + return this.interfaceDescriptor() + "@Stub"; + } + + //@Override + public void onTransact(int _hidl_code, HwParcel _hidl_request, final HwParcel _hidl_reply, int _hidl_flags) + throws android.os.RemoteException { + switch (_hidl_code) { + case 1 /* initializationComplete */: + { + _hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName); + + int status = _hidl_request.readInt32(); + initializationComplete(status); + _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS); + _hidl_reply.send(); + break; + } + + case 2 /* hciEventReceived */: + { + _hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName); + + java.util.ArrayList event = _hidl_request.readInt8Vector(); + hciEventReceived(event); + _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS); + _hidl_reply.send(); + break; + } + + case 3 /* aclDataReceived */: + { + _hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName); + + java.util.ArrayList data = _hidl_request.readInt8Vector(); + aclDataReceived(data); + _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS); + _hidl_reply.send(); + break; + } + + case 4 /* scoDataReceived */: + { + _hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName); + + java.util.ArrayList data = _hidl_request.readInt8Vector(); + scoDataReceived(data); + _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS); + _hidl_reply.send(); + break; + } + + case 256067662 /* interfaceChain */: + { + _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + java.util.ArrayList _hidl_out_descriptors = interfaceChain(); + _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS); + _hidl_reply.writeStringVector(_hidl_out_descriptors); + _hidl_reply.send(); + break; + } + + case 256131655 /* debug */: + { + _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + NativeHandle fd = _hidl_request.readNativeHandle(); + java.util.ArrayList options = _hidl_request.readStringVector(); + debug(fd, options); + _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS); + _hidl_reply.send(); + break; + } + + case 256136003 /* interfaceDescriptor */: + { + _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + String _hidl_out_descriptor = interfaceDescriptor(); + _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS); + _hidl_reply.writeString(_hidl_out_descriptor); + _hidl_reply.send(); + break; + } + + case 256398152 /* getHashChain */: + { + _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + java.util.ArrayList _hidl_out_hashchain = getHashChain(); + _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS); + { + HwBlob _hidl_blob = new HwBlob(16 /* size */); + { + int _hidl_vec_size = _hidl_out_hashchain.size(); + _hidl_blob.putInt32(0 /* offset */ + 8 /* offsetof(hidl_vec, mSize) */, _hidl_vec_size); + _hidl_blob.putBool(0 /* offset */ + 12 /* offsetof(hidl_vec, mOwnsBuffer) */, false); + HwBlob childBlob = new HwBlob((int)(_hidl_vec_size * 32)); + for (int _hidl_index_0 = 0; _hidl_index_0 < _hidl_vec_size; ++_hidl_index_0) { + { + long _hidl_array_offset_1 = _hidl_index_0 * 32; + byte[] _hidl_array_item_1 = (byte[/* 32 */]) _hidl_out_hashchain.get(_hidl_index_0); + + if (_hidl_array_item_1 == null || _hidl_array_item_1.length != 32) { + throw new IllegalArgumentException("Array element is not of the expected length"); + } + + childBlob.putInt8Array(_hidl_array_offset_1, _hidl_array_item_1); + _hidl_array_offset_1 += 32 * 1; + } + } + _hidl_blob.putBlob(0 /* offset */ + 0 /* offsetof(hidl_vec, mBuffer) */, childBlob); + } + _hidl_reply.writeBuffer(_hidl_blob); + } + _hidl_reply.send(); + break; + } + + case 256462420 /* setHALInstrumentation */: + { + _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + setHALInstrumentation(); + break; + } + + case 256660548 /* linkToDeath */: + { + break; + } + + case 256921159 /* ping */: + { + _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + ping(); + _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS); + _hidl_reply.send(); + break; + } + + case 257049926 /* getDebugInfo */: + { + _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + android.internal.hidl.base.V1_0.DebugInfo _hidl_out_info = getDebugInfo(); + _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS); + ((android.internal.hidl.base.V1_0.DebugInfo) _hidl_out_info).writeToParcel(_hidl_reply); + _hidl_reply.send(); + break; + } + + case 257120595 /* notifySyspropsChanged */: + { + _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + notifySyspropsChanged(); + break; + } + + case 257250372 /* unlinkToDeath */: + { + break; + } + + } + } + } +} diff --git a/extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/V1_0/Status.java b/extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/V1_0/Status.java new file mode 100644 index 00000000..92350056 --- /dev/null +++ b/extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/V1_0/Status.java @@ -0,0 +1,48 @@ +package android.hardware.bluetooth.V1_0; + + +public final class Status { + public static final int SUCCESS = 0; + public static final int TRANSPORT_ERROR = 1 /* ::android::hardware::bluetooth::V1_0::Status.SUCCESS implicitly + 1 */; + public static final int INITIALIZATION_ERROR = 2 /* ::android::hardware::bluetooth::V1_0::Status.TRANSPORT_ERROR implicitly + 1 */; + public static final int UNKNOWN = 3 /* ::android::hardware::bluetooth::V1_0::Status.INITIALIZATION_ERROR implicitly + 1 */; + public static final String toString(int o) { + if (o == SUCCESS) { + return "SUCCESS"; + } + if (o == TRANSPORT_ERROR) { + return "TRANSPORT_ERROR"; + } + if (o == INITIALIZATION_ERROR) { + return "INITIALIZATION_ERROR"; + } + if (o == UNKNOWN) { + return "UNKNOWN"; + } + return "0x" + Integer.toHexString(o); + } + + public static final String dumpBitfield(int o) { + java.util.ArrayList list = new java.util.ArrayList<>(); + int flipped = 0; + list.add("SUCCESS"); // SUCCESS == 0 + if ((o & TRANSPORT_ERROR) == TRANSPORT_ERROR) { + list.add("TRANSPORT_ERROR"); + flipped |= TRANSPORT_ERROR; + } + if ((o & INITIALIZATION_ERROR) == INITIALIZATION_ERROR) { + list.add("INITIALIZATION_ERROR"); + flipped |= INITIALIZATION_ERROR; + } + if ((o & UNKNOWN) == UNKNOWN) { + list.add("UNKNOWN"); + flipped |= UNKNOWN; + } + if (o != flipped) { + list.add("0x" + Integer.toHexString(o & (~flipped))); + } + return String.join(" | ", list); + } + +}; + diff --git a/extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/V1_1/IBluetoothHci.java b/extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/V1_1/IBluetoothHci.java new file mode 100644 index 00000000..44394516 --- /dev/null +++ b/extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/V1_1/IBluetoothHci.java @@ -0,0 +1,840 @@ +package android.hardware.bluetooth.V1_1; + +import android.os.HidlSupport; +import android.os.HwBinder; +import android.os.IHwBinder; +import android.os.HwBlob; +import android.os.HwParcel; +import android.os.IHwInterface; +import android.os.NativeHandle; + +/** + * The Host Controller Interface (HCI) is the layer defined by the Bluetooth + * specification between the software that runs on the host and the Bluetooth + * controller chip. This boundary is the natural choice for a Hardware + * Abstraction Layer (HAL). Dealing only in HCI packets and events simplifies + * the stack and abstracts away power management, initialization, and other + * implementation-specific details related to the hardware. + */ +public interface IBluetoothHci extends android.hardware.bluetooth.V1_0.IBluetoothHci { + /** + * Fully-qualified interface name for this interface. + */ + public static final String kInterfaceName = "android.hardware.bluetooth@1.1::IBluetoothHci"; + + /** + * Does a checked conversion from a binder to this class. + */ + /* package private */ static IBluetoothHci asInterface(IHwBinder binder) { + if (binder == null) { + return null; + } + + IHwInterface iface = + binder.queryLocalInterface(kInterfaceName); + + if ((iface != null) && (iface instanceof IBluetoothHci)) { + return (IBluetoothHci)iface; + } + + IBluetoothHci proxy = new IBluetoothHci.Proxy(binder); + + try { + for (String descriptor : proxy.interfaceChain()) { + if (descriptor.equals(kInterfaceName)) { + return proxy; + } + } + } catch (android.os.RemoteException e) { + } + + return null; + } + + /** + * Does a checked conversion from any interface to this class. + */ + public static IBluetoothHci castFrom(IHwInterface iface) { + return (iface == null) ? null : IBluetoothHci.asInterface(iface.asBinder()); + } + + @Override + public IHwBinder asBinder(); + + /** + * This will invoke the equivalent of the C++ getService(std::string) if retry is + * true or tryGetService(std::string) if retry is false. If the service is + * available on the device and retry is true, this will wait for the service to + * start. + * + */ + public static IBluetoothHci getService(String serviceName, boolean retry) throws android.os.RemoteException { + return IBluetoothHci.asInterface(HwBinder.getService("android.hardware.bluetooth@1.1::IBluetoothHci", serviceName, retry)); + } + + /** + * Calls getService("default",retry). + */ + public static IBluetoothHci getService(boolean retry) throws android.os.RemoteException { + return getService("default", retry); + } + + /** + * @deprecated this will not wait for the interface to come up if it hasn't yet + * started. See getService(String,boolean) instead. + */ + @Deprecated + public static IBluetoothHci getService(String serviceName) throws android.os.RemoteException { + return IBluetoothHci.asInterface(HwBinder.getService("android.hardware.bluetooth@1.1::IBluetoothHci", serviceName)); + } + + /** + * @deprecated this will not wait for the interface to come up if it hasn't yet + * started. See getService(boolean) instead. + */ + @Deprecated + public static IBluetoothHci getService() throws android.os.RemoteException { + return getService("default"); + } + + /** + * Same as @1.0, but uses 1.1 Callbacks version + */ + void initialize_1_1(android.hardware.bluetooth.V1_1.IBluetoothHciCallbacks callback) + throws android.os.RemoteException; + /** + * Send an ISO data packet (as specified in the Bluetooth Core + * Specification v5.2) to the Bluetooth controller. + * Packets must be processed in order. + * @param data HCI data packet to be sent + */ + void sendIsoData(java.util.ArrayList data) + throws android.os.RemoteException; + /* + * Provides run-time type information for this object. + * For example, for the following interface definition: + * package android.hardware.foo@1.0; + * interface IParent {}; + * interface IChild extends IParent {}; + * Calling interfaceChain on an IChild object must yield the following: + * ["android.hardware.foo@1.0::IChild", + * "android.hardware.foo@1.0::IParent" + * "android.internal.hidl.base@1.0::IBase"] + * + * @return descriptors a vector of descriptors of the run-time type of the + * object. + */ + java.util.ArrayList interfaceChain() + throws android.os.RemoteException; + /* + * Emit diagnostic information to the given file. + * + * Optionally overriden. + * + * @param fd File descriptor to dump data to. + * Must only be used for the duration of this call. + * @param options Arguments for debugging. + * Must support empty for default debug information. + */ + void debug(NativeHandle fd, java.util.ArrayList options) + throws android.os.RemoteException; + /* + * Provides run-time type information for this object. + * For example, for the following interface definition: + * package android.hardware.foo@1.0; + * interface IParent {}; + * interface IChild extends IParent {}; + * Calling interfaceDescriptor on an IChild object must yield + * "android.hardware.foo@1.0::IChild" + * + * @return descriptor a descriptor of the run-time type of the + * object (the first element of the vector returned by + * interfaceChain()) + */ + String interfaceDescriptor() + throws android.os.RemoteException; + /* + * Returns hashes of the source HAL files that define the interfaces of the + * runtime type information on the object. + * For example, for the following interface definition: + * package android.hardware.foo@1.0; + * interface IParent {}; + * interface IChild extends IParent {}; + * Calling interfaceChain on an IChild object must yield the following: + * [(hash of IChild.hal), + * (hash of IParent.hal) + * (hash of IBase.hal)]. + * + * SHA-256 is used as the hashing algorithm. Each hash has 32 bytes + * according to SHA-256 standard. + * + * @return hashchain a vector of SHA-1 digests + */ + java.util.ArrayList getHashChain() + throws android.os.RemoteException; + /* + * This method trigger the interface to enable/disable instrumentation based + * on system property hal.instrumentation.enable. + */ + void setHALInstrumentation() + throws android.os.RemoteException; + /* + * Registers a death recipient, to be called when the process hosting this + * interface dies. + * + * @param recipient a hidl_death_recipient callback object + * @param cookie a cookie that must be returned with the callback + * @return success whether the death recipient was registered successfully. + */ + boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie) + throws android.os.RemoteException; + /* + * Provides way to determine if interface is running without requesting + * any functionality. + */ + void ping() + throws android.os.RemoteException; + /* + * Get debug information on references on this interface. + * @return info debugging information. See comments of DebugInfo. + */ + android.internal.hidl.base.V1_0.DebugInfo getDebugInfo() + throws android.os.RemoteException; + /* + * This method notifies the interface that one or more system properties + * have changed. The default implementation calls + * (C++) report_sysprop_change() in libcutils or + * (Java) android.os.SystemProperties.reportSyspropChanged, + * which in turn calls a set of registered callbacks (eg to update trace + * tags). + */ + void notifySyspropsChanged() + throws android.os.RemoteException; + /* + * Unregisters the registered death recipient. If this service was registered + * multiple times with the same exact death recipient, this unlinks the most + * recently registered one. + * + * @param recipient a previously registered hidl_death_recipient callback + * @return success whether the death recipient was unregistered successfully. + */ + boolean unlinkToDeath(IHwBinder.DeathRecipient recipient) + throws android.os.RemoteException; + + public static final class Proxy implements IBluetoothHci { + private IHwBinder mRemote; + + public Proxy(IHwBinder remote) { + mRemote = java.util.Objects.requireNonNull(remote); + } + + @Override + public IHwBinder asBinder() { + return mRemote; + } + + @Override + public String toString() { + try { + return this.interfaceDescriptor() + "@Proxy"; + } catch (android.os.RemoteException ex) { + /* ignored; handled below. */ + } + return "[class or subclass of " + IBluetoothHci.kInterfaceName + "]@Proxy"; + } + + @Override + public final boolean equals(java.lang.Object other) { + return HidlSupport.interfacesEqual(this, other); + } + + @Override + public final int hashCode() { + return this.asBinder().hashCode(); + } + + // Methods from ::android::hardware::bluetooth::V1_0::IBluetoothHci follow. + @Override + public void initialize(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks callback) + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName); + _hidl_request.writeStrongBinder(callback == null ? null : callback.asBinder()); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(1 /* initialize */, _hidl_request, _hidl_reply, 0 /* flags */); + _hidl_reply.verifySuccess(); + _hidl_request.releaseTemporaryStorage(); + } finally { + _hidl_reply.release(); + } + } + + @Override + public void sendHciCommand(java.util.ArrayList command) + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName); + _hidl_request.writeInt8Vector(command); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(2 /* sendHciCommand */, _hidl_request, _hidl_reply, 0 /* flags */); + _hidl_reply.verifySuccess(); + _hidl_request.releaseTemporaryStorage(); + } finally { + _hidl_reply.release(); + } + } + + @Override + public void sendAclData(java.util.ArrayList data) + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName); + _hidl_request.writeInt8Vector(data); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(3 /* sendAclData */, _hidl_request, _hidl_reply, 0 /* flags */); + _hidl_reply.verifySuccess(); + _hidl_request.releaseTemporaryStorage(); + } finally { + _hidl_reply.release(); + } + } + + @Override + public void sendScoData(java.util.ArrayList data) + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName); + _hidl_request.writeInt8Vector(data); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(4 /* sendScoData */, _hidl_request, _hidl_reply, 0 /* flags */); + _hidl_reply.verifySuccess(); + _hidl_request.releaseTemporaryStorage(); + } finally { + _hidl_reply.release(); + } + } + + @Override + public void close() + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(5 /* close */, _hidl_request, _hidl_reply, 0 /* flags */); + _hidl_reply.verifySuccess(); + _hidl_request.releaseTemporaryStorage(); + } finally { + _hidl_reply.release(); + } + } + + // Methods from ::android::hardware::bluetooth::V1_1::IBluetoothHci follow. + @Override + public void initialize_1_1(android.hardware.bluetooth.V1_1.IBluetoothHciCallbacks callback) + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_1.IBluetoothHci.kInterfaceName); + _hidl_request.writeStrongBinder(callback == null ? null : callback.asBinder()); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(6 /* initialize_1_1 */, _hidl_request, _hidl_reply, 0 /* flags */); + _hidl_reply.verifySuccess(); + _hidl_request.releaseTemporaryStorage(); + } finally { + _hidl_reply.release(); + } + } + + @Override + public void sendIsoData(java.util.ArrayList data) + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_1.IBluetoothHci.kInterfaceName); + _hidl_request.writeInt8Vector(data); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(7 /* sendIsoData */, _hidl_request, _hidl_reply, 0 /* flags */); + _hidl_reply.verifySuccess(); + _hidl_request.releaseTemporaryStorage(); + } finally { + _hidl_reply.release(); + } + } + + // Methods from ::android::hidl::base::V1_0::IBase follow. + @Override + public java.util.ArrayList interfaceChain() + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(256067662 /* interfaceChain */, _hidl_request, _hidl_reply, 0 /* flags */); + _hidl_reply.verifySuccess(); + _hidl_request.releaseTemporaryStorage(); + + java.util.ArrayList _hidl_out_descriptors = _hidl_reply.readStringVector(); + return _hidl_out_descriptors; + } finally { + _hidl_reply.release(); + } + } + + @Override + public void debug(NativeHandle fd, java.util.ArrayList options) + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + _hidl_request.writeNativeHandle(fd); + _hidl_request.writeStringVector(options); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(256131655 /* debug */, _hidl_request, _hidl_reply, 0 /* flags */); + _hidl_reply.verifySuccess(); + _hidl_request.releaseTemporaryStorage(); + } finally { + _hidl_reply.release(); + } + } + + @Override + public String interfaceDescriptor() + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(256136003 /* interfaceDescriptor */, _hidl_request, _hidl_reply, 0 /* flags */); + _hidl_reply.verifySuccess(); + _hidl_request.releaseTemporaryStorage(); + + String _hidl_out_descriptor = _hidl_reply.readString(); + return _hidl_out_descriptor; + } finally { + _hidl_reply.release(); + } + } + + @Override + public java.util.ArrayList getHashChain() + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(256398152 /* getHashChain */, _hidl_request, _hidl_reply, 0 /* flags */); + _hidl_reply.verifySuccess(); + _hidl_request.releaseTemporaryStorage(); + + java.util.ArrayList _hidl_out_hashchain = new java.util.ArrayList(); + { + HwBlob _hidl_blob = _hidl_reply.readBuffer(16 /* size */); + { + int _hidl_vec_size = _hidl_blob.getInt32(0 /* offset */ + 8 /* offsetof(hidl_vec, mSize) */); + HwBlob childBlob = _hidl_reply.readEmbeddedBuffer( + _hidl_vec_size * 32,_hidl_blob.handle(), + 0 /* offset */ + 0 /* offsetof(hidl_vec, mBuffer) */,true /* nullable */); + + ((java.util.ArrayList) _hidl_out_hashchain).clear(); + for (int _hidl_index_0 = 0; _hidl_index_0 < _hidl_vec_size; ++_hidl_index_0) { + byte[/* 32 */] _hidl_vec_element = new byte[32]; + { + long _hidl_array_offset_1 = _hidl_index_0 * 32; + childBlob.copyToInt8Array(_hidl_array_offset_1, (byte[/* 32 */]) _hidl_vec_element, 32 /* size */); + _hidl_array_offset_1 += 32 * 1; + } + ((java.util.ArrayList) _hidl_out_hashchain).add(_hidl_vec_element); + } + } + } + return _hidl_out_hashchain; + } finally { + _hidl_reply.release(); + } + } + + @Override + public void setHALInstrumentation() + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(256462420 /* setHALInstrumentation */, _hidl_request, _hidl_reply, 1 /* oneway */); + _hidl_request.releaseTemporaryStorage(); + } finally { + _hidl_reply.release(); + } + } + + @Override + public boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie) + throws android.os.RemoteException { + return mRemote.linkToDeath(recipient, cookie); + } + @Override + public void ping() + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(256921159 /* ping */, _hidl_request, _hidl_reply, 0 /* flags */); + _hidl_reply.verifySuccess(); + _hidl_request.releaseTemporaryStorage(); + } finally { + _hidl_reply.release(); + } + } + + @Override + public android.internal.hidl.base.V1_0.DebugInfo getDebugInfo() + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(257049926 /* getDebugInfo */, _hidl_request, _hidl_reply, 0 /* flags */); + _hidl_reply.verifySuccess(); + _hidl_request.releaseTemporaryStorage(); + + android.internal.hidl.base.V1_0.DebugInfo _hidl_out_info = new android.internal.hidl.base.V1_0.DebugInfo(); + ((android.internal.hidl.base.V1_0.DebugInfo) _hidl_out_info).readFromParcel(_hidl_reply); + return _hidl_out_info; + } finally { + _hidl_reply.release(); + } + } + + @Override + public void notifySyspropsChanged() + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(257120595 /* notifySyspropsChanged */, _hidl_request, _hidl_reply, 1 /* oneway */); + _hidl_request.releaseTemporaryStorage(); + } finally { + _hidl_reply.release(); + } + } + + @Override + public boolean unlinkToDeath(IHwBinder.DeathRecipient recipient) + throws android.os.RemoteException { + return mRemote.unlinkToDeath(recipient); + } + } + + public static abstract class Stub extends HwBinder implements IBluetoothHci { + @Override + public IHwBinder asBinder() { + return this; + } + + @Override + public final java.util.ArrayList interfaceChain() { + return new java.util.ArrayList(java.util.Arrays.asList( + android.hardware.bluetooth.V1_1.IBluetoothHci.kInterfaceName, + android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName, + android.internal.hidl.base.V1_0.IBase.kInterfaceName)); + + } + + @Override + public void debug(NativeHandle fd, java.util.ArrayList options) { + return; + + } + + @Override + public final String interfaceDescriptor() { + return android.hardware.bluetooth.V1_1.IBluetoothHci.kInterfaceName; + + } + + @Override + public final java.util.ArrayList getHashChain() { + return new java.util.ArrayList(java.util.Arrays.asList( + new byte[/* 32 */]{54,47,-47,-62,22,65,-62,34,79,59,-128,-61,13,-105,-105,-71,-120,-6,63,52,66,67,-43,49,-70,115,-59,83,119,-102,87,99} /* 362fd1c21641c2224f3b80c30d9797b988fa3f344243d531ba73c553779a5763 */, + new byte[/* 32 */]{52,124,-25,70,-127,86,7,86,127,95,59,83,-28,-128,9,-104,-54,90,-71,53,81,65,-16,-120,15,-64,-49,12,31,-59,-61,85} /* 347ce746815607567f5f3b53e4800998ca5ab9355141f0880fc0cf0c1fc5c355 */, + new byte[/* 32 */]{-20,127,-41,-98,-48,45,-6,-123,-68,73,-108,38,-83,-82,62,-66,35,-17,5,36,-13,-51,105,87,19,-109,36,-72,59,24,-54,76} /* ec7fd79ed02dfa85bc499426adae3ebe23ef0524f3cd6957139324b83b18ca4c */)); + + } + + @Override + public final void setHALInstrumentation() { + + } + + @Override + public final boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie) { + return true; + + } + + @Override + public final void ping() { + return; + + } + + @Override + public final android.internal.hidl.base.V1_0.DebugInfo getDebugInfo() { + android.internal.hidl.base.V1_0.DebugInfo info = new android.internal.hidl.base.V1_0.DebugInfo(); + info.pid = HidlSupport.getPidIfSharable(); + info.ptr = 0; + info.arch = android.internal.hidl.base.V1_0.DebugInfo.Architecture.UNKNOWN; + return info; + + } + + @Override + public final void notifySyspropsChanged() { + HwBinder.enableInstrumentation(); + + } + + @Override + public final boolean unlinkToDeath(IHwBinder.DeathRecipient recipient) { + return true; + + } + + @Override + public IHwInterface queryLocalInterface(String descriptor) { + if (kInterfaceName.equals(descriptor)) { + return this; + } + return null; + } + + public void registerAsService(String serviceName) throws android.os.RemoteException { + registerService(serviceName); + } + + @Override + public String toString() { + return this.interfaceDescriptor() + "@Stub"; + } + + //@Override + public void onTransact(int _hidl_code, HwParcel _hidl_request, final HwParcel _hidl_reply, int _hidl_flags) + throws android.os.RemoteException { + switch (_hidl_code) { + case 1 /* initialize */: + { + _hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName); + + android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks callback = android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.asInterface(_hidl_request.readStrongBinder()); + initialize(callback); + _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS); + _hidl_reply.send(); + break; + } + + case 2 /* sendHciCommand */: + { + _hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName); + + java.util.ArrayList command = _hidl_request.readInt8Vector(); + sendHciCommand(command); + _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS); + _hidl_reply.send(); + break; + } + + case 3 /* sendAclData */: + { + _hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName); + + java.util.ArrayList data = _hidl_request.readInt8Vector(); + sendAclData(data); + _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS); + _hidl_reply.send(); + break; + } + + case 4 /* sendScoData */: + { + _hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName); + + java.util.ArrayList data = _hidl_request.readInt8Vector(); + sendScoData(data); + _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS); + _hidl_reply.send(); + break; + } + + case 5 /* close */: + { + _hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHci.kInterfaceName); + + close(); + _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS); + _hidl_reply.send(); + break; + } + + case 6 /* initialize_1_1 */: + { + _hidl_request.enforceInterface(android.hardware.bluetooth.V1_1.IBluetoothHci.kInterfaceName); + + android.hardware.bluetooth.V1_1.IBluetoothHciCallbacks callback = android.hardware.bluetooth.V1_1.IBluetoothHciCallbacks.asInterface(_hidl_request.readStrongBinder()); + initialize_1_1(callback); + _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS); + _hidl_reply.send(); + break; + } + + case 7 /* sendIsoData */: + { + _hidl_request.enforceInterface(android.hardware.bluetooth.V1_1.IBluetoothHci.kInterfaceName); + + java.util.ArrayList data = _hidl_request.readInt8Vector(); + sendIsoData(data); + _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS); + _hidl_reply.send(); + break; + } + + case 256067662 /* interfaceChain */: + { + _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + java.util.ArrayList _hidl_out_descriptors = interfaceChain(); + _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS); + _hidl_reply.writeStringVector(_hidl_out_descriptors); + _hidl_reply.send(); + break; + } + + case 256131655 /* debug */: + { + _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + NativeHandle fd = _hidl_request.readNativeHandle(); + java.util.ArrayList options = _hidl_request.readStringVector(); + debug(fd, options); + _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS); + _hidl_reply.send(); + break; + } + + case 256136003 /* interfaceDescriptor */: + { + _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + String _hidl_out_descriptor = interfaceDescriptor(); + _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS); + _hidl_reply.writeString(_hidl_out_descriptor); + _hidl_reply.send(); + break; + } + + case 256398152 /* getHashChain */: + { + _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + java.util.ArrayList _hidl_out_hashchain = getHashChain(); + _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS); + { + HwBlob _hidl_blob = new HwBlob(16 /* size */); + { + int _hidl_vec_size = _hidl_out_hashchain.size(); + _hidl_blob.putInt32(0 /* offset */ + 8 /* offsetof(hidl_vec, mSize) */, _hidl_vec_size); + _hidl_blob.putBool(0 /* offset */ + 12 /* offsetof(hidl_vec, mOwnsBuffer) */, false); + HwBlob childBlob = new HwBlob((int)(_hidl_vec_size * 32)); + for (int _hidl_index_0 = 0; _hidl_index_0 < _hidl_vec_size; ++_hidl_index_0) { + { + long _hidl_array_offset_1 = _hidl_index_0 * 32; + byte[] _hidl_array_item_1 = (byte[/* 32 */]) _hidl_out_hashchain.get(_hidl_index_0); + + if (_hidl_array_item_1 == null || _hidl_array_item_1.length != 32) { + throw new IllegalArgumentException("Array element is not of the expected length"); + } + + childBlob.putInt8Array(_hidl_array_offset_1, _hidl_array_item_1); + _hidl_array_offset_1 += 32 * 1; + } + } + _hidl_blob.putBlob(0 /* offset */ + 0 /* offsetof(hidl_vec, mBuffer) */, childBlob); + } + _hidl_reply.writeBuffer(_hidl_blob); + } + _hidl_reply.send(); + break; + } + + case 256462420 /* setHALInstrumentation */: + { + _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + setHALInstrumentation(); + break; + } + + case 256660548 /* linkToDeath */: + { + break; + } + + case 256921159 /* ping */: + { + _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + ping(); + _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS); + _hidl_reply.send(); + break; + } + + case 257049926 /* getDebugInfo */: + { + _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + android.internal.hidl.base.V1_0.DebugInfo _hidl_out_info = getDebugInfo(); + _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS); + ((android.internal.hidl.base.V1_0.DebugInfo) _hidl_out_info).writeToParcel(_hidl_reply); + _hidl_reply.send(); + break; + } + + case 257120595 /* notifySyspropsChanged */: + { + _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + notifySyspropsChanged(); + break; + } + + case 257250372 /* unlinkToDeath */: + { + break; + } + + } + } + } +} diff --git a/extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/V1_1/IBluetoothHciCallbacks.java b/extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/V1_1/IBluetoothHciCallbacks.java new file mode 100644 index 00000000..905e3a05 --- /dev/null +++ b/extras/android/RemoteHCI/app/src/main/java/android/hardware/bluetooth/V1_1/IBluetoothHciCallbacks.java @@ -0,0 +1,774 @@ +package android.hardware.bluetooth.V1_1; + +import android.os.HidlSupport; +import android.os.HwBinder; +import android.os.IHwBinder; +import android.os.HwBlob; +import android.os.HwParcel; +import android.os.IHwInterface; +import android.os.NativeHandle; + +/** + * The interface from the Bluetooth Controller to the stack. + */ +public interface IBluetoothHciCallbacks extends android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks { + /** + * Fully-qualified interface name for this interface. + */ + public static final String kInterfaceName = "android.hardware.bluetooth@1.1::IBluetoothHciCallbacks"; + + /** + * Does a checked conversion from a binder to this class. + */ + /* package private */ static IBluetoothHciCallbacks asInterface(IHwBinder binder) { + if (binder == null) { + return null; + } + + IHwInterface iface = + binder.queryLocalInterface(kInterfaceName); + + if ((iface != null) && (iface instanceof IBluetoothHciCallbacks)) { + return (IBluetoothHciCallbacks)iface; + } + + IBluetoothHciCallbacks proxy = new IBluetoothHciCallbacks.Proxy(binder); + + try { + for (String descriptor : proxy.interfaceChain()) { + if (descriptor.equals(kInterfaceName)) { + return proxy; + } + } + } catch (android.os.RemoteException e) { + } + + return null; + } + + /** + * Does a checked conversion from any interface to this class. + */ + public static IBluetoothHciCallbacks castFrom(IHwInterface iface) { + return (iface == null) ? null : IBluetoothHciCallbacks.asInterface(iface.asBinder()); + } + + @Override + public IHwBinder asBinder(); + + /** + * This will invoke the equivalent of the C++ getService(std::string) if retry is + * true or tryGetService(std::string) if retry is false. If the service is + * available on the device and retry is true, this will wait for the service to + * start. + * + */ + public static IBluetoothHciCallbacks getService(String serviceName, boolean retry) throws android.os.RemoteException { + return IBluetoothHciCallbacks.asInterface(HwBinder.getService("android.hardware.bluetooth@1.1::IBluetoothHciCallbacks", serviceName, retry)); + } + + /** + * Calls getService("default",retry). + */ + public static IBluetoothHciCallbacks getService(boolean retry) throws android.os.RemoteException { + return getService("default", retry); + } + + /** + * @deprecated this will not wait for the interface to come up if it hasn't yet + * started. See getService(String,boolean) instead. + */ + @Deprecated + public static IBluetoothHciCallbacks getService(String serviceName) throws android.os.RemoteException { + return IBluetoothHciCallbacks.asInterface(HwBinder.getService("android.hardware.bluetooth@1.1::IBluetoothHciCallbacks", serviceName)); + } + + /** + * @deprecated this will not wait for the interface to come up if it hasn't yet + * started. See getService(boolean) instead. + */ + @Deprecated + public static IBluetoothHciCallbacks getService() throws android.os.RemoteException { + return getService("default"); + } + + /** + * Send a ISO data packet form the controller to the host. + * @param data the ISO HCI packet to be passed to the host stack + */ + void isoDataReceived(java.util.ArrayList data) + throws android.os.RemoteException; + /* + * Provides run-time type information for this object. + * For example, for the following interface definition: + * package android.hardware.foo@1.0; + * interface IParent {}; + * interface IChild extends IParent {}; + * Calling interfaceChain on an IChild object must yield the following: + * ["android.hardware.foo@1.0::IChild", + * "android.hardware.foo@1.0::IParent" + * "android.internal.hidl.base@1.0::IBase"] + * + * @return descriptors a vector of descriptors of the run-time type of the + * object. + */ + java.util.ArrayList interfaceChain() + throws android.os.RemoteException; + /* + * Emit diagnostic information to the given file. + * + * Optionally overriden. + * + * @param fd File descriptor to dump data to. + * Must only be used for the duration of this call. + * @param options Arguments for debugging. + * Must support empty for default debug information. + */ + void debug(NativeHandle fd, java.util.ArrayList options) + throws android.os.RemoteException; + /* + * Provides run-time type information for this object. + * For example, for the following interface definition: + * package android.hardware.foo@1.0; + * interface IParent {}; + * interface IChild extends IParent {}; + * Calling interfaceDescriptor on an IChild object must yield + * "android.hardware.foo@1.0::IChild" + * + * @return descriptor a descriptor of the run-time type of the + * object (the first element of the vector returned by + * interfaceChain()) + */ + String interfaceDescriptor() + throws android.os.RemoteException; + /* + * Returns hashes of the source HAL files that define the interfaces of the + * runtime type information on the object. + * For example, for the following interface definition: + * package android.hardware.foo@1.0; + * interface IParent {}; + * interface IChild extends IParent {}; + * Calling interfaceChain on an IChild object must yield the following: + * [(hash of IChild.hal), + * (hash of IParent.hal) + * (hash of IBase.hal)]. + * + * SHA-256 is used as the hashing algorithm. Each hash has 32 bytes + * according to SHA-256 standard. + * + * @return hashchain a vector of SHA-1 digests + */ + java.util.ArrayList getHashChain() + throws android.os.RemoteException; + /* + * This method trigger the interface to enable/disable instrumentation based + * on system property hal.instrumentation.enable. + */ + void setHALInstrumentation() + throws android.os.RemoteException; + /* + * Registers a death recipient, to be called when the process hosting this + * interface dies. + * + * @param recipient a hidl_death_recipient callback object + * @param cookie a cookie that must be returned with the callback + * @return success whether the death recipient was registered successfully. + */ + boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie) + throws android.os.RemoteException; + /* + * Provides way to determine if interface is running without requesting + * any functionality. + */ + void ping() + throws android.os.RemoteException; + /* + * Get debug information on references on this interface. + * @return info debugging information. See comments of DebugInfo. + */ + android.internal.hidl.base.V1_0.DebugInfo getDebugInfo() + throws android.os.RemoteException; + /* + * This method notifies the interface that one or more system properties + * have changed. The default implementation calls + * (C++) report_sysprop_change() in libcutils or + * (Java) android.os.SystemProperties.reportSyspropChanged, + * which in turn calls a set of registered callbacks (eg to update trace + * tags). + */ + void notifySyspropsChanged() + throws android.os.RemoteException; + /* + * Unregisters the registered death recipient. If this service was registered + * multiple times with the same exact death recipient, this unlinks the most + * recently registered one. + * + * @param recipient a previously registered hidl_death_recipient callback + * @return success whether the death recipient was unregistered successfully. + */ + boolean unlinkToDeath(IHwBinder.DeathRecipient recipient) + throws android.os.RemoteException; + + public static final class Proxy implements IBluetoothHciCallbacks { + private IHwBinder mRemote; + + public Proxy(IHwBinder remote) { + mRemote = java.util.Objects.requireNonNull(remote); + } + + @Override + public IHwBinder asBinder() { + return mRemote; + } + + @Override + public String toString() { + try { + return this.interfaceDescriptor() + "@Proxy"; + } catch (android.os.RemoteException ex) { + /* ignored; handled below. */ + } + return "[class or subclass of " + IBluetoothHciCallbacks.kInterfaceName + "]@Proxy"; + } + + @Override + public final boolean equals(java.lang.Object other) { + return HidlSupport.interfacesEqual(this, other); + } + + @Override + public final int hashCode() { + return this.asBinder().hashCode(); + } + + // Methods from ::android::hardware::bluetooth::V1_0::IBluetoothHciCallbacks follow. + @Override + public void initializationComplete(int status) + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName); + _hidl_request.writeInt32(status); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(1 /* initializationComplete */, _hidl_request, _hidl_reply, 0 /* flags */); + _hidl_reply.verifySuccess(); + _hidl_request.releaseTemporaryStorage(); + } finally { + _hidl_reply.release(); + } + } + + @Override + public void hciEventReceived(java.util.ArrayList event) + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName); + _hidl_request.writeInt8Vector(event); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(2 /* hciEventReceived */, _hidl_request, _hidl_reply, 0 /* flags */); + _hidl_reply.verifySuccess(); + _hidl_request.releaseTemporaryStorage(); + } finally { + _hidl_reply.release(); + } + } + + @Override + public void aclDataReceived(java.util.ArrayList data) + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName); + _hidl_request.writeInt8Vector(data); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(3 /* aclDataReceived */, _hidl_request, _hidl_reply, 0 /* flags */); + _hidl_reply.verifySuccess(); + _hidl_request.releaseTemporaryStorage(); + } finally { + _hidl_reply.release(); + } + } + + @Override + public void scoDataReceived(java.util.ArrayList data) + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName); + _hidl_request.writeInt8Vector(data); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(4 /* scoDataReceived */, _hidl_request, _hidl_reply, 0 /* flags */); + _hidl_reply.verifySuccess(); + _hidl_request.releaseTemporaryStorage(); + } finally { + _hidl_reply.release(); + } + } + + // Methods from ::android::hardware::bluetooth::V1_1::IBluetoothHciCallbacks follow. + @Override + public void isoDataReceived(java.util.ArrayList data) + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.hardware.bluetooth.V1_1.IBluetoothHciCallbacks.kInterfaceName); + _hidl_request.writeInt8Vector(data); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(5 /* isoDataReceived */, _hidl_request, _hidl_reply, 0 /* flags */); + _hidl_reply.verifySuccess(); + _hidl_request.releaseTemporaryStorage(); + } finally { + _hidl_reply.release(); + } + } + + // Methods from ::android::hidl::base::V1_0::IBase follow. + @Override + public java.util.ArrayList interfaceChain() + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(256067662 /* interfaceChain */, _hidl_request, _hidl_reply, 0 /* flags */); + _hidl_reply.verifySuccess(); + _hidl_request.releaseTemporaryStorage(); + + java.util.ArrayList _hidl_out_descriptors = _hidl_reply.readStringVector(); + return _hidl_out_descriptors; + } finally { + _hidl_reply.release(); + } + } + + @Override + public void debug(NativeHandle fd, java.util.ArrayList options) + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + _hidl_request.writeNativeHandle(fd); + _hidl_request.writeStringVector(options); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(256131655 /* debug */, _hidl_request, _hidl_reply, 0 /* flags */); + _hidl_reply.verifySuccess(); + _hidl_request.releaseTemporaryStorage(); + } finally { + _hidl_reply.release(); + } + } + + @Override + public String interfaceDescriptor() + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(256136003 /* interfaceDescriptor */, _hidl_request, _hidl_reply, 0 /* flags */); + _hidl_reply.verifySuccess(); + _hidl_request.releaseTemporaryStorage(); + + String _hidl_out_descriptor = _hidl_reply.readString(); + return _hidl_out_descriptor; + } finally { + _hidl_reply.release(); + } + } + + @Override + public java.util.ArrayList getHashChain() + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(256398152 /* getHashChain */, _hidl_request, _hidl_reply, 0 /* flags */); + _hidl_reply.verifySuccess(); + _hidl_request.releaseTemporaryStorage(); + + java.util.ArrayList _hidl_out_hashchain = new java.util.ArrayList(); + { + HwBlob _hidl_blob = _hidl_reply.readBuffer(16 /* size */); + { + int _hidl_vec_size = _hidl_blob.getInt32(0 /* offset */ + 8 /* offsetof(hidl_vec, mSize) */); + HwBlob childBlob = _hidl_reply.readEmbeddedBuffer( + _hidl_vec_size * 32,_hidl_blob.handle(), + 0 /* offset */ + 0 /* offsetof(hidl_vec, mBuffer) */,true /* nullable */); + + ((java.util.ArrayList) _hidl_out_hashchain).clear(); + for (int _hidl_index_0 = 0; _hidl_index_0 < _hidl_vec_size; ++_hidl_index_0) { + byte[/* 32 */] _hidl_vec_element = new byte[32]; + { + long _hidl_array_offset_1 = _hidl_index_0 * 32; + childBlob.copyToInt8Array(_hidl_array_offset_1, (byte[/* 32 */]) _hidl_vec_element, 32 /* size */); + _hidl_array_offset_1 += 32 * 1; + } + ((java.util.ArrayList) _hidl_out_hashchain).add(_hidl_vec_element); + } + } + } + return _hidl_out_hashchain; + } finally { + _hidl_reply.release(); + } + } + + @Override + public void setHALInstrumentation() + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(256462420 /* setHALInstrumentation */, _hidl_request, _hidl_reply, 1 /* oneway */); + _hidl_request.releaseTemporaryStorage(); + } finally { + _hidl_reply.release(); + } + } + + @Override + public boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie) + throws android.os.RemoteException { + return mRemote.linkToDeath(recipient, cookie); + } + @Override + public void ping() + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(256921159 /* ping */, _hidl_request, _hidl_reply, 0 /* flags */); + _hidl_reply.verifySuccess(); + _hidl_request.releaseTemporaryStorage(); + } finally { + _hidl_reply.release(); + } + } + + @Override + public android.internal.hidl.base.V1_0.DebugInfo getDebugInfo() + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(257049926 /* getDebugInfo */, _hidl_request, _hidl_reply, 0 /* flags */); + _hidl_reply.verifySuccess(); + _hidl_request.releaseTemporaryStorage(); + + android.internal.hidl.base.V1_0.DebugInfo _hidl_out_info = new android.internal.hidl.base.V1_0.DebugInfo(); + ((android.internal.hidl.base.V1_0.DebugInfo) _hidl_out_info).readFromParcel(_hidl_reply); + return _hidl_out_info; + } finally { + _hidl_reply.release(); + } + } + + @Override + public void notifySyspropsChanged() + throws android.os.RemoteException { + HwParcel _hidl_request = new HwParcel(); + _hidl_request.writeInterfaceToken(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + HwParcel _hidl_reply = new HwParcel(); + try { + mRemote.transact(257120595 /* notifySyspropsChanged */, _hidl_request, _hidl_reply, 1 /* oneway */); + _hidl_request.releaseTemporaryStorage(); + } finally { + _hidl_reply.release(); + } + } + + @Override + public boolean unlinkToDeath(IHwBinder.DeathRecipient recipient) + throws android.os.RemoteException { + return mRemote.unlinkToDeath(recipient); + } + } + + public static abstract class Stub extends HwBinder implements IBluetoothHciCallbacks { + @Override + public IHwBinder asBinder() { + return this; + } + + @Override + public final java.util.ArrayList interfaceChain() { + return new java.util.ArrayList(java.util.Arrays.asList( + android.hardware.bluetooth.V1_1.IBluetoothHciCallbacks.kInterfaceName, + android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName, + android.internal.hidl.base.V1_0.IBase.kInterfaceName)); + + } + + @Override + public void debug(NativeHandle fd, java.util.ArrayList options) { + return; + + } + + @Override + public final String interfaceDescriptor() { + return android.hardware.bluetooth.V1_1.IBluetoothHciCallbacks.kInterfaceName; + + } + + @Override + public final java.util.ArrayList getHashChain() { + return new java.util.ArrayList(java.util.Arrays.asList( + new byte[/* 32 */]{64,-85,44,104,102,-63,-115,50,-70,-10,-28,-98,48,83,-108,-98,121,96,31,86,-106,58,121,30,-109,-26,-117,-98,-31,-113,113,-115} /* 40ab2c6866c18d32baf6e49e3053949e79601f56963a791e93e68b9ee18f718d */, + new byte[/* 32 */]{-125,95,65,-66,34,-127,-65,-78,47,62,51,-58,-6,-121,11,-34,123,-62,30,55,-27,-49,-70,-7,-93,111,-1,23,6,50,-9,84} /* 835f41be2281bfb22f3e33c6fa870bde7bc21e37e5cfbaf9a36fff170632f754 */, + new byte[/* 32 */]{-20,127,-41,-98,-48,45,-6,-123,-68,73,-108,38,-83,-82,62,-66,35,-17,5,36,-13,-51,105,87,19,-109,36,-72,59,24,-54,76} /* ec7fd79ed02dfa85bc499426adae3ebe23ef0524f3cd6957139324b83b18ca4c */)); + + } + + @Override + public final void setHALInstrumentation() { + + } + + @Override + public final boolean linkToDeath(IHwBinder.DeathRecipient recipient, long cookie) { + return true; + + } + + @Override + public final void ping() { + return; + + } + + @Override + public final android.internal.hidl.base.V1_0.DebugInfo getDebugInfo() { + android.internal.hidl.base.V1_0.DebugInfo info = new android.internal.hidl.base.V1_0.DebugInfo(); + info.pid = HidlSupport.getPidIfSharable(); + info.ptr = 0; + info.arch = android.internal.hidl.base.V1_0.DebugInfo.Architecture.UNKNOWN; + return info; + + } + + @Override + public final void notifySyspropsChanged() { + HwBinder.enableInstrumentation(); + + } + + @Override + public final boolean unlinkToDeath(IHwBinder.DeathRecipient recipient) { + return true; + + } + + @Override + public IHwInterface queryLocalInterface(String descriptor) { + if (kInterfaceName.equals(descriptor)) { + return this; + } + return null; + } + + public void registerAsService(String serviceName) throws android.os.RemoteException { + registerService(serviceName); + } + + @Override + public String toString() { + return this.interfaceDescriptor() + "@Stub"; + } + + //@Override + public void onTransact(int _hidl_code, HwParcel _hidl_request, final HwParcel _hidl_reply, int _hidl_flags) + throws android.os.RemoteException { + switch (_hidl_code) { + case 1 /* initializationComplete */: + { + _hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName); + + int status = _hidl_request.readInt32(); + initializationComplete(status); + _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS); + _hidl_reply.send(); + break; + } + + case 2 /* hciEventReceived */: + { + _hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName); + + java.util.ArrayList event = _hidl_request.readInt8Vector(); + hciEventReceived(event); + _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS); + _hidl_reply.send(); + break; + } + + case 3 /* aclDataReceived */: + { + _hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName); + + java.util.ArrayList data = _hidl_request.readInt8Vector(); + aclDataReceived(data); + _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS); + _hidl_reply.send(); + break; + } + + case 4 /* scoDataReceived */: + { + _hidl_request.enforceInterface(android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.kInterfaceName); + + java.util.ArrayList data = _hidl_request.readInt8Vector(); + scoDataReceived(data); + _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS); + _hidl_reply.send(); + break; + } + + case 5 /* isoDataReceived */: + { + _hidl_request.enforceInterface(android.hardware.bluetooth.V1_1.IBluetoothHciCallbacks.kInterfaceName); + + java.util.ArrayList data = _hidl_request.readInt8Vector(); + isoDataReceived(data); + _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS); + _hidl_reply.send(); + break; + } + + case 256067662 /* interfaceChain */: + { + _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + java.util.ArrayList _hidl_out_descriptors = interfaceChain(); + _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS); + _hidl_reply.writeStringVector(_hidl_out_descriptors); + _hidl_reply.send(); + break; + } + + case 256131655 /* debug */: + { + _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + NativeHandle fd = _hidl_request.readNativeHandle(); + java.util.ArrayList options = _hidl_request.readStringVector(); + debug(fd, options); + _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS); + _hidl_reply.send(); + break; + } + + case 256136003 /* interfaceDescriptor */: + { + _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + String _hidl_out_descriptor = interfaceDescriptor(); + _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS); + _hidl_reply.writeString(_hidl_out_descriptor); + _hidl_reply.send(); + break; + } + + case 256398152 /* getHashChain */: + { + _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + java.util.ArrayList _hidl_out_hashchain = getHashChain(); + _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS); + { + HwBlob _hidl_blob = new HwBlob(16 /* size */); + { + int _hidl_vec_size = _hidl_out_hashchain.size(); + _hidl_blob.putInt32(0 /* offset */ + 8 /* offsetof(hidl_vec, mSize) */, _hidl_vec_size); + _hidl_blob.putBool(0 /* offset */ + 12 /* offsetof(hidl_vec, mOwnsBuffer) */, false); + HwBlob childBlob = new HwBlob((int)(_hidl_vec_size * 32)); + for (int _hidl_index_0 = 0; _hidl_index_0 < _hidl_vec_size; ++_hidl_index_0) { + { + long _hidl_array_offset_1 = _hidl_index_0 * 32; + byte[] _hidl_array_item_1 = (byte[/* 32 */]) _hidl_out_hashchain.get(_hidl_index_0); + + if (_hidl_array_item_1 == null || _hidl_array_item_1.length != 32) { + throw new IllegalArgumentException("Array element is not of the expected length"); + } + + childBlob.putInt8Array(_hidl_array_offset_1, _hidl_array_item_1); + _hidl_array_offset_1 += 32 * 1; + } + } + _hidl_blob.putBlob(0 /* offset */ + 0 /* offsetof(hidl_vec, mBuffer) */, childBlob); + } + _hidl_reply.writeBuffer(_hidl_blob); + } + _hidl_reply.send(); + break; + } + + case 256462420 /* setHALInstrumentation */: + { + _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + setHALInstrumentation(); + break; + } + + case 256660548 /* linkToDeath */: + { + break; + } + + case 256921159 /* ping */: + { + _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + ping(); + _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS); + _hidl_reply.send(); + break; + } + + case 257049926 /* getDebugInfo */: + { + _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + android.internal.hidl.base.V1_0.DebugInfo _hidl_out_info = getDebugInfo(); + _hidl_reply.writeStatus(HwParcel.STATUS_SUCCESS); + ((android.internal.hidl.base.V1_0.DebugInfo) _hidl_out_info).writeToParcel(_hidl_reply); + _hidl_reply.send(); + break; + } + + case 257120595 /* notifySyspropsChanged */: + { + _hidl_request.enforceInterface(android.internal.hidl.base.V1_0.IBase.kInterfaceName); + + notifySyspropsChanged(); + break; + } + + case 257250372 /* unlinkToDeath */: + { + break; + } + + } + } + } +} diff --git a/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/HciHal.java b/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/HciHal.java new file mode 100644 index 00000000..bac35ea8 --- /dev/null +++ b/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/HciHal.java @@ -0,0 +1,260 @@ +package com.github.google.bumble.remotehci; + +import android.hardware.bluetooth.V1_0.Status; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Log; + +import java.util.ArrayList; +import java.util.NoSuchElementException; + +public interface HciHal { + public enum Status { + SUCCESS("SUCCESS"), + ALREADY_INITIALIZED("ALREADY_INITIALIZED"), + UNABLE_TO_OPEN_INTERFACE("UNABLE_TO_OPEN_INTERFACE"), + INITIALIZATION_ERROR("INITIALIZATION_ERROR"), + TRANSPORT_ERROR("TRANSPORT_ERROR"), + UNKNOWN("UNKNOWN"); + + public final String label; + + private Status(String label) { + this.label = label; + } + } + static final String TAG = "HciHal"; + public static HciHal create(HciHalCallback hciCallbacks) { + // First try HIDL + HciHal hciHal = HciHidlHal.create(hciCallbacks); + if (hciHal != null) { + Log.d(TAG, "Found HIDL HAL"); + return hciHal; + } + + // Then try AIDL + hciHal = HciAidlHal.create(hciCallbacks); + if (hciHal != null) { + Log.d(TAG, "Found AIDL HAL"); + return hciHal; + } + + Log.d(TAG, "No HAL found"); + return null; + } + + public Status initialize() throws RemoteException, InterruptedException; + public void sendPacket(HciPacket.Type type, byte[] packet); +} + +class HciHidlHal extends android.hardware.bluetooth.V1_0.IBluetoothHciCallbacks.Stub implements HciHal { + private static final String TAG = "HciHidlHal"; + private final android.hardware.bluetooth.V1_0.IBluetoothHci mHciService; + private final HciHalCallback mHciCallbacks; + private int mInitializationStatus = -1; + + + public static HciHidlHal create(HciHalCallback hciCallbacks) { + // Get the HAL service. + android.hardware.bluetooth.V1_0.IBluetoothHci hciService; + try { + hciService = android.hardware.bluetooth.V1_0.IBluetoothHci.getService(true); + } catch (NoSuchElementException e) { + Log.d(TAG, "HIDL HAL V1.0 not found"); + return null; + } catch (android.os.RemoteException e) { + Log.w(TAG, "Exception from getService: " + e); + return null; + } + Log.d(TAG, "Found HIDL HAL V1.0"); + return new HciHidlHal(hciService, hciCallbacks); + } + + private HciHidlHal(android.hardware.bluetooth.V1_0.IBluetoothHci hciService, HciHalCallback hciCallbacks) { + mHciService = hciService; + mHciCallbacks = hciCallbacks; + } + + public Status initialize() throws RemoteException, InterruptedException { + // Trigger the initialization. + mHciService.initialize(this); + + // Wait for the initialization to complete. + Log.d(TAG, "Waiting for initialization status..."); + synchronized (this) { + while (mInitializationStatus == -1) { + wait(); + } + } + + // Map the status code. + switch (mInitializationStatus) { + case android.hardware.bluetooth.V1_0.Status.SUCCESS: + return Status.SUCCESS; + + case android.hardware.bluetooth.V1_0.Status.TRANSPORT_ERROR: + return Status.TRANSPORT_ERROR; + + case android.hardware.bluetooth.V1_0.Status.INITIALIZATION_ERROR: + return Status.INITIALIZATION_ERROR; + + default: + return Status.UNKNOWN; + } + } + + @Override + public void sendPacket(HciPacket.Type type, byte[] packet) { + ArrayList data = HciPacket.byteArrayToList(packet); + + try { + switch (type) { + case COMMAND: + mHciService.sendHciCommand(data); + break; + + case ACL_DATA: + mHciService.sendAclData(data); + break; + + case SCO_DATA: + mHciService.sendScoData(data); + break; + } + } catch (RemoteException error) { + Log.w(TAG, "failed to forward packet: " + error); + } + } + + @Override + public synchronized void initializationComplete(int status) throws RemoteException { + mInitializationStatus = status; + notifyAll(); + } + + @Override + public void hciEventReceived(ArrayList event) throws RemoteException { + byte[] packet = HciPacket.listToByteArray(event); + mHciCallbacks.onPacket(HciPacket.Type.EVENT, packet); + } + + @Override + public void aclDataReceived(ArrayList data) throws RemoteException { + byte[] packet = HciPacket.listToByteArray(data); + mHciCallbacks.onPacket(HciPacket.Type.ACL_DATA, packet); + } + + @Override + public void scoDataReceived(ArrayList data) throws RemoteException { + byte[] packet = HciPacket.listToByteArray(data); + mHciCallbacks.onPacket(HciPacket.Type.SCO_DATA, packet); + } +} + +class HciAidlHal extends android.hardware.bluetooth.IBluetoothHciCallbacks.Stub implements HciHal { + private static final String TAG = "HciAidlHal"; + private final android.hardware.bluetooth.IBluetoothHci mHciService; + private final HciHalCallback mHciCallbacks; + private int mInitializationStatus = android.hardware.bluetooth.Status.SUCCESS; //-1; + + public static HciAidlHal create(HciHalCallback hciCallbacks) { + IBinder binder = ServiceManager.getService("android.hardware.bluetooth.IBluetoothHci/default"); + if (binder == null) { + Log.d(TAG, "AIDL HAL not found"); + return null; + } + android.hardware.bluetooth.IBluetoothHci hciService = android.hardware.bluetooth.IBluetoothHci.Stub.asInterface(binder); + return new HciAidlHal(hciService, hciCallbacks); + } + + private HciAidlHal(android.hardware.bluetooth.IBluetoothHci hciService, HciHalCallback hciCallbacks) { + super(); + mHciService = hciService; + mHciCallbacks = hciCallbacks; + } + + public Status initialize() throws RemoteException, InterruptedException { + // Trigger the initialization. + mHciService.initialize(this); + + // Wait for the initialization to complete. + Log.d(TAG, "Waiting for initialization status..."); + synchronized (this) { + while (mInitializationStatus == -1) { + wait(); + } + } + + // Map the status code. + switch (mInitializationStatus) { + case android.hardware.bluetooth.Status.SUCCESS: + return Status.SUCCESS; + + case android.hardware.bluetooth.Status.ALREADY_INITIALIZED: + return Status.ALREADY_INITIALIZED; + + case android.hardware.bluetooth.Status.UNABLE_TO_OPEN_INTERFACE: + return Status.UNABLE_TO_OPEN_INTERFACE; + + case android.hardware.bluetooth.Status.HARDWARE_INITIALIZATION_ERROR: + return Status.INITIALIZATION_ERROR; + + default: + return Status.UNKNOWN; + } + } + + // HciHal methods. + @Override + public void sendPacket(HciPacket.Type type, byte[] packet) { + try { + switch (type) { + case COMMAND: + mHciService.sendHciCommand(packet); + break; + + case ACL_DATA: + mHciService.sendAclData(packet); + break; + + case SCO_DATA: + mHciService.sendScoData(packet); + break; + + case ISO_DATA: + mHciService.sendIsoData(packet); + break; + } + } catch (RemoteException error) { + Log.w(TAG, "failed to forward packet: " + error); + } + } + + // IBluetoothHciCallbacks methods. + @Override + public /* synchronized */ void initializationComplete(int status) throws RemoteException { + mInitializationStatus = status; + notifyAll(); + } + + @Override + public void hciEventReceived(byte[] event) throws RemoteException { + mHciCallbacks.onPacket(HciPacket.Type.EVENT, event); + } + + @Override + public void aclDataReceived(byte[] data) throws RemoteException { + mHciCallbacks.onPacket(HciPacket.Type.ACL_DATA, data); + } + + @Override + public void scoDataReceived(byte[] data) throws RemoteException { + mHciCallbacks.onPacket(HciPacket.Type.SCO_DATA, data); + } + + @Override + public void isoDataReceived(byte[] data) throws RemoteException { + mHciCallbacks.onPacket(HciPacket.Type.ISO_DATA, data); + } +} \ No newline at end of file diff --git a/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/HciHalCallback.java b/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/HciHalCallback.java new file mode 100644 index 00000000..5878fc16 --- /dev/null +++ b/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/HciHalCallback.java @@ -0,0 +1,5 @@ +package com.github.google.bumble.remotehci; + +public interface HciHalCallback { + public void onPacket(HciPacket.Type type, byte[] packet); +} diff --git a/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/HciPacket.java b/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/HciPacket.java new file mode 100644 index 00000000..2f3162b3 --- /dev/null +++ b/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/HciPacket.java @@ -0,0 +1,69 @@ +package com.github.google.bumble.remotehci; + +import java.util.ArrayList; + +public class HciPacket { + public enum Type { + COMMAND((byte) 1), + ACL_DATA((byte) 2), + SCO_DATA((byte) 3), + EVENT((byte) 4), + ISO_DATA((byte)5); + + final byte value; + final int lengthSize; + final int lengthOffset; + + Type(byte value) throws IllegalArgumentException { + switch (value) { + case 1: + case 3: + lengthSize = 1; + lengthOffset = 2; + break; + + case 2: + case 5: + lengthSize = 2; + lengthOffset = 2; + break; + + case 4: + lengthSize = 1; + lengthOffset = 1; + break; + + default: + throw new IllegalArgumentException(); + + } + this.value = value; + } + + static Type fromValue(byte value) { + for (Type type : values()) { + if (type.value == value) { + return type; + } + } + return null; + } + } + + public static ArrayList byteArrayToList(byte[] byteArray) { + ArrayList list = new ArrayList<>(); + list.ensureCapacity(byteArray.length); + for (byte x : byteArray) { + list.add(x); + } + return list; + } + + public static byte[] listToByteArray(ArrayList byteList) { + byte[] byteArray = new byte[byteList.size()]; + for (int i = 0; i < byteList.size(); i++) { + byteArray[i] = byteList.get(i); + } + return byteArray; + } +} diff --git a/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/HciParser.java b/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/HciParser.java new file mode 100644 index 00000000..0a3513ca --- /dev/null +++ b/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/HciParser.java @@ -0,0 +1,82 @@ +package com.github.google.bumble.remotehci; + +import static java.lang.Integer.min; + +import java.io.ByteArrayOutputStream; +import java.nio.ByteBuffer; + + +class HciParser { + enum State { + NEED_TYPE, + NEED_LENGTH, + NEED_BODY + } + + interface Sink { + void onPacket(HciPacket.Type type, byte[] packet); + } + + static class InvalidFormatException extends RuntimeException { + } + + Sink sink; + State state; + int bytesNeeded; + ByteArrayOutputStream packet = new ByteArrayOutputStream(); + HciPacket.Type packetType; + + HciParser(Sink sink) { + this.sink = sink; + reset(); + } + + void feedData(byte[] data, int dataSize) { + int dataOffset = 0; + int dataLeft = dataSize; + + while (dataLeft > 0 && bytesNeeded > 0) { + int consumed = min(dataLeft, bytesNeeded); + if (state != State.NEED_TYPE) { + packet.write(data, dataOffset, consumed); + } + bytesNeeded -= consumed; + + if (bytesNeeded == 0) { + if (state == State.NEED_TYPE) { + packetType = HciPacket.Type.fromValue(data[dataOffset]); + if (packetType == null) { + throw new InvalidFormatException(); + } + bytesNeeded = packetType.lengthOffset + packetType.lengthSize; + state = State.NEED_LENGTH; + } else if (state == State.NEED_LENGTH) { + ByteBuffer lengthBuffer = ByteBuffer.wrap(packet.toByteArray()); + bytesNeeded = packetType.lengthSize == 1 ? + lengthBuffer.get(packetType.lengthOffset) & 0xFF : + lengthBuffer.getShort(packetType.lengthOffset) & 0xFFFF; + state = State.NEED_BODY; + } + + // Emit a packet if one is complete. + if (state == State.NEED_BODY && bytesNeeded == 0) { + if (sink != null) { + sink.onPacket(packetType, packet.toByteArray()); + } + + reset(); + } + } + + dataOffset += consumed; + dataLeft -= consumed; + } + } + + void reset() { + state = State.NEED_TYPE; + bytesNeeded = 1; + packet.reset(); + packetType = null; + } +} \ No newline at end of file diff --git a/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/HciProxy.java b/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/HciProxy.java new file mode 100644 index 00000000..7ded1578 --- /dev/null +++ b/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/HciProxy.java @@ -0,0 +1,128 @@ +package com.github.google.bumble.remotehci; + +import android.os.RemoteException; +import android.util.Log; + +import java.io.IOException; + +public class HciProxy { + private static final String TAG = "HciProxy"; + private final HciServer mServer; + private final Listener mListener; + private int mCommandPacketsReceived; + private int mAclPacketsReceived; + private int mScoPacketsReceived; + private int mEventPacketsSent; + private int mAclPacketsSent; + private int mScoPacketsSent; + + HciProxy(int port, Listener listener) throws HalException { + this.mListener = listener; + + // Instantiate a HAL to communicate with the hardware. + HciHal hciHal = HciHal.create(new HciHalCallback() { + @Override + public void onPacket(HciPacket.Type type, byte[] packet) { + mServer.sendPacket(type, packet); + } + }); + if (hciHal == null) { + String message = "Could not instantiate a HAL instance"; + Log.w(TAG, message); + throw new HalException(message); + } + + // Initialize the HAL. + HciHal.Status status = null; + try { + status = hciHal.initialize(); + } catch (RemoteException | InterruptedException e) { + throw new HalException("Exception while initializing"); + } + if (status != HciHal.Status.SUCCESS) { + String message = "HAL initialization failed: " + status.label; + Log.w(TAG, message); + throw new HalException(message); + } + + // Create a server to accept clients. + mServer = new HciServer(port, new HciServer.Listener() { + @Override + public void onHostConnectionState(boolean connected) { + mListener.onHostConnectionState(connected); + if (connected) { + mCommandPacketsReceived = 0; + mAclPacketsReceived = 0; + mScoPacketsReceived = 0; + mEventPacketsSent = 0; + mAclPacketsSent = 0; + mScoPacketsSent = 0; + updateHciPacketCount(); + } + } + + @Override + public void onMessage(String message) { + listener.onMessage(message); + } + + @Override + public void onPacket(HciPacket.Type type, byte[] packet) { + Log.d(TAG, String.format("onPacket: type=%s, size=%d", type, packet.length)); + hciHal.sendPacket(type, packet); + + switch (type) { + case COMMAND: + mCommandPacketsReceived += 1; + break; + + case ACL_DATA: + mAclPacketsReceived += 1; + break; + + case SCO_DATA: + mScoPacketsReceived += 1; + break; + } + updateHciPacketCount(); + } + }); + } + + public void run() throws IOException { + mServer.run(); + } + + private void updateHciPacketCount() { + mListener.onHciPacketCountChange( + mCommandPacketsReceived, + mAclPacketsReceived, + mScoPacketsReceived, + mEventPacketsSent, + mAclPacketsSent, + mScoPacketsSent + ); + } + + public interface Listener { + void onHostConnectionState(boolean connected); + + void onHciPacketCountChange( + int commandPacketsReceived, + int aclPacketsReceived, + int scoPacketsReceived, + int eventPacketsSent, + int aclPacketsSent, + int scoPacketsSent + ); + + void onMessage(String message); + } + + public static class HalException extends RuntimeException { + public final String message; + public HalException(String message) { + this.message = message; + } + } +} diff --git a/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/HciServer.java b/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/HciServer.java new file mode 100644 index 00000000..5c71ec04 --- /dev/null +++ b/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/HciServer.java @@ -0,0 +1,93 @@ +package com.github.google.bumble.remotehci; + +import android.util.Log; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.ServerSocket; +import java.net.Socket; + + +public class HciServer { + private static final String TAG = "HciServer"; + private static final int BUFFER_SIZE = 1024; + private final int mPort; + private final Listener mListener; + private OutputStream mOutputStream; + + public interface Listener extends HciParser.Sink { + void onHostConnectionState(boolean connected); + void onMessage(String message); + } + + HciServer(int port, Listener listener) { + this.mPort = port; + this.mListener = listener; + } + + public void run() throws IOException { + for (;;) { + try { + loop(); + } catch (IOException error) { + mListener.onMessage("Cannot listen on port " + mPort); + return; + } + } + } + + private void loop() throws IOException { + mListener.onHostConnectionState(false); + mListener.onMessage("Waiting for connection on port " + mPort); + try ( + ServerSocket serverSocket = new ServerSocket(mPort); + Socket clientSocket = serverSocket.accept() + ) { + mListener.onHostConnectionState(true); + mListener.onMessage("Connected"); + HciParser parser = new HciParser(mListener); + InputStream inputStream = clientSocket.getInputStream(); + synchronized (this) { + mOutputStream = clientSocket.getOutputStream(); + } + byte[] buffer = new byte[BUFFER_SIZE]; + + try { + for (;;) { + int bytesRead = inputStream.read(buffer); + if (bytesRead < 0) { + Log.d(TAG, "end of stream"); + break; + } + parser.feedData(buffer, bytesRead); + } + } catch (IOException error) { + Log.d(TAG, "exception in read loop: " + error); + } + } finally { + synchronized (this) { + mOutputStream = null; + } + } + } + + public void sendPacket(HciPacket.Type type, byte[] packet) { + // Create a combined data buffer so we can write it out in a single call. + byte[] data = new byte[packet.length + 1]; + data[0] = type.value; + System.arraycopy(packet, 0, data, 1, packet.length); + + synchronized (this) { + if (mOutputStream != null) { + try { + mOutputStream.write(data); + } catch (IOException error) { + Log.w(TAG, "failed to write packet: " + error); + } + } else { + Log.d(TAG, "no client, dropping packet"); + } + } + } +} diff --git a/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/MainActivity.kt b/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/MainActivity.kt new file mode 100644 index 00000000..eafab286 --- /dev/null +++ b/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/MainActivity.kt @@ -0,0 +1,225 @@ +package com.github.google.bumble.remotehci + +import android.content.Context +import android.content.SharedPreferences +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.Button +import androidx.compose.material3.Divider +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.ViewModel +import com.github.google.bumble.remotehci.HciProxy.HalException +import com.github.google.bumble.remotehci.ui.theme.RemoteHCITheme +import java.io.IOException +import java.util.logging.Logger +import kotlin.concurrent.thread + +const val DEFAULT_TCP_PORT = 9993 +const val TCP_PORT_PREF_KEY = "tcp_port" + +class AppViewModel : ViewModel(), HciProxy.Listener { + private var preferences: SharedPreferences? = null + var tcpPort by mutableStateOf(DEFAULT_TCP_PORT) + var canStart by mutableStateOf(true) + var message by mutableStateOf("") + var hostConnected by mutableStateOf(false) + var hciCommandPacketsReceived by mutableStateOf(0) + var hciAclPacketsReceived by mutableStateOf(0) + var hciScoPacketsReceived by mutableStateOf(0) + var hciEventPacketsSent by mutableStateOf(0) + var hciAclPacketsSent by mutableStateOf(0) + var hciScoPacketsSent by mutableStateOf(0) + + fun loadPreferences(preferences: SharedPreferences) { + this.preferences = preferences + val savedTcpPortString = preferences.getString(TCP_PORT_PREF_KEY, null) + if (savedTcpPortString != null) { + val savedTcpPortInt = savedTcpPortString.toIntOrNull() + if (savedTcpPortInt != null) { + tcpPort = savedTcpPortInt + } + } + } + + fun updateTcpPort(tcpPort: Int) { + this.tcpPort = tcpPort + + // Save the port to the preferences + with (preferences!!.edit()) { + putString(TCP_PORT_PREF_KEY, tcpPort.toString()) + apply() + } + } + + override fun onHostConnectionState(connected: Boolean) { + hostConnected = connected + } + + override fun onHciPacketCountChange( + commandPacketsReceived: Int, + aclPacketsReceived: Int, + scoPacketsReceived: Int, + eventPacketsSent: Int, + aclPacketsSent: Int, + scoPacketsSent: Int + ) { + hciCommandPacketsReceived = commandPacketsReceived + hciAclPacketsReceived = aclPacketsReceived + hciScoPacketsReceived = scoPacketsReceived + hciEventPacketsSent = eventPacketsSent + hciAclPacketsSent = aclPacketsSent + hciScoPacketsSent = scoPacketsSent + + } + + override fun onMessage(message: String) { + this.message = message + } +} + +class MainActivity : ComponentActivity() { + private val log = Logger.getLogger(MainActivity::class.java.name) + private val appViewModel = AppViewModel() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + appViewModel.loadPreferences(getPreferences(Context.MODE_PRIVATE)) + + setContent { + MainView(appViewModel, ::startProxy) + } + + run() + } + + private fun run() { + } + + private fun startProxy() { + // Run the proxy in a thread. + appViewModel.message = "" + thread { + log.info("HCI Proxy thread starting") + appViewModel.canStart = false + try { + val hciProxy = HciProxy(appViewModel.tcpPort, appViewModel) + hciProxy.run() + } catch (error: IOException) { + log.warning("Exception while running HCI Server: $error") + } catch (error: HalException) { + log.warning("HAL exception: ${error.message}") + appViewModel.message = "Cannot bind to HAL (${error.message}). You may need to use the command 'setenforce 0' in a root adb shell." + } + log.info("HCI Proxy thread ended") + appViewModel.canStart = true + } + } +} + +@Composable +fun ActionButton(text: String, onClick: () -> Unit, enabled: Boolean) { + Button(onClick = onClick, enabled = enabled) { + Text(text = text) + } +} + +@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class) +@Composable +fun MainView(appViewModel: AppViewModel, startProxy: () -> Unit) { + RemoteHCITheme { + // A surface container using the 'background' color from the theme + Surface( + modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background + ) { + Column(modifier = Modifier.padding(horizontal = 16.dp)) { + Text( + text = "Bumble Remote HCI", + fontSize = 24.sp, + fontWeight = FontWeight.Bold, + textAlign = TextAlign.Center + ) + Divider() + Text( + text = appViewModel.message + ) + Divider() + val keyboardController = LocalSoftwareKeyboardController.current + TextField( + label = { + Text(text = "TCP Port") + }, + value = appViewModel.tcpPort.toString(), + modifier = Modifier.fillMaxWidth(), + keyboardOptions = KeyboardOptions.Default.copy(keyboardType = KeyboardType.Number, imeAction = ImeAction.Done), + onValueChange = { + if (it.isNotEmpty()) { + val tcpPort = it.toIntOrNull() + if (tcpPort != null) { + appViewModel.updateTcpPort(tcpPort) + } + } + }, + keyboardActions = KeyboardActions( + onDone = {keyboardController?.hide()} + ) + ) + Divider() + val connectState = if (appViewModel.hostConnected) "CONNECTED" else "DISCONNECTED" + Text( + text = "HOST: $connectState", + modifier = Modifier.background(color = if (appViewModel.hostConnected) Color.Green else Color.Red), + color = Color.Black + ) + Divider() + Text( + text = "Command Packets Received: ${appViewModel.hciCommandPacketsReceived}" + ) + Text( + text = "ACL Packets Received: ${appViewModel.hciAclPacketsReceived}" + ) + Text( + text = "SCO Packets Received: ${appViewModel.hciScoPacketsReceived}" + ) + Text( + text = "Event Packets Sent: ${appViewModel.hciEventPacketsSent}" + ) + Text( + text = "ACL Packets Sent: ${appViewModel.hciAclPacketsSent}" + ) + Text( + text = "SCO Packets Sent: ${appViewModel.hciScoPacketsSent}" + ) + Divider() + ActionButton( + text = "Start", onClick = startProxy, enabled = appViewModel.canStart + ) + } + } + } +} \ No newline at end of file diff --git a/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/ui/theme/Color.kt b/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/ui/theme/Color.kt new file mode 100644 index 00000000..420958d5 --- /dev/null +++ b/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/ui/theme/Color.kt @@ -0,0 +1,11 @@ +package com.github.google.bumble.remotehci.ui.theme + +import androidx.compose.ui.graphics.Color + +val Purple80 = Color(0xFFD0BCFF) +val PurpleGrey80 = Color(0xFFCCC2DC) +val Pink80 = Color(0xFFEFB8C8) + +val Purple40 = Color(0xFF6650a4) +val PurpleGrey40 = Color(0xFF625b71) +val Pink40 = Color(0xFF7D5260) \ No newline at end of file diff --git a/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/ui/theme/Theme.kt b/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/ui/theme/Theme.kt new file mode 100644 index 00000000..8d707c98 --- /dev/null +++ b/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/ui/theme/Theme.kt @@ -0,0 +1,70 @@ +package com.github.google.bumble.remotehci.ui.theme + +import android.app.Activity +import android.os.Build +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalView +import androidx.core.view.WindowCompat + +private val DarkColorScheme = darkColorScheme( + primary = Purple80, + secondary = PurpleGrey80, + tertiary = Pink80 +) + +private val LightColorScheme = lightColorScheme( + primary = Purple40, + secondary = PurpleGrey40, + tertiary = Pink40 + + /* Other default colors to override + background = Color(0xFFFFFBFE), + surface = Color(0xFFFFFBFE), + onPrimary = Color.White, + onSecondary = Color.White, + onTertiary = Color.White, + onBackground = Color(0xFF1C1B1F), + onSurface = Color(0xFF1C1B1F), + */ +) + +@Composable +fun RemoteHCITheme( + darkTheme: Boolean = isSystemInDarkTheme(), + // Dynamic color is available on Android 12+ + dynamicColor: Boolean = true, + content: @Composable () -> Unit +) { + val colorScheme = when { + dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + val context = LocalContext.current + if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) + } + + darkTheme -> DarkColorScheme + else -> LightColorScheme + } + val view = LocalView.current + if (!view.isInEditMode) { + SideEffect { + val window = (view.context as Activity).window + window.statusBarColor = colorScheme.primary.toArgb() + WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme + } + } + + MaterialTheme( + colorScheme = colorScheme, + typography = Typography, + content = content + ) +} \ No newline at end of file diff --git a/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/ui/theme/Type.kt b/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/ui/theme/Type.kt new file mode 100644 index 00000000..5017af23 --- /dev/null +++ b/extras/android/RemoteHCI/app/src/main/java/com/github/google/bumble/remotehci/ui/theme/Type.kt @@ -0,0 +1,34 @@ +package com.github.google.bumble.remotehci.ui.theme + +import androidx.compose.material3.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Set of Material typography styles to start with +val Typography = Typography( + bodyLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp + ) + /* Other default text styles to override + titleLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 22.sp, + lineHeight = 28.sp, + letterSpacing = 0.sp + ), + labelSmall = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Medium, + fontSize = 11.sp, + lineHeight = 16.sp, + letterSpacing = 0.5.sp + ) + */ +) \ No newline at end of file diff --git a/extras/android/RemoteHCI/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/extras/android/RemoteHCI/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 00000000..036d09bc --- /dev/null +++ b/extras/android/RemoteHCI/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/extras/android/RemoteHCI/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/extras/android/RemoteHCI/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 00000000..036d09bc --- /dev/null +++ b/extras/android/RemoteHCI/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/extras/android/RemoteHCI/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/extras/android/RemoteHCI/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..f01f61e6f21b63502d6c0840c08b8cc61c37a894 GIT binary patch literal 2600 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 literal 0 HcmV?d00001 diff --git a/extras/android/RemoteHCI/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/extras/android/RemoteHCI/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..1d9c6ae185c8a5ae361ed90f21290c3075c85e70 GIT binary patch literal 4408 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 W4!?yXGSP&+eB40w$g#p6m=gf07$7PD literal 0 HcmV?d00001 diff --git a/extras/android/RemoteHCI/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/extras/android/RemoteHCI/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000000000000000000000000000000000000..9764352d751b387e0f9feb55cd000b6672f76c11 GIT binary patch literal 2408 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 literal 0 HcmV?d00001 diff --git a/extras/android/RemoteHCI/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/extras/android/RemoteHCI/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..c607bc0ad9423c6a472bd0c5b2ba5054eee28319 GIT binary patch literal 3752 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 SO&zG~%DCkO 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 literal 0 HcmV?d00001 diff --git a/extras/android/RemoteHCI/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/extras/android/RemoteHCI/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..54c5d2f3048f5f7452e3627c4d8bbdfd1571491c GIT binary patch literal 6138 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 M48OskWpV9l02_R@*Z=?k literal 0 HcmV?d00001 diff --git a/extras/android/RemoteHCI/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/extras/android/RemoteHCI/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..9afe5656fd7bd3ce41ca8d5da9b8791935acd867 GIT binary patch literal 5676 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 literal 0 HcmV?d00001 diff --git a/extras/android/RemoteHCI/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/extras/android/RemoteHCI/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000000000000000000000000000000000000..434da3f57c321ba3f5131d93f9ece33b0c79b21d GIT binary patch literal 9474 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 E2WQSZ0RR91 literal 0 HcmV?d00001 diff --git a/extras/android/RemoteHCI/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/extras/android/RemoteHCI/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..b48797d8ea6b3153aaccf99cc04bf0959c3eefdf GIT binary patch literal 13260 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 KMTlCDlVf7Nvl&PL literal 0 HcmV?d00001 diff --git a/extras/android/RemoteHCI/app/src/main/res/values/colors.xml b/extras/android/RemoteHCI/app/src/main/res/values/colors.xml new file mode 100644 index 00000000..f8c6127d --- /dev/null +++ b/extras/android/RemoteHCI/app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/extras/android/RemoteHCI/app/src/main/res/values/ic_launcher_background.xml b/extras/android/RemoteHCI/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 00000000..c5d5899f --- /dev/null +++ b/extras/android/RemoteHCI/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #FFFFFF + \ No newline at end of file diff --git a/extras/android/RemoteHCI/app/src/main/res/values/strings.xml b/extras/android/RemoteHCI/app/src/main/res/values/strings.xml new file mode 100644 index 00000000..af779413 --- /dev/null +++ b/extras/android/RemoteHCI/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + Remote HCI + \ No newline at end of file diff --git a/extras/android/RemoteHCI/app/src/main/res/values/themes.xml b/extras/android/RemoteHCI/app/src/main/res/values/themes.xml new file mode 100644 index 00000000..cd813bf3 --- /dev/null +++ b/extras/android/RemoteHCI/app/src/main/res/values/themes.xml @@ -0,0 +1,5 @@ + + + +