0

My app has functionality for allowing the user to either select a photo or video from their current library, or to take a new one, and then attach to an email. It seems to be working on the majority of devices and Android versions. However, I'm seeing some crashes intermittently for some device setups when getting the uri to the image or video to attach to the email. An example crash is: Fatal Exception: java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=67425, result=-1, data=Intent { dat=content://com.google.android.apps.docs.storage/document/acc=1;doc=3 flg=0x1 }} to activity: java.lang.NullPointerException: Attempt to invoke virtual method 'char[] java.lang.String.toCharArray()' on a null object reference. This happens on the "File file = new File(imagePath);" line in the email() method below. Are there changes I can make to my code to allow for a more universal solution to this? My minSdkVersion is 16, and my targetSdkVersion is 23.

Below is my code for the implementation:

    private enum Type {
        PHOTO,
        VIDEO
    }
    private enum Source {
        LIBRARY,
        CAMERA
    }

    private void select(Type type) {
        if(selectedSource == Source.LIBRARY) {
            // choose from library
            Intent getIntent = new Intent(Intent.ACTION_GET_CONTENT);
            if (type == Type.PHOTO) {
                getIntent.setType("image/*");
            } else {
                getIntent.setType("video/*");
            }

            Intent pickIntent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
            if (type == Type.PHOTO) {
                pickIntent.setType("image/*");
            } else {
                pickIntent.setType("video/*");
            }

            Intent chooserIntent = Intent.createChooser(getIntent, "Select Photo/Video");
            chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[]{pickIntent});

            startActivityForResult(chooserIntent, CHOOSE_IMAGE_VIDEO_ACTIVITY_REQUEST_CODE);
        } else if(selectedSource == Source.CAMERA) {
            // take photo/video from camera
            // check for camera permission
            if ( ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED ) {
                requestPermissions(new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA);
            }
            if (Build.VERSION.SDK_INT >= 23 && ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
                //Toast.makeText(getActivity(), "You need to allow camera access", Toast.LENGTH_LONG).show();
                return;
            } else {
                if (getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)) {
                    if ( ContextCompat.checkSelfPermission(getActivity(), READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED ) {
                        requestPermissions(new String[]{READ_EXTERNAL_STORAGE}, REQUEST_EXTERNAL_STORAGE);
                    }
                    if (Build.VERSION.SDK_INT >= 23 && ContextCompat.checkSelfPermission(getActivity(), READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                        //Toast.makeText(getActivity(), "You need to allow external storage access", Toast.LENGTH_LONG).show();
                        return;
                    } else {
                        if (selectedType == Type.PHOTO) {
                            Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                            if (intent.resolveActivity(getActivity().getPackageManager()) != null) {
                                File photo = null;
                                try {
                                    // place where to store camera taken picture
                                    photo = this.createTemporaryFile("photo", ".jpg");
                                    photo.delete();
                                } catch (Exception e) {
                                    //Log.v(TAG, "Can't create file to take picture!");
                                    Toast.makeText(getActivity(), "Please check SD card! Image shot is impossible!", Toast.LENGTH_LONG).show();
                                }
                                mImageUri = Uri.fromFile(photo);
                                intent.putExtra(MediaStore.EXTRA_OUTPUT, mImageUri);
                                startActivityForResult(intent, TAKE_IMAGE_ACTIVITY_REQUEST_CODE);
                            } else {
                                Toast.makeText(getActivity(), "Unable to access the camera", Toast.LENGTH_LONG).show();
                            }
                        } else if (selectedType == Type.VIDEO) {
                            Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
                            if (intent.resolveActivity(getActivity().getPackageManager()) != null) {
                                File video = null;
                                try {
                                    // place where to store camera taken video
                                    video = this.createTemporaryFile("video", ".mp4");
                                    video.delete();
                                } catch (Exception e) {
                                    //Log.v(TAG, "Can't create file to take picture!");
                                    Toast.makeText(getActivity(), "Please check SD card! Video is impossible!", Toast.LENGTH_LONG).show();
                                }
                                mImageUri = Uri.fromFile(video);
                                intent.putExtra(MediaStore.EXTRA_OUTPUT, mImageUri);
                                startActivityForResult(intent, TAKE_VIDEO_ACTIVITY_REQUEST_CODE);
                            } else {
                                Toast.makeText(getActivity(), "Unable to access the camera", Toast.LENGTH_LONG).show();
                            }
                        }
                    }
                } else {
                    Toast.makeText(getActivity(), "No camera available", Toast.LENGTH_LONG).show();
                }
            }
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        switch (requestCode) {
            case REQUEST_CAMERA: {
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    System.out.println("REQUEST CAMERA RESULT");
                    select(selectedType);
                } else {
                    //Permission denied
                    Toast.makeText(getActivity(), "You need to allow camera access", Toast.LENGTH_LONG).show();
                }
                return;
            }
            case REQUEST_EXTERNAL_STORAGE: {
                if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    System.out.println("REQUEST EXTERNAL STORAGE RESULT");
                    select(selectedType);
                } else {
                    //Permission denied
                    Toast.makeText(getActivity(), "You need to allow external storage access", Toast.LENGTH_LONG).show();
                }
                return;
            }
        }
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == CHOOSE_IMAGE_VIDEO_ACTIVITY_REQUEST_CODE) {
            // from image picker
            if (resultCode == Activity.RESULT_OK) {
                if(data != null) {
                    //InputStream inputStream = getActivity().getContentResolver().openInputStream(data.getData());
                    mImageUri = data.getData();
                    imagePath = getPath(getActivity(), mImageUri);

                    email();
                }
            }
        } else if(requestCode == TAKE_IMAGE_ACTIVITY_REQUEST_CODE || requestCode == TAKE_VIDEO_ACTIVITY_REQUEST_CODE) {
            if (resultCode == Activity.RESULT_OK) {
                grabImageOrVideoTaken();
            }
        }
    }

    private void grabImageOrVideoTaken() {
        getActivity().getContentResolver().notifyChange(mImageUri, null);
        imagePath = getPath(getActivity(), mImageUri);
        email();
    }

    public static String getPath(final Context context, final Uri uri) {

        final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;

        // DocumentProvider
        if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
            // ExternalStorageProvider
            if (isExternalStorageDocument(uri)) {
                final String docId = DocumentsContract.getDocumentId(uri);
                final String[] split = docId.split(":");
                final String type = split[0];

                if ("primary".equalsIgnoreCase(type)) {
                    return Environment.getExternalStorageDirectory() + "/" + split[1];
                }

                // TODO handle non-primary volumes
            }
            // DownloadsProvider
            else if (isDownloadsDocument(uri)) {

                final String id = DocumentsContract.getDocumentId(uri);
                final Uri contentUri = ContentUris.withAppendedId(
                        Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));

                return getDataColumn(context, contentUri, null, null);
            }
            // MediaProvider
            else if (isMediaDocument(uri)) {
                final String docId = DocumentsContract.getDocumentId(uri);
                final String[] split = docId.split(":");
                final String type = split[0];

                Uri contentUri = null;
                if ("image".equals(type)) {
                    contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
                } else if ("video".equals(type)) {
                    contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
                } else if ("audio".equals(type)) {
                    contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
                }

                final String selection = "_id=?";
                final String[] selectionArgs = new String[] {
                        split[1]
                };

                return getDataColumn(context, contentUri, selection, selectionArgs);
            }
        }
        // MediaStore (and general)
        else if ("content".equalsIgnoreCase(uri.getScheme())) {
            return getDataColumn(context, uri, null, null);
        }
        // File
        else if ("file".equalsIgnoreCase(uri.getScheme())) {
            return uri.getPath();
        }

        return null;
    }

    /**
     * Get the value of the data column for this Uri. This is useful for
     * MediaStore Uris, and other file-based ContentProviders.
     *
     * @param context The context.
     * @param uri The Uri to query.
     * @param selection (Optional) Filter used in the query.
     * @param selectionArgs (Optional) Selection arguments used in the query.
     * @return The value of the _data column, which is typically a file path.
     */
    public static String getDataColumn(Context context, Uri uri, String selection,
                                       String[] selectionArgs) {

        Cursor cursor = null;
        final String column = "_data";
        final String[] projection = {
                column
        };

        try {
            cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
                    null);
            if (cursor != null && cursor.moveToFirst()) {
                final int column_index = cursor.getColumnIndexOrThrow(column);
                return cursor.getString(column_index);
            }
        } finally {
            if (cursor != null)
                cursor.close();
        }
        return null;
    }

    private void email() {
            String mediaType = "photo";
            if(selectedType == Type.VIDEO) {
                mediaType = "video";
            }
            String email = "test@test.com";
            Intent intent = new Intent(Intent.ACTION_SEND, Uri.fromParts("mailto", email, null));
            intent.putExtra(Intent.EXTRA_SUBJECT, getResources().getString(R.string.app_name) + ": Photo/Video Submission");
            intent.putExtra(Intent.EXTRA_EMAIL, new String[]{email});
            File file = new File(imagePath);
            intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            if (selectedType == Type.PHOTO) {
                intent.setType("image/jpeg");
            } else {
                intent.setType("video/3gp");
            }
            intent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + file.getAbsolutePath()));
            startActivity(Intent.createChooser(intent, "Submit Photo/Video"));
    }
codeman
  • 8,868
  • 13
  • 50
  • 79

1 Answers1

0

Are there changes I can make to my code to allow for a more universal solution to this?

Sure. Get rid of getPath().

It seems to be working on the majority of devices and Android versions.

Not really.

Use the Uri properly. It is not a file. Even in the few cases where it does point to a file, you may not have access to that file via the filesystem.

You say that you are using this for an email attachment. If that is ACTION_SEND with a EXTRA_STREAM extra, use the Uri that you were given. Add FLAG_GRANT_READ_URI_PERMISSION on the ACTION_SEND Intent, so that the read permissions that you were granted get sent along to the ACTION_SEND handler.

If you are using something else for the email, use a ContentResolver and openInputStream() to get an InputStream on the file or content Uri. Then, either pass that stream to the other code, or use that stream to make your own local file copy (e.g., in getCacheDir()), and then use that local copy, deleting it when you do not need it.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491