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