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.NonNull;
20 import android.annotation.Nullable;
21 import android.app.appsearch.annotation.CanIgnoreReturnValue;
22 
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.Collection;
26 import java.util.List;
27 import java.util.Objects;
28 
29 // TODO(b/319285816): link converter here.
30 /**
31  * Class holds detailed stats of a search session, converted from {@link
32  * android.app.appsearch.PutDocumentsRequest#getTakenActionGenericDocuments}. It contains a list of
33  * {@link SearchIntentStats} and aggregated metrics of them.
34  *
35  * <p>A search session is consist of a sequence of related search intents. See {@link
36  * SearchIntentStats} for more details.
37  *
38  * @hide
39  */
40 public final class SearchSessionStats {
41     @NonNull private final String mPackageName;
42 
43     @Nullable private final String mDatabase;
44 
45     @NonNull private final List<SearchIntentStats> mSearchIntentsStats;
46 
SearchSessionStats(@onNull Builder builder)47     SearchSessionStats(@NonNull Builder builder) {
48         Objects.requireNonNull(builder);
49         mPackageName = builder.mPackageName;
50         mDatabase = builder.mDatabase;
51         mSearchIntentsStats = builder.mSearchIntentsStats;
52     }
53 
54     /**
55      * Returns a nullable {@link SearchIntentStats} instance containing information of the last
56      * search intent which ended the search session.
57      *
58      * <p>If {@link #getSearchIntentsStats} is empty (i.e. the caller didn't add any {@link
59      * SearchIntentStats} via {@link Builder#addSearchIntentsStats}), then return null.
60      *
61      * <p>It is similar to the last element in {@link #getSearchIntentsStats}, except there is no
62      * previous query and the query correction type is tagged as {@link
63      * SearchIntentStats#QUERY_CORRECTION_TYPE_END_SESSION}.
64      *
65      * <p>This stats is useful to determine whether the user ended the search session with
66      * satisfaction (i.e. had found desired result documents) or not.
67      */
68     @Nullable
getEndSessionSearchIntentStats()69     public SearchIntentStats getEndSessionSearchIntentStats() {
70         if (mSearchIntentsStats.isEmpty()) {
71             return null;
72         }
73 
74         SearchIntentStats lastSearchIntentStats =
75                 mSearchIntentsStats.get(mSearchIntentsStats.size() - 1);
76         return new SearchIntentStats.Builder(lastSearchIntentStats)
77                 .setPrevQuery(null)
78                 .setQueryCorrectionType(SearchIntentStats.QUERY_CORRECTION_TYPE_END_SESSION)
79                 .build();
80     }
81 
82     /** Returns calling package name. */
83     @NonNull
getPackageName()84     public String getPackageName() {
85         return mPackageName;
86     }
87 
88     /**
89      * Returns calling database name.
90      *
91      * <p>For global search, database name will be null.
92      */
93     @Nullable
getDatabase()94     public String getDatabase() {
95         return mDatabase;
96     }
97 
98     /** Returns the list of {@link SearchIntentStats} in this search session. */
99     @NonNull
getSearchIntentsStats()100     public List<SearchIntentStats> getSearchIntentsStats() {
101         return mSearchIntentsStats;
102     }
103 
104     /** Builder for {@link SearchSessionStats}. */
105     public static final class Builder {
106         @NonNull private final String mPackageName;
107 
108         @Nullable private String mDatabase;
109 
110         @NonNull private List<SearchIntentStats> mSearchIntentsStats = new ArrayList<>();
111 
112         private boolean mBuilt = false;
113 
114         /** Constructor for the {@link Builder}. */
Builder(@onNull String packageName)115         public Builder(@NonNull String packageName) {
116             mPackageName = Objects.requireNonNull(packageName);
117         }
118 
119         /**
120          * Sets calling database name.
121          *
122          * <p>For global search, database name will be null.
123          */
124         @CanIgnoreReturnValue
125         @NonNull
setDatabase(@ullable String database)126         public Builder setDatabase(@Nullable String database) {
127             resetIfBuilt();
128             mDatabase = database;
129             return this;
130         }
131 
132         /** Adds one or more {@link SearchIntentStats} objects to this search intent. */
133         @CanIgnoreReturnValue
134         @NonNull
addSearchIntentsStats(@onNull SearchIntentStats... searchIntentsStats)135         public Builder addSearchIntentsStats(@NonNull SearchIntentStats... searchIntentsStats) {
136             Objects.requireNonNull(searchIntentsStats);
137             resetIfBuilt();
138             return addSearchIntentsStats(Arrays.asList(searchIntentsStats));
139         }
140 
141         /** Adds a collection of {@link SearchIntentStats} objects to this search intent. */
142         @CanIgnoreReturnValue
143         @NonNull
addSearchIntentsStats( @onNull Collection<? extends SearchIntentStats> searchIntentsStats)144         public Builder addSearchIntentsStats(
145                 @NonNull Collection<? extends SearchIntentStats> searchIntentsStats) {
146             Objects.requireNonNull(searchIntentsStats);
147             resetIfBuilt();
148             mSearchIntentsStats.addAll(searchIntentsStats);
149             return this;
150         }
151 
152         /**
153          * If built, make a copy of previous data for every field so that the builder can be reused.
154          */
resetIfBuilt()155         private void resetIfBuilt() {
156             if (mBuilt) {
157                 mSearchIntentsStats = new ArrayList<>(mSearchIntentsStats);
158                 mBuilt = false;
159             }
160         }
161 
162         /** Builds a new {@link SearchSessionStats} from the {@link Builder}. */
163         @NonNull
build()164         public SearchSessionStats build() {
165             mBuilt = true;
166             return new SearchSessionStats(/* builder= */ this);
167         }
168     }
169 }
170