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