package app.grapheneos.setupwizard.view.activity import android.app.Activity import android.os.Bundle import android.view.SurfaceHolder import android.view.SurfaceView import android.view.ViewGroup import android.widget.FrameLayout import android.hardware.camera2.* import android.media.ImageReader import android.graphics.ImageFormat import android.os.Handler import android.os.HandlerThread import android.util.Slog import android.content.Context import com.google.zxing.* import com.google.zxing.common.HybridBinarizer import com.google.zxing.PlanarYUVLuminanceSource class SecureLineQrScanActivity : Activity() { private val TAG = "SecureLineQR" private lateinit var preview: SurfaceView private lateinit var cameraDevice: CameraDevice private lateinit var session: CameraCaptureSession private lateinit var imageReader: ImageReader private val reader = MultiFormatReader() private val handlerThread = HandlerThread("qr") private lateinit var handler: Handler override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) preview = SurfaceView(this) setContentView( FrameLayout(this).apply { addView( preview, FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ) ) } ) handlerThread.start() handler = Handler(handlerThread.looper) preview.holder.addCallback(object : SurfaceHolder.Callback { override fun surfaceCreated(holder: SurfaceHolder) { openCamera(holder) } override fun surfaceDestroyed(holder: SurfaceHolder) {} override fun surfaceChanged( holder: SurfaceHolder, format: Int, width: Int, height: Int ) {} }) } private fun openCamera(holder: SurfaceHolder) { val manager = getSystemService(Context.CAMERA_SERVICE) as CameraManager val cameraId = manager.cameraIdList.first() imageReader = ImageReader.newInstance( 1280, 720, ImageFormat.YUV_420_888, 2 ) imageReader.setOnImageAvailableListener({ val image = it.acquireLatestImage() ?: return@setOnImageAvailableListener val plane = image.planes[0] val buffer = plane.buffer val bytes = ByteArray(buffer.remaining()) buffer.get(bytes) image.close() decode(bytes) }, handler) manager.openCamera( cameraId, object : CameraDevice.StateCallback() { override fun onOpened(cam: CameraDevice) { cameraDevice = cam cam.createCaptureSession( listOf( holder.surface, imageReader.surface ), object : CameraCaptureSession.StateCallback() { override fun onConfigured( s: CameraCaptureSession ) { session = s val request = cam.createCaptureRequest( CameraDevice.TEMPLATE_PREVIEW ) request.addTarget( holder.surface ) request.addTarget( imageReader.surface ) session.setRepeatingRequest( request.build(), null, handler ) } override fun onConfigureFailed( session: CameraCaptureSession ) {} }, handler ) } override fun onDisconnected(cam: CameraDevice) {} override fun onError( cam: CameraDevice, error: Int ) {} }, handler ) } private fun decode(data: ByteArray) { try { val source = PlanarYUVLuminanceSource( data, 1280, 720, 0, 0, 1280, 720, false ) val bitmap = BinaryBitmap( HybridBinarizer(source) ) val hints = mapOf( DecodeHintType.TRY_HARDER to true, DecodeHintType.POSSIBLE_FORMATS to listOf( BarcodeFormat.QR_CODE ) ) val result = reader.decode(bitmap, hints) val text = result.text Slog.i(TAG, "QR detected: $text") runOnUiThread { setResult( RESULT_OK, intent.putExtra( "activation_code", parse(text) ) ) finish() } } catch (_: Exception) {} } private fun parse(raw: String): String { return try { val obj = org.json.JSONObject(raw) obj.getString("activation_code") } catch (_: Exception) { raw } } override fun onDestroy() { try { session.close() } catch (_: Exception) {} try { cameraDevice.close() } catch (_: Exception) {} try { imageReader.close() } catch (_: Exception) {} handlerThread.quitSafely() super.onDestroy() } }