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 import static android.app.appsearch.AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_NONE; 20 import static android.app.appsearch.AppSearchSchema.StringPropertyConfig.JOINABLE_VALUE_TYPE_QUALIFIED_ID; 21 import static android.app.appsearch.SearchSessionUtil.safeExecute; 22 23 import android.annotation.CallbackExecutor; 24 import android.annotation.NonNull; 25 import android.annotation.Nullable; 26 import android.app.appsearch.aidl.AppSearchAttributionSource; 27 import android.app.appsearch.aidl.AppSearchResultParcel; 28 import android.app.appsearch.aidl.GetNextPageAidlRequest; 29 import android.app.appsearch.aidl.GlobalSearchAidlRequest; 30 import android.app.appsearch.aidl.IAppSearchManager; 31 import android.app.appsearch.aidl.IAppSearchResultCallback; 32 import android.app.appsearch.aidl.InvalidateNextPageTokenAidlRequest; 33 import android.app.appsearch.aidl.SearchAidlRequest; 34 import android.app.appsearch.util.ExceptionUtil; 35 import android.os.RemoteException; 36 import android.os.SystemClock; 37 import android.os.UserHandle; 38 import android.util.Log; 39 40 import com.android.internal.util.Preconditions; 41 42 import java.io.Closeable; 43 import java.util.List; 44 import java.util.Objects; 45 import java.util.concurrent.Executor; 46 import java.util.function.Consumer; 47 48 /** 49 * Encapsulates results of a search operation. 50 * 51 * <p>Each {@link AppSearchSession#search} operation returns a list of {@link SearchResult} objects, 52 * referred to as a "page", limited by the size configured by {@link 53 * SearchSpec.Builder#setResultCountPerPage}. 54 * 55 * <p>To fetch a page of results, call {@link #getNextPage}. 56 * 57 * <p>All instances of {@link SearchResults} must call {@link SearchResults#close()} after the 58 * results are fetched. 59 * 60 * <p>This class is not thread safe. 61 */ 62 public class SearchResults implements Closeable { 63 private static final String TAG = "SearchResults"; 64 65 private final IAppSearchManager mService; 66 67 // The permission identity of the caller 68 private final AppSearchAttributionSource mAttributionSource; 69 70 // The database name to search over. If null, this will search over all database names. 71 @Nullable private final String mDatabaseName; 72 73 private final String mQueryExpression; 74 75 private final SearchSpec mSearchSpec; 76 77 private final UserHandle mUserHandle; 78 79 private final boolean mIsForEnterprise; 80 81 private long mNextPageToken; 82 83 private boolean mIsFirstLoad = true; 84 85 private boolean mIsClosed = false; 86 SearchResults( @onNull IAppSearchManager service, @NonNull AppSearchAttributionSource attributionSource, @Nullable String databaseName, @NonNull String queryExpression, @NonNull SearchSpec searchSpec, @NonNull UserHandle userHandle, boolean isForEnterprise)87 SearchResults( 88 @NonNull IAppSearchManager service, 89 @NonNull AppSearchAttributionSource attributionSource, 90 @Nullable String databaseName, 91 @NonNull String queryExpression, 92 @NonNull SearchSpec searchSpec, 93 @NonNull UserHandle userHandle, 94 boolean isForEnterprise) { 95 mService = Objects.requireNonNull(service); 96 mAttributionSource = Objects.requireNonNull(attributionSource); 97 mDatabaseName = databaseName; 98 mQueryExpression = Objects.requireNonNull(queryExpression); 99 mSearchSpec = Objects.requireNonNull(searchSpec); 100 mUserHandle = Objects.requireNonNull(userHandle); 101 mIsForEnterprise = isForEnterprise; 102 } 103 104 /** 105 * Retrieves the next page of {@link SearchResult} objects. 106 * 107 * <p>The page size is configured by {@link SearchSpec.Builder#setResultCountPerPage}. 108 * 109 * <p>Continue calling this method to access results until it returns an empty list, signifying 110 * there are no more results. 111 * 112 * @param executor Executor on which to invoke the callback. 113 * @param callback Callback to receive the pending result of performing this operation. 114 */ getNextPage( @onNull @allbackExecutor Executor executor, @NonNull Consumer<AppSearchResult<List<SearchResult>>> callback)115 public void getNextPage( 116 @NonNull @CallbackExecutor Executor executor, 117 @NonNull Consumer<AppSearchResult<List<SearchResult>>> callback) { 118 Objects.requireNonNull(executor); 119 Objects.requireNonNull(callback); 120 Preconditions.checkState(!mIsClosed, "SearchResults has already been closed"); 121 try { 122 long binderCallStartTimeMillis = SystemClock.elapsedRealtime(); 123 if (mIsFirstLoad) { 124 mIsFirstLoad = false; 125 if (mDatabaseName == null) { 126 // Global search, there's no one package-database combination to check. 127 mService.globalSearch( 128 new GlobalSearchAidlRequest( 129 mAttributionSource, 130 mQueryExpression, 131 mSearchSpec, 132 mUserHandle, 133 binderCallStartTimeMillis, 134 mIsForEnterprise), 135 wrapCallback(executor, callback)); 136 } else { 137 // Normal local search, pass in specified database. 138 mService.search( 139 new SearchAidlRequest( 140 mAttributionSource, 141 mDatabaseName, 142 mQueryExpression, 143 mSearchSpec, 144 mUserHandle, 145 binderCallStartTimeMillis), 146 wrapCallback(executor, callback)); 147 } 148 } else { 149 // TODO(b/276349029): Log different join types when they get added. 150 @AppSearchSchema.StringPropertyConfig.JoinableValueType 151 int joinType = JOINABLE_VALUE_TYPE_NONE; 152 JoinSpec joinSpec = mSearchSpec.getJoinSpec(); 153 if (joinSpec != null && !joinSpec.getChildPropertyExpression().isEmpty()) { 154 joinType = JOINABLE_VALUE_TYPE_QUALIFIED_ID; 155 } 156 mService.getNextPage( 157 new GetNextPageAidlRequest( 158 mAttributionSource, 159 mDatabaseName, 160 mNextPageToken, 161 joinType, 162 mUserHandle, 163 binderCallStartTimeMillis, 164 mIsForEnterprise), 165 wrapCallback(executor, callback)); 166 } 167 } catch (RemoteException e) { 168 ExceptionUtil.handleRemoteException(e); 169 } 170 } 171 172 @Override close()173 public void close() { 174 if (!mIsClosed) { 175 try { 176 mService.invalidateNextPageToken( 177 new InvalidateNextPageTokenAidlRequest( 178 mAttributionSource, 179 mNextPageToken, 180 mUserHandle, 181 /* binderCallStartTimeMillis= */ SystemClock.elapsedRealtime(), 182 mIsForEnterprise)); 183 mIsClosed = true; 184 } catch (RemoteException e) { 185 Log.e(TAG, "Unable to close the SearchResults", e); 186 } 187 } 188 } 189 wrapCallback( @onNull @allbackExecutor Executor executor, @NonNull Consumer<AppSearchResult<List<SearchResult>>> callback)190 private IAppSearchResultCallback wrapCallback( 191 @NonNull @CallbackExecutor Executor executor, 192 @NonNull Consumer<AppSearchResult<List<SearchResult>>> callback) { 193 return new IAppSearchResultCallback.Stub() { 194 @Override 195 public void onResult(AppSearchResultParcel resultParcel) { 196 safeExecute( 197 executor, 198 callback, 199 () -> invokeCallback(resultParcel.getResult(), callback)); 200 } 201 }; 202 } 203 204 private void invokeCallback( 205 @NonNull AppSearchResult<SearchResultPage> searchResultPageResult, 206 @NonNull Consumer<AppSearchResult<List<SearchResult>>> callback) { 207 if (searchResultPageResult.isSuccess()) { 208 try { 209 SearchResultPage searchResultPage = 210 Objects.requireNonNull(searchResultPageResult.getResultValue()); 211 mNextPageToken = searchResultPage.getNextPageToken(); 212 callback.accept(AppSearchResult.newSuccessfulResult(searchResultPage.getResults())); 213 } catch (RuntimeException e) { 214 callback.accept(AppSearchResult.throwableToFailedResult(e)); 215 } 216 } else { 217 callback.accept(AppSearchResult.newFailedResult(searchResultPageResult)); 218 } 219 } 220 } 221