0

I started a new project in Android Studio with: empty activity, Java, API 29, Android 10.0 (Q)

I added permissions to AndroidManifest.xml

  <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

I added ImageView and a Button to activity_main.xml

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="80dp"
        android:text="button"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <ImageView
        android:id="@+id/my_image"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/button"
        />

I loaded an image in MainActivity.java - Permissions are checked at run time, after a button click. Then after another button click the image should be viewed. The code also contains a few checks for correct activity.

package mrppanther.com.example.read_image;

import android.Manifest;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
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 androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import java.io.File;

public class MainActivity extends AppCompatActivity {
    private int STORAGE_PERMISSION_CODE = 1;
    private int new_state = 1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // NOTE: use a button to request activity
        Button buttonRequest = findViewById(R.id.button);
        buttonRequest.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                // NOTE: declare "myfile", this is want I want to view
                File myfile = new File("/storage/emulated/0/Pictures/henry1.jpg");

                // NOTE: on the 1st click, check permissions
                if (new_state == 1) {
                    requestStoragePermission();
                    new_state = 2; // NOTE: move to the next state, on the next click

                // NOTE: on the 2nd click, view image
                } else {

                    // NOTE: check that the "myfile"" exists
                    if (myfile.exists()) {
                        Log.i("myfile", "FILE EXISTS PASS! " + myfile);
                    } else {
                        Log.i("myfile", "FILE ABSENT FAIL! " + myfile);
                    }

                    // NOTE: declare "myimage"
                    ImageView myimage = (ImageView) findViewById(R.id.my_image);

                    // NOTE: experimenting with options did not help
                    BitmapFactory.Options options=new BitmapFactory.Options();
                    //options.inJustDecodeBounds=true;
                    options.inSampleSize=1;

                    // ERROR: E/BitmapFactory: Unable to decode stream: java.io.FileNotFoundException:
                    // ERROR: /storage/emulated/0/Pictures/henry1.jpg: open failed: EACCES (Permission denied)
                    Bitmap bitmap = BitmapFactory.decodeFile(String.valueOf(myfile), options);

                    myimage.setImageBitmap(bitmap);

                    // NOTE: here is a test to check "myimage" is correct
                    // NOTE: this works OK, and image from drawable resource is displayed
                    // myimage.setImageResource(R.drawable.henry1);

                    new_state = 1; // NOTE: go back to the 1st state, on the next click
                }
            }
        }); // NOTE: this is the end of button activity
    }

    // NOTE: request permission to read storage, at run time
    private void requestStoragePermission() {
        ActivityCompat.requestPermissions(this,
                new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, STORAGE_PERMISSION_CODE);
        // NOTE: here is a test which sets the wrong permission
        // new String[]{Manifest.permission.READ_PHONE_NUMBERS,Manifest.permission.READ_PHONE_STATE}, STORAGE_PERMISSION_CODE);
    }

    // NOTE: check read permission is granted, at run time
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (requestCode == STORAGE_PERMISSION_CODE) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                Toast.makeText(this, "Permission GRANTED", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(this, "Permission DENIED", Toast.LENGTH_SHORT).show();
            }
        }
    }
}

Results: I saw the error message below, and the image was not displayed. However, the run time permission check indicated "Permission GRANTED". Finally I rebooted my Tablet. I checked the app is allowed access to storage (in settings, app, storage permission). I reran the app with USB disconnected, but the image was not displayed.

I/myfile: FILE EXISTS PASS! /storage/emulated/0/Pictures/henry1.jpg
E/BitmapFactory: Unable to decode stream: java.io.FileNotFoundException: /storage/emulated/0/Pictures/henry1.jpg: open failed: EACCES (Permission denied)
Mr.P.Panther
  • 1
  • 1
  • 3

1 Answers1

0

In Android 10 and above you may use the File class only for media located in your local private directory. There is no direct access to external media by using file paths.

When it comes to public media, you must use the actual media Uri from the Media Store. For your specific case do as next:

  1. Obtain your target image Uri using the Media Store
  2. Open a Stream targeting such Uri.
  3. Use the stream with BitmapFactory.
  4. Don't forget to close the stream.

Remark: The DATA field in the Media Store has been deprecated and it may or not contain a path, therefore you should avoid using paths when it comes to files, and adhere to content Uri's.

PerracoLabs
  • 16,449
  • 15
  • 74
  • 127
  • `Don't forget to close the stream.` You can forget that as BitmapFactory closes it. – blackapps Oct 08 '20 at 15:56
  • @blackapps As far as I know BitmapFactory doesn't close any passed stream, by checking the source code you may see the only streams it closes are the ones that it opens internally, but none of the ones passed as parameters. – PerracoLabs Oct 08 '20 at 16:12
  • It was also only as far as i know. I think i'm confused with Bitmap.compress() that does close the stream. As far as i know. Thanks. – blackapps Oct 08 '20 at 16:17