diff --git a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java index 6c5a09d23c618..baf12a0ceefee 100644 --- a/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java +++ b/packages/SettingsLib/src/com/android/settingslib/drawer/TileUtils.java @@ -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 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 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 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 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 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 pathSegments = uri.getPathSegments(); + if ((pathSegments == null) || pathSegments.isEmpty()) { + return null; + } + return pathSegments.get(0); + } + public static final Comparator TILE_COMPARATOR = new Comparator() { @Override diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java index c6c6aad22cf0f..eb99cac94e40a 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/drawer/TileUtilsTest.java @@ -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, Tile> addedCache = new ArrayMap<>(); + List outTiles = new ArrayList<>(); + List 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, Tile> addedCache = new ArrayMap<>(); + List outTiles = new ArrayList<>(); + List 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;