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