Android Permission denial in Widget RemoteViewsFactory for Content
Asked Answered
P

3

14

I have a widget which I am trying to use to display information from my app's local database inside of a listview.

I'm using the RemoteViewsService.RemoteViewsFactory interface to load my list's contents. If I run the block of code which reloads the list in the onDataSetChanged method. the app crashes with the following message:

11-01 16:40:39.540: E/ACRA(27175): DataDisplay fatal error : Permission Denial: reading com.datadisplay.content.ContentProviderAdapter uri content://com.datadisplay.provider.internalDB/events from pid=573, uid=10029 requires the provider be exported, or grantUriPermission()

However, this same code run in the class's constructor works just fine. Of course, I need to have this also work in the onDataSetChanged method for updating and stuff.

Here is my provider's entry in the manifest:

    <provider android:name="com.datadisplay.content.ContentProviderAdapter"
        android:authorities="com.datadisplay.provider.internalDB"
        android:exported="true"
        android:enabled="true"
        android:grantUriPermissions="true">
            <grant-uri-permission android:pathPattern="/events/"/>
    </provider>

I am both exporting it AND granting Uri permissions like the error message requests, but it still fails. I found this question, where the guy had an issue but eventually removes his custom permissions and it worked. I don't have any custom permissions like that, but still no luck:

Widget with content provider; impossible to use ReadPermission?

If anyone has insight I'd be really grateful, this is getting incredibly frustrating, haha.

Pinnace answered 1/11, 2012 at 23:53 Comment(0)
P
9

Put this in your onDataSetChanged() method:

    Thread thread = new Thread() {
        public void run() {
            query();
        }
    };
    thread.start();
    try {
        thread.join();
    } catch (InterruptedException e) {
    }

Fetch data from the database inside query() method. I do not know why fetching data in a separate thread helps get around this problem, but it works! I got this from one of the Android examples.

Principally answered 2/11, 2012 at 14:47 Comment(2)
Solved the issue. Of course, another popped up in it's place. But at least this is solved. Thanks!Pinnace
This works because the query() is happening on a different Thread, one that doesn't have the current Binder remote call context. See my answer below on how to clear the caller identity on the current thread.Slog
S
31

This is happening because RemoteViewsFactory is being called from a remote process, and that context is being used for permission enforcement. (The remote caller doesn't have permission to use your provider, so it throws a SecurityException.)

To solve this, you can clear the identity of the remote process, so that permission enforcement is checked against your app instead of against the remote caller. Here's a common pattern you'll find across the platform:

final long token = Binder.clearCallingIdentity();
try {
    [perform your query, etc]
} finally {
    Binder.restoreCallingIdentity(token);
}
Slog answered 17/12, 2013 at 22:19 Comment(2)
You rock. This helped me with my issue.Barrelchested
Thank you for the real solution. Note that this method can keep your provider not exported, since you uses your own identity instead of the identity of incoming IPC set by default.Suspender
P
9

Put this in your onDataSetChanged() method:

    Thread thread = new Thread() {
        public void run() {
            query();
        }
    };
    thread.start();
    try {
        thread.join();
    } catch (InterruptedException e) {
    }

Fetch data from the database inside query() method. I do not know why fetching data in a separate thread helps get around this problem, but it works! I got this from one of the Android examples.

Principally answered 2/11, 2012 at 14:47 Comment(2)
Solved the issue. Of course, another popped up in it's place. But at least this is solved. Thanks!Pinnace
This works because the query() is happening on a different Thread, one that doesn't have the current Binder remote call context. See my answer below on how to clear the caller identity on the current thread.Slog
C
9

If this only happens for 4.2 and not the rest, you need to set the android:exported="true", because the default is changed: http://developer.android.com/about/versions/android-4.2.html

Content providers are no longer exported by default. That is, the default value for the android:exported attribute is now “false". If it’s important that other apps be able to access your content provider, you must now explicitly set android:exported="true".

Coordination answered 9/1, 2013 at 14:16 Comment(2)
+1, for Android 4.2 this is the solution! The sdk example it isn't updated! <provider android:name="WeatherDataProvider" android:exported="true"Leven
Thanks, this cleared my mind not only on this particular case, but more generally on interactions with\between Content Providers and Widgets.Galloping

© 2022 - 2024 — McMap. All rights reserved.