FileUriExposedException в Android N с камерой

Основная деятельность

import android.Manifest;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.os.Parcelable;
import android.provider.MediaStore;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.FileProvider;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;

import java.io.File;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private Button mBtn;
    private Context context;
    private static final int SELECT_PICTURE_CAMARA = 101, SELECT_PICTURE = 201, CROP_IMAGE = 301;
    private Uri outputFileUri;
    String mCurrentPhotoPath;
    private Uri selectedImageUri;
    private File finalFile = null;
    private ImageView imageView;
    private PermissionUtil permissionUtil;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    mBtn = (Button) findViewById(R.id.btn_img);
    imageView = (ImageView) findViewById(R.id.img_photo);
    permissionUtil = new PermissionUtil();
    mBtn.setOnClickListener(this);
    context = this;
}

@Override
public void onClick(View view) {
    selectImageOption();
}

private void selectImageOption() {
    final CharSequence[] items = {"Capture Photo", "Choose from Gallery", "Cancel"};

    AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
    builder.setTitle("Add Photo!");
    builder.setItems(items, new DialogInterface.OnClickListener() {
        @Override
        public void onClick(DialogInterface dialog, int item) {
            if (items[item].equals("Capture Photo")) {                
                if (permissionUtil.checkMarshMellowPermission()) {
                    if (permissionUtil.verifyPermissions(MainActivity.this, permissionUtil.getCameraPermissions()))
                        onClickCamera();
                    else
                        ActivityCompat.requestPermissions(MainActivity.this, permissionUtil.getCameraPermissions(), SELECT_PICTURE_CAMARA);
                } else
                    onClickCamera();
            } else if (items[item].equals("Choose from Gallery")) {
                if (permissionUtil.checkMarshMellowPermission()) {
                    if (permissionUtil.verifyPermissions(MainActivity.this, permissionUtil.getGalleryPermissions()))
                        onClickGallery();
                    else
                        ActivityCompat.requestPermissions(MainActivity.this, permissionUtil.getGalleryPermissions(), SELECT_PICTURE);
                } else
                    onClickGallery();
            } else if (items[item].equals("Cancel")) {
                dialog.dismiss();
            }
        }
    });
    builder.show();
}

public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if (resultCode == RESULT_OK) {

        if (requestCode == SELECT_PICTURE) {

            selectedImageUri = data.getData();
            cropImage(selectedImageUri);

        } else if (requestCode == CROP_IMAGE) {
            /*if (data != null) {
                // get the returned data
                Bundle extras = data.getExtras();
                // get the cropped bitmap
                Bitmap selectedBitmap = extras.getParcelable("data");

                imageView.setImageBitmap(selectedBitmap);
            }*/

            Uri imageUri = Uri.parse(mCurrentPhotoPath);
            File file = new File(imageUri.getPath());
            try {
                InputStream ims = new FileInputStream(file);
                imageView.setImageBitmap(BitmapFactory.decodeStream(ims));
            } catch (FileNotFoundException e) {
                return;
            }

        } else if (requestCode == SELECT_PICTURE_CAMARA && resultCode == Activity.RESULT_OK) {
            cropImage(Uri.parse(mCurrentPhotoPath));

        }
    }
}

private void onClickCamera() {
    Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);

    if (takePictureIntent.resolveActivity(context.getPackageManager()) != null) {
        File photoFile = null;
        try {
            photoFile = createImageFile();
        } catch (IOException ex) {
        }
        if (photoFile != null) {

            Uri photoURI;
            if (Build.VERSION.SDK_INT >= 24) {
                photoURI = FileProvider.getUriForFile(MainActivity.this,
                        BuildConfig.APPLICATION_ID + ".provider", photoFile);
            } else {
                photoURI = Uri.fromFile(photoFile);
            }

            takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
            startActivityForResult(takePictureIntent, SELECT_PICTURE_CAMARA);

        }
    }
}

private void onClickGallery() {
    List<Intent> targets = new ArrayList<>();
    Intent intent = new Intent();
    intent.setType("image/*");
    intent.setAction(Intent.ACTION_PICK);
    intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
    List<ResolveInfo> candidates = getApplicationContext().getPackageManager().queryIntentActivities(intent, 0);

    for (ResolveInfo candidate : candidates) {
        String packageName = candidate.activityInfo.packageName;
        if (!packageName.equals("com.google.android.apps.photos") && !packageName.equals("com.google.android.apps.plus") && !packageName.equals("com.android.documentsui")) {
            Intent iWantThis = new Intent();
            iWantThis.setType("image/*");
            iWantThis.setAction(Intent.ACTION_PICK);
            iWantThis.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
            iWantThis.setPackage(packageName);
            targets.add(iWantThis);
        }
    }
    if (targets.size() > 0) {
        Intent chooser = Intent.createChooser(targets.remove(0), "Select Picture");
        chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, targets.toArray(new Parcelable[targets.size()]));
        startActivityForResult(chooser, SELECT_PICTURE);
    } else {
        Intent intent1 = new Intent(Intent.ACTION_PICK);
        intent1.setType("image/*");
        startActivityForResult(Intent.createChooser(intent1, "Select Picture"), SELECT_PICTURE);
    }
}

