Search code examples
javaandroidandroid-contentprovider

Trying to access the LauncherProvider


I am trying to access the LauncherProvider. You can find its source code here

I tried to query this ContentProvider like this:

Uri uri = new Uri.Builder().scheme("content").authority("com.android.launcher.settings").appendPath("favorites").build();
String[] projection = new String[]{
        "_id", "title", "intent", "container", "screen", "cellX", "cellY",
        "spanX", "spanY", "itemType", "appWidgetId", "isShortcut", "iconType",
        "iconPackage", "iconResource", "icon", "uri", "displayMode"
};
String selection = null;
String[] selectionArgs = null;
String sortOrder = ContactsContract.Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC";

Cursor query = getActivity().getContentResolver().query(uri, projection, selection, selectionArgs, sortOrder);
if (query != null) {
    while (query.moveToNext()) {
        Log.d(TAG, query.getString(2));
    }
}
if (query != null) {
    query.close();
}

However the Cursor I get is always null! This is the error I get in the logcat:

07-31 15:55:14.703  24773-24773/x.y.z.testE/ActivityThread﹕ Failed to find provider info for com.android.launcher.settings

Can anybody tell me what I'm doing wrong?


Solution

  • Explanation of your problems

    You are trying to access the LauncherProvider, but that may not be as easy you want. There are two main problems:

    1. The LauncherProvider has two permissions:

      • com.android.launcher.permission.READ_SETTINGS to read from it.
      • com.android.launcher.permission.WRITE_SETTINGS to write to it.

    2. Different OEMs write their own version of the LauncherProvider and its authority might be different! On different versions of Android the authority might also be different. For example on my Nexus 5 the authority of the correct provider is com.android.launcher3.settings.

    Declaring the permissions is of course not a problem, but finding the correct ContentProvider can prove difficult, fortunately there is a solution!


    Solution

    You first have to declare all the required permissions in the manifest like this:

    <uses-permission android:name="com.android.launcher.permission.READ_SETTINGS" />
    <uses-permission android:name="com.android.launcher.permission.WRITE_SETTINGS" />
    

    And after that we need to find the correct provider! You can do that by reading the PackageInfo of all the installed apps and looping through all the ContentProviders of each app. The ContentProvider which has READ_SETTINGS and WRITE_SETTINGS as read and write permission is the one we are looking for. But we need a more complex logic to find the right one since the permissions are often based on the package name.

    public static String findLauncherProviderAuthority(Context context) {
        // Gets PackageInfo about all installed apps
        final List<PackageInfo> packs = context.getPackageManager().getInstalledPackages(PackageManager.GET_PROVIDERS);
        if (packs == null) {
            return null;
        }
    
        for (PackageInfo pack : packs) {
            // This gets the ProviderInfo of every ContentProvider in that app
            final ProviderInfo[] providers = pack.providers;
            if (providers == null) {
                continue;
            }
    
            // This loops through the ContentProviders
            for (ProviderInfo provider : providers) {
    
                // And finally we look for the one with the correct permissions
                // We use `startsWith()` and `endsWith()` since only the middle 
                // part might change
                final String readPermission = provider.readPermission;
                final String writePermission = provider.writePermission;
                if(readPermission != null && writePermission != null) {
                    final boolean readPermissionMatches = readPermission.startsWith("com.android.") && readPermission.endsWith(".permission.READ_SETTINGS");
                    final boolean writePermissionMatches = writePermission.startsWith("com.android.") && writePermission.endsWith(".permission.WRITE_SETTINGS");
                    if(readPermissionMatches && writePermissionMatches) {
    
                        // And if we found the right one we return the authority
                        return provider.authority;
                    }
                }
            }
        }
        return null;
    }  
    

    So this should very reliably return the correct ContentProvider! You may want to tweak it a little more if you run into problems on some devices, but on all devices I could test it (and that's a lot of devices) it is working perfectly. You can use the method above like this:

    final String authority = findLauncherProviderAuthority(getActivity());
    final Uri uri = new Uri.Builder().scheme(CONTENT).authority(authority).appendPath(PATH).build();
    ...
    

    I hope I could help you and if you have any further questions please feel free to ask!