diff options
author | 2024-10-25 17:00:00 +0000 | |
---|---|---|
committer | 2024-10-30 14:13:12 +0000 | |
commit | 726d3dff6b4b9861ff6e850564b2012bbedcd68b (patch) | |
tree | e840aafdacf4f2000238e8711bc717401e54b006 | |
parent | 91f2d2e2cbd5736c89fdbd23da2d0d44c7e85654 (diff) |
Add worker for search results sync
Bug: 361042632
Test: atest SearchResultsSyncWorkerTest
Test: atest SearchRequestDatabaseUtilTest
Change-Id: Ic1aee325a0a3f0073b0c00d503d2117fd2103ae1
Flag: com.android.providers.media.flags.enable_photopicker_search
-rw-r--r-- | src/com/android/providers/media/photopicker/sync/PickerSearchProviderClient.java (renamed from src/com/android/providers/media/photopicker/v2/PickerSearchProviderClient.java) | 12 | ||||
-rw-r--r-- | src/com/android/providers/media/photopicker/sync/PickerSyncManager.java | 1 | ||||
-rw-r--r-- | src/com/android/providers/media/photopicker/sync/SearchResultsSyncWorker.java | 338 | ||||
-rw-r--r-- | src/com/android/providers/media/photopicker/v2/model/SearchRequest.java | 9 | ||||
-rw-r--r-- | src/com/android/providers/media/photopicker/v2/sqlite/SearchLocalMediaSubQuery.java | 3 | ||||
-rw-r--r-- | src/com/android/providers/media/photopicker/v2/sqlite/SearchRequestDatabaseUtil.java | 45 | ||||
-rw-r--r-- | src/com/android/providers/media/photopicker/v2/sqlite/SearchResultsDatabaseUtil.java | 61 |
7 files changed, 461 insertions, 8 deletions
diff --git a/src/com/android/providers/media/photopicker/v2/PickerSearchProviderClient.java b/src/com/android/providers/media/photopicker/sync/PickerSearchProviderClient.java index 0857d2c9a..7b37865d9 100644 --- a/src/com/android/providers/media/photopicker/v2/PickerSearchProviderClient.java +++ b/src/com/android/providers/media/photopicker/sync/PickerSearchProviderClient.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.providers.media.photopicker.v2; +package com.android.providers.media.photopicker.sync; import static java.util.Objects.requireNonNull; @@ -60,8 +60,12 @@ public class PickerSearchProviderClient { * Note: This functions does not expect pagination args. */ @Nullable - public Cursor fetchSearchResultsFromCmp(@Nullable String suggestedMediaSetId, - @Nullable String searchText, @NonNull @SortOrder int sortOrder, + public Cursor fetchSearchResultsFromCmp( + @Nullable String suggestedMediaSetId, + @Nullable String searchText, + @SortOrder int sortOrder, + int pageSize, + @Nullable String resumePageToken, @Nullable CancellationSignal cancellationSignal) { if (suggestedMediaSetId == null && searchText == null) { throw new IllegalArgumentException( @@ -70,6 +74,8 @@ public class PickerSearchProviderClient { final Bundle queryArgs = new Bundle(); queryArgs.putString(CloudMediaProviderContract.KEY_SEARCH_TEXT, searchText); queryArgs.putString(CloudMediaProviderContract.KEY_MEDIA_SET_ID, suggestedMediaSetId); + queryArgs.putInt(CloudMediaProviderContract.EXTRA_PAGE_SIZE, pageSize); + queryArgs.putString(CloudMediaProviderContract.EXTRA_PAGE_TOKEN, resumePageToken); queryArgs.putInt(CloudMediaProviderContract.EXTRA_SORT_ORDER, sortOrder); return mContext.getContentResolver().query( diff --git a/src/com/android/providers/media/photopicker/sync/PickerSyncManager.java b/src/com/android/providers/media/photopicker/sync/PickerSyncManager.java index 88f52e08e..f39517f08 100644 --- a/src/com/android/providers/media/photopicker/sync/PickerSyncManager.java +++ b/src/com/android/providers/media/photopicker/sync/PickerSyncManager.java @@ -86,6 +86,7 @@ public class PickerSyncManager { static final String SYNC_WORKER_INPUT_SYNC_SOURCE = "INPUT_SYNC_TYPE"; static final String SYNC_WORKER_INPUT_RESET_TYPE = "INPUT_RESET_TYPE"; static final String SYNC_WORKER_INPUT_ALBUM_ID = "INPUT_ALBUM_ID"; + static final String SYNC_WORKER_INPUT_SEARCH_REQUEST_ID = "INPUT_SEARCH_REQUEST_ID"; static final String SYNC_WORKER_TAG_IS_PERIODIC = "PERIODIC"; static final long PROACTIVE_SYNC_DELAY_MS = 1500; private static final int SYNC_MEDIA_PERIODIC_WORK_INTERVAL = 4; // Time unit is hours. diff --git a/src/com/android/providers/media/photopicker/sync/SearchResultsSyncWorker.java b/src/com/android/providers/media/photopicker/sync/SearchResultsSyncWorker.java new file mode 100644 index 000000000..76511ba2a --- /dev/null +++ b/src/com/android/providers/media/photopicker/sync/SearchResultsSyncWorker.java @@ -0,0 +1,338 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.providers.media.photopicker.sync; + +import static com.android.providers.media.photopicker.sync.PickerSyncManager.SYNC_CLOUD_ONLY; +import static com.android.providers.media.photopicker.sync.PickerSyncManager.SYNC_LOCAL_ONLY; +import static com.android.providers.media.photopicker.sync.PickerSyncManager.SYNC_WORKER_INPUT_SEARCH_REQUEST_ID; +import static com.android.providers.media.photopicker.sync.PickerSyncManager.SYNC_WORKER_INPUT_SYNC_SOURCE; + +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.os.Bundle; +import android.os.CancellationSignal; +import android.provider.CloudMediaProviderContract; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; +import androidx.work.ListenableWorker; +import androidx.work.Worker; +import androidx.work.WorkerParameters; + +import com.android.providers.media.photopicker.PickerSyncController; +import com.android.providers.media.photopicker.util.exceptions.RequestObsoleteException; +import com.android.providers.media.photopicker.v2.model.SearchRequest; +import com.android.providers.media.photopicker.v2.model.SearchSuggestionRequest; +import com.android.providers.media.photopicker.v2.model.SearchSuggestionType; +import com.android.providers.media.photopicker.v2.model.SearchTextRequest; +import com.android.providers.media.photopicker.v2.sqlite.SearchRequestDatabaseUtil; +import com.android.providers.media.photopicker.v2.sqlite.SearchResultsDatabaseUtil; + +import java.util.List; + +/** + * This is a {@link Worker} class responsible for syncing search results media with the + * correct sync source. + */ +public class SearchResultsSyncWorker extends Worker { + private static final String TAG = "SearchSyncWorker"; + private static final int SYNC_PAGE_COUNT = 3; + private static final int PAGE_SIZE = 500; + private static final int INVALID_SYNC_SOURCE = -1; + private static final int INVALID_SEARCH_REQUEST_ID = -1; + @VisibleForTesting + public static final String SYNC_COMPLETE_RESUME_KEY = "SYNCED"; + private final Context mContext; + private final CancellationSignal mCancellationSignal; + + /** + * Creates an instance of the {@link Worker}. + * + * @param context the application {@link Context} + * @param workerParams the set of {@link WorkerParameters} + */ + public SearchResultsSyncWorker( + @NonNull Context context, + @NonNull WorkerParameters workerParams) { + super(context, workerParams); + + mContext = context; + mCancellationSignal = new CancellationSignal(); + } + + @NonNull + @Override + public ListenableWorker.Result doWork() { + // Do not allow endless re-runs of this worker, if this isn't the original run, + // just succeed and wait until the next scheduled run. + if (getRunAttemptCount() > 0) { + Log.w(TAG, "Worker retry was detected, ending this run in failure."); + return ListenableWorker.Result.failure(); + } + + final int syncSource = getInputData().getInt(SYNC_WORKER_INPUT_SYNC_SOURCE, + /* defaultValue */ INVALID_SYNC_SOURCE); + final int searchRequestId = getInputData().getInt(SYNC_WORKER_INPUT_SEARCH_REQUEST_ID, + /* defaultValue */ INVALID_SEARCH_REQUEST_ID); + + Log.i(TAG, String.format( + "Starting search results sync from sync source: %s search request id: %s", + syncSource, searchRequestId)); + + try { + throwIfWorkerStopped(); + + final SearchRequest searchRequest = SearchRequestDatabaseUtil + .getSearchRequestDetails(getDatabase(), searchRequestId); + validateWorkInput(syncSource, searchRequestId, searchRequest); + + syncWithSource(syncSource, searchRequestId, searchRequest); + + Log.i(TAG, String.format( + "Completed search results sync from sync source: %s search request id: %s", + syncSource, searchRequestId)); + return ListenableWorker.Result.success(); + } catch (RuntimeException | RequestObsoleteException e) { + Log.e(TAG, String.format("Could not complete search results sync sync from " + + "sync source: %s search request id: %s", + syncSource, searchRequestId), e); + return ListenableWorker.Result.failure(); + } + } + + /** + * Sync search results with the given sync source. + * + * @param syncSource Identifies if we need to sync with local provider or cloud provider. + * @param searchRequestId Identifier for the search request. + * @param searchRequest Details of the search request. + * @throws IllegalArgumentException If the search request could not be identified. + * @throws RequestObsoleteException If the search request has become obsolete. + */ + private void syncWithSource( + int syncSource, + int searchRequestId, + @Nullable SearchRequest searchRequest) + throws IllegalArgumentException, RequestObsoleteException { + final String authority = getProviderAuthority(syncSource, searchRequest); + final PickerSearchProviderClient searchClient = + PickerSearchProviderClient.create(mContext, authority); + + String resumePageToken = searchRequest.getResumeKey(); + + if (SYNC_COMPLETE_RESUME_KEY.equals(resumePageToken)) { + Log.i(TAG, "Sync has already been completed."); + return; + } + + try { + for (int iteration = 0; iteration < SYNC_PAGE_COUNT; iteration++) { + throwIfWorkerStopped(); + throwIfCloudProviderHasChanged(authority); + + try (Cursor cursor = fetchSearchResultsFromCmp( + searchClient, searchRequest, resumePageToken)) { + + List<ContentValues> contentValues = + SearchResultsDatabaseUtil.extractContentValuesList( + searchRequestId, cursor, isLocal(authority)); + + SearchResultsDatabaseUtil + .cacheSearchResults(getDatabase(), authority, contentValues); + + resumePageToken = getResumePageToken(cursor.getExtras()); + if (SYNC_COMPLETE_RESUME_KEY.equals(resumePageToken)) { + // Stop syncing if there are no more pages to sync. + break; + } + } + } + } finally { + // Save sync resume key till the point it was performed successfully + searchRequest.setResumeKey(resumePageToken); + SearchRequestDatabaseUtil + .updateResumeKey(getDatabase(), searchRequestId, resumePageToken); + } + } + + /** + * @param extras Bundle received from the CloudMediaProvider with the search results cursor. + * @return Extracts the rsume page token from the extras and returns it. If it is not present + * in the extras, returns {@link SearchResultsSyncWorker#SYNC_COMPLETE_RESUME_KEY} + */ + @NonNull + private String getResumePageToken(@Nullable Bundle extras) { + if (extras == null + || extras.getString(CloudMediaProviderContract.EXTRA_PAGE_TOKEN) == null) { + return SYNC_COMPLETE_RESUME_KEY; + } + + return extras.getString(CloudMediaProviderContract.EXTRA_PAGE_TOKEN); + } + + /** + * Get search results from the CloudMediaProvider. + */ + @NonNull + private Cursor fetchSearchResultsFromCmp( + @NonNull PickerSearchProviderClient searchClient, + @NonNull SearchRequest searchRequest, + @Nullable String resumePageToken) { + final String suggestedMediaSetId; + final String searchText; + if (searchRequest instanceof SearchSuggestionRequest searchSuggestionRequest) { + suggestedMediaSetId = searchSuggestionRequest.getMediaSetId(); + searchText = searchSuggestionRequest.getSearchText(); + } else if (searchRequest instanceof SearchTextRequest searchTextRequest) { + suggestedMediaSetId = null; + searchText = searchTextRequest.getSearchText(); + } else { + throw new IllegalArgumentException("Could not recognize the type of SearchRequest"); + } + + final Cursor cursor = searchClient.fetchSearchResultsFromCmp( + suggestedMediaSetId, + searchText, + CloudMediaProviderContract.SORT_ORDER_DESC_DATE_TAKEN, + PAGE_SIZE, + resumePageToken, + mCancellationSignal + ); + + if (cursor == null) { + throw new IllegalStateException("Cursor returned from provider is null."); + } + + return cursor; + } + + + /** + * Validates input data received by the Worker for an immediate album sync. + */ + private void validateWorkInput( + int syncSource, + int searchRequestId, + @Nullable SearchRequest searchRequest) throws IllegalArgumentException { + // Search result sync can only happen with either local provider or cloud provider. This + // information needs to be provided in the {@code inputData}. + if (syncSource != SYNC_LOCAL_ONLY && syncSource != SYNC_CLOUD_ONLY) { + throw new IllegalArgumentException("Invalid search results sync source " + syncSource); + } + if (searchRequestId == INVALID_SEARCH_REQUEST_ID) { + throw new IllegalArgumentException("Invalid search request id " + searchRequestId); + } + if (searchRequest == null) { + throw new IllegalArgumentException( + "Could not get search request details for search request id " + + searchRequestId); + } + if (searchRequest instanceof SearchSuggestionRequest searchSuggestionRequest) { + if (searchSuggestionRequest.getSearchSuggestionType() == SearchSuggestionType.ALBUM) { + final boolean isLocal = isLocal(searchSuggestionRequest.getAuthority()); + + if (isLocal && syncSource == SYNC_CLOUD_ONLY) { + throw new IllegalArgumentException( + "Cannot sync with cloud provider for local album suggestion. " + + "Search request id: " + searchRequestId); + } else if (!isLocal && syncSource == SYNC_LOCAL_ONLY) { + throw new IllegalArgumentException( + "Cannot sync with local provider for cloud album suggestion. " + + "Search request id: " + searchRequestId); + } + } + } + } + + private String getProviderAuthority( + int syncSource, + @NonNull SearchRequest searchRequest) { + final String authority; + if (syncSource == SYNC_LOCAL_ONLY) { + authority = getLocalProviderAuthority(); + } else if (syncSource == SYNC_CLOUD_ONLY) { + authority = getCurrentCloudProviderAuthority(); + } else { + throw new IllegalArgumentException("Invalid search results sync source " + syncSource); + } + + if (authority == null) { + throw new IllegalArgumentException("Authority of the provider to sync search results " + + "with cannot be null"); + } + + // Only in case of ALBUM type search suggestion, we want to explicitly query the source + // suggestion authority. For the rest of the suggestion types, we can query both + // available providers - local and cloud. + if (searchRequest instanceof SearchSuggestionRequest searchSuggestionRequest) { + if (searchSuggestionRequest.getSearchSuggestionType() == SearchSuggestionType.ALBUM) { + if (!authority.equals(searchSuggestionRequest.getAuthority())) { + throw new IllegalArgumentException(String.format( + "Mismatch in the suggestion source authority %s and the " + + "current sync authority %s for album search results sync", + searchSuggestionRequest.getAuthority(), + authority)); + } + } + } + + return authority; + } + + private void throwIfCloudProviderHasChanged(@NonNull String authority) + throws RequestObsoleteException { + // Local provider's authority cannot change. + if (isLocal(authority)) { + return; + } + + final String currentCloudAuthority = getCurrentCloudProviderAuthority(); + if (!authority.equals(currentCloudAuthority)) { + throw new RequestObsoleteException("Cloud provider authority has changed. " + + " Current cloud provider authority: " + currentCloudAuthority + + " Cloud provider authority to sync with: " + authority); + } + } + + private void throwIfWorkerStopped() throws RequestObsoleteException { + if (isStopped()) { + throw new RequestObsoleteException("Work is stopped " + getId()); + } + } + + private boolean isLocal(@NonNull String authority) { + return getLocalProviderAuthority().equals(authority); + } + + @Nullable + private String getLocalProviderAuthority() { + return PickerSyncController.getInstanceOrThrow().getLocalProvider(); + } + + @Nullable + private String getCurrentCloudProviderAuthority() { + return PickerSyncController.getInstanceOrThrow().getCloudProvider(); + } + + private SQLiteDatabase getDatabase() { + return PickerSyncController.getInstanceOrThrow().getDbFacade().getDatabase(); + } +} diff --git a/src/com/android/providers/media/photopicker/v2/model/SearchRequest.java b/src/com/android/providers/media/photopicker/v2/model/SearchRequest.java index aac92aa99..7e0292e54 100644 --- a/src/com/android/providers/media/photopicker/v2/model/SearchRequest.java +++ b/src/com/android/providers/media/photopicker/v2/model/SearchRequest.java @@ -36,7 +36,7 @@ public abstract class SearchRequest { @Nullable protected final List<String> mMimeTypes; @Nullable - protected final String mResumeKey; + protected String mResumeKey; protected SearchRequest(@Nullable List<String> rawMimeTypes) { this ( @@ -106,5 +106,12 @@ public abstract class SearchRequest { public String getResumeKey() { return mResumeKey; } + + /** + * Set the resume key for a given search request. + */ + public void setResumeKey(@Nullable String mResumeKey) { + this.mResumeKey = mResumeKey; + } } diff --git a/src/com/android/providers/media/photopicker/v2/sqlite/SearchLocalMediaSubQuery.java b/src/com/android/providers/media/photopicker/v2/sqlite/SearchLocalMediaSubQuery.java index 836728019..4fbf855c3 100644 --- a/src/com/android/providers/media/photopicker/v2/sqlite/SearchLocalMediaSubQuery.java +++ b/src/com/android/providers/media/photopicker/v2/sqlite/SearchLocalMediaSubQuery.java @@ -58,6 +58,9 @@ public class SearchLocalMediaSubQuery extends SearchMediaSubQuery { ) { super.addWhereClause(queryBuilder, table, localAuthority, cloudAuthority, reverseOrder); + // In order to identify if a row represents local media item and not a cloud media item, + // check if the cloud_id is null. We can't have a check on local_id because local_id can be + // populated for a cloud media item as well. queryBuilder.appendWhereStandalone( String.format( Locale.ROOT, diff --git a/src/com/android/providers/media/photopicker/v2/sqlite/SearchRequestDatabaseUtil.java b/src/com/android/providers/media/photopicker/v2/sqlite/SearchRequestDatabaseUtil.java index 9f60ad688..2ad4f384b 100644 --- a/src/com/android/providers/media/photopicker/v2/sqlite/SearchRequestDatabaseUtil.java +++ b/src/com/android/providers/media/photopicker/v2/sqlite/SearchRequestDatabaseUtil.java @@ -34,6 +34,7 @@ import com.android.providers.media.photopicker.v2.model.SearchSuggestionType; import com.android.providers.media.photopicker.v2.model.SearchTextRequest; import java.util.List; +import java.util.Locale; /** * Convenience class for running Picker Search related sql queries. @@ -51,15 +52,15 @@ public class SearchRequestDatabaseUtil { public static final String PLACEHOLDER_FOR_NULL = ""; /** - * Tries to insert the given search request in the DB with the IGNORE constraint conflict + * Tries to insert the given search request in the DB with the REPLACE constraint conflict * resolution strategy. * * @param database The database you need to run the query on. * @param searchRequest An object that contains search request details. - * @return The row id of the inserted row. If the insertion did not happen, return -1. + * @return The row id of the inserted row or -1 in case of a SQLite constraint conflict. * @throws RuntimeException if an error occurs in running the sql command. */ - public static long saveSearchRequestIfRequired( + public static long saveSearchRequest( @NonNull SQLiteDatabase database, @NonNull SearchRequest searchRequest) { final String table = PickerSQLConstants.Table.SEARCH_REQUEST.name(); @@ -73,7 +74,7 @@ public class SearchRequestDatabaseUtil { ); if (result == -1) { - Log.e(TAG, "Insertion ignored because the row already exists"); + Log.e(TAG, "Could not save request due to a conflict constraint"); } return result; } catch (RuntimeException e) { @@ -82,6 +83,41 @@ public class SearchRequestDatabaseUtil { } /** + * Update resume key for the given search request ID. + * + * @param database The database you need to run the query on. + * @param searchRequestId Identifier for a search request. + * @param resumeKey The resume key that can be used to fetch the next page of results, + * or indicate that the sync is complete. + * @throws RuntimeException if an error occurs in running the sql command. + */ + public static void updateResumeKey( + @NonNull SQLiteDatabase database, + int searchRequestId, + @Nullable String resumeKey) { + final String table = PickerSQLConstants.Table.SEARCH_REQUEST.name(); + + ContentValues contentValues = new ContentValues(); + contentValues.put( + PickerSQLConstants.SearchRequestTableColumns.SYNC_RESUME_KEY.getColumnName(), + resumeKey); + + database.update( + table, + contentValues, + String.format( + Locale.ROOT, + "%s.%s = %d", + table, + PickerSQLConstants.SearchRequestTableColumns + .SEARCH_REQUEST_ID.getColumnName(), + searchRequestId + ), + null + ); + } + + /** * Queries the database to try and fetch a unique search request ID for the given search * request. * @@ -134,6 +170,7 @@ public class SearchRequestDatabaseUtil { * or null if it can't find the search request in the database. In case multiple search * requests are a match, the first one is returned. */ + @Nullable public static SearchRequest getSearchRequestDetails( @NonNull SQLiteDatabase database, @NonNull int searchRequestID diff --git a/src/com/android/providers/media/photopicker/v2/sqlite/SearchResultsDatabaseUtil.java b/src/com/android/providers/media/photopicker/v2/sqlite/SearchResultsDatabaseUtil.java index 13a7fa43d..7ddb401e5 100644 --- a/src/com/android/providers/media/photopicker/v2/sqlite/SearchResultsDatabaseUtil.java +++ b/src/com/android/providers/media/photopicker/v2/sqlite/SearchResultsDatabaseUtil.java @@ -25,11 +25,14 @@ import static com.android.providers.media.photopicker.v2.sqlite.PickerMediaDatab import static java.util.Objects.requireNonNull; +import android.content.ContentUris; import android.content.ContentValues; import android.database.Cursor; import android.database.SQLException; import android.database.sqlite.SQLiteDatabase; +import android.net.Uri; import android.os.Bundle; +import android.provider.CloudMediaProviderContract; import android.util.Log; import androidx.annotation.NonNull; @@ -37,12 +40,70 @@ import androidx.annotation.Nullable; import com.android.providers.media.photopicker.PickerSyncController; +import java.util.ArrayList; import java.util.List; public class SearchResultsDatabaseUtil { private static final String TAG = "SearchResultsDatabaseUtil"; /** + * Utility method that extracts ContentValues in a format that can be inserted in the + * search_result_table. + * + * @param searchRequestId Identifier for a search request. + * @param cursor Cursor received from a CloudMediaProvider with the projection + * {@link CloudMediaProviderContract.MediaColumns} + * @param isLocal true if the received cursor came from the local provider, otherwise false. + * @return a list of ContentValues that can be inserted in the search_result_media table. + */ + @NonNull + public static List<ContentValues> extractContentValuesList( + int searchRequestId, @NonNull Cursor cursor, boolean isLocal + ) { + final List<ContentValues> contentValuesList = new ArrayList<>(cursor.getCount()); + if (cursor.moveToFirst()) { + do { + contentValuesList.add(extractContentValues(searchRequestId, cursor, isLocal)); + } while (cursor.moveToNext()); + } + return contentValuesList; + } + + @NonNull + private static ContentValues extractContentValues( + int searchRequestId, + @NonNull Cursor cursor, + boolean isLocal) { + final ContentValues contentValues = new ContentValues(); + + final String id = cursor.getString(cursor.getColumnIndexOrThrow( + CloudMediaProviderContract.MediaColumns.ID)); + final String rawMediaStoreUri = cursor.getString(cursor.getColumnIndexOrThrow( + CloudMediaProviderContract.MediaColumns.MEDIA_STORE_URI)); + final Uri mediaStoreUri = rawMediaStoreUri == null ? null : Uri.parse(rawMediaStoreUri); + final String extractedLocalId = mediaStoreUri == null ? null + : String.valueOf(ContentUris.parseId(mediaStoreUri)); + + final String localId = isLocal ? id : extractedLocalId; + final String cloudId = isLocal ? null : id; + + contentValues.put( + PickerSQLConstants.SearchResultMediaTableColumns.SEARCH_REQUEST_ID.getColumnName(), + searchRequestId + ); + contentValues.put( + PickerSQLConstants.SearchResultMediaTableColumns.LOCAL_ID.getColumnName(), + localId + ); + contentValues.put( + PickerSQLConstants.SearchResultMediaTableColumns.CLOUD_ID.getColumnName(), + cloudId + ); + + return contentValues; + } + + /** * Saved the search results media items received from CMP in the database as a temporary cache. * * @param database SQLite database object that holds DB connection(s) and provides a wrapper |