1 /* 2 * Copyright (C) 2024 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 17 package android.provider; 18 19 import static android.provider.VerificationLogsHelper.createIsNotNullLog; 20 import static android.provider.VerificationLogsHelper.createIsNotValidLog; 21 import static android.provider.VerificationLogsHelper.createIsNullLog; 22 import static android.provider.VerificationLogsHelper.logVerifications; 23 import static android.provider.VerificationLogsHelper.verifyCursorNotNullAndMediaCollectionIdPresent; 24 import static android.provider.VerificationLogsHelper.verifyMediaCollectionId; 25 import static android.provider.VerificationLogsHelper.verifyProjectionForCursor; 26 import static android.provider.VerificationLogsHelper.verifyTotalTimeForExecution; 27 28 import android.annotation.StringDef; 29 import android.content.Intent; 30 import android.content.res.AssetFileDescriptor; 31 import android.database.Cursor; 32 import android.graphics.BitmapFactory; 33 import android.graphics.Point; 34 import android.os.Bundle; 35 import android.os.ParcelFileDescriptor; 36 import android.os.SystemProperties; 37 import android.util.Log; 38 39 import com.android.providers.media.flags.Flags; 40 41 import java.lang.annotation.Retention; 42 import java.lang.annotation.RetentionPolicy; 43 import java.util.ArrayList; 44 import java.util.Arrays; 45 import java.util.HashMap; 46 import java.util.List; 47 import java.util.Map; 48 49 /** 50 * Provides helper methods that help verify that the received results from cloud provider 51 * implementations are staying true to contract by returning non null outputs and setting required 52 * extras/states in the result. 53 * 54 * Note: logs for local provider and not printed. 55 */ 56 final class CmpApiVerifier { 57 private static final String LOCAL_PROVIDER_AUTHORITY = 58 "com.android.providers.media.photopicker"; 59 isCloudMediaProviderLoggingEnabled()60 private static boolean isCloudMediaProviderLoggingEnabled() { 61 return (SystemProperties.getInt("ro.debuggable", 0) == 1) && Log.isLoggable( 62 "CloudMediaProvider", Log.VERBOSE); 63 } 64 65 /** 66 * Verifies and logs results received by CloudMediaProvider Apis. 67 * 68 * <p><b>Note:</b> It only logs the errors and does not throw any exceptions. 69 */ verifyApiResult(CmpApiResult result, long totalTimeTakenForExecution, String authority)70 static void verifyApiResult(CmpApiResult result, long totalTimeTakenForExecution, 71 String authority) { 72 // Do not perform any operation if the authority is of the local provider or when the 73 // logging is not enabled. 74 if (!LOCAL_PROVIDER_AUTHORITY.equals(authority) 75 && isCloudMediaProviderLoggingEnabled()) { 76 try { 77 ArrayList<String> verificationResult = new ArrayList<>(); 78 ArrayList<String> errors = new ArrayList<>(); 79 verifyTotalTimeForExecution(totalTimeTakenForExecution, 80 CMP_API_TO_THRESHOLD_MAP.get(result.getApi()), errors); 81 82 switch (result.getApi()) { 83 case CloudMediaProviderApis.OnGetCapabilities: { 84 verifyOnGetCapabilities(result.getBundle(), verificationResult, errors); 85 break; 86 } 87 case CloudMediaProviderApis.OnGetMediaCollectionInfo: { 88 verifyOnGetMediaCollectionInfo(result.getBundle(), verificationResult, 89 errors); 90 break; 91 } 92 case CloudMediaProviderApis.OnQueryMedia: { 93 verifyOnQueryMedia(result.getCursor(), verificationResult, errors); 94 break; 95 } 96 case CloudMediaProviderApis.OnQueryDeletedMedia: { 97 verifyOnQueryDeletedMedia(result.getCursor(), verificationResult, errors); 98 break; 99 } 100 case CloudMediaProviderApis.OnQueryAlbums: { 101 verifyOnQueryAlbums(result.getCursor(), verificationResult, errors); 102 break; 103 } 104 case CloudMediaProviderApis.OnOpenPreview: { 105 verifyOnOpenPreview(result.getAssetFileDescriptor(), result.getDimensions(), 106 verificationResult, errors); 107 break; 108 } 109 case CloudMediaProviderApis.OnOpenMedia: { 110 verifyOnOpenMedia(result.getParcelFileDescriptor(), verificationResult, 111 errors); 112 break; 113 } 114 case CloudMediaProviderApis.OnQueryMediaCategories: { 115 verifyOnQueryMediaCategories(result.getCursor(), 116 verificationResult, errors); 117 break; 118 } 119 case CloudMediaProviderApis.OnQueryMediaSets: { 120 verifyOnQueryMediaSets(result.getCursor(), 121 verificationResult, errors); 122 break; 123 } 124 case CloudMediaProviderApis.OnQuerySearchSuggestions: { 125 verifyOnQuerySearchSuggestions(result.getCursor(), 126 verificationResult, errors); 127 break; 128 } 129 case CloudMediaProviderApis.OnSearchMedia: { 130 verifyOnSearchMedia(result.getCursor(), 131 verificationResult, errors); 132 break; 133 } 134 case CloudMediaProviderApis.OnQueryMediaInMediaSet: { 135 verifyOnQueryMediaInMediaSet(result.getCursor(), 136 verificationResult, errors); 137 } 138 default: 139 throw new UnsupportedOperationException( 140 "The verification for requested API is not supported."); 141 } 142 logVerifications(authority, result.getApi(), totalTimeTakenForExecution, 143 verificationResult, errors); 144 } catch (Exception e) { 145 VerificationLogsHelper.logException(e.getMessage()); 146 } 147 } 148 } 149 150 /** 151 * Verifies the {@link CloudMediaProvider#onGetCapabilities()} API. 152 * 153 * Verifies the Capabilities object returned is non-null. 154 */ verifyOnGetCapabilities( Bundle outputBundle, List<String> verificationResult, List<String> errors)155 static void verifyOnGetCapabilities( 156 Bundle outputBundle, List<String> verificationResult, List<String> errors) { 157 158 // Only Verify if the flag for capabilities is on. 159 if (Flags.enableCloudMediaProviderCapabilities()) { 160 161 if (outputBundle != null 162 && outputBundle.containsKey( 163 CloudMediaProviderContract.EXTRA_PROVIDER_CAPABILITIES)) { 164 165 verificationResult.add("Capabilities is present."); 166 167 CloudMediaProviderContract.Capabilities capabilities = outputBundle 168 .getParcelable(CloudMediaProviderContract.EXTRA_PROVIDER_CAPABILITIES); 169 170 // Verify CMP search capabilities if the search flag is on. 171 if (Flags.cloudMediaProviderSearch()) { 172 if (capabilities.isAlbumsAsCategoryEnabled() 173 && !capabilities.isMediaCategoriesEnabled()) { 174 errors.add(createIsNotValidLog("Declared capabilities are invalid. " 175 + "AlbumsAsCategory capability can only be enabled when " 176 + "MediaCollections is enabled.")); 177 } else { 178 verificationResult.add("Declared Capabilities are valid."); 179 } 180 181 } 182 } else { 183 errors.add(createIsNullLog("Capabilities were not returned by OnGetCapabilities")); 184 } 185 } 186 } 187 188 /** 189 * Verifies OnGetMediaCollectionInfo API by performing and logging the following checks: 190 * 191 * <ul> 192 * <li>Received Bundle is not null.</li> 193 * <li>Bundle contains media collection ID: 194 * {@link CloudMediaProviderContract.MediaCollectionInfo#MEDIA_COLLECTION_ID}</li> 195 * <li>Bundle contains last sync generation: 196 * {@link CloudMediaProviderContract.MediaCollectionInfo#LAST_MEDIA_SYNC_GENERATION}</li> 197 * <li>Bundle contains account name: 198 * {@link CloudMediaProviderContract.MediaCollectionInfo#ACCOUNT_NAME}</li> 199 * <li>Bundle contains account configuration intent: 200 * {@link CloudMediaProviderContract.MediaCollectionInfo#ACCOUNT_CONFIGURATION_INTENT}</li> 201 * </ul> 202 */ verifyOnGetMediaCollectionInfo( Bundle outputBundle, List<String> verificationResult, List<String> errors )203 static void verifyOnGetMediaCollectionInfo( 204 Bundle outputBundle, List<String> verificationResult, List<String> errors 205 ) { 206 if (outputBundle != null) { 207 verificationResult.add(createIsNotNullLog("Received bundle")); 208 209 String mediaCollectionId = outputBundle.getString( 210 CloudMediaProviderContract.MediaCollectionInfo.MEDIA_COLLECTION_ID 211 ); 212 // verifies media collection id. 213 verifyMediaCollectionId( 214 mediaCollectionId, 215 verificationResult, 216 errors 217 ); 218 219 long syncGeneration = outputBundle.getLong( 220 CloudMediaProviderContract.MediaCollectionInfo.LAST_MEDIA_SYNC_GENERATION, 221 -1L 222 ); 223 224 // verified last sync generation. 225 if (syncGeneration != -1L) { 226 if (syncGeneration >= 0) { 227 verificationResult.add( 228 CloudMediaProviderContract.MediaCollectionInfo 229 .LAST_MEDIA_SYNC_GENERATION + " : " + syncGeneration 230 ); 231 } else { 232 errors.add( 233 CloudMediaProviderContract.MediaCollectionInfo 234 .LAST_MEDIA_SYNC_GENERATION + " is < 0" 235 ); 236 } 237 } else { 238 errors.add( 239 createIsNotValidLog( 240 CloudMediaProviderContract.MediaCollectionInfo 241 .LAST_MEDIA_SYNC_GENERATION 242 ) 243 ); 244 } 245 246 String accountName = outputBundle.getString( 247 CloudMediaProviderContract.MediaCollectionInfo.ACCOUNT_NAME 248 ); 249 250 // verifies account name. 251 if (accountName != null) { 252 if (!accountName.isEmpty()) { 253 // In future if the cloud media provider is extended to have multiple 254 // accounts then logging account name itself might be a useful 255 // information to log but for now only logging its presence. 256 verificationResult.add( 257 CloudMediaProviderContract.MediaCollectionInfo.ACCOUNT_NAME 258 + " is present " 259 ); 260 } else { 261 errors.add( 262 CloudMediaProviderContract.MediaCollectionInfo.ACCOUNT_NAME 263 + " is empty" 264 ); 265 } 266 } else { 267 errors.add(createIsNullLog( 268 CloudMediaProviderContract.MediaCollectionInfo.ACCOUNT_NAME 269 ) 270 ); 271 } 272 273 Intent intent = outputBundle.getParcelable( 274 CloudMediaProviderContract.MediaCollectionInfo.ACCOUNT_CONFIGURATION_INTENT 275 ); 276 // verified the presence of account configuration intent. 277 if (intent != null) { 278 verificationResult.add( 279 CloudMediaProviderContract.MediaCollectionInfo 280 .ACCOUNT_CONFIGURATION_INTENT 281 + " is present." 282 ); 283 } else { 284 errors.add(createIsNullLog( 285 CloudMediaProviderContract.MediaCollectionInfo 286 .ACCOUNT_CONFIGURATION_INTENT 287 ) 288 ); 289 } 290 291 } else { 292 errors.add(createIsNullLog("Received output bundle")); 293 } 294 } 295 296 /** 297 * Verifies OnQueryMedia API by performing and logging the following checks: 298 * 299 * <ul> 300 * <li>Received Cursor is not null.</li> 301 * <li>Cursor contains non empty media collection ID: 302 * {@link CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID}</li> 303 * <li>Projection for cursor is as expected: 304 * {@link CloudMediaProviderContract.MediaColumns#ALL_PROJECTION}</li> 305 * <li>Logs count of rows in the cursor, if cursor is non null.</li> 306 * </ul> 307 */ verifyOnQueryMedia( Cursor c, List<String> verificationResult, List<String> errors )308 static void verifyOnQueryMedia( 309 Cursor c, List<String> verificationResult, List<String> errors 310 ) { 311 if (c != null) { 312 verifyCursorNotNullAndMediaCollectionIdPresent( 313 c, 314 verificationResult, 315 errors 316 ); 317 // verify that all columns are present per CloudMediaProviderContract.AlbumColumns 318 verifyProjectionForCursor( 319 c, 320 Arrays.asList(CloudMediaProviderContract.MediaColumns.ALL_PROJECTION), 321 errors 322 ); 323 } else { 324 errors.add(createIsNullLog("Received cursor")); 325 } 326 } 327 328 /** 329 * Verifies OnQueryDeletedMedia API by performing and logging the following checks: 330 * 331 * <ul> 332 * <li>Received Cursor is not null.</li> 333 * <li>Cursor contains non empty media collection ID: 334 * {@link CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID}</li> 335 * <li>Logs count of rows in the cursor, if cursor is non null.</li> 336 * </ul> 337 */ verifyOnQueryDeletedMedia( Cursor c, List<String> verificationResult, List<String> errors )338 static void verifyOnQueryDeletedMedia( 339 Cursor c, List<String> verificationResult, List<String> errors 340 ) { 341 verifyCursorNotNullAndMediaCollectionIdPresent(c, verificationResult, errors); 342 } 343 344 /** 345 * Verifies OnQueryAlbums API by performing and logging the following checks: 346 * 347 * <ul> 348 * <li>Received Cursor is not null.</li> 349 * <li>Cursor contains non empty media collection ID: 350 * {@link CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID}</li> 351 * <li>Projection for cursor is as expected: 352 * {@link CloudMediaProviderContract.AlbumColumns#ALL_PROJECTION}</li> 353 * <li>Logs count of rows in the cursor and the album names, if cursor is non null.</li> 354 * </ul> 355 */ verifyOnQueryAlbums( Cursor c, List<String> verificationResult, List<String> errors )356 static void verifyOnQueryAlbums( 357 Cursor c, List<String> verificationResult, List<String> errors 358 ) { 359 if (c != null) { 360 verifyCursorNotNullAndMediaCollectionIdPresent(c, verificationResult, errors); 361 362 // verify that all columns are present per CloudMediaProviderContract.AlbumColumns 363 verifyProjectionForCursor( 364 c, 365 Arrays.asList(CloudMediaProviderContract.AlbumColumns.ALL_PROJECTION), 366 errors 367 ); 368 if (c.getCount() > 0) { 369 // Only log album data if projection and other checks have returned positive 370 // results. 371 StringBuilder strBuilder = new StringBuilder("Albums present and their count: "); 372 int columnIndexForId = c.getColumnIndex(CloudMediaProviderContract.AlbumColumns.ID); 373 int columnIndexForItemCount = c.getColumnIndex( 374 CloudMediaProviderContract.AlbumColumns.MEDIA_COUNT); 375 c.moveToPosition(-1); 376 while (c.moveToNext()) { 377 strBuilder.append("\n\t\t\t" + c.getString(columnIndexForId) + ", " + c.getLong( 378 columnIndexForItemCount)); 379 } 380 c.moveToPosition(-1); 381 verificationResult.add(strBuilder.toString()); 382 } 383 } 384 } 385 386 /** 387 * Verifies OnQueryMediaCategories API by performing and logging the following checks: 388 * 389 * <ul> 390 * <li>Received Cursor is not null.</li> 391 * <li>Cursor contains non empty media collection ID: 392 * {@link CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID}</li> 393 * <li>Projection for cursor is as expected: 394 * {@link CloudMediaProviderContract.MediaCategoryColumns#ALL_PROJECTION}</li> 395 * </ul> 396 */ verifyOnQueryMediaCategories( Cursor cursor, List<String> verificationResult, List<String> errors )397 static void verifyOnQueryMediaCategories( 398 Cursor cursor, List<String> verificationResult, List<String> errors 399 ) { 400 verifyCursorNotNullAndMediaCollectionIdPresent(cursor, verificationResult, errors); 401 if (cursor != null && Flags.cloudMediaProviderSearch()) { 402 403 verifyProjectionForCursor( 404 cursor, 405 Arrays.asList(CloudMediaProviderContract.MediaCategoryColumns.ALL_PROJECTION), 406 errors 407 ); 408 } 409 } 410 411 /** 412 * Verifies OnQueryMediaSets API by performing and logging the following checks: 413 * 414 * <ul> 415 * <li>Received Cursor is not null.</li> 416 * <li>Cursor contains non empty media collection ID: 417 * {@link CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID}</li> 418 * <li>Projection for cursor is as expected: 419 * {@link CloudMediaProviderContract.MediaSetColumns#ALL_PROJECTION}</li> 420 * </ul> 421 */ verifyOnQueryMediaSets( Cursor cursor, List<String> verificationResult, List<String> errors )422 static void verifyOnQueryMediaSets( 423 Cursor cursor, List<String> verificationResult, List<String> errors 424 ) { 425 verifyCursorNotNullAndMediaCollectionIdPresent(cursor, verificationResult, errors); 426 if (cursor != null && Flags.cloudMediaProviderSearch()) { 427 428 verifyProjectionForCursor( 429 cursor, 430 Arrays.asList(CloudMediaProviderContract.MediaSetColumns.ALL_PROJECTION), 431 errors 432 ); 433 } 434 } 435 436 /** 437 * Verifies OnQuerySearchSuggestions API by performing and logging the following checks: 438 * 439 * <ul> 440 * <li>Received Cursor is not null.</li> 441 * <li>Cursor contains non empty media collection ID: 442 * {@link CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID}</li> 443 * <li>Projection for cursor is as expected: 444 * {@link CloudMediaProviderContract.SearchSuggestionColumns#ALL_PROJECTION}</li> 445 * </ul> 446 */ verifyOnQuerySearchSuggestions( Cursor cursor, List<String> verificationResult, List<String> errors )447 static void verifyOnQuerySearchSuggestions( 448 Cursor cursor, List<String> verificationResult, List<String> errors 449 ) { 450 verifyCursorNotNullAndMediaCollectionIdPresent(cursor, verificationResult, errors); 451 if (cursor != null && Flags.cloudMediaProviderSearch()) { 452 453 verifyProjectionForCursor( 454 cursor, 455 Arrays.asList( 456 CloudMediaProviderContract.SearchSuggestionColumns.ALL_PROJECTION), 457 errors 458 ); 459 } 460 } 461 462 /** 463 * Verifies OnSearchMedia API by performing and logging the following checks: 464 * 465 * <ul> 466 * <li>Received Cursor is not null.</li> 467 * <li>Cursor contains non empty media collection ID: 468 * {@link CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID}</li> 469 * <li>Projection for cursor is as expected: 470 * {@link CloudMediaProviderContract.MediaColumns#ALL_PROJECTION}</li> 471 * </ul> 472 */ verifyOnSearchMedia( Cursor cursor, List<String> verificationResult, List<String> errors )473 static void verifyOnSearchMedia( 474 Cursor cursor, List<String> verificationResult, List<String> errors 475 ) { 476 verifyCursorNotNullAndMediaCollectionIdPresent(cursor, verificationResult, errors); 477 if (cursor != null && Flags.cloudMediaProviderSearch()) { 478 479 verifyProjectionForCursor( 480 cursor, 481 Arrays.asList( 482 CloudMediaProviderContract.MediaColumns.ALL_PROJECTION), 483 errors 484 ); 485 } 486 } 487 488 /** 489 * Verifies OnQueryMediaInMediaSet by performing and logging the following checks: 490 * 491 * <ul> 492 * <li>Received Cursor is not null.</li> 493 * <li>Cursor contains non empty media collection ID: 494 * {@link CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID}</li> 495 * <li>Projection for cursor is as expected: 496 * {@link CloudMediaProviderContract.MediaColumns#ALL_PROJECTION}</li> 497 * </ul> 498 */ verifyOnQueryMediaInMediaSet( Cursor cursor, List<String> verificationResult, List<String> errors )499 static void verifyOnQueryMediaInMediaSet( 500 Cursor cursor, List<String> verificationResult, List<String> errors 501 ) { 502 verifyCursorNotNullAndMediaCollectionIdPresent(cursor, verificationResult, errors); 503 if (cursor != null && Flags.cloudMediaProviderSearch()) { 504 505 verifyProjectionForCursor( 506 cursor, 507 Arrays.asList( 508 CloudMediaProviderContract.MediaColumns.ALL_PROJECTION), 509 errors 510 ); 511 } 512 } 513 514 515 516 /** 517 * Verifies OnOpenPreview API by performing and logging the following checks: 518 * 519 * <ul> 520 * <li>Received AssetFileDescriptor is not null.</li> 521 * <li>Logs size of the thumbnail.</li> 522 * </ul> 523 */ verifyOnOpenPreview( AssetFileDescriptor assetFileDescriptor, Point expectedSize, List<String> verificationResult, List<String> errors )524 static void verifyOnOpenPreview( 525 AssetFileDescriptor assetFileDescriptor, 526 Point expectedSize, List<String> verificationResult, List<String> errors 527 ) { 528 if (assetFileDescriptor == null) { 529 errors.add(createIsNullLog("Received AssetFileDescriptor")); 530 } else { 531 verificationResult.add(createIsNotNullLog("Received AssetFileDescriptor")); 532 BitmapFactory.Options options = new BitmapFactory.Options(); 533 options.inJustDecodeBounds = true; // Only decode the bounds 534 BitmapFactory.decodeFileDescriptor(assetFileDescriptor.getFileDescriptor(), null, 535 options); 536 537 int width = options.outWidth; 538 int height = options.outHeight; 539 540 verificationResult.add("Dimensions of file received: " 541 + "Width: " + width + ", Height: " + height + ", expected: " + expectedSize.x 542 + ", " + expectedSize.y); 543 } 544 } 545 546 /** 547 * Verifies OnOpenMedia API by performing and logging the following checks: 548 * 549 * <ul> 550 * <li>Received ParcelFileDescriptor is not null.</li> 551 * </ul> 552 */ verifyOnOpenMedia( ParcelFileDescriptor fd, List<String> verificationResult, List<String> errors )553 static void verifyOnOpenMedia( 554 ParcelFileDescriptor fd, 555 List<String> verificationResult, List<String> errors 556 ) { 557 if (fd == null) { 558 errors.add(createIsNullLog("Received FileDescriptor")); 559 } else { 560 verificationResult.add(createIsNotNullLog("Received FileDescriptor")); 561 } 562 } 563 564 @StringDef({ 565 CloudMediaProviderApis.OnGetCapabilities, 566 CloudMediaProviderApis.OnGetMediaCollectionInfo, 567 CloudMediaProviderApis.OnQueryMedia, 568 CloudMediaProviderApis.OnQueryDeletedMedia, 569 CloudMediaProviderApis.OnQueryAlbums, 570 CloudMediaProviderApis.OnOpenPreview, 571 CloudMediaProviderApis.OnOpenMedia, 572 CloudMediaProviderApis.OnQueryMediaCategories, 573 CloudMediaProviderApis.OnQueryMediaSets, 574 CloudMediaProviderApis.OnQuerySearchSuggestions, 575 CloudMediaProviderApis.OnSearchMedia, 576 CloudMediaProviderApis.OnQueryMediaInMediaSet, 577 }) 578 @Retention(RetentionPolicy.SOURCE) 579 @interface CloudMediaProviderApis { 580 String OnGetCapabilities = "OnGetCapabilities"; 581 String OnGetMediaCollectionInfo = "onGetMediaCollectionInfo"; 582 String OnQueryMedia = "onQueryMedia"; 583 String OnQueryDeletedMedia = "onQueryDeletedMedia"; 584 String OnQueryAlbums = "onQueryAlbums"; 585 String OnOpenPreview = "onOpenPreview"; 586 String OnOpenMedia = "onOpenMedia"; 587 String OnQueryMediaCategories = "onQueryMediaCategories"; 588 String OnQueryMediaSets = "onQueryMediaSets"; 589 String OnQuerySearchSuggestions = "onQuerySearchSuggestions"; 590 String OnSearchMedia = "onSearchMedia"; 591 String OnQueryMediaInMediaSet = "onQueryMediaInMediaSet"; 592 } 593 594 private static final Map<String, Long> CMP_API_TO_THRESHOLD_MAP = new HashMap<>(Map.ofEntries( 595 Map.entry(CloudMediaProviderApis.OnGetCapabilities, 200L), 596 Map.entry(CloudMediaProviderApis.OnGetMediaCollectionInfo, 200L), 597 Map.entry(CloudMediaProviderApis.OnQueryMedia, 500L), 598 Map.entry(CloudMediaProviderApis.OnQueryDeletedMedia, 500L), 599 Map.entry(CloudMediaProviderApis.OnQueryAlbums, 500L), 600 Map.entry(CloudMediaProviderApis.OnOpenPreview, 1000L), 601 Map.entry(CloudMediaProviderApis.OnOpenMedia, 1000L), 602 Map.entry(CloudMediaProviderApis.OnQueryMediaCategories, 500L), 603 Map.entry(CloudMediaProviderApis.OnQueryMediaSets, 500L), 604 Map.entry(CloudMediaProviderApis.OnQuerySearchSuggestions, 300L), 605 Map.entry(CloudMediaProviderApis.OnSearchMedia, 3000L), 606 Map.entry(CloudMediaProviderApis.OnQueryMediaInMediaSet, 500L) 607 )); 608 609 } 610