1 /*
2  * Copyright 2024 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  *      https://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 com.android.devicediagnostics.evaluated
18 
19 import android.Manifest
20 import android.graphics.Matrix
21 import android.graphics.Outline
22 import android.graphics.Rect
23 import android.graphics.SurfaceTexture
24 import android.os.Bundle
25 import android.os.VibrationEffect
26 import android.os.Vibrator
27 import android.util.Size
28 import android.view.TextureView
29 import android.view.View
30 import android.view.ViewOutlineProvider
31 import androidx.fragment.app.commit
32 import androidx.preference.Preference
33 import androidx.preference.PreferenceFragmentCompat
34 import com.android.devicediagnostics.ApplicationInterface
35 import com.android.devicediagnostics.PermissionsHelper
36 import com.android.devicediagnostics.Protos.TrustedDeviceInfo
37 import com.android.devicediagnostics.R
38 import com.android.devicediagnostics.bluetooth.BluetoothClient
39 import com.android.devicediagnostics.bluetooth.BluetoothConnectionData
40 import com.android.settingslib.collapsingtoolbar.CollapsingToolbarBaseActivity
41 import com.android.settingslib.qrcode.QrCamera
42 import com.android.settingslib.widget.LayoutPreference
43 
44 class QrCodeScanFragment(private val callbacks: Callbacks) :
45     TextureView.SurfaceTextureListener, QrCamera.ScannerCallback, PreferenceFragmentCompat() {
46     private var textureView: TextureView? = null
47     private var mCamera: QrCamera? = null
48 
49     interface Callbacks {
isQrCodeValidnull50         fun isQrCodeValid(qrCode: String?): Boolean
51 
52         fun onQrCodeScanned(qrCode: String)
53     }
54 
55     override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
56         setPreferencesFromResource(R.xml.preferences_evaluated_scan, rootKey)
57         mCamera = ApplicationInterface.app.getQrCamera(activity!!, this)
58     }
59 
onViewCreatednull60     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
61         super.onViewCreated(view, savedInstanceState)
62         val layoutPreference = findPreference("qr_code") as LayoutPreference?
63         textureView = layoutPreference!!.findViewById<TextureView>(R.id.preview_view)
64         textureView!!.surfaceTextureListener = this
65         textureView!!.outlineProvider =
66             object : ViewOutlineProvider() {
67                 override fun getOutline(view: View, outline: Outline) {
68                     outline.setRoundRect(0, 0, view.width, view.height, 12f)
69                 }
70             }
71         textureView!!.clipToOutline = true
72 
73         // Tap the QR code scanner to use a fake bluetooth client during testing.
74         var counter = 0
75         textureView!!.setOnClickListener {
76             if (++counter == 5 && ApplicationInterface.app.enterSingleDeviceMode()) {
77                 val fakeQrCode = BluetoothConnectionData(0, byteArrayOf(0)).toString()
78                 callbacks.onQrCodeScanned(fakeQrCode)
79             }
80             true
81         }
82     }
83 
setConnectionProgressVisibilitynull84     fun setConnectionProgressVisibility(visible: Boolean) {
85         val pref = findPreference<Preference>("connection_progress")!!
86         pref.isVisible = visible
87     }
88 
setConnectionFailedVisibilitynull89     fun setConnectionFailedVisibility(visible: Boolean) {
90         val pref = findPreference<Preference>("connection_failed")!!
91         pref.isVisible = visible
92     }
93 
onSurfaceTextureAvailablenull94     override fun onSurfaceTextureAvailable(surface: SurfaceTexture, width: Int, height: Int) {
95         mCamera?.start(surface)
96     }
97 
onSurfaceTextureSizeChangednull98     override fun onSurfaceTextureSizeChanged(surface: SurfaceTexture, width: Int, height: Int) {}
99 
onSurfaceTextureDestroyednull100     override fun onSurfaceTextureDestroyed(surface: SurfaceTexture): Boolean {
101         mCamera?.stop()
102         return true
103     }
104 
onSurfaceTextureUpdatednull105     override fun onSurfaceTextureUpdated(surface: SurfaceTexture) {}
106 
handleCameraFailurenull107     override fun handleCameraFailure() {
108         mCamera?.stop()
109     }
110 
getViewSizenull111     override fun getViewSize(): Size {
112         return Size(textureView!!.width, textureView!!.height)
113     }
114 
getFramePositionnull115     override fun getFramePosition(previewSize: Size, cameraOrientation: Int): Rect {
116         return Rect(0, 0, previewSize.height, previewSize.height)
117     }
118 
setTransformnull119     override fun setTransform(transform: Matrix?) {
120         textureView!!.setTransform(transform)
121     }
122 
isValidnull123     override fun isValid(qrCode: String?): Boolean {
124         return callbacks.isQrCodeValid(qrCode)
125     }
126 
handleSuccessfulResultnull127     override fun handleSuccessfulResult(qrCode: String?) {
128         callbacks.onQrCodeScanned(qrCode!!)
129     }
130 }
131 
132 class QrCodeScanActivity :
133     CollapsingToolbarBaseActivity(), QrCodeScanFragment.Callbacks, BluetoothClient.ScanListener {
134     private var mFragment = QrCodeScanFragment(this)
135     private var bluetoothClient: BluetoothClient? = null
136 
137     private val permissions =
138         PermissionsHelper(
139             this,
140             arrayOf(
141                 Manifest.permission.CAMERA,
142                 Manifest.permission.BLUETOOTH_ADVERTISE,
143                 Manifest.permission.BLUETOOTH_CONNECT,
144                 Manifest.permission.BLUETOOTH_SCAN,
145                 Manifest.permission.ACCESS_FINE_LOCATION
146             )
<lambda>null147         ) {
148             onPermissionsGranted()
149         }
150 
onCreatenull151     override fun onCreate(savedInstanceState: Bundle?) {
152         super.onCreate(savedInstanceState)
153         setContentView(R.layout.activity_one_fragment)
154         setTitle(R.string.evaluation_mode_title)
155 
156         ApplicationInterface.app.leaveSingleDeviceMode()
157     }
158 
onResumenull159     override fun onResume() {
160         super.onResume()
161         permissions.requestPermissions()
162     }
163 
onPermissionsGrantednull164     private fun onPermissionsGranted() {
165         supportFragmentManager.commit {
166             setReorderingAllowed(true)
167             add(R.id.fragment_container_view, mFragment)
168         }
169     }
170 
isQrCodeValidnull171     override fun isQrCodeValid(qrCode: String?): Boolean {
172         try {
173             BluetoothConnectionData.fromJson(qrCode!!)
174         } catch (e: Exception) {
175             return false
176         }
177         return true
178     }
179 
onQrCodeScannednull180     override fun onQrCodeScanned(qrCode: String) {
181         getSystemService<Vibrator>(Vibrator::class.java)!!.vibrate(
182             VibrationEffect.createOneShot(500, VibrationEffect.DEFAULT_AMPLITUDE)
183         )
184         startBluetooth(BluetoothConnectionData.fromJson(qrCode))
185     }
186 
startBluetoothnull187     private fun startBluetooth(connectionData: BluetoothConnectionData) {
188         bluetoothClient = ApplicationInterface.app.getBluetoothClient()
189         bluetoothClient!!.connectionData = connectionData
190         bluetoothClient!!.start(this)
191 
192         runOnUiThread { mFragment.setConnectionProgressVisibility(true) }
193     }
194 
onBluetoothConnectednull195     override fun onBluetoothConnected(trustedDeviceInfo: TrustedDeviceInfo) {
196         // Can be called from the test thread, so wrap this call in runOnUiThread.
197         runOnUiThread { onDeviceConnected(trustedDeviceInfo) }
198     }
199 
onDeviceConnectednull200     private fun onDeviceConnected(trustedDeviceInfo: TrustedDeviceInfo) {
201         mFragment.setConnectionProgressVisibility(false)
202         startFullTestFlow(this, trustedDeviceInfo)
203     }
204 
onBluetoothConnectionFailednull205     override fun onBluetoothConnectionFailed() {
206         mFragment.setConnectionProgressVisibility(false)
207         mFragment.setConnectionFailedVisibility(true)
208 
209         val bt = ApplicationInterface.app.getBluetoothClient()
210         bt.reset()
211     }
212 }
213