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      * Name of the meta-data item that should be set in the AndroidManifest.xml
234      * to specify whether the {@link android.app.Activity} should be launched in a separate task.
235      * This should be a boolean value {@code true} or {@code false}, set using {@code android:value}
236      */
237     public static final String META_DATA_NEW_TASK = "com.android.settings.new_task";
238 
239     /**
240      * Build a list of DashboardCategory.
241      */
getCategories(Context context, Map<Pair<String, String>, Tile> cache)242     public static List<DashboardCategory> getCategories(Context context,
243             Map<Pair<String, String>, Tile> cache) {
244         final long startTime = System.currentTimeMillis();
245         final boolean setup =
246                 Global.getInt(context.getContentResolver(), Global.DEVICE_PROVISIONED, 0) != 0;
247         final ArrayList<Tile> tiles = new ArrayList<>();
248         final UserManager userManager = (UserManager) context.getSystemService(
249                 Context.USER_SERVICE);
250         for (UserHandle user : userManager.getUserProfiles()) {
251             // TODO: Needs much optimization, too many PM queries going on here.
252             if (user.getIdentifier() == ActivityManager.getCurrentUser()) {
253                 // Only add Settings for this user.
254                 loadTilesForAction(context, user, SETTINGS_ACTION, cache, null, tiles, true);
255                 loadTilesForAction(context, user, OPERATOR_SETTINGS, cache,
256                         OPERATOR_DEFAULT_CATEGORY, tiles, false);
257                 loadTilesForAction(context, user, MANUFACTURER_SETTINGS, cache,
258                         MANUFACTURER_DEFAULT_CATEGORY, tiles, false);
259             }
260             if (setup) {
261                 loadTilesForAction(context, user, EXTRA_SETTINGS_ACTION, cache, null, tiles, false);
262                 loadTilesForAction(context, user, IA_SETTINGS_ACTION, cache, null, tiles, false);
263             }
264         }
265 
266         final HashMap<String, DashboardCategory> categoryMap = new HashMap<>();
267         for (Tile tile : tiles) {
268             final String categoryKey = tile.getCategory();
269             DashboardCategory category = categoryMap.get(categoryKey);
270             if (category == null) {
271                 category = new DashboardCategory(categoryKey);
272 
273                 if (category == null) {
274                     Log.w(LOG_TAG, "Couldn't find category " + categoryKey);
275                     continue;
276                 }
277                 categoryMap.put(categoryKey, category);
278             }
279             category.addTile(tile);
280         }
281         final ArrayList<DashboardCategory> categories = new ArrayList<>(categoryMap.values());
282         for (DashboardCategory category : categories) {
283             category.sortTiles();
284         }
285 
286         if (DEBUG_TIMING) {
287             Log.d(LOG_TAG, "getCategories took "
288                     + (System.currentTimeMillis() - startTime) + " ms");
289         }
290         return categories;
291     }
292 
293     @VisibleForTesting
loadTilesForAction(Context context, UserHandle user, String action, Map<Pair<String, String>, Tile> addedCache, String defaultCategory, List<Tile> outTiles, boolean requireSettings)294     static void loadTilesForAction(Context context,
295             UserHandle user, String action, Map<Pair<String, String>, Tile> addedCache,
296             String defaultCategory, List<Tile> outTiles, boolean requireSettings) {
297         final Intent intent = new Intent(action);
298         if (requireSettings) {
299             intent.setPackage(SETTING_PKG);
300         }
301         loadActivityTiles(context, user, addedCache, defaultCategory, outTiles, intent);
302         loadProviderTiles(context, user, addedCache, defaultCategory, outTiles, intent);
303     }
304 
loadActivityTiles(Context context, UserHandle user, Map<Pair<String, String>, Tile> addedCache, String defaultCategory, List<Tile> outTiles, Intent intent)305     private static void loadActivityTiles(Context context,
306             UserHandle user, Map<Pair<String, String>, Tile> addedCache,
307             String defaultCategory, List<Tile> outTiles, Intent intent) {
308         final PackageManager pm = context.getPackageManager();
309         final List<ResolveInfo> results = pm.queryIntentActivitiesAsUser(intent,
310                 PackageManager.GET_META_DATA, user.getIdentifier());
311         for (ResolveInfo resolved : results) {
312             if (!resolved.system) {
313                 // Do not allow any app to add to settings, only system ones.
314                 continue;
315             }
316             final ActivityInfo activityInfo = resolved.activityInfo;
317             final Bundle metaData = activityInfo.metaData;
318             loadTile(user, addedCache, defaultCategory, outTiles, intent, metaData, activityInfo);
319         }
320     }
321 
loadProviderTiles(Context context, UserHandle user, Map<Pair<String, String>, Tile> addedCache, String defaultCategory, List<Tile> outTiles, Intent intent)322     private static void loadProviderTiles(Context context,
323             UserHandle user, Map<Pair<String, String>, Tile> addedCache,
324             String defaultCategory, List<Tile> outTiles, Intent intent) {
325         final PackageManager pm = context.getPackageManager();
326         final List<ResolveInfo> results = pm.queryIntentContentProvidersAsUser(intent,
327                 0 /* flags */, user.getIdentifier());
328         for (ResolveInfo resolved : results) {
329             if (!resolved.system) {
330                 // Do not allow any app to add to settings, only system ones.
331                 continue;
332             }
333             final ProviderInfo providerInfo = resolved.providerInfo;
334             final List<Bundle> switchData = getSwitchDataFromProvider(context,
335                     providerInfo.authority);
336             if (switchData == null || switchData.isEmpty()) {
337                 continue;
338             }
339             for (Bundle metaData : switchData) {
340                 loadTile(user, addedCache, defaultCategory, outTiles, intent, metaData,
341                         providerInfo);
342             }
343         }
344     }
345 
loadTile(UserHandle user, Map<Pair<String, String>, Tile> addedCache, String defaultCategory, List<Tile> outTiles, Intent intent, Bundle metaData, ComponentInfo componentInfo)346     private static void loadTile(UserHandle user, Map<Pair<String, String>, Tile> addedCache,
347             String defaultCategory, List<Tile> outTiles, Intent intent, Bundle metaData,
348             ComponentInfo componentInfo) {
349         // Skip loading tile if the component is tagged primary_profile_only but not running on
350         // the current user.
351         if (user.getIdentifier() != ActivityManager.getCurrentUser()
352                 && Tile.isPrimaryProfileOnly(componentInfo.metaData)) {
353             Log.w(LOG_TAG, "Found " + componentInfo.name + " for intent "
354                     + intent + " is primary profile only, skip loading tile for uid "
355                     + user.getIdentifier());
356             return;
357         }
358 
359         String categoryKey = defaultCategory;
360         // Load category
361         if ((metaData == null || !metaData.containsKey(EXTRA_CATEGORY_KEY))
362                 && categoryKey == null) {
363             Log.w(LOG_TAG, "Found " + componentInfo.name + " for intent "
364                     + intent + " missing metadata "
365                     + (metaData == null ? "" : EXTRA_CATEGORY_KEY));
366             return;
367         } else {
368             categoryKey = metaData.getString(EXTRA_CATEGORY_KEY);
369         }
370 
371         final boolean isProvider = componentInfo instanceof ProviderInfo;
372         final Pair<String, String> key = isProvider
373                 ? new Pair<>(((ProviderInfo) componentInfo).authority,
374                         metaData.getString(META_DATA_PREFERENCE_KEYHINT))
375                 : new Pair<>(componentInfo.packageName, componentInfo.name);
376         Tile tile = addedCache.get(key);
377         if (tile == null) {
378             tile = isProvider
379                     ? new ProviderTile((ProviderInfo) componentInfo, categoryKey, metaData)
380                     : new ActivityTile((ActivityInfo) componentInfo, categoryKey);
381             addedCache.put(key, tile);
382         } else {
383             tile.setMetaData(metaData);
384         }
385 
386         if (!tile.userHandle.contains(user)) {
387             tile.userHandle.add(user);
388         }
389         if (!outTiles.contains(tile)) {
390             outTiles.add(tile);
391         }
392     }
393 
394     /** Returns the switch data of the key specified from the provider */
395     // TODO(b/144732809): rearrange methods by access level modifiers
getSwitchDataFromProvider(Context context, String authority, String key)396     static Bundle getSwitchDataFromProvider(Context context, String authority, String key) {
397         final Map<String, IContentProvider> providerMap = new ArrayMap<>();
398         final Uri uri = buildUri(authority, SwitchesProvider.METHOD_GET_SWITCH_DATA, key);
399         return getBundleFromUri(context, uri, providerMap, null /* bundle */);
400     }
401 
402     /** Returns all switch data from the provider */
getSwitchDataFromProvider(Context context, String authority)403     private static List<Bundle> getSwitchDataFromProvider(Context context, String authority) {
404         final Map<String, IContentProvider> providerMap = new ArrayMap<>();
405         final Uri uri = buildUri(authority, SwitchesProvider.METHOD_GET_SWITCH_DATA);
406         final Bundle result = getBundleFromUri(context, uri, providerMap, null /* bundle */);
407         return result != null
408                 ? result.getParcelableArrayList(SwitchesProvider.EXTRA_SWITCH_DATA)
409                 : null;
410     }
411 
412     /**
413      * Returns the complete uri from the meta data key of the tile.
414      *
415      * A complete uri should contain at least one path segment and be one of the following types:
416      *      content://authority/method
417      *      content://authority/method/key
418      *
419      * If the uri from the tile is not complete, build a uri by the default method and the
420      * preference key.
421      *
422      * @param tile          Tile which contains meta data
423      * @param metaDataKey   Key mapping to the uri in meta data
424      * @param defaultMethod Method to be attached to the uri by default if it has no path segment
425      * @return Uri associated with the key
426      */
getCompleteUri(Tile tile, String metaDataKey, String defaultMethod)427     public static Uri getCompleteUri(Tile tile, String metaDataKey, String defaultMethod) {
428         final String uriString = tile.getMetaData().getString(metaDataKey);
429         if (TextUtils.isEmpty(uriString)) {
430             return null;
431         }
432 
433         final Uri uri = Uri.parse(uriString);
434         final List<String> pathSegments = uri.getPathSegments();
435         if (pathSegments != null && !pathSegments.isEmpty()) {
436             return uri;
437         }
438 
439         final String key = tile.getMetaData().getString(META_DATA_PREFERENCE_KEYHINT);
440         if (TextUtils.isEmpty(key)) {
441             Log.w(LOG_TAG, "Please specify the meta-data " + META_DATA_PREFERENCE_KEYHINT
442                     + " in AndroidManifest.xml for " + uriString);
443             return buildUri(uri.getAuthority(), defaultMethod);
444         }
445         return buildUri(uri.getAuthority(), defaultMethod, key);
446     }
447 
buildUri(String authority, String method, String key)448     static Uri buildUri(String authority, String method, String key) {
449         return new Uri.Builder()
450                 .scheme(ContentResolver.SCHEME_CONTENT)
451                 .authority(authority)
452                 .appendPath(method)
453                 .appendPath(key)
454                 .build();
455     }
456 
buildUri(String authority, String method)457     private static Uri buildUri(String authority, String method) {
458         return new Uri.Builder()
459                 .scheme(ContentResolver.SCHEME_CONTENT)
460                 .authority(authority)
461                 .appendPath(method)
462                 .build();
463     }
464 
465     /**
466      * Gets the icon package name and resource id from content provider.
467      *
468      * @param context     context
469      * @param packageName package name of the target activity
470      * @param uri         URI for the content provider
471      * @param providerMap Maps URI authorities to providers
472      * @return package name and resource id of the icon specified
473      */
getIconFromUri(Context context, String packageName, Uri uri, Map<String, IContentProvider> providerMap)474     public static Pair<String, Integer> getIconFromUri(Context context, String packageName,
475             Uri uri, Map<String, IContentProvider> providerMap) {
476         final Bundle bundle = getBundleFromUri(context, uri, providerMap, null /* bundle */);
477         if (bundle == null) {
478             return null;
479         }
480         final String iconPackageName = bundle.getString(EXTRA_PREFERENCE_ICON_PACKAGE);
481         if (TextUtils.isEmpty(iconPackageName)) {
482             return null;
483         }
484         int resId = bundle.getInt(META_DATA_PREFERENCE_ICON, 0);
485         if (resId == 0) {
486             return null;
487         }
488         // Icon can either come from the target package or from the Settings app.
489         if (iconPackageName.equals(packageName)
490                 || iconPackageName.equals(context.getPackageName())) {
491             return Pair.create(iconPackageName, resId);
492         }
493         return null;
494     }
495 
496     /**
497      * Gets text associated with the input key from the content provider.
498      *
499      * @param context     context
500      * @param uri         URI for the content provider
501      * @param providerMap Maps URI authorities to providers
502      * @param key         Key mapping to the text in bundle returned by the content provider
503      * @return Text associated with the key, if returned by the content provider
504      */
getTextFromUri(Context context, Uri uri, Map<String, IContentProvider> providerMap, String key)505     public static String getTextFromUri(Context context, Uri uri,
506             Map<String, IContentProvider> providerMap, String key) {
507         final Bundle bundle = getBundleFromUri(context, uri, providerMap, null /* bundle */);
508         return (bundle != null) ? bundle.getString(key) : null;
509     }
510 
511     /**
512      * Gets boolean associated with the input key from the content provider.
513      *
514      * @param context     context
515      * @param uri         URI for the content provider
516      * @param providerMap Maps URI authorities to providers
517      * @param key         Key mapping to the text in bundle returned by the content provider
518      * @return Boolean associated with the key, if returned by the content provider
519      */
getBooleanFromUri(Context context, Uri uri, Map<String, IContentProvider> providerMap, String key)520     public static boolean getBooleanFromUri(Context context, Uri uri,
521             Map<String, IContentProvider> providerMap, String key) {
522         final Bundle bundle = getBundleFromUri(context, uri, providerMap, null /* bundle */);
523         return (bundle != null) ? bundle.getBoolean(key) : false;
524     }
525 
526     /**
527      * Puts boolean associated with the input key to the content provider.
528      *
529      * @param context     context
530      * @param uri         URI for the content provider
531      * @param providerMap Maps URI authorities to providers
532      * @param key         Key mapping to the text in bundle returned by the content provider
533      * @param value       Boolean associated with the key
534      * @return Bundle associated with the action, if returned by the content provider
535      */
putBooleanToUriAndGetResult(Context context, Uri uri, Map<String, IContentProvider> providerMap, String key, boolean value)536     public static Bundle putBooleanToUriAndGetResult(Context context, Uri uri,
537             Map<String, IContentProvider> providerMap, String key, boolean value) {
538         final Bundle bundle = new Bundle();
539         bundle.putBoolean(key, value);
540         return getBundleFromUri(context, uri, providerMap, bundle);
541     }
542 
getBundleFromUri(Context context, Uri uri, Map<String, IContentProvider> providerMap, Bundle bundle)543     private static Bundle getBundleFromUri(Context context, Uri uri,
544             Map<String, IContentProvider> providerMap, Bundle bundle) {
545         final Pair<String, String> args = getMethodAndKey(uri);
546         if (args == null) {
547             return null;
548         }
549         final String method = args.first;
550         final String key = args.second;
551         if (TextUtils.isEmpty(method)) {
552             return null;
553         }
554         final IContentProvider provider = getProviderFromUri(context, uri, providerMap);
555         if (provider == null) {
556             return null;
557         }
558         if (!TextUtils.isEmpty(key)) {
559             if (bundle == null) {
560                 bundle = new Bundle();
561             }
562             bundle.putString(META_DATA_PREFERENCE_KEYHINT, key);
563         }
564         try {
565             return provider.call(context.getAttributionSource(),
566                     uri.getAuthority(), method, uri.toString(), bundle);
567         } catch (RemoteException e) {
568             return null;
569         }
570     }
571 
getProviderFromUri(Context context, Uri uri, Map<String, IContentProvider> providerMap)572     private static IContentProvider getProviderFromUri(Context context, Uri uri,
573             Map<String, IContentProvider> providerMap) {
574         if (uri == null) {
575             return null;
576         }
577         final String authority = uri.getAuthority();
578         if (TextUtils.isEmpty(authority)) {
579             return null;
580         }
581         if (!providerMap.containsKey(authority)) {
582             providerMap.put(authority, context.getContentResolver().acquireUnstableProvider(uri));
583         }
584         return providerMap.get(authority);
585     }
586 
587     /** Returns method and key of the complete uri. */
getMethodAndKey(Uri uri)588     private static Pair<String, String> getMethodAndKey(Uri uri) {
589         if (uri == null) {
590             return null;
591         }
592         final List<String> pathSegments = uri.getPathSegments();
593         if (pathSegments == null || pathSegments.isEmpty()) {
594             return null;
595         }
596         final String method = pathSegments.get(0);
597         final String key = pathSegments.size() > 1 ? pathSegments.get(1) : null;
598         return Pair.create(method, key);
599     }
600 }
601