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 com.android.providers.media.photopicker; 18 19 import static android.provider.CloudMediaProviderContract.EXTRA_AUTHORITY; 20 import static android.provider.CloudMediaProviderContract.EXTRA_LOOPING_PLAYBACK_ENABLED; 21 import static android.provider.CloudMediaProviderContract.EXTRA_MEDIASTORE_THUMB; 22 import static android.provider.CloudMediaProviderContract.EXTRA_SURFACE_CONTROLLER_AUDIO_MUTE_ENABLED; 23 24 import android.content.ContentProviderClient; 25 import android.content.ContentResolver; 26 import android.content.res.AssetFileDescriptor; 27 import android.database.Cursor; 28 import android.graphics.Point; 29 import android.net.Uri; 30 import android.os.Bundle; 31 import android.os.CancellationSignal; 32 import android.os.OperationCanceledException; 33 import android.os.ParcelFileDescriptor; 34 import android.provider.CloudMediaProvider; 35 import android.provider.MediaStore; 36 37 import androidx.annotation.NonNull; 38 import androidx.annotation.Nullable; 39 40 import com.android.providers.media.LocalCallingIdentity; 41 import com.android.providers.media.MediaProvider; 42 import com.android.providers.media.photopicker.data.CloudProviderQueryExtras; 43 import com.android.providers.media.photopicker.data.ExternalDbFacade; 44 import com.android.providers.media.photopicker.ui.remotepreview.RemotePreviewHandler; 45 import com.android.providers.media.photopicker.ui.remotepreview.RemoteSurfaceController; 46 47 import java.io.FileNotFoundException; 48 49 /** 50 * Implements the {@link CloudMediaProvider} interface over the local items in the MediaProvider 51 * database. 52 */ 53 public class PhotoPickerProvider extends CloudMediaProvider { 54 private static final String TAG = "PhotoPickerProvider"; 55 56 private MediaProvider mMediaProvider; 57 private ExternalDbFacade mDbFacade; 58 59 @Override onCreate()60 public boolean onCreate() { 61 mMediaProvider = getMediaProvider(); 62 mDbFacade = mMediaProvider.getExternalDbFacade(); 63 return true; 64 } 65 66 @Override onQueryMedia(@ullable Bundle extras)67 public Cursor onQueryMedia(@Nullable Bundle extras) { 68 // TODO(b/190713331): Handle extra_page 69 final CloudProviderQueryExtras queryExtras = 70 CloudProviderQueryExtras.fromCloudMediaBundle(extras); 71 72 return mDbFacade.queryMedia(queryExtras.getGeneration(), queryExtras.getAlbumId(), 73 queryExtras.getMimeType()); 74 } 75 76 @Override onQueryDeletedMedia(@ullable Bundle extras)77 public Cursor onQueryDeletedMedia(@Nullable Bundle extras) { 78 final CloudProviderQueryExtras queryExtras = 79 CloudProviderQueryExtras.fromCloudMediaBundle(extras); 80 81 return mDbFacade.queryDeletedMedia(queryExtras.getGeneration()); 82 } 83 84 @Override onQueryAlbums(@ullable Bundle extras)85 public Cursor onQueryAlbums(@Nullable Bundle extras) { 86 final CloudProviderQueryExtras queryExtras = 87 CloudProviderQueryExtras.fromCloudMediaBundle(extras); 88 89 return mDbFacade.queryAlbums(queryExtras.getMimeType()); 90 } 91 92 @Override onOpenPreview(@onNull String mediaId, @NonNull Point size, @NonNull Bundle extras, @NonNull CancellationSignal signal)93 public AssetFileDescriptor onOpenPreview(@NonNull String mediaId, @NonNull Point size, 94 @NonNull Bundle extras, @NonNull CancellationSignal signal) 95 throws FileNotFoundException { 96 final Bundle opts = new Bundle(); 97 opts.putParcelable(ContentResolver.EXTRA_SIZE, size); 98 99 String mimeTypeFilter = null; 100 if (extras.getBoolean(EXTRA_MEDIASTORE_THUMB)) { 101 // This is a request for thumbnail, set "image/*" to get cached thumbnails from 102 // MediaProvider. 103 mimeTypeFilter = "image/*"; 104 } 105 106 final LocalCallingIdentity token = mMediaProvider.clearLocalCallingIdentity(); 107 try { 108 return mMediaProvider.openTypedAssetFile(fromMediaId(mediaId), mimeTypeFilter, opts); 109 } finally { 110 mMediaProvider.restoreLocalCallingIdentity(token); 111 } 112 } 113 114 @Override onOpenMedia(@onNull String mediaId, @NonNull Bundle extras, @NonNull CancellationSignal signal)115 public ParcelFileDescriptor onOpenMedia(@NonNull String mediaId, 116 @NonNull Bundle extras, @NonNull CancellationSignal signal) 117 throws FileNotFoundException { 118 final LocalCallingIdentity token = mMediaProvider.clearLocalCallingIdentity(); 119 try { 120 return mMediaProvider.openFile(fromMediaId(mediaId), "r"); 121 } finally { 122 mMediaProvider.restoreLocalCallingIdentity(token); 123 } 124 } 125 126 @Override onGetMediaCollectionInfo(@ullable Bundle extras)127 public Bundle onGetMediaCollectionInfo(@Nullable Bundle extras) { 128 final CloudProviderQueryExtras queryExtras = 129 CloudProviderQueryExtras.fromCloudMediaBundle(extras); 130 131 return mDbFacade.getMediaCollectionInfo(queryExtras.getGeneration()); 132 } 133 134 @Override 135 @Nullable onCreateCloudMediaSurfaceController(@onNull Bundle config, CloudMediaSurfaceStateChangedCallback callback)136 public CloudMediaSurfaceController onCreateCloudMediaSurfaceController(@NonNull Bundle config, 137 CloudMediaSurfaceStateChangedCallback callback) { 138 if (RemotePreviewHandler.isRemotePreviewEnabled()) { 139 final String authority = config.getString(EXTRA_AUTHORITY, 140 PickerSyncController.LOCAL_PICKER_PROVIDER_AUTHORITY); 141 final boolean enableLoop = config.getBoolean(EXTRA_LOOPING_PLAYBACK_ENABLED, false); 142 final boolean muteAudio = config.getBoolean(EXTRA_SURFACE_CONTROLLER_AUDIO_MUTE_ENABLED, 143 false); 144 return new RemoteSurfaceController(getContext(), authority, enableLoop, muteAudio, 145 callback); 146 } 147 return null; 148 } 149 getMediaProvider()150 private MediaProvider getMediaProvider() { 151 ContentResolver cr = getContext().getContentResolver(); 152 try (ContentProviderClient cpc = cr.acquireContentProviderClient(MediaStore.AUTHORITY)) { 153 return (MediaProvider) cpc.getLocalContentProvider(); 154 } catch (OperationCanceledException e) { 155 throw new IllegalStateException("Failed to acquire MediaProvider", e); 156 } 157 } 158 fromMediaId(String mediaId)159 private static Uri fromMediaId(String mediaId) { 160 return MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL, 161 Long.parseLong(mediaId)); 162 } 163 } 164