private File createImageFile() throws IOException {
    // Create an image file name
    String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
    String imageFileName = "JPEG_" + timeStamp + "_";

    File storageDir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
    File image = File.createTempFile(
            imageFileName,  /* prefix */
            ".jpg",         /* suffix */
            storageDir      /* directory */
    );

    // Save a file: path for use with ACTION_VIEW intents
    if (Build.VERSION.SDK_INT >= 24) {
        mCurrentPhotoPath = String.valueOf(FileProvider.getUriForFile(MainActivity.this,
                BuildConfig.APPLICATION_ID + ".provider", image));
    } else {
        mCurrentPhotoPath = String.valueOf(Uri.fromFile(image));
    }

    return image;
}

private void cropImage(Uri selectedImageUri) {
    Intent cropIntent = new Intent("com.android.camera.action.CROP");

    cropIntent.setDataAndType(selectedImageUri, "image/*");

    cropIntent.putExtra("crop", "true");
    cropIntent.putExtra("aspectX", 1);
    cropIntent.putExtra("aspectY", 1.5);
    cropIntent.putExtra("return-data", true);

    outputFileUri = Uri.fromFile(createCropFile());

    cropIntent.putExtra(MediaStore.EXTRA_OUTPUT, outputFileUri);
    startActivityForResult(cropIntent, CROP_IMAGE);
}

private File createCropFile() {
    File storageDir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
    String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
    //  path = path + (timeStamp + "1jpg");
    File file = null;
    try {
        file = File.createTempFile(timeStamp, ".jpg", storageDir);
    } catch (IOException e) {
        e.printStackTrace();
    }

    mCurrentPhotoPath = String.valueOf(Uri.fromFile(file));
    return file;
}
}

PermissionUtil.java

package com.example.shwetachauhan.imagecropasoebi;

import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import android.support.v4.app.ActivityCompat;

public class PermissionUtil {
    private String[] galleryPermissions = {
        "android.permission.WRITE_EXTERNAL_STORAGE",
        "android.permission.READ_EXTERNAL_STORAGE"
};

private String[] cameraPermissions = {
        "android.permission.CAMERA",
        "android.permission.WRITE_EXTERNAL_STORAGE",
        "android.permission.READ_EXTERNAL_STORAGE"
};

public String[] getGalleryPermissions(){
    return galleryPermissions;
}

public String[] getCameraPermissions() {
    return cameraPermissions;
}

public boolean verifyPermissions(int[] grantResults) {
    if(grantResults.length < 1){
        return false;
    }

    for (int result : grantResults) {
        if (result != PackageManager.PERMISSION_GRANTED) {
            return false;
        }
    }
    return true;
}

public boolean verifyPermissions(Context context, String[] grantResults) {
    for (String result : grantResults) {
        if (ActivityCompat.checkSelfPermission(context, result) != PackageManager.PERMISSION_GRANTED) {
            return false;
        }
    }
    return true;
}

public boolean checkMarshMellowPermission(){
    return(Build.VERSION.SDK_INT> Build.VERSION_CODES.LOLLIPOP_MR1);
}

public boolean checkJellyBean(){
    return(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN);
}
}
  • Этот код предназначен для выбора изображения для обрезки из камеры или галереи
  • Этот код работает для всех ОС Android, но когда я пытаюсь запустить на устройстве Android N, он падает при открытии камеры. Он отлично работает в Android N с Gallery.

Ответ 1

Я также столкнулся с той же проблемой в устройствах Android N. Но я решил это.

Вот мой код, который может решить проблему:

 public void launchCamera() {
    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
        intent.putExtra(MediaStore.EXTRA_OUTPUT, getPhotoFileUri());
    } else {
        File file = new File(getPhotoFileUri().getPath());
        Uri photoUri = FileProvider.getUriForFile(getApplicationContext(), getApplicationContext().getPackageName() + ".provider", file);
        intent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
    }
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    if (intent.resolveActivity(getApplicationContext().getPackageManager()) != null) {
        startActivityForResult(intent, REQUEST_CAMERA);
    }
}

После этого вам нужно создать папку XML в res, и в этой папке вам нужно создать xml помеченный provider_paths.xml

Код в provider_paths.xml

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>
</paths>

Позже в манифесте вам нужно добавить следующее внутри тега приложения и убедиться, что compileSdkVersion> = 24

<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.provider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
</provider>

Вот две справочные ссылки, которые помогут вам лучше понять.

Ссылки:

link1 - от среднего сотрудничества

link2 - от inthecheesefactory

Ответ 2

Если вы не трогаете другие приложения в системе, просто установите

StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
StrictMode.setVmPolicy(builder.build());

в Application.onCreate(). Он будет игнорировать экспозицию URI, и вам хорошо идти.

Ответ 3

Начиная с Android N, Android изменил способ предоставления URI файла. Использование файла://uri запрещен и выкинет это. Используйте FileProvider для преодоления этого.

Передача файла://URI за пределами домена пакета могут оставить приемник с недостижимым путем. Поэтому попытки передать файл://URI запускают FileUriExposedException. Рекомендуемым способом совместного использования содержимого частного файла является использование FileProvider.

Более подробную информацию можно найти здесь

Ответ 4

Вы можете использовать StrictMode, добавить ниже код в свою активность onCreate()

    StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
    StrictMode.setVmPolicy(builder.build());

Ответ 5

изменить ur buildToolsVersion

 buildToolsVersion "25.0.1"

в

 buildToolsVersion "23.0.1"