1

I'm trying to follow this tutorial https://developer.android.com/training/camera/photobasics.html#TaskPath for taking an image and saving the file. It opens the camera, I take a picture, click the check mark, then it crashes. I have found several articles on StackOverflow and have tried all of their solutions that I could understand (which were file.mkdirs(), file.getParentFile().mkdirs()).

It seems that, even though it's a RuntimeException, the root problem is the IOException (which I catch and print the message). I'm using Genymotion, not sure if that's relevant or not. What have I missed that is keeping me from being able to create the file?

I have added permissions to my manifest:

<uses-feature android:name="android.hardware.camera"
    android:required="true" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

My button click:

public void onClick(View v) {
    Log.d("DebugMySocks", "Change photo button clicked");
    Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    try {
        // CHANGED THIS
        mImageUri = FileProvider.getUriForFile(getApplicationContext(), getApplicationContext().getPackageName() + "socks", createImageFile());
        takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, mImageUri);
    } catch (Exception e) {
        Log.d("DebugMySocks", "Error creating file " + e.getMessage());
    }
    if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
        startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
    }
}

and the problem code (I think):

  private File createImageFile() throws IOException {
    //ADDED THIS
      int permissionCheck = ContextCompat.checkSelfPermission(this,
                Manifest.permission.WRITE_EXTERNAL_STORAGE);
      if (permissionCheck != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this,
                    new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
                    5);
      }
      // Create an image file name
      String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
      String imageFileName = "JPEG_" + timeStamp + "_";
      File storageDir = Environment.getExternalStorageDirectory();
      Log.d("DebugMySocks", "Storage directory is: " + storageDir);
      File image = new File(Environment.getExternalStorageDirectory() + File.separator + "socks"+ File.separator + imageFileName);
      Log.d("DebugMySocks", "getAbsoluteFile: " + image.getAbsoluteFile());
      image.getParentFile().mkdirs();
      image.mkdirs();
      image.createNewFile();
      //File image = File.createTempFile(
      //        imageFileName,  /* prefix */
      //        ".jpg",         /* suffix */
      //        storageDir      /* directory */
      //);

      // Save a file: path for use with ACTION_VIEW intents
      mCurrentPhotoPath = image.getAbsolutePath();
      return image;
  }

Output from LogCat:

12-14 08:05:01.411 4967-4967/com.example.kevin.moresocksplease D/DebugMySocks: Change photo button clicked
12-14 08:05:01.412 4967-4967/com.example.kevin.moresocksplease D/DebugMySocks: Storage directory is: /storage/emulated/0
12-14 08:05:01.413 4967-4967/com.example.kevin.moresocksplease D/DebugMySocks: getAbsoluteFile: /storage/emulated/0/socks/JPEG_20171214_080501_
12-14 08:05:01.416 4967-4967/com.example.kevin.moresocksplease D/DebugMySocks: Error creating file No such file or directory

And the exception:

FATAL EXCEPTION: main
Process: com.example.kevin.moresocksplease, PID: 4967
java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=111, result=-1, data=Intent { act=inline-data (has extras) }} to activity {com.example.kevin.moresocksplease/com.example.kevin.moresocksplease.EditProfileActivity}: java.lang.NullPointerException: uri
 at android.app.ActivityThread.deliverResults(ActivityThread.java:4089)
 at android.app.ActivityThread.handleSendResult(ActivityThread.java:4132)
 at android.app.ActivityThread.-wrap20(ActivityThread.java)
 at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1533)
 at android.os.Handler.dispatchMessage(Handler.java:102)
 at android.os.Looper.loop(Looper.java:154)
 at android.app.ActivityThread.main(ActivityThread.java:6119)
 at java.lang.reflect.Method.invoke(Native Method)
 at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
 at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
Caused by: java.lang.NullPointerException: uri
 at com.android.internal.util.Preconditions.checkNotNull(Preconditions.java:111)
 at android.content.ContentResolver.notifyChange(ContentResolver.java:1714)
 at android.content.ContentResolver.notifyChange(ContentResolver.java:1693)
 at com.example.kevin.moresocksplease.EditProfileActivity.onActivityResult(EditProfileActivity.java:254)
 at android.app.Activity.dispatchActivityResult(Activity.java:6932)
 at android.app.ActivityThread.deliverResults(ActivityThread.java:4085)
 at android.app.ActivityThread.handleSendResult(ActivityThread.java:4132) 
 at android.app.ActivityThread.-wrap20(ActivityThread.java) 
 at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1533) 
 at android.os.Handler.dispatchMessage(Handler.java:102) 
 at android.os.Looper.loop(Looper.java:154) 
 at android.app.ActivityThread.main(ActivityThread.java:6119) 
 at java.lang.reflect.Method.invoke(Native Method) 
 at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886) 
 at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776) 

