content: package com.adafruit.bluefruit.le.connect.app;
  
  import android.Manifest;
  import android.annotation.SuppressLint;
  import android.app.AlertDialog;
  import android.app.Dialog;
  import android.content.Context;
  import android.content.DialogInterface;
  import android.content.Intent;
  import android.content.IntentFilter;
  import android.content.pm.PackageManager;
  import android.hardware.Camera;
  import android.media.AudioManager;
  import android.media.ToneGenerator;
  import android.os.Bundle;
  import android.util.Log;
  import android.view.LayoutInflater;
  import android.view.ScaleGestureDetector;
  import android.view.View;
  import android.view.ViewGroup;
  import android.widget.Toast;
  
  import androidx.annotation.NonNull;
  import androidx.annotation.Nullable;
  import androidx.core.app.ActivityCompat;
  import androidx.fragment.app.Fragment;
  import androidx.fragment.app.FragmentActivity;
  import androidx.fragment.app.FragmentManager;
  
  import com.adafruit.bluefruit.le.connect.R;
  import com.adafruit.bluefruit.le.connect.app.barcode.BarcodeGraphic;
  import com.adafruit.bluefruit.le.connect.app.barcode.BarcodeGraphicTracker;
  import com.adafruit.bluefruit.le.connect.app.barcode.BarcodeTrackerFactory;
  import com.adafruit.bluefruit.le.connect.app.barcode.CameraSource;
  import com.adafruit.bluefruit.le.connect.app.barcode.CameraSourcePreview;
  import com.adafruit.bluefruit.le.connect.app.barcode.GraphicOverlay;
  import com.google.android.gms.common.ConnectionResult;
  import com.google.android.gms.common.GoogleApiAvailability;
  import com.google.android.gms.vision.MultiProcessor;
  import com.google.android.gms.vision.barcode.BarcodeDetector;
  import com.google.android.material.snackbar.Snackbar;
  
  import java.io.IOException;
  
  // Based on Android-Vision sample by Google: https://github.com/googlesamples/android-vision
  
  public class MqttSettingsCodeReaderFragment extends Fragment implements BarcodeGraphicTracker.BarcodeTrackerListener {
      // Log
      private final static String TAG = MqttSettingsCodeReaderFragment.class.getSimpleName();
  
      // Constants
      private final boolean kAutoFocus = true;
      private final boolean kUseFlash = false;
  
      // intent request code to handle updating play services if needed.
      private static final int RC_HANDLE_GMS = 9001;
  
      // permission request codes need to be < 256
      private static final int RC_HANDLE_CAMERA_PERM = 2;
  
      // camera graphics
      private CameraSource mCameraSource;
      private CameraSourcePreview mPreview;
      private GraphicOverlay<BarcodeGraphic> mGraphicOverlay;
  
      // helper objects for detecting taps and pinches.
      private ScaleGestureDetector scaleGestureDetector;
  
      // fragment listener
      public interface OnFragmentInteractionListener {
          void onPasswordUpdated(String password);
      }
  
      private OnFragmentInteractionListener mListener;
  
      // region Fragment Lifecycle
  
      @SuppressWarnings("UnnecessaryLocalVariable")
      public static MqttSettingsCodeReaderFragment newInstance() {
          MqttSettingsCodeReaderFragment fragment = new MqttSettingsCodeReaderFragment();
          return fragment;
      }
  
      public MqttSettingsCodeReaderFragment() {
          // Required empty public constructor
      }
  
      @Override
      public void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
      }
  
      @Override
      public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
          // Inflate the layout for this fragment
          return inflater.inflate(R.layout.fragment_mqttsettings_codereader, container, false);
      }
  
      @Override
      public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
          super.onViewCreated(view, savedInstanceState);
  
          mPreview = view.findViewById(R.id.preview);
          mGraphicOverlay = view.findViewById(R.id.graphicOverlay);
  
          mPreview.setOnTouchListener((view1, motionEvent) -> {
              final boolean isScaleDetected = scaleGestureDetector.onTouchEvent(motionEvent);
              return isScaleDetected;
          });
  
          Context context = getContext();
          if (context != null) {
  
              // Check for the camera permission before accessing the camera.  If the permission is not granted yet, request permission.
              int rc = ActivityCompat.checkSelfPermission(context, Manifest.permission.CAMERA);
              if (rc == PackageManager.PERMISSION_GRANTED) {
                  createCameraSource(kAutoFocus, kUseFlash);
              } else {
                  requestCameraPermission();
              }
  
              scaleGestureDetector = new ScaleGestureDetector(context, new ScaleListener());
          }
      }
  
      @Override
      public void onAttach(@NonNull Context context) {
          super.onAttach(context);
          if (context instanceof OnFragmentInteractionListener) {
              mListener = (OnFragmentInteractionListener) context;
          } else if (getTargetFragment() instanceof OnFragmentInteractionListener) {
              mListener = (OnFragmentInteractionListener) getTargetFragment();
          } else {
              throw new RuntimeException(context.toString() + " must implement OnImageCropListener");
          }
      }
  
      @Override
      public void onDetach() {
          super.onDetach();
          mListener = null;
      }
  
      @Override
      public void onResume() {
          super.onResume();
  
          startCameraSource();
      }
  
      @Override
      public void onPause() {
          super.onPause();
  
          if (mPreview != null) {
              mPreview.stop();
          }
      }
  
      /**
       * Releases the resources associated with the camera source, the associated detectors, and the
       * rest of the processing pipeline.
       */
      @Override
      public void onDestroy() {
          super.onDestroy();
          if (mPreview != null) {
              mPreview.release();
          }
      }
  
      // endregion
  
      // region Permissions
      private void requestCameraPermission() {
          FragmentActivity activity = getActivity();
          if (activity == null) {
              return;
          }
  
          Log.w(TAG, "Camera permission is not granted. Requesting permission");
  
          final String[] permissions = new String[]{Manifest.permission.CAMERA};
  
          if (!shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) {
              requestPermissions(permissions, RC_HANDLE_CAMERA_PERM);
              return;
          }
  
          View.OnClickListener listener = view -> requestPermissions(permissions, RC_HANDLE_CAMERA_PERM);
  
          Snackbar.make(mGraphicOverlay, R.string.mqttcodereader_cameraneeded, Snackbar.LENGTH_INDEFINITE)
                  .setAction(android.R.string.ok, listener)
                  .show();
      }
  
      @Override
      public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
          super.onRequestPermissionsResult(requestCode, permissions, grantResults);
  
          if (requestCode != RC_HANDLE_CAMERA_PERM) {
              Log.d(TAG, "Got unexpected permission result: " + requestCode);
              super.onRequestPermissionsResult(requestCode, permissions, grantResults);
              return;
          }
  
          if (grantResults.length != 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
              Log.d(TAG, "Camera permission granted - initialize the camera source");
              // we have permission, so create the camerasource
              createCameraSource(kAutoFocus, kUseFlash);
              return;
          }
  
          Log.e(TAG, "Permission not granted: results len = " + grantResults.length +
                  " Result code = " + (grantResults.length > 0 ? grantResults[0] : "(empty)"));
  
          DialogInterface.OnClickListener listener = (dialog, id) -> {
              FragmentActivity activity = getActivity();
              if (activity != null) {
                  FragmentManager fragmentManager = activity.getSupportFragmentManager();
                  fragmentManager.popBackStack();
              }
          };
  
          AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
          builder.setMessage(R.string.mqttcodereader_nocamerapermission)
                  .setPositiveButton(android.R.string.ok, listener)
                  .show();
      }
  
      // endregion
  
      /**
       * Creates and starts the camera.  Note that this uses a higher resolution in comparison
       * to other detection examples to enable the barcode detector to detect small barcodes
       * at long distances.
       * <p/>
       * Suppressing InlinedApi since there is a check that the minimum version is met before using
       * the constant.
       */
      @SuppressLint("InlinedApi")
      private void createCameraSource(boolean autoFocus, boolean useFlash) {
          Context context = getContext().getApplicationContext();
  
          // A barcode detector is created to track barcodes.  An associated multi-processor instance
          // is set to receive the barcode detection results, track the barcodes, and maintain
          // graphics for each barcode on screen.  The factory is used by the multi-processor to
          // create a separate tracker instance for each barcode.
          BarcodeDetector barcodeDetector = new BarcodeDetector.Builder(context).build();
          BarcodeTrackerFactory barcodeFactory = new BarcodeTrackerFactory(mGraphicOverlay, this);
          barcodeDetector.setProcessor(
                  new MultiProcessor.Builder<>(barcodeFactory).build());
  
          if (!barcodeDetector.isOperational()) {
              // Note: The first time that an app using the barcode or face API is installed on a
              // device, GMS will download a native libraries to the device in order to do detection.
              // Usually this completes before the app is run for the first time.  But if that
              // download has not yet completed, then the above call will not detect any barcodes
              // and/or faces.
              //
              // isOperational() can be used to check if the required native libraries are currently
              // available.  The detectors will automatically become operational once the library
              // downloads complete on device.
              Log.w(TAG, "Detector dependencies are not yet available.");
  
              // Check for low storage.  If there is low storage, the native library will not be
              // downloaded, so detection will not become operational.
              IntentFilter lowstorageFilter = new IntentFilter(Intent.ACTION_DEVICE_STORAGE_LOW);
              boolean hasLowStorage = context.registerReceiver(null, lowstorageFilter) != null;
  
              if (hasLowStorage) {
                  Toast.makeText(context, R.string.mqttcodereader_lowstorageerror, Toast.LENGTH_LONG).show();
                  Log.w(TAG, getString(R.string.mqttcodereader_lowstorageerror));
              }
          }
  
          // Creates and starts the camera.  Note that this uses a higher resolution in comparison
          // to other detection examples to enable the barcode detector to detect small barcodes
          // at long distances.
          CameraSource.Builder builder = new CameraSource.Builder(context, barcodeDetector)
                  .setFacing(CameraSource.CAMERA_FACING_BACK)
                  .setRequestedPreviewSize(1600, 1024)
                  .setRequestedFps(15.0f);
  
          builder = builder.setFocusMode(
                  autoFocus ? Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE : null);
  
          mCameraSource = builder
                  .setFlashMode(useFlash ? Camera.Parameters.FLASH_MODE_TORCH : null)
                  .build();
      }
  
      // region
  
      /**
       * Starts or restarts the camera source, if it exists.  If the camera source doesn't exist yet
       * (e.g., because onResume was called before the camera source was created), this will be called
       * again when the camera source is created.
       */
      private void startCameraSource() throws SecurityException {
          // check that the device has play services available.
          Context context = getContext();
          if (context == null) {
              return;
          }
  
          int code = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context.getApplicationContext());
          if (code != ConnectionResult.SUCCESS) {
              Dialog dlg = GoogleApiAvailability.getInstance().getErrorDialog(getActivity(), code, RC_HANDLE_GMS);
              dlg.show();
          }
  
          if (mCameraSource != null) {
              try {
                  mPreview.start(mCameraSource, mGraphicOverlay);
              } catch (IOException e) {
                  Log.e(TAG, "Unable to start camera source.", e);
                  mCameraSource.release();
                  mCameraSource = null;
              }
          }
      }
      // endregion
  
      // region BarcodeGraphicTracker.BarcodeTrackerListener
      private boolean isCodeAlreadyScanned = false;
  
      @Override
      public void onCodeScanned(String contents) {
          Log.d(TAG, "Code Scanned: " + contents);
          if (isCodeAlreadyScanned) {
              return;
          }       // To avoid double scans and pop 2 times the fragment
          isCodeAlreadyScanned = true;
  
          // Beep
          final ToneGenerator tg = new ToneGenerator(AudioManager.STREAM_NOTIFICATION, 100);
          tg.startTone(ToneGenerator.TONE_PROP_BEEP);
  
          //
          mListener.onPasswordUpdated(contents);
  
          // Pop current fragment
          FragmentActivity activity = getActivity();
          if (activity != null) {
              FragmentManager fragmentManager = activity.getSupportFragmentManager();
              fragmentManager.popBackStack();
          }
      }
  
      // endregion
  
  
      // region
      private class ScaleListener implements ScaleGestureDetector.OnScaleGestureListener {
  
          /**
           * Responds to scaling events for a gesture in progress.
           * Reported by pointer motion.
           *
           * @param detector The detector reporting the event - use this to
           *                 retrieve extended info about event state.
           * @return Whether or not the detector should consider this event
           * as handled. If an event was not handled, the detector
           * will continue to accumulate movement until an event is
           * handled. This can be useful if an application, for example,
           * only wants to update scaling factors if the change is
           * greater than 0.01.
           */
          @Override
          public boolean onScale(ScaleGestureDetector detector) {
              return false;
          }
  
          /**
           * Responds to the beginning of a scaling gesture. Reported by
           * new pointers going down.
           *
           * @param detector The detector reporting the event - use this to
           *                 retrieve extended info about event state.
           * @return Whether or not the detector should continue recognizing
           * this gesture. For example, if a gesture is beginning
           * with a focal point outside of a region where it makes
           * sense, onScaleBegin() may return false to ignore the
           * rest of the gesture.
           */
          @Override
          public boolean onScaleBegin(ScaleGestureDetector detector) {
              return true;
          }
  
          /**
           * Responds to the end of a scale gesture. Reported by existing
           * pointers going up.
           * <p/>
           * Once a scale has ended, {@link ScaleGestureDetector#getFocusX()}
           * and {@link ScaleGestureDetector#getFocusY()} will return focal point
           * of the pointers remaining on the screen.
           *
           * @param detector The detector reporting the event - use this to
           *                 retrieve extended info about event state.
           */
          @Override
          public void onScaleEnd(ScaleGestureDetector detector) {
              mCameraSource.doZoom(detector.getScaleFactor());
          }
      }
      // endregion
  }
  