• 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 com.android.providers.media.photopicker.sync;
18 
19 import static android.provider.CloudMediaProviderContract.EXTRA_PROVIDER_CAPABILITIES;
20 import static android.provider.CloudMediaProviderContract.METHOD_GET_CAPABILITIES;
21 
22 import static java.util.Objects.requireNonNull;
23 
24 import android.content.Context;
25 import android.content.Intent;
26 import android.database.Cursor;
27 import android.net.Uri;
28 import android.os.Bundle;
29 import android.os.CancellationSignal;
30 import android.os.OperationCanceledException;
31 import android.provider.CloudMediaProviderContract;
32 import android.provider.CloudMediaProviderContract.SortOrder;
33 import android.util.Log;
34 
35 import androidx.annotation.NonNull;
36 import androidx.annotation.Nullable;
37 
38 import java.util.List;
39 
40 /**
41  * A client class responsible for fetching search results from
42  * cloud media provider and local search provider.
43  */
44 public class PickerSearchProviderClient {
45     private static final String TAG = "PickerSearchProviderClient";
46 
47     @NonNull
48     private final Context mContext;
49 
50     @NonNull
51     private final String mCloudProviderAuthority;
52 
PickerSearchProviderClient(@onNull Context context, @NonNull String cloudProviderAuthority)53     private PickerSearchProviderClient(@NonNull Context context,
54             @NonNull String cloudProviderAuthority) {
55         mContext = requireNonNull(context);
56         mCloudProviderAuthority = requireNonNull(cloudProviderAuthority);
57     }
58 
59     /**
60      * Create instance of a picker search client.
61      */
create(@onNull Context context, @NonNull String cloudProviderAuthority)62     public static PickerSearchProviderClient create(@NonNull Context context,
63             @NonNull String cloudProviderAuthority) {
64         return new PickerSearchProviderClient(context, cloudProviderAuthority);
65     }
66 
67     /**
68      * Method for querying CloudMediaProvider for media search result.
69      * Note: This functions does not expect pagination args.
70      */
71     @Nullable
fetchSearchResultsFromCmp( @ullable String suggestedMediaSetId, @Nullable String searchText, @SortOrder int sortOrder, @Nullable List<String> mimeTypes, int pageSize, @Nullable String resumePageToken, @Nullable CancellationSignal cancellationSignal)72     public Cursor fetchSearchResultsFromCmp(
73             @Nullable String suggestedMediaSetId,
74             @Nullable String searchText,
75             @SortOrder int sortOrder,
76             @Nullable List<String> mimeTypes,
77             int pageSize,
78             @Nullable String resumePageToken,
79             @Nullable CancellationSignal cancellationSignal) {
80         if (suggestedMediaSetId == null && searchText == null) {
81             throw new IllegalArgumentException(
82                     "both suggestedMediaSet and searchText can not be null at once");
83         }
84         final Bundle queryArgs = new Bundle();
85         queryArgs.putString(CloudMediaProviderContract.KEY_SEARCH_TEXT, searchText);
86         queryArgs.putString(CloudMediaProviderContract.KEY_MEDIA_SET_ID, suggestedMediaSetId);
87         queryArgs.putInt(CloudMediaProviderContract.EXTRA_PAGE_SIZE, pageSize);
88         queryArgs.putString(CloudMediaProviderContract.EXTRA_PAGE_TOKEN, resumePageToken);
89         queryArgs.putInt(CloudMediaProviderContract.EXTRA_SORT_ORDER, sortOrder);
90         if (mimeTypes != null) {
91             queryArgs.putStringArray(
92                     Intent.EXTRA_MIME_TYPES,
93                     mimeTypes.toArray(new String[mimeTypes.size()]));
94         }
95 
96         Log.d(TAG, "Search results query sent to CMP: " + queryArgs);
97 
98         final Cursor cursor = mContext.getContentResolver().query(
99                 getCloudUriFromPath(CloudMediaProviderContract.URI_PATH_SEARCH_MEDIA),
100                 null, queryArgs, null);
101 
102         if (cursor == null) {
103             Log.d(TAG, "Search results response from the CMP is null.");
104 
105         } else {
106             Log.d(TAG, "Search results received from the CMP: " + cursor.getCount()
107                     + " extras: " + cursor.getExtras());
108         }
109         return cursor;
110     }
111 
112     /**
113      * Method for querying CloudMediaProvider for search suggestions
114      */
115     @Nullable
fetchSearchSuggestionsFromCmp(@onNull String prefixText, int limit, @Nullable CancellationSignal cancellationSignal)116     public Cursor fetchSearchSuggestionsFromCmp(@NonNull String prefixText,
117             int limit,
118             @Nullable CancellationSignal cancellationSignal) {
119         final Bundle queryArgs = new Bundle();
120         queryArgs.putString(CloudMediaProviderContract.KEY_PREFIX_TEXT, requireNonNull(prefixText));
121         queryArgs.putInt(CloudMediaProviderContract.EXTRA_PAGE_SIZE, limit);
122 
123         Log.d(TAG, "Search suggestions query sent to CMP: " + queryArgs);
124 
125         final Cursor cursor = mContext.getContentResolver().query(
126                 getCloudUriFromPath(CloudMediaProviderContract.URI_PATH_SEARCH_SUGGESTION),
127                 null, queryArgs, null);
128 
129         if (cursor == null) {
130             Log.d(TAG, "Search suggestions response from the CMP is null.");
131 
132         } else {
133             Log.d(TAG, "Search suggestions received from the CMP: " + cursor.getCount()
134                     + " extras: " + cursor.getExtras());
135         }
136         return cursor;
137     }
138 
139     /**
140      * Method for querying CloudMediaProvider for MediaCategories
141      */
142     @Nullable
fetchMediaCategoriesFromCmp( @ullable String parentCategoryId, @Nullable Bundle queryArgs, @Nullable CancellationSignal cancellationSignal)143     public Cursor fetchMediaCategoriesFromCmp(
144             @Nullable String parentCategoryId,
145             @Nullable Bundle queryArgs,
146             @Nullable CancellationSignal cancellationSignal) {
147         if (queryArgs == null) {
148             queryArgs = new Bundle();
149         }
150         queryArgs.putString(CloudMediaProviderContract.KEY_PARENT_CATEGORY_ID, parentCategoryId);
151 
152         Log.d(TAG, "Categories query sent to CMP: " + queryArgs);
153 
154         final Cursor cursor = mContext.getContentResolver().query(
155                 getCloudUriFromPath(CloudMediaProviderContract.URI_PATH_MEDIA_CATEGORY),
156                 null, queryArgs, cancellationSignal);
157 
158         if (cursor == null) {
159             Log.d(TAG, "Categories response from the CMP is null.");
160 
161         } else {
162             Log.d(TAG, "Categories received from the CMP: " + cursor.getCount()
163                     + " extras: " + cursor.getExtras());
164         }
165         return cursor;
166     }
167 
168     /**
169      * Method for querying CloudMediaProvider for MediaSets
170      */
171     @Nullable
fetchMediaSetsFromCmp( @onNull String mediaCategoryId, @Nullable String nextPageToken, int pageSize, @Nullable String[] mimeTypes, @Nullable CancellationSignal cancellationSignal)172     public Cursor fetchMediaSetsFromCmp(
173             @NonNull String mediaCategoryId, @Nullable String nextPageToken, int pageSize,
174             @Nullable String[] mimeTypes, @Nullable CancellationSignal cancellationSignal)
175             throws OperationCanceledException {
176         final Bundle queryArgs = new Bundle();
177         queryArgs.putString(CloudMediaProviderContract.KEY_MEDIA_CATEGORY_ID,
178                 requireNonNull(mediaCategoryId));
179         queryArgs.putString(CloudMediaProviderContract.EXTRA_PAGE_TOKEN, nextPageToken);
180         queryArgs.putInt(CloudMediaProviderContract.EXTRA_PAGE_SIZE, pageSize);
181         queryArgs.putStringArray(Intent.EXTRA_MIME_TYPES, mimeTypes);
182 
183         Log.d(TAG, "Media sets query sent to CMP: " + queryArgs);
184 
185         final Cursor cursor = mContext.getContentResolver().query(
186                 getCloudUriFromPath(CloudMediaProviderContract.URI_PATH_MEDIA_SET),
187                 null, queryArgs, cancellationSignal);
188 
189         if (cursor == null) {
190             Log.d(TAG, "Media sets response from the CMP is null.");
191 
192         } else {
193             Log.d(TAG, "Media sets received from the CMP: " + cursor.getCount()
194                     + " extras: " + cursor.getExtras());
195         }
196         return cursor;
197     }
198 
199     /**
200      * Method for querying Medias inside a  MediaSet
201      */
202     @Nullable
fetchMediasInMediaSetFromCmp( @onNull String mediaSetId, @Nullable String pageToken, int pageSize, int sortOrder, @Nullable String[] mimeTypes, @Nullable CancellationSignal cancellationSignal)203     public Cursor fetchMediasInMediaSetFromCmp(
204             @NonNull String mediaSetId,
205             @Nullable String pageToken,
206             int pageSize,
207             int sortOrder,
208             @Nullable String[] mimeTypes,
209             @Nullable CancellationSignal cancellationSignal) throws OperationCanceledException {
210         final Bundle queryArgs = new Bundle();
211         queryArgs.putString(CloudMediaProviderContract.KEY_MEDIA_SET_ID,
212                 requireNonNull(mediaSetId));
213         queryArgs.putInt(CloudMediaProviderContract.EXTRA_PAGE_SIZE, pageSize);
214         queryArgs.putString(CloudMediaProviderContract.EXTRA_PAGE_TOKEN, pageToken);
215         queryArgs.putInt(CloudMediaProviderContract.EXTRA_SORT_ORDER, sortOrder);
216         queryArgs.putStringArray(Intent.EXTRA_MIME_TYPES, mimeTypes);
217 
218         Log.d(TAG, "Media set content query sent to CMP: " + queryArgs);
219 
220         final Cursor cursor = mContext.getContentResolver().query(
221                 getCloudUriFromPath(CloudMediaProviderContract.URI_PATH_MEDIA_IN_MEDIA_SET),
222                 null, queryArgs, cancellationSignal);
223 
224         if (cursor == null) {
225             Log.d(TAG, "Media set contents response from the CMP is null.");
226 
227         } else {
228             Log.d(TAG, "Media set contents received from the CMP: " + cursor.getCount()
229                     + " extras: " + cursor.getExtras());
230         }
231         return cursor;
232     }
233 
getCloudUriFromPath(String uriPath)234     private Uri getCloudUriFromPath(String uriPath) {
235         return Uri.parse("content://" + mCloudProviderAuthority + "/" + uriPath);
236     }
237 
238     /**
239      * Fetches the {@link android.provider.CloudMediaProviderContract.Capabilities} from the
240      * cloud media provider and returns them. In case there is an issue in fetching the
241      * capabilities, this method returns the default capabilities.
242      */
243     @NonNull
fetchCapabilities()244     public CloudMediaProviderContract.Capabilities fetchCapabilities() {
245         try {
246             final Bundle response = mContext.getContentResolver().call(
247                     mCloudProviderAuthority,
248                     METHOD_GET_CAPABILITIES,
249                     /* arg */ null,
250                     /* extras */ null);
251             requireNonNull(response);
252 
253             final CloudMediaProviderContract.Capabilities capabilities =
254                     response.getParcelable(EXTRA_PROVIDER_CAPABILITIES);
255             requireNonNull(capabilities);
256             Log.d(TAG, "Capabilities received from CMP: " + capabilities);
257 
258             return capabilities;
259         } catch (RuntimeException e) {
260             Log.e(TAG, "Could not fetch capabilities from " + mCloudProviderAuthority);
261 
262             // Return default capabilities.
263             return new CloudMediaProviderContract.Capabilities.Builder().build();
264         }
265     }
266 }
267