Lastly, including because it's in the exception stack:

public void onActivityResult(int requestCode, int resultCode, Intent data) {
    Log.d("DebugMySocks", "onActivityResult in EditProfileActivity");
    if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == this.RESULT_OK) {
        this.getContentResolver().notifyChange(mImageUri, null); // <-- THIS IS LINE 254!
        ContentResolver cr = this.getContentResolver();
        Bitmap bitmap;
        try
        {
            bitmap = android.provider.MediaStore.Images.Media.getBitmap(cr, mImageUri);
            mProfileImage.setImageBitmap(bitmap);
            saveToFirebase(bitmap);
        }
        catch (Exception e)
        {
            Toast.makeText(this, "Failed to load", Toast.LENGTH_SHORT).show();
            Log.d("DebugMySocks", "Failed to load", e);
        }
    }
}

Additional links that I've been drawing information from:

File.createNewFile() thowing IOException No such file or directory

Android Camera Intent: how to get full sized photo?

Edit 1:

After viewing this, I added logs to the mkdir requests and they both return false. According to this gentleman, that means the directories already exist, so it's just the creation of the file that is failing? Creating a directory in /sdcard fails

Log.d("DebugMySocks", "parentfile mkdirs: " + image.getParentFile().mkdirs());
Log.d("DebugMySocks", "image.mkdirs: " + image.mkdirs());
Log.d("DebugMySocks", "create file: " + image.createNewFile());

DebugMySocks: parentfile mkdirs: false
DebugMySocks: image.mkdirs: false
DebugMySocks: Error creating file No such file or directory

Edit 2: I now have:

public void onClick(View v) {
                        Log.d("DebugMySocks", "Change photo button clicked");
                        Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                        try {
                            if (checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)== PackageManager.PERMISSION_GRANTED) {
                                mImageUri = FileProvider.getUriForFile(getApplicationContext(), getApplicationContext().getPackageName() + "socks", createImageFile());
                            } else {
                                Log.v("DebugMySocks","Permission is revoked");
                                ActivityCompat.requestPermissions(getParent(), new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
                            }
                            takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, mImageUri);
                        } catch (Exception e) {
                            Log.d("DebugMySocks", "Error creating file " + e.getMessage());
                        }
                        if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
                            startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
                        }
                    }

    public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
        switch (requestCode) {
            case 0:
                boolean permissionsGranted = true;
                if (grantResults.length > 0 && permissions.length==grantResults.length) {
                    for (int i = 0; i < permissions.length; i++){
                        if (grantResults[i] != PackageManager.PERMISSION_GRANTED){
                            permissionsGranted=false;
                        }
                    }

                } else {
                    permissionsGranted=false;
                }
                if(permissionsGranted){
                    try {
                        mImageUri = FileProvider.getUriForFile(getApplicationContext(), getApplicationContext().getPackageName() + "socks", createImageFile());
                    } catch (Exception e) {
                        Log.d("DebugMySocks", "Permissions granted but exception happened " + e.getMessage());
                    }
                }
                break;
        }
    }

But I get Error creating file Attempt to invoke virtual method 'android.content.res.XmlResourceParser android.content.pm.ProviderInfo.loadXmlMetaData(android.content.pm.PackageManager, java.lang.String)' on a null object reference

user1766768
  • 29
  • 1
  • 6
  • did you open permission in settings of application. – Ergin Ersoy Dec 14 '17 at 14:19
  • You mean the little popup window that asks users to confirm permissions? If so, no I didn't. I tried: `int permissionCheck = ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE);` but WRITE_EXTERNAL_STORAGE doesn't resolve. – user1766768 Dec 14 '17 at 14:25
  • no in setting of phone, in applications you need to find your application, and then you need to find permissions, if permission is not granted, you cannot save anything to storage of phone. If this is the case i will post an answer how you can ask user if user didn't grant permission. – Ergin Ersoy Dec 14 '17 at 14:28
  • I added a quick/dirty permission check and allowed it. Now I get the following exception in the onClick function: `android.os.FileUriExposedException: file:///storage/emulated/0/socks/JPEG_20171214_083623_ exposed beyond app through ClipData.Item.getUri()` – user1766768 Dec 14 '17 at 14:34
  • `image.mkdirs(); image.createNewFile();`. Impossible code. First you create a directory . And after that you try to create a file with the same name. Impossible. Take a file explorer app and remove the directory. Adapt your code. – greenapps Dec 14 '17 at 15:49

2 Answers2

3

You need to check permission if is granted by user.

private void checkPermission(){
    if (checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)== PackageManager.PERMISSION_GRANTED) {        
        createFile();
    } else {    
        Log.v(TAG,"Permission is revoked");
        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);  
    }
}


