• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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