Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 676add41 authored by Shahriyar Amini's avatar Shahriyar Amini
Browse files

Allow Settings tiles to use content provider data.

Bug: 31002801
Test: make RunSettingsLibRoboTests

Adds optional metadata fields to specify a URI to a content provider
for reading the tile data.

Change-Id: Idc47eefc067fb9d4832dfbd58171943c29f0a529
parent d5115103
Loading
Loading
Loading
Loading
+99 −2
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@ package com.android.settingslib.drawer;

import android.app.ActivityManager;
import android.content.Context;
import android.content.IContentProvider;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
@@ -24,11 +25,14 @@ import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings.Global;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
import android.util.Pair;

@@ -112,6 +116,15 @@ public class TileUtils {
     */
    public static final String META_DATA_PREFERENCE_ICON = "com.android.settings.icon";

    /**
     * Name of the meta-data item that should be set in the AndroidManifest.xml
     * to specify the content provider providing the icon that should be displayed for
     * the preference.
     *
     * Icon provided by the content provider overrides any static icon.
     */
    public static final String META_DATA_PREFERENCE_ICON_URI = "com.android.settings.icon_uri";

    /**
     * Name of the meta-data item that should be set in the AndroidManifest.xml
     * to specify the title that should be displayed for the preference.
@@ -124,6 +137,16 @@ public class TileUtils {
     */
    public static final String META_DATA_PREFERENCE_SUMMARY = "com.android.settings.summary";

    /**
     * Name of the meta-data item that should be set in the AndroidManifest.xml
     * to specify the content provider providing the summary text that should be displayed for the
     * preference.
     *
     * Summary provided by the content provider overrides any static summary.
     */
    public static final String META_DATA_PREFERENCE_SUMMARY_URI =
            "com.android.settings.summary_uri";

    private static final String SETTING_PKG = "com.android.settings";

    /**
@@ -315,6 +338,10 @@ public class TileUtils {
            CharSequence title = null;
            String summary = null;
            String keyHint = null;
            String uriString = null;
            Uri uri = null;
            // Several resources can be using the same provider. Only acquire a single provider.
            Map<String, IContentProvider> providerMap = new ArrayMap<>();

            // Get the activity's meta-data
            try {
@@ -323,7 +350,11 @@ public class TileUtils {
                Bundle metaData = activityInfo.metaData;

                if (res != null && metaData != null) {
                    if (metaData.containsKey(META_DATA_PREFERENCE_ICON)) {
                    if (metaData.containsKey(META_DATA_PREFERENCE_ICON_URI)) {
                        icon = getIconFromUri(context,
                                metaData.getString(META_DATA_PREFERENCE_ICON_URI), providerMap);
                    }
                    if ((icon == 0) && metaData.containsKey(META_DATA_PREFERENCE_ICON)) {
                        icon = metaData.getInt(META_DATA_PREFERENCE_ICON);
                    }
                    if (metaData.containsKey(META_DATA_PREFERENCE_TITLE)) {
@@ -333,7 +364,13 @@ public class TileUtils {
                            title = metaData.getString(META_DATA_PREFERENCE_TITLE);
                        }
                    }
                    if (metaData.containsKey(META_DATA_PREFERENCE_SUMMARY)) {
                    if (metaData.containsKey(META_DATA_PREFERENCE_SUMMARY_URI)) {
                        summary = getTextFromUri(context,
                                metaData.getString(META_DATA_PREFERENCE_SUMMARY_URI), providerMap,
                                META_DATA_PREFERENCE_SUMMARY);
                    }
                    if (TextUtils.isEmpty(summary)
                            && metaData.containsKey(META_DATA_PREFERENCE_SUMMARY)) {
                        if (metaData.get(META_DATA_PREFERENCE_SUMMARY) instanceof Integer) {
                            summary = res.getString(metaData.getInt(META_DATA_PREFERENCE_SUMMARY));
                        } else {
@@ -377,6 +414,66 @@ public class TileUtils {
        return false;
    }

    private static int getIconFromUri(Context context, String uriString,
            Map<String, IContentProvider> providerMap) {
        Bundle bundle = getBundleFromUri(context, uriString, providerMap);
        return (bundle != null) ? bundle.getInt(META_DATA_PREFERENCE_ICON, 0) : 0;
    }

    private static String getTextFromUri(Context context, String uriString,
            Map<String, IContentProvider> providerMap, String key) {
        Bundle bundle = getBundleFromUri(context, uriString, providerMap);
        return (bundle != null) ? bundle.getString(key) : null;
    }

    private static Bundle getBundleFromUri(Context context, String uriString,
            Map<String, IContentProvider> providerMap) {
        if (TextUtils.isEmpty(uriString)) {
            return null;
        }
        Uri uri = Uri.parse(uriString);
        String method = getMethodFromUri(uri);
        if (TextUtils.isEmpty(method)) {
            return null;
        }
        IContentProvider provider = getProviderFromUri(context, uri, providerMap);
        if (provider == null) {
            return null;
        }
        try {
            return provider.call(context.getPackageName(), method, uriString, null);
        } catch (RemoteException e) {
            return null;
        }
    }

    private static IContentProvider getProviderFromUri(Context context, Uri uri,
            Map<String, IContentProvider> providerMap) {
        if (uri == null) {
            return null;
        }
        String authority = uri.getAuthority();
        if (TextUtils.isEmpty(authority)) {
            return null;
        }
        if (!providerMap.containsKey(authority)) {
            providerMap.put(authority, context.getContentResolver().acquireProvider(uri));
        }
        return providerMap.get(authority);
    }

    /** Returns the first path segment of the uri if it exists as the method, otherwise null. */
    static String getMethodFromUri(Uri uri) {
        if (uri == null) {
            return null;
        }
        List<String> pathSegments = uri.getPathSegments();
        if ((pathSegments == null) || pathSegments.isEmpty()) {
            return null;
        }
        return pathSegments.get(0);
    }

    public static final Comparator<Tile> TILE_COMPARATOR =
            new Comparator<Tile>() {
        @Override
+102 −0
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.settingslib.drawer;

import android.content.IContentProvider;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -24,7 +26,9 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Bundle;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings.Global;
@@ -40,6 +44,7 @@ import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;

import java.util.ArrayList;
@@ -47,10 +52,12 @@ import java.util.List;
import java.util.Map;

import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.argThat;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;

@RunWith(RobolectricTestRunner.class)
@@ -65,12 +72,21 @@ public class TileUtilsTest {
    private Resources mResources;
    @Mock
    private UserManager mUserManager;
    @Mock
    private IContentProvider mIContentProvider;
    @Mock
    private ContentResolver mContentResolver;

    private static final String URI_GET_SUMMARY = "content://authority/text/summary";
    private static final String URI_GET_ICON = "content://authority/icon/my_icon";

    @Before
    public void setUp() throws NameNotFoundException {
        MockitoAnnotations.initMocks(this);
        when(mContext.getPackageManager()).thenReturn(mPackageManager);
        when(mPackageManager.getResourcesForApplication(anyString())).thenReturn(mResources);
        mContentResolver = spy(RuntimeEnvironment.application.getContentResolver());
        when(mContext.getContentResolver()).thenReturn(mContentResolver);
    }

    @Test
@@ -158,11 +174,89 @@ public class TileUtilsTest {
        assertThat(categoryList.get(0).tiles.get(0).category).isEqualTo(testCategory);
    }

    @Test
    public void getTilesForIntent_shouldNotProcessInvalidUriContentSystemApp()
            throws RemoteException {
        Intent intent = new Intent();
        Map<Pair<String, String>, Tile> addedCache = new ArrayMap<>();
        List<Tile> outTiles = new ArrayList<>();
        List<ResolveInfo> info = new ArrayList<>();
        ResolveInfo resolveInfo = newInfo(true, null /* category */, null, null, URI_GET_SUMMARY);
        info.add(resolveInfo);

        when(mPackageManager.queryIntentActivitiesAsUser(eq(intent), anyInt(), anyInt()))
                .thenReturn(info);

        // Case 1: No provider associated with the uri specified.
        TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache,
                null /* defaultCategory */, outTiles, false /* usePriority */,
                false /* checkCategory */);

        assertThat(outTiles.size()).isEqualTo(1);
        assertThat(outTiles.get(0).icon.getResId()).isEqualTo(314159);
        assertThat(outTiles.get(0).summary).isEqualTo("static-summary");

        // Case 2: Empty bundle.
        Bundle bundle = new Bundle();
        when(mIContentProvider.call(anyString(),
                eq(TileUtils.getMethodFromUri(Uri.parse(URI_GET_SUMMARY))), eq(URI_GET_SUMMARY),
                any())).thenReturn(bundle);
        when(mContentResolver.acquireProvider(anyString())).thenReturn(mIContentProvider);
        when(mContentResolver.acquireProvider(any(Uri.class))).thenReturn(mIContentProvider);

        TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache,
                null /* defaultCategory */, outTiles, false /* usePriority */,
                false /* checkCategory */);

        assertThat(outTiles.size()).isEqualTo(1);
        assertThat(outTiles.get(0).icon.getResId()).isEqualTo(314159);
        assertThat(outTiles.get(0).summary).isEqualTo("static-summary");
    }

    @Test
    public void getTilesForIntent_shouldProcessUriContentForSystemApp() throws RemoteException {
        Intent intent = new Intent();
        Map<Pair<String, String>, Tile> addedCache = new ArrayMap<>();
        List<Tile> outTiles = new ArrayList<>();
        List<ResolveInfo> info = new ArrayList<>();
        ResolveInfo resolveInfo = newInfo(true, null /* category */, null, URI_GET_ICON,
                URI_GET_SUMMARY);
        info.add(resolveInfo);

        when(mPackageManager.queryIntentActivitiesAsUser(eq(intent), anyInt(), anyInt()))
                .thenReturn(info);

        Bundle bundle = new Bundle();
        bundle.putInt("com.android.settings.icon", 161803);
        bundle.putString("com.android.settings.summary", "dynamic-summary");
        when(mIContentProvider.call(anyString(),
                eq(TileUtils.getMethodFromUri(Uri.parse(URI_GET_ICON))), eq(URI_GET_ICON), any()))
                .thenReturn(bundle);
        when(mIContentProvider.call(anyString(),
                eq(TileUtils.getMethodFromUri(Uri.parse(URI_GET_SUMMARY))), eq(URI_GET_SUMMARY),
                any())).thenReturn(bundle);
        when(mContentResolver.acquireProvider(anyString())).thenReturn(mIContentProvider);
        when(mContentResolver.acquireProvider(any(Uri.class))).thenReturn(mIContentProvider);

        TileUtils.getTilesForIntent(mContext, UserHandle.CURRENT, intent, addedCache,
                null /* defaultCategory */, outTiles, false /* usePriority */,
                false /* checkCategory */);

        assertThat(outTiles.size()).isEqualTo(1);
        assertThat(outTiles.get(0).icon.getResId()).isEqualTo(161803);
        assertThat(outTiles.get(0).summary).isEqualTo("dynamic-summary");
    }

    private ResolveInfo newInfo(boolean systemApp, String category) {
        return newInfo(systemApp, category, null);
    }

    private ResolveInfo newInfo(boolean systemApp, String category, String keyHint) {
        return newInfo(systemApp, category, keyHint, null, null);
    }

    private ResolveInfo newInfo(boolean systemApp, String category, String keyHint, String iconUri,
            String summaryUri) {
        ResolveInfo info = new ResolveInfo();
        info.system = systemApp;
        info.activityInfo = new ActivityInfo();
@@ -170,9 +264,17 @@ public class TileUtilsTest {
        info.activityInfo.name = "123";
        info.activityInfo.metaData = new Bundle();
        info.activityInfo.metaData.putString("com.android.settings.category", category);
        info.activityInfo.metaData.putInt("com.android.settings.icon", 314159);
        info.activityInfo.metaData.putString("com.android.settings.summary", "static-summary");
        if (keyHint != null) {
            info.activityInfo.metaData.putString("com.android.settings.keyhint", keyHint);
        }
        if (iconUri != null) {
            info.activityInfo.metaData.putString("com.android.settings.icon_uri", iconUri);
        }
        if (summaryUri != null) {
            info.activityInfo.metaData.putString("com.android.settings.summary_uri", summaryUri);
        }
        info.activityInfo.applicationInfo = new ApplicationInfo();
        if (systemApp) {
            info.activityInfo.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;