• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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