Add ScanResult class

Implement a ScanResult class that represents scan result for Bluetooth
LE scan. Also add unit test for the class.

Bug: 278997201
Test: ./gradlew bluetooth:bluetooth:connectedAndroidTest
Relnote: NA
Change-Id: I35e5c906344fbb3a1d37ba884cdfaf6b61447ad4
diff --git a/bluetooth/bluetooth/api/current.txt b/bluetooth/bluetooth/api/current.txt
index d682668..cdcca53 100644
--- a/bluetooth/bluetooth/api/current.txt
+++ b/bluetooth/bluetooth/api/current.txt
@@ -71,5 +71,19 @@
   public static final class ScanFilter.Companion {
   }
 
+  public final class ScanResult {
+    method public androidx.bluetooth.BluetoothDevice getDevice();
+    method public androidx.bluetooth.BluetoothAddress getDeviceAddress();
+    method public byte[]? getManufacturerSpecificData(int manufacturerId);
+    method public byte[]? getServiceData(java.util.UUID serviceUuid);
+    method public java.util.List<java.util.UUID> getServiceUuids();
+    method public long getTimestampNanos();
+    method public boolean isConnectable();
+    property public final androidx.bluetooth.BluetoothDevice device;
+    property public final androidx.bluetooth.BluetoothAddress deviceAddress;
+    property public final java.util.List<java.util.UUID> serviceUuids;
+    property public final long timestampNanos;
+  }
+
 }
 
diff --git a/bluetooth/bluetooth/api/restricted_current.txt b/bluetooth/bluetooth/api/restricted_current.txt
index d682668..cdcca53 100644
--- a/bluetooth/bluetooth/api/restricted_current.txt
+++ b/bluetooth/bluetooth/api/restricted_current.txt
@@ -71,5 +71,19 @@
   public static final class ScanFilter.Companion {
   }
 
+  public final class ScanResult {
+    method public androidx.bluetooth.BluetoothDevice getDevice();
+    method public androidx.bluetooth.BluetoothAddress getDeviceAddress();
+    method public byte[]? getManufacturerSpecificData(int manufacturerId);
+    method public byte[]? getServiceData(java.util.UUID serviceUuid);
+    method public java.util.List<java.util.UUID> getServiceUuids();
+    method public long getTimestampNanos();
+    method public boolean isConnectable();
+    property public final androidx.bluetooth.BluetoothDevice device;
+    property public final androidx.bluetooth.BluetoothAddress deviceAddress;
+    property public final java.util.List<java.util.UUID> serviceUuids;
+    property public final long timestampNanos;
+  }
+
 }
 
