package dev.garrettmills.starship.hyperlink; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.camera.core.Camera; import androidx.camera.core.CameraSelector; import androidx.camera.core.ImageAnalysis; import androidx.camera.core.Preview; import androidx.camera.lifecycle.ProcessCameraProvider; import androidx.camera.view.PreviewView; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import androidx.lifecycle.LifecycleOwner; import android.Manifest; import android.content.Intent; import android.content.pm.PackageManager; import android.os.Bundle; import android.util.Size; import android.widget.Toast; import com.google.common.util.concurrent.ListenableFuture; import java.util.concurrent.ExecutionException; import dev.garrettmills.starship.hyperlink.scanner.QRCodeImageAnalyzer; import dev.garrettmills.starship.hyperlink.scanner.QRCodeListener; import dev.garrettmills.starship.hyperlink.util.InvalidLoginTokenException; import dev.garrettmills.starship.hyperlink.util.LoginToken; public class LoginTokenScannerActivity extends AppCompatActivity implements QRCodeListener { private PreviewView previewView; private ListenableFuture cameraProviderFuture; private String qrCode; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login_token_scanner); previewView = findViewById(R.id.activity_login_token_scanner_previewView); cameraProviderFuture = ProcessCameraProvider.getInstance(this); requestCamera(); } protected void requestCamera() { if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { startCamera(); } else { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, Hyperlink.REQUEST_PERMISSION_CAMERA); } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (requestCode == Hyperlink.REQUEST_PERMISSION_CAMERA) { if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { startCamera(); } else { if ( BuildConfig.DEBUG ) { Toast.makeText(this, "Camera permission denied", Toast.LENGTH_SHORT).show(); } // If we denied, go back up to the main activity, w/o any login token finish(); } } } private void startCamera() { cameraProviderFuture.addListener(() -> { try { ProcessCameraProvider cameraProvider = cameraProviderFuture.get(); bindCameraPreview(cameraProvider); } catch (ExecutionException | InterruptedException e) { if ( BuildConfig.DEBUG ) { Toast.makeText(this, "Error starting camera: " + e.getMessage(), Toast.LENGTH_LONG).show(); } finish(); } }, ContextCompat.getMainExecutor(this)); } private void bindCameraPreview(@NonNull ProcessCameraProvider cameraProvider) { previewView.setPreferredImplementationMode(PreviewView.ImplementationMode.SURFACE_VIEW); Preview preview = new Preview.Builder().build(); CameraSelector cameraSelector = new CameraSelector.Builder() .requireLensFacing(CameraSelector.LENS_FACING_BACK) .build(); preview.setSurfaceProvider(previewView.createSurfaceProvider()); ImageAnalysis imageAnalysis = new ImageAnalysis.Builder() .setTargetResolution(new Size(1280, 720)) .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) .build(); imageAnalysis.setAnalyzer(ContextCompat.getMainExecutor(this), new QRCodeImageAnalyzer(this)); Camera camera = cameraProvider.bindToLifecycle((LifecycleOwner) this, cameraSelector, imageAnalysis, preview); } public void onQRCodeFound(String qrCode) { if ( !isValidLoginToken(qrCode) ) return; Intent result = new Intent(); result.putExtra(Hyperlink.EXTRA_LOGIN_TOKEN, qrCode); setResult(RESULT_OK, result); finish(); } public void onQRCodeNotFound() {} public boolean isValidLoginToken(String token) { try { new LoginToken(token); return true; } catch (InvalidLoginTokenException e) { return false; } } }