1 /*
2  * Copyright (C) 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 package com.android.server.appsearch;
17 
18 import static android.app.appsearch.AppSearchResult.RESULT_DENIED;
19 import static android.app.appsearch.AppSearchResult.RESULT_INTERNAL_ERROR;
20 import static android.app.appsearch.AppSearchResult.RESULT_INVALID_ARGUMENT;
21 import static android.app.appsearch.AppSearchResult.RESULT_NOT_FOUND;
22 import static android.app.appsearch.AppSearchResult.RESULT_OK;
23 import static android.app.appsearch.AppSearchResult.RESULT_RATE_LIMITED;
24 import static android.app.appsearch.AppSearchResult.RESULT_SECURITY_ERROR;
25 import static android.app.appsearch.AppSearchResult.RESULT_TIMED_OUT;
26 import static android.app.appsearch.AppSearchResult.throwableToFailedResult;
27 import static android.app.appsearch.functions.AppFunctionManager.PERMISSION_BIND_APP_FUNCTION_SERVICE;
28 import static android.os.Process.INVALID_UID;
29 
30 import static com.android.server.appsearch.external.localstorage.stats.SearchStats.VISIBILITY_SCOPE_GLOBAL;
31 import static com.android.server.appsearch.external.localstorage.stats.SearchStats.VISIBILITY_SCOPE_LOCAL;
32 import static com.android.server.appsearch.util.ServiceImplHelper.invokeCallbackOnError;
33 import static com.android.server.appsearch.util.ServiceImplHelper.invokeCallbackOnResult;
34 
35 import android.annotation.BinderThread;
36 import android.annotation.NonNull;
37 import android.annotation.Nullable;
38 import android.annotation.UserIdInt;
39 import android.annotation.WorkerThread;
40 import android.app.appsearch.AppSearchBatchResult;
41 import android.app.appsearch.AppSearchEnvironment;
42 import android.app.appsearch.AppSearchEnvironmentFactory;
43 import android.app.appsearch.AppSearchMigrationHelper;
44 import android.app.appsearch.AppSearchResult;
45 import android.app.appsearch.GenericDocument;
46 import android.app.appsearch.GetSchemaResponse;
47 import android.app.appsearch.InternalSetSchemaResponse;
48 import android.app.appsearch.SearchResultPage;
49 import android.app.appsearch.SearchSpec;
50 import android.app.appsearch.SearchSuggestionResult;
51 import android.app.appsearch.SetSchemaResponse;
52 import android.app.appsearch.SetSchemaResponse.MigrationFailure;
53 import android.app.appsearch.StorageInfo;
54 import android.app.appsearch.aidl.AppSearchBatchResultParcel;
55 import android.app.appsearch.aidl.AppSearchResultParcel;
56 import android.app.appsearch.aidl.ExecuteAppFunctionAidlRequest;
57 import android.app.appsearch.aidl.GetDocumentsAidlRequest;
58 import android.app.appsearch.aidl.GetNamespacesAidlRequest;
59 import android.app.appsearch.aidl.GetNextPageAidlRequest;
60 import android.app.appsearch.aidl.GetSchemaAidlRequest;
61 import android.app.appsearch.aidl.GetStorageInfoAidlRequest;
62 import android.app.appsearch.aidl.GlobalSearchAidlRequest;
63 import android.app.appsearch.aidl.IAppFunctionService;
64 import android.app.appsearch.aidl.IAppSearchBatchResultCallback;
65 import android.app.appsearch.aidl.IAppSearchManager;
66 import android.app.appsearch.aidl.IAppSearchObserverProxy;
67 import android.app.appsearch.aidl.IAppSearchResultCallback;
68 import android.app.appsearch.aidl.InitializeAidlRequest;
69 import android.app.appsearch.aidl.InvalidateNextPageTokenAidlRequest;
70 import android.app.appsearch.aidl.PersistToDiskAidlRequest;
71 import android.app.appsearch.aidl.PutDocumentsAidlRequest;
72 import android.app.appsearch.aidl.PutDocumentsFromFileAidlRequest;
73 import android.app.appsearch.aidl.RegisterObserverCallbackAidlRequest;
74 import android.app.appsearch.aidl.RemoveByDocumentIdAidlRequest;
75 import android.app.appsearch.aidl.RemoveByQueryAidlRequest;
76 import android.app.appsearch.aidl.ReportUsageAidlRequest;
77 import android.app.appsearch.aidl.SearchAidlRequest;
78 import android.app.appsearch.aidl.SearchSuggestionAidlRequest;
79 import android.app.appsearch.aidl.SetSchemaAidlRequest;
80 import android.app.appsearch.aidl.UnregisterObserverCallbackAidlRequest;
81 import android.app.appsearch.aidl.WriteSearchResultsToFileAidlRequest;
82 import android.app.appsearch.exceptions.AppSearchException;
83 import android.app.appsearch.functions.AppFunctionService;
84 import android.app.appsearch.functions.ExecuteAppFunctionRequest;
85 import android.app.appsearch.functions.SafeOneTimeAppSearchResultCallback;
86 import android.app.appsearch.functions.ServiceCallHelper;
87 import android.app.appsearch.functions.ServiceCallHelper.ServiceUsageCompleteListener;
88 import android.app.appsearch.functions.ServiceCallHelperImpl;
89 import android.app.appsearch.safeparcel.GenericDocumentParcel;
90 import android.app.appsearch.stats.SchemaMigrationStats;
91 import android.app.appsearch.util.ExceptionUtil;
92 import android.app.appsearch.util.LogUtil;
93 import android.app.role.RoleManager;
94 import android.content.BroadcastReceiver;
95 import android.content.ComponentName;
96 import android.content.Context;
97 import android.content.Intent;
98 import android.content.IntentFilter;
99 import android.content.pm.PackageInfo;
100 import android.content.pm.PackageManager;
101 import android.content.pm.PackageStats;
102 import android.content.pm.ResolveInfo;
103 import android.content.pm.ServiceInfo;
104 import android.net.Uri;
105 import android.os.Binder;
106 import android.os.RemoteException;
107 import android.os.SystemClock;
108 import android.os.UserHandle;
109 import android.text.TextUtils;
110 import android.util.ArraySet;
111 import android.util.Log;
112 
113 import com.android.internal.annotations.VisibleForTesting;
114 import com.android.server.LocalManagerRegistry;
115 import com.android.server.SystemService;
116 import com.android.server.appsearch.external.localstorage.stats.CallStats;
117 import com.android.server.appsearch.external.localstorage.stats.OptimizeStats;
118 import com.android.server.appsearch.external.localstorage.stats.SearchStats;
119 import com.android.server.appsearch.external.localstorage.stats.SetSchemaStats;
120 import com.android.server.appsearch.external.localstorage.usagereporting.SearchSessionStatsExtractor;
121 import com.android.server.appsearch.external.localstorage.visibilitystore.VisibilityStore;
122 import com.android.server.appsearch.observer.AppSearchObserverProxy;
123 import com.android.server.appsearch.stats.StatsCollector;
124 import com.android.server.appsearch.transformer.EnterpriseSearchResultPageTransformer;
125 import com.android.server.appsearch.transformer.EnterpriseSearchSpecTransformer;
126 import com.android.server.appsearch.util.AdbDumpUtil;
127 import com.android.server.appsearch.util.ApiCallRecord;
128 import com.android.server.appsearch.util.ExecutorManager;
129 import com.android.server.appsearch.util.PackageManagerUtil;
130 import com.android.server.appsearch.util.ServiceImplHelper;
131 import com.android.server.appsearch.visibilitystore.FrameworkCallerAccess;
132 import com.android.server.usage.StorageStatsManagerLocal;
133 import com.android.server.usage.StorageStatsManagerLocal.StorageStatsAugmenter;
134 
135 import com.google.android.icing.proto.DebugInfoProto;
136 import com.google.android.icing.proto.DebugInfoVerbosity;
137 import com.google.android.icing.proto.PersistType;
138 
139 import java.io.DataInputStream;
140 import java.io.DataOutputStream;
141 import java.io.EOFException;
142 import java.io.FileDescriptor;
143 import java.io.FileInputStream;
144 import java.io.FileOutputStream;
145 import java.io.IOException;
146 import java.io.PrintWriter;
147 import java.util.ArrayList;
148 import java.util.List;
149 import java.util.Map;
150 import java.util.Objects;
151 import java.util.Set;
152 import java.util.concurrent.Executor;
153 
154 /**
155  * The main service implementation which contains AppSearch's platform functionality.
156  *
157  * @hide
158  */
159 public class AppSearchManagerService extends SystemService {
160     private static final String TAG = "AppSearchManagerService";
161     @VisibleForTesting
162     static final String SYSTEM_UI_INTELLIGENCE = "android.app.role.SYSTEM_UI_INTELLIGENCE";
163 
164     /**
165      * An executor for system activity not tied to any particular user.
166      *
167      * <p>NOTE: Never call shutdownNow(). AppSearchManagerService persists forever even as
168      * individual users are added and removed -- without this pool the service will be broken. And,
169      * clients waiting for callbacks will never receive anything and will hang.
170      */
171     private static final Executor SHARED_EXECUTOR = ExecutorManager.createDefaultExecutorService();
172 
173     private final Context mContext;
174     private final ExecutorManager mExecutorManager;
175     private final AppSearchEnvironment mAppSearchEnvironment;
176     private final ServiceAppSearchConfig mAppSearchConfig;
177 
178     private PackageManager mPackageManager;
179     private RoleManager mRoleManager;
180     private ServiceImplHelper mServiceImplHelper;
181     private AppSearchUserInstanceManager mAppSearchUserInstanceManager;
182 
183     // Keep a reference for the lifecycle instance, so we can access other services like
184     // ContactsIndexer for dumpsys purpose.
185     private final AppSearchModule.Lifecycle mLifecycle;
186     private final ServiceCallHelper<IAppFunctionService> mAppFunctionServiceCallHelper;
187     private final SearchSessionStatsExtractor mSearchSessionStatsExtractor;
188 
AppSearchManagerService(Context context, AppSearchModule.Lifecycle lifecycle)189     public AppSearchManagerService(Context context, AppSearchModule.Lifecycle lifecycle) {
190         this(context, lifecycle, new ServiceCallHelperImpl<>(
191                 context, IAppFunctionService.Stub::asInterface, SHARED_EXECUTOR));
192     }
193 
194     @VisibleForTesting
AppSearchManagerService( Context context, AppSearchModule.Lifecycle lifecycle, ServiceCallHelper<IAppFunctionService> appFunctionServiceCallHelper)195     public AppSearchManagerService(
196             Context context,
197             AppSearchModule.Lifecycle lifecycle,
198             ServiceCallHelper<IAppFunctionService> appFunctionServiceCallHelper) {
199         super(context);
200         mContext = Objects.requireNonNull(context);
201         mLifecycle = Objects.requireNonNull(lifecycle);
202         mAppSearchEnvironment = AppSearchEnvironmentFactory.getEnvironmentInstance();
203         mAppSearchConfig = AppSearchComponentFactory.getConfigInstance(SHARED_EXECUTOR);
204         mExecutorManager = new ExecutorManager(mAppSearchConfig);
205         mAppFunctionServiceCallHelper = Objects.requireNonNull(appFunctionServiceCallHelper);
206         mSearchSessionStatsExtractor = new SearchSessionStatsExtractor();
207     }
208 
209     @Override
onStart()210     public void onStart() {
211         publishBinderService(Context.APP_SEARCH_SERVICE, new Stub());
212         mPackageManager = getContext().getPackageManager();
213         mRoleManager = getContext().getSystemService(RoleManager.class);
214         mServiceImplHelper = new ServiceImplHelper(mContext, mExecutorManager);
215         mAppSearchUserInstanceManager = AppSearchUserInstanceManager.getInstance();
216         registerReceivers();
217         LocalManagerRegistry.getManager(StorageStatsManagerLocal.class)
218                 .registerStorageStatsAugmenter(new AppSearchStorageStatsAugmenter(), TAG);
219         LocalManagerRegistry.addManager(LocalService.class, new LocalService());
220     }
221 
222     @Override
onBootPhase( int phase)223     public void onBootPhase(/* @BootPhase */ int phase) {
224         if (phase == PHASE_BOOT_COMPLETED) {
225             StatsCollector.getInstance(mContext, SHARED_EXECUTOR);
226         }
227     }
228 
registerReceivers()229     private void registerReceivers() {
230         mContext.registerReceiverForAllUsers(
231                 new UserActionReceiver(),
232                 new IntentFilter(Intent.ACTION_USER_REMOVED),
233                 /* broadcastPermission= */ null,
234                 /* scheduler= */ null);
235 
236         //TODO(b/145759910) Add a direct callback when user clears the data instead of relying on
237         // broadcasts
238         IntentFilter packageChangedFilter = new IntentFilter();
239         packageChangedFilter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED);
240         packageChangedFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED);
241         packageChangedFilter.addDataScheme("package");
242         packageChangedFilter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
243         mContext.registerReceiverForAllUsers(
244                 new PackageChangedReceiver(),
245                 packageChangedFilter,
246                 /* broadcastPermission= */ null,
247                 /* scheduler= */ null);
248     }
249 
250     private class UserActionReceiver extends BroadcastReceiver {
251         @Override
onReceive(@onNull Context context, @NonNull Intent intent)252         public void onReceive(@NonNull Context context, @NonNull Intent intent) {
253             Objects.requireNonNull(context);
254             Objects.requireNonNull(intent);
255             if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) {
256                 UserHandle userHandle = intent.getParcelableExtra(Intent.EXTRA_USER);
257                 if (userHandle == null) {
258                     Log.e(TAG,
259                             "Extra " + Intent.EXTRA_USER + " is missing in the intent: " + intent);
260                     return;
261                 }
262                 // We can handle user removal the same way as user stopping: shut down the executor
263                 // and close icing. The data of AppSearch is saved in the "credential encrypted"
264                 // system directory of each user. That directory will be auto-deleted when a user is
265                 // removed, so we don't need it handle it specially.
266                 onUserStopping(userHandle);
267             } else {
268                 Log.e(TAG, "Received unknown intent: " + intent);
269             }
270         }
271     }
272 
273     private class PackageChangedReceiver extends BroadcastReceiver {
274         @Override
onReceive(@onNull Context context, @NonNull Intent intent)275         public void onReceive(@NonNull Context context, @NonNull Intent intent) {
276             Objects.requireNonNull(context);
277             Objects.requireNonNull(intent);
278 
279             String action = intent.getAction();
280             if (action == null) {
281                 return;
282             }
283 
284             switch (action) {
285                 case Intent.ACTION_PACKAGE_FULLY_REMOVED:
286                 case Intent.ACTION_PACKAGE_DATA_CLEARED:
287                     Uri data = intent.getData();
288                     if (data == null) {
289                         Log.e(TAG, "Data is missing in the intent: " + intent);
290                         return;
291                     }
292 
293                     String packageName = data.getSchemeSpecificPart();
294                     if (packageName == null) {
295                         Log.e(TAG, "Package name is missing in the intent: " + intent);
296                         return;
297                     }
298 
299                     if (LogUtil.DEBUG) {
300                         Log.d(TAG, "Received " + action + " broadcast on package: " + packageName);
301                     }
302 
303                     int uid = intent.getIntExtra(Intent.EXTRA_UID, INVALID_UID);
304                     if (uid == INVALID_UID) {
305                         Log.e(TAG, "uid is missing in the intent: " + intent);
306                         return;
307                     }
308 
309                     handlePackageRemoved(packageName, uid);
310                     break;
311                 default:
312                     Log.e(TAG, "Received unknown intent: " + intent);
313             }
314         }
315     }
316 
handlePackageRemoved(@onNull String packageName, int uid)317     private void handlePackageRemoved(@NonNull String packageName, int uid) {
318         UserHandle userHandle = UserHandle.getUserHandleForUid(uid);
319         if (mServiceImplHelper.isUserLocked(userHandle)) {
320             // We cannot access a locked user's directory and remove package data from it.
321             // We should remove those uninstalled package data when the user is unlocking.
322             return;
323         }
324         // Only clear the package's data if AppSearch exists for this user.
325         if (mAppSearchEnvironment.getAppSearchDir(mContext, userHandle).exists()) {
326             mExecutorManager.getOrCreateUserExecutor(userHandle).execute(() -> {
327                 try {
328                     Context userContext = mAppSearchEnvironment
329                             .createContextAsUser(mContext, userHandle);
330                     AppSearchUserInstance instance =
331                             mAppSearchUserInstanceManager.getOrCreateUserInstance(
332                                     userContext,
333                                     userHandle,
334                                     mAppSearchConfig);
335                     instance.getAppSearchImpl().clearPackageData(packageName);
336                     dispatchChangeNotifications(instance);
337                     instance.getLogger().removeCacheForPackage(packageName);
338                 } catch (AppSearchException | RuntimeException e) {
339                     Log.e(TAG, "Unable to remove data for package: " + packageName, e);
340                     ExceptionUtil.handleException(e);
341                 }
342             });
343         }
344     }
345 
346     @Override
onUserUnlocking(@onNull TargetUser user)347     public void onUserUnlocking(@NonNull TargetUser user) {
348         Objects.requireNonNull(user);
349         UserHandle userHandle = user.getUserHandle();
350         mServiceImplHelper.setUserIsLocked(userHandle, false);
351 
352         // Only schedule task if AppSearch exists for this user.
353         if (mAppSearchEnvironment.getAppSearchDir(mContext, userHandle).exists()) {
354             mExecutorManager.getOrCreateUserExecutor(userHandle).execute(() -> {
355                 // Try to prune garbage package data, this is to recover if user remove a package
356                 // and reboot the device before we prune the package data.
357                 try {
358                     Context userContext = mAppSearchEnvironment
359                             .createContextAsUser(mContext, userHandle);
360                     AppSearchUserInstance instance =
361                             mAppSearchUserInstanceManager.getOrCreateUserInstance(
362                                     userContext,
363                                     userHandle,
364                                     mAppSearchConfig);
365                     List<PackageInfo> installedPackageInfos = userContext
366                             .getPackageManager()
367                             .getInstalledPackages(/* flags= */ 0);
368                     Set<String> packagesToKeep = new ArraySet<>(installedPackageInfos.size());
369                     for (int i = 0; i < installedPackageInfos.size(); i++) {
370                         packagesToKeep.add(installedPackageInfos.get(i).packageName);
371                     }
372                     packagesToKeep.add(VisibilityStore.VISIBILITY_PACKAGE_NAME);
373                     instance.getAppSearchImpl().prunePackageData(packagesToKeep);
374                 } catch (AppSearchException | RuntimeException e) {
375                     Log.e(TAG, "Unable to prune packages for " + user, e);
376                     ExceptionUtil.handleException(e);
377                 }
378 
379                 // Try to schedule fully persist job.
380                 try {
381                     AppSearchMaintenanceService.scheduleFullyPersistJob(mContext,
382                             userHandle.getIdentifier(),
383                             mAppSearchConfig.getCachedFullyPersistJobIntervalMillis());
384                 } catch (RuntimeException e) {
385                     Log.e(TAG, "Unable to schedule fully persist job for " + user, e);
386                     ExceptionUtil.handleException(e);
387                 }
388             });
389         }
390     }
391 
392     @Override
onUserStopping(@onNull TargetUser user)393     public void onUserStopping(@NonNull TargetUser user) {
394         Objects.requireNonNull(user);
395         onUserStopping(user.getUserHandle());
396     }
397 
onUserStopping(@onNull UserHandle userHandle)398     private void onUserStopping(@NonNull UserHandle userHandle) {
399         Objects.requireNonNull(userHandle);
400         if (LogUtil.INFO) {
401             Log.i(TAG, "Shutting down AppSearch for user " + userHandle);
402         }
403         try {
404             mServiceImplHelper.setUserIsLocked(userHandle, true);
405             mExecutorManager.shutDownAndRemoveUserExecutor(userHandle);
406             mAppSearchUserInstanceManager.closeAndRemoveUserInstance(userHandle);
407             AppSearchMaintenanceService.cancelFullyPersistJobIfScheduled(
408                     mContext, userHandle.getIdentifier());
409             if (LogUtil.INFO) {
410                 Log.i(TAG, "Removed AppSearchImpl instance for: " + userHandle);
411             }
412         } catch (InterruptedException | RuntimeException e) {
413             Log.e(TAG, "Unable to remove data for: " + userHandle, e);
414             ExceptionUtil.handleException(e);
415         }
416     }
417 
418     class LocalService {
419         /** Persist all pending mutation operation to disk for the given user. */
doFullyPersistForUser(@serIdInt int userId)420         public void doFullyPersistForUser(@UserIdInt int userId) throws AppSearchException {
421             UserHandle targetUser = UserHandle.getUserHandleForUid(userId);
422             AppSearchUserInstance instance =
423                 mAppSearchUserInstanceManager.getUserInstance(targetUser);
424             instance.getAppSearchImpl().persistToDisk(PersistType.Code.FULL);
425         }
426     }
427 
428     private class Stub extends IAppSearchManager.Stub {
429         @Override
setSchema( @onNull SetSchemaAidlRequest request, @NonNull IAppSearchResultCallback callback)430         public void setSchema(
431                 @NonNull SetSchemaAidlRequest request,
432                 @NonNull IAppSearchResultCallback callback) {
433             Objects.requireNonNull(request);
434             Objects.requireNonNull(callback);
435 
436             long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
437             long verifyIncomingCallLatencyStartTimeMillis = SystemClock.elapsedRealtime();
438             UserHandle targetUser = mServiceImplHelper.verifyIncomingCallWithCallback(
439                     request.getCallerAttributionSource(), request.getUserHandle(), callback);
440             String callingPackageName = request.getCallerAttributionSource().getPackageName();
441             if (targetUser == null) {
442                 return;  // Verification failed; verifyIncomingCall triggered callback.
443             }
444             if (checkCallDenied(callingPackageName, request.getDatabaseName(),
445                     CallStats.CALL_TYPE_SET_SCHEMA, callback, targetUser,
446                     request.getBinderCallStartTimeMillis(), totalLatencyStartTimeMillis,
447                     /* numOperations= */ 1)) {
448                 return;
449             }
450             long verifyIncomingCallLatencyEndTimeMillis = SystemClock.elapsedRealtime();
451 
452             long waitExecutorStartTimeMillis = SystemClock.elapsedRealtime();
453             boolean callAccepted = mServiceImplHelper.executeLambdaForUserAsync(
454                     targetUser, callback, callingPackageName, CallStats.CALL_TYPE_SET_SCHEMA,
455                     () -> {
456                 long waitExecutorEndTimeMillis = SystemClock.elapsedRealtime();
457 
458                 @AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK;
459                 AppSearchUserInstance instance = null;
460                 SetSchemaStats.Builder setSchemaStatsBuilder = new SetSchemaStats.Builder(
461                         callingPackageName, request.getDatabaseName());
462                 int operationSuccessCount = 0;
463                 int operationFailureCount = 0;
464                 try {
465                     instance = mAppSearchUserInstanceManager.getUserInstance(targetUser);
466                     InternalSetSchemaResponse internalSetSchemaResponse =
467                             instance.getAppSearchImpl().setSchema(
468                                     callingPackageName,
469                                     request.getDatabaseName(),
470                                     request.getSchemas(),
471                                     request.getVisibilityConfigs(),
472                                     request.isForceOverride(),
473                                     request.getSchemaVersion(),
474                                     setSchemaStatsBuilder);
475                     ++operationSuccessCount;
476                     invokeCallbackOnResult(callback, AppSearchResultParcel
477                             .fromInternalSetSchemaResponse(internalSetSchemaResponse));
478 
479                     // Schedule a task to dispatch change notifications. See requirements for where
480                     // the method is called documented in the method description.
481                     long dispatchNotificationLatencyStartTimeMillis = SystemClock.elapsedRealtime();
482                     dispatchChangeNotifications(instance);
483                     long dispatchNotificationLatencyEndTimeMillis = SystemClock.elapsedRealtime();
484 
485                     // setSchema will sync the schemas in the request to AppSearch, any existing
486                     // schemas which are not included in the request will be deleted if we force
487                     // override incompatible schemas. And all documents of these types will be
488                     // deleted as well. We should checkForOptimize for these deletion.
489                     long checkForOptimizeLatencyStartTimeMillis = SystemClock.elapsedRealtime();
490                     checkForOptimize(targetUser, instance);
491                     long checkForOptimizeLatencyEndTimeMillis = SystemClock.elapsedRealtime();
492 
493                     setSchemaStatsBuilder
494                             .setVerifyIncomingCallLatencyMillis(
495                                     (int) (verifyIncomingCallLatencyEndTimeMillis
496                                             - verifyIncomingCallLatencyStartTimeMillis))
497                             .setExecutorAcquisitionLatencyMillis(
498                                     (int) (waitExecutorEndTimeMillis
499                                             - waitExecutorStartTimeMillis))
500                             // This operation no longer exists, so this latency is always 0
501                             .setRebuildFromBundleLatencyMillis(0)
502                             .setDispatchChangeNotificationsLatencyMillis(
503                                     (int) (dispatchNotificationLatencyEndTimeMillis
504                                             - dispatchNotificationLatencyStartTimeMillis))
505                             .setOptimizeLatencyMillis(
506                                     (int) (checkForOptimizeLatencyEndTimeMillis
507                                             - checkForOptimizeLatencyStartTimeMillis));
508                 } catch (AppSearchException | RuntimeException e) {
509                     ++operationFailureCount;
510                     AppSearchResult<Void> failedResult = throwableToFailedResult(e);
511                     statusCode = failedResult.getResultCode();
512                     invokeCallbackOnResult(callback, AppSearchResultParcel.fromFailedResult(
513                             failedResult));
514                 } finally {
515                     if (instance != null) {
516                         int estimatedBinderLatencyMillis =
517                                 2 * (int) (totalLatencyStartTimeMillis -
518                                         request.getBinderCallStartTimeMillis());
519                         int totalLatencyMillis =
520                                 (int) (SystemClock.elapsedRealtime() - totalLatencyStartTimeMillis);
521                         instance.getLogger().logStats(new CallStats.Builder()
522                                 .setPackageName(callingPackageName)
523                                 .setDatabase(request.getDatabaseName())
524                                 .setStatusCode(statusCode)
525                                 .setTotalLatencyMillis(totalLatencyMillis)
526                                 .setCallType(CallStats.CALL_TYPE_SET_SCHEMA)
527                                 // TODO(b/173532925) check the existing binder call latency chart
528                                 // is good enough for us:
529                                 // http://dashboards/view/_72c98f9a_91d9_41d4_ab9a_bc14f79742b4
530                                 .setEstimatedBinderLatencyMillis(estimatedBinderLatencyMillis)
531                                 .setNumOperationsSucceeded(operationSuccessCount)
532                                 .setNumOperationsFailed(operationFailureCount)
533                                 .build());
534                         instance.getLogger().logStats(setSchemaStatsBuilder
535                                 .setStatusCode(statusCode)
536                                 .setSchemaMigrationCallType(request.getSchemaMigrationCallType())
537                                 .setTotalLatencyMillis(totalLatencyMillis)
538                                 .build());
539                     }
540                 }
541             });
542             if (!callAccepted) {
543                 logRateLimitedOrCallDeniedCallStats(callingPackageName, request.getDatabaseName(),
544                         CallStats.CALL_TYPE_SET_SCHEMA, targetUser,
545                         request.getBinderCallStartTimeMillis(), totalLatencyStartTimeMillis,
546                         /*numOperations=*/ 1, RESULT_RATE_LIMITED);
547             }
548         }
549 
550         @Override
getSchema( @onNull GetSchemaAidlRequest request, @NonNull IAppSearchResultCallback callback)551         public void getSchema(
552                 @NonNull GetSchemaAidlRequest request,
553                 @NonNull IAppSearchResultCallback callback) {
554             Objects.requireNonNull(request);
555             Objects.requireNonNull(callback);
556 
557             long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
558             UserHandle targetUser = mServiceImplHelper.verifyIncomingCallWithCallback(
559                     request.getCallerAttributionSource(), request.getUserHandle(), callback);
560             String callingPackageName = request.getCallerAttributionSource().getPackageName();
561             if (targetUser == null) {
562                 return;  // Verification failed; verifyIncomingCall triggered callback.
563             }
564             // Get the enterprise user for enterprise calls
565             UserHandle userToQuery = mServiceImplHelper.getUserToQuery(request.isForEnterprise(),
566                     targetUser);
567             if (userToQuery == null) {
568                 // Return an empty response if we tried to and couldn't get the enterprise user
569                 invokeCallbackOnResult(callback, AppSearchResultParcel.fromGetSchemaResponse(
570                         new GetSchemaResponse.Builder().build()));
571                 return;
572             }
573             boolean global = isGlobalCall(callingPackageName, request.getTargetPackageName(),
574                     request.isForEnterprise());
575             // We deny based on the calling package and calling database names. If the calling
576             // package does not match the target package, then the call is global and the target
577             // database is not a calling database.
578             String callingDatabaseName = global ? null : request.getDatabaseName();
579             int callType = global ? CallStats.CALL_TYPE_GLOBAL_GET_SCHEMA
580                     : CallStats.CALL_TYPE_GET_SCHEMA;
581             if (checkCallDenied(callingPackageName, callingDatabaseName, callType, callback,
582                     targetUser, request.getBinderCallStartTimeMillis(), totalLatencyStartTimeMillis,
583                     /* numOperations= */ 1)) {
584                 return;
585             }
586             boolean callAccepted = mServiceImplHelper.executeLambdaForUserAsync(targetUser,
587                     callback, callingPackageName, callType, () -> {
588                 @AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK;
589                 AppSearchUserInstance instance = null;
590                 int operationSuccessCount = 0;
591                 int operationFailureCount = 0;
592                 try {
593                     instance = mAppSearchUserInstanceManager.getUserInstance(userToQuery);
594 
595                     boolean callerHasSystemAccess = instance.getVisibilityChecker()
596                             .doesCallerHaveSystemAccess(callingPackageName);
597                     GetSchemaResponse response =
598                             instance.getAppSearchImpl().getSchema(
599                                     request.getTargetPackageName(),
600                                     request.getDatabaseName(),
601                                     new FrameworkCallerAccess(request.getCallerAttributionSource(),
602                                             callerHasSystemAccess, request.isForEnterprise()));
603                     ++operationSuccessCount;
604                     invokeCallbackOnResult(callback, AppSearchResultParcel
605                             .fromGetSchemaResponse(response)
606                     );
607                 } catch (AppSearchException | RuntimeException e) {
608                     ++operationFailureCount;
609                     AppSearchResult<Void> failedResult = throwableToFailedResult(e);
610                     statusCode = failedResult.getResultCode();
611                     invokeCallbackOnResult(callback, AppSearchResultParcel.fromFailedResult(
612                             failedResult));
613                 } finally {
614                     if (instance != null) {
615                         int estimatedBinderLatencyMillis =
616                                 2 * (int) (totalLatencyStartTimeMillis
617                                         - request.getBinderCallStartTimeMillis());
618                         int totalLatencyMillis =
619                                 (int) (SystemClock.elapsedRealtime() - totalLatencyStartTimeMillis);
620                         instance.getLogger().logStats(new CallStats.Builder()
621                                 .setPackageName(callingPackageName)
622                                 .setDatabase(request.getDatabaseName())
623                                 .setStatusCode(statusCode)
624                                 .setTotalLatencyMillis(totalLatencyMillis)
625                                 .setCallType(callType)
626                                 // TODO(b/173532925) check the existing binder call latency chart
627                                 // is good enough for us:
628                                 // http://dashboards/view/_72c98f9a_91d9_41d4_ab9a_bc14f79742b4
629                                 .setEstimatedBinderLatencyMillis(estimatedBinderLatencyMillis)
630                                 .setNumOperationsSucceeded(operationSuccessCount)
631                                 .setNumOperationsFailed(operationFailureCount)
632                                 .build());
633                     }
634                 }
635             });
636             if (!callAccepted) {
637                 logRateLimitedOrCallDeniedCallStats(callingPackageName, callingDatabaseName,
638                         callType, targetUser, request.getBinderCallStartTimeMillis(),
639                         totalLatencyStartTimeMillis, /*numOperations=*/ 1, RESULT_RATE_LIMITED);
640             }
641         }
642 
643         @Override
getNamespaces( @onNull GetNamespacesAidlRequest request, @NonNull IAppSearchResultCallback callback)644         public void getNamespaces(
645                 @NonNull GetNamespacesAidlRequest request,
646                 @NonNull IAppSearchResultCallback callback) {
647             Objects.requireNonNull(request);
648             Objects.requireNonNull(callback);
649 
650             long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
651             UserHandle targetUser = mServiceImplHelper.verifyIncomingCallWithCallback(
652                     request.getCallerAttributionSource(), request.getUserHandle(), callback);
653             String callingPackageName = request.getCallerAttributionSource().getPackageName();
654             if (targetUser == null) {
655                 return;  // Verification failed; verifyIncomingCall triggered callback.
656             }
657             if (checkCallDenied(callingPackageName, request.getDatabaseName(),
658                     CallStats.CALL_TYPE_GET_NAMESPACES, callback, targetUser,
659                     request.getBinderCallStartTimeMillis(), totalLatencyStartTimeMillis,
660                     /* numOperations= */ 1)) {
661                 return;
662             }
663             boolean callAccepted = mServiceImplHelper.executeLambdaForUserAsync(targetUser,
664                     callback, callingPackageName, CallStats.CALL_TYPE_GET_NAMESPACES, () -> {
665                 @AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK;
666                 AppSearchUserInstance instance = null;
667                 int operationSuccessCount = 0;
668                 int operationFailureCount = 0;
669                 try {
670                     instance = mAppSearchUserInstanceManager.getUserInstance(targetUser);
671                     List<String> namespaces =
672                             instance.getAppSearchImpl().getNamespaces(
673                                     callingPackageName, request.getDatabaseName());
674                     ++operationSuccessCount;
675                     invokeCallbackOnResult(callback, AppSearchResultParcel
676                             .fromStringList(namespaces)
677                     );
678                 } catch (AppSearchException | RuntimeException e) {
679                     ++operationFailureCount;
680                     AppSearchResult<Void> failedResult = throwableToFailedResult(e);
681                     statusCode = failedResult.getResultCode();
682                     invokeCallbackOnResult(callback, AppSearchResultParcel.fromFailedResult(
683                             failedResult));
684                 } finally {
685                     if (instance != null) {
686                         int estimatedBinderLatencyMillis =
687                                 2 * (int) (totalLatencyStartTimeMillis
688                                         - request.getBinderCallStartTimeMillis());
689                         int totalLatencyMillis =
690                                 (int) (SystemClock.elapsedRealtime() - totalLatencyStartTimeMillis);
691                         instance.getLogger().logStats(new CallStats.Builder()
692                                 .setPackageName(callingPackageName)
693                                 .setDatabase(request.getDatabaseName())
694                                 .setStatusCode(statusCode)
695                                 .setTotalLatencyMillis(totalLatencyMillis)
696                                 .setCallType(CallStats.CALL_TYPE_GET_NAMESPACES)
697                                 // TODO(b/173532925) check the existing binder call latency chart
698                                 // is good enough for us:
699                                 // http://dashboards/view/_72c98f9a_91d9_41d4_ab9a_bc14f79742b4
700                                 .setEstimatedBinderLatencyMillis(estimatedBinderLatencyMillis)
701                                 .setNumOperationsSucceeded(operationSuccessCount)
702                                 .setNumOperationsFailed(operationFailureCount)
703                                 .build());
704                     }
705                 }
706             });
707             if (!callAccepted) {
708                 logRateLimitedOrCallDeniedCallStats(callingPackageName, request.getDatabaseName(),
709                         CallStats.CALL_TYPE_GET_NAMESPACES, targetUser,
710                         request.getBinderCallStartTimeMillis(), totalLatencyStartTimeMillis,
711                         /*numOperations=*/ 1, RESULT_RATE_LIMITED);
712             }
713         }
714 
715         @Override
putDocuments( @onNull PutDocumentsAidlRequest request, @NonNull IAppSearchBatchResultCallback callback)716         public void putDocuments(
717                 @NonNull PutDocumentsAidlRequest request,
718                 @NonNull IAppSearchBatchResultCallback callback) {
719             Objects.requireNonNull(request);
720             Objects.requireNonNull(callback);
721 
722             long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
723             UserHandle targetUser = mServiceImplHelper.verifyIncomingCallWithCallback(
724                     request.getCallerAttributionSource(), request.getUserHandle(), callback);
725             String callingPackageName = request.getCallerAttributionSource().getPackageName();
726             if (targetUser == null) {
727                 return;  // Verification failed; verifyIncomingCall triggered callback.
728             }
729             if (checkCallDenied(callingPackageName, request.getDatabaseName(),
730                     CallStats.CALL_TYPE_PUT_DOCUMENTS, callback, targetUser,
731                     request.getBinderCallStartTimeMillis(), totalLatencyStartTimeMillis,
732                     /* numOperations= */ request.getDocumentsParcel().getTotalDocumentCount())) {
733                 return;
734             }
735             boolean callAccepted = mServiceImplHelper.executeLambdaForUserAsync(targetUser,
736                     callback, callingPackageName, CallStats.CALL_TYPE_PUT_DOCUMENTS, () -> {
737                 @AppSearchResult.ResultCode int statusCode = RESULT_OK;
738                 AppSearchUserInstance instance = null;
739                 int operationSuccessCount = 0;
740                 int operationFailureCount = 0;
741                 List<GenericDocument> takenActionGenericDocuments = null;  // initialize later
742 
743                 try {
744                     AppSearchBatchResult.Builder<String, Void> resultBuilder =
745                             new AppSearchBatchResult.Builder<>();
746                     instance = mAppSearchUserInstanceManager.getUserInstance(targetUser);
747                     List<GenericDocumentParcel> documentParcels =
748                             request.getDocumentsParcel().getDocumentParcels();
749                     List<GenericDocumentParcel> takenActionDocumentParcels =
750                             request.getDocumentsParcel().getTakenActionGenericDocumentParcels();
751 
752                     // Write GenericDocument of general documents
753                     for (int i = 0; i < documentParcels.size(); i++) {
754                         GenericDocument document = new GenericDocument(documentParcels.get(i));
755                         try {
756                             instance.getAppSearchImpl().putDocument(
757                                     callingPackageName,
758                                     request.getDatabaseName(),
759                                     document,
760                                     /* sendChangeNotifications= */ true,
761                                     instance.getLogger());
762                             resultBuilder.setSuccess(document.getId(), /* value= */ null);
763                             ++operationSuccessCount;
764                         } catch (AppSearchException | RuntimeException e) {
765                             // We don't rethrow here, so we can keep trying with the
766                             // following documents.
767                             AppSearchResult<Void> result = throwableToFailedResult(e);
768                             resultBuilder.setResult(document.getId(), result);
769                             // Since we can only include one status code in the atom,
770                             // for failures, we would just save the one for the last failure
771                             statusCode = result.getResultCode();
772                             ++operationFailureCount;
773                         }
774                     }
775 
776                     // Write GenericDocument of taken actions
777                     if (!takenActionDocumentParcels.isEmpty()) {
778                         takenActionGenericDocuments =
779                                 new ArrayList<>(takenActionDocumentParcels.size());
780                     }
781                     for (int i = 0; i < takenActionDocumentParcels.size(); i++) {
782                         GenericDocument document =
783                                 new GenericDocument(takenActionDocumentParcels.get(i));
784                         takenActionGenericDocuments.add(document);
785                         try {
786                             instance.getAppSearchImpl().putDocument(
787                                     callingPackageName,
788                                     request.getDatabaseName(),
789                                     document,
790                                     /* sendChangeNotifications= */ true,
791                                     instance.getLogger());
792                             resultBuilder.setSuccess(document.getId(), /* value= */ null);
793                             ++operationSuccessCount;
794                         } catch (AppSearchException | RuntimeException e) {
795                             // We don't rethrow here, so we can keep trying with the
796                             // following documents.
797                             AppSearchResult<Void> result = throwableToFailedResult(e);
798                             resultBuilder.setResult(document.getId(), result);
799                             // Since we can only include one status code in the atom,
800                             // for failures, we would just save the one for the last failure
801                             statusCode = result.getResultCode();
802                             ++operationFailureCount;
803                         }
804                     }
805 
806                     // Now that the batch has been written. Persist the newly written data.
807                     instance.getAppSearchImpl().persistToDisk(PersistType.Code.LITE);
808                     invokeCallbackOnResult(callback, AppSearchBatchResultParcel
809                             .fromStringToVoid(resultBuilder.build()));
810 
811                     // Schedule a task to dispatch change notifications. See requirements for where
812                     // the method is called documented in the method description.
813                     dispatchChangeNotifications(instance);
814 
815                     // The existing documents with same ID will be deleted, so there may be some
816                     // resources that could be released after optimize().
817                     checkForOptimize(
818                             targetUser,
819                             instance,
820                             /* mutateBatchSize= */
821                             request.getDocumentsParcel().getTotalDocumentCount());
822                 } catch (AppSearchException | RuntimeException e) {
823                     ++operationFailureCount;
824                     AppSearchResult<Void> failedResult = throwableToFailedResult(e);
825                     statusCode = failedResult.getResultCode();
826                     invokeCallbackOnError(callback, failedResult);
827                 } finally {
828                     // TODO(b/261959320) add outstanding latency fields in AppSearch stats
829                     if (instance != null) {
830                         int estimatedBinderLatencyMillis =
831                                 2 * (int) (totalLatencyStartTimeMillis
832                                         - request.getBinderCallStartTimeMillis());
833                         int totalLatencyMillis =
834                                 (int) (SystemClock.elapsedRealtime() - totalLatencyStartTimeMillis);
835                         instance.getLogger().logStats(new CallStats.Builder()
836                                 .setPackageName(callingPackageName)
837                                 .setDatabase(request.getDatabaseName())
838                                 .setStatusCode(statusCode)
839                                 .setTotalLatencyMillis(totalLatencyMillis)
840                                 .setCallType(CallStats.CALL_TYPE_PUT_DOCUMENTS)
841                                 // TODO(b/173532925) check the existing binder call latency chart
842                                 // is good enough for us:
843                                 // http://dashboards/view/_72c98f9a_91d9_41d4_ab9a_bc14f79742b4
844                                 .setEstimatedBinderLatencyMillis(estimatedBinderLatencyMillis)
845                                 .setNumOperationsSucceeded(operationSuccessCount)
846                                 .setNumOperationsFailed(operationFailureCount)
847                                 .build());
848 
849                         // Extract metrics from taken action generic documents and add log.
850                         if (takenActionGenericDocuments != null
851                                 && !takenActionGenericDocuments.isEmpty()) {
852                             instance.getLogger()
853                                     .logStats(mSearchSessionStatsExtractor.extract(
854                                             callingPackageName,
855                                             request.getDatabaseName(),
856                                             takenActionGenericDocuments));
857                         }
858                     }
859                 }
860             });
861             if (!callAccepted) {
862                 logRateLimitedOrCallDeniedCallStats(callingPackageName, request.getDatabaseName(),
863                         CallStats.CALL_TYPE_PUT_DOCUMENTS, targetUser,
864                         request.getBinderCallStartTimeMillis(),
865                         totalLatencyStartTimeMillis,
866                         /* numOperations= */
867                         request.getDocumentsParcel().getTotalDocumentCount(), RESULT_RATE_LIMITED);
868             }
869         }
870 
871         @Override
getDocuments( @onNull GetDocumentsAidlRequest request, @NonNull IAppSearchBatchResultCallback callback)872         public void getDocuments(
873                 @NonNull GetDocumentsAidlRequest request,
874                 @NonNull IAppSearchBatchResultCallback callback) {
875             Objects.requireNonNull(request);
876             Objects.requireNonNull(callback);
877 
878             long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
879             UserHandle targetUser = mServiceImplHelper.verifyIncomingCallWithCallback(
880                     request.getCallerAttributionSource(), request.getUserHandle(), callback);
881             String callingPackageName = request.getCallerAttributionSource().getPackageName();
882             if (targetUser == null) {
883                 return;  // Verification failed; verifyIncomingCall triggered callback.
884             }
885             // Get the enterprise user for enterprise calls
886             UserHandle userToQuery = mServiceImplHelper.getUserToQuery(
887                     request.isForEnterprise(), targetUser);
888             if (userToQuery == null) {
889                 // Return an empty batch result if we tried to and couldn't get the enterprise user
890                 invokeCallbackOnResult(callback, AppSearchBatchResultParcel
891                         .fromStringToGenericDocumentParcel(new AppSearchBatchResult
892                                 .Builder<String, GenericDocumentParcel>().build()));
893                 return;
894             }
895             // TODO(b/319315074): consider removing local getDocument and just use globalGetDocument
896             //  instead; this would simplify the code and assure us that enterprise calls definitely
897             //  go through visibility checks
898             boolean global = isGlobalCall(callingPackageName, request.getTargetPackageName(),
899                     request.isForEnterprise());
900             // We deny based on the calling package and calling database names. If the calling
901             // package does not match the target package, then the call is global and the target
902             // database is not a calling database.
903             String callingDatabaseName = global ? null : request.getDatabaseName();
904             int callType = global ? CallStats.CALL_TYPE_GLOBAL_GET_DOCUMENT_BY_ID
905                     : CallStats.CALL_TYPE_GET_DOCUMENTS;
906             if (checkCallDenied(callingPackageName, callingDatabaseName, callType, callback,
907                     targetUser, request.getBinderCallStartTimeMillis(), totalLatencyStartTimeMillis,
908                     /* numOperations= */ request.getGetByDocumentIdRequest().getIds().size())) {
909                 return;
910             }
911             boolean callAccepted = mServiceImplHelper.executeLambdaForUserAsync(targetUser,
912                     callback, callingPackageName, callType, () -> {
913                 @AppSearchResult.ResultCode int statusCode = RESULT_OK;
914                 AppSearchUserInstance instance = null;
915                 int operationSuccessCount = 0;
916                 int operationFailureCount = 0;
917                 try {
918                     AppSearchBatchResult.Builder<String, GenericDocumentParcel> resultBuilder =
919                             new AppSearchBatchResult.Builder<>();
920                     instance = mAppSearchUserInstanceManager.getUserInstance(userToQuery);
921                     for (String id : request.getGetByDocumentIdRequest().getIds()) {
922                         try {
923                             GenericDocument document;
924                             if (global) {
925                                 boolean callerHasSystemAccess = instance.getVisibilityChecker()
926                                         .doesCallerHaveSystemAccess(
927                                                 request.getCallerAttributionSource()
928                                                         .getPackageName());
929                                 Map<String, List<String>> typePropertyPaths =
930                                         request.getGetByDocumentIdRequest().getProjections();
931                                 if (request.isForEnterprise()) {
932                                     EnterpriseSearchSpecTransformer.transformPropertiesMap(
933                                             typePropertyPaths);
934                                 }
935                                 document = instance.getAppSearchImpl().globalGetDocument(
936                                         request.getTargetPackageName(),
937                                         request.getDatabaseName(),
938                                         request.getGetByDocumentIdRequest().getNamespace(),
939                                         id,
940                                         typePropertyPaths,
941                                         new FrameworkCallerAccess(
942                                                 request.getCallerAttributionSource(),
943                                                 callerHasSystemAccess,
944                                                 request.isForEnterprise()));
945                                 if (request.isForEnterprise()) {
946                                     document =
947                                             EnterpriseSearchResultPageTransformer.transformDocument(
948                                                     request.getTargetPackageName(),
949                                                     request.getDatabaseName(),
950                                                     document);
951                                 }
952                             } else {
953                                 document = instance.getAppSearchImpl().getDocument(
954                                         request.getTargetPackageName(),
955                                         request.getDatabaseName(),
956                                         request.getGetByDocumentIdRequest().getNamespace(),
957                                         id,
958                                         request.getGetByDocumentIdRequest().getProjections());
959                             }
960                             ++operationSuccessCount;
961                             resultBuilder.setSuccess(id, document.getDocumentParcel());
962                         } catch (AppSearchException | RuntimeException e) {
963                             // Since we can only include one status code in the atom,
964                             // for failures, we would just save the one for the last failure
965                             // Also, we don't rethrow here, so we can keep trying for
966                             // the following ones.
967                             AppSearchResult<GenericDocumentParcel> result =
968                                     throwableToFailedResult(e);
969                             resultBuilder.setResult(id, result);
970                             statusCode = result.getResultCode();
971                             ++operationFailureCount;
972                         }
973                     }
974                     invokeCallbackOnResult(callback, AppSearchBatchResultParcel
975                             .fromStringToGenericDocumentParcel(resultBuilder.build()));
976                 } catch (RuntimeException e) {
977                     ++operationFailureCount;
978                     AppSearchResult<Void> failedResult = throwableToFailedResult(e);
979                     statusCode = failedResult.getResultCode();
980                     invokeCallbackOnError(callback, failedResult);
981                 } finally {
982                     // TODO(b/261959320) add outstanding latency fields in AppSearch stats
983                     if (instance != null) {
984                         int estimatedBinderLatencyMillis =
985                                 2 * (int) (totalLatencyStartTimeMillis -
986                                         request.getBinderCallStartTimeMillis());
987                         int totalLatencyMillis =
988                                 (int) (SystemClock.elapsedRealtime() - totalLatencyStartTimeMillis);
989                         instance.getLogger().logStats(new CallStats.Builder()
990                                 .setPackageName(callingPackageName)
991                                 .setDatabase(request.getDatabaseName())
992                                 .setStatusCode(statusCode)
993                                 .setTotalLatencyMillis(totalLatencyMillis)
994                                 .setCallType(callType)
995                                 // TODO(b/173532925) check the existing binder call latency chart
996                                 // is good enough for us:
997                                 // http://dashboards/view/_72c98f9a_91d9_41d4_ab9a_bc14f79742b4
998                                 .setEstimatedBinderLatencyMillis(estimatedBinderLatencyMillis)
999                                 .setNumOperationsSucceeded(operationSuccessCount)
1000                                 .setNumOperationsFailed(operationFailureCount)
1001                                 .build());
1002                     }
1003                 }
1004             });
1005             if (!callAccepted) {
1006                 logRateLimitedOrCallDeniedCallStats(callingPackageName, callingDatabaseName,
1007                         callType, targetUser, request.getBinderCallStartTimeMillis(),
1008                         totalLatencyStartTimeMillis,
1009                         /* numOperations= */ request.getGetByDocumentIdRequest().getIds().size(),
1010                         RESULT_RATE_LIMITED);
1011 
1012             }
1013         }
1014 
1015         @Override
search( @onNull SearchAidlRequest request, @NonNull IAppSearchResultCallback callback)1016         public void search(
1017                 @NonNull SearchAidlRequest request,
1018                 @NonNull IAppSearchResultCallback callback) {
1019             Objects.requireNonNull(request);
1020             Objects.requireNonNull(callback);
1021 
1022             long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
1023             UserHandle targetUser = mServiceImplHelper.verifyIncomingCallWithCallback(
1024                     request.getCallerAttributionSource(), request.getUserHandle(), callback);
1025             String callingPackageName = request.getCallerAttributionSource().getPackageName();
1026             if (targetUser == null) {
1027                 return;  // Verification failed; verifyIncomingCall triggered callback.
1028             }
1029             if (checkCallDenied(callingPackageName, request.getDatabaseName(),
1030                     CallStats.CALL_TYPE_SEARCH, callback, targetUser,
1031                     request.getBinderCallStartTimeMillis(), totalLatencyStartTimeMillis,
1032                     /* numOperations= */ 1)) {
1033                 return;
1034             }
1035             boolean callAccepted = mServiceImplHelper.executeLambdaForUserAsync(targetUser,
1036                     callback, callingPackageName, CallStats.CALL_TYPE_SEARCH, () -> {
1037                 @AppSearchResult.ResultCode int statusCode = RESULT_OK;
1038                 AppSearchUserInstance instance = null;
1039                 int operationSuccessCount = 0;
1040                 int operationFailureCount = 0;
1041                 try {
1042                     instance = mAppSearchUserInstanceManager.getUserInstance(targetUser);
1043                     SearchResultPage searchResultPage = instance.getAppSearchImpl().query(
1044                             callingPackageName,
1045                             request.getDatabaseName(),
1046                             request.getSearchExpression(),
1047                             request.getSearchSpec(),
1048                             instance.getLogger());
1049                     ++operationSuccessCount;
1050                     invokeCallbackOnResult(
1051                             callback,
1052                             AppSearchResultParcel.fromSearchResultPage(searchResultPage));
1053                 } catch (AppSearchException | RuntimeException e) {
1054                     ++operationFailureCount;
1055                     AppSearchResult<Void> failedResult = throwableToFailedResult(e);
1056                     statusCode = failedResult.getResultCode();
1057                     invokeCallbackOnResult(callback, AppSearchResultParcel.fromFailedResult(
1058                             failedResult));
1059                 } finally {
1060                     if (instance != null) {
1061                         int estimatedBinderLatencyMillis = 2 * (int) (totalLatencyStartTimeMillis
1062                                 - request.getBinderCallStartTimeMillis());
1063                         int totalLatencyMillis =
1064                                 (int) (SystemClock.elapsedRealtime() - totalLatencyStartTimeMillis);
1065                         instance.getLogger().logStats(new CallStats.Builder()
1066                                 .setPackageName(callingPackageName)
1067                                 .setDatabase(request.getDatabaseName())
1068                                 .setStatusCode(statusCode)
1069                                 .setTotalLatencyMillis(totalLatencyMillis)
1070                                 .setCallType(CallStats.CALL_TYPE_SEARCH)
1071                                 // TODO(b/173532925) check the existing binder call latency chart
1072                                 // is good enough for us:
1073                                 // http://dashboards/view/_72c98f9a_91d9_41d4_ab9a_bc14f79742b4
1074                                 .setEstimatedBinderLatencyMillis(estimatedBinderLatencyMillis)
1075                                 .setNumOperationsSucceeded(operationSuccessCount)
1076                                 .setNumOperationsFailed(operationFailureCount)
1077                                 .build());
1078                     }
1079                 }
1080             });
1081             if (!callAccepted) {
1082                 logRateLimitedOrCallDeniedCallStats(callingPackageName, request.getDatabaseName(),
1083                         CallStats.CALL_TYPE_SEARCH, targetUser,
1084                         request.getBinderCallStartTimeMillis(), totalLatencyStartTimeMillis,
1085                         /* numOperations= */ 1, RESULT_RATE_LIMITED);
1086             }
1087         }
1088 
1089         @Override
globalSearch( @onNull GlobalSearchAidlRequest request, @NonNull IAppSearchResultCallback callback)1090         public void globalSearch(
1091                 @NonNull GlobalSearchAidlRequest request,
1092                 @NonNull IAppSearchResultCallback callback) {
1093             Objects.requireNonNull(request);
1094             Objects.requireNonNull(callback);
1095 
1096             long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
1097             UserHandle targetUser = mServiceImplHelper.verifyIncomingCallWithCallback(
1098                     request.getCallerAttributionSource(), request.getUserHandle(), callback);
1099             String callingPackageName = request.getCallerAttributionSource().getPackageName();
1100             if (targetUser == null) {
1101                 return;  // Verification failed; verifyIncomingCall triggered callback.
1102             }
1103             // Get the enterprise user for enterprise calls
1104             UserHandle userToQuery = mServiceImplHelper.getUserToQuery(request.isForEnterprise(),
1105                     targetUser);
1106             if (userToQuery == null) {
1107                 // Return an empty result if we tried to and couldn't get the enterprise user
1108                 invokeCallbackOnResult(callback,
1109                         AppSearchResultParcel.fromSearchResultPage(new SearchResultPage()));
1110                 return;
1111             }
1112             if (checkCallDenied(callingPackageName, /* callingDatabaseName= */ null,
1113                     CallStats.CALL_TYPE_GLOBAL_SEARCH, callback, targetUser,
1114                     request.getBinderCallStartTimeMillis(), totalLatencyStartTimeMillis,
1115                     /* numOperations= */ 1)) {
1116                 return;
1117             }
1118             boolean callAccepted = mServiceImplHelper.executeLambdaForUserAsync(targetUser,
1119                     callback, callingPackageName, CallStats.CALL_TYPE_GLOBAL_SEARCH, () -> {
1120                 @AppSearchResult.ResultCode int statusCode = RESULT_OK;
1121                 AppSearchUserInstance instance = null;
1122                 int operationSuccessCount = 0;
1123                 int operationFailureCount = 0;
1124                 try {
1125                     instance = mAppSearchUserInstanceManager.getUserInstance(userToQuery);
1126                     boolean callerHasSystemAccess = instance.getVisibilityChecker()
1127                             .doesCallerHaveSystemAccess(callingPackageName);
1128                     SearchSpec querySearchSpec = request.isForEnterprise()
1129                             ? EnterpriseSearchSpecTransformer.transformSearchSpec(
1130                             request.getSearchSpec()) : request.getSearchSpec();
1131                     SearchResultPage searchResultPage = instance.getAppSearchImpl().globalQuery(
1132                             request.getSearchExpression(),
1133                             querySearchSpec,
1134                             new FrameworkCallerAccess(request.getCallerAttributionSource(),
1135                                     callerHasSystemAccess, request.isForEnterprise()),
1136                             instance.getLogger());
1137                     if (request.isForEnterprise()) {
1138                         searchResultPage =
1139                                 EnterpriseSearchResultPageTransformer.transformSearchResultPage(
1140                                         searchResultPage);
1141                     }
1142                     ++operationSuccessCount;
1143                     invokeCallbackOnResult(
1144                             callback,
1145                             AppSearchResultParcel.fromSearchResultPage(searchResultPage));
1146                 } catch (AppSearchException | RuntimeException e) {
1147                     ++operationFailureCount;
1148                     AppSearchResult<Void> failedResult = throwableToFailedResult(e);
1149                     statusCode = failedResult.getResultCode();
1150                     invokeCallbackOnResult(callback, AppSearchResultParcel.fromFailedResult(
1151                             failedResult));
1152                 } finally {
1153                     if (instance != null) {
1154                         int estimatedBinderLatencyMillis = 2 * (int) (totalLatencyStartTimeMillis
1155                                 - request.getBinderCallStartTimeMillis());
1156                         int totalLatencyMillis =
1157                                 (int) (SystemClock.elapsedRealtime() - totalLatencyStartTimeMillis);
1158                         instance.getLogger().logStats(new CallStats.Builder()
1159                                 .setPackageName(callingPackageName)
1160                                 .setStatusCode(statusCode)
1161                                 .setTotalLatencyMillis(totalLatencyMillis)
1162                                 .setCallType(CallStats.CALL_TYPE_GLOBAL_SEARCH)
1163                                 // TODO(b/173532925) check the existing binder call latency chart
1164                                 // is good enough for us:
1165                                 // http://dashboards/view/_72c98f9a_91d9_41d4_ab9a_bc14f79742b4
1166                                 .setEstimatedBinderLatencyMillis(estimatedBinderLatencyMillis)
1167                                 .setNumOperationsSucceeded(operationSuccessCount)
1168                                 .setNumOperationsFailed(operationFailureCount)
1169                                 .build());
1170                     }
1171                 }
1172             });
1173             if (!callAccepted) {
1174                 logRateLimitedOrCallDeniedCallStats(callingPackageName,
1175                         /* callingDatabaseName= */ null, CallStats.CALL_TYPE_GLOBAL_SEARCH,
1176                         targetUser, request.getBinderCallStartTimeMillis(),
1177                         totalLatencyStartTimeMillis, /* numOperations= */ 1, RESULT_RATE_LIMITED);
1178             }
1179         }
1180 
1181         @Override
getNextPage( @onNull GetNextPageAidlRequest request, @NonNull IAppSearchResultCallback callback)1182         public void getNextPage(
1183                 @NonNull GetNextPageAidlRequest request,
1184                 @NonNull IAppSearchResultCallback callback) {
1185             Objects.requireNonNull(request);
1186             Objects.requireNonNull(callback);
1187 
1188             long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
1189             UserHandle targetUser = mServiceImplHelper.verifyIncomingCallWithCallback(
1190                     request.getCallerAttributionSource(), request.getUserHandle(), callback);
1191             String callingPackageName = request.getCallerAttributionSource().getPackageName();
1192             if (targetUser == null) {
1193                 return;  // Verification failed; verifyIncomingCall triggered callback.
1194             }
1195             // Get the enterprise user for enterprise calls
1196             UserHandle userToQuery = mServiceImplHelper.getUserToQuery(request.isForEnterprise(),
1197                     targetUser);
1198             if (userToQuery == null) {
1199                 // Return an empty result if we tried to and couldn't get the enterprise user
1200                 invokeCallbackOnResult(callback,
1201                         AppSearchResultParcel.fromSearchResultPage(new SearchResultPage()));
1202                 return;
1203             }
1204             // Enterprise session calls are considered global for CallStats logging
1205             boolean global = request.getDatabaseName() == null || request.isForEnterprise();
1206             int callType = global ? CallStats.CALL_TYPE_GLOBAL_GET_NEXT_PAGE
1207                     : CallStats.CALL_TYPE_GET_NEXT_PAGE;
1208             if (checkCallDenied(callingPackageName, request.getDatabaseName(), callType, callback,
1209                     targetUser, request.getBinderCallStartTimeMillis(), totalLatencyStartTimeMillis,
1210                     /* numOperations= */ 1)) {
1211                 return;
1212             }
1213             boolean callAccepted = mServiceImplHelper.executeLambdaForUserAsync(targetUser,
1214                     callback, callingPackageName, callType, () -> {
1215                 @AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK;
1216                 AppSearchUserInstance instance = null;
1217                 int operationSuccessCount = 0;
1218                 int operationFailureCount = 0;
1219                 SearchStats.Builder statsBuilder;
1220                 if (global) {
1221                     statsBuilder = new SearchStats.Builder(VISIBILITY_SCOPE_GLOBAL,
1222                             callingPackageName)
1223                             .setJoinType(request.getJoinType());
1224                 } else {
1225                     statsBuilder = new SearchStats.Builder(VISIBILITY_SCOPE_LOCAL,
1226                             callingPackageName)
1227                             .setDatabase(request.getDatabaseName())
1228                             .setJoinType(request.getJoinType());
1229                 }
1230                 try {
1231                     instance = mAppSearchUserInstanceManager.getUserInstance(userToQuery);
1232                     SearchResultPage searchResultPage =
1233                             instance.getAppSearchImpl().getNextPage(callingPackageName,
1234                                     request.getNextPageToken(), statsBuilder);
1235                     if (request.isForEnterprise()) {
1236                         searchResultPage =
1237                                 EnterpriseSearchResultPageTransformer.transformSearchResultPage(
1238                                         searchResultPage);
1239                     }
1240                     ++operationSuccessCount;
1241                     invokeCallbackOnResult(
1242                             callback,
1243                             AppSearchResultParcel.fromSearchResultPage(searchResultPage));
1244                 } catch (AppSearchException | RuntimeException e) {
1245                     ++operationFailureCount;
1246                     AppSearchResult<Void> failedResult = throwableToFailedResult(e);
1247                     statusCode = failedResult.getResultCode();
1248                     invokeCallbackOnResult(callback, AppSearchResultParcel.fromFailedResult(
1249                             failedResult));
1250                 } finally {
1251                     if (instance != null) {
1252                         int estimatedBinderLatencyMillis = 2 * (int) (totalLatencyStartTimeMillis
1253                                 - request.getBinderCallStartTimeMillis());
1254                         int totalLatencyMillis =
1255                                 (int) (SystemClock.elapsedRealtime() - totalLatencyStartTimeMillis);
1256                         CallStats.Builder builder = new CallStats.Builder()
1257                                 .setPackageName(callingPackageName)
1258                                 .setDatabase(request.getDatabaseName())
1259                                 .setStatusCode(statusCode)
1260                                 .setTotalLatencyMillis(totalLatencyMillis)
1261                                 .setCallType(callType)
1262                                 // TODO(b/173532925) check the existing binder call latency chart
1263                                 // is good enough for us:
1264                                 // http://dashboards/view/_72c98f9a_91d9_41d4_ab9a_bc14f79742b4
1265                                 .setEstimatedBinderLatencyMillis(estimatedBinderLatencyMillis)
1266                                 .setNumOperationsSucceeded(operationSuccessCount)
1267                                 .setNumOperationsFailed(operationFailureCount);
1268                         instance.getLogger().logStats(builder.build());
1269                         instance.getLogger().logStats(statsBuilder.build());
1270                     }
1271                 }
1272             });
1273             if (!callAccepted) {
1274                 logRateLimitedOrCallDeniedCallStats(callingPackageName, request.getDatabaseName(),
1275                         callType, targetUser, request.getBinderCallStartTimeMillis(),
1276                         totalLatencyStartTimeMillis, /* numOperations= */ 1, RESULT_RATE_LIMITED);
1277             }
1278         }
1279 
1280         @Override
invalidateNextPageToken(@onNull InvalidateNextPageTokenAidlRequest request)1281         public void invalidateNextPageToken(@NonNull InvalidateNextPageTokenAidlRequest request) {
1282             Objects.requireNonNull(request);
1283 
1284             long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
1285             try {
1286                 UserHandle targetUser = mServiceImplHelper.verifyIncomingCall(
1287                         request.getCallerAttributionSource(), request.getUserHandle());
1288                 // Get the enterprise user for enterprise calls
1289                 UserHandle userToQuery = mServiceImplHelper.getUserToQuery(
1290                         request.isForEnterprise(), targetUser);
1291                 if (userToQuery == null) {
1292                     // Return if we tried to and couldn't get the enterprise user
1293                     return;
1294                 }
1295                 String callingPackageName = request.getCallerAttributionSource().getPackageName();
1296                 if (checkCallDenied(callingPackageName, /* callingDatabaseName= */ null,
1297                         CallStats.CALL_TYPE_INVALIDATE_NEXT_PAGE_TOKEN, targetUser,
1298                         request.getBinderCallStartTimeMillis(), totalLatencyStartTimeMillis,
1299                         /* numOperations= */ 1)) {
1300                     return;
1301                 }
1302                 boolean callAccepted = mServiceImplHelper.executeLambdaForUserNoCallbackAsync(
1303                         targetUser, callingPackageName,
1304                         CallStats.CALL_TYPE_INVALIDATE_NEXT_PAGE_TOKEN, () -> {
1305                     @AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK;
1306                     AppSearchUserInstance instance = null;
1307                     int operationSuccessCount = 0;
1308                     int operationFailureCount = 0;
1309                     try {
1310                         instance = mAppSearchUserInstanceManager.getUserInstance(userToQuery);
1311                         instance.getAppSearchImpl().invalidateNextPageToken(
1312                                 callingPackageName, request.getNextPageToken());
1313                         operationSuccessCount++;
1314                     } catch (AppSearchException | RuntimeException e) {
1315                         ++operationFailureCount;
1316                         statusCode = throwableToFailedResult(e).getResultCode();
1317                         Log.e(TAG, "Unable to invalidate the query page token", e);
1318                         ExceptionUtil.handleException(e);
1319                     } finally {
1320                         if (instance != null) {
1321                             int estimatedBinderLatencyMillis =
1322                                     2 * (int) (totalLatencyStartTimeMillis
1323                                             - request.getBinderCallStartTimeMillis());
1324                             int totalLatencyMillis =
1325                                     (int) (SystemClock.elapsedRealtime()
1326                                             - totalLatencyStartTimeMillis);
1327                             instance.getLogger().logStats(new CallStats.Builder()
1328                                     .setPackageName(callingPackageName)
1329                                     .setStatusCode(statusCode)
1330                                     .setTotalLatencyMillis(totalLatencyMillis)
1331                                     .setCallType(CallStats.CALL_TYPE_INVALIDATE_NEXT_PAGE_TOKEN)
1332                                     // TODO(b/173532925) check the existing binder call latency
1333                                     //  chart
1334                                     // is good enough for us:
1335                                     // http://dashboards/view/_72c98f9a_91d9_41d4_ab9a_bc14f79742b4
1336                                     .setEstimatedBinderLatencyMillis(estimatedBinderLatencyMillis)
1337                                     .setNumOperationsSucceeded(operationSuccessCount)
1338                                     .setNumOperationsFailed(operationFailureCount)
1339                                     .build());
1340                         }
1341                     }
1342                 });
1343                 if (!callAccepted) {
1344                     logRateLimitedOrCallDeniedCallStats(
1345                             callingPackageName, /* callingDatabaseName= */ null,
1346                             CallStats.CALL_TYPE_INVALIDATE_NEXT_PAGE_TOKEN, targetUser,
1347                             request.getBinderCallStartTimeMillis(), totalLatencyStartTimeMillis,
1348                             /* numOperations= */ 1, RESULT_RATE_LIMITED);
1349                 }
1350             } catch (RuntimeException e) {
1351                 Log.e(TAG, "Unable to invalidate the query page token", e);
1352                 ExceptionUtil.handleException(e);
1353             }
1354         }
1355 
1356         @Override
writeSearchResultsToFile( @onNull WriteSearchResultsToFileAidlRequest request, @NonNull IAppSearchResultCallback callback)1357         public void writeSearchResultsToFile(
1358                 @NonNull WriteSearchResultsToFileAidlRequest request,
1359                 @NonNull IAppSearchResultCallback callback) {
1360             Objects.requireNonNull(request);
1361             Objects.requireNonNull(callback);
1362 
1363             long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
1364             UserHandle targetUser = mServiceImplHelper.verifyIncomingCallWithCallback(
1365                     request.getCallerAttributionSource(), request.getUserHandle(), callback);
1366             String callingPackageName = request.getCallerAttributionSource().getPackageName();
1367             if (targetUser == null) {
1368                 return;  // Verification failed; verifyIncomingCall triggered callback.
1369             }
1370             if (checkCallDenied(callingPackageName, request.getDatabaseName(),
1371                     CallStats.CALL_TYPE_WRITE_SEARCH_RESULTS_TO_FILE, callback, targetUser,
1372                     request.getBinderCallStartTimeMillis(), totalLatencyStartTimeMillis,
1373                     /* numOperations= */ 1)) {
1374                 return;
1375             }
1376             boolean callAccepted = mServiceImplHelper.executeLambdaForUserAsync(targetUser,
1377                     callback, callingPackageName, CallStats.CALL_TYPE_WRITE_SEARCH_RESULTS_TO_FILE,
1378                     () -> {
1379                 @AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK;
1380                 AppSearchUserInstance instance = null;
1381                 int operationSuccessCount = 0;
1382                 int operationFailureCount = 0;
1383                 try {
1384                     instance = mAppSearchUserInstanceManager.getUserInstance(targetUser);
1385                     // we don't need to append the file. The file is always brand new.
1386                     try (DataOutputStream outputStream = new DataOutputStream(new FileOutputStream(
1387                             request.getParcelFileDescriptor().getFileDescriptor()))) {
1388                         SearchResultPage searchResultPage = instance.getAppSearchImpl().query(
1389                                 callingPackageName,
1390                                 request.getDatabaseName(),
1391                                 request.getSearchExpression(),
1392                                 request.getSearchSpec(),
1393                                 /* logger= */ null);
1394                         while (!searchResultPage.getResults().isEmpty()) {
1395                             for (int i = 0; i < searchResultPage.getResults().size(); i++) {
1396                                 AppSearchMigrationHelper.writeDocumentToOutputStream(
1397                                         outputStream,
1398                                         searchResultPage.getResults().get(i).getGenericDocument());
1399                             }
1400                             operationSuccessCount += searchResultPage.getResults().size();
1401                             // TODO(b/173532925): Implement logging for statsBuilder
1402                             searchResultPage = instance.getAppSearchImpl().getNextPage(
1403                                     callingPackageName,
1404                                     searchResultPage.getNextPageToken(),
1405                                     /* sStatsBuilder= */ null);
1406                         }
1407                     }
1408                     invokeCallbackOnResult(callback, AppSearchResultParcel.fromVoid());
1409                 } catch (AppSearchException | IOException | RuntimeException e) {
1410                     ++operationFailureCount;
1411                     AppSearchResult<Void> failedResult = throwableToFailedResult(e);
1412                     statusCode = failedResult.getResultCode();
1413                     invokeCallbackOnResult(callback, AppSearchResultParcel.fromFailedResult(
1414                             failedResult));
1415                 } finally {
1416                     if (instance != null) {
1417                         int estimatedBinderLatencyMillis = 2 * (int) (totalLatencyStartTimeMillis
1418                                 - request.getBinderCallStartTimeMillis());
1419                         int totalLatencyMillis =
1420                                 (int) (SystemClock.elapsedRealtime() - totalLatencyStartTimeMillis);
1421                         instance.getLogger().logStats(new CallStats.Builder()
1422                                 .setPackageName(callingPackageName)
1423                                 .setDatabase(request.getDatabaseName())
1424                                 .setStatusCode(statusCode)
1425                                 .setTotalLatencyMillis(totalLatencyMillis)
1426                                 .setCallType(CallStats.CALL_TYPE_WRITE_SEARCH_RESULTS_TO_FILE)
1427                                 // TODO(b/173532925) check the existing binder call latency chart
1428                                 // is good enough for us:
1429                                 // http://dashboards/view/_72c98f9a_91d9_41d4_ab9a_bc14f79742b4
1430                                 .setEstimatedBinderLatencyMillis(estimatedBinderLatencyMillis)
1431                                 .setNumOperationsSucceeded(operationSuccessCount)
1432                                 .setNumOperationsFailed(operationFailureCount)
1433                                 .build());
1434                     }
1435                 }
1436             });
1437             if (!callAccepted) {
1438                 logRateLimitedOrCallDeniedCallStats(callingPackageName, request.getDatabaseName(),
1439                         CallStats.CALL_TYPE_WRITE_SEARCH_RESULTS_TO_FILE, targetUser,
1440                         request.getBinderCallStartTimeMillis(), totalLatencyStartTimeMillis,
1441                         /* numOperations= */ 1, RESULT_RATE_LIMITED);
1442             }
1443         }
1444 
1445         @Override
putDocumentsFromFile( @onNull PutDocumentsFromFileAidlRequest request, @NonNull IAppSearchResultCallback callback)1446         public void putDocumentsFromFile(
1447                 @NonNull PutDocumentsFromFileAidlRequest request,
1448                 @NonNull IAppSearchResultCallback callback) {
1449             Objects.requireNonNull(request);
1450             Objects.requireNonNull(callback);
1451 
1452             long callStatsTotalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
1453             UserHandle targetUser = mServiceImplHelper.verifyIncomingCallWithCallback(
1454                     request.getCallerAttributionSource(), request.getUserHandle(), callback);
1455             String callingPackageName = request.getCallerAttributionSource().getPackageName();
1456             if (targetUser == null) {
1457                 return;  // Verification failed; verifyIncomingCall triggered callback.
1458             }
1459             // Since we don't read from the given file, we don't know the number of documents so we
1460             // just set numOperations to 1 instead
1461             if (checkCallDenied(callingPackageName, request.getDatabaseName(),
1462                     CallStats.CALL_TYPE_PUT_DOCUMENTS_FROM_FILE, callback, targetUser,
1463                     request.getBinderCallStartTimeMillis(), callStatsTotalLatencyStartTimeMillis,
1464                     /* numOperations= */ 1)) {
1465                 return;
1466             }
1467             boolean callAccepted = mServiceImplHelper.executeLambdaForUserAsync(targetUser,
1468                     callback, callingPackageName, CallStats.CALL_TYPE_PUT_DOCUMENTS_FROM_FILE,
1469                     () -> {
1470                 @AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK;
1471                 AppSearchUserInstance instance = null;
1472                 int operationSuccessCount = 0;
1473                 int operationFailureCount = 0;
1474                 SchemaMigrationStats.Builder schemaMigrationStatsBuilder = new SchemaMigrationStats
1475                         .Builder(request.getSchemaMigrationStats());
1476                 try {
1477                     instance = mAppSearchUserInstanceManager.getUserInstance(targetUser);
1478 
1479                     GenericDocument document;
1480                     ArrayList<MigrationFailure> migrationFailures = new ArrayList<>();
1481                     try (DataInputStream inputStream = new DataInputStream(new FileInputStream(
1482                             request.getParcelFileDescriptor().getFileDescriptor()))) {
1483                         while (true) {
1484                             try {
1485                                 document = AppSearchMigrationHelper
1486                                         .readDocumentFromInputStream(inputStream);
1487                             } catch (EOFException e) {
1488                                 // nothing wrong, we just finish the reading.
1489                                 break;
1490                             }
1491                             try {
1492                                 // Per this method's documentation, individual document change
1493                                 // notifications are not dispatched.
1494                                 instance.getAppSearchImpl().putDocument(
1495                                         callingPackageName,
1496                                         request.getDatabaseName(),
1497                                         document,
1498                                         /* sendChangeNotifications= */ false,
1499                                         /* logger= */ null);
1500                                 ++operationSuccessCount;
1501                             } catch (AppSearchException | RuntimeException e) {
1502                                 // We don't rethrow here, so we can still keep going with the
1503                                 // following documents.
1504                                 ++operationFailureCount;
1505                                 AppSearchResult<Void> failedResult = throwableToFailedResult(e);
1506                                 statusCode = failedResult.getResultCode();
1507                                 migrationFailures.add(new SetSchemaResponse.MigrationFailure(
1508                                         document.getNamespace(),
1509                                         document.getId(),
1510                                         document.getSchemaType(),
1511                                         failedResult));
1512                             }
1513                         }
1514                     }
1515                     instance.getAppSearchImpl().persistToDisk(PersistType.Code.FULL);
1516 
1517                     schemaMigrationStatsBuilder
1518                             .setTotalSuccessMigratedDocumentCount(operationSuccessCount)
1519                             .setMigrationFailureCount(migrationFailures.size());
1520                     invokeCallbackOnResult(callback, AppSearchResultParcel
1521                             .fromMigrationFailuresList(migrationFailures));
1522                 } catch (AppSearchException | IOException | RuntimeException e) {
1523                     ++operationFailureCount;
1524                     AppSearchResult<Void> failedResult = throwableToFailedResult(e);
1525                     statusCode = failedResult.getResultCode();
1526                     invokeCallbackOnResult(callback, AppSearchResultParcel.fromFailedResult(
1527                             failedResult));
1528                 } finally {
1529                     if (instance != null) {
1530                         long latencyEndTimeMillis =
1531                                 SystemClock.elapsedRealtime();
1532                         int estimatedBinderLatencyMillis =
1533                                 2 * (int) (callStatsTotalLatencyStartTimeMillis
1534                                         - request.getBinderCallStartTimeMillis());
1535                         int callStatsTotalLatencyMillis =
1536                                 (int) (latencyEndTimeMillis - callStatsTotalLatencyStartTimeMillis);
1537                         // totalLatencyStartTimeMillis is captured in the SDK side, and
1538                         // put migrate documents is the last step of migration process.
1539                         // This should includes whole schema migration process.
1540                         // Like get old schema, first and second set schema, query old
1541                         // documents, transform documents and save migrated documents.
1542                         int totalLatencyMillis = (int) (latencyEndTimeMillis
1543                                 - request.getTotalLatencyStartTimeMillis());
1544                         int saveDocumentLatencyMillis = (int) (latencyEndTimeMillis
1545                                 - request.getBinderCallStartTimeMillis());
1546                         instance.getLogger().logStats(new CallStats.Builder()
1547                                 .setPackageName(callingPackageName)
1548                                 .setDatabase(request.getDatabaseName())
1549                                 .setStatusCode(statusCode)
1550                                 .setTotalLatencyMillis(callStatsTotalLatencyMillis)
1551                                 .setCallType(CallStats.CALL_TYPE_PUT_DOCUMENTS_FROM_FILE)
1552                                 // TODO(b/173532925) check the existing binder call latency chart
1553                                 // is good enough for us:
1554                                 // http://dashboards/view/_72c98f9a_91d9_41d4_ab9a_bc14f79742b4
1555                                 .setEstimatedBinderLatencyMillis(estimatedBinderLatencyMillis)
1556                                 .setNumOperationsSucceeded(operationSuccessCount)
1557                                 .setNumOperationsFailed(operationFailureCount)
1558                                 .build());
1559                         instance.getLogger().logStats(schemaMigrationStatsBuilder
1560                                 .setStatusCode(statusCode)
1561                                 .setTotalLatencyMillis(totalLatencyMillis)
1562                                 .setSaveDocumentLatencyMillis(saveDocumentLatencyMillis)
1563                                 .build());
1564                     }
1565                 }
1566             });
1567             if (!callAccepted) {
1568                 logRateLimitedOrCallDeniedCallStats(callingPackageName, request.getDatabaseName(),
1569                         CallStats.CALL_TYPE_PUT_DOCUMENTS_FROM_FILE, targetUser,
1570                         request.getBinderCallStartTimeMillis(),
1571                         callStatsTotalLatencyStartTimeMillis, /* numOperations= */ 1,
1572                         RESULT_RATE_LIMITED);
1573             }
1574         }
1575 
1576         @Override
searchSuggestion( @onNull SearchSuggestionAidlRequest request, @NonNull IAppSearchResultCallback callback)1577         public void searchSuggestion(
1578                 @NonNull SearchSuggestionAidlRequest request,
1579                 @NonNull IAppSearchResultCallback callback) {
1580             Objects.requireNonNull(request);
1581             Objects.requireNonNull(callback);
1582 
1583             long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
1584             UserHandle targetUser = mServiceImplHelper.verifyIncomingCallWithCallback(
1585                     request.getCallerAttributionSource(), request.getUserHandle(), callback);
1586             String callingPackageName = request.getCallerAttributionSource().getPackageName();
1587             if (targetUser == null) {
1588                 return;  // Verification failed; verifyIncomingCall triggered callback.
1589             }
1590             if (checkCallDenied(callingPackageName, request.getDatabaseName(),
1591                     CallStats.CALL_TYPE_SEARCH_SUGGESTION, callback, targetUser,
1592                     request.getBinderCallStartTimeMillis(), totalLatencyStartTimeMillis,
1593                     /* numOperations= */ 1)) {
1594                 return;
1595             }
1596             boolean callAccepted = mServiceImplHelper.executeLambdaForUserAsync(targetUser,
1597                     callback, callingPackageName, CallStats.CALL_TYPE_SEARCH_SUGGESTION,
1598                     () -> {
1599                 @AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK;
1600                 AppSearchUserInstance instance = null;
1601                 int operationSuccessCount = 0;
1602                 int operationFailureCount = 0;
1603                 try {
1604                     instance = mAppSearchUserInstanceManager.getUserInstance(targetUser);
1605                     // TODO(b/173532925): Implement logging for statsBuilder
1606                     List<SearchSuggestionResult> searchSuggestionResults =
1607                             instance.getAppSearchImpl().searchSuggestion(
1608                                     callingPackageName,
1609                                     request.getDatabaseName(),
1610                                     request.getSuggestionQueryExpression(),
1611                                     request.getSearchSuggestionSpec());
1612                     ++operationSuccessCount;
1613                     invokeCallbackOnResult(
1614                             callback, AppSearchResultParcel
1615                                     .fromSearchSuggestionResultList(searchSuggestionResults));
1616                 } catch (AppSearchException | RuntimeException e) {
1617                     ++operationFailureCount;
1618                     AppSearchResult<Void> failedResult = throwableToFailedResult(e);
1619                     statusCode = failedResult.getResultCode();
1620                     invokeCallbackOnResult(callback, AppSearchResultParcel.fromFailedResult(
1621                             failedResult));
1622                 } finally {
1623                     if (instance != null) {
1624                         int estimatedBinderLatencyMillis =
1625                                 2 * (int) (totalLatencyStartTimeMillis
1626                                         - request.getBinderCallStartTimeMillis());
1627                         int totalLatencyMillis =
1628                                 (int) (SystemClock.elapsedRealtime() - totalLatencyStartTimeMillis);
1629                         instance.getLogger().logStats(new CallStats.Builder()
1630                                 .setPackageName(callingPackageName)
1631                                 .setDatabase(request.getDatabaseName())
1632                                 .setStatusCode(statusCode)
1633                                 .setTotalLatencyMillis(totalLatencyMillis)
1634                                 .setCallType(CallStats.CALL_TYPE_SEARCH_SUGGESTION)
1635                                 // TODO(b/173532925) check the existing binder call latency chart
1636                                 // is good enough for us:
1637                                 // http://dashboards/view/_72c98f9a_91d9_41d4_ab9a_bc14f79742b4
1638                                 .setEstimatedBinderLatencyMillis(estimatedBinderLatencyMillis)
1639                                 .setNumOperationsSucceeded(operationSuccessCount)
1640                                 .setNumOperationsFailed(operationFailureCount)
1641                                 .build());
1642                     }
1643                 }
1644             });
1645             if (!callAccepted) {
1646                 logRateLimitedOrCallDeniedCallStats(callingPackageName, request.getDatabaseName(),
1647                         CallStats.CALL_TYPE_SEARCH_SUGGESTION, targetUser,
1648                         request.getBinderCallStartTimeMillis(), totalLatencyStartTimeMillis,
1649                         /* numOperations= */ 1, RESULT_RATE_LIMITED);
1650             }
1651         }
1652 
1653         @Override
reportUsage( @onNull ReportUsageAidlRequest request, @NonNull IAppSearchResultCallback callback)1654         public void reportUsage(
1655                 @NonNull ReportUsageAidlRequest request,
1656                 @NonNull IAppSearchResultCallback callback) {
1657             Objects.requireNonNull(request);
1658             Objects.requireNonNull(callback);
1659 
1660             long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
1661             UserHandle targetUser = mServiceImplHelper.verifyIncomingCallWithCallback(
1662                     request.getCallerAttributionSource(), request.getUserHandle(), callback);
1663             String callingPackageName = request.getCallerAttributionSource().getPackageName();
1664             if (targetUser == null) {
1665                 return;  // Verification failed; verifyIncomingCall triggered callback.
1666             }
1667             // We deny based on the calling package and calling database names. If the API call is
1668             // intended for system usage, then the call is global, and the target database is not a
1669             // calling database.
1670             String callingDatabaseName = request.isSystemUsage()
1671                     ? null : request.getDatabaseName();
1672             int callType = request.isSystemUsage() ? CallStats.CALL_TYPE_REPORT_SYSTEM_USAGE
1673                     : CallStats.CALL_TYPE_REPORT_USAGE;
1674             if (checkCallDenied(callingPackageName, callingDatabaseName, callType, callback,
1675                     targetUser, request.getBinderCallStartTimeMillis(), totalLatencyStartTimeMillis,
1676                     /* numOperations= */ 1)) {
1677                 return;
1678             }
1679             boolean callAccepted = mServiceImplHelper.executeLambdaForUserAsync(targetUser,
1680                     callback, callingPackageName, CallStats.CALL_TYPE_REPORT_USAGE,
1681                     () -> {
1682                 @AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK;
1683                 AppSearchUserInstance instance = null;
1684                 int operationSuccessCount = 0;
1685                 int operationFailureCount = 0;
1686                 try {
1687                     instance = mAppSearchUserInstanceManager.getUserInstance(targetUser);
1688                     if (request.isSystemUsage()) {
1689                         if (!instance.getVisibilityChecker().doesCallerHaveSystemAccess(
1690                                 callingPackageName)) {
1691                             throw new AppSearchException(RESULT_SECURITY_ERROR,
1692                                     callingPackageName
1693                                             + " does not have access to report system usage");
1694                         }
1695                     } else {
1696                         if (!callingPackageName.equals(request.getTargetPackageName())) {
1697                             throw new AppSearchException(RESULT_SECURITY_ERROR,
1698                                     "Cannot report usage to different package: "
1699                                             + request.getTargetPackageName() + " from package: "
1700                                             + callingPackageName);
1701                         }
1702                     }
1703 
1704                     instance.getAppSearchImpl().reportUsage(request.getTargetPackageName(),
1705                             request.getDatabaseName(),
1706                             request.getReportUsageRequest().getNamespace(),
1707                             request.getReportUsageRequest().getDocumentId(),
1708                             request.getReportUsageRequest().getUsageTimestampMillis(),
1709                             request.isSystemUsage());
1710                     ++operationSuccessCount;
1711                     invokeCallbackOnResult(callback, AppSearchResultParcel.fromVoid());
1712                 } catch (AppSearchException | RuntimeException e) {
1713                     ++operationFailureCount;
1714                     AppSearchResult<Void> failedResult = throwableToFailedResult(e);
1715                     statusCode = failedResult.getResultCode();
1716                     invokeCallbackOnResult(callback, AppSearchResultParcel.fromFailedResult(
1717                             failedResult));
1718                 } finally {
1719                     if (instance != null) {
1720                         int estimatedBinderLatencyMillis =
1721                                 2 * (int) (totalLatencyStartTimeMillis -
1722                                         request.getBinderCallStartTimeMillis());
1723                         int totalLatencyMillis =
1724                                 (int) (SystemClock.elapsedRealtime() - totalLatencyStartTimeMillis);
1725                         instance.getLogger().logStats(new CallStats.Builder()
1726                                 .setPackageName(callingPackageName)
1727                                 .setDatabase(request.getDatabaseName())
1728                                 .setStatusCode(statusCode)
1729                                 .setTotalLatencyMillis(totalLatencyMillis)
1730                                 .setCallType(callType)
1731                                 // TODO(b/173532925) check the existing binder call latency chart
1732                                 // is good enough for us:
1733                                 // http://dashboards/view/_72c98f9a_91d9_41d4_ab9a_bc14f79742b4
1734                                 .setEstimatedBinderLatencyMillis(estimatedBinderLatencyMillis)
1735                                 .setNumOperationsSucceeded(operationSuccessCount)
1736                                 .setNumOperationsFailed(operationFailureCount)
1737                                 .build());
1738                     }
1739                 }
1740             });
1741             if (!callAccepted) {
1742                 logRateLimitedOrCallDeniedCallStats(callingPackageName, callingDatabaseName,
1743                         callType, targetUser, request.getBinderCallStartTimeMillis(),
1744                         totalLatencyStartTimeMillis,
1745                         /* numOperations= */ 1, RESULT_RATE_LIMITED);
1746             }
1747         }
1748 
1749         @Override
removeByDocumentId( @onNull RemoveByDocumentIdAidlRequest request, @NonNull IAppSearchBatchResultCallback callback)1750         public void removeByDocumentId(
1751                 @NonNull RemoveByDocumentIdAidlRequest request,
1752                 @NonNull IAppSearchBatchResultCallback callback) {
1753             Objects.requireNonNull(request);
1754             Objects.requireNonNull(callback);
1755 
1756             long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
1757             UserHandle targetUser = mServiceImplHelper.verifyIncomingCallWithCallback(
1758                     request.getCallerAttributionSource(), request.getUserHandle(), callback);
1759             String callingPackageName = request.getCallerAttributionSource().getPackageName();
1760             if (targetUser == null) {
1761                 return;  // Verification failed; verifyIncomingCall triggered callback.
1762             }
1763             if (checkCallDenied(callingPackageName, request.getDatabaseName(),
1764                     CallStats.CALL_TYPE_REMOVE_DOCUMENTS_BY_ID, callback, targetUser,
1765                     request.getBinderCallStartTimeMillis(), totalLatencyStartTimeMillis,
1766                     /* numOperations= */ request.getRemoveByDocumentIdRequest().getIds().size())) {
1767                 return;
1768             }
1769             boolean callAccepted = mServiceImplHelper.executeLambdaForUserAsync(targetUser,
1770                     callback, callingPackageName, CallStats.CALL_TYPE_REMOVE_DOCUMENTS_BY_ID,
1771                     () -> {
1772                 @AppSearchResult.ResultCode int statusCode = RESULT_OK;
1773                 AppSearchUserInstance instance = null;
1774                 int operationSuccessCount = 0;
1775                 int operationFailureCount = 0;
1776                 try {
1777                     AppSearchBatchResult.Builder<String, Void> resultBuilder =
1778                             new AppSearchBatchResult.Builder<>();
1779                     instance = mAppSearchUserInstanceManager.getUserInstance(targetUser);
1780                     for (String id : request.getRemoveByDocumentIdRequest().getIds()) {
1781                         try {
1782                             instance.getAppSearchImpl().remove(
1783                                     callingPackageName,
1784                                     request.getDatabaseName(),
1785                                     request.getRemoveByDocumentIdRequest().getNamespace(),
1786                                     id,
1787                                     /* removeStatsBuilder= */ null);
1788                             ++operationSuccessCount;
1789                             resultBuilder.setSuccess(id, /*result= */ null);
1790                         } catch (AppSearchException | RuntimeException e) {
1791                             // We don't rethrow here, so we can still keep trying for the following
1792                             // ones.
1793                             AppSearchResult<Void> result = throwableToFailedResult(e);
1794                             resultBuilder.setResult(id, result);
1795                             // Since we can only include one status code in the atom,
1796                             // for failures, we would just save the one for the last failure
1797                             statusCode = result.getResultCode();
1798                             ++operationFailureCount;
1799                         }
1800                     }
1801                     // Now that the batch has been written. Persist the newly written data.
1802                     instance.getAppSearchImpl().persistToDisk(PersistType.Code.LITE);
1803                     invokeCallbackOnResult(callback, AppSearchBatchResultParcel.fromStringToVoid(
1804                             resultBuilder.build()));
1805 
1806                     // Schedule a task to dispatch change notifications. See requirements for where
1807                     // the method is called documented in the method description.
1808                     dispatchChangeNotifications(instance);
1809 
1810                     checkForOptimize(targetUser, instance,
1811                             request.getRemoveByDocumentIdRequest().getIds().size());
1812                 } catch (AppSearchException | RuntimeException e) {
1813                     ++operationFailureCount;
1814                     AppSearchResult<Void> failedResult = throwableToFailedResult(e);
1815                     statusCode = failedResult.getResultCode();
1816                     invokeCallbackOnError(callback, failedResult);
1817                 } finally {
1818                     // TODO(b/261959320) add outstanding latency fields in AppSearch stats
1819                     if (instance != null) {
1820                         int estimatedBinderLatencyMillis =
1821                                 2 * (int) (totalLatencyStartTimeMillis
1822                                         - request.getBinderCallStartTimeMillis());
1823                         int totalLatencyMillis =
1824                                 (int) (SystemClock.elapsedRealtime() - totalLatencyStartTimeMillis);
1825                         instance.getLogger().logStats(new CallStats.Builder()
1826                                 .setPackageName(callingPackageName)
1827                                 .setDatabase(request.getDatabaseName())
1828                                 .setStatusCode(statusCode)
1829                                 .setTotalLatencyMillis(totalLatencyMillis)
1830                                 .setCallType(CallStats.CALL_TYPE_REMOVE_DOCUMENTS_BY_ID)
1831                                 // TODO(b/173532925) check the existing binder call latency chart
1832                                 // is good enough for us:
1833                                 // http://dashboards/view/_72c98f9a_91d9_41d4_ab9a_bc14f79742b4
1834                                 .setEstimatedBinderLatencyMillis(estimatedBinderLatencyMillis)
1835                                 .setNumOperationsSucceeded(operationSuccessCount)
1836                                 .setNumOperationsFailed(operationFailureCount)
1837                                 .build());
1838                     }
1839                 }
1840             });
1841             if (!callAccepted) {
1842                 logRateLimitedOrCallDeniedCallStats(callingPackageName, request.getDatabaseName(),
1843                         CallStats.CALL_TYPE_REMOVE_DOCUMENTS_BY_ID, targetUser,
1844                         request.getBinderCallStartTimeMillis(), totalLatencyStartTimeMillis,
1845                         /* numOperations= */ request.getRemoveByDocumentIdRequest().getIds().size(),
1846                         RESULT_RATE_LIMITED);
1847             }
1848         }
1849 
1850         @Override
removeByQuery( @onNull RemoveByQueryAidlRequest request, @NonNull IAppSearchResultCallback callback)1851         public void removeByQuery(
1852                 @NonNull RemoveByQueryAidlRequest request,
1853                 @NonNull IAppSearchResultCallback callback) {
1854             Objects.requireNonNull(request);
1855             Objects.requireNonNull(callback);
1856 
1857             long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
1858             UserHandle targetUser = mServiceImplHelper.verifyIncomingCallWithCallback(
1859                     request.getCallerAttributionSource(), request.getUserHandle(), callback);
1860             String callingPackageName = request.getCallerAttributionSource().getPackageName();
1861             if (targetUser == null) {
1862                 return;  // Verification failed; verifyIncomingCall triggered callback.
1863             }
1864             if (checkCallDenied(callingPackageName, request.getDatabaseName(),
1865                     CallStats.CALL_TYPE_REMOVE_DOCUMENTS_BY_SEARCH, callback, targetUser,
1866                     request.getBinderCallStartTimeMillis(), totalLatencyStartTimeMillis,
1867                     /* numOperations= */ 1)) {
1868                 return;
1869             }
1870             boolean callAccepted = mServiceImplHelper.executeLambdaForUserAsync(targetUser,
1871                     callback, callingPackageName, CallStats.CALL_TYPE_REMOVE_DOCUMENTS_BY_SEARCH,
1872                     () -> {
1873                 @AppSearchResult.ResultCode int statusCode = RESULT_OK;
1874                 AppSearchUserInstance instance = null;
1875                 int operationSuccessCount = 0;
1876                 int operationFailureCount = 0;
1877                 try {
1878                     instance = mAppSearchUserInstanceManager.getUserInstance(targetUser);
1879                     instance.getAppSearchImpl().removeByQuery(
1880                             callingPackageName,
1881                             request.getDatabaseName(),
1882                             request.getQueryExpression(),
1883                             request.getSearchSpec(),
1884                             /* removeStatsBuilder= */ null);
1885                     // Now that the batch has been written. Persist the newly written data.
1886                     instance.getAppSearchImpl().persistToDisk(PersistType.Code.LITE);
1887                     ++operationSuccessCount;
1888                     invokeCallbackOnResult(callback, AppSearchResultParcel.fromVoid());
1889 
1890                     // Schedule a task to dispatch change notifications. See requirements for where
1891                     // the method is called documented in the method description.
1892                     dispatchChangeNotifications(instance);
1893 
1894                     checkForOptimize(targetUser, instance);
1895                 } catch (AppSearchException | RuntimeException e) {
1896                     ++operationFailureCount;
1897                     AppSearchResult<Void> failedResult = throwableToFailedResult(e);
1898                     statusCode = failedResult.getResultCode();
1899                     invokeCallbackOnResult(callback, AppSearchResultParcel.fromFailedResult(
1900                             failedResult));
1901                 } finally {
1902                     // TODO(b/261959320) add outstanding latency fields in AppSearch stats
1903                     if (instance != null) {
1904                         int estimatedBinderLatencyMillis =
1905                                 2 * (int) (totalLatencyStartTimeMillis
1906                                         - request.getBinderCallStartTimeMillis());
1907                         int totalLatencyMillis =
1908                                 (int) (SystemClock.elapsedRealtime() - totalLatencyStartTimeMillis);
1909                         instance.getLogger().logStats(new CallStats.Builder()
1910                                 .setPackageName(callingPackageName)
1911                                 .setDatabase(request.getDatabaseName())
1912                                 .setStatusCode(statusCode)
1913                                 .setTotalLatencyMillis(totalLatencyMillis)
1914                                 .setCallType(CallStats.CALL_TYPE_REMOVE_DOCUMENTS_BY_SEARCH)
1915                                 // TODO(b/173532925) check the existing binder call latency chart
1916                                 // is good enough for us:
1917                                 // http://dashboards/view/_72c98f9a_91d9_41d4_ab9a_bc14f79742b4
1918                                 .setEstimatedBinderLatencyMillis(estimatedBinderLatencyMillis)
1919                                 .setNumOperationsSucceeded(operationSuccessCount)
1920                                 .setNumOperationsFailed(operationFailureCount)
1921                                 .build());
1922                     }
1923                 }
1924             });
1925             if (!callAccepted) {
1926                 logRateLimitedOrCallDeniedCallStats(callingPackageName, request.getDatabaseName(),
1927                         CallStats.CALL_TYPE_REMOVE_DOCUMENTS_BY_SEARCH, targetUser,
1928                         request.getBinderCallStartTimeMillis(), totalLatencyStartTimeMillis,
1929                         /* numOperations= */ 1, RESULT_RATE_LIMITED);
1930             }
1931         }
1932 
1933         @Override
getStorageInfo( @onNull GetStorageInfoAidlRequest request, @NonNull IAppSearchResultCallback callback)1934         public void getStorageInfo(
1935                 @NonNull GetStorageInfoAidlRequest request,
1936                 @NonNull IAppSearchResultCallback callback) {
1937             Objects.requireNonNull(request);
1938             Objects.requireNonNull(callback);
1939 
1940             long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
1941             UserHandle targetUser = mServiceImplHelper.verifyIncomingCallWithCallback(
1942                     request.getCallerAttributionSource(), request.getUserHandle(), callback);
1943             String callingPackageName = request.getCallerAttributionSource().getPackageName();
1944             if (targetUser == null) {
1945                 return;  // Verification failed; verifyIncomingCall triggered callback.
1946             }
1947             if (checkCallDenied(callingPackageName, request.getDatabaseName(),
1948                     CallStats.CALL_TYPE_GET_STORAGE_INFO, callback, targetUser,
1949                     request.getBinderCallStartTimeMillis(), totalLatencyStartTimeMillis,
1950                     /* numOperations= */ 1)) {
1951                 return;
1952             }
1953             boolean callAccepted = mServiceImplHelper.executeLambdaForUserAsync(targetUser,
1954                     callback, callingPackageName, CallStats.CALL_TYPE_GET_STORAGE_INFO, () -> {
1955                 @AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK;
1956                 AppSearchUserInstance instance = null;
1957                 int operationSuccessCount = 0;
1958                 int operationFailureCount = 0;
1959                 try {
1960                     instance = mAppSearchUserInstanceManager.getUserInstance(targetUser);
1961                     StorageInfo storageInfo = instance.getAppSearchImpl().getStorageInfoForDatabase(
1962                             callingPackageName, request.getDatabaseName());
1963                     ++operationSuccessCount;
1964                     invokeCallbackOnResult(
1965                             callback, AppSearchResultParcel.fromStorageInfo(storageInfo));
1966                 } catch (AppSearchException | RuntimeException e) {
1967                     ++operationFailureCount;
1968                     AppSearchResult<Void> failedResult = throwableToFailedResult(e);
1969                     statusCode = failedResult.getResultCode();
1970                     invokeCallbackOnResult(callback, AppSearchResultParcel.fromFailedResult(
1971                             failedResult));
1972                 } finally {
1973                     if (instance != null) {
1974                         int estimatedBinderLatencyMillis =
1975                                 2 * (int) (totalLatencyStartTimeMillis
1976                                         - request.getBinderCallStartTimeMillis());
1977                         int totalLatencyMillis =
1978                                 (int) (SystemClock.elapsedRealtime() - totalLatencyStartTimeMillis);
1979                         instance.getLogger().logStats(new CallStats.Builder()
1980                                 .setPackageName(callingPackageName)
1981                                 .setDatabase(request.getDatabaseName())
1982                                 .setStatusCode(statusCode)
1983                                 .setTotalLatencyMillis(totalLatencyMillis)
1984                                 .setCallType(CallStats.CALL_TYPE_GET_STORAGE_INFO)
1985                                 // TODO(b/173532925) check the existing binder call latency chart
1986                                 // is good enough for us:
1987                                 // http://dashboards/view/_72c98f9a_91d9_41d4_ab9a_bc14f79742b4
1988                                 .setEstimatedBinderLatencyMillis(estimatedBinderLatencyMillis)
1989                                 .setNumOperationsSucceeded(operationSuccessCount)
1990                                 .setNumOperationsFailed(operationFailureCount)
1991                                 .build());
1992                     }
1993                 }
1994             });
1995             if (!callAccepted) {
1996                 logRateLimitedOrCallDeniedCallStats(callingPackageName, request.getDatabaseName(),
1997                         CallStats.CALL_TYPE_GET_STORAGE_INFO, targetUser,
1998                         request.getBinderCallStartTimeMillis(), totalLatencyStartTimeMillis,
1999                         /* numOperations= */ 1, RESULT_RATE_LIMITED);
2000             }
2001         }
2002 
2003         @Override
persistToDisk(@onNull PersistToDiskAidlRequest request)2004         public void persistToDisk(@NonNull PersistToDiskAidlRequest request) {
2005             Objects.requireNonNull(request);
2006 
2007             long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
2008             try {
2009                 UserHandle targetUser = mServiceImplHelper.verifyIncomingCall(
2010                         request.getCallerAttributionSource(), request.getUserHandle());
2011                 String callingPackageName = request.getCallerAttributionSource().getPackageName();
2012                 if (checkCallDenied(callingPackageName, /* callingDatabaseName= */ null,
2013                         CallStats.CALL_TYPE_FLUSH, targetUser,
2014                         request.getBinderCallStartTimeMillis(), totalLatencyStartTimeMillis,
2015                         /* numOperations= */ 1)) {
2016                     return;
2017                 }
2018                 boolean callAccepted = mServiceImplHelper.executeLambdaForUserNoCallbackAsync(
2019                         targetUser, callingPackageName, CallStats.CALL_TYPE_FLUSH, () -> {
2020                     @AppSearchResult.ResultCode int statusCode = RESULT_OK;
2021                     AppSearchUserInstance instance = null;
2022                     int operationSuccessCount = 0;
2023                     int operationFailureCount = 0;
2024                     try {
2025                         instance = mAppSearchUserInstanceManager.getUserInstance(targetUser);
2026                         instance.getAppSearchImpl().persistToDisk(PersistType.Code.FULL);
2027                         ++operationSuccessCount;
2028                     } catch (AppSearchException | RuntimeException e) {
2029                         ++operationFailureCount;
2030                         statusCode = throwableToFailedResult(e).getResultCode();
2031                         // We will print two error messages if we rethrow, but I would rather keep
2032                         // this print statement here, so we know where the actual exception
2033                         // comes from.
2034                         Log.e(TAG, "Unable to persist the data to disk", e);
2035                         ExceptionUtil.handleException(e);
2036                     } finally {
2037                         if (instance != null) {
2038                             int estimatedBinderLatencyMillis =
2039                                     2 * (int) (totalLatencyStartTimeMillis
2040                                             - request.getBinderCallStartTimeMillis());
2041                             int totalLatencyMillis =
2042                                     (int) (SystemClock.elapsedRealtime()
2043                                             - totalLatencyStartTimeMillis);
2044                             instance.getLogger().logStats(new CallStats.Builder()
2045                                     .setPackageName(callingPackageName)
2046                                     .setStatusCode(statusCode)
2047                                     .setTotalLatencyMillis(totalLatencyMillis)
2048                                     .setCallType(CallStats.CALL_TYPE_FLUSH)
2049                                     // TODO(b/173532925) check the existing binder call latency
2050                                     // chart is good enough for us:
2051                                     // http://dashboards/view/_72c98f9a_91d9_41d4_ab9a_bc14f79742b4
2052                                     .setEstimatedBinderLatencyMillis(estimatedBinderLatencyMillis)
2053                                     .setNumOperationsSucceeded(operationSuccessCount)
2054                                     .setNumOperationsFailed(operationFailureCount)
2055                                     .build());
2056                         }
2057                     }
2058                 });
2059                 if (!callAccepted) {
2060                     logRateLimitedOrCallDeniedCallStats(
2061                             callingPackageName, /* callingDatabaseName= */ null,
2062                             CallStats.CALL_TYPE_FLUSH, targetUser,
2063                             request.getBinderCallStartTimeMillis(), totalLatencyStartTimeMillis,
2064                             /* numOperations= */ 1, RESULT_RATE_LIMITED);
2065                 }
2066             } catch (RuntimeException e) {
2067                 Log.e(TAG, "Unable to persist the data to disk", e);
2068                 ExceptionUtil.handleException(e);
2069             }
2070         }
2071 
2072         @Override
registerObserverCallback( @onNull RegisterObserverCallbackAidlRequest request, @NonNull IAppSearchObserverProxy observerProxyStub)2073         public AppSearchResultParcel<Void> registerObserverCallback(
2074                 @NonNull RegisterObserverCallbackAidlRequest request,
2075                 @NonNull IAppSearchObserverProxy observerProxyStub) {
2076             Objects.requireNonNull(request);
2077             Objects.requireNonNull(observerProxyStub);
2078 
2079             @AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK;
2080             AppSearchUserInstance instance = null;
2081             String callingPackageName = null;
2082             int operationSuccessCount = 0;
2083             int operationFailureCount = 0;
2084             long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
2085             // Note: registerObserverCallback is performed on the binder thread, unlike most
2086             // AppSearch APIs
2087             try {
2088                 UserHandle targetUser = mServiceImplHelper.verifyIncomingCall(
2089                         request.getCallerAttributionSource(), request.getUserHandle());
2090                 callingPackageName = request.getCallerAttributionSource().getPackageName();
2091                 if (checkCallDenied(callingPackageName, /* callingDatabaseName= */ null,
2092                         CallStats.CALL_TYPE_REGISTER_OBSERVER_CALLBACK, targetUser,
2093                         request.getBinderCallStartTimeMillis(), totalLatencyStartTimeMillis,
2094                         /* numOperations= */ 1)) {
2095                     return AppSearchResultParcel.fromFailedResult(AppSearchResult.newFailedResult(
2096                             RESULT_DENIED, null));
2097                 }
2098                 long callingIdentity = Binder.clearCallingIdentity();
2099                 try {
2100                     instance = mAppSearchUserInstanceManager.getUserInstance(targetUser);
2101 
2102                     // Prepare a new ObserverProxy linked to this binder.
2103                     AppSearchObserverProxy observerProxy =
2104                             new AppSearchObserverProxy(observerProxyStub);
2105 
2106                     // Watch for client disconnection, unregistering the observer if it happens.
2107                     final AppSearchUserInstance finalInstance = instance;
2108                     observerProxyStub.asBinder().linkToDeath(
2109                             () -> finalInstance.getAppSearchImpl()
2110                                     .unregisterObserverCallback(
2111                                             request.getTargetPackageName(), observerProxy),
2112                             /* flags= */ 0);
2113 
2114                     // Register the observer.
2115                     boolean callerHasSystemAccess = instance.getVisibilityChecker()
2116                             .doesCallerHaveSystemAccess(callingPackageName);
2117                     instance.getAppSearchImpl().registerObserverCallback(
2118                             new FrameworkCallerAccess(request.getCallerAttributionSource(),
2119                                     callerHasSystemAccess, /*isForEnterprise=*/ false),
2120                             request.getTargetPackageName(),
2121                             request.getObserverSpec(),
2122                             mExecutorManager.getOrCreateUserExecutor(targetUser),
2123                             new AppSearchObserverProxy(observerProxyStub));
2124                     ++operationSuccessCount;
2125                     return AppSearchResultParcel.fromVoid();
2126                 } finally {
2127                     Binder.restoreCallingIdentity(callingIdentity);
2128                 }
2129             } catch (RemoteException | RuntimeException e) {
2130                 ++operationFailureCount;
2131                 AppSearchResult<Void> failedResult = throwableToFailedResult(e);
2132                 statusCode = failedResult.getResultCode();
2133                 return AppSearchResultParcel.fromFailedResult(failedResult);
2134             } finally {
2135                 if (instance != null) {
2136                     int estimatedBinderLatencyMillis =
2137                             2 * (int) (totalLatencyStartTimeMillis
2138                                     - request.getBinderCallStartTimeMillis());
2139                     int totalLatencyMillis =
2140                             (int) (SystemClock.elapsedRealtime() - totalLatencyStartTimeMillis);
2141                     instance.getLogger().logStats(new CallStats.Builder()
2142                             .setPackageName(callingPackageName)
2143                             .setStatusCode(statusCode)
2144                             .setTotalLatencyMillis(totalLatencyMillis)
2145                             .setCallType(CallStats.CALL_TYPE_REGISTER_OBSERVER_CALLBACK)
2146                             // TODO(b/173532925) check the existing binder call latency chart
2147                             // is good enough for us:
2148                             // http://dashboards/view/_72c98f9a_91d9_41d4_ab9a_bc14f79742b4
2149                             .setEstimatedBinderLatencyMillis(estimatedBinderLatencyMillis)
2150                             .setNumOperationsSucceeded(operationSuccessCount)
2151                             .setNumOperationsFailed(operationFailureCount)
2152                             .build());
2153                 }
2154             }
2155         }
2156 
2157         @Override
unregisterObserverCallback( @onNull UnregisterObserverCallbackAidlRequest request, @NonNull IAppSearchObserverProxy observerProxyStub)2158         public AppSearchResultParcel<Void> unregisterObserverCallback(
2159                 @NonNull UnregisterObserverCallbackAidlRequest request,
2160                 @NonNull IAppSearchObserverProxy observerProxyStub) {
2161             Objects.requireNonNull(request);
2162             Objects.requireNonNull(observerProxyStub);
2163 
2164             @AppSearchResult.ResultCode int statusCode = AppSearchResult.RESULT_OK;
2165             AppSearchUserInstance instance = null;
2166             int operationSuccessCount = 0;
2167             int operationFailureCount = 0;
2168             long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
2169             // Note: unregisterObserverCallback is performed on the binder thread, unlike most
2170             // AppSearch APIs
2171             try {
2172                 UserHandle targetUser = mServiceImplHelper.verifyIncomingCall(
2173                         request.getCallerAttributionSource(), request.getUserHandle());
2174                 String callingPackageName = request.getCallerAttributionSource().getPackageName();
2175                 if (checkCallDenied(callingPackageName, /* callingDatabaseName= */ null,
2176                         CallStats.CALL_TYPE_UNREGISTER_OBSERVER_CALLBACK, targetUser,
2177                         request.getBinderCallStartTimeMillis(), totalLatencyStartTimeMillis,
2178                         /* numOperations= */ 1)) {
2179                     return AppSearchResultParcel.fromFailedResult(AppSearchResult.newFailedResult(
2180                             RESULT_DENIED, null));
2181                 }
2182                 long callingIdentity = Binder.clearCallingIdentity();
2183                 try {
2184                     instance = mAppSearchUserInstanceManager.getUserInstance(targetUser);
2185                     instance.getAppSearchImpl().unregisterObserverCallback(
2186                             request.getObservedPackage(),
2187                             new AppSearchObserverProxy(observerProxyStub));
2188                     ++operationSuccessCount;
2189                     return AppSearchResultParcel.fromVoid();
2190                 } finally {
2191                     Binder.restoreCallingIdentity(callingIdentity);
2192                 }
2193             } catch (RuntimeException e) {
2194                 ++operationFailureCount;
2195                 AppSearchResult<Void> failedResult = throwableToFailedResult(e);
2196                 statusCode = failedResult.getResultCode();
2197                 return AppSearchResultParcel.fromFailedResult(failedResult);
2198             } finally {
2199                 if (instance != null) {
2200                     int estimatedBinderLatencyMillis =
2201                             2 * (int) (totalLatencyStartTimeMillis
2202                                     - request.getBinderCallStartTimeMillis());
2203                     int totalLatencyMillis =
2204                             (int) (SystemClock.elapsedRealtime() - totalLatencyStartTimeMillis);
2205                     String callingPackageName = request.getCallerAttributionSource()
2206                             .getPackageName();
2207                     instance.getLogger().logStats(new CallStats.Builder()
2208                             .setPackageName(callingPackageName)
2209                             .setStatusCode(statusCode)
2210                             .setTotalLatencyMillis(totalLatencyMillis)
2211                             .setCallType(CallStats.CALL_TYPE_UNREGISTER_OBSERVER_CALLBACK)
2212                             // TODO(b/173532925) check the existing binder call latency chart
2213                             // is good enough for us:
2214                             // http://dashboards/view/_72c98f9a_91d9_41d4_ab9a_bc14f79742b4
2215                             .setEstimatedBinderLatencyMillis(estimatedBinderLatencyMillis)
2216                             .setNumOperationsSucceeded(operationSuccessCount)
2217                             .setNumOperationsFailed(operationFailureCount)
2218                             .build());
2219                 }
2220             }
2221         }
2222 
2223         @Override
initialize( @onNull InitializeAidlRequest request, @NonNull IAppSearchResultCallback callback)2224         public void initialize(
2225                 @NonNull InitializeAidlRequest request,
2226                 @NonNull IAppSearchResultCallback callback) {
2227             Objects.requireNonNull(request);
2228             Objects.requireNonNull(callback);
2229 
2230             long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
2231             UserHandle targetUser = mServiceImplHelper.verifyIncomingCallWithCallback(
2232                     request.getCallerAttributionSource(), request.getUserHandle(), callback);
2233             String callingPackageName = request.getCallerAttributionSource().getPackageName();
2234             if (targetUser == null) {
2235                 return;  // Verification failed; verifyIncomingCall triggered callback.
2236             }
2237             if (mAppSearchConfig.getCachedDenylist().checkDeniedPackage(callingPackageName,
2238                     CallStats.CALL_TYPE_INITIALIZE)) {
2239                 // Note: can't log CallStats here since UserInstance isn't guaranteed to (and most
2240                 // likely does not) exist
2241                 invokeCallbackOnResult(callback, AppSearchResultParcel.fromFailedResult(
2242                         AppSearchResult.newFailedResult(RESULT_DENIED, null)));
2243                 return;
2244             }
2245             mServiceImplHelper.executeLambdaForUserAsync(targetUser, callback, callingPackageName,
2246                     CallStats.CALL_TYPE_INITIALIZE, () -> {
2247                 @AppSearchResult.ResultCode int statusCode = RESULT_OK;
2248                 AppSearchUserInstance instance = null;
2249                 int operationSuccessCount = 0;
2250                 int operationFailureCount = 0;
2251                 try {
2252                     Context targetUserContext = mAppSearchEnvironment
2253                             .createContextAsUser(mContext, request.getUserHandle());
2254                     instance = mAppSearchUserInstanceManager.getOrCreateUserInstance(
2255                             targetUserContext,
2256                             targetUser,
2257                             mAppSearchConfig);
2258                     ++operationSuccessCount;
2259                     invokeCallbackOnResult(callback, AppSearchResultParcel.fromVoid());
2260                 } catch (AppSearchException | RuntimeException e) {
2261                     ++operationFailureCount;
2262                     AppSearchResult<Void> failedResult = throwableToFailedResult(e);
2263                     statusCode = failedResult.getResultCode();
2264                     invokeCallbackOnResult(callback, AppSearchResultParcel.fromFailedResult(
2265                             failedResult));
2266                 } finally {
2267                     if (instance != null) {
2268                         int estimatedBinderLatencyMillis =
2269                                 2 * (int) (totalLatencyStartTimeMillis
2270                                         - request.getBinderCallStartTimeMillis());
2271                         int totalLatencyMillis =
2272                                 (int) (SystemClock.elapsedRealtime() - totalLatencyStartTimeMillis);
2273                         instance.getLogger().logStats(new CallStats.Builder()
2274                                 .setPackageName(callingPackageName)
2275                                 .setStatusCode(statusCode)
2276                                 .setTotalLatencyMillis(totalLatencyMillis)
2277                                 .setCallType(CallStats.CALL_TYPE_INITIALIZE)
2278                                 // TODO(b/173532925) check the existing binder call latency chart
2279                                 // is good enough for us:
2280                                 // http://dashboards/view/_72c98f9a_91d9_41d4_ab9a_bc14f79742b4
2281                                 .setEstimatedBinderLatencyMillis(estimatedBinderLatencyMillis)
2282                                 .setNumOperationsSucceeded(operationSuccessCount)
2283                                 .setNumOperationsFailed(operationFailureCount)
2284                                 .build());
2285                     }
2286                 }
2287             });
2288         }
2289 
2290         @Override
executeAppFunction( @onNull ExecuteAppFunctionAidlRequest request, @NonNull IAppSearchResultCallback callback)2291         public void executeAppFunction(
2292                 @NonNull ExecuteAppFunctionAidlRequest request,
2293                 @NonNull IAppSearchResultCallback callback) {
2294             Objects.requireNonNull(request);
2295             Objects.requireNonNull(callback);
2296 
2297             long totalLatencyStartTimeMillis = SystemClock.elapsedRealtime();
2298 
2299             String callingPackageName = request.getCallerAttributionSource().getPackageName();
2300             UserHandle targetUser = mServiceImplHelper.verifyIncomingCallWithCallback(
2301                     request.getCallerAttributionSource(), request.getUserHandle(), callback);
2302             if (targetUser == null) {
2303                 return;  // Verification failed; verifyIncomingCall triggered callback.
2304             }
2305             if (checkCallDenied(
2306                     callingPackageName, /* databaseName= */ null,
2307                     CallStats.CALL_TYPE_EXECUTE_APP_FUNCTION, callback, targetUser,
2308                     request.getBinderCallStartTimeMillis(), totalLatencyStartTimeMillis,
2309                     /* numOperations= */ 1)) {
2310                 return;
2311             }
2312 
2313             // Log the stats as well whenever we invoke the AppSearchResultCallback.
2314             final SafeOneTimeAppSearchResultCallback safeCallback =
2315                     new SafeOneTimeAppSearchResultCallback(callback, result -> {
2316                         AppSearchUserInstance instance =
2317                                 mAppSearchUserInstanceManager.getUserInstance(targetUser);
2318                         int totalLatencyMillis =
2319                                 (int) (SystemClock.elapsedRealtime() - totalLatencyStartTimeMillis);
2320                         int estimatedBinderLatencyMillis =
2321                                 2 * (int) (totalLatencyStartTimeMillis
2322                                         - request.getBinderCallStartTimeMillis());
2323                         instance.getLogger().logStats(new CallStats.Builder()
2324                                 .setPackageName(callingPackageName)
2325                                 .setStatusCode(result.getResultCode())
2326                                 .setTotalLatencyMillis(totalLatencyMillis)
2327                                 .setCallType(CallStats.CALL_TYPE_EXECUTE_APP_FUNCTION)
2328                                 .setEstimatedBinderLatencyMillis(estimatedBinderLatencyMillis)
2329                                 .build());
2330                     });
2331 
2332             // TODO(b/327134039): Add a new policy for this in W timeframe.
2333             if (mServiceImplHelper.isUserOrganizationManaged(targetUser)) {
2334                 safeCallback.onFailedResult(AppSearchResult.newFailedResult(
2335                         RESULT_SECURITY_ERROR,
2336                         "Cannot run on a device with a device owner or from the managed profile."));
2337                 return;
2338             }
2339 
2340             String targetPackageName = request.getClientRequest().getTargetPackageName();
2341             if (TextUtils.isEmpty(targetPackageName)) {
2342                 safeCallback.onFailedResult(AppSearchResult.newFailedResult(
2343                         RESULT_INVALID_ARGUMENT,
2344                         "targetPackageName cannot be empty."));
2345                 return;
2346             }
2347             if (!verifyExecuteAppFunctionCaller(
2348                     callingPackageName,
2349                     targetPackageName,
2350                     targetUser)) {
2351                 safeCallback.onFailedResult(AppSearchResult.newFailedResult(
2352                         RESULT_SECURITY_ERROR,
2353                         callingPackageName + " is not allowed to call executeAppFunction"));
2354                 return;
2355             }
2356 
2357             boolean callAccepted = mServiceImplHelper.executeLambdaForUserAsync(
2358                     targetUser, callback, callingPackageName,
2359                     CallStats.CALL_TYPE_EXECUTE_APP_FUNCTION,
2360                     () -> executeAppFunctionUnchecked(
2361                             request.getClientRequest(),
2362                             targetUser,
2363                             safeCallback));
2364             if (!callAccepted) {
2365                 logRateLimitedOrCallDeniedCallStats(callingPackageName, /* databaseName= */ null,
2366                         CallStats.CALL_TYPE_EXECUTE_APP_FUNCTION, targetUser,
2367                         request.getBinderCallStartTimeMillis(), totalLatencyStartTimeMillis,
2368                         /*numOperations=*/ 1, RESULT_RATE_LIMITED);
2369             }
2370         }
2371 
2372         /**
2373          * The same as {@link #executeAppFunction}, except this is without the caller check.
2374          * This method runs on the user-local thread pool.
2375          */
2376         @WorkerThread
executeAppFunctionUnchecked( @onNull ExecuteAppFunctionRequest request, @NonNull UserHandle userHandle, @NonNull SafeOneTimeAppSearchResultCallback safeCallback)2377         private void executeAppFunctionUnchecked(
2378                 @NonNull ExecuteAppFunctionRequest request,
2379                 @NonNull UserHandle userHandle,
2380                 @NonNull SafeOneTimeAppSearchResultCallback safeCallback) {
2381             Intent serviceIntent = new Intent(AppFunctionService.SERVICE_INTERFACE);
2382             serviceIntent.setPackage(request.getTargetPackageName());
2383 
2384             Context userContext = mAppSearchEnvironment.createContextAsUser(mContext, userHandle);
2385             ResolveInfo resolveInfo = userContext.getPackageManager()
2386                     .resolveService(serviceIntent, 0);
2387             if (resolveInfo == null || resolveInfo.serviceInfo == null) {
2388                 safeCallback.onFailedResult(AppSearchResult.newFailedResult(
2389                         RESULT_NOT_FOUND, "Cannot find the target service."));
2390                 return;
2391             }
2392             ServiceInfo serviceInfo = resolveInfo.serviceInfo;
2393             if (!PERMISSION_BIND_APP_FUNCTION_SERVICE.equals(serviceInfo.permission)) {
2394                 safeCallback.onFailedResult(AppSearchResult.newFailedResult(
2395                         RESULT_NOT_FOUND,
2396                         "Failed to find a valid target service. The resolved service is missing "
2397                                 + "the BIND_APP_FUNCTION_SERVICE permission."));
2398                 return;
2399             }
2400             serviceIntent.setComponent(
2401                     new ComponentName(serviceInfo.packageName, serviceInfo.name));
2402 
2403             if (request.getSha256Certificate() != null) {
2404                 if (!PackageManagerUtil.hasSigningCertificate(
2405                         mContext, request.getTargetPackageName(), request.getSha256Certificate())) {
2406                     safeCallback.onFailedResult(
2407                             AppSearchResult.newFailedResult(
2408                                     RESULT_NOT_FOUND, "Cannot find the target service"));
2409                     return;
2410                 }
2411             }
2412 
2413             boolean bindServiceResult = mAppFunctionServiceCallHelper.runServiceCall(
2414                     serviceIntent,
2415                     Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS | Context.BIND_AUTO_CREATE,
2416                     mAppSearchConfig.getAppFunctionCallTimeoutMillis(),
2417                     userHandle,
2418                     new ServiceCallHelper.RunServiceCallCallback<>() {
2419                         @Override
2420                         public void onServiceConnected(
2421                                 @NonNull IAppFunctionService service,
2422                                 @NonNull ServiceUsageCompleteListener completeListener) {
2423                             try {
2424                                 service.executeAppFunction(
2425                                         request,
2426                                         new IAppSearchResultCallback.Stub() {
2427                                             @Override
2428                                             public void onResult(
2429                                                     AppSearchResultParcel resultParcel) {
2430                                                 safeCallback.onResult(resultParcel);
2431                                                 completeListener.onCompleted();
2432                                             }
2433                                         });
2434                             } catch (Exception e) {
2435                                 safeCallback.onFailedResult(AppSearchResult
2436                                         .throwableToFailedResult(e));
2437                                 completeListener.onCompleted();
2438                             }
2439                         }
2440 
2441                         @Override
2442                         public void onFailedToConnect() {
2443                             safeCallback.onFailedResult(
2444                                     AppSearchResult.newFailedResult(RESULT_INTERNAL_ERROR, null));
2445                         }
2446 
2447                         @Override
2448                         public void onTimedOut() {
2449                             safeCallback.onFailedResult(
2450                                     AppSearchResult.newFailedResult(RESULT_TIMED_OUT, null));
2451                         }
2452                     });
2453             if (!bindServiceResult) {
2454                 safeCallback.onFailedResult(AppSearchResult.newFailedResult(
2455                         RESULT_INTERNAL_ERROR, "Failed to bind the target service."));
2456             }
2457         }
2458 
2459         /**
2460          * Determines whether the caller is authorized to execute an app function via
2461          * {@link #executeAppFunction}.
2462          * <p>
2463          * Authorization is granted under the following conditions:
2464          * <ul>
2465          *     <li>The caller is the same app that owns the target function.</li>
2466          *     <li>The caller possesses the SYSTEM_UI_INTELLIGENCE role for the target user. </li>
2467          * </ul>
2468          *
2469          * @param callingPackage The validated package name of the calling app.
2470          * @param targetPackage  The package name of the target app.
2471          * @param targetUser     The target user.
2472          * @return               {@code true} if the caller is authorized, {@code false} otherwise.
2473          */
verifyExecuteAppFunctionCaller( @onNull String callingPackage, @NonNull String targetPackage, @NonNull UserHandle targetUser)2474         private boolean verifyExecuteAppFunctionCaller(
2475                 @NonNull String callingPackage,
2476                 @NonNull String targetPackage,
2477                 @NonNull UserHandle targetUser) {
2478             // While adding new system role-based permissions through mainline updates is possible,
2479             // granting them to system apps in previous android versions is not. System apps must
2480             // request permissions in their prebuilt APKs included in the system image. We cannot
2481             // modify prebuilts in older images anymore.
2482             // TODO(b/327134039): Enforce permission checking for Android V+ or W+, depending on
2483             // whether the new prebuilt can be included in the system image on time.
2484             if (callingPackage.equals(targetPackage)) {
2485                 return true;
2486             }
2487             long originalToken = Binder.clearCallingIdentity();
2488             try {
2489                 List<String> systemUiIntelligencePackages =
2490                         mRoleManager.getRoleHoldersAsUser(SYSTEM_UI_INTELLIGENCE, targetUser);
2491                 return systemUiIntelligencePackages.contains(callingPackage);
2492             } finally {
2493                 Binder.restoreCallingIdentity(originalToken);
2494             }
2495         }
2496 
2497         @BinderThread
dumpContactsIndexer(@onNull PrintWriter pw, boolean verbose)2498         private void dumpContactsIndexer(@NonNull PrintWriter pw, boolean verbose) {
2499             Objects.requireNonNull(pw);
2500             UserHandle currentUser = UserHandle.getUserHandleForUid(Binder.getCallingUid());
2501             try {
2502                 pw.println("ContactsIndexer stats for " + currentUser);
2503                 mLifecycle.dumpContactsIndexerForUser(currentUser, pw, verbose);
2504             } catch (Exception e) {
2505                 String errorMessage =
2506                         "Unable to dump the internal contacts indexer state for the user: "
2507                                 + currentUser;
2508                 Log.e(TAG, errorMessage, e);
2509                 pw.println(errorMessage);
2510             }
2511         }
2512 
2513         @BinderThread
dumpAppSearch(@onNull PrintWriter pw, boolean verbose)2514         private void dumpAppSearch(@NonNull PrintWriter pw, boolean verbose) {
2515             Objects.requireNonNull(pw);
2516 
2517             UserHandle currentUser = UserHandle.getUserHandleForUid(Binder.getCallingUid());
2518             try {
2519                 AppSearchUserInstance instance = mAppSearchUserInstanceManager.getUserInstance(
2520                         currentUser);
2521 
2522                 // Print out the recorded last called APIs.
2523                 List<ApiCallRecord> lastCalledApis = instance.getLogger().getLastCalledApis();
2524                 if (!lastCalledApis.isEmpty()) {
2525                     pw.println("Last Called APIs:");
2526                     for (int i = 0; i < lastCalledApis.size(); i++) {
2527                         pw.println(lastCalledApis.get(i));
2528                     }
2529                     pw.println();
2530                 }
2531 
2532                 DebugInfoProto debugInfo = instance.getAppSearchImpl().getRawDebugInfoProto(
2533                         verbose ? DebugInfoVerbosity.Code.DETAILED
2534                                 : DebugInfoVerbosity.Code.BASIC);
2535                 // TODO(b/229778472) Consider showing the original names of namespaces and types
2536                 //  for a specific package if the package name is passed as a parameter from users.
2537                 debugInfo = AdbDumpUtil.desensitizeDebugInfo(debugInfo);
2538                 pw.println(debugInfo.getIndexInfo().getIndexStorageInfo());
2539                 pw.println();
2540                 pw.println("lite_index_info:");
2541                 pw.println(debugInfo.getIndexInfo().getLiteIndexInfo());
2542                 pw.println();
2543                 pw.println("main_index_info:");
2544                 pw.println(debugInfo.getIndexInfo().getMainIndexInfo());
2545                 pw.println();
2546                 pw.println(debugInfo.getDocumentInfo());
2547                 pw.println();
2548                 pw.println(debugInfo.getSchemaInfo());
2549             } catch (Exception e) {
2550                 String errorMessage =
2551                         "Unable to dump the internal state for the user: " + currentUser;
2552                 Log.e(TAG, errorMessage, e);
2553                 pw.println(errorMessage);
2554             }
2555         }
2556 
2557         @Override
dump(FileDescriptor fd, PrintWriter pw, String[] args)2558         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
2559             if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
2560                     != PackageManager.PERMISSION_GRANTED) {
2561                 pw.println("Permission Denial: can't dump AppSearchManagerService from pid="
2562                         + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
2563                         + " due to missing android.permission.DUMP permission");
2564                 return;
2565             }
2566             boolean verbose = false;
2567             if (args != null) {
2568                 for (int i = 0; i < args.length; i++) {
2569                     String arg = args[i];
2570                     if (Objects.equals(arg, "-h")) {
2571                         pw.println(
2572                                 "Dumps the internal state of AppSearch platform storage and "
2573                                         + "AppSearch Contacts Indexer for the current user.");
2574                         pw.println("-v, verbose mode");
2575                         return;
2576                     } else if (Objects.equals(arg, "-v") || Objects.equals(arg, "-a")) {
2577                         // "-a" is included when adb dumps all services e.g. in adb bugreport so we
2578                         // want to run in verbose mode when this happens
2579                         verbose = true;
2580                     }
2581                 }
2582             }
2583             dumpAppSearch(pw, verbose);
2584             dumpContactsIndexer(pw, verbose);
2585         }
2586     }
2587 
2588     private class AppSearchStorageStatsAugmenter implements StorageStatsAugmenter {
2589         @Override
augmentStatsForPackageForUser( @onNull PackageStats stats, @NonNull String packageName, @NonNull UserHandle userHandle, boolean canCallerAccessAllStats)2590         public void augmentStatsForPackageForUser(
2591                 @NonNull PackageStats stats,
2592                 @NonNull String packageName,
2593                 @NonNull UserHandle userHandle,
2594                 boolean canCallerAccessAllStats) {
2595             Objects.requireNonNull(stats);
2596             Objects.requireNonNull(packageName);
2597             Objects.requireNonNull(userHandle);
2598 
2599             try {
2600                 mServiceImplHelper.verifyUserUnlocked(userHandle);
2601                 AppSearchUserInstance instance =
2602                         mAppSearchUserInstanceManager.getUserInstanceOrNull(userHandle);
2603                 if (instance == null) {
2604                     // augment storage info from file
2605                     Context userContext = mAppSearchEnvironment
2606                             .createContextAsUser(mContext, userHandle);
2607                     UserStorageInfo userStorageInfo =
2608                             mAppSearchUserInstanceManager.getOrCreateUserStorageInfoInstance(
2609                                     userContext, userHandle);
2610                     stats.dataSize +=
2611                             userStorageInfo.getSizeBytesForPackage(packageName);
2612                 } else {
2613                     stats.dataSize += instance.getAppSearchImpl()
2614                             .getStorageInfoForPackage(packageName).getSizeBytes();
2615                 }
2616             } catch (AppSearchException | RuntimeException e) {
2617                 Log.e(
2618                         TAG,
2619                         "Unable to augment storage stats for "
2620                                 + userHandle
2621                                 + " packageName "
2622                                 + packageName,
2623                         e);
2624                 ExceptionUtil.handleException(e);
2625             }
2626         }
2627 
2628         @Override
augmentStatsForUid( @onNull PackageStats stats, int uid, boolean canCallerAccessAllStats)2629         public void augmentStatsForUid(
2630                 @NonNull PackageStats stats, int uid, boolean canCallerAccessAllStats) {
2631             Objects.requireNonNull(stats);
2632 
2633             UserHandle userHandle = UserHandle.getUserHandleForUid(uid);
2634             try {
2635                 mServiceImplHelper.verifyUserUnlocked(userHandle);
2636                 String[] packagesForUid = mPackageManager.getPackagesForUid(uid);
2637                 if (packagesForUid == null) {
2638                     return;
2639                 }
2640                 AppSearchUserInstance instance =
2641                         mAppSearchUserInstanceManager.getUserInstanceOrNull(userHandle);
2642                 if (instance == null) {
2643                     // augment storage info from file
2644                     Context userContext = mAppSearchEnvironment
2645                             .createContextAsUser(mContext, userHandle);
2646                     UserStorageInfo userStorageInfo =
2647                             mAppSearchUserInstanceManager.getOrCreateUserStorageInfoInstance(
2648                                     userContext, userHandle);
2649                     for (int i = 0; i < packagesForUid.length; i++) {
2650                         stats.dataSize += userStorageInfo.getSizeBytesForPackage(
2651                                 packagesForUid[i]);
2652                     }
2653                 } else {
2654                     for (int i = 0; i < packagesForUid.length; i++) {
2655                         stats.dataSize += instance.getAppSearchImpl()
2656                                 .getStorageInfoForPackage(packagesForUid[i]).getSizeBytes();
2657                     }
2658                 }
2659             } catch (AppSearchException | RuntimeException e) {
2660                 Log.e(TAG, "Unable to augment storage stats for uid " + uid, e);
2661                 ExceptionUtil.handleException(e);
2662             }
2663         }
2664 
2665         @Override
augmentStatsForUser( @onNull PackageStats stats, @NonNull UserHandle userHandle)2666         public void augmentStatsForUser(
2667                 @NonNull PackageStats stats, @NonNull UserHandle userHandle) {
2668             // TODO(b/179160886): this implementation could incur many jni calls and a lot of
2669             //  in-memory processing from getStorageInfoForPackage. Instead, we can just compute the
2670             //  size of the icing dir (or use the overall StorageInfo without interpolating it).
2671             Objects.requireNonNull(stats);
2672             Objects.requireNonNull(userHandle);
2673 
2674             try {
2675                 mServiceImplHelper.verifyUserUnlocked(userHandle);
2676                 AppSearchUserInstance instance =
2677                         mAppSearchUserInstanceManager.getUserInstanceOrNull(userHandle);
2678                 if (instance == null) {
2679                     // augment storage info from file
2680                     Context userContext = mAppSearchEnvironment
2681                             .createContextAsUser(mContext, userHandle);
2682                     UserStorageInfo userStorageInfo =
2683                             mAppSearchUserInstanceManager.getOrCreateUserStorageInfoInstance(
2684                                     userContext, userHandle);
2685                     stats.dataSize += userStorageInfo.getTotalSizeBytes();
2686                 } else {
2687                     List<PackageInfo> packagesForUser = mPackageManager.getInstalledPackagesAsUser(
2688                             /* flags= */ 0, userHandle.getIdentifier());
2689                     if (packagesForUser != null) {
2690                         for (int i = 0; i < packagesForUser.size(); i++) {
2691                             String packageName = packagesForUser.get(i).packageName;
2692                             stats.dataSize += instance.getAppSearchImpl()
2693                                     .getStorageInfoForPackage(packageName).getSizeBytes();
2694                         }
2695                     }
2696                 }
2697             } catch (AppSearchException | RuntimeException e) {
2698                 Log.e(TAG, "Unable to augment storage stats for " + userHandle, e);
2699                 ExceptionUtil.handleException(e);
2700             }
2701         }
2702     }
2703 
2704     /**
2705      * Dispatches change notifications if there are any to dispatch.
2706      *
2707      * <p>This method is async; notifications are dispatched onto their own registered executors.
2708      *
2709      * <p>IMPORTANT: You must always call this within the background task that contains the
2710      * operation that mutated the index. If you called it outside of that task, it could start
2711      * before the task completes, causing notifications to be missed.
2712      */
2713     @WorkerThread
dispatchChangeNotifications(@onNull AppSearchUserInstance instance)2714     private void dispatchChangeNotifications(@NonNull AppSearchUserInstance instance) {
2715         instance.getAppSearchImpl().dispatchAndClearChangeNotifications();
2716     }
2717 
2718     @WorkerThread
checkForOptimize( @onNull UserHandle targetUser, @NonNull AppSearchUserInstance instance, int mutateBatchSize)2719     private void checkForOptimize(
2720             @NonNull UserHandle targetUser,
2721             @NonNull AppSearchUserInstance instance,
2722             int mutateBatchSize) {
2723         if (mServiceImplHelper.isUserLocked(targetUser)) {
2724             // We shouldn't schedule any task to locked user.
2725             return;
2726         }
2727         mExecutorManager.getOrCreateUserExecutor(targetUser).execute(() -> {
2728             long totalLatencyStartMillis = SystemClock.elapsedRealtime();
2729             OptimizeStats.Builder builder = new OptimizeStats.Builder();
2730             try {
2731                 instance.getAppSearchImpl().checkForOptimize(mutateBatchSize, builder);
2732             } catch (Exception e) {
2733                 Log.w(TAG, "Error occurred when check for optimize", e);
2734             } finally {
2735                 OptimizeStats oStats = builder
2736                         .setTotalLatencyMillis(
2737                                 (int) (SystemClock.elapsedRealtime() - totalLatencyStartMillis))
2738                         .build();
2739                 if (oStats.getOriginalDocumentCount() > 0) {
2740                     // see if optimize has been run by checking originalDocumentCount
2741                     instance.getLogger().logStats(oStats);
2742                 }
2743             }
2744         });
2745     }
2746 
2747     @WorkerThread
checkForOptimize( @onNull UserHandle targetUser, @NonNull AppSearchUserInstance instance)2748     private void checkForOptimize(
2749             @NonNull UserHandle targetUser,
2750             @NonNull AppSearchUserInstance instance) {
2751         if (mServiceImplHelper.isUserLocked(targetUser)) {
2752             // We shouldn't schedule any task to locked user.
2753             return;
2754         }
2755         mExecutorManager.getOrCreateUserExecutor(targetUser).execute(() -> {
2756             long totalLatencyStartMillis = SystemClock.elapsedRealtime();
2757             OptimizeStats.Builder builder = new OptimizeStats.Builder();
2758             try {
2759                 instance.getAppSearchImpl().checkForOptimize(builder);
2760             } catch (Exception e) {
2761                 Log.w(TAG, "Error occurred when check for optimize", e);
2762             } finally {
2763                 OptimizeStats oStats = builder
2764                         .setTotalLatencyMillis(
2765                                 (int) (SystemClock.elapsedRealtime() - totalLatencyStartMillis))
2766                         .build();
2767                 if (oStats.getOriginalDocumentCount() > 0) {
2768                     // see if optimize has been run by checking originalDocumentCount
2769                     instance.getLogger().logStats(oStats);
2770                 }
2771             }
2772         });
2773     }
2774 
2775     /**
2776      * An API call is considered global if the calling package and target package names do not
2777      * match.
2778      * <p>
2779      * Enterprise session calls do not necessarily have access to same-package data; therefore, even
2780      * if the calling and target packages are the same, enterprise session calls must always be
2781      * global to go through the proper visibility checks. (Enterprise session calls are also always
2782      * considered global for CallStats logging.)
2783      */
isGlobalCall(@onNull String callingPackageName, @NonNull String targetPackageName, boolean isForEnterprise)2784     private boolean isGlobalCall(@NonNull String callingPackageName,
2785             @NonNull String targetPackageName, boolean isForEnterprise) {
2786         return !callingPackageName.equals(targetPackageName) || isForEnterprise;
2787     }
2788 
2789     /**
2790      * Logs rate-limited or denied calls to CallStats.
2791      */
logRateLimitedOrCallDeniedCallStats(@onNull String callingPackageName, @Nullable String callingDatabaseName, @CallStats.CallType int apiType, @NonNull UserHandle targetUser, long binderCallStartTimeMillis, long totalLatencyStartTimeMillis, int numOperations, @AppSearchResult.ResultCode int statusCode)2792     private void logRateLimitedOrCallDeniedCallStats(@NonNull String callingPackageName,
2793             @Nullable String callingDatabaseName, @CallStats.CallType int apiType,
2794             @NonNull UserHandle targetUser, long binderCallStartTimeMillis,
2795             long totalLatencyStartTimeMillis, int numOperations,
2796             @AppSearchResult.ResultCode int statusCode) {
2797         Objects.requireNonNull(callingPackageName);
2798         Objects.requireNonNull(targetUser);
2799         int estimatedBinderLatencyMillis =
2800                 2 * (int) (totalLatencyStartTimeMillis - binderCallStartTimeMillis);
2801         int totalLatencyMillis =
2802                 (int) (SystemClock.elapsedRealtime() - totalLatencyStartTimeMillis);
2803         mAppSearchUserInstanceManager.getUserInstance(targetUser).getLogger().logStats(
2804                 new CallStats.Builder()
2805                         .setPackageName(callingPackageName)
2806                         .setDatabase(callingDatabaseName)
2807                         .setStatusCode(statusCode)
2808                         .setTotalLatencyMillis(totalLatencyMillis)
2809                         .setCallType(apiType)
2810                         .setEstimatedBinderLatencyMillis(estimatedBinderLatencyMillis)
2811                         .setNumOperationsFailed(numOperations)
2812                         .build());
2813     }
2814 
2815     /**
2816      * Checks if an API call for a given calling package and calling database should be denied
2817      * according to the denylist. If the call is denied, also logs the denial through CallStats.
2818      *
2819      * @return true if the given api call should be denied for the given calling package and calling
2820      * database; otherwise false
2821      */
checkCallDenied(@onNull String callingPackageName, @Nullable String callingDatabaseName, @CallStats.CallType int apiType, @NonNull UserHandle targetUser, long binderCallStartTimeMillis, long totalLatencyStartTimeMillis, int numOperations)2822     private boolean checkCallDenied(@NonNull String callingPackageName,
2823             @Nullable String callingDatabaseName, @CallStats.CallType int apiType,
2824             @NonNull UserHandle targetUser, long binderCallStartTimeMillis,
2825             long totalLatencyStartTimeMillis, int numOperations) {
2826         Denylist denylist = mAppSearchConfig.getCachedDenylist();
2827         boolean denied = callingDatabaseName == null ? denylist.checkDeniedPackage(
2828                 callingPackageName, apiType) : denylist.checkDeniedPackageDatabase(
2829                 callingPackageName, callingDatabaseName, apiType);
2830         if (denied) {
2831             logRateLimitedOrCallDeniedCallStats(callingPackageName, callingDatabaseName, apiType,
2832                     targetUser, binderCallStartTimeMillis, totalLatencyStartTimeMillis,
2833                     numOperations, RESULT_DENIED);
2834         }
2835         return denied;
2836     }
2837 
2838     /**
2839      * Checks if an API call for a given calling package and calling database should be denied
2840      * according to the denylist. If the call is denied, also logs the denial through CallStats and
2841      * invokes the given {@link IAppSearchResultCallback} with a failed result.
2842      *
2843      * @return true if the given api call should be denied for the given calling package and calling
2844      * database; otherwise false
2845      */
checkCallDenied(@onNull String callingPackageName, @Nullable String callingDatabaseName, @CallStats.CallType int apiType, @NonNull IAppSearchResultCallback callback, @NonNull UserHandle targetUser, long binderCallStartTimeMillis, long totalLatencyStartTimeMillis, int numOperations)2846     private boolean checkCallDenied(@NonNull String callingPackageName,
2847             @Nullable String callingDatabaseName, @CallStats.CallType int apiType,
2848             @NonNull IAppSearchResultCallback callback, @NonNull UserHandle targetUser,
2849             long binderCallStartTimeMillis, long totalLatencyStartTimeMillis, int numOperations) {
2850         if (checkCallDenied(callingPackageName, callingDatabaseName, apiType, targetUser,
2851                 binderCallStartTimeMillis, totalLatencyStartTimeMillis, numOperations)) {
2852             invokeCallbackOnResult(callback, AppSearchResultParcel.fromFailedResult(
2853                     AppSearchResult.newFailedResult(RESULT_DENIED, null)));
2854             return true;
2855         }
2856         return false;
2857     }
2858 
2859     /**
2860      * Checks if an API call for a given calling package and calling database should be denied
2861      * according to the denylist. If the call is denied, also logs the denial through CallStats and
2862      * invokes the given {@link IAppSearchBatchResultCallback} with a failed result.
2863      *
2864      * @return true if the given api call should be denied for the given calling package and calling
2865      * database; otherwise false
2866      */
checkCallDenied(@onNull String callingPackageName, @Nullable String callingDatabaseName, @CallStats.CallType int apiType, @NonNull IAppSearchBatchResultCallback callback, @NonNull UserHandle targetUser, long binderCallStartTimeMillis, long totalLatencyStartTimeMillis, int numOperations)2867     private boolean checkCallDenied(@NonNull String callingPackageName,
2868             @Nullable String callingDatabaseName, @CallStats.CallType int apiType,
2869             @NonNull IAppSearchBatchResultCallback callback, @NonNull UserHandle targetUser,
2870             long binderCallStartTimeMillis, long totalLatencyStartTimeMillis, int numOperations) {
2871         if (checkCallDenied(callingPackageName, callingDatabaseName, apiType, targetUser,
2872                 binderCallStartTimeMillis, totalLatencyStartTimeMillis, numOperations)) {
2873             invokeCallbackOnError(callback, AppSearchResult.newFailedResult(RESULT_DENIED, null));
2874             return true;
2875         }
2876         return false;
2877     }
2878 }
2879