• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.settingslib.drawer;
17 
18 import android.app.ActivityManager;
19 import android.content.ContentResolver;
20 import android.content.Context;
21 import android.content.IContentProvider;
22 import android.content.Intent;
23 import android.content.pm.ActivityInfo;
24 import android.content.pm.ComponentInfo;
25 import android.content.pm.PackageManager;
26 import android.content.pm.ProviderInfo;
27 import android.content.pm.ResolveInfo;
28 import android.net.Uri;
29 import android.os.Bundle;
30 import android.os.RemoteException;
31 import android.os.UserHandle;
32 import android.os.UserManager;
33 import android.provider.Settings.Global;
34 import android.text.TextUtils;
35 import android.util.ArrayMap;
36 import android.util.Log;
37 import android.util.Pair;
38 
39 import androidx.annotation.VisibleForTesting;
40 
41 import java.util.ArrayList;
42 import java.util.HashMap;
43 import java.util.List;
44 import java.util.Map;
45 
46 /**
47  * Utils is a helper class that contains profile key, meta data, settings action
48  * and static methods for get icon or text from uri.
49  */
50 public class TileUtils {
51 
52     private static final boolean DEBUG_TIMING = false;
53 
54     private static final String LOG_TAG = "TileUtils";
55     @VisibleForTesting
56     static final String SETTING_PKG = "com.android.settings";
57 
58     /**
59      * Settings will search for system activities of this action and add them as a top level
60      * settings tile using the following parameters.
61      *
62      * <p>A category must be specified in the meta-data for the activity named
63      * {@link #EXTRA_CATEGORY_KEY}
64      *
65      * <p>The title may be defined by meta-data named {@link #META_DATA_PREFERENCE_TITLE}
66      * otherwise the label for the activity will be used.
67      *
68      * <p>The icon may be defined by meta-data named {@link #META_DATA_PREFERENCE_ICON}
69      * otherwise the icon for the activity will be used.
70      *
71      * <p>A summary my be defined by meta-data named {@link #META_DATA_PREFERENCE_SUMMARY}
72      */
73     public static final String EXTRA_SETTINGS_ACTION = "com.android.settings.action.EXTRA_SETTINGS";
74 
75     /**
76      * @See {@link #EXTRA_SETTINGS_ACTION}.
77      */
78     public static final String IA_SETTINGS_ACTION = "com.android.settings.action.IA_SETTINGS";
79 
80     /**
81      * Same as #EXTRA_SETTINGS_ACTION but used for the platform Settings activities.
82      */
83     private static final String SETTINGS_ACTION = "com.android.settings.action.SETTINGS";
84 
85     private static final String OPERATOR_SETTINGS =
86             "com.android.settings.OPERATOR_APPLICATION_SETTING";
87 
88     private static final String OPERATOR_DEFAULT_CATEGORY =
89             "com.android.settings.category.wireless";
90 
91     private static final String MANUFACTURER_SETTINGS =
92             "com.android.settings.MANUFACTURER_APPLICATION_SETTING";
93 
94     private static final String MANUFACTURER_DEFAULT_CATEGORY =
95             "com.android.settings.category.device";
96 
97     /**
98      * The key used to get the category from metadata of activities of action
99      * {@link #EXTRA_SETTINGS_ACTION}
100      * The value must be from {@link CategoryKey}.
101      */
102     static final String EXTRA_CATEGORY_KEY = "com.android.settings.category";
103 
104     /**
105      * The key used to get the package name of the icon resource for the preference.
106      */
107     static final String EXTRA_PREFERENCE_ICON_PACKAGE = "com.android.settings.icon_package";
108 
109     /**
110      * Name of the meta-data item that should be set in the AndroidManifest.xml
111      * to specify the key that should be used for the preference.
112      */
113     public static final String META_DATA_PREFERENCE_KEYHINT = "com.android.settings.keyhint";
114 
115     /**
116      * Order of the item that should be displayed on screen. Bigger value items displays closer on
117      * top.
118      */
119     public static final String META_DATA_KEY_ORDER = "com.android.settings.order";
120 
121     /**
122      * Name of the meta-data item that should be set in the AndroidManifest.xml
123      * to specify the icon that should be displayed for the preference.
124      */
125     public static final String META_DATA_PREFERENCE_ICON = "com.android.settings.icon";
126 
127     /**
128      * Name of the meta-data item that should be set in the AndroidManifest.xml
129      * to specify the icon background color. The value may or may not be used by Settings app.
130      */
131     public static final String META_DATA_PREFERENCE_ICON_BACKGROUND_HINT =
132             "com.android.settings.bg.hint";
133 
134     /**
135      * Name of the meta-data item that should be set in the AndroidManifest.xml
136      * to specify the icon background color as raw ARGB.
137      */
138     public static final String META_DATA_PREFERENCE_ICON_BACKGROUND_ARGB =
139             "com.android.settings.bg.argb";
140 
141     /**
142      * Name of the meta-data item that should be set in the AndroidManifest.xml
143      * to specify the content provider providing the icon that should be displayed for
144      * the preference.
145      *
146      * Icon provided by the content provider overrides any static icon.
147      */
148     public static final String META_DATA_PREFERENCE_ICON_URI = "com.android.settings.icon_uri";
149 
150     /**
151      * Name of the meta-data item that should be set in the AndroidManifest.xml
152      * to specify whether the icon is tintable. This should be a boolean value {@code true} or
153      * {@code false}, set using {@code android:value}
154      */
155     public static final String META_DATA_PREFERENCE_ICON_TINTABLE =
156             "com.android.settings.icon_tintable";
157 
158     /**
159      * Name of the meta-data item that should be set in the AndroidManifest.xml
160      * to specify the title that should be displayed for the preference.
161      *
162      * <p>Note: It is preferred to provide this value using {@code android:resource} with a string
163      * resource for localization.
164      */
165     public static final String META_DATA_PREFERENCE_TITLE = "com.android.settings.title";
166 
167     /**
168      * Name of the meta-data item that should be set in the AndroidManifest.xml
169      * to specify the content provider providing the title text that should be displayed for the
170      * preference.
171      *
172      * Title provided by the content provider overrides any static title.
173      */
174     public static final String META_DATA_PREFERENCE_TITLE_URI =
175             "com.android.settings.title_uri";
176 
177     /**
178      * Name of the meta-data item that should be set in the AndroidManifest.xml
179      * to specify the summary text that should be displayed for the preference.
180      */
181     public static final String META_DATA_PREFERENCE_SUMMARY = "com.android.settings.summary";
182 
183     /**
184      * Name of the meta-data item that should be set in the AndroidManifest.xml
185      * to specify the content provider providing the summary text that should be displayed for the
186      * preference.
187      *
188      * Summary provided by the content provider overrides any static summary.
189      */
190     public static final String META_DATA_PREFERENCE_SUMMARY_URI =
191             "com.android.settings.summary_uri";
192 
193     /**
194      * Name of the meta-data item that should be set in the AndroidManifest.xml
195      * to specify the content provider providing the switch that should be displayed for the
196      * preference.
197      *
198      * This works with {@link #META_DATA_PREFERENCE_KEYHINT} which should also be set in the
199      * AndroidManifest.xml
200      */
201     public static final String META_DATA_PREFERENCE_SWITCH_URI =
202             "com.android.settings.switch_uri";
203 
204     /**
205      * Value for {@link #META_DATA_KEY_PROFILE}. When the device has a managed profile,
206      * the app will always be run in the primary profile.
207      *
208      * @see #META_DATA_KEY_PROFILE
209      */
210     public static final String PROFILE_PRIMARY = "primary_profile_only";
211 
212     /**
213      * Value for {@link #META_DATA_KEY_PROFILE}. When the device has a managed profile, the user
214      * will be presented with a dialog to choose the profile the app will be run in.
215      *
216      * @see #META_DATA_KEY_PROFILE
217      */
218     public static final String PROFILE_ALL = "all_profiles";
219 
220     /**
221      * Name of the meta-data item that should be set in the AndroidManifest.xml
222      * to specify the profile in which the app should be run when the device has a managed profile.
223      * The default value is {@link #PROFILE_ALL} which means the user will be presented with a
224      * dialog to choose the profile. If set to {@link #PROFILE_PRIMARY} the app will always be
225      * run in the primary profile.
226      *
227      * @see #PROFILE_PRIMARY
228      * @see #PROFILE_ALL
229      */
230     public static final String META_DATA_KEY_PROFILE = "com.android.settings.profile";
231 
232     /**
233      * Build a list of DashboardCategory.
234      */
getCategories(Context context, Map<Pair<String, String>, Tile> cache)235     public static List<DashboardCategory> getCategories(Context context,
236             Map<Pair<String, String>, Tile> cache) {
237         final long startTime = System.currentTimeMillis();
238         final boolean setup =
239                 Global.getInt(context.getContentResolver(), Global.DEVICE_PROVISIONED, 0) != 0;
240         final ArrayList<Tile> tiles = new ArrayList<>();
241         final UserManager userManager = (UserManager) context.getSystemService(
242                 Context.USER_SERVICE);
243         for (UserHandle user : userManager.getUserProfiles()) {
244             // TODO: Needs much optimization, too many PM queries going on here.
245             if (user.getIdentifier() == ActivityManager.getCurrentUser()) {
246                 // Only add Settings for this user.
247                 loadTilesForAction(context, user, SETTINGS_ACTION, cache, null, tiles, true);
248                 loadTilesForAction(context, user, OPERATOR_SETTINGS, cache,
249                         OPERATOR_DEFAULT_CATEGORY, tiles, false);
250                 loadTilesForAction(context, user, MANUFACTURER_SETTINGS, cache,
251                         MANUFACTURER_DEFAULT_CATEGORY, tiles, false);
252             }
253             if (setup) {
254                 loadTilesForAction(context, user, EXTRA_SETTINGS_ACTION, cache, null, tiles, false);
255                 loadTilesForAction(context, user, IA_SETTINGS_ACTION, cache, null, tiles, false);
256             }
257         }
258 
259         final HashMap<String, DashboardCategory> categoryMap = new HashMap<>();
260         for (Tile tile : tiles) {
261             final String categoryKey = tile.getCategory();
262             DashboardCategory category = categoryMap.get(categoryKey);
263             if (category == null) {
264                 category = new DashboardCategory(categoryKey);
265 
266                 if (category == null) {
267                     Log.w(LOG_TAG, "Couldn't find category " + categoryKey);
268                     continue;
269                 }
270                 categoryMap.put(categoryKey, category);
271             }
272             category.addTile(tile);
273         }
274         final ArrayList<DashboardCategory> categories = new ArrayList<>(categoryMap.values());
275         for (DashboardCategory category : categories) {
276             category.sortTiles();
277         }
278 
279         if (DEBUG_TIMING) {
280             Log.d(LOG_TAG, "getCategories took "
281                     + (System.currentTimeMillis() - startTime) + " ms");
282         }
283         return categories;
284     }
285 
286     @VisibleForTesting
loadTilesForAction(Context context, UserHandle user, String action, Map<Pair<String, String>, Tile> addedCache, String defaultCategory, List<Tile> outTiles, boolean requireSettings)287     static void loadTilesForAction(Context context,
288             UserHandle user, String action, Map<Pair<String, String>, Tile> addedCache,
289             String defaultCategory, List<Tile> outTiles, boolean requireSettings) {
290         final Intent intent = new Intent(action);
291         if (requireSettings) {
292             intent.setPackage(SETTING_PKG);
293         }
294         loadActivityTiles(context, user, addedCache, defaultCategory, outTiles, intent);
295         loadProviderTiles(context, user, addedCache, defaultCategory, outTiles, intent);
296     }
297 
loadActivityTiles(Context context, UserHandle user, Map<Pair<String, String>, Tile> addedCache, String defaultCategory, List<Tile> outTiles, Intent intent)298     private static void loadActivityTiles(Context context,
299             UserHandle user, Map<Pair<String, String>, Tile> addedCache,
300             String defaultCategory, List<Tile> outTiles, Intent intent) {
301         final PackageManager pm = context.getPackageManager();
302         final List<ResolveInfo> results = pm.queryIntentActivitiesAsUser(intent,
303                 PackageManager.GET_META_DATA, user.getIdentifier());
304         for (ResolveInfo resolved : results) {
305             if (!resolved.system) {
306                 // Do not allow any app to add to settings, only system ones.
307                 continue;
308             }
309             final ActivityInfo activityInfo = resolved.activityInfo;
310             final Bundle metaData = activityInfo.metaData;
311             loadTile(user, addedCache, defaultCategory, outTiles, intent, metaData, activityInfo);
312         }
313     }
314 
loadProviderTiles(Context context, UserHandle user, Map<Pair<String, String>, Tile> addedCache, String defaultCategory, List<Tile> outTiles, Intent intent)315     private static void loadProviderTiles(Context context,
316             UserHandle user, Map<Pair<String, String>, Tile> addedCache,
317             String defaultCategory, List<Tile> outTiles, Intent intent) {
318         final PackageManager pm = context.getPackageManager();
319         final List<ResolveInfo> results = pm.queryIntentContentProvidersAsUser(intent,
320                 0 /* flags */, user.getIdentifier());
321         for (ResolveInfo resolved : results) {
322             if (!resolved.system) {
323                 // Do not allow any app to add to settings, only system ones.
324                 continue;
325             }
326             final ProviderInfo providerInfo = resolved.providerInfo;
327             final List<Bundle> switchData = getSwitchDataFromProvider(context,
328                     providerInfo.authority);
329             if (switchData == null || switchData.isEmpty()) {
330                 continue;
331             }
332             for (Bundle metaData : switchData) {
333                 loadTile(user, addedCache, defaultCategory, outTiles, intent, metaData,
334                         providerInfo);
335             }
336         }
337     }
338 
loadTile(UserHandle user, Map<Pair<String, String>, Tile> addedCache, String defaultCategory, List<Tile> outTiles, Intent intent, Bundle metaData, ComponentInfo componentInfo)339     private static void loadTile(UserHandle user, Map<Pair<String, String>, Tile> addedCache,
340             String defaultCategory, List<Tile> outTiles, Intent intent, Bundle metaData,
341             ComponentInfo componentInfo) {
342         // Skip loading tile if the component is tagged primary_profile_only but not running on
343         // the current user.
344         if (user.getIdentifier() != ActivityManager.getCurrentUser()
345                 && Tile.isPrimaryProfileOnly(componentInfo.metaData)) {
346             Log.w(LOG_TAG, "Found " + componentInfo.name + " for intent "
347                     + intent + " is primary profile only, skip loading tile for uid "
348                     + user.getIdentifier());
349             return;
350         }
351 
352         String categoryKey = defaultCategory;
353         // Load category
354         if ((metaData == null || !metaData.containsKey(EXTRA_CATEGORY_KEY))
355                 && categoryKey == null) {
356             Log.w(LOG_TAG, "Found " + componentInfo.name + " for intent "
357                     + intent + " missing metadata "
358                     + (metaData == null ? "" : EXTRA_CATEGORY_KEY));
359             return;
360         } else {
361             categoryKey = metaData.getString(EXTRA_CATEGORY_KEY);
362         }
363 
364         final boolean isProvider = componentInfo instanceof ProviderInfo;
365         final Pair<String, String> key = isProvider
366                 ? new Pair<>(((ProviderInfo) componentInfo).authority,
367                         metaData.getString(META_DATA_PREFERENCE_KEYHINT))
368                 : new Pair<>(componentInfo.packageName, componentInfo.name);
369         Tile tile = addedCache.get(key);
370         if (tile == null) {
371             tile = isProvider
372                     ? new ProviderTile((ProviderInfo) componentInfo, categoryKey, metaData)
373                     : new ActivityTile((ActivityInfo) componentInfo, categoryKey);
374             addedCache.put(key, tile);
375         } else {
376             tile.setMetaData(metaData);
377         }
378 
379         if (!tile.userHandle.contains(user)) {
380             tile.userHandle.add(user);
381         }
382         if (!outTiles.contains(tile)) {
383             outTiles.add(tile);
384         }
385     }
386 
387     /** Returns the switch data of the key specified from the provider */
388     // TODO(b/144732809): rearrange methods by access level modifiers
getSwitchDataFromProvider(Context context, String authority, String key)389     static Bundle getSwitchDataFromProvider(Context context, String authority, String key) {
390         final Map<String, IContentProvider> providerMap = new ArrayMap<>();
391         final Uri uri = buildUri(authority, SwitchesProvider.METHOD_GET_SWITCH_DATA, key);
392         return getBundleFromUri(context, uri, providerMap, null /* bundle */);
393     }
394 
395     /** Returns all switch data from the provider */
getSwitchDataFromProvider(Context context, String authority)396     private static List<Bundle> getSwitchDataFromProvider(Context context, String authority) {
397         final Map<String, IContentProvider> providerMap = new ArrayMap<>();
398         final Uri uri = buildUri(authority, SwitchesProvider.METHOD_GET_SWITCH_DATA);
399         final Bundle result = getBundleFromUri(context, uri, providerMap, null /* bundle */);
400         return result != null
401                 ? result.getParcelableArrayList(SwitchesProvider.EXTRA_SWITCH_DATA)
402                 : null;
403     }
404 
405     /**
406      * Returns the complete uri from the meta data key of the tile.
407      *
408      * A complete uri should contain at least one path segment and be one of the following types:
409      *      content://authority/method
410      *      content://authority/method/key
411      *
412      * If the uri from the tile is not complete, build a uri by the default method and the
413      * preference key.
414      *
415      * @param tile          Tile which contains meta data
416      * @param metaDataKey   Key mapping to the uri in meta data
417      * @param defaultMethod Method to be attached to the uri by default if it has no path segment
418      * @return Uri associated with the key
419      */
getCompleteUri(Tile tile, String metaDataKey, String defaultMethod)420     public static Uri getCompleteUri(Tile tile, String metaDataKey, String defaultMethod) {
421         final String uriString = tile.getMetaData().getString(metaDataKey);
422         if (TextUtils.isEmpty(uriString)) {
423             return null;
424         }
425 
426         final Uri uri = Uri.parse(uriString);
427         final List<String> pathSegments = uri.getPathSegments();
428         if (pathSegments != null && !pathSegments.isEmpty()) {
429             return uri;
430         }
431 
432         final String key = tile.getMetaData().getString(META_DATA_PREFERENCE_KEYHINT);
433         if (TextUtils.isEmpty(key)) {
434             Log.w(LOG_TAG, "Please specify the meta-data " + META_DATA_PREFERENCE_KEYHINT
435                     + " in AndroidManifest.xml for " + uriString);
436             return buildUri(uri.getAuthority(), defaultMethod);
437         }
438         return buildUri(uri.getAuthority(), defaultMethod, key);
439     }
440 
buildUri(String authority, String method, String key)441     static Uri buildUri(String authority, String method, String key) {
442         return new Uri.Builder()
443                 .scheme(ContentResolver.SCHEME_CONTENT)
444                 .authority(authority)
445                 .appendPath(method)
446                 .appendPath(key)
447                 .build();
448     }
449 
buildUri(String authority, String method)450     private static Uri buildUri(String authority, String method) {
451         return new Uri.Builder()
452                 .scheme(ContentResolver.SCHEME_CONTENT)
453                 .authority(authority)
454                 .appendPath(method)
455                 .build();
456     }
457 
458     /**
459      * Gets the icon package name and resource id from content provider.
460      *
461      * @param context     context
462      * @param packageName package name of the target activity
463      * @param uri         URI for the content provider
464      * @param providerMap Maps URI authorities to providers
465      * @return package name and resource id of the icon specified
466      */
getIconFromUri(Context context, String packageName, Uri uri, Map<String, IContentProvider> providerMap)467     public static Pair<String, Integer> getIconFromUri(Context context, String packageName,
468             Uri uri, Map<String, IContentProvider> providerMap) {
469         final Bundle bundle = getBundleFromUri(context, uri, providerMap, null /* bundle */);
470         if (bundle == null) {
471             return null;
472         }
473         final String iconPackageName = bundle.getString(EXTRA_PREFERENCE_ICON_PACKAGE);
474         if (TextUtils.isEmpty(iconPackageName)) {
475             return null;
476         }
477         int resId = bundle.getInt(META_DATA_PREFERENCE_ICON, 0);
478         if (resId == 0) {
479             return null;
480         }
481         // Icon can either come from the target package or from the Settings app.
482         if (iconPackageName.equals(packageName)
483                 || iconPackageName.equals(context.getPackageName())) {
484             return Pair.create(iconPackageName, resId);
485         }
486         return null;
487     }
488 
489     /**
490      * Gets text associated with the input key from the content provider.
491      *
492      * @param context     context
493      * @param uri         URI for the content provider
494      * @param providerMap Maps URI authorities to providers
495      * @param key         Key mapping to the text in bundle returned by the content provider
496      * @return Text associated with the key, if returned by the content provider
497      */
getTextFromUri(Context context, Uri uri, Map<String, IContentProvider> providerMap, String key)498     public static String getTextFromUri(Context context, Uri uri,
499             Map<String, IContentProvider> providerMap, String key) {
500         final Bundle bundle = getBundleFromUri(context, uri, providerMap, null /* bundle */);
501         return (bundle != null) ? bundle.getString(key) : null;
502     }
503 
504     /**
505      * Gets boolean associated with the input key from the content provider.
506      *
507      * @param context     context
508      * @param uri         URI for the content provider
509      * @param providerMap Maps URI authorities to providers
510      * @param key         Key mapping to the text in bundle returned by the content provider
511      * @return Boolean associated with the key, if returned by the content provider
512      */
getBooleanFromUri(Context context, Uri uri, Map<String, IContentProvider> providerMap, String key)513     public static boolean getBooleanFromUri(Context context, Uri uri,
514             Map<String, IContentProvider> providerMap, String key) {
515         final Bundle bundle = getBundleFromUri(context, uri, providerMap, null /* bundle */);
516         return (bundle != null) ? bundle.getBoolean(key) : false;
517     }
518 
519     /**
520      * Puts boolean associated with the input key to the content provider.
521      *
522      * @param context     context
523      * @param uri         URI for the content provider
524      * @param providerMap Maps URI authorities to providers
525      * @param key         Key mapping to the text in bundle returned by the content provider
526      * @param value       Boolean associated with the key
527      * @return Bundle associated with the action, if returned by the content provider
528      */
putBooleanToUriAndGetResult(Context context, Uri uri, Map<String, IContentProvider> providerMap, String key, boolean value)529     public static Bundle putBooleanToUriAndGetResult(Context context, Uri uri,
530             Map<String, IContentProvider> providerMap, String key, boolean value) {
531         final Bundle bundle = new Bundle();
532         bundle.putBoolean(key, value);
533         return getBundleFromUri(context, uri, providerMap, bundle);
534     }
535 
getBundleFromUri(Context context, Uri uri, Map<String, IContentProvider> providerMap, Bundle bundle)536     private static Bundle getBundleFromUri(Context context, Uri uri,
537             Map<String, IContentProvider> providerMap, Bundle bundle) {
538         final Pair<String, String> args = getMethodAndKey(uri);
539         if (args == null) {
540             return null;
541         }
542         final String method = args.first;
543         final String key = args.second;
544         if (TextUtils.isEmpty(method)) {
545             return null;
546         }
547         final IContentProvider provider = getProviderFromUri(context, uri, providerMap);
548         if (provider == null) {
549             return null;
550         }
551         if (!TextUtils.isEmpty(key)) {
552             if (bundle == null) {
553                 bundle = new Bundle();
554             }
555             bundle.putString(META_DATA_PREFERENCE_KEYHINT, key);
556         }
557         try {
558             return provider.call(context.getAttributionSource(),
559                     uri.getAuthority(), method, uri.toString(), bundle);
560         } catch (RemoteException e) {
561             return null;
562         }
563     }
564 
getProviderFromUri(Context context, Uri uri, Map<String, IContentProvider> providerMap)565     private static IContentProvider getProviderFromUri(Context context, Uri uri,
566             Map<String, IContentProvider> providerMap) {
567         if (uri == null) {
568             return null;
569         }
570         final String authority = uri.getAuthority();
571         if (TextUtils.isEmpty(authority)) {
572             return null;
573         }
574         if (!providerMap.containsKey(authority)) {
575             providerMap.put(authority, context.getContentResolver().acquireUnstableProvider(uri));
576         }
577         return providerMap.get(authority);
578     }
579 
580     /** Returns method and key of the complete uri. */
getMethodAndKey(Uri uri)581     private static Pair<String, String> getMethodAndKey(Uri uri) {
582         if (uri == null) {
583             return null;
584         }
585         final List<String> pathSegments = uri.getPathSegments();
586         if (pathSegments == null || pathSegments.isEmpty()) {
587             return null;
588         }
589         final String method = pathSegments.get(0);
590         final String key = pathSegments.size() > 1 ? pathSegments.get(1) : null;
591         return Pair.create(method, key);
592     }
593 }
594