Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Intent chooser stays open when using a Thread to get an image resource

I am building a photo upload form which has to grab an image either from the camera or gallery or whatever other resource is available (Dropbox/Google Drive/Astro File Manager, etc.). I think I have it all working except for one small component.

When the selected image is coming from a network resource, such as Google Drive, it can take a long time to return back to the activity. I would like to make this asynchronous to avoid having the user stare at a black screen while the image downloads.

There's a lot of code that I've written to get this whole thing to work, but here's the general process of it:

  1. The Activity starts and displays the form.

  2. The Intent chooser automatically opens and asks the user to select an image from either the camera or another app.

  3. The user selects some app such as Google Drive, selects an image and that app closes, returning the selected image Uri as the result.

  4. Cache the contents of the file to workaround KitKat permissions which can block from re-reading the image if the Activity closes and needs to resume.

  5. Asynchronously display the scaled bitmap using the cached file as the source.

  6. The Intent chooser closes, the image is displayed in an ImageButton and the form is visible to the user to be filled out before submission.

  7. Once the submit button is tapped, an asynchronous task starts which allows for the multipart upload to the server.

  8. The asynchronous upload uses a callback function to notify the user of the upload progress.

I'm getting stuck in the transition between #3 and #4.

If I use a Thread or AsyncTask The Intent chooser will remain open, but the image will also be displayed on the form.

If I simply force the user to wait (serially) The user is stuck looking at a black screen, then the Intent Chooser closes and the image is displayed on the form.

Opening the Intent chooser

/**
 * see http://stackoverflow.com/a/12347567/940217
 */
private void openImageIntent() {

    // Camera.
    final List<Intent> cameraIntents = new ArrayList<Intent>();
    final PackageManager packageManager = getPackageManager();
    Intent captureIntent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);

    File photoFile;
    try {
        uploadableImage.makeCameraTempFile();
        photoFile = uploadableImage.getCameraTempFile();
        uploadableImage.setCameraFilepath(photoFile.getAbsolutePath());
    } catch (IOException ex) {
        // Error occurred while creating the File
        photoFile = null;
        uploadableImage.invalidate();
        Log.e(TAG, "Error while creating the temporary file needed for image capture from camera.");
        ex.printStackTrace();
    }
    // Continue only if the File was successfully created
    if (photoFile != null && packageManager != null) {
        final List<ResolveInfo> listCam = packageManager.queryIntentActivities(captureIntent, 0);
        for (ResolveInfo res : listCam) {
            // Create the File where the photo should go
            if (res.activityInfo == null) {
                continue;
            }
            final String packageName = res.activityInfo.packageName;
            final Intent intent = new Intent(captureIntent);
            intent.setComponent(new ComponentName(res.activityInfo.packageName, res.activityInfo.name));
            intent.setPackage(packageName);
            intent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile));
            cameraIntents.add(intent);
        }
    } else {
        Toast.makeText(this, "Could not make temporary file.", Toast.LENGTH_LONG).show();
    }

    // Filesystem.
    final Intent galleryIntent = new Intent();
    galleryIntent.setType("image/*");
    galleryIntent.setAction(Intent.ACTION_GET_CONTENT);

    // Chooser of filesystem options.
    final Intent chooserIntent = Intent.createChooser(galleryIntent, "Select Source");

    // Add the camera options.
    chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, cameraIntents.toArray(new Parcelable[cameraIntents.size()]));
    startActivityForResult(chooserIntent, REQUEST_IMAGE_CAPTURE);
}

