Question

I've a widget with nine-patch image background. The image was saved in /sdcard/mydir/bgs.

When I try to load a image with setImageViewUri method, I've this error:

Unable to open content: file:///storage/emulated/0/sdcard/mydir/bgs

..

then

...

open failed: EACCES (Permission denied)

This appears only on the the home screen and only with Nexus 10 and Nexus 7 (with latest launcher 4.4 this bug not exist). I've also have some RemoteViews on my application and all works correctly.

I've also added into manifest either READ_EXTERNAL_STORAGE, either WRITE_EXTERNAL_STORAGE.

How can I solve?

UPDATE: I've inspect the method setImageViewUri and I've found that it changes the path of my file.

if (value != null) {
            // Resolve any filesystem path before sending remotely
            value = value.getCanonicalUri();
            if (StrictMode.vmFileUriExposureEnabled()) {
                value.checkFileUriExposed("RemoteViews.setUri()");
            }
        }

This method receive my value (/sdcard/mydir/bgs) and changes it into (storage/emulated/0/sdcard/mydir/bgs). But this file not exists into system via adb.

Was it helpful?

Solution 3

It appears setImageViewUri is no longer safe to use with file:// uris.

Why?

Jellybean introduced the READ_EXTERNAL_STORAGE permission. Apps that want to read from external storage must hold this permission. This wasn't enforced by default until KitKat.

The launcher does not hold this permission. In fact you are not guaranteed that any RemoteView you attach to holds that permission. This makes it unsafe to use setImageViewUri, since you don't know if the remote image view will even be able to read the given uri.

What now?

Option 1: Use setImageViewBitmap.

You may have moved away from this option due to failed binder transactions. The trick to those is just making sure your image is under 1 MB. This isn't as hard as it sounds. You can calculate exactly how big your image is going to be. For instance if you are using an ARGB_8888 image that means you'll need 4 bytes per pixel. We can calulate the max size by:

1 Mb = 1048576 bytes = 262144 pixels = 512 x 512 image

Of course you can squeeze more out of it using RGB_565 to get 2x the pixels.

Also note that you may not need a huge image if your widget is small. Your appwidget can ask about its specific options by the following:

Bundle options = appWidgetManager.getAppWidgetOptions(appWidgetIds[i]);

int minWidth = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH);
int minHeight = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT);

int maxWidth = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH);
int maxHeight = options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT);

Just be aware that the returned values are in dip not pixels so you will need to convert them to scale your bitmap.

Option 2: Use a content provider

If for some reason you still don't like the IPC restriction, you can always build a custom content provider. Pass that uri into the setImageViewUri, and you should be good to go.

What about the path switch?

The path switch is not the real issue. It looks like it is a problem, but the emulation actually works fine. Try creating a file inside //storage/emulated/0/sdcard/mydir/bgs and the file will create just fine.

You'll notice that while the exception is a FileNotFoundException the message is Permission Denied.

OTHER TIPS

As from Lollipop, Google introduced a new way of explicitly giving apps the permission you want them to use on your device and disabling the ones you want to deny the app. If you notice, in your android monitor, the log shows

java.io.FileNotFoundException: /storage/emulated/0/advert.mp4: open failed: EACCES (Permission denied)

EACCESS permission denied is the cause of the java's infamous FileNotFoundException.

To solve this, just goto your App permission and enable storage for your app

enter image description here

You need

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

in your manifest.

I think you are entering SD card path manually. Instead of manually entering SD Card path you should get storage path like this :

File extStore = Environment.getExternalStorageDirectory();
String mPath = extStore.getAbsolutePath() + "/mydir/bgs";
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top