1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.bluetooth 18 19 import android.content.BroadcastReceiver 20 import android.content.Context 21 import android.content.Intent 22 import android.content.IntentFilter 23 import android.net.MacAddress 24 import androidx.test.ext.junit.runners.AndroidJUnit4 25 import androidx.test.platform.app.InstrumentationRegistry 26 import com.android.pandora.intentFlow 27 import com.google.common.truth.Truth.assertThat 28 import com.google.protobuf.ByteString 29 import com.google.protobuf.Empty 30 import io.grpc.ManagedChannel 31 import io.grpc.okhttp.OkHttpChannelBuilder 32 import java.util.concurrent.TimeUnit 33 import kotlinx.coroutines.async 34 import kotlinx.coroutines.flow.Flow 35 import kotlinx.coroutines.flow.MutableStateFlow 36 import kotlinx.coroutines.flow.SharingStarted 37 import kotlinx.coroutines.flow.filter 38 import kotlinx.coroutines.flow.first 39 import kotlinx.coroutines.flow.shareIn 40 import kotlinx.coroutines.runBlocking 41 import kotlinx.coroutines.test.TestScope 42 import kotlinx.coroutines.test.UnconfinedTestDispatcher 43 import kotlinx.coroutines.test.runTest 44 import org.junit.After 45 import org.junit.Before 46 import org.junit.BeforeClass 47 import org.junit.Test 48 import org.junit.runner.RunWith 49 import pandora.HostGrpc 50 import pandora.HostProto.ConnectRequest 51 import pandora.HostProto.DisconnectRequest 52 53 @Suppress("DEPRECATION") 54 @kotlinx.coroutines.ExperimentalCoroutinesApi 55 @RunWith(AndroidJUnit4::class) 56 class BluetoothMetricsHelperTest { 57 58 companion object { 59 private const val TAG = "BluetoothMetricsHelperTest" 60 61 private lateinit var mChannel: ManagedChannel 62 private lateinit var mHostBlockingStub: HostGrpc.HostBlockingStub 63 private lateinit var mHostStub: HostGrpc.HostStub 64 65 @BeforeClass setUpClassnull66 fun setUpClass() { 67 InstrumentationRegistry.getInstrumentation() 68 .getUiAutomation() 69 .adoptShellPermissionIdentity() 70 } 71 } 72 73 private val testDispatcher = UnconfinedTestDispatcher() 74 private val testScope = TestScope(testDispatcher) 75 private val context = InstrumentationRegistry.getInstrumentation().getContext() 76 77 private val bluetoothAdapter: BluetoothAdapter = 78 context.getSystemService(BluetoothManager::class.java)!!.adapter 79 private val adapterState = MutableStateFlow(bluetoothAdapter.state) 80 81 init { 82 context.registerReceiver( 83 object : BroadcastReceiver() { onReceivenull84 override fun onReceive(context: Context, intent: Intent) { 85 adapterState.tryEmit( 86 intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR) 87 ) 88 } 89 }, 90 IntentFilter(BluetoothAdapter.ACTION_BLE_STATE_CHANGED) 91 ) 92 } 93 94 @Before setUpnull95 fun setUp() { 96 val uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation() 97 // Adopt all the permissions of the shell 98 uiAutomation.adoptShellPermissionIdentity() 99 100 // FactorReset is killing the server and restart 101 // all channel created before the server restarted 102 // cannot be reused 103 val channel = OkHttpChannelBuilder.forAddress("localhost", 7999).usePlaintext().build() 104 105 HostGrpc.newBlockingStub(channel).factoryReset(Empty.getDefaultInstance()) 106 107 // terminate the channel 108 channel.shutdown().awaitTermination(1, TimeUnit.SECONDS) 109 110 // Create a new channel for all successive grpc calls 111 mChannel = OkHttpChannelBuilder.forAddress("localhost", 7999).usePlaintext().build() 112 113 mHostBlockingStub = HostGrpc.newBlockingStub(mChannel) 114 mHostStub = HostGrpc.newStub(mChannel) 115 mHostBlockingStub.withWaitForReady()?.readLocalAddress(Empty.getDefaultInstance()) 116 117 // Make sure the Adapter is on. 118 bluetoothAdapter.enable() 119 runBlocking { adapterState.first { it == BluetoothAdapter.STATE_ON } } 120 } 121 122 @After tearDownnull123 fun tearDown() { 124 // terminate the channel 125 mChannel.shutdown()?.awaitTermination(1, TimeUnit.SECONDS) 126 } 127 128 @Test incomingClassicConnectionTestnull129 fun incomingClassicConnectionTest() = runTest { 130 val intentFilter = IntentFilter() 131 intentFilter.addAction(BluetoothDevice.ACTION_ACL_CONNECTED) 132 intentFilter.addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED) 133 val flow: Flow<Intent> = 134 intentFlow(context, intentFilter, testScope).shareIn(testScope, SharingStarted.Eagerly) 135 136 val incomingConnection = async { 137 flow 138 .filter { it.action == BluetoothDevice.ACTION_ACL_CONNECTED } 139 .filter { 140 it.getIntExtra(BluetoothDevice.EXTRA_TRANSPORT, BluetoothDevice.ERROR) == 141 BluetoothDevice.TRANSPORT_BREDR 142 } 143 .first() 144 } 145 146 val localMacAddress = MacAddress.fromString(bluetoothAdapter.getAddress()) 147 val connectRequest = 148 ConnectRequest.newBuilder() 149 .setAddress(ByteString.copyFrom(localMacAddress.toByteArray())) 150 .build() 151 val connectResponse = mHostBlockingStub.connect(connectRequest) 152 assertThat(connectResponse).isNotNull() 153 assertThat(connectResponse.hasConnection()).isTrue() 154 incomingConnection.await() 155 156 val disconnectRequest = 157 DisconnectRequest.newBuilder().setConnection(connectResponse.connection).build() 158 mHostBlockingStub.disconnect(disconnectRequest) 159 } 160 161 @Test <lambda>null162 fun testBluetoothDisableEnable() = runTest { 163 bluetoothAdapter.disable() 164 adapterState.first { it == BluetoothAdapter.STATE_OFF } 165 bluetoothAdapter.enable() 166 adapterState.first { it == BluetoothAdapter.STATE_ON } 167 } 168 } 169