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