onActivityResult

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (resultCode == RESULT_OK && requestCode == REQUEST_IMAGE_CAPTURE) {
        if (tempUpFile != null){
            tempUpFile.invalidate();
        }
        if (data == null) {
            Log.d(TAG, "Data was null -- source was from camera");
            uploadableImage.preserveCameraFile();
            setPic();
        } else {

            Uri selectedImageUri = data.getData();
            if (selectedImageUri == null) {
                Log.e(TAG, "An error that should never occur has occurred -- selectedImageUri was null when trying to get the data from the Image Capture activity result.");
                Toast.makeText(this, "Unexpected error when trying to get an image from the gallery or file manager.", Toast.LENGTH_LONG).show();
                return;
            }
            uploadableImage.setSourceImageUri(selectedImageUri, imageButton);
            setPic();  // only do this if doing this serially.
        }
    } else if (resultCode == RESULT_CANCELED){
        uploadableImage.invalidate();
        if (tempUpFile != null){
            uploadableImage = tempUpFile;
        }
        setPic();
    }
}

UploadableImage#setSourceImageUri

public void setSourceImageUri(Uri selectedImageUri, final ImageView iView) {

    // Important!! - clean up after ourselves!
    deleteFile(this.cameraFile);
    this.cameraFile = null;

    InputStream tempInStream = null;
    OutputStream tempOutStream = null;
    try {
        this.cacheFile = createFile();
        tempInStream = context.getContentResolver().openInputStream(selectedImageUri);
        tempOutStream = new FileOutputStream(this.cacheFile);
    } catch (IOException e) {
        Toast.makeText(context, "Error while trying to read the selected image.", Toast.LENGTH_LONG).show();
        Log.e(TAG, "IOException while trying to copy the selected image to our cache file");
        e.printStackTrace();
    }

    if (tempInStream == null || tempOutStream == null){
        Toast.makeText(context, "Error while trying to "+(tempInStream == null ? "read" :"store")+" the selected image.", Toast.LENGTH_LONG).show();
        Log.e(TAG, "Error while trying to get the " + (tempInStream == null ? "input" : "output") + " stream");
        return;
    }

    // Works, but makes the UI hang on a black screen while waiting for the resource.
    try {
        copy(tempInStream, tempOutStream);
    }catch (IOException e){
        Toast.makeText(context, "Error while trying to read the selected image.", Toast.LENGTH_LONG).show();
        Log.e(TAG, "IOException while trying to copy the selected image to our cache file");
        e.printStackTrace();
    }

    /*
    // Works, but leaves the IntentChooser open
    final InputStream inStream = tempInStream;
    final OutputStream outStream = tempOutStream;
    new Thread(new Runnable() {
        public void run() {
            try {
                copy(inStream, outStream);
                // FIXME: somehow notify/display the bitmap in the UI
                // --maybe use Message and Handler? callback Interface?
            } catch (IOException e) {
                Toast.makeText(context, "Error while trying to read the selected image.", Toast.LENGTH_LONG).show();
                Log.e(TAG, "IOException while trying to copy the selected image to our cache file");
                e.printStackTrace();
            }
        }
    }).start();
    */
}

Edit:

I've found that this will/will not occur depending on API level.

Does not occur:

  • KitKat (Nexus 4)
  • ICS MR1 (emulator)

Does occur:

  • KitKat (emulator)
  • ICS MR1 (Galaxy Tab 10.1)

Edit 2:

Here are some screenshots of what should not happen; images are from KitKat emulator.

Start the Activity

Intent chooser opens

Select an image from the gallery or some other resource

Select image from gallery

The onActivityResult returns and the chooser remains open

Intent chooser is open and image is displayed

This is how it should look after onActivityResult

Intent chooser is closed and image is displayed

like image 715
Kyle Falconer Avatar asked Nov 01 '22 22:11

Kyle Falconer


1 Answers

Feeling pretty stupid, but I found the solution.

The issue was in step two: "The Intent chooser automatically opens..."

In order to do this, I was checking to see if a bitmap was available, and if it wasn't then it should display the placeholder image and then open the Intent chooser. I had this logic inside the onStart method which normally gets run after onActivityResult. However, because this was all happening asynchronously, I could not rely on this order.

What I had to do was put in a boolean flag which could tell me if we were waiting for a bitmap, and then not open the chooser if we were.

like image 119
Kyle Falconer Avatar answered Nov 08 '22 04:11

Kyle Falconer