I am trying to use camera and save image. Followed the steps as commonsware suggested. Constantly I am getting error -
2018-11-12 02:10:54.588 3145-3173/com.bisw.weac E/DatabaseUtils: Writing exception to parcel java.lang.SecurityException: Permission Denial: writing android.support.v4.content.FileProvider uri content://com.bisw.weac.provider/external_files/Android/data/com.bisw.weac/files/wallpaper/theme.jpg from pid=5566, uid=10071 requires the provider be exported, or grantUriPermission() at android.content.ContentProvider.enforceWritePermissionInner(ContentProvider.java:713) at android.content.ContentProvider$Transport.enforceWritePermission(ContentProvider.java:519) at android.content.ContentProvider$Transport.enforceFilePermission(ContentProvider.java:491) at android.content.ContentProvider$Transport.openAssetFile(ContentProvider.java:389) at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:251) at android.os.Binder.execTransact(Binder.java:682)
I have tried almost everything like - intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); but nothing helps. I am croping the image from camera. Activity code
public class LocalAlbumActivity extends BaseActivity implements View.OnClickListener { private static final int REQUEST_IMAGE_CAPTURE_THEME = 1; private static final int REQUEST_IMAGE_CAPTURE_QRCODE_LOGO = 4; private static final int REQUEST_IMAGE_CROP_THEME = 2; private static final int REQUEST_IMAGE_CROP_QRCODE_LOGO = 5; private static final int REQUEST_ALBUM_DETAIL = 3; public static final String ALBUM_PATH = "album_path"; public static final String ALBUM_NAME = "album_name"; private LocalAlbumAdapter mLocalAlbumAdapter; private List<ImageBucket> mLocalAlbumList; private AsyncTask<Void, Void, List<ImageBucket>> mBucketLoadTask; private ListView mLocalAlbumListView; /** * 访问本地相册类型:0,主题;1,扫码;2,造码 */ private int mRequestType; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); OttoAppConfig.getInstance().register(this); setContentView(R.layout.activity_local_album); ViewGroup backGround = (ViewGroup) findViewById(R.id.background); MyUtil.setBackgroundBlur(backGround, this); initAdapter(); assignViews(); } private void initAdapter() { mLocalAlbumList = new ArrayList<>(); mLocalAlbumAdapter = new LocalAlbumAdapter(this, mLocalAlbumList); //trying permission mBucketLoadTask = new AsyncTask<Void, Void, List<ImageBucket>>() { @Override protected void onPreExecute() { super.onPreExecute(); // showLoading(); } @Override protected List<ImageBucket> doInBackground(Void... params) { return LocalAlbumImagePickerHelper.getInstance(LocalAlbumActivity.this) .getImagesBucketList(); } @Override protected void onPostExecute(List<ImageBucket> list) { dismissLoadingDialog(); TextView emptyView = (TextView) findViewById(R.id.local_album_lv_empty); mLocalAlbumListView.setEmptyView(emptyView); mLocalAlbumList.addAll(list); mLocalAlbumAdapter.notifyDataSetChanged(); } }; mBucketLoadTask.execute(); } private void dismissLoadingDialog() { ViewGroup progressBarLlyt = (ViewGroup) findViewById(R.id.progress_bar_llyt); progressBarLlyt.setVisibility(View.GONE); } private void assignViews() { TextView loadingMsg = (TextView) findViewById(R.id.loading_msg); loadingMsg.setText(R.string.scanning); ImageView backBtn = (ImageView) findViewById(R.id.action_back); TextView captureBtn = (TextView) findViewById(R.id.action_capture); backBtn.setOnClickListener(this); mRequestType = getIntent().getIntExtra(WeacConstants.REQUEST_LOCAL_ALBUM_TYPE, 0); switch (mRequestType) { // 主题 case 0: // 造码 case 2: captureBtn.setOnClickListener(this); break; // 扫码 case 1: // 隐藏拍照按钮 captureBtn.setVisibility(View.GONE); break; } mLocalAlbumListView = (ListView) findViewById(R.id.local_album_lv); mLocalAlbumListView.setAdapter(mLocalAlbumAdapter); mLocalAlbumListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { if (MyUtil.isFastDoubleClick()) { return; } Intent intent = new Intent(LocalAlbumActivity.this, LocalAlbumDetailActivity.class); intent.putParcelableArrayListExtra(ALBUM_PATH, mLocalAlbumAdapter.getItem(position).bucketList); intent.putExtra(ALBUM_NAME, mLocalAlbumAdapter.getItem(position).bucketName); intent.putExtra(WeacConstants.REQUEST_LOCAL_ALBUM_TYPE, mRequestType); startActivityForResult(intent, REQUEST_ALBUM_DETAIL); } }); // OverScrollDecoratorHelper.setUpOverScroll(mLocalAlbumListView); } private Uri mImageUri; @Override public void onClick(View v) { switch (v.getId()) { // 返回 case R.id.action_back: myFinish(); break; // 拍照 case R.id.action_capture: PackageManager pm = getPackageManager(); // FEATURE_CAMERA - 后置相机 // FEATURE_CAMERA_FRONT - 前置相机 if (pm.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)) { // 访问相机类型 int requestType; // 截取主题壁纸 if (mRequestType != 2) { requestType = REQUEST_IMAGE_CAPTURE_THEME; } else { // 截取二维码logo requestType = REQUEST_IMAGE_CAPTURE_QRCODE_LOGO; } Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); //mImageUri = Uri.fromFile(MyUtil.getFileDirectory(this, "/Android/data/" + // getPackageName() + "/capture/temporary.jpg")); mImageUri = FileProvider.getUriForFile(this,BuildConfig.APPLICATION_ID+".provider",MyUtil.getFileDirectory(this, "/Android/data/" + getPackageName() + "/capture/temporary.jpg")); this.grantUriPermission(getPackageName(),mImageUri,Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.putExtra(MediaStore.EXTRA_OUTPUT, mImageUri); startActivityForResult(intent, requestType); overridePendingTransition(0, R.anim.zoomin); } else { // 没有可用相机 Intent intent = new Intent(this, MyDialogActivitySingle.class); intent.putExtra(WeacConstants.TITLE, getString(R.string.prompt)); intent.putExtra(WeacConstants.DETAIL, getString(R.string.camera_error)); startActivity(intent); } break; } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode != RESULT_OK) { // 截图/相机返回 overridePendingTransition(0, R.anim.zoomout); return; } // 扫描二维码相册详细取消 if (data != null) { boolean isFinishMe = data.getBooleanExtra(LocalAlbumDetailActivity.FINISH_ACTIVITY, false); if (isFinishMe && !isFinishing()) { myFinish2(); return; } } switch (requestCode) { // 拍照(截取主题壁纸) case REQUEST_IMAGE_CAPTURE_THEME: cropImage(0, REQUEST_IMAGE_CROP_THEME, WeacConstants.DIY_WALLPAPER_PATH); break; // 拍照(截取二维码logo) case REQUEST_IMAGE_CAPTURE_QRCODE_LOGO: cropImage(1, REQUEST_IMAGE_CROP_QRCODE_LOGO, WeacConstants.DIY_QRCODE_LOGO_PATH); break; // 截图(截取主题壁纸) case REQUEST_IMAGE_CROP_THEME: String filePath = MyUtil.getFilePath(this, WeacConstants.DIY_WALLPAPER_PATH); // 更新壁纸信息 MyUtil.saveWallpaper(this, WeacConstants.WALLPAPER_PATH, filePath); // 发送壁纸更新事件 OttoAppConfig.getInstance().post(new WallpaperEvent()); myFinish(); break; // 截图(截取二维码logo) case REQUEST_IMAGE_CROP_QRCODE_LOGO: String logoPath = MyUtil.getFilePath(this, WeacConstants.DIY_QRCODE_LOGO_PATH); // 保存自定义二维码logo地址 MyUtil.saveQRcodeLogoPath(this, logoPath); // 发送自定义二维码logo截取地址事件 OttoAppConfig.getInstance().post(new QRcodeLogoEvent(logoPath)); myFinish(); break; // 相册详细图片 case REQUEST_ALBUM_DETAIL: assert data != null; String url = data.getStringExtra(WeacConstants.IMAGE_URL); OttoAppConfig.getInstance().post(new ScanCodeEvent(url)); myFinish2(); break; } } private void cropImage(int type, int requestType, String path) { ToastUtil.showLongToast(this, "path:"+path); Intent intent = MyUtil.getCropImageOptions(this, mImageUri, path, type); intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); if (intent.resolveActivity(getPackageManager()) != null) { startActivityForResult(intent, requestType); overridePendingTransition(0, 0); } else { // 不可以复制其他应用的内部文件 // TODO: 全屏裁剪&自定义裁剪功能 ToastUtil.showLongToast(this, getString(R.string.no_crop_action)); } } @Subscribe public void finishMeEvent(FinishLocalAlbumActivityEvent event) { myFinish2(); } @Override public void onBackPressed() { myFinish(); } private void myFinish() { finish(); if (mRequestType != 2) { overridePendingTransition(0, R.anim.zoomout); } else { overridePendingTransition(0, R.anim.move_out_bottom); } } private void myFinish2() { finish(); overridePendingTransition(0, 0); } @Override protected void onDestroy() { super.onDestroy(); OttoAppConfig.getInstance().unregister(this); if (null != mBucketLoadTask && mBucketLoadTask.getStatus() == AsyncTask.Status.RUNNING) { mBucketLoadTask.cancel(true); } } } Helper Methods
/** * Returns specified directory(/mnt/sdcard/...). * directory will be created on SD card by defined path if card * is mounted. Else - Android defines files directory on device's * files(/data/data/<application package>/files) system. * * @param context context * @param path file path (e.g.: "/AppDir/a.mp3", "/AppDir/files/images/a.jp") * @return File {@link File directory} */ public static File getFileDirectory(Context context, String path) { File file = null; if (isHasSDCard()) { file = new File(Environment.getExternalStorageDirectory(), path); if (!file.getParentFile().exists()) { if (!file.getParentFile().mkdirs()) { file = null; } } } if (file == null) { // 使用内部缓存[MediaStore.EXTRA_OUTPUT ("output")]是无法正确写入裁切后的图片的。 // 系统是用全局的ContentResolver来做这个过程的文件io操作,app内部的存储被忽略。(猜测) file = new File(context.getFilesDir(), path); } return file; } /** * Returns specified directory(/mnt/sdcard/Android/data/<application package>/files/...). * directory will be created on SD card by defined path if card * is mounted. Else - Android defines files directory on device's * files(/data/data/<application package>/files) system. * * @param context context * @param path file path (e.g.: "/music/a.mp3", "/pictures/a.jpg") * @return File {@link File directory} */ public static File getExternalFileDirectory(Context context, String path) { File file = null; if (isHasSDCard()) { file = new File(context.getExternalFilesDir(null), path); if (!file.getParentFile().exists()) { if (!file.getParentFile().mkdirs()) { file = null; } } } if (file == null) { // 使用内部缓存[MediaStore.EXTRA_OUTPUT ("output")]是无法正确写入裁切后的图片的。 // 系统是用全局的ContentResolver来做这个过程的文件io操作,app内部的存储被忽略。(猜测) file = new File(context.getFilesDir(), path); } return file; } public static boolean isHasSDCard() { return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()); } /** * Returns directory absolutePath. * * @param context context * @param path file path (e.g.: "/AppDir/a.mp3", "/AppDir/files/images/a.jpg") * @return /mnt/sdcard/Android/data/<application package>/files/.... */ public static String getFilePath(Context context, String path) { return getExternalFileDirectory(context, path).getAbsolutePath(); } /** * set intent options * * @param context context * @param uri image path uri * @param filePath save path (e.g.: "/AppDir/a.mp3", "/AppDir/files/images/a.jpg") * @param type 0,截取壁纸/拍照;1,截取Logo * @return Intent */ public static Intent getCropImageOptions(Context context, Uri uri, String filePath, int type) { int width; int height; // 截取壁纸/拍照 if (type == 0) { width = context.getResources().getDisplayMetrics().widthPixels; height = context.getResources().getDisplayMetrics().heightPixels; } else { // 截取logo width = height = dip2px(context, 30); } //filePath="/Internal storage/Download/a.jpg"; LogUtil.e(LOG_TAG, " filePath:" + filePath ); Intent intent = new Intent(); intent.setAction("com.android.camera.action.CROP"); intent.setDataAndType(uri, "image/*"); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); intent.putExtra("crop", "true"); // 裁剪框比例 intent.putExtra("aspectX", width); intent.putExtra("aspectY", height); // 保存路径 //#ToDO: Uri.fromFile change to FileProvider.getUriForFile Uri mImageUri; //mImageUri=Uri.fromFile(getExternalFileDirectory(context, filePath)); intent.putExtra(MediaStore.EXTRA_OUTPUT, FileProvider.getUriForFile(context,BuildConfig.APPLICATION_ID+".provider",getExternalFileDirectory(context, filePath))); // 是否去除面部检测 intent.putExtra("noFaceDetection", true); intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); // 是否保留比例 intent.putExtra("scale", true); intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString()); // 裁剪区的宽高 intent.putExtra("outputX", width); intent.putExtra("outputY", height); // 是否将数据保留在Bitmap中返回 intent.putExtra("return-data", false); return intent; }