• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2020 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 android.app.appsearch;
18 
19 
20 import android.annotation.FlaggedApi;
21 import android.annotation.NonNull;
22 import android.app.appsearch.annotation.CanIgnoreReturnValue;
23 import android.app.appsearch.exceptions.AppSearchException;
24 import android.util.ArraySet;
25 
26 import com.android.appsearch.flags.Flags;
27 
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.Collection;
31 import java.util.Collections;
32 import java.util.List;
33 import java.util.Objects;
34 import java.util.Set;
35 
36 /**
37  * Encapsulates a request to index documents into an {@link AppSearchSession} database.
38  *
39  * @see AppSearchSession#put
40  */
41 // TODO(b/384721898): Switch to JSpecify annotations
42 @SuppressWarnings("JSpecifyNullness")
43 public final class PutDocumentsRequest {
44     private final List<GenericDocument> mDocuments;
45 
46     private final List<GenericDocument> mTakenActions;
47 
PutDocumentsRequest(List<GenericDocument> documents, List<GenericDocument> takenActions)48     PutDocumentsRequest(List<GenericDocument> documents, List<GenericDocument> takenActions) {
49         mDocuments = documents;
50         mTakenActions = takenActions;
51     }
52 
53     /** Returns a list of {@link GenericDocument} objects that are part of this request. */
getGenericDocuments()54     public @NonNull List<GenericDocument> getGenericDocuments() {
55         return Collections.unmodifiableList(mDocuments);
56     }
57 
58     /**
59      * Returns a list of {@link GenericDocument} objects containing taken action metrics that are
60      * part of this request.
61      *
62      * <p>See {@link Builder#addTakenActionGenericDocuments(GenericDocument...)}.
63      */
64     @FlaggedApi(Flags.FLAG_ENABLE_PUT_DOCUMENTS_REQUEST_ADD_TAKEN_ACTIONS)
getTakenActionGenericDocuments()65     public @NonNull List<GenericDocument> getTakenActionGenericDocuments() {
66         return Collections.unmodifiableList(mTakenActions);
67     }
68 
69     /** Builder for {@link PutDocumentsRequest} objects. */
70     public static final class Builder {
71         private ArrayList<GenericDocument> mDocuments = new ArrayList<>();
72         private ArrayList<GenericDocument> mTakenActions = new ArrayList<>();
73         private boolean mBuilt = false;
74 
75         /** Adds one or more {@link GenericDocument} objects to the request. */
76         @CanIgnoreReturnValue
addGenericDocuments(@onNull GenericDocument... documents)77         public @NonNull Builder addGenericDocuments(@NonNull GenericDocument... documents) {
78             Objects.requireNonNull(documents);
79             resetIfBuilt();
80             return addGenericDocuments(Arrays.asList(documents));
81         }
82 
83         /** Adds a collection of {@link GenericDocument} objects to the request. */
84         @CanIgnoreReturnValue
addGenericDocuments( @onNull Collection<? extends GenericDocument> documents)85         public @NonNull Builder addGenericDocuments(
86                 @NonNull Collection<? extends GenericDocument> documents) {
87             Objects.requireNonNull(documents);
88             resetIfBuilt();
89             mDocuments.addAll(documents);
90             return this;
91         }
92 
93         /**
94          * Adds one or more {@link GenericDocument} objects containing taken action metrics to the
95          * request.
96          *
97          * <p>It is recommended to use taken action document classes in Jetpack library to construct
98          * taken action documents.
99          *
100          * <p>The document creation timestamp of the {@link GenericDocument} should be set to the
101          * actual action timestamp via {@link GenericDocument.Builder#setCreationTimestampMillis}.
102          *
103          * <p>Clients should report search and click actions together sorted by {@link
104          * GenericDocument#getCreationTimestampMillis} in chronological order.
105          *
106          * <p>For example, if there are 2 search actions, with 1 click action associated with the
107          * first and 2 click actions associated with the second, then clients should report
108          * [searchAction1, clickAction1, searchAction2, clickAction2, clickAction3].
109          *
110          * <p>Different types of taken actions and metrics to be collected by AppSearch:
111          *
112          * <ul>
113          *   <li>Search action
114          *       <ul>
115          *         <li>actionType: LONG, the enum value of the action type.
116          *             <p>Requires to be {@code 1} for search actions.
117          *         <li>query: STRING, the user-entered search input (without any operators or
118          *             rewriting).
119          *         <li>fetchedResultCount: LONG, the number of {@link SearchResult} documents
120          *             fetched from AppSearch in this search action.
121          *       </ul>
122          *   <li>Click action
123          *       <ul>
124          *         <li>actionType: LONG, the enum value of the action type.
125          *             <p>Requires to be {@code 2} for click actions.
126          *         <li>query: STRING, the user-entered search input (without any operators or
127          *             rewriting) that yielded the {@link SearchResult} on which the user took
128          *             action.
129          *         <li>referencedQualifiedId: STRING, the qualified id of the {@link SearchResult}
130          *             document that the user takes action on.
131          *             <p>A qualified id is a string generated by package, database, namespace, and
132          *             document id. See {@link
133          *             android.app.appsearch.util.DocumentIdUtil#createQualifiedId} for more
134          *             details.
135          *         <li>resultRankInBlock: LONG, the rank of the {@link SearchResult} document among
136          *             the user-defined block.
137          *             <p>The client can define its own custom definition for block, for example,
138          *             corpus name, group, etc.
139          *             <p>For example, a client defines the block as corpus, and AppSearch returns 5
140          *             documents with corpus = ["corpus1", "corpus1", "corpus2", "corpus3",
141          *             "corpus2"]. Then the block ranks of them = [1, 2, 1, 1, 2].
142          *             <p>If the client is not presenting the results in multiple blocks, they
143          *             should set this value to match resultRankGlobal.
144          *         <li>resultRankGlobal: LONG, the global rank of the {@link SearchResult} document.
145          *             <p>Global rank reflects the order of {@link SearchResult} documents returned
146          *             by AppSearch.
147          *             <p>For example, AppSearch returns 2 pages with 10 {@link SearchResult}
148          *             documents for each page. Then the global ranks of them will be 1 to 10 for
149          *             the first page, and 11 to 20 for the second page.
150          *         <li>timeStayOnResultMillis: LONG, the time in milliseconds that user stays on the
151          *             {@link SearchResult} document after clicking it.
152          *       </ul>
153          * </ul>
154          *
155          * <p>Certain anonymized information about actions reported using this API may be uploaded
156          * using statsd and may be used to improve the quality of the search algorithms. Most of the
157          * information in this class is already non-identifiable, such as durations and its position
158          * in the result set. Identifiable information which you choose to provide, such as the
159          * query string, will be anonymized using techniques like Federated Analytics to ensure only
160          * the most frequently searched terms across the whole user population are retained and
161          * available for study.
162          *
163          * <p>You can alternatively use the {@link #addGenericDocuments(GenericDocument...)} API to
164          * retain the benefits of joining and using it on-device, without triggering any of the
165          * anonymized stats uploading described above.
166          *
167          * @param takenActionGenericDocuments one or more {@link GenericDocument} objects containing
168          *     taken action metric fields.
169          */
170         @CanIgnoreReturnValue
171         @FlaggedApi(Flags.FLAG_ENABLE_PUT_DOCUMENTS_REQUEST_ADD_TAKEN_ACTIONS)
addTakenActionGenericDocuments( @onNull GenericDocument... takenActionGenericDocuments)172         public @NonNull Builder addTakenActionGenericDocuments(
173                 @NonNull GenericDocument... takenActionGenericDocuments) throws AppSearchException {
174             Objects.requireNonNull(takenActionGenericDocuments);
175             resetIfBuilt();
176             return addTakenActionGenericDocuments(Arrays.asList(takenActionGenericDocuments));
177         }
178 
179         /**
180          * Adds a collection of {@link GenericDocument} objects containing taken action metrics to
181          * the request.
182          *
183          * @see #addTakenActionGenericDocuments(GenericDocument...)
184          * @param takenActionGenericDocuments a collection of {@link GenericDocument} objects
185          *     containing taken action metric fields.
186          */
187         @CanIgnoreReturnValue
188         @FlaggedApi(Flags.FLAG_ENABLE_PUT_DOCUMENTS_REQUEST_ADD_TAKEN_ACTIONS)
addTakenActionGenericDocuments( @onNull Collection<? extends GenericDocument> takenActionGenericDocuments)189         public @NonNull Builder addTakenActionGenericDocuments(
190                 @NonNull Collection<? extends GenericDocument> takenActionGenericDocuments)
191                 throws AppSearchException {
192             Objects.requireNonNull(takenActionGenericDocuments);
193             resetIfBuilt();
194             mTakenActions.addAll(takenActionGenericDocuments);
195             return this;
196         }
197 
198         /**
199          * Creates a new {@link PutDocumentsRequest} object.
200          *
201          * @throws IllegalArgumentException if there is any id collision between normal and action
202          *     documents.
203          */
build()204         public @NonNull PutDocumentsRequest build() {
205             mBuilt = true;
206 
207             // Verify there is no id collision between normal documents and action documents.
208             Set<String> idSet = new ArraySet<>();
209             for (int i = 0; i < mDocuments.size(); i++) {
210                 idSet.add(mDocuments.get(i).getId());
211             }
212             for (int i = 0; i < mTakenActions.size(); i++) {
213                 GenericDocument takenAction = mTakenActions.get(i);
214                 if (idSet.contains(takenAction.getId())) {
215                     throw new IllegalArgumentException(
216                             "Document id "
217                                     + takenAction.getId()
218                                     + " cannot exist in both taken action and normal document");
219                 }
220             }
221 
222             return new PutDocumentsRequest(mDocuments, mTakenActions);
223         }
224 
resetIfBuilt()225         private void resetIfBuilt() {
226             if (mBuilt) {
227                 mDocuments = new ArrayList<>(mDocuments);
228                 mTakenActions = new ArrayList<>(mTakenActions);
229                 mBuilt = false;
230             }
231         }
232     }
233 }
234