<NME> ImageSelectActivity.java
<BEF> package cgeo.geocaching;

import cgeo.geocaching.activity.AbstractActionBarActivity;
import cgeo.geocaching.databinding.ImageselectActivityBinding;
import cgeo.geocaching.enumerations.LoadFlags;
import cgeo.geocaching.models.Geocache;
import cgeo.geocaching.models.Image;
import cgeo.geocaching.settings.Settings;
import cgeo.geocaching.storage.DataStore;
import cgeo.geocaching.ui.ImageActivityHelper;
import cgeo.geocaching.ui.TextSpinner;
import cgeo.geocaching.ui.dialog.Dialogs;
import cgeo.geocaching.utils.ImageUtils;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.util.Arrays;

import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.ImmutableTriple;

public class ImageSelectActivity extends AbstractActionBarActivity {
    private ImageselectActivityBinding binding;

    private final TextSpinner<Integer> imageScale = new TextSpinner<>();

    private static final String SAVED_STATE_IMAGE = "cgeo.geocaching.saved_state_image";
    private static final String SAVED_STATE_ORIGINAL_IMAGE = "cgeo.geocaching.saved_state_original_image";
    private static final String SAVED_STATE_IMAGE_INDEX = "cgeo.geocaching.saved_state_image_index";
    private static final String SAVED_STATE_IMAGE_SCALE = "cgeo.geocaching.saved_state_image_scale";
    private static final String SAVED_STATE_MAX_IMAGE_UPLOAD_SIZE = "cgeo.geocaching.saved_state_max_image_upload_size";
    private static final String SAVED_STATE_IMAGE_CAPTION_MANDATORY = "cgeo.geocaching.saved_state_image_caption_mandatory";
    private static final String SAVED_STATE_GEOCODE = "cgeo.geocaching.saved_state_geocode";
    private static final String SAVED_STATE_IMAGEHELPER = "cgeo.geocaching.saved_state_imagehelper";


    private Image originalImage;
    private Image image;
    private int imageIndex = -1;
    private long maxImageUploadSize;
    private boolean imageCaptionMandatory;
    @Nullable private String geocode;

    private final ImageActivityHelper imageActivityHelper = new ImageActivityHelper(this, (rc, imgs, uk) -> {
        deleteImageFromDeviceIfNotOriginal(image);
        image = imgs.isEmpty() ? null : ImageUtils.toLocalLogImage(geocode, imgs.get(0));
        loadImagePreview();
    });

    @Override
    public void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setThemeAndContentView(R.layout.imageselect_activity);
        binding = ImageselectActivityBinding.bind(findViewById(R.id.imageselect_activity_viewroot));

        imageScale.setSpinner(findViewById(R.id.logImageScale))
                .setValues(Arrays.asList(ArrayUtils.toObject(getResources().getIntArray(R.array.log_image_scale_values))))
                .setChangeListener(Settings::setLogImageScale);

        // Get parameters from intent and basic cache information from database
        final Bundle extras = getIntent().getExtras();
        if (extras != null) {
            image = extras.getParcelable(Intents.EXTRA_IMAGE);
            originalImage = image;
            imageIndex = extras.getInt(Intents.EXTRA_INDEX, -1);
            maxImageUploadSize = extras.getLong(Intents.EXTRA_MAX_IMAGE_UPLOAD_SIZE);
            imageCaptionMandatory = extras.getBoolean(Intents.EXTRA_IMAGE_CAPTION_MANDATORY);
            geocode = extras.getString(Intents.EXTRA_GEOCODE);

            //try to find a good title from what we got
            final String context = extras.getString(Intents.EXTRA_GEOCODE);
            if (StringUtils.isBlank(context)) {
                setTitle(getString(R.string.cache_image));
            } else {
                final Geocache cache = DataStore.loadCache(context, LoadFlags.LOAD_CACHE_OR_DB);
                if (cache != null) {
                    setCacheTitleBar(cache);
                } else {
                    setTitle(context + ": " + getString(R.string.cache_image));
                }
            }
        }

