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.v2.model; 18 19 import static com.android.providers.media.photopicker.data.PickerDbFacade.KEY_CLOUD_ID; 20 import static com.android.providers.media.photopicker.data.PickerDbFacade.KEY_DATE_TAKEN_MS; 21 import static com.android.providers.media.photopicker.data.PickerDbFacade.KEY_ID; 22 import static com.android.providers.media.photopicker.data.PickerDbFacade.KEY_IS_VISIBLE; 23 import static com.android.providers.media.photopicker.data.PickerDbFacade.KEY_LOCAL_ID; 24 import static com.android.providers.media.photopicker.data.PickerDbFacade.KEY_MIME_TYPE; 25 26 import android.content.Intent; 27 import android.os.Bundle; 28 import android.text.TextUtils; 29 30 import androidx.annotation.NonNull; 31 import androidx.annotation.Nullable; 32 33 import com.android.providers.media.photopicker.v2.SelectSQLiteQueryBuilder; 34 35 import java.util.ArrayList; 36 import java.util.List; 37 import java.util.Objects; 38 39 /** 40 * Encapsulates all query arguments of a Media query i.e. query to fetch Media Items from Picker DB. 41 */ 42 public class MediaQuery { 43 private final long mDateTakenMs; 44 private final long mPickerId; 45 @NonNull 46 private final String mIntentAction; 47 @NonNull 48 private final List<String> mProviders; 49 // If this is not null or empty, only fetch the rows that match at least one of the 50 // given mime types. 51 @Nullable 52 protected List<String> mMimeTypes; 53 protected int mPageSize; 54 // If this is true, only fetch the rows from Picker Database where the IS_VISIBLE flag is on. 55 protected boolean mShouldDedupe; 56 MediaQuery(Bundle queryArgs)57 public MediaQuery(Bundle queryArgs) { 58 mPickerId = queryArgs.getLong("picker_id", Long.MAX_VALUE); 59 mDateTakenMs = queryArgs.getLong("date_taken_millis", Long.MAX_VALUE); 60 mPageSize = queryArgs.getInt("page_size", Integer.MAX_VALUE); 61 mIntentAction = Objects.requireNonNull(queryArgs.getString("intent_action")); 62 63 // Make deep copies of the arrays to avoid leaking changes made to the arrays. 64 mProviders = new ArrayList<>( 65 Objects.requireNonNull(queryArgs.getStringArrayList("providers"))); 66 mMimeTypes = queryArgs.getStringArrayList("mime_types") != null 67 ? new ArrayList<>(queryArgs.getStringArrayList("mime_types")) 68 : null; 69 70 // This is true by default. 71 mShouldDedupe = true; 72 } 73 74 @NonNull getPageSize()75 public Integer getPageSize() { 76 return mPageSize; 77 } 78 79 @NonNull getProviders()80 public List<String> getProviders() { 81 return mProviders; 82 } 83 84 @Nullable getMimeTypes()85 public List<String> getMimeTypes() { 86 return mMimeTypes; 87 } 88 89 @NonNull getIntentAction()90 public String getIntentAction() { 91 return mIntentAction; 92 } 93 94 /** 95 * Create and return a bundle for extras for CMP queries made from Media Provider. 96 */ 97 @NonNull prepareCMPQueryArgs()98 public Bundle prepareCMPQueryArgs() { 99 final Bundle queryArgs = new Bundle(); 100 if (mMimeTypes != null) { 101 queryArgs.putStringArray(Intent.EXTRA_MIME_TYPES, mMimeTypes.toArray(new String[0])); 102 } 103 return queryArgs; 104 } 105 106 /** 107 * @param queryBuilder Adds SQL query where clause based on the Media query arguments to the 108 * given query builder. 109 * @param localAuthority the authority of the local provider if we should include local media in 110 * the query response. Otherwise, this is null. 111 * @param cloudAuthority The authority of the cloud provider if we should include cloud media in 112 * the query response. Otherwise, this is null. 113 * @param reverseOrder by default, the sort order of the media query is 114 * (Date taken DESC, Picker ID DESC). But for some queries we want to query 115 * media in the reverse sort order i.e. (Date taken ASC, Picker id ASC). 116 * This is true when the query is running in the reverse sort order. 117 */ addWhereClause( @onNull SelectSQLiteQueryBuilder queryBuilder, @Nullable String localAuthority, @Nullable String cloudAuthority, boolean reverseOrder )118 public void addWhereClause( 119 @NonNull SelectSQLiteQueryBuilder queryBuilder, 120 @Nullable String localAuthority, 121 @Nullable String cloudAuthority, 122 boolean reverseOrder 123 ) { 124 if (mShouldDedupe) { 125 queryBuilder.appendWhereStandalone(KEY_IS_VISIBLE + " = 1"); 126 } 127 128 if (cloudAuthority == null) { 129 queryBuilder.appendWhereStandalone(KEY_CLOUD_ID + " IS NULL"); 130 } 131 132 if (localAuthority == null) { 133 queryBuilder.appendWhereStandalone(KEY_LOCAL_ID + " IS NULL"); 134 } 135 136 addMimeTypeClause(queryBuilder); 137 addDateTakenClause(queryBuilder, reverseOrder); 138 } 139 140 /** 141 * Adds the date taken clause to the given query builder. 142 * 143 * @param queryBuilder SelectSQLiteQueryBuilder to add the where clause 144 * @param reverseOrder Since the media results are sorted by (Date taken DESC, Picker ID DESC), 145 * this field is true when the query is made in the reverse order of the 146 * expected sort order i.e. (Date taken ASC, Picker ID ASC), 147 * and is false otherwise. 148 */ addDateTakenClause( @onNull SelectSQLiteQueryBuilder queryBuilder, boolean reverseOrder )149 private void addDateTakenClause( 150 @NonNull SelectSQLiteQueryBuilder queryBuilder, 151 boolean reverseOrder 152 ) { 153 if (reverseOrder) { 154 queryBuilder.appendWhereStandalone( 155 KEY_DATE_TAKEN_MS + " > " + mDateTakenMs 156 + " OR ( " + KEY_DATE_TAKEN_MS + " = " + mDateTakenMs 157 + " AND " + KEY_ID + " > " + mPickerId + ")"); 158 } else { 159 queryBuilder.appendWhereStandalone( 160 KEY_DATE_TAKEN_MS + " < " + mDateTakenMs 161 + " OR ( " + KEY_DATE_TAKEN_MS + " = " + mDateTakenMs 162 + " AND " + KEY_ID + " <= " + mPickerId + ")"); 163 } 164 } 165 166 /** 167 * Adds the mime type filter clause(s) to the given query builder. 168 * 169 * @param queryBuilder SelectSQLiteQueryBuilder to add the where clause. 170 */ addMimeTypeClause(@onNull SelectSQLiteQueryBuilder queryBuilder)171 private void addMimeTypeClause(@NonNull SelectSQLiteQueryBuilder queryBuilder) { 172 if (mMimeTypes == null || mMimeTypes.isEmpty()) { 173 return; 174 } 175 176 List<String> whereClauses = new ArrayList<>(); 177 for (String mimeType : mMimeTypes) { 178 if (!TextUtils.isEmpty(mimeType)) { 179 whereClauses.add(KEY_MIME_TYPE + " LIKE '" + mimeType.replace('*', '%') + "'"); 180 } 181 } 182 queryBuilder.appendWhereStandalone(" ( " + TextUtils.join(" OR ", whereClauses) + " ) "); 183 } 184 } 185