@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
    switch (requestCode) {
        case 0:
            boolean permissionsGranted = true;
            if (grantResults.length > 0 && permissions.length==grantResults.length) {
                for (int i = 0; i < permissions.length; i++){
                    if (grantResults[i] != PackageManager.PERMISSION_GRANTED){
                        permissionsGranted=false;
                    }
                }

            } else {
                permissionsGranted=false;
            }
            if(permissionsGranted){
                createFile();
            }
            break;
    }
}

private void createFile(){
   ContentResolver cr = this.getContentResolver();
   Bitmap bitmap;
   try{
       bitmap = android.provider.MediaStore.Images.Media.getBitmap(cr, mImageUri);
       mProfileImage.setImageBitmap(bitmap);
       saveToFirebase(bitmap);
   }catch (Exception e){
     Toast.makeText(this, "Failed to load", Toast.LENGTH_SHORT).show();
     Log.d("DebugMySocks", "Failed to load", e);
   }
}

public void onActivityResult(int requestCode, int resultCode, Intent data) {
    Log.d("DebugMySocks", "onActivityResult in EditProfileActivity");
    if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == this.RESULT_OK) {
        this.getContentResolver().notifyChange(mImageUri, null); // <-- THIS IS LINE 254!
        checkPermission();
    }
}
Ergin Ersoy
  • 890
  • 8
  • 28
  • for your `FileUriExposedException` you can check https://stackoverflow.com/questions/38200282/android-os-fileuriexposedexception-file-storage-emulated-0-test-txt-exposed – Ergin Ersoy Dec 14 '17 at 14:39
  • I added the permission checker and handled the FileUriExposedException. Now I get `D/DebugMySocks: create file: false` and `Error creating file Attempt to invoke virtual method 'android.content.res.XmlResourceParser android.content.pm.ProviderInfo.loadXmlMetaData(android.content.pm.PackageManager, java.lang.String)' on a null object reference`. I think that means my permission checking isn't right? I will implement your checkPermission instead of my sloppy one and see what happens. Thanks. – user1766768 Dec 14 '17 at 14:43
  • I have added Edit 2. It did not work for the PackageManager problem. – user1766768 Dec 14 '17 at 14:56
  • i think your getApplicationContext().getPackageName() returns null. You can check https://stackoverflow.com/questions/30896130/fileprovider-crash-npe-attempting-to-invoke-xmlresourceparser-on-a-null-string – Ergin Ersoy Dec 14 '17 at 15:00
  • Changed FileProvider code to `mImageUri = FileProvider.getUriForFile(getApplicationContext(), "com.example.kevin.moresocksplease.fileprovider", createImageFile());` and added `android:authorities="com.example.kevin.moresocksplease.fileprovider"` in the manifest file inside the application tag. Didn't help. – user1766768 Dec 14 '17 at 15:01
  • I think maybe my FileProvider needs a in the manifest file. Trying that now. https://developer.android.com/reference/android/support/v4/content/FileProvider.html – user1766768 Dec 14 '17 at 15:06
  • Well, this has turned into a different problem, but I am making progress. The original IOException problem has been solved so I will mark your answer as accepted and try to continue. Thanks for the help. – user1766768 Dec 14 '17 at 15:14
  • I am glad to help. – Ergin Ersoy Dec 14 '17 at 15:19
0

I managed to make it work after several attempts i used the following code:

Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        Date dat = Calendar.getInstance().getTime();
        SimpleDateFormat simpleDate =  new SimpleDateFormat("yyyy-mm-dd-hh:mm:ss");
        String nameFoto = simpleDate.format(dat) + ".png";
        String filename = Environment.getExternalStorageDirectory().getAbsolutePath() + "/"+ File.separator +nameFoto;
        File ff = new File(filename);

        try {
            ff.createNewFile();
            //imageUri = Uri.fromFile(ff);
            imageUri = FileProvider.getUriForFile(IngresarFactura.this, BuildConfig.APPLICATION_ID + ".provider",ff);
            //imageUri = new Uri(filename).
            if (imageUri.getPath() == null){
                mensaje.setText(filename+ " Error path es nulo.");
            }
            cameraIntent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, imageUri);

            //COMPATIBILITY
            if (Build.VERSION.SDK_INT>= Build.VERSION_CODES.LOLLIPOP) {
                cameraIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
            } else {
                List<ResolveInfo> resInfoList = IngresarFactura.this.getPackageManager().queryIntentActivities(cameraIntent, PackageManager.MATCH_DEFAULT_ONLY);
                for (ResolveInfo resolveInfo : resInfoList) {
                    String packageName = resolveInfo.activityInfo.packageName;
                    IngresarFactura.this.grantUriPermission(packageName, imageUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
                }
            }
            //COMPATIBILITY
            activityResultLaunch.launch(cameraIntent);
        }

i got the code from several places and combined it to make it work. you have to add a policy as it says here

https://stackoverflow.com/a/45751453/16645882