1 /*
2  * Copyright 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.server.appsearch.external.localstorage.stats;
18 
19 import android.annotation.IntDef;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.app.appsearch.annotation.CanIgnoreReturnValue;
23 
24 import java.lang.annotation.Retention;
25 import java.lang.annotation.RetentionPolicy;
26 import java.util.ArrayList;
27 import java.util.Arrays;
28 import java.util.Collection;
29 import java.util.List;
30 import java.util.Objects;
31 
32 // TODO(b/319285816): link converter here.
33 /**
34  * Class holds detailed stats of a search intent, converted from {@link
35  * android.app.appsearch.PutDocumentsRequest#getTakenActionGenericDocuments}.
36  *
37  * <p>A search intent includes a valid AppSearch search request, potentially followed by several
38  * user click actions (see {@link ClickStats}) on fetched result documents. Related information of a
39  * search intent will be extracted from {@link
40  * android.app.appsearch.PutDocumentsRequest#getTakenActionGenericDocuments}.
41  *
42  * @hide
43  */
44 public final class SearchIntentStats {
45     /** AppSearch query correction type compared with the previous query. */
46     @IntDef(
47             value = {
48                 QUERY_CORRECTION_TYPE_UNKNOWN,
49                 QUERY_CORRECTION_TYPE_FIRST_QUERY,
50                 QUERY_CORRECTION_TYPE_REFINEMENT,
51                 QUERY_CORRECTION_TYPE_ABANDONMENT,
52                 QUERY_CORRECTION_TYPE_END_SESSION,
53             })
54     @Retention(RetentionPolicy.SOURCE)
55     public @interface QueryCorrectionType {}
56 
57     public static final int QUERY_CORRECTION_TYPE_UNKNOWN = 0;
58 
59     public static final int QUERY_CORRECTION_TYPE_FIRST_QUERY = 1;
60 
61     public static final int QUERY_CORRECTION_TYPE_REFINEMENT = 2;
62 
63     public static final int QUERY_CORRECTION_TYPE_ABANDONMENT = 3;
64 
65     public static final int QUERY_CORRECTION_TYPE_END_SESSION = 4;
66 
67     @NonNull private final String mPackageName;
68 
69     @Nullable private final String mDatabase;
70 
71     @Nullable private final String mPrevQuery;
72 
73     @Nullable private final String mCurrQuery;
74 
75     private final long mTimestampMillis;
76 
77     private final int mNumResultsFetched;
78 
79     @QueryCorrectionType private final int mQueryCorrectionType;
80 
81     @NonNull private final List<ClickStats> mClicksStats;
82 
SearchIntentStats(@onNull Builder builder)83     SearchIntentStats(@NonNull Builder builder) {
84         Objects.requireNonNull(builder);
85         mPackageName = builder.mPackageName;
86         mDatabase = builder.mDatabase;
87         mPrevQuery = builder.mPrevQuery;
88         mCurrQuery = builder.mCurrQuery;
89         mTimestampMillis = builder.mTimestampMillis;
90         mNumResultsFetched = builder.mNumResultsFetched;
91         mQueryCorrectionType = builder.mQueryCorrectionType;
92         mClicksStats = builder.mClicksStats;
93     }
94 
95     /** Returns calling package name. */
96     @NonNull
getPackageName()97     public String getPackageName() {
98         return mPackageName;
99     }
100 
101     /**
102      * Returns calling database name.
103      *
104      * <p>For global search, database name will be null.
105      */
106     @Nullable
getDatabase()107     public String getDatabase() {
108         return mDatabase;
109     }
110 
111     /** Returns the raw query string of the previous search intent. */
112     @Nullable
getPrevQuery()113     public String getPrevQuery() {
114         return mPrevQuery;
115     }
116 
117     /** Returns the raw query string of this (current) search intent. */
118     @Nullable
getCurrQuery()119     public String getCurrQuery() {
120         return mCurrQuery;
121     }
122 
123     /** Returns the search intent timestamp in milliseconds since Unix epoch. */
getTimestampMillis()124     public long getTimestampMillis() {
125         return mTimestampMillis;
126     }
127 
128     /**
129      * Returns total number of results fetched from AppSearch by the client in this search intent.
130      */
getNumResultsFetched()131     public int getNumResultsFetched() {
132         return mNumResultsFetched;
133     }
134 
135     /**
136      * Returns the correction type of the query in this search intent compared with the previous
137      * search intent. Default value: {@link SearchIntentStats#QUERY_CORRECTION_TYPE_UNKNOWN}.
138      */
139     @QueryCorrectionType
getQueryCorrectionType()140     public int getQueryCorrectionType() {
141         return mQueryCorrectionType;
142     }
143 
144     /** Returns the list of {@link ClickStats} in this search intent. */
145     @NonNull
getClicksStats()146     public List<ClickStats> getClicksStats() {
147         return mClicksStats;
148     }
149 
150     /** Builder for {@link SearchIntentStats} */
151     public static final class Builder {
152         @NonNull private final String mPackageName;
153 
154         @Nullable private String mDatabase;
155 
156         @Nullable private String mPrevQuery;
157 
158         @Nullable private String mCurrQuery;
159 
160         private long mTimestampMillis;
161 
162         private int mNumResultsFetched;
163 
164         @QueryCorrectionType private int mQueryCorrectionType = QUERY_CORRECTION_TYPE_UNKNOWN;
165 
166         @NonNull private List<ClickStats> mClicksStats = new ArrayList<>();
167 
168         private boolean mBuilt = false;
169 
170         /** Constructor for the {@link Builder}. */
Builder(@onNull String packageName)171         public Builder(@NonNull String packageName) {
172             mPackageName = Objects.requireNonNull(packageName);
173         }
174 
175         /** Constructor the {@link Builder} from an existing {@link SearchIntentStats}. */
Builder(@onNull SearchIntentStats searchIntentStats)176         public Builder(@NonNull SearchIntentStats searchIntentStats) {
177             Objects.requireNonNull(searchIntentStats);
178 
179             mPackageName = searchIntentStats.getPackageName();
180             mDatabase = searchIntentStats.getDatabase();
181             mPrevQuery = searchIntentStats.getPrevQuery();
182             mCurrQuery = searchIntentStats.getCurrQuery();
183             mTimestampMillis = searchIntentStats.getTimestampMillis();
184             mNumResultsFetched = searchIntentStats.getNumResultsFetched();
185             mQueryCorrectionType = searchIntentStats.getQueryCorrectionType();
186             mClicksStats.addAll(searchIntentStats.getClicksStats());
187         }
188 
189         /**
190          * Sets calling database name.
191          *
192          * <p>For global search, database name will be null.
193          */
194         @CanIgnoreReturnValue
195         @NonNull
setDatabase(@ullable String database)196         public Builder setDatabase(@Nullable String database) {
197             resetIfBuilt();
198             mDatabase = database;
199             return this;
200         }
201 
202         /** Sets the raw query string of the previous search intent. */
203         @CanIgnoreReturnValue
204         @NonNull
setPrevQuery(@ullable String prevQuery)205         public Builder setPrevQuery(@Nullable String prevQuery) {
206             resetIfBuilt();
207             mPrevQuery = prevQuery;
208             return this;
209         }
210 
211         /** Sets the raw query string of this (current) search intent. */
212         @CanIgnoreReturnValue
213         @NonNull
setCurrQuery(@ullable String currQuery)214         public Builder setCurrQuery(@Nullable String currQuery) {
215             resetIfBuilt();
216             mCurrQuery = currQuery;
217             return this;
218         }
219 
220         /** Sets the search intent timestamp in milliseconds since Unix epoch. */
221         @CanIgnoreReturnValue
222         @NonNull
setTimestampMillis(long timestampMillis)223         public Builder setTimestampMillis(long timestampMillis) {
224             resetIfBuilt();
225             mTimestampMillis = timestampMillis;
226             return this;
227         }
228 
229         /**
230          * Sets total number of results fetched from AppSearch by the client in this search intent.
231          */
232         @CanIgnoreReturnValue
233         @NonNull
setNumResultsFetched(int numResultsFetched)234         public Builder setNumResultsFetched(int numResultsFetched) {
235             resetIfBuilt();
236             mNumResultsFetched = numResultsFetched;
237             return this;
238         }
239 
240         /**
241          * Sets the correction type of the query in this search intent compared with the previous
242          * search intent.
243          */
244         @CanIgnoreReturnValue
245         @NonNull
setQueryCorrectionType(@ueryCorrectionType int queryCorrectionType)246         public Builder setQueryCorrectionType(@QueryCorrectionType int queryCorrectionType) {
247             resetIfBuilt();
248             mQueryCorrectionType = queryCorrectionType;
249             return this;
250         }
251 
252         /** Adds one or more {@link ClickStats} objects to this search intent. */
253         @CanIgnoreReturnValue
254         @NonNull
addClicksStats(@onNull ClickStats... clicksStats)255         public Builder addClicksStats(@NonNull ClickStats... clicksStats) {
256             Objects.requireNonNull(clicksStats);
257             resetIfBuilt();
258             return addClicksStats(Arrays.asList(clicksStats));
259         }
260 
261         /** Adds a collection of {@link ClickStats} objects to this search intent. */
262         @CanIgnoreReturnValue
263         @NonNull
addClicksStats(@onNull Collection<? extends ClickStats> clicksStats)264         public Builder addClicksStats(@NonNull Collection<? extends ClickStats> clicksStats) {
265             Objects.requireNonNull(clicksStats);
266             resetIfBuilt();
267             mClicksStats.addAll(clicksStats);
268             return this;
269         }
270 
271         /**
272          * If built, make a copy of previous data for every field so that the builder can be reused.
273          */
resetIfBuilt()274         private void resetIfBuilt() {
275             if (mBuilt) {
276                 mClicksStats = new ArrayList<>(mClicksStats);
277                 mBuilt = false;
278             }
279         }
280 
281         /** Builds a new {@link SearchIntentStats} from the {@link Builder}. */
282         @NonNull
build()283         public SearchIntentStats build() {
284             mBuilt = true;
285             return new SearchIntentStats(/* builder= */ this);
286         }
287     }
288 }
289