• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.appfunctions;
18 
19 import static android.app.appfunctions.AppFunctionRuntimeMetadata.APP_FUNCTION_RUNTIME_METADATA_DB;
20 import static android.app.appfunctions.AppFunctionRuntimeMetadata.APP_FUNCTION_RUNTIME_NAMESPACE;
21 
22 import static com.android.server.appfunctions.AppFunctionExecutors.THREAD_POOL_EXECUTOR;
23 import static com.android.server.appfunctions.CallerValidator.CAN_EXECUTE_APP_FUNCTIONS_ALLOWED_HAS_PERMISSION;
24 import static com.android.server.appfunctions.CallerValidator.CAN_EXECUTE_APP_FUNCTIONS_DENIED;
25 
26 import android.annotation.NonNull;
27 import android.annotation.Nullable;
28 import android.annotation.WorkerThread;
29 import android.app.appfunctions.AppFunctionException;
30 import android.app.appfunctions.AppFunctionManager;
31 import android.app.appfunctions.AppFunctionManagerHelper;
32 import android.app.appfunctions.AppFunctionManagerHelper.AppFunctionNotFoundException;
33 import android.app.appfunctions.AppFunctionRuntimeMetadata;
34 import android.app.appfunctions.AppFunctionStaticMetadataHelper;
35 import android.app.appfunctions.ExecuteAppFunctionAidlRequest;
36 import android.app.appfunctions.ExecuteAppFunctionResponse;
37 import android.app.appfunctions.IAppFunctionEnabledCallback;
38 import android.app.appfunctions.IAppFunctionManager;
39 import android.app.appfunctions.IAppFunctionService;
40 import android.app.appfunctions.ICancellationCallback;
41 import android.app.appfunctions.IExecuteAppFunctionCallback;
42 import android.app.appfunctions.SafeOneTimeExecuteAppFunctionCallback;
43 import android.app.appsearch.AppSearchBatchResult;
44 import android.app.appsearch.AppSearchManager;
45 import android.app.appsearch.AppSearchManager.SearchContext;
46 import android.app.appsearch.AppSearchResult;
47 import android.app.appsearch.GenericDocument;
48 import android.app.appsearch.GetByDocumentIdRequest;
49 import android.app.appsearch.PutDocumentsRequest;
50 import android.app.appsearch.observer.DocumentChangeInfo;
51 import android.app.appsearch.observer.ObserverCallback;
52 import android.app.appsearch.observer.ObserverSpec;
53 import android.app.appsearch.observer.SchemaChangeInfo;
54 import android.content.Context;
55 import android.content.Intent;
56 import android.content.pm.PackageInfo;
57 import android.content.pm.PackageManager;
58 import android.content.pm.PackageManagerInternal;
59 import android.content.pm.SigningInfo;
60 import android.os.Binder;
61 import android.os.CancellationSignal;
62 import android.os.IBinder;
63 import android.os.ICancellationSignal;
64 import android.os.OutcomeReceiver;
65 import android.os.ParcelableException;
66 import android.os.RemoteException;
67 import android.os.UserHandle;
68 import android.text.TextUtils;
69 import android.util.Slog;
70 
71 import com.android.internal.annotations.GuardedBy;
72 import com.android.internal.annotations.VisibleForTesting;
73 import com.android.internal.infra.AndroidFuture;
74 import com.android.internal.util.DumpUtils;
75 import com.android.server.SystemService.TargetUser;
76 
77 import java.io.FileDescriptor;
78 import java.io.PrintWriter;
79 import java.util.Collections;
80 import java.util.Map;
81 import java.util.Objects;
82 import java.util.WeakHashMap;
83 import java.util.concurrent.CompletionException;
84 import java.util.concurrent.Executor;
85 
86 /** Implementation of the AppFunctionManagerService. */
87 public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub {
88     private static final String TAG = AppFunctionManagerServiceImpl.class.getSimpleName();
89 
90     private final RemoteServiceCaller<IAppFunctionService> mRemoteServiceCaller;
91     private final CallerValidator mCallerValidator;
92     private final ServiceHelper mInternalServiceHelper;
93     private final ServiceConfig mServiceConfig;
94     private final Context mContext;
95     private final Map<String, Object> mLocks = new WeakHashMap<>();
96     private final AppFunctionsLoggerWrapper mLoggerWrapper;
97     private final PackageManagerInternal mPackageManagerInternal;
98 
AppFunctionManagerServiceImpl( @onNull Context context, @NonNull PackageManagerInternal packageManagerInternal)99     public AppFunctionManagerServiceImpl(
100             @NonNull Context context, @NonNull PackageManagerInternal packageManagerInternal) {
101         this(
102                 context,
103                 new RemoteServiceCallerImpl<>(
104                         context, IAppFunctionService.Stub::asInterface, THREAD_POOL_EXECUTOR),
105                 new CallerValidatorImpl(context),
106                 new ServiceHelperImpl(context),
107                 new ServiceConfigImpl(),
108                 new AppFunctionsLoggerWrapper(context),
109                 packageManagerInternal);
110     }
111 
112     @VisibleForTesting
AppFunctionManagerServiceImpl( Context context, RemoteServiceCaller<IAppFunctionService> remoteServiceCaller, CallerValidator callerValidator, ServiceHelper appFunctionInternalServiceHelper, ServiceConfig serviceConfig, AppFunctionsLoggerWrapper loggerWrapper, PackageManagerInternal packageManagerInternal)113     AppFunctionManagerServiceImpl(
114             Context context,
115             RemoteServiceCaller<IAppFunctionService> remoteServiceCaller,
116             CallerValidator callerValidator,
117             ServiceHelper appFunctionInternalServiceHelper,
118             ServiceConfig serviceConfig,
119             AppFunctionsLoggerWrapper loggerWrapper,
120             PackageManagerInternal packageManagerInternal) {
121         mContext = Objects.requireNonNull(context);
122         mRemoteServiceCaller = Objects.requireNonNull(remoteServiceCaller);
123         mCallerValidator = Objects.requireNonNull(callerValidator);
124         mInternalServiceHelper = Objects.requireNonNull(appFunctionInternalServiceHelper);
125         mServiceConfig = serviceConfig;
126         mLoggerWrapper = loggerWrapper;
127         mPackageManagerInternal = Objects.requireNonNull(packageManagerInternal);
128     }
129 
130     /** Called when the user is unlocked. */
onUserUnlocked(TargetUser user)131     public void onUserUnlocked(TargetUser user) {
132         Objects.requireNonNull(user);
133 
134         registerAppSearchObserver(user);
135         trySyncRuntimeMetadata(user);
136     }
137 
138     /** Called when the user is stopping. */
onUserStopping(@onNull TargetUser user)139     public void onUserStopping(@NonNull TargetUser user) {
140         Objects.requireNonNull(user);
141 
142         MetadataSyncPerUser.removeUserSyncAdapter(user.getUserHandle());
143     }
144 
145     @Override
dump(@onNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args)146     public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) {
147         if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) {
148             return;
149         }
150 
151         final long token = Binder.clearCallingIdentity();
152         try {
153             AppFunctionDumpHelper.dumpAppFunctionsState(mContext, pw);
154         } finally {
155             Binder.restoreCallingIdentity(token);
156         }
157     }
158 
159     @Override
executeAppFunction( @onNull ExecuteAppFunctionAidlRequest requestInternal, @NonNull IExecuteAppFunctionCallback executeAppFunctionCallback)160     public ICancellationSignal executeAppFunction(
161             @NonNull ExecuteAppFunctionAidlRequest requestInternal,
162             @NonNull IExecuteAppFunctionCallback executeAppFunctionCallback) {
163         Objects.requireNonNull(requestInternal);
164         Objects.requireNonNull(executeAppFunctionCallback);
165 
166         int callingUid = Binder.getCallingUid();
167         int callingPid = Binder.getCallingPid();
168 
169         final SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback =
170                 initializeSafeExecuteAppFunctionCallback(
171                         requestInternal, executeAppFunctionCallback, callingUid);
172 
173         String validatedCallingPackage;
174         try {
175             validatedCallingPackage =
176                     mCallerValidator.validateCallingPackage(requestInternal.getCallingPackage());
177             mCallerValidator.verifyTargetUserHandle(
178                     requestInternal.getUserHandle(), validatedCallingPackage);
179         } catch (SecurityException exception) {
180             safeExecuteAppFunctionCallback.onError(
181                     new AppFunctionException(
182                             AppFunctionException.ERROR_DENIED, exception.getMessage()));
183             return null;
184         }
185 
186         ICancellationSignal localCancelTransport = CancellationSignal.createTransport();
187 
188         THREAD_POOL_EXECUTOR.execute(
189                 () -> {
190                     try {
191                         executeAppFunctionInternal(
192                                 requestInternal,
193                                 callingUid,
194                                 callingPid,
195                                 localCancelTransport,
196                                 safeExecuteAppFunctionCallback,
197                                 executeAppFunctionCallback.asBinder());
198                     } catch (Exception e) {
199                         safeExecuteAppFunctionCallback.onError(
200                                 mapExceptionToExecuteAppFunctionResponse(e));
201                     }
202                 });
203         return localCancelTransport;
204     }
205 
206     @WorkerThread
executeAppFunctionInternal( @onNull ExecuteAppFunctionAidlRequest requestInternal, int callingUid, int callingPid, @NonNull ICancellationSignal localCancelTransport, @NonNull SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback, @NonNull IBinder callerBinder)207     private void executeAppFunctionInternal(
208             @NonNull ExecuteAppFunctionAidlRequest requestInternal,
209             int callingUid,
210             int callingPid,
211             @NonNull ICancellationSignal localCancelTransport,
212             @NonNull SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback,
213             @NonNull IBinder callerBinder) {
214         UserHandle targetUser = requestInternal.getUserHandle();
215         UserHandle callingUser = UserHandle.getUserHandleForUid(callingUid);
216         if (!mCallerValidator.verifyEnterprisePolicyIsAllowed(callingUser, targetUser)) {
217             safeExecuteAppFunctionCallback.onError(
218                     new AppFunctionException(
219                             AppFunctionException.ERROR_ENTERPRISE_POLICY_DISALLOWED,
220                             "Cannot run on a user with a restricted enterprise policy"));
221             return;
222         }
223 
224         String targetPackageName = requestInternal.getClientRequest().getTargetPackageName();
225         if (TextUtils.isEmpty(targetPackageName)) {
226             safeExecuteAppFunctionCallback.onError(
227                     new AppFunctionException(
228                             AppFunctionException.ERROR_INVALID_ARGUMENT,
229                             "Target package name cannot be empty."));
230             return;
231         }
232 
233         mCallerValidator
234                 .verifyCallerCanExecuteAppFunction(
235                         callingUid,
236                         callingPid,
237                         targetUser,
238                         requestInternal.getCallingPackage(),
239                         targetPackageName,
240                         requestInternal.getClientRequest().getFunctionIdentifier())
241                 .thenCompose(
242                         canExecuteResult -> {
243                             if (canExecuteResult == CAN_EXECUTE_APP_FUNCTIONS_DENIED) {
244                                 return AndroidFuture.failedFuture(new SecurityException(
245                                         "Caller does not have permission to execute the"
246                                                 + " appfunction"));
247                             }
248                             return isAppFunctionEnabled(
249                                     requestInternal
250                                             .getClientRequest()
251                                             .getFunctionIdentifier(),
252                                     requestInternal
253                                             .getClientRequest()
254                                             .getTargetPackageName(),
255                                     getAppSearchManagerAsUser(
256                                             requestInternal.getUserHandle()),
257                                     THREAD_POOL_EXECUTOR)
258                                     .thenApply(
259                                             isEnabled -> {
260                                                 if (!isEnabled) {
261                                                     throw new DisabledAppFunctionException(
262                                                             "The app function is disabled");
263                                                 }
264                                                 return canExecuteResult;
265                                             });
266                         })
267                 .thenAccept(
268                         canExecuteResult -> {
269                             int bindFlags = Context.BIND_AUTO_CREATE;
270                             if (canExecuteResult
271                                     == CAN_EXECUTE_APP_FUNCTIONS_ALLOWED_HAS_PERMISSION) {
272                                 // If the caller doesn't have the permission, do not use
273                                 // BIND_FOREGROUND_SERVICE to avoid it raising its process state by
274                                 // calling its own AppFunctions.
275                                 bindFlags |= Context.BIND_FOREGROUND_SERVICE;
276                             }
277                             Intent serviceIntent =
278                                     mInternalServiceHelper.resolveAppFunctionService(
279                                             targetPackageName, targetUser);
280                             if (serviceIntent == null) {
281                                 safeExecuteAppFunctionCallback.onError(
282                                         new AppFunctionException(
283                                                 AppFunctionException.ERROR_SYSTEM_ERROR,
284                                                 "Cannot find the target service."));
285                                 return;
286                             }
287                             // Grant target app implicit visibility to the caller
288                             final int grantRecipientUserId = targetUser.getIdentifier();
289                             final int grantRecipientAppId =
290                                     UserHandle.getAppId(
291                                             mPackageManagerInternal.getPackageUid(
292                                                     requestInternal
293                                                             .getClientRequest()
294                                                             .getTargetPackageName(),
295                                                     /* flags= */ 0,
296                                                     /* userId= */ grantRecipientUserId));
297                             if (grantRecipientAppId > 0) {
298                                 mPackageManagerInternal.grantImplicitAccess(
299                                         grantRecipientUserId,
300                                         serviceIntent,
301                                         grantRecipientAppId,
302                                         callingUid,
303                                         /* direct= */ true);
304                             }
305                             bindAppFunctionServiceUnchecked(
306                                     requestInternal,
307                                     serviceIntent,
308                                     targetUser,
309                                     localCancelTransport,
310                                     safeExecuteAppFunctionCallback,
311                                     bindFlags,
312                                     callerBinder,
313                                     callingUid);
314                         })
315                 .exceptionally(
316                         ex -> {
317                             safeExecuteAppFunctionCallback.onError(
318                                     mapExceptionToExecuteAppFunctionResponse(ex));
319                             return null;
320                         });
321     }
322 
isAppFunctionEnabled( @onNull String functionIdentifier, @NonNull String targetPackage, @NonNull AppSearchManager appSearchManager, @NonNull Executor executor)323     private static AndroidFuture<Boolean> isAppFunctionEnabled(
324             @NonNull String functionIdentifier,
325             @NonNull String targetPackage,
326             @NonNull AppSearchManager appSearchManager,
327             @NonNull Executor executor) {
328         AndroidFuture<Boolean> future = new AndroidFuture<>();
329         AppFunctionManagerHelper.isAppFunctionEnabled(
330                 functionIdentifier,
331                 targetPackage,
332                 appSearchManager,
333                 executor,
334                 new OutcomeReceiver<>() {
335                     @Override
336                     public void onResult(@NonNull Boolean result) {
337                         future.complete(result);
338                     }
339 
340                     @Override
341                     public void onError(@NonNull Exception error) {
342                         future.completeExceptionally(error);
343                     }
344                 });
345         return future;
346     }
347 
348     @Override
setAppFunctionEnabled( @onNull String callingPackage, @NonNull String functionIdentifier, @NonNull UserHandle userHandle, @AppFunctionManager.EnabledState int enabledState, @NonNull IAppFunctionEnabledCallback callback)349     public void setAppFunctionEnabled(
350             @NonNull String callingPackage,
351             @NonNull String functionIdentifier,
352             @NonNull UserHandle userHandle,
353             @AppFunctionManager.EnabledState int enabledState,
354             @NonNull IAppFunctionEnabledCallback callback) {
355         try {
356             mCallerValidator.validateCallingPackage(callingPackage);
357         } catch (SecurityException e) {
358             reportException(callback, e);
359             return;
360         }
361         THREAD_POOL_EXECUTOR.execute(
362                 () -> {
363                     try {
364                         synchronized (getLockForPackage(callingPackage)) {
365                             setAppFunctionEnabledInternalLocked(
366                                     callingPackage, functionIdentifier, userHandle, enabledState);
367                         }
368                         callback.onSuccess();
369                     } catch (Exception e) {
370                         Slog.e(TAG, "Error in setAppFunctionEnabled: ", e);
371                         reportException(callback, e);
372                     }
373                 });
374     }
375 
reportException( @onNull IAppFunctionEnabledCallback callback, @NonNull Exception exception)376     private static void reportException(
377             @NonNull IAppFunctionEnabledCallback callback, @NonNull Exception exception) {
378         try {
379             callback.onError(new ParcelableException(exception));
380         } catch (RemoteException e) {
381             Slog.w(TAG, "Failed to report the exception", e);
382         }
383     }
384 
385     /**
386      * Sets the enabled status of a specified app function.
387      *
388      * <p>Required to hold a lock to call this function to avoid document changes during the
389      * process.
390      */
391     @WorkerThread
392     @GuardedBy("getLockForPackage(callingPackage)")
setAppFunctionEnabledInternalLocked( @onNull String callingPackage, @NonNull String functionIdentifier, @NonNull UserHandle userHandle, @AppFunctionManager.EnabledState int enabledState)393     private void setAppFunctionEnabledInternalLocked(
394             @NonNull String callingPackage,
395             @NonNull String functionIdentifier,
396             @NonNull UserHandle userHandle,
397             @AppFunctionManager.EnabledState int enabledState)
398             throws Exception {
399         AppSearchManager perUserAppSearchManager = getAppSearchManagerAsUser(userHandle);
400 
401         if (perUserAppSearchManager == null) {
402             throw new IllegalStateException(
403                     "AppSearchManager not found for user:" + userHandle.getIdentifier());
404         }
405         SearchContext runtimeMetadataSearchContext =
406                 new SearchContext.Builder(APP_FUNCTION_RUNTIME_METADATA_DB).build();
407 
408         try (FutureAppSearchSession runtimeMetadataSearchSession =
409                 new FutureAppSearchSessionImpl(
410                         perUserAppSearchManager,
411                         THREAD_POOL_EXECUTOR,
412                         runtimeMetadataSearchContext)) {
413             AppFunctionRuntimeMetadata existingMetadata =
414                     new AppFunctionRuntimeMetadata(
415                             getRuntimeMetadataGenericDocument(
416                                     callingPackage,
417                                     functionIdentifier,
418                                     runtimeMetadataSearchSession));
419             AppFunctionRuntimeMetadata newMetadata =
420                     new AppFunctionRuntimeMetadata.Builder(existingMetadata)
421                             .setEnabled(enabledState)
422                             .build();
423             AppSearchBatchResult<String, Void> putDocumentBatchResult =
424                     runtimeMetadataSearchSession
425                             .put(
426                                     new PutDocumentsRequest.Builder()
427                                             .addGenericDocuments(newMetadata)
428                                             .build())
429                             .get();
430             if (!putDocumentBatchResult.isSuccess()) {
431                 throw new IllegalStateException(
432                         "Failed writing updated doc to AppSearch due to " + putDocumentBatchResult);
433             }
434         }
435     }
436 
437     @WorkerThread
438     @NonNull
getRuntimeMetadataGenericDocument( @onNull String packageName, @NonNull String functionId, @NonNull FutureAppSearchSession runtimeMetadataSearchSession)439     private AppFunctionRuntimeMetadata getRuntimeMetadataGenericDocument(
440             @NonNull String packageName,
441             @NonNull String functionId,
442             @NonNull FutureAppSearchSession runtimeMetadataSearchSession)
443             throws Exception {
444         String documentId =
445                 AppFunctionRuntimeMetadata.getDocumentIdForAppFunction(packageName, functionId);
446         GetByDocumentIdRequest request =
447                 new GetByDocumentIdRequest.Builder(APP_FUNCTION_RUNTIME_NAMESPACE)
448                         .addIds(documentId)
449                         .build();
450         AppSearchBatchResult<String, GenericDocument> result =
451                 runtimeMetadataSearchSession.getByDocumentId(request).get();
452         if (result.isSuccess()) {
453             return new AppFunctionRuntimeMetadata((result.getSuccesses().get(documentId)));
454         }
455         throw new IllegalArgumentException("Function " + functionId + " does not exist");
456     }
457 
bindAppFunctionServiceUnchecked( @onNull ExecuteAppFunctionAidlRequest requestInternal, @NonNull Intent serviceIntent, @NonNull UserHandle targetUser, @NonNull ICancellationSignal cancellationSignalTransport, @NonNull SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback, int bindFlags, @NonNull IBinder callerBinder, int callingUid)458     private void bindAppFunctionServiceUnchecked(
459             @NonNull ExecuteAppFunctionAidlRequest requestInternal,
460             @NonNull Intent serviceIntent,
461             @NonNull UserHandle targetUser,
462             @NonNull ICancellationSignal cancellationSignalTransport,
463             @NonNull SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback,
464             int bindFlags,
465             @NonNull IBinder callerBinder,
466             int callingUid) {
467         CancellationSignal cancellationSignal =
468                 CancellationSignal.fromTransport(cancellationSignalTransport);
469         ICancellationCallback cancellationCallback =
470                 new ICancellationCallback.Stub() {
471                     @Override
472                     public void sendCancellationTransport(
473                             @NonNull ICancellationSignal cancellationTransport) {
474                         cancellationSignal.setRemote(cancellationTransport);
475                     }
476                 };
477         boolean bindServiceResult =
478                 mRemoteServiceCaller.runServiceCall(
479                         serviceIntent,
480                         bindFlags,
481                         targetUser,
482                         mServiceConfig.getExecuteAppFunctionCancellationTimeoutMillis(),
483                         cancellationSignal,
484                         new RunAppFunctionServiceCallback(
485                                 requestInternal,
486                                 cancellationCallback,
487                                 safeExecuteAppFunctionCallback,
488                                 getPackageSigningInfo(
489                                         targetUser,
490                                         requestInternal.getCallingPackage(),
491                                         callingUid)),
492                         callerBinder);
493 
494         if (!bindServiceResult) {
495             Slog.e(TAG, "Failed to bind to the AppFunctionService");
496             safeExecuteAppFunctionCallback.onError(
497                     new AppFunctionException(
498                             AppFunctionException.ERROR_SYSTEM_ERROR,
499                             "Failed to bind the AppFunctionService."));
500         }
501     }
502 
503     @NonNull
getPackageSigningInfo( @onNull UserHandle targetUser, @NonNull String packageName, int uid)504     private SigningInfo getPackageSigningInfo(
505             @NonNull UserHandle targetUser, @NonNull String packageName, int uid) {
506         Objects.requireNonNull(packageName);
507         Objects.requireNonNull(targetUser);
508 
509         PackageInfo packageInfo;
510         packageInfo =
511                 Objects.requireNonNull(
512                         mPackageManagerInternal.getPackageInfo(
513                                 packageName,
514                                 PackageManager.GET_SIGNING_CERTIFICATES,
515                                 uid,
516                                 targetUser.getIdentifier()));
517         return Objects.requireNonNull(packageInfo.signingInfo);
518     }
519 
getAppSearchManagerAsUser(@onNull UserHandle userHandle)520     private AppSearchManager getAppSearchManagerAsUser(@NonNull UserHandle userHandle) {
521         return mContext.createContextAsUser(userHandle, /* flags= */ 0)
522                 .getSystemService(AppSearchManager.class);
523     }
524 
mapExceptionToExecuteAppFunctionResponse(Throwable e)525     private AppFunctionException mapExceptionToExecuteAppFunctionResponse(Throwable e) {
526         if (e instanceof CompletionException) {
527             e = e.getCause();
528         }
529         int resultCode = AppFunctionException.ERROR_SYSTEM_ERROR;
530         if (e instanceof AppFunctionNotFoundException) {
531             resultCode = AppFunctionException.ERROR_FUNCTION_NOT_FOUND;
532         } else if (e instanceof AppSearchException appSearchException) {
533             resultCode =
534                     mapAppSearchResultFailureCodeToExecuteAppFunctionResponse(
535                             appSearchException.getResultCode());
536         } else if (e instanceof SecurityException) {
537             resultCode = AppFunctionException.ERROR_DENIED;
538         } else if (e instanceof DisabledAppFunctionException) {
539             resultCode = AppFunctionException.ERROR_DISABLED;
540         }
541         return new AppFunctionException(resultCode, e.getMessage());
542     }
543 
mapAppSearchResultFailureCodeToExecuteAppFunctionResponse(int resultCode)544     private int mapAppSearchResultFailureCodeToExecuteAppFunctionResponse(int resultCode) {
545         if (resultCode == AppSearchResult.RESULT_OK) {
546             throw new IllegalArgumentException(
547                     "This method can only be used to convert failure result codes.");
548         }
549 
550         switch (resultCode) {
551             case AppSearchResult.RESULT_NOT_FOUND:
552                 return AppFunctionException.ERROR_FUNCTION_NOT_FOUND;
553             case AppSearchResult.RESULT_INVALID_ARGUMENT:
554             case AppSearchResult.RESULT_INTERNAL_ERROR:
555             case AppSearchResult.RESULT_SECURITY_ERROR:
556                 // fall-through
557         }
558         return AppFunctionException.ERROR_SYSTEM_ERROR;
559     }
560 
registerAppSearchObserver(@onNull TargetUser user)561     private void registerAppSearchObserver(@NonNull TargetUser user) {
562         AppSearchManager perUserAppSearchManager =
563                 mContext.createContextAsUser(user.getUserHandle(), /* flags= */ 0)
564                         .getSystemService(AppSearchManager.class);
565         if (perUserAppSearchManager == null) {
566             Slog.d(TAG, "AppSearch Manager not found for user: " + user.getUserIdentifier());
567             return;
568         }
569         FutureGlobalSearchSession futureGlobalSearchSession =
570                 new FutureGlobalSearchSession(perUserAppSearchManager, THREAD_POOL_EXECUTOR);
571         AppFunctionMetadataObserver appFunctionMetadataObserver =
572                 new AppFunctionMetadataObserver(
573                         user.getUserHandle(),
574                         mContext.createContextAsUser(user.getUserHandle(), /* flags= */ 0));
575         var unused =
576                 futureGlobalSearchSession
577                         .registerObserverCallbackAsync(
578                                 "android",
579                                 new ObserverSpec.Builder().build(),
580                                 THREAD_POOL_EXECUTOR,
581                                 appFunctionMetadataObserver)
582                         .whenComplete(
583                                 (voidResult, ex) -> {
584                                     if (ex != null) {
585                                         Slog.e(TAG, "Failed to register observer: ", ex);
586                                     }
587                                     futureGlobalSearchSession.close();
588                                 });
589     }
590 
trySyncRuntimeMetadata(@onNull TargetUser user)591     private void trySyncRuntimeMetadata(@NonNull TargetUser user) {
592         MetadataSyncAdapter metadataSyncAdapter =
593                 MetadataSyncPerUser.getPerUserMetadataSyncAdapter(
594                         user.getUserHandle(),
595                         mContext.createContextAsUser(user.getUserHandle(), /* flags= */ 0));
596         if (metadataSyncAdapter != null) {
597             var unused =
598                     metadataSyncAdapter
599                             .submitSyncRequest()
600                             .whenComplete(
601                                     (isSuccess, ex) -> {
602                                         if (ex != null || !isSuccess) {
603                                             Slog.e(TAG, "Sync was not successful");
604                                         }
605                                     });
606         }
607     }
608 
609     /**
610      * Retrieves the lock object associated with the given package name.
611      *
612      * <p>This method returns the lock object from the {@code mLocks} map if it exists. If no lock
613      * is found for the given package name, a new lock object is created, stored in the map, and
614      * returned.
615      */
616     @VisibleForTesting
617     @NonNull
getLockForPackage(String callingPackage)618     Object getLockForPackage(String callingPackage) {
619         // Synchronized the access to mLocks to prevent race condition.
620         synchronized (mLocks) {
621             // By using a WeakHashMap, we allow the garbage collector to reclaim memory by removing
622             // entries associated with unused callingPackage keys. Therefore, we remove the null
623             // values before getting/computing a new value. The goal is to not let the size of this
624             // map grow without an upper bound.
625             mLocks.values().removeAll(Collections.singleton(null)); // Remove null values
626             return mLocks.computeIfAbsent(callingPackage, k -> new Object());
627         }
628     }
629 
630     /**
631      * Returns a new {@link SafeOneTimeExecuteAppFunctionCallback} initialized with a {@link
632      * SafeOneTimeExecuteAppFunctionCallback.CompletionCallback} that logs the results.
633      */
634     @VisibleForTesting
initializeSafeExecuteAppFunctionCallback( @onNull ExecuteAppFunctionAidlRequest requestInternal, @NonNull IExecuteAppFunctionCallback executeAppFunctionCallback, int callingUid)635     SafeOneTimeExecuteAppFunctionCallback initializeSafeExecuteAppFunctionCallback(
636             @NonNull ExecuteAppFunctionAidlRequest requestInternal,
637             @NonNull IExecuteAppFunctionCallback executeAppFunctionCallback,
638             int callingUid) {
639         return new SafeOneTimeExecuteAppFunctionCallback(
640                 executeAppFunctionCallback,
641                 new SafeOneTimeExecuteAppFunctionCallback.CompletionCallback() {
642                     @Override
643                     public void finalizeOnSuccess(
644                             @NonNull ExecuteAppFunctionResponse result,
645                             long executionStartTimeMillis) {
646                         mLoggerWrapper.logAppFunctionSuccess(
647                                 requestInternal, result, callingUid, executionStartTimeMillis);
648                     }
649 
650                     @Override
651                     public void finalizeOnError(
652                             @NonNull AppFunctionException error, long executionStartTimeMillis) {
653                         mLoggerWrapper.logAppFunctionError(
654                                 requestInternal,
655                                 error.getErrorCode(),
656                                 callingUid,
657                                 executionStartTimeMillis);
658                     }
659                 });
660     }
661 
662     private static class AppFunctionMetadataObserver implements ObserverCallback {
663         @Nullable private final MetadataSyncAdapter mPerUserMetadataSyncAdapter;
664 
665         AppFunctionMetadataObserver(@NonNull UserHandle userHandle, @NonNull Context userContext) {
666             mPerUserMetadataSyncAdapter =
667                     MetadataSyncPerUser.getPerUserMetadataSyncAdapter(userHandle, userContext);
668         }
669 
670         @Override
671         public void onDocumentChanged(@NonNull DocumentChangeInfo documentChangeInfo) {
672             if (mPerUserMetadataSyncAdapter == null) {
673                 return;
674             }
675             if (documentChangeInfo
676                             .getDatabaseName()
677                             .equals(AppFunctionStaticMetadataHelper.APP_FUNCTION_STATIC_METADATA_DB)
678                     && documentChangeInfo
679                             .getNamespace()
680                             .equals(
681                                     AppFunctionStaticMetadataHelper
682                                             .APP_FUNCTION_STATIC_NAMESPACE)) {
683                 var unused = mPerUserMetadataSyncAdapter.submitSyncRequest();
684             }
685         }
686 
687         @Override
688         public void onSchemaChanged(@NonNull SchemaChangeInfo schemaChangeInfo) {
689             if (mPerUserMetadataSyncAdapter == null) {
690                 return;
691             }
692             if (schemaChangeInfo
693                     .getDatabaseName()
694                     .equals(AppFunctionStaticMetadataHelper.APP_FUNCTION_STATIC_METADATA_DB)) {
695                 boolean shouldInitiateSync = false;
696                 for (String schemaName : schemaChangeInfo.getChangedSchemaNames()) {
697                     if (schemaName.startsWith(AppFunctionStaticMetadataHelper.STATIC_SCHEMA_TYPE)) {
698                         shouldInitiateSync = true;
699                         break;
700                     }
701                 }
702                 if (shouldInitiateSync) {
703                     var unused = mPerUserMetadataSyncAdapter.submitSyncRequest();
704                 }
705             }
706         }
707     }
708 
709     /** Throws when executing a disabled app function. */
710     private static class DisabledAppFunctionException extends RuntimeException {
711         private DisabledAppFunctionException(@NonNull String errorMessage) {
712             super(errorMessage);
713         }
714     }
715 }
716