Add BluetoothX core's BluetoothGattDescriptor

Test: none
Bug: 236227494
Change-Id: I479b48ffdb9aeee8b649e3c0768fd3331fce6c52
diff --git a/bluetooth/bluetooth/build.gradle b/bluetooth/bluetooth/build.gradle
index 9bf971b..a83aefa 100644
--- a/bluetooth/bluetooth/build.gradle
+++ b/bluetooth/bluetooth/build.gradle
@@ -24,6 +24,7 @@
 
 dependencies {
     implementation(libs.kotlinStdlib)
+    implementation 'androidx.annotation:annotation:1.4.0'
 }
 
 androidx {
@@ -36,4 +37,7 @@
 
 android {
     namespace "androidx.bluetooth"
+    defaultConfig {
+        minSdkVersion 21
+    }
 }
diff --git a/bluetooth/bluetooth/src/main/AndroidManifest.xml b/bluetooth/bluetooth/src/main/AndroidManifest.xml
index 6abff90..24c3903 100644
--- a/bluetooth/bluetooth/src/main/AndroidManifest.xml
+++ b/bluetooth/bluetooth/src/main/AndroidManifest.xml
@@ -13,4 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<manifest />
\ No newline at end of file
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
+</manifest>
\ No newline at end of file
diff --git a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothGattDescriptor.kt b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothGattDescriptor.kt
new file mode 100644
index 0000000..2aaf3a5
--- /dev/null
+++ b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/BluetoothGattDescriptor.kt
@@ -0,0 +1,185 @@
+/*
+ * 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.
+ */
+
+package androidx.bluetooth
+
+import android.os.Build
+import android.os.Bundle
+import androidx.annotation.RequiresApi
+import androidx.bluetooth.utils.Bundleable
+
+import java.util.UUID
+
+/**
+ * @hide
+ */
+class BluetoothGattDescriptor internal constructor(
+    descriptor: android.bluetooth.BluetoothGattDescriptor
+) : Bundleable {
+    private val impl: GattDescriptorImpl =
+        if (Build.VERSION.SDK_INT >= 24) {
+            GattDescriptorImplApi24(descriptor)
+        } else {
+            GattDescriptorImplApi21(descriptor)
+        }
+    internal val fwkDescriptor: android.bluetooth.BluetoothGattDescriptor
+        get() = impl.fwkDescriptor
+    val permissions: Int
+        get() = impl.permissions
+    val uuid: UUID
+        get() = impl.uuid
+    val characteristic: android.bluetooth.BluetoothGattCharacteristic?
+        get() = impl.characteristic
+
+    constructor(uuid: UUID, permissions: Int) : this(
+        android.bluetooth.BluetoothGattDescriptor(
+            uuid,
+            permissions
+        )
+    )
+
+    companion object {
+        const val PERMISSION_READ = android.bluetooth.BluetoothGattDescriptor.PERMISSION_READ
+        const val PERMISSION_READ_ENCRYPTED =
+            android.bluetooth.BluetoothGattDescriptor.PERMISSION_READ_ENCRYPTED
+        const val PERMISSION_READ_ENCRYPTED_MITM =
+            android.bluetooth.BluetoothGattDescriptor.PERMISSION_READ_ENCRYPTED_MITM
+        const val PERMISSION_WRITE = android.bluetooth.BluetoothGattDescriptor.PERMISSION_WRITE
+        const val PERMISSION_WRITE_ENCRYPTED =
+            android.bluetooth.BluetoothGattDescriptor.PERMISSION_WRITE_ENCRYPTED
+        const val PERMISSION_WRITE_ENCRYPTED_MITM =
+            android.bluetooth.BluetoothGattDescriptor.PERMISSION_WRITE_ENCRYPTED_MITM
+        const val PERMISSION_WRITE_SIGNED =
+            android.bluetooth.BluetoothGattDescriptor.PERMISSION_WRITE_SIGNED
+        const val PERMISSION_WRITE_SIGNED_MITM =
+            android.bluetooth.BluetoothGattDescriptor.PERMISSION_WRITE_SIGNED_MITM
+
+        val ENABLE_NOTIFICATION_VALUE = byteArrayOf(0x01, 0x00)
+        val ENABLE_INDICATION_VALUE = byteArrayOf(0x02, 0x00)
+        val DISABLE_NOTIFICATION_VALUE = byteArrayOf(0x00, 0x00)
+
+        internal fun keyForField(field: Int): String? {
+            return field.toString(Character.MAX_RADIX)
+        }
+
+        val CREATOR: Bundleable.Creator<BluetoothGattDescriptor> =
+            if (Build.VERSION.SDK_INT >= 24) GattDescriptorImplApi24.CREATOR
+            else GattDescriptorImplApi21.CREATOR
+    }
+
+    override fun toBundle(): Bundle {
+        return impl.toBundle()
+    }
+
+    private interface GattDescriptorImpl {
+        val fwkDescriptor: android.bluetooth.BluetoothGattDescriptor
+        val permissions: Int
+        val uuid: UUID
+        val characteristic: android.bluetooth.BluetoothGattCharacteristic?
+        fun toBundle(): Bundle
+    }
+
+    private open class GattDescriptorImplApi21(
+        descriptor: android.bluetooth.BluetoothGattDescriptor
+    ) : GattDescriptorImpl {
+        companion object {
+
+            internal const val FIELD_FWK_DESCRIPTOR_PERMISSIONS = 1
+            internal const val FIELD_FWK_DESCRIPTOR_UUID = 2
+            internal const val FIELD_FWK_DESCRIPTOR_INSTANCE = 3
+
+            val CREATOR: Bundleable.Creator<BluetoothGattDescriptor> =
+                object : Bundleable.Creator<BluetoothGattDescriptor> {
+
+                    @Suppress("DEPRECATION")
+                    override fun fromBundle(bundle: Bundle): BluetoothGattDescriptor {
+                        val permissions =
+                            bundle.getInt(
+                                keyForField(FIELD_FWK_DESCRIPTOR_PERMISSIONS),
+                                -1
+                            )
+                        val uuid = bundle.getString(
+                            keyForField(FIELD_FWK_DESCRIPTOR_UUID),
+                        ) ?: throw IllegalArgumentException("Bundle doesn't include uuid")
+
+                        if (permissions == -1) {
+                            throw IllegalArgumentException("Bundle doesn't include permission")
+                        }
+
+                        val descriptor =
+                            android.bluetooth.BluetoothGattDescriptor(
+                                UUID.fromString(uuid),
+                                permissions
+                            )
+
+                        descriptor.javaClass.getDeclaredField("mInstance").setInt(
+                            descriptor, bundle.getInt(
+                                keyForField(FIELD_FWK_DESCRIPTOR_INSTANCE), 0
+                            )
+                        )
+                        return BluetoothGattDescriptor(descriptor)
+                    }
+                }
+        }
+
+        override val fwkDescriptor: android.bluetooth.BluetoothGattDescriptor = descriptor
+        override val permissions: Int
+            get() = fwkDescriptor.permissions
+        override val uuid: UUID
+            get() = fwkDescriptor.uuid
+        override val characteristic: android.bluetooth.BluetoothGattCharacteristic?
+            get() = fwkDescriptor.characteristic
+
+        override fun toBundle(): Bundle {
+            val bundle = Bundle()
+            bundle.putString(keyForField(FIELD_FWK_DESCRIPTOR_UUID), uuid.toString())
+            bundle.putInt(keyForField(FIELD_FWK_DESCRIPTOR_PERMISSIONS), permissions)
+            val instanceId: Int =
+                fwkDescriptor.javaClass.getDeclaredField("mInstance").getInt(fwkDescriptor)
+            bundle.putInt(keyForField(FIELD_FWK_DESCRIPTOR_INSTANCE), instanceId)
+            return bundle
+        }
+    }
+
+    @RequiresApi(Build.VERSION_CODES.N)
+    private open class GattDescriptorImplApi24(
+        descriptor: android.bluetooth.BluetoothGattDescriptor
+    ) : GattDescriptorImplApi21(descriptor) {
+        companion object {
+            internal const val FIELD_FWK_DESCRIPTOR = 0
+            val CREATOR: Bundleable.Creator<BluetoothGattDescriptor> =
+                object : Bundleable.Creator<BluetoothGattDescriptor> {
+                    @Suppress("DEPRECATION")
+                    override fun fromBundle(bundle: Bundle): BluetoothGattDescriptor {
+                        val fwkDescriptor =
+                            bundle.getParcelable<android.bluetooth.BluetoothGattDescriptor>(
+                                keyForField(FIELD_FWK_DESCRIPTOR)
+                            ) ?: throw IllegalArgumentException("Bundle doesn't contain descriptor")
+                        return BluetoothGattDescriptor(fwkDescriptor)
+                    }
+                }
+        }
+
+        override fun toBundle(): Bundle {
+            val bundle = Bundle()
+            bundle.putParcelable(
+                keyForField(FIELD_FWK_DESCRIPTOR),
+                fwkDescriptor
+            )
+            return bundle
+        }
+    }
+}
\ No newline at end of file
diff --git a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/utils/Bundleable.kt b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/utils/Bundleable.kt
new file mode 100644
index 0000000..73440dd
--- /dev/null
+++ b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/utils/Bundleable.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+package androidx.bluetooth.utils
+
+import android.os.Bundle
+
+/**
+ * @hide
+ */
+interface Bundleable {
+    /** Returns a [Bundle] representing the information stored in this object.  */
+    fun toBundle(): Bundle
+
+    /** Interface for the static `CREATOR` field of [Bundleable] classes.  */
+    interface Creator<T : Bundleable> {
+        /**
+         * Restores a [Bundleable] instance from a [Bundle] produced by [ ][Bundleable.toBundle].
+         *
+         *
+         * It guarantees the compatibility of [Bundle] representations produced by different
+         * versions of [Bundleable.toBundle] by providing best default values for missing
+         * fields. It throws an exception if any essential fields are missing.
+         */
+        fun fromBundle(bundle: Bundle): T
+    }
+}
\ No newline at end of file