content: package com.adafruit.bluefruit.le.connect.app.imagetransfer;
  
  import android.Manifest;
  import android.app.Activity;
  import android.bluetooth.BluetoothGatt;
  import android.content.Context;
  import android.content.Intent;
  import android.content.SharedPreferences;
  import android.content.pm.PackageManager;
  import android.content.res.Configuration;
  import android.graphics.Bitmap;
  import android.graphics.BitmapFactory;
  import android.graphics.Canvas;
  import android.graphics.Color;
  import android.graphics.Paint;
  import android.graphics.drawable.BitmapDrawable;
  import android.net.Uri;
  import android.os.Bundle;
  import android.os.Environment;
  import android.os.Handler;
  import android.os.Looper;
  import android.provider.MediaStore;
  import android.text.InputType;
  import android.util.Log;
  import android.util.Size;
  import android.view.LayoutInflater;
  import android.view.Menu;
  import android.view.MenuInflater;
  import android.view.MenuItem;
  import android.view.View;
  import android.view.ViewGroup;
  import android.view.ViewTreeObserver;
  import android.widget.Button;
  import android.widget.EditText;
  import android.widget.FrameLayout;
  import android.widget.ImageButton;
  import android.widget.ImageView;
  import android.widget.RelativeLayout;
  import android.widget.TextView;
  
  import androidx.annotation.NonNull;
  import androidx.annotation.Nullable;
  import androidx.appcompat.app.AlertDialog;
  import androidx.core.app.ActivityCompat;
  import androidx.core.content.FileProvider;
  import androidx.fragment.app.FragmentActivity;
  import androidx.fragment.app.FragmentManager;
  import androidx.fragment.app.FragmentTransaction;
  
  import com.adafruit.bluefruit.le.connect.R;
  import com.adafruit.bluefruit.le.connect.app.CommonHelpFragment;
  import com.adafruit.bluefruit.le.connect.app.ConnectedPeripheralFragment;
  import com.adafruit.bluefruit.le.connect.ble.central.BlePeripheralUart;
  import com.adafruit.bluefruit.le.connect.ble.central.UartPacketManager;
  import com.adafruit.bluefruit.le.connect.dfu.ProgressFragmentDialog;
  import com.adafruit.bluefruit.le.connect.utils.DialogUtils;
  import com.adafruit.bluefruit.le.connect.utils.ImageMagickUtils;
  import com.adafruit.bluefruit.le.connect.utils.ImageUtils;
  
  import java.io.File;
  import java.io.FileNotFoundException;
  import java.io.FileOutputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.lang.ref.WeakReference;
  import java.nio.ByteBuffer;
  import java.text.SimpleDateFormat;
  import java.util.Date;
  import java.util.Locale;
  
  
  public class ImageTransferFragment extends ConnectedPeripheralFragment implements ImageCropFragment.OnImageCropListener, ImageTransferFormatSelectorDialogFragment.FormatSelectorListener {
      // Log
      private final static String TAG = ImageTransferFragment.class.getSimpleName();
  
      // Config
      private static final boolean kShowInterleaveControls = true;
      private static final int kDefaultInterlavedWithoutResponseCount = 50;
      private static final String kAuthorityField = ".fileprovider";          // Same as the authority field on the manifest provider
  
      // Constants
      private static final int kActivityRequestCode_pickFromGallery = 1;
      private static final int kActivityRequestCode_takePicture = 2;
      private static final int kActivityRequestCode_cropPicture = 3;
      private static final int kActivityRequestCode_requestCameraPermission = 4;
      private static final int kActivityRequestCode_requestReadExternalStoragePermission = 5;
  
      private final static String kPreferences = "ImageTransferFragment_prefs";
      private final static String kPreferences_resolutionWidth = "resolution_width";
      private final static String kPreferences_resolutionHeight = "resolution_height";
      private final static String kPreferences_interleavedWithoutResponseCount = "interleaved_withoutresponse_count";
      private final static String kPreferences_isColorSpace24Bits = "is_color_space_24_bits";
      private final static String kPreferences_isEInkModeEnabled = "is_eink_mode_enabled";
  
      private final Size kDefaultResolution = new Size(64, 64);
  
      // UI
      private TextView mUartWaitingTextView;
      private ImageView mCameraImageView;
      private Button mResolutionButton;
      private ViewGroup mResolutionViewGroup;
      private ViewGroup mResolutionContainerViewGroup;
      private Button mTransferModeButton;
      private Button mColorSpaceButton;
  
      // Data
      private UartPacketManager mUartManager;
      private final Handler mMainHandler = new Handler(Looper.getMainLooper());
      private BlePeripheralUart mBlePeripheralUart;
      private Size mResolution;
      private float mImageRotationDegrees;
      private int mInterleavedWithoutResponseCount;
      private boolean mIsColorSpace24Bits;
      private boolean mIsEInkModeEnabled;
      private Bitmap mBitmap;
      private ProgressFragmentDialog mProgressDialog;
  
      // Data - photo
      private String mTemporalPhotoPath;
  
      public static ImageTransferFragment newInstance(@Nullable String singlePeripheralIdentifier) {
          ImageTransferFragment fragment = new ImageTransferFragment();
          fragment.setArguments(createFragmentArgs(singlePeripheralIdentifier));
          return fragment;
      }
  
      public ImageTransferFragment() {
          // 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_imagetransfer, container, false);
      }
  
      @Override
      public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
          super.onViewCreated(view, savedInstanceState);
  
          Context context = getContext();
          if (context == null) return;
  
          // Update ActionBar
          setActionBarTitle(R.string.imagetransfer_tab_title);
  
  
          // set image cache temp directory in ImageMagick:
          FragmentActivity activity = getActivity();
          if (activity != null) {
              ImageMagickUtils.setCacheDir(activity);
          }
  
          // Init Data
          SharedPreferences preferences = context.getSharedPreferences(kPreferences, Context.MODE_PRIVATE);
          final int resolutionWidth = preferences.getInt(kPreferences_resolutionWidth, kDefaultResolution.getWidth());
          final int resolutionHeight = preferences.getInt(kPreferences_resolutionHeight, kDefaultResolution.getHeight());
          mResolution = new Size(resolutionWidth, resolutionHeight);
          mInterleavedWithoutResponseCount = preferences.getInt(kPreferences_interleavedWithoutResponseCount, kDefaultInterlavedWithoutResponseCount);
          mIsColorSpace24Bits = preferences.getBoolean(kPreferences_isColorSpace24Bits, false);
          mIsEInkModeEnabled = preferences.getBoolean(kPreferences_isEInkModeEnabled, false);
  
          // UI
          mUartWaitingTextView = view.findViewById(R.id.uartWaitingTextView);
          mUartWaitingTextView.setVisibility(mBlePeripheralUart != null && mBlePeripheralUart.isUartEnabled() ? View.GONE : View.VISIBLE);
  
          mCameraImageView = view.findViewById(R.id.cameraImageView);
          mCameraImageView.setImageBitmap(null);      // Clear any image
          mResolutionViewGroup = view.findViewById(R.id.resolutionViewGroup);
          mResolutionContainerViewGroup = view.findViewById(R.id.resolutionContainerViewGroup);
  
          mResolutionButton = view.findViewById(R.id.resolutionButton);
          mResolutionButton.setOnClickListener(v -> chooseResolution());
          Button imageButton = view.findViewById(R.id.imageButton);
          imageButton.setOnClickListener(v -> chooseImage());
  
          mTransferModeButton = view.findViewById(R.id.transferModeButton);
          mTransferModeButton.setOnClickListener(v -> {
              SharedPreferences settings = context.getSharedPreferences(kPreferences, Context.MODE_PRIVATE);
              SharedPreferences.Editor editor = settings.edit();
  
              if (kShowInterleaveControls) {
                  AlertDialog.Builder builder = new AlertDialog.Builder(context);
                  builder.setTitle(R.string.imagetransfer_transfermode_title);
  
                  String[] items = {getString(R.string.imagetransfer_transfermode_value_withoutresponse), getString(R.string.imagetransfer_transfermode_value_withresponse), getString(R.string.imagetransfer_transfermode_value_interleaved)};
  
                  builder.setItems(items, (dialog, which) -> {
                      switch (which) {
                          case 0:
                              mInterleavedWithoutResponseCount = Integer.MAX_VALUE;
                              updateTransferModeUI();
  
                              // Save selected mode
                              editor.putInt(kPreferences_interleavedWithoutResponseCount, mInterleavedWithoutResponseCount);
                              editor.apply();
                              break;
  
                          case 1:
                              mInterleavedWithoutResponseCount = 0;
                              updateTransferModeUI();
  
                              // Save selected mode
                              editor.putInt(kPreferences_interleavedWithoutResponseCount, mInterleavedWithoutResponseCount);
                              editor.apply();
                              break;
  
                          case 2: {
  
                              AlertDialog.Builder alert = new AlertDialog.Builder(context);
                              alert.setTitle(R.string.imagetransfer_transfermode_interleavedcount_title);
                              alert.setMessage(R.string.imagetransfer_transfermode_interleavedcount_message);
                              final EditText input = new EditText(context);
                              input.setHint(R.string.imagetransfer_transfermode_interleavedcount_hint);
                              input.setInputType(InputType.TYPE_CLASS_NUMBER);
                              input.setRawInputType(Configuration.KEYBOARD_12KEY);
  
                              // Add horizontal margin (https://stackoverflow.com/questions/27774414/add-bigger-margin-to-edittext-in-android-alertdialog)
                              FrameLayout container = new FrameLayout(context);
                              FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
                              params.leftMargin = getResources().getDimensionPixelSize(R.dimen.alertview_embedded_edittext_horizontalmargin);
                              params.rightMargin = params.leftMargin;
                              input.setLayoutParams(params);
                              container.addView(input);
  
                              alert.setView(container);
                              alert.setPositiveButton(R.string.dialog_ok, (dialog2, whichButton) -> {
                                  String valueString = String.valueOf(input.getText());
                                  int value = 0;
                                  try {
                                      value = Integer.parseInt(valueString);
                                  } catch (Exception e) {
                                      Log.d(TAG, "Cannot parse value");
                                  }
  
  
                                  // Set selected value
                                  mInterleavedWithoutResponseCount = value;
                                  updateTransferModeUI();
                                  editor.putInt(kPreferences_interleavedWithoutResponseCount, mInterleavedWithoutResponseCount);
                                  editor.apply();
  
                              });
                              alert.setNegativeButton(android.R.string.cancel, (dialog2, whichButton) -> {
                              });
                              alert.show();
  
                              break;
                          }
                      }
                      dialog.dismiss();
                  });
  
                  builder.setNegativeButton(R.string.dialog_cancel, null);
  
                  AlertDialog dialog = builder.create();
                  dialog.show();
  
              } else {
                  mInterleavedWithoutResponseCount = mInterleavedWithoutResponseCount == 0 ? Integer.MAX_VALUE : 0;
                  updateTransferModeUI();
  
                  // Save selected mode
                  editor.putInt(kPreferences_interleavedWithoutResponseCount, mInterleavedWithoutResponseCount);
                  editor.apply();
              }
  
  
          });
  
          mColorSpaceButton = view.findViewById(R.id.colorSpaceButton);
          updateColorSpaceUI();
          mColorSpaceButton.setOnClickListener(v -> {
              mIsColorSpace24Bits = !mIsColorSpace24Bits;
              updateColorSpaceUI();
  
              // Save to preferences
              SharedPreferences settings = context.getSharedPreferences(kPreferences, Context.MODE_PRIVATE);
              SharedPreferences.Editor editor = settings.edit();
              editor.putBoolean(kPreferences_isColorSpace24Bits, mIsColorSpace24Bits);
              editor.apply();
          });
  
          ImageButton rotateLeftButton = view.findViewById(R.id.rotateLeftButton);
          rotateLeftButton.setOnClickListener(v -> {
              final float rotation = (mImageRotationDegrees - 90) % 360;
              updateImage(mResolution, mIsEInkModeEnabled, rotation);
          });
  
          ImageButton rotateRightButton = view.findViewById(R.id.rotateRightButton);
          rotateRightButton.setOnClickListener(v -> {
              final float rotation = (mImageRotationDegrees + 90) % 360;
              updateImage(mResolution, mIsEInkModeEnabled, rotation);
          });
  
          Button sendButton = view.findViewById(R.id.sendButton);
          sendButton.setOnClickListener(view1 -> sendImage(mInterleavedWithoutResponseCount, mIsColorSpace24Bits));
  
          mResolutionContainerViewGroup.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
              @Override
              public void onGlobalLayout() {
                  mResolutionContainerViewGroup.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                  updateImage(mResolution, mIsEInkModeEnabled, mImageRotationDegrees);
              }
          });
  
          updateTransferModeUI();
  
          // Setup
          if (mUartManager == null) {      // Don't setup if already init (because fragment was recreated)
              // UartManager
              mUartManager = new UartPacketManager(context, null, false, null);
              start();
          }
      }
  
      @Override
      public void onDestroy() {
          super.onDestroy();
  
          stop();
      }
  
      @Override
      public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) {
          super.onCreateOptionsMenu(menu, inflater);
          inflater.inflate(R.menu.menu_help, menu);
      }
  
      @Override
      public boolean onOptionsItemSelected(MenuItem item) {
          FragmentActivity activity = getActivity();
  
          //noinspection SwitchStatementWithTooFewBranches
          switch (item.getItemId()) {
              case R.id.action_help:
                  if (activity != null) {
                      FragmentManager fragmentManager = activity.getSupportFragmentManager();
                      CommonHelpFragment helpFragment = CommonHelpFragment.newInstance(getString(R.string.imagetransfer_help_title), getString(R.string.imagetransfer_help_text));
                      FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction()
                              .setCustomAnimations(R.anim.slide_in_left, R.anim.slide_out_right, R.anim.slide_in_right, R.anim.slide_out_left)
                              .replace(R.id.contentLayout, helpFragment, "Help");
                      fragmentTransaction.addToBackStack(null);
                      fragmentTransaction.commit();
                  }
                  return true;
  
              default:
                  return super.onOptionsItemSelected(item);
          }
      }
  
  
      // region Uart
      private void start() {
          Log.d(TAG, "ImageTransfer start");
  
          // Enable Uart
          mBlePeripheralUart = new BlePeripheralUart(mBlePeripheral);
          mBlePeripheralUart.uartEnable(mUartManager, status -> mMainHandler.post(() -> {
              mUartWaitingTextView.setVisibility(status == BluetoothGatt.GATT_SUCCESS ? View.GONE : View.VISIBLE);
  
              if (status == BluetoothGatt.GATT_SUCCESS) {
                  // Done
                  Log.d(TAG, "Uart enabled");
  
                  // Set the default image
                  Context context = getContext();
                  if (context != null) {
                      Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.imagetransfer_default);
                      setImage(bitmap);
                  }
  
              } else {
                  Log.d(TAG, "Uart error");
                  Context context = getContext();
                  if (context != null) {
                      WeakReference<BlePeripheralUart> weakBlePeripheralUart = new WeakReference<>(mBlePeripheralUart);
                      AlertDialog.Builder builder = new AlertDialog.Builder(context);
                      AlertDialog dialog = builder.setMessage(R.string.uart_error_peripheralinit)
                              .setPositiveButton(android.R.string.ok, (dialogInterface, which) -> {
                                  BlePeripheralUart strongBlePeripheralUart = weakBlePeripheralUart.get();
                                  if (strongBlePeripheralUart != null) {
                                      try {
                                          strongBlePeripheralUart.disconnect();
                                      } catch (SecurityException e) {
                                          Log.e(TAG, "security exception: " + e);
                                      }
                                  }
                              })
                              .show();
                      DialogUtils.keepDialogOnOrientationChanges(dialog);
                  }
              }
          }));
      }
  
      private void stop() {
          Log.d(TAG, "ImageTransfer stop");
          dismissProgressDialog();
          mBlePeripheral.reset();
          mBlePeripheralUart = null;
      }
  
      // endregion
  
      // region UI
      private void updateTransferModeUI() {
          String text;
  
          if (mInterleavedWithoutResponseCount == 0) {
              text = getString(R.string.imagetransfer_transfermode_value_withresponse);
          } else if (mInterleavedWithoutResponseCount == Integer.MAX_VALUE) {
              text = getString(R.string.imagetransfer_transfermode_value_withoutresponse);
  
          } else {
              text = String.format(getString(R.string.imagetransfer_transfermode_value_interleaved_format), mInterleavedWithoutResponseCount);
          }
  
          mTransferModeButton.setText(text);
      }
  
      private void updateColorSpaceUI() {
          mColorSpaceButton.setText(mIsColorSpace24Bits ? R.string.imagetransfer_colorspace_24bit : R.string.imagetransfer_colorspace_16bit);
      }
  
      private void updateResolutionUI() {
          final String format = mIsEInkModeEnabled ? getString(R.string.imagetransfer_resolution_einkprefix) + " " + "%d x %d" : "%d x %d";
          final String text = String.format(Locale.US, format, mResolution.getWidth(), mResolution.getHeight());
          mResolutionButton.setText(text);
      }
  
      private void updateImage(Size resolution, boolean isEInkModeEnabled, float rotation) {
          Context context = getContext();
          if (context == null) return;
  
          mResolution = resolution;
          mIsEInkModeEnabled = isEInkModeEnabled;
          mImageRotationDegrees = rotation;
          final int width = mResolution.getWidth();
          final int height = mResolution.getHeight();
  
          // Save selected resolution
          SharedPreferences settings = context.getSharedPreferences(kPreferences, Context.MODE_PRIVATE);
          SharedPreferences.Editor editor = settings.edit();
          editor.putInt(kPreferences_resolutionWidth, width);
          editor.putInt(kPreferences_resolutionHeight, height);
          editor.putBoolean(kPreferences_isEInkModeEnabled, isEInkModeEnabled);
          editor.apply();
  
          // Change UI to adjust aspect ratio of the displayed image
          final float resolutionAspectRatio = resolution.getWidth() / (float) resolution.getHeight();
  
          final int maxWidth = mResolutionContainerViewGroup.getWidth();
          final int maxHeight = mResolutionContainerViewGroup.getHeight();
  
          RelativeLayout.LayoutParams relativeLayoutParams = (RelativeLayout.LayoutParams) mResolutionViewGroup.getLayoutParams();
          if (maxWidth > maxHeight * resolutionAspectRatio) {
              relativeLayoutParams.height = maxHeight;//(int) MetricsUtils.convertPixelsToDp(context, maxHeight);
              relativeLayoutParams.width = (int) (relativeLayoutParams.height * resolutionAspectRatio);
          } else {
              relativeLayoutParams.width = maxWidth;//(int) MetricsUtils.convertPixelsToDp(context, maxWidth);
              relativeLayoutParams.height = (int) (relativeLayoutParams.width * (1.f / resolutionAspectRatio));
  
          }
          mResolutionViewGroup.requestLayout();
  
          // Calculate transformed image
          if (mBitmap != null) {
              Bitmap transformedBitmap = ImageUtils.scaleAndRotateImage(mBitmap, mResolution, mImageRotationDegrees, Color.BLACK);
  
              if (isEInkModeEnabled) {
                  transformedBitmap = ImageUtils.applyEInkModeToImage(context, transformedBitmap);
              }
  
              BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(), transformedBitmap);        // Create bitmap drawable to control filtering method
              bitmapDrawable.setFilterBitmap(false);
  
              mCameraImageView.setImageDrawable(bitmapDrawable);
          }
  
          //
          updateResolutionUI();
      }
  
  
      // endregion
  
      // region Actions
  
      private void chooseResolution() {
          Context context = getContext();
          if (context == null) return;
  
          FragmentManager fragmentManager = getFragmentManager();
          if (fragmentManager == null) return;
  
          ImageTransferFormatSelectorDialogFragment dialogFragment = ImageTransferFormatSelectorDialogFragment.newInstance(mIsEInkModeEnabled, mResolution);
          dialogFragment.setTargetFragment(this, 0);
          dialogFragment.show(fragmentManager, ImageTransferFormatSelectorDialogFragment.class.getSimpleName());
      }
  
      private void chooseImage() {
          Context context = getContext();
          if (context == null) return;
  
          PackageManager packageManager = context.getPackageManager();
          final boolean hasCamera = packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
  
          boolean isCameraAvailable = false;
          if (hasCamera) {
              Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
              if (takePictureIntent.resolveActivity(packageManager) != null) {
                  isCameraAvailable = true;
              }
          }
  
          // Show image picker choices
          String[] imageChoices;
          if (isCameraAvailable) {
              imageChoices = new String[]{getString(R.string.imagetransfer_imagepicker_camera), getString(R.string.imagetransfer_imagepicker_photolibrary)};
          } else {
              imageChoices = new String[]{getString(R.string.imagetransfer_imagepicker_photolibrary)};
          }
  
          AlertDialog.Builder builder = new AlertDialog.Builder(context);
          boolean finalIsCameraAvailable = isCameraAvailable;
          builder.setTitle(R.string.imagetransfer_imageorigin_choose)
                  .setItems(imageChoices, (dialog, which) -> {
                      boolean isCameraSelected = which == 0 && finalIsCameraAvailable;
  
                      if (isCameraSelected) {     // Get image from camera
                          chooseFromCameraAskingPermissionIfNeeded(context);
                      } else {            // Get image from gallery
                          chooseFromLibraryAskingPermissionIfNeeded(context);
                      }
                  });
          builder.show();
      }
  
      private void chooseFromCameraAskingPermissionIfNeeded(@NonNull Context context) {
          int rc = ActivityCompat.checkSelfPermission(context, Manifest.permission.CAMERA);
          if (rc == PackageManager.PERMISSION_GRANTED) {
              chooseFromCamera(context);
          } else {
              requestCameraPermission();
          }
      }
  
      private void chooseFromCamera(@NonNull Context context) {
          PackageManager packageManager = context.getPackageManager();
  
          Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
          if (takePictureIntent.resolveActivity(packageManager) != null) {
  
              File photoFile = null;
              try {
                  photoFile = createImageFile(context);
              } catch (IOException ex) {
                  // Error occurred while creating the File
                  Log.w(TAG, "Could not create file to save picture");
                  new AlertDialog.Builder(context)
                          .setMessage(R.string.imagetransfer_cameranotavailable)
                          .setPositiveButton(android.R.string.ok, null)
                          .show();
              }
              // Continue only if the File was successfully created
              if (photoFile != null) {
                  final String authority = context.getApplicationContext().getPackageName() + kAuthorityField;
                  Uri photoUri = FileProvider.getUriForFile(context.getApplicationContext(), authority, photoFile);
                  takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
  
                  Log.d(TAG, "Start takePictureIntent");
                  startActivityForResult(takePictureIntent, kActivityRequestCode_takePicture);
                  Log.d(TAG, "Started takePictureIntent");
              }
  
          } else {
              Log.w(TAG, "Image capture not available");
          }
      }
  
      private void chooseFromLibraryAskingPermissionIfNeeded(@NonNull Context context) {
          int rc = ActivityCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE);
          if (rc == PackageManager.PERMISSION_GRANTED) {
              chooseFromLibrary(context);
          } else {
              requestExternalReadPermission();
          }
      }
  
      private void chooseFromLibrary(@NonNull Context context) {
          Intent intent = new Intent();
          intent.setType("image/*");
          intent.setAction(Intent.ACTION_GET_CONTENT);
          Intent pickPictureIntent = Intent.createChooser(intent, getString(R.string.imagetransfer_imageorigin_choose));
          if (pickPictureIntent.resolveActivity(context.getPackageManager()) != null) {
              startActivityForResult(pickPictureIntent, kActivityRequestCode_pickFromGallery);
          } else {
              Log.w(TAG, "There is no photo picker available");
          }
      }
  
      private void setImage(Bitmap bitmap) {
          mImageRotationDegrees = 0;      // reset rotation
          mBitmap = bitmap;
          updateImage(mResolution, mIsEInkModeEnabled, mImageRotationDegrees);
      }
      // endregion
  
      // region FormatSelectorListener
      @Override
      public void onResolutionSelected(Size resolution, boolean isEInkMode) {
          Log.d(TAG, "Resolution selected: " + resolution.getWidth() + ", " + resolution.getHeight() + " isEInk: " + isEInkMode);
  
          updateImage(resolution, isEInkMode, mImageRotationDegrees);
      }
      // endregion
  
      // region Image Picker
  
      @Override
      public void onActivityResult(int requestCode, int resultCode, Intent data) {
          Context context = getContext();
          if (context == null) return;
  
          if (requestCode == kActivityRequestCode_pickFromGallery && resultCode == Activity.RESULT_OK) {
              if (data != null && data.getData() != null) {
  
                  // Copy image to temporary file
                  try {
                      InputStream input = context.getContentResolver().openInputStream(data.getData());
                      if (input != null) {
                          final File temporaryFile = File.createTempFile("imagetransfer_picture", null);
                          temporaryFile.deleteOnExit();
                          try (FileOutputStream output = new FileOutputStream(temporaryFile)) {
                              byte[] buffer = new byte[4 * 1024];
                              int read;
                              while ((read = input.read(buffer)) != -1) {
                                  output.write(buffer, 0, read);
                              }
  
                              output.flush();
                          }
  
                          cropImage(temporaryFile.getPath());
                      }
  
                  } catch (FileNotFoundException e) {
                      Log.e(TAG, "Error opening image: " + e);
                  } catch (IOException e) {
                      Log.e(TAG, "Error creating temporary image: " + e);
                  }
  
              } else {
                  Log.w(TAG, "Couldn't pick a photo");
              }
          } else if (requestCode == kActivityRequestCode_takePicture && resultCode == Activity.RESULT_OK) {
              Log.d(TAG, "Picture taken");
  
              addPictureToGallery(context, mTemporalPhotoPath);
              Uri photoUri = Uri.parse(mTemporalPhotoPath);
              String photoPath = photoUri.getPath();
              if (photoPath != null) {
                  cropImage(photoPath);
              }
          } else if (requestCode == kActivityRequestCode_cropPicture && resultCode == Activity.RESULT_OK) {
              Log.d(TAG, "kActivityRequestCode_cropPicture");
          }
  
          super.onActivityResult(requestCode, resultCode, data);
  
      }
  
      private File createImageFile(@NonNull Context context) throws IOException {
          // Create an image file name
          String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date());
          String imageFileName = "JPEG_" + timeStamp + "_";
          File storageDir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
          File image = File.createTempFile(imageFileName, ".jpg", storageDir);
  
          // Save a file: path for use with ACTION_VIEW intents
          mTemporalPhotoPath = "file:" + image.getAbsolutePath();
          return image;
      }
  
      private void addPictureToGallery(@NonNull Context context, @NonNull String photoPath) {
          Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
          try {
              File f = new File(photoPath);
              Uri contentUri = Uri.fromFile(f);
              mediaScanIntent.setData(contentUri);
              context.sendBroadcast(mediaScanIntent);
          } catch (NullPointerException e) {
              Log.e(TAG, "Error opening file: " + photoPath);
          }
      }
      // 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};
  
          requestPermissions(permissions, kActivityRequestCode_requestCameraPermission);
  
          /* No need to explain rationale
          if (!shouldShowRequestPermissionRationale(Manifest.permission.CAMERA)) {
              requestPermissions(permissions, kActivityRequestCode_requestCameraPermission);
              return;
          }
          Toast.makeText(activity, R.string.imagetransfer_cameraneeded, Toast.LENGTH_LONG).show();
           */
      }
  
      private void requestExternalReadPermission() {
          FragmentActivity activity = getActivity();
          if (activity == null) {
              return;
          }
  
          Log.w(TAG, "External read permission is not granted. Requesting permission");
  
          final String[] permissions = new String[]{Manifest.permission.READ_EXTERNAL_STORAGE};
          requestPermissions(permissions, kActivityRequestCode_requestReadExternalStoragePermission);
  
          /* No need to explain rationale
          if (!shouldShowRequestPermissionRationale(Manifest.permission.READ_EXTERNAL_STORAGE)) {
              requestPermissions(permissions, kActivityRequestCode_requestReadExternalStoragePermission);
              return;
          }
          Toast.makeText(activity, R.string.imagetransfer_readexternalneeded, Toast.LENGTH_LONG).show();
          */
      }
  
      @Override
      public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
          super.onRequestPermissionsResult(requestCode, permissions, grantResults);
  
          Context context = getContext();
          if (context == null) return;
  
          if (requestCode == kActivityRequestCode_requestCameraPermission) {
              if (grantResults.length != 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                  Log.d(TAG, "Camera permission granted - initialize the camera source");
                  chooseFromCamera(context);
              } else {
  
                  Log.e(TAG, "Permission not granted: results len = " + grantResults.length + " Result code = " + (grantResults.length > 0 ? grantResults[0] : "(empty)"));
  
                  AlertDialog.Builder builder = new AlertDialog.Builder(context);
                  builder.setMessage(R.string.imagetransfer_cameraneeded)
                          .setPositiveButton(android.R.string.ok, null)
                          .show();
              }
          } else if (requestCode == kActivityRequestCode_requestReadExternalStoragePermission) {
              if (grantResults.length != 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                  Log.d(TAG, "External read permission granted");
                  chooseFromLibrary(context);
              } else {
                  Log.e(TAG, "Permission not granted: results len = " + grantResults.length + " Result code = " + (grantResults.length > 0 ? grantResults[0] : "(empty)"));
  
                  AlertDialog.Builder builder = new AlertDialog.Builder(context);
                  builder.setMessage(R.string.imagetransfer_readexternalneeded)
                          .setPositiveButton(android.R.string.ok, null)
                          .show();
              }
          } else {
              Log.d(TAG, "Got unexpected permission result: " + requestCode);
              super.onRequestPermissionsResult(requestCode, permissions, grantResults);
          }
      }
  
      // endregion
  
      // region Transform Image
  
      private void cropImage(@NonNull String imagePath) {
          FragmentManager fragmentManager = getFragmentManager();
          if (fragmentManager != null) {
              ImageCropFragment imageCropFragment = ImageCropFragment.newInstance(imagePath, mResolution.getWidth(), mResolution.getHeight());
              imageCropFragment.setTargetFragment(ImageTransferFragment.this, 0);
  
              fragmentManager.beginTransaction()
                      .add(R.id.contentLayout, imageCropFragment)
                      .addToBackStack(ImageCropFragment.TAG)
                      .commitAllowingStateLoss();
          }
      }
  
      // endregion
  
      // region Send Image
  
      private void sendImage(int packetWithResponseEveryPacketCount, boolean isColorSpace24Bits) {
          Bitmap bitmap = ((BitmapDrawable) mCameraImageView.getDrawable()).getBitmap();
          //Bitmap bitmap = mBitmapDrawable.getBitmap();
  
          // Create 32bits
          final int height = bitmap.getHeight();
          final int width = bitmap.getWidth();
          Bitmap rgbaBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
          Canvas canvas = new Canvas(rgbaBitmap);
          Paint paint = new Paint();
          canvas.drawBitmap(bitmap, 0, 0, paint);
  
          // get bytes
          final int rgbaSize = rgbaBitmap.getRowBytes() * rgbaBitmap.getHeight();
          ByteBuffer byteBuffer = ByteBuffer.allocate(rgbaSize);
          bitmap.copyPixelsToBuffer(byteBuffer);
          byte[] rgbaBytes = byteBuffer.array();
  
          //bitmap.recycle();
          byteBuffer = null;
  
          int rgbSize;
          byte[] rgbBytes;
          if (isColorSpace24Bits) {
              // Convert 32bit color data to 24bit (888)
              rgbSize = width * height * 3;
              rgbBytes = new byte[rgbSize];
              int k = 0;
              for (int i = 0; i < rgbaSize; i++) {
                  if (i % 4 != 3) {
                      rgbBytes[k++] = rgbaBytes[i];
                  }
              }
          } else {
              // Convert 32bit color data to 16bit (565)
              byte r = 0, g = 0;
              rgbSize = width * height * 2;
              rgbBytes = new byte[rgbSize];
              int k = 0;
              for (int i = 0; i < rgbaSize; i++) {
                  int j = i % 4;
                  if (j == 0) {
                      r = rgbaBytes[i];
                  } else if (j == 1) {
                      g = rgbaBytes[i];
  
                  } else if (j == 2) {
                      byte b = rgbaBytes[i];
  
                      int rShort = (r & 0xF8) & 0xffff;
                      int gShort = (g & 0xFC) & 0xffff;
                      int bShort = b & 0xff;
                      int rgb16 = (rShort << 8) | (gShort << 3) | (bShort >>> 3);
  
                      byte high = (byte) ((rgb16 >> 8) & 0xff);
                      byte low = (byte) (rgb16 & 0xff);
  
                      // Add as little endian
                      rgbBytes[k++] = low;
                      rgbBytes[k++] = high;
                  }
              }
          }
  
          rgbaBitmap.recycle();
  
          // Send command
          ByteBuffer buffer = ByteBuffer.allocate(2 + 1 + 2 + 2 + rgbSize).order(java.nio.ByteOrder.LITTLE_ENDIAN);
  
          // Command: '!I'
          String prefix = "!I";
          buffer.put(prefix.getBytes());
          buffer.put((byte) (isColorSpace24Bits ? 24 : 16));
          buffer.putShort((short) width);
          buffer.putShort((short) height);
          buffer.put(rgbBytes);
  
          byte[] result = buffer.array();
  
          sendCrcData(result, packetWithResponseEveryPacketCount);
  
          rgbaBytes = null;
      }
  
      private void sendCrcData(byte[] data, int packetWithResponseEveryPacketCount) {
          Context context = getContext();
          if (context == null) return;
  
          if (mUartManager == null) {
              return;
          }
  
          /*
          if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
              mBlePeripheral.readPhy();
          }*/
  
          // Progress fragment
          FragmentManager fragmentManager = getFragmentManager();
          if (fragmentManager != null) {
              dismissProgressDialog();
  
              mProgressDialog = ProgressFragmentDialog.newInstance(context.getString(R.string.imagetransfer_transferring));
              mProgressDialog.show(fragmentManager, null);
              fragmentManager.executePendingTransactions();
  
              mProgressDialog.setIndeterminate(false);
              mProgressDialog.setOnCancelListener(dialog -> {
                  cancelCurrentSendCommand();
                  dismissProgressDialog();
              });
          }
  
          final byte[] crcData = BlePeripheralUart.appendCrc(data);
  
          mUartManager.sendEachPacketSequentially(mBlePeripheralUart, crcData, packetWithResponseEveryPacketCount, progress -> {
              if (mProgressDialog != null) {
                  //Log.d(TAG, "progress: " + ((int) (progress * 100)));
                  mProgressDialog.setProgress((int) (progress * 100));
              }
          }, status -> {
              dismissProgressDialog();
  
              if (status != BluetoothGatt.GATT_SUCCESS) {
                  AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
                  AlertDialog dialog = builder.setMessage(R.string.imagetransfer_senddata_error)
                          .setPositiveButton(android.R.string.ok, null)
                          .show();
                  DialogUtils.keepDialogOnOrientationChanges(dialog);
              }
          });
      }
  
      // endregion
  
      // region Progress
  
      private void cancelCurrentSendCommand() {
          if (mBlePeripheralUart != null) {     // mBlePeripheralUart could be null if the peripheral disconnected
              mUartManager.cancelOngoingSendPacketSequentiallyInThread(mBlePeripheralUart);
          }
      }
  
      private void dismissProgressDialog() {
          if (mProgressDialog != null) {
              mProgressDialog.dismiss();
              mProgressDialog = null;
          }
      }
      // endregion
  
      // region ImageCropFragment
      public void onCropFinished(Bitmap bitmap) {
          Log.d(TAG, "onCropFinished");
          setImage(bitmap);
  
      }
      // endregion
  }
  