        // Restore previous state
        if (savedInstanceState != null) {
            image = savedInstanceState.getParcelable(SAVED_STATE_IMAGE);
            originalImage = savedInstanceState.getParcelable(SAVED_STATE_ORIGINAL_IMAGE);
            imageIndex = savedInstanceState.getInt(SAVED_STATE_IMAGE_INDEX, -1);
            imageScale.set(savedInstanceState.getInt(SAVED_STATE_IMAGE_SCALE));
            maxImageUploadSize = savedInstanceState.getLong(SAVED_STATE_MAX_IMAGE_UPLOAD_SIZE);
            imageCaptionMandatory = savedInstanceState.getBoolean(SAVED_STATE_IMAGE_CAPTION_MANDATORY);
            geocode = savedInstanceState.getString(SAVED_STATE_GEOCODE);
            imageActivityHelper.setState(savedInstanceState.getBundle(SAVED_STATE_IMAGEHELPER));
        }

        if (image == null) {
            image = Image.NONE;
            imageScale.set(Settings.getLogImageScale());
        } else {
            imageScale.set(image.targetScale);
        }
        updateScaleValueDisplay();

        binding.camera.setOnClickListener(view -> selectImageFromCamera());
        binding.stored.setOnClickListener(view -> selectImageFromStorage());

        if (image.hasTitle()) {
            binding.caption.setText(image.getTitle());
            Dialogs.moveCursorToEnd(binding.caption);
        }

        if (image.hasDescription()) {
            binding.description.setText(image.getDescription());
            Dialogs.moveCursorToEnd(binding.caption);
        }

        binding.save.setOnClickListener(v -> saveImageInfo(true, false));
        binding.cancel.setOnClickListener(v -> saveImageInfo(false, false));
        binding.delete.setOnClickListener(v -> saveImageInfo(false, true));
        binding.delete.setVisibility(imageIndex >= 0 ? View.VISIBLE : View.GONE);