diff --git a/bluetooth/bluetooth/src/androidTest/java/androidx/bluetooth/ScanResultTest.kt b/bluetooth/bluetooth/src/androidTest/java/androidx/bluetooth/ScanResultTest.kt
new file mode 100644
index 0000000..7f488d7
--- /dev/null
+++ b/bluetooth/bluetooth/src/androidTest/java/androidx/bluetooth/ScanResultTest.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2023 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.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothManager
+import android.bluetooth.le.ScanResult as FwkScanResult
+import android.content.Context
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.rule.GrantPermissionRule
+import java.util.UUID
+import junit.framework.TestCase.assertEquals
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class ScanResultTest {
+    @Rule
+    @JvmField
+    val permissionRule: GrantPermissionRule = GrantPermissionRule.grant(
+        android.Manifest.permission.BLUETOOTH_CONNECT)
+
+    private val context: Context = ApplicationProvider.getApplicationContext()
+    private val bluetoothManager: BluetoothManager =
+        context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager
+    private val bluetoothAdapter: BluetoothAdapter? = bluetoothManager.adapter
+
+    @Test
+    fun constructorWithFwkInstance() {
+        val address = "00:01:02:03:04:05"
+        val fwkBluetoothDevice = bluetoothAdapter!!.getRemoteDevice(address)
+        val timeStampNanos: Long = 1
+        val serviceUuid = UUID.randomUUID()
+
+        // TODO(kihongs) Find a way to create framework ScanRecord and use in test
+        val fwkScanResult = FwkScanResult(fwkBluetoothDevice, 1, 0, 0, 0, 0, 0, 0, null,
+            timeStampNanos)
+        val scanResult = ScanResult(fwkScanResult)
+
+        assertEquals(scanResult.device.name, BluetoothDevice(fwkBluetoothDevice).name)
+        assertEquals(scanResult.device.bondState, BluetoothDevice(fwkBluetoothDevice).bondState)
+        assertEquals(scanResult.deviceAddress.address, address)
+        assertEquals(scanResult.deviceAddress.addressType, AddressType.ADDRESS_TYPE_RANDOM)
+        assertEquals(scanResult.isConnectable(), true)
+        assertEquals(scanResult.timestampNanos, timeStampNanos)
+        assertEquals(scanResult.getManufacturerSpecificData(1), null)
+        assertEquals(scanResult.serviceUuids, emptyList<UUID>())
+        assertEquals(scanResult.getServiceData(serviceUuid), null)
+    }
+}
\ No newline at end of file
diff --git a/bluetooth/bluetooth/src/main/java/androidx/bluetooth/ScanResult.kt b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/ScanResult.kt
new file mode 100644
index 0000000..ac79d4a
--- /dev/null
+++ b/bluetooth/bluetooth/src/main/java/androidx/bluetooth/ScanResult.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2023 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.bluetooth.le.ScanResult as FwkScanResult
+import android.os.ParcelUuid
+import java.util.UUID
+
+/**
+ * ScanResult for Bluetooth LE scan.
+ *
+ * The ScanResult class is used by Bluetooth LE applications to scan for and discover Bluetooth LE
+ * devices. When a Bluetooth LE application scans for devices, it will receive a list of ScanResult
+ * objects that contain information about the scanned devices. The application can then use this
+ * information to determine which devices it wants to connect to.
+ *
+ * @property device Remote device found
+ * @property deviceAddress Bluetooth address for the remote device found
+ * @property timestampNanos Device timestamp when the result was last seen
+ * @property serviceUuids A list of service UUIDs within advertisement that are used to identify the
+ * bluetooth GATT services.
+ *
+ */
+class ScanResult internal constructor(private val fwkScanResult: FwkScanResult) {
+    /** Remote Bluetooth device found. */
+    val device: BluetoothDevice
+        get() = BluetoothDevice(fwkScanResult.device)
+
+    // TODO(kihongs) Find a way to get address type from framework scan result
+    /** Bluetooth address for the remote device found. */
+    val deviceAddress: BluetoothAddress
+        get() = BluetoothAddress(fwkScanResult.device.address, AddressType.ADDRESS_TYPE_RANDOM)
+
+    /** Device timestamp when the advertisement was last seen. */
+    val timestampNanos: Long
+        get() = fwkScanResult.timestampNanos
+
+    /**
+     * Returns the manufacturer specific data associated with the manufacturer id.
+     *
+     * @param manufacturerId The manufacturer id of the scanned device
+     * @return the manufacturer specific data associated with the manufacturer id, or null if the
+     * manufacturer specific data is not present
+     */
+    fun getManufacturerSpecificData(manufacturerId: Int): ByteArray? {
+        return fwkScanResult.scanRecord?.getManufacturerSpecificData(manufacturerId)
+    }
+
+    /**
+     * A list of service UUIDs within advertisement that are used to identify the bluetooth GATT
+     * services.
+     */
+    val serviceUuids: List<UUID>
+        get() = fwkScanResult.scanRecord?.serviceUuids?.map { it.uuid }.orEmpty()
+
+    /**
+     * Returns the service data associated with the service UUID.
+     *
+     * @param serviceUuid The service UUID of the service data
+     * @return the service data associated with the specified service UUID, or null if the service
+     * UUID is not found
+     */
+    fun getServiceData(serviceUuid: UUID): ByteArray? {
+        return fwkScanResult.scanRecord?.getServiceData(ParcelUuid(serviceUuid))
+    }
+
+    /**
+     * Checks if this object represents connectable scan result.
+     *
+     * @return true if the scanned device is connectable; false otherwise
+     */
+    fun isConnectable(): Boolean {
+        return fwkScanResult.isConnectable
+    }
+}
\ No newline at end of file