• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.CloudMediaProviderContract.EXTRA_ASYNC_CONTENT_PROVIDER;
20 import static android.provider.CloudMediaProviderContract.EXTRA_AUTHORITY;
21 import static android.provider.CloudMediaProviderContract.EXTRA_ERROR_MESSAGE;
22 import static android.provider.CloudMediaProviderContract.EXTRA_FILE_DESCRIPTOR;
23 import static android.provider.CloudMediaProviderContract.EXTRA_LOOPING_PLAYBACK_ENABLED;
24 import static android.provider.CloudMediaProviderContract.EXTRA_MEDIASTORE_THUMB;
25 import static android.provider.CloudMediaProviderContract.EXTRA_SURFACE_CONTROLLER;
26 import static android.provider.CloudMediaProviderContract.EXTRA_SURFACE_CONTROLLER_AUDIO_MUTE_ENABLED;
27 import static android.provider.CloudMediaProviderContract.EXTRA_SURFACE_STATE_CALLBACK;
28 import static android.provider.CloudMediaProviderContract.METHOD_CREATE_SURFACE_CONTROLLER;
29 import static android.provider.CloudMediaProviderContract.METHOD_GET_ASYNC_CONTENT_PROVIDER;
30 import static android.provider.CloudMediaProviderContract.METHOD_GET_MEDIA_COLLECTION_INFO;
31 import static android.provider.CloudMediaProviderContract.URI_PATH_ALBUM;
32 import static android.provider.CloudMediaProviderContract.URI_PATH_DELETED_MEDIA;
33 import static android.provider.CloudMediaProviderContract.URI_PATH_MEDIA;
34 import static android.provider.CloudMediaProviderContract.URI_PATH_MEDIA_COLLECTION_INFO;
35 import static android.provider.CloudMediaProviderContract.URI_PATH_SURFACE_CONTROLLER;
36 
37 import android.annotation.DurationMillisLong;
38 import android.annotation.IntDef;
39 import android.annotation.NonNull;
40 import android.annotation.Nullable;
41 import android.annotation.SuppressLint;
42 import android.content.ContentProvider;
43 import android.content.ContentResolver;
44 import android.content.ContentValues;
45 import android.content.Context;
46 import android.content.UriMatcher;
47 import android.content.pm.ProviderInfo;
48 import android.content.res.AssetFileDescriptor;
49 import android.database.Cursor;
50 import android.graphics.PixelFormat;
51 import android.graphics.Point;
52 import android.media.MediaPlayer;
53 import android.net.Uri;
54 import android.os.Bundle;
55 import android.os.CancellationSignal;
56 import android.os.IBinder;
57 import android.os.ParcelFileDescriptor;
58 import android.os.RemoteCallback;
59 import android.util.DisplayMetrics;
60 import android.util.Log;
61 import android.view.Surface;
62 import android.view.SurfaceHolder;
63 
64 import java.io.FileNotFoundException;
65 import java.lang.annotation.Retention;
66 import java.lang.annotation.RetentionPolicy;
67 import java.util.Objects;
68 
69 /**
70  * Base class for a cloud media provider. A cloud media provider offers read-only access to durable
71  * media files, specifically photos and videos stored on a local disk, or files in a cloud storage
72  * service. To create a cloud media provider, extend this class, implement the abstract methods,
73  * and add it to your manifest like this:
74  *
75  * <pre class="prettyprint">&lt;manifest&gt;
76  *    ...
77  *    &lt;application&gt;
78  *        ...
79  *        &lt;provider
80  *            android:name="com.example.MyCloudProvider"
81  *            android:authorities="com.example.mycloudprovider"
82  *            android:exported="true"
83  *            android:permission="com.android.providers.media.permission.MANAGE_CLOUD_MEDIA_PROVIDERS"
84  *            &lt;intent-filter&gt;
85  *                &lt;action android:name="android.content.action.CLOUD_MEDIA_PROVIDER" /&gt;
86  *            &lt;/intent-filter&gt;
87  *        &lt;/provider&gt;
88  *        ...
89  *    &lt;/application&gt;
90  *&lt;/manifest&gt;</pre>
91  * <p>
92  * When defining your provider, you must protect it with the
93  * {@link CloudMediaProviderContract#MANAGE_CLOUD_MEDIA_PROVIDERS_PERMISSION}, which is a permission
94  * only the system can obtain, trying to define an unprotected {@link CloudMediaProvider} will
95  * result in a {@link SecurityException}.
96  * <p>
97  * Applications cannot use a cloud media provider directly; they must go through
98  * {@link MediaStore#ACTION_PICK_IMAGES} which requires a user to actively navigate and select
99  * media items. When a user selects a media item through that UI, the system issues narrow URI
100  * permission grants to the requesting application.
101  * <h3>Media items</h3>
102  * <p>
103  * A media item must be an openable stream (with a specific MIME type). Media items can belong to
104  * zero or more albums. Albums cannot contain other albums.
105  * <p>
106  * Each item under a provider is uniquely referenced by its media or album id, which must not
107  * change which must be unique across all collection IDs as returned by
108  * {@link #onGetMediaCollectionInfo}.
109  *
110  * @see MediaStore#ACTION_PICK_IMAGES
111  */
112 public abstract class CloudMediaProvider extends ContentProvider {
113     private static final String TAG = "CloudMediaProvider";
114 
115     private static final int MATCH_MEDIAS = 1;
116     private static final int MATCH_DELETED_MEDIAS = 2;
117     private static final int MATCH_ALBUMS = 3;
118     private static final int MATCH_MEDIA_COLLECTION_INFO = 4;
119     private static final int MATCH_SURFACE_CONTROLLER = 5;
120 
121     private static final boolean DEFAULT_LOOPING_PLAYBACK_ENABLED = true;
122     private static final boolean DEFAULT_SURFACE_CONTROLLER_AUDIO_MUTE_ENABLED = false;
123 
124     private final UriMatcher mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
125     private volatile int mMediaStoreAuthorityAppId;
126 
127     private final AsyncContentProviderWrapper mAsyncContentProviderWrapper =
128             new AsyncContentProviderWrapper();
129 
130     /**
131      * Implementation is provided by the parent class. Cannot be overridden.
132      */
133     @Override
attachInfo(@onNull Context context, @NonNull ProviderInfo info)134     public final void attachInfo(@NonNull Context context, @NonNull ProviderInfo info) {
135         registerAuthority(info.authority);
136 
137         super.attachInfo(context, info);
138     }
139 
registerAuthority(String authority)140     private void registerAuthority(String authority) {
141         mMatcher.addURI(authority, URI_PATH_MEDIA, MATCH_MEDIAS);
142         mMatcher.addURI(authority, URI_PATH_DELETED_MEDIA, MATCH_DELETED_MEDIAS);
143         mMatcher.addURI(authority, URI_PATH_ALBUM, MATCH_ALBUMS);
144         mMatcher.addURI(authority, URI_PATH_MEDIA_COLLECTION_INFO, MATCH_MEDIA_COLLECTION_INFO);
145         mMatcher.addURI(authority, URI_PATH_SURFACE_CONTROLLER, MATCH_SURFACE_CONTROLLER);
146     }
147 
148     /**
149      * Returns {@link Bundle} containing binder to {@link IAsyncContentProvider}.
150      *
151      * @hide
152      */
153     @NonNull
onGetAsyncContentProvider()154     public final Bundle onGetAsyncContentProvider() {
155         Bundle bundle = new Bundle();
156         bundle.putBinder(EXTRA_ASYNC_CONTENT_PROVIDER, mAsyncContentProviderWrapper.asBinder());
157         return bundle;
158     }
159 
160     /**
161      * Returns metadata about the media collection itself.
162      * <p>
163      * This is useful for the OS to determine if its cache of media items in the collection is
164      * still valid and if a full or incremental sync is required with {@link #onQueryMedia}.
165      * <p>
166      * This method might be called by the OS frequently and is performance critical, hence it should
167      * avoid long running operations.
168      * <p>
169      * If the provider handled any filters in {@code extras}, it must add the key to the
170      * {@link ContentResolver#EXTRA_HONORED_ARGS} as part of the returned {@link Bundle}.
171      *
172      * @param extras containing keys to filter result:
173      * <ul>
174      * <li> {@link CloudMediaProviderContract#EXTRA_ALBUM_ID}
175      * </ul>
176      *
177      * @return {@link Bundle} containing {@link CloudMediaProviderContract.MediaCollectionInfo}
178      * <ul>
179      * <li> {@link CloudMediaProviderContract.MediaCollectionInfo#MEDIA_COLLECTION_ID}
180      * <li> {@link CloudMediaProviderContract.MediaCollectionInfo#LAST_MEDIA_SYNC_GENERATION}
181      * <li> {@link CloudMediaProviderContract.MediaCollectionInfo#ACCOUNT_NAME}
182      * <li> {@link CloudMediaProviderContract.MediaCollectionInfo#ACCOUNT_CONFIGURATION_INTENT}
183      * </ul>
184      */
185     @SuppressWarnings("unused")
186     @NonNull
onGetMediaCollectionInfo(@onNull Bundle extras)187     public abstract Bundle onGetMediaCollectionInfo(@NonNull Bundle extras);
188 
189     /**
190      * Returns a cursor representing all media items in the media collection optionally filtered by
191      * {@code extras} and sorted in reverse chronological order of
192      * {@link CloudMediaProviderContract.MediaColumns#DATE_TAKEN_MILLIS}, i.e. most recent items
193      * first.
194      * <p>
195      * The cloud media provider must set the
196      * {@link CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID} as part of the returned
197      * {@link Cursor#setExtras} {@link Bundle}. Not setting this is an error and invalidates the
198      * returned {@link Cursor}.
199      * <p>
200      * If the cloud media provider handled any filters in {@code extras}, it must add the key to
201      * the {@link ContentResolver#EXTRA_HONORED_ARGS} as part of the returned
202      * {@link Cursor#setExtras} {@link Bundle}.
203      *
204      * @param extras containing keys to filter media items:
205      * <ul>
206      * <li> {@link CloudMediaProviderContract#EXTRA_SYNC_GENERATION}
207      * <li> {@link CloudMediaProviderContract#EXTRA_PAGE_TOKEN}
208      * <li> {@link CloudMediaProviderContract#EXTRA_ALBUM_ID}
209      * </ul>
210      * @return cursor representing media items containing all
211      * {@link CloudMediaProviderContract.MediaColumns} columns
212      */
213     @SuppressWarnings("unused")
214     @NonNull
onQueryMedia(@onNull Bundle extras)215     public abstract Cursor onQueryMedia(@NonNull Bundle extras);
216 
217     /**
218      * Returns a {@link Cursor} representing all deleted media items in the entire media collection
219      * within the current provider version as returned by {@link #onGetMediaCollectionInfo}. These
220      * items can be optionally filtered by {@code extras}.
221      * <p>
222      * The cloud media provider must set the
223      * {@link CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID} as part of the returned
224      * {@link Cursor#setExtras} {@link Bundle}. Not setting this is an error and invalidates the
225      * returned {@link Cursor}.
226      * <p>
227      * If the provider handled any filters in {@code extras}, it must add the key to
228      * the {@link ContentResolver#EXTRA_HONORED_ARGS} as part of the returned
229      * {@link Cursor#setExtras} {@link Bundle}.
230      *
231      * @param extras containing keys to filter deleted media items:
232      * <ul>
233      * <li> {@link CloudMediaProviderContract#EXTRA_SYNC_GENERATION}
234      * <li> {@link CloudMediaProviderContract#EXTRA_PAGE_TOKEN}
235      * </ul>
236      * @return cursor representing deleted media items containing just the
237      * {@link CloudMediaProviderContract.MediaColumns#ID} column
238      */
239     @SuppressWarnings("unused")
240     @NonNull
onQueryDeletedMedia(@onNull Bundle extras)241     public abstract Cursor onQueryDeletedMedia(@NonNull Bundle extras);
242 
243     /**
244      * Returns a cursor representing all album items in the media collection optionally filtered
245      * by {@code extras} and sorted in reverse chronological order of
246      * {@link CloudMediaProviderContract.AlbumColumns#DATE_TAKEN_MILLIS}, i.e. most recent items
247      * first.
248      * <p>
249      * The cloud media provider must set the
250      * {@link CloudMediaProviderContract#EXTRA_MEDIA_COLLECTION_ID} as part of the returned
251      * {@link Cursor#setExtras} {@link Bundle}. Not setting this is an error and invalidates the
252      * returned {@link Cursor}.
253      * <p>
254      * If the provider handled any filters in {@code extras}, it must add the key to
255      * the {@link ContentResolver#EXTRA_HONORED_ARGS} as part of the returned
256      * {@link Cursor#setExtras} {@link Bundle}.
257      *
258      * @param extras containing keys to filter album items:
259      * <ul>
260      * <li> {@link CloudMediaProviderContract#EXTRA_SYNC_GENERATION}
261      * <li> {@link CloudMediaProviderContract#EXTRA_PAGE_TOKEN}
262      * </ul>
263      * @return cursor representing album items containing all
264      * {@link CloudMediaProviderContract.AlbumColumns} columns
265      */
266     @SuppressWarnings("unused")
267     @NonNull
onQueryAlbums(@onNull Bundle extras)268     public Cursor onQueryAlbums(@NonNull Bundle extras) {
269         throw new UnsupportedOperationException("queryAlbums not supported");
270     }
271 
272     /**
273      * Returns a thumbnail of {@code size} for a media item identified by {@code mediaId}.
274      * <p>
275      * This is expected to be a much lower resolution version than the item returned by
276      * {@link #onOpenMedia}.
277      * <p>
278      * If you block while downloading content, you should periodically check
279      * {@link CancellationSignal#isCanceled()} to abort abandoned open requests.
280      *
281      * @param mediaId the media item to return
282      * @param size the dimensions of the thumbnail to return. The returned file descriptor doesn't
283      * have to match the {@code size} precisely because the OS will adjust the dimensions before
284      * usage. Implementations can return close approximations especially if the approximation is
285      * already locally on the device and doesn't require downloading from the cloud.
286      * @param extras to modify the way the fd is opened, e.g. for video files we may request a
287      * thumbnail image instead of a video with
288      * {@link CloudMediaProviderContract#EXTRA_PREVIEW_THUMBNAIL}
289      * @param signal used by the OS to signal if the request should be cancelled
290      * @return read-only file descriptor for accessing the thumbnail for the media file
291      *
292      * @see #onOpenMedia
293      * @see CloudMediaProviderContract#EXTRA_PREVIEW_THUMBNAIL
294      */
295     @SuppressWarnings("unused")
296     @NonNull
onOpenPreview(@onNull String mediaId, @NonNull Point size, @Nullable Bundle extras, @Nullable CancellationSignal signal)297     public abstract AssetFileDescriptor onOpenPreview(@NonNull String mediaId,
298             @NonNull Point size, @Nullable Bundle extras, @Nullable CancellationSignal signal)
299             throws FileNotFoundException;
300 
301     /**
302      * Returns the full size media item identified by {@code mediaId}.
303      * <p>
304      * If you block while downloading content, you should periodically check
305      * {@link CancellationSignal#isCanceled()} to abort abandoned open requests.
306      *
307      * @param mediaId the media item to return
308      * @param extras to modify the way the fd is opened, there's none at the moment, but some
309      * might be implemented in the future
310      * @param signal used by the OS to signal if the request should be cancelled
311      * @return read-only file descriptor for accessing the media file
312      *
313      * @see #onOpenPreview
314      */
315     @SuppressWarnings("unused")
316     @NonNull
onOpenMedia(@onNull String mediaId, @Nullable Bundle extras, @Nullable CancellationSignal signal)317     public abstract ParcelFileDescriptor onOpenMedia(@NonNull String mediaId,
318             @Nullable Bundle extras, @Nullable CancellationSignal signal)
319             throws FileNotFoundException;
320 
321     /**
322      * Returns a {@link CloudMediaSurfaceController} used for rendering the preview of media items,
323      * or null if preview rendering is not supported.
324      *
325      * @param config containing configuration parameters for {@link CloudMediaSurfaceController}
326      * <ul>
327      * <li> {@link CloudMediaProviderContract#EXTRA_LOOPING_PLAYBACK_ENABLED}
328      * <li> {@link CloudMediaProviderContract#EXTRA_SURFACE_CONTROLLER_AUDIO_MUTE_ENABLED}
329      * </ul>
330      * @param callback {@link CloudMediaSurfaceStateChangedCallback} to send state updates for
331      *                 {@link Surface} to picker launched via {@link MediaStore#ACTION_PICK_IMAGES}
332      */
333     @Nullable
onCreateCloudMediaSurfaceController(@onNull Bundle config, @NonNull CloudMediaSurfaceStateChangedCallback callback)334     public CloudMediaSurfaceController onCreateCloudMediaSurfaceController(@NonNull Bundle config,
335             @NonNull CloudMediaSurfaceStateChangedCallback callback) {
336         return null;
337     }
338 
339     /**
340      * Implementation is provided by the parent class. Cannot be overridden.
341      */
342     @Override
343     @NonNull
call(@onNull String method, @Nullable String arg, @Nullable Bundle extras)344     public final Bundle call(@NonNull String method, @Nullable String arg,
345             @Nullable Bundle extras) {
346         if (!method.startsWith("android:")) {
347             // Ignore non-platform methods
348             return super.call(method, arg, extras);
349         }
350 
351         try {
352             return callUnchecked(method, arg, extras);
353         } catch (FileNotFoundException e) {
354             throw new RuntimeException(e);
355         }
356     }
357 
callUnchecked(String method, String arg, Bundle extras)358     private Bundle callUnchecked(String method, String arg, Bundle extras)
359             throws FileNotFoundException {
360         if (METHOD_GET_MEDIA_COLLECTION_INFO.equals(method)) {
361             return onGetMediaCollectionInfo(extras);
362         } else if (METHOD_CREATE_SURFACE_CONTROLLER.equals(method)) {
363             return onCreateCloudMediaSurfaceController(extras);
364         } else if (METHOD_GET_ASYNC_CONTENT_PROVIDER.equals(method)) {
365             return onGetAsyncContentProvider();
366         } else {
367             throw new UnsupportedOperationException("Method not supported " + method);
368         }
369     }
370 
onCreateCloudMediaSurfaceController(@onNull Bundle extras)371     private Bundle onCreateCloudMediaSurfaceController(@NonNull Bundle extras) {
372         Objects.requireNonNull(extras);
373 
374         final IBinder binder = extras.getBinder(EXTRA_SURFACE_STATE_CALLBACK);
375         if (binder == null) {
376             throw new IllegalArgumentException("Missing surface state callback");
377         }
378 
379         final boolean enableLoop = extras.getBoolean(EXTRA_LOOPING_PLAYBACK_ENABLED,
380                 DEFAULT_LOOPING_PLAYBACK_ENABLED);
381         final boolean muteAudio = extras.getBoolean(EXTRA_SURFACE_CONTROLLER_AUDIO_MUTE_ENABLED,
382                 DEFAULT_SURFACE_CONTROLLER_AUDIO_MUTE_ENABLED);
383         final String authority = extras.getString(EXTRA_AUTHORITY);
384         final CloudMediaSurfaceStateChangedCallback callback =
385                 new CloudMediaSurfaceStateChangedCallback(
386                         ICloudMediaSurfaceStateChangedCallback.Stub.asInterface(binder));
387         final Bundle config = new Bundle();
388         config.putBoolean(EXTRA_LOOPING_PLAYBACK_ENABLED, enableLoop);
389         config.putBoolean(EXTRA_SURFACE_CONTROLLER_AUDIO_MUTE_ENABLED, muteAudio);
390         config.putString(EXTRA_AUTHORITY, authority);
391         final CloudMediaSurfaceController controller =
392                 onCreateCloudMediaSurfaceController(config, callback);
393         if (controller == null) {
394             Log.d(TAG, "onCreateCloudMediaSurfaceController returned null");
395             return Bundle.EMPTY;
396         }
397 
398         Bundle result = new Bundle();
399         result.putBinder(EXTRA_SURFACE_CONTROLLER,
400                 new CloudMediaSurfaceControllerWrapper(controller).asBinder());
401         return result;
402     }
403 
404     /**
405      * Implementation is provided by the parent class. Cannot be overridden.
406      *
407      * @see #onOpenMedia
408      */
409     @NonNull
410     @Override
openFile(@onNull Uri uri, @NonNull String mode)411     public final ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode)
412             throws FileNotFoundException {
413         return openFile(uri, mode, null);
414     }
415 
416     /**
417      * Implementation is provided by the parent class. Cannot be overridden.
418      *
419      * @see #onOpenMedia
420      */
421     @NonNull
422     @Override
openFile(@onNull Uri uri, @NonNull String mode, @Nullable CancellationSignal signal)423     public final ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode,
424             @Nullable CancellationSignal signal) throws FileNotFoundException {
425         String mediaId = uri.getLastPathSegment();
426 
427         return onOpenMedia(mediaId, /* extras */ null, signal);
428     }
429 
430     /**
431      * Implementation is provided by the parent class. Cannot be overridden.
432      *
433      * @see #onOpenPreview
434      * @see #onOpenMedia
435      */
436     @NonNull
437     @Override
openTypedAssetFile(@onNull Uri uri, @NonNull String mimeTypeFilter, @Nullable Bundle opts)438     public final AssetFileDescriptor openTypedAssetFile(@NonNull Uri uri,
439             @NonNull String mimeTypeFilter, @Nullable Bundle opts) throws FileNotFoundException {
440         return openTypedAssetFile(uri, mimeTypeFilter, opts, null);
441     }
442 
443     /**
444      * Implementation is provided by the parent class. Cannot be overridden.
445      *
446      * @see #onOpenPreview
447      * @see #onOpenMedia
448      */
449     @NonNull
450     @Override
openTypedAssetFile( @onNull Uri uri, @NonNull String mimeTypeFilter, @Nullable Bundle opts, @Nullable CancellationSignal signal)451     public final AssetFileDescriptor openTypedAssetFile(
452             @NonNull Uri uri, @NonNull String mimeTypeFilter, @Nullable Bundle opts,
453             @Nullable CancellationSignal signal) throws FileNotFoundException {
454         final String mediaId = uri.getLastPathSegment();
455         final Bundle bundle = new Bundle();
456         Point previewSize = null;
457 
458         final DisplayMetrics screenMetrics = getContext().getResources().getDisplayMetrics();
459         int minPreviewLength = Math.min(screenMetrics.widthPixels, screenMetrics.heightPixels);
460 
461         if (opts != null) {
462             bundle.putBoolean(EXTRA_MEDIASTORE_THUMB, opts.getBoolean(EXTRA_MEDIASTORE_THUMB));
463 
464             if (opts.containsKey(CloudMediaProviderContract.EXTRA_PREVIEW_THUMBNAIL)) {
465                 bundle.putBoolean(CloudMediaProviderContract.EXTRA_PREVIEW_THUMBNAIL, true);
466                 minPreviewLength = minPreviewLength / 2;
467             }
468 
469             previewSize = opts.getParcelable(ContentResolver.EXTRA_SIZE);
470         }
471 
472         if (previewSize == null) {
473             previewSize = new Point(minPreviewLength, minPreviewLength);
474         }
475 
476         return onOpenPreview(mediaId, previewSize, bundle, signal);
477     }
478 
479     /**
480      * Implementation is provided by the parent class. Cannot be overridden.
481      *
482      * @see #onQueryMedia
483      * @see #onQueryDeletedMedia
484      * @see #onQueryAlbums
485      */
486     @NonNull
487     @Override
query(@onNull Uri uri, @Nullable String[] projection, @Nullable Bundle queryArgs, @Nullable CancellationSignal cancellationSignal)488     public final Cursor query(@NonNull Uri uri, @Nullable String[] projection,
489             @Nullable Bundle queryArgs, @Nullable CancellationSignal cancellationSignal) {
490         switch (mMatcher.match(uri)) {
491             case MATCH_MEDIAS:
492                 return onQueryMedia(queryArgs);
493             case MATCH_DELETED_MEDIAS:
494                 return onQueryDeletedMedia(queryArgs);
495             case MATCH_ALBUMS:
496                 return onQueryAlbums(queryArgs);
497             default:
498                 throw new UnsupportedOperationException("Unsupported Uri " + uri);
499         }
500     }
501 
502     /**
503      * Implementation is provided by the parent class. Throws by default, and
504      * cannot be overridden.
505      */
506     @NonNull
507     @Override
getType(@onNull Uri uri)508     public final String getType(@NonNull Uri uri) {
509         throw new UnsupportedOperationException("getType not supported");
510     }
511 
512     /**
513      * Implementation is provided by the parent class. Throws by default, and
514      * cannot be overridden.
515      */
516     @NonNull
517     @Override
canonicalize(@onNull Uri uri)518     public final Uri canonicalize(@NonNull Uri uri) {
519         throw new UnsupportedOperationException("Canonicalize not supported");
520     }
521 
522     /**
523      * Implementation is provided by the parent class. Throws by default, and
524      * cannot be overridden.
525      */
526     @NonNull
527     @Override
query(@onNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder)528     public final Cursor query(@NonNull Uri uri, @Nullable String[] projection,
529             @Nullable String selection, @Nullable String[] selectionArgs,
530             @Nullable String sortOrder) {
531         // As of Android-O, ContentProvider#query (w/ bundle arg) is the primary
532         // transport method. We override that, and don't ever delegate to this method.
533         throw new UnsupportedOperationException("Pre-Android-O query format not supported.");
534     }
535 
536     /**
537      * Implementation is provided by the parent class. Throws by default, and
538      * cannot be overridden.
539      */
540     @NonNull
541     @Override
query(@onNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder, @Nullable CancellationSignal cancellationSignal)542     public final Cursor query(@NonNull Uri uri, @Nullable String[] projection,
543             @Nullable String selection, @Nullable String[] selectionArgs,
544             @Nullable String sortOrder, @Nullable CancellationSignal cancellationSignal) {
545         // As of Android-O, ContentProvider#query (w/ bundle arg) is the primary
546         // transport method. We override that, and don't ever delegate to this metohd.
547         throw new UnsupportedOperationException("Pre-Android-O query format not supported.");
548     }
549 
550     /**
551      * Implementation is provided by the parent class. Throws by default, and
552      * cannot be overridden.
553      */
554     @NonNull
555     @Override
insert(@onNull Uri uri, @NonNull ContentValues values)556     public final Uri insert(@NonNull Uri uri, @NonNull ContentValues values) {
557         throw new UnsupportedOperationException("Insert not supported");
558     }
559 
560     /**
561      * Implementation is provided by the parent class. Throws by default, and
562      * cannot be overridden.
563      */
564     @Override
delete(@onNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs)565     public final int delete(@NonNull Uri uri, @Nullable String selection,
566             @Nullable String[] selectionArgs) {
567         throw new UnsupportedOperationException("Delete not supported");
568     }
569 
570     /**
571      * Implementation is provided by the parent class. Throws by default, and
572      * cannot be overridden.
573      */
574     @Override
update(@onNull Uri uri, @NonNull ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs)575     public final int update(@NonNull Uri uri, @NonNull ContentValues values,
576             @Nullable String selection, @Nullable String[] selectionArgs) {
577         throw new UnsupportedOperationException("Update not supported");
578     }
579 
580     /**
581      * Manages rendering the preview of media items on given instances of {@link Surface}.
582      *
583      * <p>The methods of this class are meant to be asynchronous, and should not block by performing
584      * any heavy operation.
585      * <p>Note that a single CloudMediaSurfaceController instance would be responsible for
586      * rendering multiple media items associated with multiple surfaces.
587      */
588     @SuppressLint("PackageLayering") // We need to pass in a Surface which can be prepared for
589     // rendering a media item.
590     public static abstract class CloudMediaSurfaceController {
591 
592         /**
593          * Creates any player resource(s) needed for rendering.
594          */
onPlayerCreate()595         public abstract void onPlayerCreate();
596 
597         /**
598          * Releases any player resource(s) used for rendering.
599          */
onPlayerRelease()600         public abstract void onPlayerRelease();
601 
602         /**
603          * Indicates creation of the given {@link Surface} with given {@code surfaceId} for
604          * rendering the preview of a media item with given {@code mediaId}.
605          *
606          * <p>This is called immediately after the surface is first created. Implementations of this
607          * should start up whatever rendering code they desire.
608          * <p>Note that the given media item remains associated with the given surface id till the
609          * {@link Surface} is destroyed.
610          *
611          * @param surfaceId id which uniquely identifies the {@link Surface} for rendering
612          * @param surface instance of the {@link Surface} on which the media item should be rendered
613          * @param mediaId id which uniquely identifies the media to be rendered
614          *
615          * @see SurfaceHolder.Callback#surfaceCreated(SurfaceHolder)
616          */
onSurfaceCreated(int surfaceId, @NonNull Surface surface, @NonNull String mediaId)617         public abstract void onSurfaceCreated(int surfaceId, @NonNull Surface surface,
618                 @NonNull String mediaId);
619 
620         /**
621          * Indicates structural changes (format or size) in the {@link Surface} for rendering.
622          *
623          * <p>This method is always called at least once, after {@link #onSurfaceCreated}.
624          *
625          * @param surfaceId id which uniquely identifies the {@link Surface} for rendering
626          * @param format the new {@link PixelFormat} of the surface
627          * @param width the new width of the {@link Surface}
628          * @param height the new height of the {@link Surface}
629          *
630          * @see SurfaceHolder.Callback#surfaceChanged(SurfaceHolder, int, int, int)
631          */
onSurfaceChanged(int surfaceId, int format, int width, int height)632         public abstract void onSurfaceChanged(int surfaceId, int format, int width, int height);
633 
634         /**
635          * Indicates destruction of a {@link Surface} with given {@code surfaceId}.
636          *
637          * <p>This is called immediately before a surface is being destroyed. After returning from
638          * this call, you should no longer try to access this surface.
639          *
640          * @param surfaceId id which uniquely identifies the {@link Surface} for rendering
641          *
642          * @see SurfaceHolder.Callback#surfaceDestroyed(SurfaceHolder)
643          */
onSurfaceDestroyed(int surfaceId)644         public abstract void onSurfaceDestroyed(int surfaceId);
645 
646         /**
647          * Start playing the preview of the media associated with the given surface id. If
648          * playback had previously been paused, playback will continue from where it was paused.
649          * If playback had been stopped, or never started before, playback will start at the
650          * beginning.
651          *
652          * @param surfaceId id which uniquely identifies the {@link Surface} for rendering
653          */
onMediaPlay(int surfaceId)654         public abstract void onMediaPlay(int surfaceId);
655 
656         /**
657          * Pauses the playback of the media associated with the given surface id.
658          *
659          * @param surfaceId id which uniquely identifies the {@link Surface} for rendering
660          */
onMediaPause(int surfaceId)661         public abstract void onMediaPause(int surfaceId);
662 
663         /**
664          * Seeks the media associated with the given surface id to specified timestamp.
665          *
666          * @param surfaceId id which uniquely identifies the {@link Surface} for rendering
667          * @param timestampMillis the timestamp in milliseconds from the start to seek to
668          */
onMediaSeekTo(int surfaceId, @DurationMillisLong long timestampMillis)669         public abstract void onMediaSeekTo(int surfaceId, @DurationMillisLong long timestampMillis);
670 
671         /**
672          * Changes the configuration parameters for the CloudMediaSurfaceController.
673          *
674          * @param config the updated config to change to. This can include config changes for the
675          * following:
676          * <ul>
677          * <li> {@link CloudMediaProviderContract#EXTRA_LOOPING_PLAYBACK_ENABLED}
678          * <li> {@link CloudMediaProviderContract#EXTRA_SURFACE_CONTROLLER_AUDIO_MUTE_ENABLED}
679          * </ul>
680          */
onConfigChange(@onNull Bundle config)681         public abstract void onConfigChange(@NonNull Bundle config);
682 
683         /**
684          * Indicates destruction of this CloudMediaSurfaceController object.
685          *
686          * <p>This CloudMediaSurfaceController object should no longer be in use after this method
687          * has been called.
688          *
689          * <p>Note that it is possible for this method to be called directly without
690          * {@link #onPlayerRelease} being called, hence you should release any resources associated
691          * with this CloudMediaSurfaceController object, or perform any cleanup required in this
692          * method.
693          */
onDestroy()694         public abstract void onDestroy();
695     }
696 
697     /**
698      * This class is used by {@link CloudMediaProvider} to send {@link Surface} state updates to
699      * picker launched via {@link MediaStore#ACTION_PICK_IMAGES}.
700      *
701      * @see MediaStore#ACTION_PICK_IMAGES
702      */
703     public static final class CloudMediaSurfaceStateChangedCallback {
704 
705         /** {@hide} */
706         @IntDef(flag = true, prefix = { "PLAYBACK_STATE_" }, value = {
707                 PLAYBACK_STATE_BUFFERING,
708                 PLAYBACK_STATE_READY,
709                 PLAYBACK_STATE_STARTED,
710                 PLAYBACK_STATE_PAUSED,
711                 PLAYBACK_STATE_COMPLETED,
712                 PLAYBACK_STATE_ERROR_RETRIABLE_FAILURE,
713                 PLAYBACK_STATE_ERROR_PERMANENT_FAILURE,
714                 PLAYBACK_STATE_MEDIA_SIZE_CHANGED
715         })
716         @Retention(RetentionPolicy.SOURCE)
717         public @interface PlaybackState {}
718 
719         /**
720          * Constant to notify that the playback is buffering
721          */
722         public static final int PLAYBACK_STATE_BUFFERING = 1;
723 
724         /**
725          * Constant to notify that the playback is ready to be played
726          */
727         public static final int PLAYBACK_STATE_READY = 2;
728 
729         /**
730          * Constant to notify that the playback has started
731          */
732         public static final int PLAYBACK_STATE_STARTED = 3;
733 
734         /**
735          * Constant to notify that the playback is paused.
736          */
737         public static final int PLAYBACK_STATE_PAUSED = 4;
738 
739         /**
740          * Constant to notify that the playback has completed
741          */
742         public static final int PLAYBACK_STATE_COMPLETED = 5;
743 
744         /**
745          * Constant to notify that the playback has failed with a retriable error.
746          */
747         public static final int PLAYBACK_STATE_ERROR_RETRIABLE_FAILURE = 6;
748 
749         /**
750          * Constant to notify that the playback has failed with a permanent error.
751          */
752         public static final int PLAYBACK_STATE_ERROR_PERMANENT_FAILURE = 7;
753 
754         /**
755          * Constant to notify that the media size is first known or has changed.
756          *
757          * Pass the width and height of the media as a {@link Point} inside the {@link Bundle} with
758          * {@link ContentResolver#EXTRA_SIZE} as the key.
759          *
760          * @see CloudMediaSurfaceStateChangedCallback#setPlaybackState(int, int, Bundle)
761          * @see MediaPlayer.OnVideoSizeChangedListener#onVideoSizeChanged(MediaPlayer, int, int)
762          */
763         public static final int PLAYBACK_STATE_MEDIA_SIZE_CHANGED = 8;
764 
765         private final ICloudMediaSurfaceStateChangedCallback mCallback;
766 
CloudMediaSurfaceStateChangedCallback(ICloudMediaSurfaceStateChangedCallback callback)767         CloudMediaSurfaceStateChangedCallback(ICloudMediaSurfaceStateChangedCallback callback) {
768             mCallback = callback;
769         }
770 
771         /**
772          * This is called to notify playback state update for a {@link Surface}
773          * on the picker launched via {@link MediaStore#ACTION_PICK_IMAGES}.
774          *
775          * @param surfaceId id which uniquely identifies a {@link Surface}
776          * @param playbackState playback state to notify picker about
777          * @param playbackStateInfo {@link Bundle} which may contain extra information about the
778          *                          playback state, such as media size, progress/seek info or
779          *                          details about errors.
780          */
setPlaybackState(int surfaceId, @PlaybackState int playbackState, @Nullable Bundle playbackStateInfo)781         public void setPlaybackState(int surfaceId, @PlaybackState int playbackState,
782                 @Nullable Bundle playbackStateInfo) {
783             try {
784                 mCallback.setPlaybackState(surfaceId, playbackState, playbackStateInfo);
785             } catch (Exception e) {
786                 Log.w(TAG, "Failed to notify playback state (" + playbackState + ") for "
787                         + "surfaceId: " + surfaceId + " ; playbackStateInfo: " + playbackStateInfo,
788                         e);
789             }
790         }
791     }
792 
793     /** {@hide} */
794     private static class CloudMediaSurfaceControllerWrapper
795             extends ICloudMediaSurfaceController.Stub {
796 
797         final private CloudMediaSurfaceController mSurfaceController;
798 
CloudMediaSurfaceControllerWrapper(CloudMediaSurfaceController surfaceController)799         CloudMediaSurfaceControllerWrapper(CloudMediaSurfaceController surfaceController) {
800             mSurfaceController = surfaceController;
801         }
802 
803         @Override
onPlayerCreate()804         public void onPlayerCreate() {
805             Log.i(TAG, "Creating player.");
806             mSurfaceController.onPlayerCreate();
807         }
808 
809         @Override
onPlayerRelease()810         public void onPlayerRelease() {
811             Log.i(TAG, "Releasing player.");
812             mSurfaceController.onPlayerRelease();
813         }
814 
815         @Override
onSurfaceCreated(int surfaceId, @NonNull Surface surface, @NonNull String mediaId)816         public void onSurfaceCreated(int surfaceId, @NonNull Surface surface,
817                 @NonNull String mediaId) {
818             Log.i(TAG, "Surface prepared. SurfaceId: " + surfaceId + ". MediaId: " + mediaId);
819             mSurfaceController.onSurfaceCreated(surfaceId, surface, mediaId);
820         }
821 
822         @Override
onSurfaceChanged(int surfaceId, int format, int width, int height)823         public void onSurfaceChanged(int surfaceId, int format, int width, int height) {
824             Log.i(TAG, "Surface changed. SurfaceId: " + surfaceId + ". Format: " + format
825                     + ". Width: " + width + ". Height: " + height);
826             mSurfaceController.onSurfaceChanged(surfaceId, format, width, height);
827         }
828 
829         @Override
onSurfaceDestroyed(int surfaceId)830         public void onSurfaceDestroyed(int surfaceId) {
831             Log.i(TAG, "Surface released. SurfaceId: " + surfaceId);
832             mSurfaceController.onSurfaceDestroyed(surfaceId);
833         }
834 
835         @Override
onMediaPlay(int surfaceId)836         public void onMediaPlay(int surfaceId) {
837             Log.i(TAG, "Media played. SurfaceId: " + surfaceId);
838             mSurfaceController.onMediaPlay(surfaceId);
839         }
840 
841         @Override
onMediaPause(int surfaceId)842         public void onMediaPause(int surfaceId) {
843             Log.i(TAG, "Media paused. SurfaceId: " + surfaceId);
844             mSurfaceController.onMediaPause(surfaceId);
845         }
846 
847         @Override
onMediaSeekTo(int surfaceId, @DurationMillisLong long timestampMillis)848         public void onMediaSeekTo(int surfaceId, @DurationMillisLong long timestampMillis) {
849             Log.i(TAG, "Media seeked. SurfaceId: " + surfaceId + ". Seek timestamp(ms): "
850                     + timestampMillis);
851             mSurfaceController.onMediaSeekTo(surfaceId, timestampMillis);
852         }
853 
854         @Override
onConfigChange(@onNull Bundle config)855         public void onConfigChange(@NonNull Bundle config) {
856             Log.i(TAG, "Config changed. Updated config params: " + config);
857             mSurfaceController.onConfigChange(config);
858         }
859 
860         @Override
onDestroy()861         public void onDestroy() {
862             Log.i(TAG, "Controller destroyed");
863             mSurfaceController.onDestroy();
864         }
865     }
866 
867     /**
868      * @hide
869      */
870     private class AsyncContentProviderWrapper extends IAsyncContentProvider.Stub {
871 
872         @Override
openMedia(String mediaId, RemoteCallback remoteCallback)873         public void openMedia(String mediaId, RemoteCallback remoteCallback) {
874             try {
875                 ParcelFileDescriptor pfd = onOpenMedia(mediaId,/* extras */
876                         null,/* cancellationSignal */ null);
877                 sendResult(pfd, null, remoteCallback);
878             } catch (Exception e) {
879                 sendResult(null, e, remoteCallback);
880             }
881         }
882 
sendResult(ParcelFileDescriptor pfd, Throwable throwable, RemoteCallback remoteCallback)883         private void sendResult(ParcelFileDescriptor pfd, Throwable throwable,
884                 RemoteCallback remoteCallback) {
885             Bundle bundle = new Bundle();
886             if (pfd == null && throwable == null) {
887                 throw new IllegalStateException("Expected ParcelFileDescriptor or an exception.");
888             }
889             if (pfd != null) {
890                 bundle.putParcelable(EXTRA_FILE_DESCRIPTOR, pfd);
891             }
892             if (throwable != null) {
893                 bundle.putString(EXTRA_ERROR_MESSAGE, throwable.getMessage());
894             }
895             remoteCallback.sendResult(bundle);
896         }
897     }
898 }
899