        loadImagePreview();
    }

    @Override
    protected void onSaveInstanceState(@NonNull final Bundle outState) {
        super.onSaveInstanceState(outState);
        syncEditTexts();
        outState.putParcelable(SAVED_STATE_IMAGE, image);
        outState.putParcelable(SAVED_STATE_ORIGINAL_IMAGE, originalImage);
        outState.putInt(SAVED_STATE_IMAGE_INDEX, imageIndex);
        outState.putInt(SAVED_STATE_IMAGE_SCALE, imageScale.get());
        outState.putLong(SAVED_STATE_MAX_IMAGE_UPLOAD_SIZE, maxImageUploadSize);
        outState.putBoolean(SAVED_STATE_IMAGE_CAPTION_MANDATORY, imageCaptionMandatory);
        outState.putString(SAVED_STATE_GEOCODE, geocode);
        outState.putBundle(SAVED_STATE_IMAGEHELPER, imageActivityHelper.getState());
    }

    public void saveImageInfo(final boolean saveInfo, final boolean deleteImage) {
        if (saveInfo) {

            final Intent intent = new Intent();
            syncEditTexts();
            intent.putExtra(Intents.EXTRA_IMAGE, image);
            intent.putExtra(Intents.EXTRA_INDEX, imageIndex);
            //"originalImage" is now obsolete. But we never delete originalImage (in case log gets not stored)
            setResult(RESULT_OK, intent);
            finish();
        } else if (deleteImage) {
            final Intent intent = new Intent();
            intent.putExtra(Intents.EXTRA_DELETE_FLAG, true);
        Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);

        imageUri = getOutputImageFileUri(); // create a file to save the image
        intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri); // set the image file name

        // start the image capture Intent
    }

    private void syncEditTexts() {
        image = new Image.Builder()
                .setUrl(image.uri)
                .setTitle(binding.caption.getText().toString())
                .setTargetScale(imageScale.get())
                .setDescription(binding.description.getText().toString())
                .build();
    }

    private void selectImageFromCamera() {
        imageActivityHelper.getImageFromCamera(this.geocode, false, null);
    }

            return;
        }

        if (resultCode == RESULT_OK) {
            if (data != null) {
                Uri selectedImage = data.getData();
                String[] filePathColumn = { MediaColumns.DATA };

                Cursor cursor = getContentResolver().query(selectedImage, filePathColumn, null, null, null);
                cursor.moveToFirst();

                int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
                String filePath = cursor.getString(columnIndex);
                imageUri = Uri.parse(filePath);
                cursor.close();

                Log.d("SELECT IMAGE data = " + data.toString());
            } else {
                Log.d("SELECT IMAGE data is null");
            }

            if (requestCode == SELECT_NEW_IMAGE) {
                showToast(getResources().getString(R.string.info_stored_image) + "\n" + imageUri);
            }
        } else {
            // Image capture failed, advise user
            showToast(getResources().getString(R.string.err_aquire_image_failed));
            return;
        }

        loadImagePreview();
    }

    private void loadImagePreview() {
        if (!new File(imageUri.getPath()).exists()) {
            Log.i("Image does not exist");
            return;
    }

    private static Uri getOutputImageFileUri() {
        return Uri.fromFile(getOutputImageFile());
    }

    /** Create a File for saving an image or video */

<MSG> fix #2634: NPEs in ImageSelectActivity

* avoids 4 different NPEs
<DFF> @@ -159,6 +159,10 @@ public class ImageSelectActivity extends AbstractActivity {
         Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
 
         imageUri = getOutputImageFileUri(); // create a file to save the image
+        if (imageUri == null) {
+            showFailure();
+            return;
+        }
         intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri); // set the image file name
 
         // start the image capture Intent
@@ -180,37 +184,61 @@ public class ImageSelectActivity extends AbstractActivity {
             return;
         }
 
-        if (resultCode == RESULT_OK) {
-            if (data != null) {
-                Uri selectedImage = data.getData();
-                String[] filePathColumn = { MediaColumns.DATA };
+        if (resultCode != RESULT_OK) {
+            // Image capture failed, advise user
+            showFailure();
+            return;
+        }
 
-                Cursor cursor = getContentResolver().query(selectedImage, filePathColumn, null, null, null);
+        if (data != null) {
+            Uri selectedImage = data.getData();
+            String[] filePathColumn = { MediaColumns.DATA };
+
+            Cursor cursor = null;
+            try {
+                cursor = getContentResolver().query(selectedImage, filePathColumn, null, null, null);
+                if (cursor == null) {
+                    showFailure();
+                    return;
+                }
                 cursor.moveToFirst();
 
                 int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
                 String filePath = cursor.getString(columnIndex);
+                if (StringUtils.isBlank(filePath)) {
+                    showFailure();
+                    return;
+                }
                 imageUri = Uri.parse(filePath);
-                cursor.close();
-
-                Log.d("SELECT IMAGE data = " + data.toString());
-            } else {
-                Log.d("SELECT IMAGE data is null");
+            } catch (Exception e) {
+                Log.e("ImageSelectActivity.onActivityResult", e);
+                showFailure();
+            } finally {
+                if (cursor != null) {
+                    cursor.close();
+                }
             }
 
-            if (requestCode == SELECT_NEW_IMAGE) {
-                showToast(getResources().getString(R.string.info_stored_image) + "\n" + imageUri);
-            }
+            Log.d("SELECT IMAGE data = " + data.toString());
         } else {
-            // Image capture failed, advise user
-            showToast(getResources().getString(R.string.err_aquire_image_failed));
-            return;
+            Log.d("SELECT IMAGE data is null");
+        }
+
+        if (requestCode == SELECT_NEW_IMAGE) {
+            showToast(getResources().getString(R.string.info_stored_image) + "\n" + imageUri);
         }
 
         loadImagePreview();
     }
 
+    private void showFailure() {
+        showToast(getResources().getString(R.string.err_aquire_image_failed));
+    }
+
     private void loadImagePreview() {
+        if (imageUri == null) {
+            return;
+        }
         if (!new File(imageUri.getPath()).exists()) {
             Log.i("Image does not exist");
             return;
@@ -225,7 +253,11 @@ public class ImageSelectActivity extends AbstractActivity {
     }
 
     private static Uri getOutputImageFileUri() {
-        return Uri.fromFile(getOutputImageFile());
+        final File file = getOutputImageFile();
+        if (file == null) {
+            return null;
+        }
+        return Uri.fromFile(file);
     }
 
     /** Create a File for saving an image or video */

fix #2634: NPEs in ImageSelectActivity