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