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