• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2022 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.os.cts.companiondevicetestapp
18 
19 import android.Manifest.permission.ACCESS_COARSE_LOCATION
20 import android.Manifest.permission.ACCESS_FINE_LOCATION
21 import android.Manifest.permission.CALL_PHONE
22 import android.app.Activity
23 import android.bluetooth.BluetoothAdapter
24 import android.bluetooth.BluetoothDevice
25 import android.bluetooth.BluetoothManager
26 import android.bluetooth.le.ScanResult
27 import android.companion.AssociationRequest
28 import android.companion.AssociationRequest.DEVICE_PROFILE_GLASSES
29 import android.companion.AssociationRequest.DEVICE_PROFILE_WATCH
30 import android.companion.BluetoothDeviceFilter
31 import android.companion.CompanionDeviceManager
32 import android.content.ComponentName
33 import android.content.Context
34 import android.content.Intent
35 import android.content.IntentSender
36 import android.content.pm.PackageManager
37 import android.os.Bundle
38 import android.os.Handler
39 import android.os.Looper
40 import android.os.Parcelable
41 import android.os.Process
42 import android.util.Log
43 import android.widget.Button
44 import android.widget.CheckBox
45 import android.widget.EditText
46 import android.widget.LinearLayout
47 import android.widget.LinearLayout.VERTICAL
48 import android.widget.TextView
49 import android.widget.Toast
50 import java.util.regex.Pattern
51 
52 class CompanionDeviceTestAppActivity : Activity() {
53 
54     val associationStatus by lazy { TextView(this) }
55     val permissionStatus by lazy { TextView(this) }
56     val notificationsStatus by lazy { TextView(this) }
57     val bypassStatus by lazy { TextView(this) }
58 
59     val nameFilter by lazy { EditText(this).apply {
60         hint = "Name Filter"
61         contentDescription = "name filter" // Do not change: used in the tests.
62     } }
63     val singleCheckbox by lazy { CheckBox(this).apply { text = "Single Device" } }
64     val watchCheckbox by lazy { CheckBox(this).apply { text = "Watch" } }
65     val glassesCheckbox by lazy { CheckBox(this).apply { text = "Glasses" } }
66 
67     val cdm: CompanionDeviceManager by lazy { val java = CompanionDeviceManager::class.java
68         getSystemService(java)!! }
69     val bt: BluetoothAdapter by lazy { val java = BluetoothManager::class.java
70         getSystemService(java)!!.adapter }
71 
72     var device: BluetoothDevice? = null
73 
74     private val mainHandler = Handler(Looper.getMainLooper())
75 
76     override fun onCreate(savedInstanceState: Bundle?) {
77         super.onCreate(savedInstanceState)
78 
79         setContentView(LinearLayout(this).apply {
80             orientation = VERTICAL
81 
82             addView(associationStatus)
83             addView(permissionStatus)
84             addView(notificationsStatus)
85             addView(bypassStatus)
86 
87             addView(Button(ctx).apply {
88                 text = "^^^ Refresh"
89                 setOnClickListener { refresh() }
90             })
91 
92             addView(nameFilter)
93             addView(singleCheckbox)
94             addView(watchCheckbox)
95             addView(glassesCheckbox)
96 
97             addView(cdmButton("Associate") {
98                 if (singleCheckbox.isChecked) {
99                     setSingleDevice(true)
100                 }
101                 if (watchCheckbox.isChecked) {
102                     setDeviceProfile(DEVICE_PROFILE_WATCH)
103                 }
104                 if (glassesCheckbox.isChecked) {
105                     setDeviceProfile(DEVICE_PROFILE_GLASSES)
106                 }
107                 addDeviceFilter(BluetoothDeviceFilter.Builder().apply {
108                     if (!nameFilter.text.isEmpty()) {
109                         setNamePattern(Pattern.compile(".*${nameFilter.text}.*"))
110                     }
111                 }.build())
112             })
113 
114             addView(Button(ctx).apply {
115                 text = "Request notifications"
116                 setOnClickListener {
117                     cdm.requestNotificationAccess(
118                             ComponentName(ctx, NotificationListener::class.java))
119                 }
120             })
121             addView(Button(ctx).apply {
122                 text = "Disassociate"
123                 setOnClickListener {
124                     cdm.associations.forEach { address ->
125                         toast("Disassociating $address")
126                         cdm.disassociate(address)
127                     }
128                 }
129             })
130 
131             addView(Button(ctx).apply {
132                 text = "Register PresenceListener"
133                 setOnClickListener {
134                     cdm.associations.forEach { address ->
135                         toast("startObservingDevicePresence $address")
136                         cdm.startObservingDevicePresence(address)
137                     }
138                 }
139             })
140 
141             addView(Button(ctx).apply {
142                 text = "Request permission transfer"
143                 setOnClickListener {
144                     cdm.myAssociations.firstNotNullOf { associationInfo ->
145                         val associationId = associationInfo.id
146                         toast("requestSystemDataTransfer $associationId")
147                         val intentSender = cdm.buildPermissionTransferUserConsentIntent(
148                                 associationId)
149                         if (intentSender != null) {
150                             startIntentSender(intentSender, null, 0, 0, 0)
151                         }
152                     }
153                 }
154             })
155 
156             addView(Button(ctx).apply {
157                 text = "Check location permission"
158                 setOnClickListener {
159                     val locationAccess = ctx.checkSelfPermission(ACCESS_FINE_LOCATION)
160                     toast("location access: $locationAccess")
161                 }
162             })
163 
164             addView(Button(ctx).apply {
165                 text = "Request location permission"
166                 setOnClickListener {
167                     requestPermissions(arrayOf(ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION), 10)
168                 }
169             })
170         })
171     }
172 
173     private fun cdmButton(label: String, initReq: AssociationRequest.Builder.() -> Unit): Button {
174         return Button(ctx).apply {
175             text = label
176 
177             setOnClickListener {
178                 cdm.associate(AssociationRequest.Builder()
179                         .apply { initReq() }
180                         .build(),
181                         object : CompanionDeviceManager.Callback() {
182                             override fun onFailure(error: CharSequence?) {
183                                 toast("error: $error")
184                             }
185 
186                             override fun onDeviceFound(chooserLauncher: IntentSender) {
187                                 toast("launching $chooserLauncher")
188                                 chooserLauncher?.let {
189                                     startIntentSenderForResult(it, REQUEST_CODE_CDM, null, 0, 0, 0)
190                                 }
191                             }
192                         },
193                         mainHandler)
194             }
195         }
196     }
197 
198     override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
199         if (requestCode == REQUEST_CODE_CDM) {
200             device = getDevice(data)
201             toast("result code: $resultCode, device: $device")
202         }
203         super.onActivityResult(requestCode, resultCode, data)
204     }
205 
206     private fun getDevice(data: Intent?): BluetoothDevice? {
207         val rawDevice = data?.getParcelableExtra<Parcelable?>(CompanionDeviceManager.EXTRA_DEVICE)
208         return when (rawDevice) {
209             is BluetoothDevice -> rawDevice
210             is ScanResult -> rawDevice.device
211             else -> null
212         }
213     }
214 
215     override fun onResume() {
216         super.onResume()
217         refresh()
218     }
219 
220     private fun refresh() {
221         associationStatus.text = "Have associations: ${cdm.associations.isNotEmpty()}"
222 
223         permissionStatus.text = "Phone granted: ${
224             checkPermission(CALL_PHONE, Process.myPid(), Process.myUid()) ==
225                     PackageManager.PERMISSION_GRANTED}"
226 
227         notificationsStatus.postDelayed({
228             notificationsStatus.text = "Notifications granted: ${
229                 try {
230                     cdm.hasNotificationAccess(
231                             ComponentName.createRelative(
232                                     this, NotificationListener::class.java.name))
233                 } catch (e: Exception) {
234                     toast("" + e.message)
235                     false
236                 }
237             }"
238         }, 1000)
239     }
240 
241     companion object {
242         const val REQUEST_CODE_CDM = 1
243     }
244 }
245 
Contextnull246 fun Context.toast(msg: String) {
247     Log.i("CompanionDeviceManagerTest", "toast: $msg")
248     Toast.makeText(this, msg, Toast.LENGTH_LONG).show()
249 }
250 
251 val Context.ctx get() = this
252