Loading services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java +53 −0 Original line number Diff line number Diff line Loading @@ -16,7 +16,15 @@ package com.android.server.appfunctions; import android.annotation.NonNull; import android.os.UserHandle; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; Loading @@ -33,5 +41,50 @@ public final class AppFunctionExecutors { /* unit= */ TimeUnit.SECONDS, /* workQueue= */ new LinkedBlockingQueue<>()); /** A map of per-user executors for queued work. */ @GuardedBy("sLock") private static final SparseArray<ExecutorService> mPerUserExecutorsLocked = new SparseArray<>(); private static final Object sLock = new Object(); /** * Returns a per-user executor for queued metadata sync request. * * <p>The work submitted to these executor (Sync request) needs to be synchronous per user hence * the use of a single thread. * * <p>Note: Use a different executor if not calling {@code submitSyncRequest} on a {@code * MetadataSyncAdapter}. */ // TODO(b/357551503): Restrict the scope of this executor to the MetadataSyncAdapter itself. public static ExecutorService getPerUserSyncExecutor(@NonNull UserHandle user) { synchronized (sLock) { ExecutorService executor = mPerUserExecutorsLocked.get(user.getIdentifier(), null); if (executor == null) { executor = Executors.newSingleThreadExecutor(); mPerUserExecutorsLocked.put(user.getIdentifier(), executor); } return executor; } } /** * Shuts down and removes the per-user executor for queued work. * * <p>This should be called when the user is removed. */ public static void shutDownAndRemoveUserExecutor(@NonNull UserHandle user) throws InterruptedException { ExecutorService executor; synchronized (sLock) { executor = mPerUserExecutorsLocked.get(user.getIdentifier()); mPerUserExecutorsLocked.remove(user.getIdentifier()); } if (executor != null) { executor.shutdown(); var unused = executor.awaitTermination(30, TimeUnit.SECONDS); } } private AppFunctionExecutors() {} } services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java +11 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.server.appfunctions; import android.annotation.NonNull; import android.app.appfunctions.AppFunctionManagerConfiguration; import android.content.Context; Loading @@ -36,4 +37,14 @@ public class AppFunctionManagerService extends SystemService { publishBinderService(Context.APP_FUNCTION_SERVICE, mServiceImpl); } } @Override public void onUserUnlocked(@NonNull TargetUser user) { mServiceImpl.onUserUnlocked(user); } @Override public void onUserStopping(@NonNull TargetUser user) { mServiceImpl.onUserStopping(user); } } services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java +183 −52 Original line number Diff line number Diff line Loading @@ -19,29 +19,35 @@ package com.android.server.appfunctions; import static com.android.server.appfunctions.AppFunctionExecutors.THREAD_POOL_EXECUTOR; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.appfunctions.AppFunctionStaticMetadataHelper; import android.app.appfunctions.ExecuteAppFunctionAidlRequest; import android.app.appfunctions.ExecuteAppFunctionResponse; import android.app.appfunctions.IAppFunctionManager; import android.app.appfunctions.IAppFunctionService; import android.app.appfunctions.IExecuteAppFunctionCallback; import android.app.appfunctions.SafeOneTimeExecuteAppFunctionCallback; import android.app.appsearch.AppSearchManager; import android.app.appsearch.AppSearchResult; import android.app.appsearch.observer.DocumentChangeInfo; import android.app.appsearch.observer.ObserverCallback; import android.app.appsearch.observer.ObserverSpec; import android.app.appsearch.observer.SchemaChangeInfo; import android.content.Context; import android.content.Intent; import android.os.Binder; import android.os.UserHandle; import android.text.TextUtils; import android.util.Slog; import android.app.appsearch.AppSearchResult; import com.android.internal.annotations.VisibleForTesting; import com.android.server.SystemService.TargetUser; import com.android.server.appfunctions.RemoteServiceCaller.RunServiceCallCallback; import com.android.server.appfunctions.RemoteServiceCaller.ServiceUsageCompleteListener; import java.io.IOException; import java.util.Objects; import java.util.concurrent.CompletionException; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** Implementation of the AppFunctionManagerService. */ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { Loading @@ -51,9 +57,11 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { private final CallerValidator mCallerValidator; private final ServiceHelper mInternalServiceHelper; private final ServiceConfig mServiceConfig; private final Context mContext; public AppFunctionManagerServiceImpl(@NonNull Context context) { this( context, new RemoteServiceCallerImpl<>( context, IAppFunctionService.Stub::asInterface, THREAD_POOL_EXECUTOR), new CallerValidatorImpl(context), Loading @@ -63,10 +71,12 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { @VisibleForTesting AppFunctionManagerServiceImpl( Context context, RemoteServiceCaller<IAppFunctionService> remoteServiceCaller, CallerValidator callerValidator, ServiceHelper appFunctionInternalServiceHelper, ServiceConfig serviceConfig) { mContext = Objects.requireNonNull(context); mRemoteServiceCaller = Objects.requireNonNull(remoteServiceCaller); mCallerValidator = Objects.requireNonNull(callerValidator); mInternalServiceHelper = Objects.requireNonNull(appFunctionInternalServiceHelper); Loading @@ -90,6 +100,26 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { } } /** Called when the user is unlocked. */ public void onUserUnlocked(TargetUser user) { Objects.requireNonNull(user); registerAppSearchObserver(user); trySyncRuntimeMetadata(user); } /** Called when the user is stopping. */ public void onUserStopping(@NonNull TargetUser user) { Objects.requireNonNull(user); try { AppFunctionExecutors.shutDownAndRemoveUserExecutor(user.getUserHandle()); MetadataSyncPerUser.removeUserSyncAdapter(user.getUserHandle()); } catch (InterruptedException e) { Slog.e(TAG, "Unable to remove data for: " + user.getUserHandle(), e); } } private void executeAppFunctionInternal( ExecuteAppFunctionAidlRequest requestInternal, SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback) { Loading Loading @@ -132,7 +162,8 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { return; } var unused = mCallerValidator var unused = mCallerValidator .verifyCallerCanExecuteAppFunction( validatedCallingPackage, targetPackageName, Loading @@ -143,8 +174,8 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { safeExecuteAppFunctionCallback.onResult( ExecuteAppFunctionResponse.newFailure( ExecuteAppFunctionResponse.RESULT_DENIED, "Caller does not have permission to execute the" + " appfunction", "Caller does not have permission to execute" + " the appfunction", /* extras= */ null)); return; } Loading @@ -154,7 +185,8 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { if (serviceIntent == null) { safeExecuteAppFunctionCallback.onResult( ExecuteAppFunctionResponse.newFailure( ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR, ExecuteAppFunctionResponse .RESULT_INTERNAL_ERROR, "Cannot find the target service.", /* extras= */ null)); return; Loading Loading @@ -291,4 +323,103 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { } return ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR; } private void registerAppSearchObserver(@NonNull TargetUser user) { AppSearchManager perUserAppSearchManager = mContext.createContextAsUser(user.getUserHandle(), /* flags= */ 0) .getSystemService(AppSearchManager.class); if (perUserAppSearchManager == null) { Slog.d(TAG, "AppSearch Manager not found for user: " + user.getUserIdentifier()); return; } try (FutureGlobalSearchSession futureGlobalSearchSession = new FutureGlobalSearchSession( perUserAppSearchManager, AppFunctionExecutors.THREAD_POOL_EXECUTOR)) { AppFunctionMetadataObserver appFunctionMetadataObserver = new AppFunctionMetadataObserver( user.getUserHandle(), mContext.createContextAsUser(user.getUserHandle(), /* flags= */ 0)); var unused = futureGlobalSearchSession .registerObserverCallbackAsync( "android", new ObserverSpec.Builder().build(), THREAD_POOL_EXECUTOR, appFunctionMetadataObserver) .whenComplete( (voidResult, ex) -> { if (ex != null) { Slog.e(TAG, "Failed to register observer: ", ex); } }); } catch (IOException ex) { Slog.e(TAG, "Failed to close observer session: ", ex); } } private void trySyncRuntimeMetadata(@NonNull TargetUser user) { MetadataSyncAdapter metadataSyncAdapter = MetadataSyncPerUser.getPerUserMetadataSyncAdapter( user.getUserHandle(), mContext.createContextAsUser(user.getUserHandle(), /* flags= */ 0)); if (metadataSyncAdapter != null) { var unused = metadataSyncAdapter .submitSyncRequest() .whenComplete( (isSuccess, ex) -> { if (ex != null || !isSuccess) { Slog.e(TAG, "Sync was not successful"); } }); } } private static class AppFunctionMetadataObserver implements ObserverCallback { @Nullable private final MetadataSyncAdapter mPerUserMetadataSyncAdapter; AppFunctionMetadataObserver(@NonNull UserHandle userHandle, @NonNull Context userContext) { mPerUserMetadataSyncAdapter = MetadataSyncPerUser.getPerUserMetadataSyncAdapter(userHandle, userContext); } @Override public void onDocumentChanged(@NonNull DocumentChangeInfo documentChangeInfo) { if (mPerUserMetadataSyncAdapter == null) { return; } if (documentChangeInfo .getDatabaseName() .equals(AppFunctionStaticMetadataHelper.APP_FUNCTION_STATIC_METADATA_DB) && documentChangeInfo .getNamespace() .equals( AppFunctionStaticMetadataHelper .APP_FUNCTION_STATIC_NAMESPACE)) { var unused = mPerUserMetadataSyncAdapter.submitSyncRequest(); } } @Override public void onSchemaChanged(@NonNull SchemaChangeInfo schemaChangeInfo) { if (mPerUserMetadataSyncAdapter == null) { return; } if (schemaChangeInfo .getDatabaseName() .equals(AppFunctionStaticMetadataHelper.APP_FUNCTION_STATIC_METADATA_DB)) { boolean shouldInitiateSync = false; for (String schemaName : schemaChangeInfo.getChangedSchemaNames()) { if (schemaName.startsWith(AppFunctionStaticMetadataHelper.STATIC_SCHEMA_TYPE)) { shouldInitiateSync = true; break; } } if (shouldInitiateSync) { var unused = mPerUserMetadataSyncAdapter.submitSyncRequest(); } } } } } services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java +47 −21 Original line number Diff line number Diff line Loading @@ -24,6 +24,8 @@ import android.annotation.WorkerThread; import android.app.appfunctions.AppFunctionRuntimeMetadata; import android.app.appfunctions.AppFunctionStaticMetadataHelper; import android.app.appsearch.AppSearchBatchResult; import android.app.appsearch.AppSearchManager; import android.app.appsearch.AppSearchManager.SearchContext; import android.app.appsearch.AppSearchResult; import android.app.appsearch.AppSearchSchema; import android.app.appsearch.PackageIdentifier; Loading Loading @@ -61,9 +63,8 @@ import java.util.concurrent.Executor; */ public class MetadataSyncAdapter { private static final String TAG = MetadataSyncAdapter.class.getSimpleName(); private final FutureAppSearchSession mRuntimeMetadataSearchSession; private final FutureAppSearchSession mStaticMetadataSearchSession; private final Executor mSyncExecutor; private final AppSearchManager mAppSearchManager; private final PackageManager mPackageManager; // Hidden constants in {@link SetSchemaRequest} that restricts runtime metadata visibility Loading @@ -73,13 +74,11 @@ public class MetadataSyncAdapter { public MetadataSyncAdapter( @NonNull Executor syncExecutor, @NonNull FutureAppSearchSession runtimeMetadataSearchSession, @NonNull FutureAppSearchSession staticMetadataSearchSession, @NonNull PackageManager packageManager) { @NonNull PackageManager packageManager, @NonNull AppSearchManager appSearchManager) { mSyncExecutor = Objects.requireNonNull(syncExecutor); mRuntimeMetadataSearchSession = Objects.requireNonNull(runtimeMetadataSearchSession); mStaticMetadataSearchSession = Objects.requireNonNull(staticMetadataSearchSession); mPackageManager = Objects.requireNonNull(packageManager); mAppSearchManager = Objects.requireNonNull(appSearchManager); } /** Loading @@ -89,31 +88,54 @@ public class MetadataSyncAdapter { * synchronization was successful. */ public AndroidFuture<Boolean> submitSyncRequest() { SearchContext staticMetadataSearchContext = new SearchContext.Builder( AppFunctionStaticMetadataHelper.APP_FUNCTION_STATIC_METADATA_DB) .build(); SearchContext runtimeMetadataSearchContext = new SearchContext.Builder( AppFunctionRuntimeMetadata.APP_FUNCTION_RUNTIME_METADATA_DB) .build(); AndroidFuture<Boolean> settableSyncStatus = new AndroidFuture<>(); mSyncExecutor.execute( () -> { try { trySyncAppFunctionMetadataBlocking(); try (FutureAppSearchSession staticMetadataSearchSession = new FutureAppSearchSessionImpl( mAppSearchManager, AppFunctionExecutors.THREAD_POOL_EXECUTOR, staticMetadataSearchContext); FutureAppSearchSession runtimeMetadataSearchSession = new FutureAppSearchSessionImpl( mAppSearchManager, AppFunctionExecutors.THREAD_POOL_EXECUTOR, runtimeMetadataSearchContext)) { trySyncAppFunctionMetadataBlocking( staticMetadataSearchSession, runtimeMetadataSearchSession); settableSyncStatus.complete(true); } catch (Exception e) { settableSyncStatus.completeExceptionally(e); } catch (Exception ex) { settableSyncStatus.completeExceptionally(ex); } }); return settableSyncStatus; } @WorkerThread private void trySyncAppFunctionMetadataBlocking() @VisibleForTesting void trySyncAppFunctionMetadataBlocking( @NonNull FutureAppSearchSession staticMetadataSearchSession, @NonNull FutureAppSearchSession runtimeMetadataSearchSession) throws ExecutionException, InterruptedException { ArrayMap<String, ArraySet<String>> staticPackageToFunctionMap = getPackageToFunctionIdMap( mStaticMetadataSearchSession, staticMetadataSearchSession, AppFunctionStaticMetadataHelper.STATIC_SCHEMA_TYPE, AppFunctionStaticMetadataHelper.PROPERTY_FUNCTION_ID, AppFunctionStaticMetadataHelper.PROPERTY_PACKAGE_NAME); ArrayMap<String, ArraySet<String>> runtimePackageToFunctionMap = getPackageToFunctionIdMap( mRuntimeMetadataSearchSession, runtimeMetadataSearchSession, RUNTIME_SCHEMA_TYPE, AppFunctionRuntimeMetadata.PROPERTY_FUNCTION_ID, AppFunctionRuntimeMetadata.PROPERTY_PACKAGE_NAME); Loading @@ -134,7 +156,7 @@ public class MetadataSyncAdapter { RemoveByDocumentIdRequest removeByDocumentIdRequest = buildRemoveRuntimeMetadataRequest(removedFunctionsDiffMap); AppSearchBatchResult<String, Void> removeDocumentBatchResult = mRuntimeMetadataSearchSession.remove(removeByDocumentIdRequest).get(); runtimeMetadataSearchSession.remove(removeByDocumentIdRequest).get(); if (!removeDocumentBatchResult.isSuccess()) { throw convertFailedAppSearchResultToException( removeDocumentBatchResult.getFailures().values()); Loading @@ -144,13 +166,14 @@ public class MetadataSyncAdapter { if (!addedFunctionsDiffMap.isEmpty()) { // TODO(b/357551503): only set schema on package diff SetSchemaRequest addSetSchemaRequest = buildSetSchemaRequestForRuntimeMetadataSchemas(appRuntimeMetadataSchemas); buildSetSchemaRequestForRuntimeMetadataSchemas( mPackageManager, appRuntimeMetadataSchemas); Objects.requireNonNull( mRuntimeMetadataSearchSession.setSchema(addSetSchemaRequest).get()); runtimeMetadataSearchSession.setSchema(addSetSchemaRequest).get()); PutDocumentsRequest putDocumentsRequest = buildPutRuntimeMetadataRequest(addedFunctionsDiffMap); AppSearchBatchResult<String, Void> putDocumentBatchResult = mRuntimeMetadataSearchSession.put(putDocumentsRequest).get(); runtimeMetadataSearchSession.put(putDocumentsRequest).get(); if (!putDocumentBatchResult.isSuccess()) { throw convertFailedAppSearchResultToException( putDocumentBatchResult.getFailures().values()); Loading Loading @@ -211,6 +234,7 @@ public class MetadataSyncAdapter { @NonNull private SetSchemaRequest buildSetSchemaRequestForRuntimeMetadataSchemas( @NonNull PackageManager packageManager, @NonNull Set<AppSearchSchema> metadataSchemaSet) { Objects.requireNonNull(metadataSchemaSet); SetSchemaRequest.Builder setSchemaRequestBuilder = Loading @@ -220,7 +244,7 @@ public class MetadataSyncAdapter { String packageName = AppFunctionRuntimeMetadata.getPackageNameFromSchema( runtimeMetadataSchema.getSchemaType()); byte[] packageCert = getCertificate(packageName); byte[] packageCert = getCertificate(packageManager, packageName); if (packageCert == null) { continue; } Loading Loading @@ -399,13 +423,15 @@ public class MetadataSyncAdapter { /** Gets the SHA-256 certificate from a {@link PackageManager}, or null if it is not found. */ @Nullable private byte[] getCertificate(@NonNull String packageName) { private byte[] getCertificate( @NonNull PackageManager packageManager, @NonNull String packageName) { Objects.requireNonNull(packageManager); Objects.requireNonNull(packageName); PackageInfo packageInfo; try { packageInfo = Objects.requireNonNull( mPackageManager.getPackageInfo( packageManager.getPackageInfo( packageName, PackageManager.GET_META_DATA | PackageManager.GET_SIGNING_CERTIFICATES)); Loading services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java 0 → 100644 +80 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.appfunctions; import android.annotation.Nullable; import android.app.appsearch.AppSearchManager; import android.content.Context; import android.content.pm.PackageManager; import android.os.UserHandle; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; /** A Singleton class that manages per-user metadata sync adapters. */ public final class MetadataSyncPerUser { private static final String TAG = MetadataSyncPerUser.class.getSimpleName(); /** A map of per-user adapter for synchronizing appFunction metadata. */ @GuardedBy("sLock") private static final SparseArray<MetadataSyncAdapter> sPerUserMetadataSyncAdapter = new SparseArray<>(); private static final Object sLock = new Object(); /** * Returns the per-user metadata sync adapter for the given user. * * @param user The user for which to get the metadata sync adapter. * @param userContext The user context for the given user. * @return The metadata sync adapter for the given user. */ @Nullable public static MetadataSyncAdapter getPerUserMetadataSyncAdapter( UserHandle user, Context userContext) { synchronized (sLock) { MetadataSyncAdapter metadataSyncAdapter = sPerUserMetadataSyncAdapter.get(user.getIdentifier(), null); if (metadataSyncAdapter == null) { AppSearchManager perUserAppSearchManager = userContext.getSystemService(AppSearchManager.class); PackageManager perUserPackageManager = userContext.getPackageManager(); if (perUserAppSearchManager != null) { metadataSyncAdapter = new MetadataSyncAdapter( AppFunctionExecutors.getPerUserSyncExecutor(user), perUserPackageManager, perUserAppSearchManager); sPerUserMetadataSyncAdapter.put(user.getIdentifier(), metadataSyncAdapter); return metadataSyncAdapter; } } return metadataSyncAdapter; } } /** * Removes the per-user metadata sync adapter for the given user. * * @param user The user for which to remove the metadata sync adapter. */ public static void removeUserSyncAdapter(UserHandle user) { synchronized (sLock) { sPerUserMetadataSyncAdapter.remove(user.getIdentifier()); } } } Loading
services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java +53 −0 Original line number Diff line number Diff line Loading @@ -16,7 +16,15 @@ package com.android.server.appfunctions; import android.annotation.NonNull; import android.os.UserHandle; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; Loading @@ -33,5 +41,50 @@ public final class AppFunctionExecutors { /* unit= */ TimeUnit.SECONDS, /* workQueue= */ new LinkedBlockingQueue<>()); /** A map of per-user executors for queued work. */ @GuardedBy("sLock") private static final SparseArray<ExecutorService> mPerUserExecutorsLocked = new SparseArray<>(); private static final Object sLock = new Object(); /** * Returns a per-user executor for queued metadata sync request. * * <p>The work submitted to these executor (Sync request) needs to be synchronous per user hence * the use of a single thread. * * <p>Note: Use a different executor if not calling {@code submitSyncRequest} on a {@code * MetadataSyncAdapter}. */ // TODO(b/357551503): Restrict the scope of this executor to the MetadataSyncAdapter itself. public static ExecutorService getPerUserSyncExecutor(@NonNull UserHandle user) { synchronized (sLock) { ExecutorService executor = mPerUserExecutorsLocked.get(user.getIdentifier(), null); if (executor == null) { executor = Executors.newSingleThreadExecutor(); mPerUserExecutorsLocked.put(user.getIdentifier(), executor); } return executor; } } /** * Shuts down and removes the per-user executor for queued work. * * <p>This should be called when the user is removed. */ public static void shutDownAndRemoveUserExecutor(@NonNull UserHandle user) throws InterruptedException { ExecutorService executor; synchronized (sLock) { executor = mPerUserExecutorsLocked.get(user.getIdentifier()); mPerUserExecutorsLocked.remove(user.getIdentifier()); } if (executor != null) { executor.shutdown(); var unused = executor.awaitTermination(30, TimeUnit.SECONDS); } } private AppFunctionExecutors() {} }
services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java +11 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.server.appfunctions; import android.annotation.NonNull; import android.app.appfunctions.AppFunctionManagerConfiguration; import android.content.Context; Loading @@ -36,4 +37,14 @@ public class AppFunctionManagerService extends SystemService { publishBinderService(Context.APP_FUNCTION_SERVICE, mServiceImpl); } } @Override public void onUserUnlocked(@NonNull TargetUser user) { mServiceImpl.onUserUnlocked(user); } @Override public void onUserStopping(@NonNull TargetUser user) { mServiceImpl.onUserStopping(user); } }
services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java +183 −52 Original line number Diff line number Diff line Loading @@ -19,29 +19,35 @@ package com.android.server.appfunctions; import static com.android.server.appfunctions.AppFunctionExecutors.THREAD_POOL_EXECUTOR; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.appfunctions.AppFunctionStaticMetadataHelper; import android.app.appfunctions.ExecuteAppFunctionAidlRequest; import android.app.appfunctions.ExecuteAppFunctionResponse; import android.app.appfunctions.IAppFunctionManager; import android.app.appfunctions.IAppFunctionService; import android.app.appfunctions.IExecuteAppFunctionCallback; import android.app.appfunctions.SafeOneTimeExecuteAppFunctionCallback; import android.app.appsearch.AppSearchManager; import android.app.appsearch.AppSearchResult; import android.app.appsearch.observer.DocumentChangeInfo; import android.app.appsearch.observer.ObserverCallback; import android.app.appsearch.observer.ObserverSpec; import android.app.appsearch.observer.SchemaChangeInfo; import android.content.Context; import android.content.Intent; import android.os.Binder; import android.os.UserHandle; import android.text.TextUtils; import android.util.Slog; import android.app.appsearch.AppSearchResult; import com.android.internal.annotations.VisibleForTesting; import com.android.server.SystemService.TargetUser; import com.android.server.appfunctions.RemoteServiceCaller.RunServiceCallCallback; import com.android.server.appfunctions.RemoteServiceCaller.ServiceUsageCompleteListener; import java.io.IOException; import java.util.Objects; import java.util.concurrent.CompletionException; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** Implementation of the AppFunctionManagerService. */ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { Loading @@ -51,9 +57,11 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { private final CallerValidator mCallerValidator; private final ServiceHelper mInternalServiceHelper; private final ServiceConfig mServiceConfig; private final Context mContext; public AppFunctionManagerServiceImpl(@NonNull Context context) { this( context, new RemoteServiceCallerImpl<>( context, IAppFunctionService.Stub::asInterface, THREAD_POOL_EXECUTOR), new CallerValidatorImpl(context), Loading @@ -63,10 +71,12 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { @VisibleForTesting AppFunctionManagerServiceImpl( Context context, RemoteServiceCaller<IAppFunctionService> remoteServiceCaller, CallerValidator callerValidator, ServiceHelper appFunctionInternalServiceHelper, ServiceConfig serviceConfig) { mContext = Objects.requireNonNull(context); mRemoteServiceCaller = Objects.requireNonNull(remoteServiceCaller); mCallerValidator = Objects.requireNonNull(callerValidator); mInternalServiceHelper = Objects.requireNonNull(appFunctionInternalServiceHelper); Loading @@ -90,6 +100,26 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { } } /** Called when the user is unlocked. */ public void onUserUnlocked(TargetUser user) { Objects.requireNonNull(user); registerAppSearchObserver(user); trySyncRuntimeMetadata(user); } /** Called when the user is stopping. */ public void onUserStopping(@NonNull TargetUser user) { Objects.requireNonNull(user); try { AppFunctionExecutors.shutDownAndRemoveUserExecutor(user.getUserHandle()); MetadataSyncPerUser.removeUserSyncAdapter(user.getUserHandle()); } catch (InterruptedException e) { Slog.e(TAG, "Unable to remove data for: " + user.getUserHandle(), e); } } private void executeAppFunctionInternal( ExecuteAppFunctionAidlRequest requestInternal, SafeOneTimeExecuteAppFunctionCallback safeExecuteAppFunctionCallback) { Loading Loading @@ -132,7 +162,8 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { return; } var unused = mCallerValidator var unused = mCallerValidator .verifyCallerCanExecuteAppFunction( validatedCallingPackage, targetPackageName, Loading @@ -143,8 +174,8 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { safeExecuteAppFunctionCallback.onResult( ExecuteAppFunctionResponse.newFailure( ExecuteAppFunctionResponse.RESULT_DENIED, "Caller does not have permission to execute the" + " appfunction", "Caller does not have permission to execute" + " the appfunction", /* extras= */ null)); return; } Loading @@ -154,7 +185,8 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { if (serviceIntent == null) { safeExecuteAppFunctionCallback.onResult( ExecuteAppFunctionResponse.newFailure( ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR, ExecuteAppFunctionResponse .RESULT_INTERNAL_ERROR, "Cannot find the target service.", /* extras= */ null)); return; Loading Loading @@ -291,4 +323,103 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { } return ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR; } private void registerAppSearchObserver(@NonNull TargetUser user) { AppSearchManager perUserAppSearchManager = mContext.createContextAsUser(user.getUserHandle(), /* flags= */ 0) .getSystemService(AppSearchManager.class); if (perUserAppSearchManager == null) { Slog.d(TAG, "AppSearch Manager not found for user: " + user.getUserIdentifier()); return; } try (FutureGlobalSearchSession futureGlobalSearchSession = new FutureGlobalSearchSession( perUserAppSearchManager, AppFunctionExecutors.THREAD_POOL_EXECUTOR)) { AppFunctionMetadataObserver appFunctionMetadataObserver = new AppFunctionMetadataObserver( user.getUserHandle(), mContext.createContextAsUser(user.getUserHandle(), /* flags= */ 0)); var unused = futureGlobalSearchSession .registerObserverCallbackAsync( "android", new ObserverSpec.Builder().build(), THREAD_POOL_EXECUTOR, appFunctionMetadataObserver) .whenComplete( (voidResult, ex) -> { if (ex != null) { Slog.e(TAG, "Failed to register observer: ", ex); } }); } catch (IOException ex) { Slog.e(TAG, "Failed to close observer session: ", ex); } } private void trySyncRuntimeMetadata(@NonNull TargetUser user) { MetadataSyncAdapter metadataSyncAdapter = MetadataSyncPerUser.getPerUserMetadataSyncAdapter( user.getUserHandle(), mContext.createContextAsUser(user.getUserHandle(), /* flags= */ 0)); if (metadataSyncAdapter != null) { var unused = metadataSyncAdapter .submitSyncRequest() .whenComplete( (isSuccess, ex) -> { if (ex != null || !isSuccess) { Slog.e(TAG, "Sync was not successful"); } }); } } private static class AppFunctionMetadataObserver implements ObserverCallback { @Nullable private final MetadataSyncAdapter mPerUserMetadataSyncAdapter; AppFunctionMetadataObserver(@NonNull UserHandle userHandle, @NonNull Context userContext) { mPerUserMetadataSyncAdapter = MetadataSyncPerUser.getPerUserMetadataSyncAdapter(userHandle, userContext); } @Override public void onDocumentChanged(@NonNull DocumentChangeInfo documentChangeInfo) { if (mPerUserMetadataSyncAdapter == null) { return; } if (documentChangeInfo .getDatabaseName() .equals(AppFunctionStaticMetadataHelper.APP_FUNCTION_STATIC_METADATA_DB) && documentChangeInfo .getNamespace() .equals( AppFunctionStaticMetadataHelper .APP_FUNCTION_STATIC_NAMESPACE)) { var unused = mPerUserMetadataSyncAdapter.submitSyncRequest(); } } @Override public void onSchemaChanged(@NonNull SchemaChangeInfo schemaChangeInfo) { if (mPerUserMetadataSyncAdapter == null) { return; } if (schemaChangeInfo .getDatabaseName() .equals(AppFunctionStaticMetadataHelper.APP_FUNCTION_STATIC_METADATA_DB)) { boolean shouldInitiateSync = false; for (String schemaName : schemaChangeInfo.getChangedSchemaNames()) { if (schemaName.startsWith(AppFunctionStaticMetadataHelper.STATIC_SCHEMA_TYPE)) { shouldInitiateSync = true; break; } } if (shouldInitiateSync) { var unused = mPerUserMetadataSyncAdapter.submitSyncRequest(); } } } } }
services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java +47 −21 Original line number Diff line number Diff line Loading @@ -24,6 +24,8 @@ import android.annotation.WorkerThread; import android.app.appfunctions.AppFunctionRuntimeMetadata; import android.app.appfunctions.AppFunctionStaticMetadataHelper; import android.app.appsearch.AppSearchBatchResult; import android.app.appsearch.AppSearchManager; import android.app.appsearch.AppSearchManager.SearchContext; import android.app.appsearch.AppSearchResult; import android.app.appsearch.AppSearchSchema; import android.app.appsearch.PackageIdentifier; Loading Loading @@ -61,9 +63,8 @@ import java.util.concurrent.Executor; */ public class MetadataSyncAdapter { private static final String TAG = MetadataSyncAdapter.class.getSimpleName(); private final FutureAppSearchSession mRuntimeMetadataSearchSession; private final FutureAppSearchSession mStaticMetadataSearchSession; private final Executor mSyncExecutor; private final AppSearchManager mAppSearchManager; private final PackageManager mPackageManager; // Hidden constants in {@link SetSchemaRequest} that restricts runtime metadata visibility Loading @@ -73,13 +74,11 @@ public class MetadataSyncAdapter { public MetadataSyncAdapter( @NonNull Executor syncExecutor, @NonNull FutureAppSearchSession runtimeMetadataSearchSession, @NonNull FutureAppSearchSession staticMetadataSearchSession, @NonNull PackageManager packageManager) { @NonNull PackageManager packageManager, @NonNull AppSearchManager appSearchManager) { mSyncExecutor = Objects.requireNonNull(syncExecutor); mRuntimeMetadataSearchSession = Objects.requireNonNull(runtimeMetadataSearchSession); mStaticMetadataSearchSession = Objects.requireNonNull(staticMetadataSearchSession); mPackageManager = Objects.requireNonNull(packageManager); mAppSearchManager = Objects.requireNonNull(appSearchManager); } /** Loading @@ -89,31 +88,54 @@ public class MetadataSyncAdapter { * synchronization was successful. */ public AndroidFuture<Boolean> submitSyncRequest() { SearchContext staticMetadataSearchContext = new SearchContext.Builder( AppFunctionStaticMetadataHelper.APP_FUNCTION_STATIC_METADATA_DB) .build(); SearchContext runtimeMetadataSearchContext = new SearchContext.Builder( AppFunctionRuntimeMetadata.APP_FUNCTION_RUNTIME_METADATA_DB) .build(); AndroidFuture<Boolean> settableSyncStatus = new AndroidFuture<>(); mSyncExecutor.execute( () -> { try { trySyncAppFunctionMetadataBlocking(); try (FutureAppSearchSession staticMetadataSearchSession = new FutureAppSearchSessionImpl( mAppSearchManager, AppFunctionExecutors.THREAD_POOL_EXECUTOR, staticMetadataSearchContext); FutureAppSearchSession runtimeMetadataSearchSession = new FutureAppSearchSessionImpl( mAppSearchManager, AppFunctionExecutors.THREAD_POOL_EXECUTOR, runtimeMetadataSearchContext)) { trySyncAppFunctionMetadataBlocking( staticMetadataSearchSession, runtimeMetadataSearchSession); settableSyncStatus.complete(true); } catch (Exception e) { settableSyncStatus.completeExceptionally(e); } catch (Exception ex) { settableSyncStatus.completeExceptionally(ex); } }); return settableSyncStatus; } @WorkerThread private void trySyncAppFunctionMetadataBlocking() @VisibleForTesting void trySyncAppFunctionMetadataBlocking( @NonNull FutureAppSearchSession staticMetadataSearchSession, @NonNull FutureAppSearchSession runtimeMetadataSearchSession) throws ExecutionException, InterruptedException { ArrayMap<String, ArraySet<String>> staticPackageToFunctionMap = getPackageToFunctionIdMap( mStaticMetadataSearchSession, staticMetadataSearchSession, AppFunctionStaticMetadataHelper.STATIC_SCHEMA_TYPE, AppFunctionStaticMetadataHelper.PROPERTY_FUNCTION_ID, AppFunctionStaticMetadataHelper.PROPERTY_PACKAGE_NAME); ArrayMap<String, ArraySet<String>> runtimePackageToFunctionMap = getPackageToFunctionIdMap( mRuntimeMetadataSearchSession, runtimeMetadataSearchSession, RUNTIME_SCHEMA_TYPE, AppFunctionRuntimeMetadata.PROPERTY_FUNCTION_ID, AppFunctionRuntimeMetadata.PROPERTY_PACKAGE_NAME); Loading @@ -134,7 +156,7 @@ public class MetadataSyncAdapter { RemoveByDocumentIdRequest removeByDocumentIdRequest = buildRemoveRuntimeMetadataRequest(removedFunctionsDiffMap); AppSearchBatchResult<String, Void> removeDocumentBatchResult = mRuntimeMetadataSearchSession.remove(removeByDocumentIdRequest).get(); runtimeMetadataSearchSession.remove(removeByDocumentIdRequest).get(); if (!removeDocumentBatchResult.isSuccess()) { throw convertFailedAppSearchResultToException( removeDocumentBatchResult.getFailures().values()); Loading @@ -144,13 +166,14 @@ public class MetadataSyncAdapter { if (!addedFunctionsDiffMap.isEmpty()) { // TODO(b/357551503): only set schema on package diff SetSchemaRequest addSetSchemaRequest = buildSetSchemaRequestForRuntimeMetadataSchemas(appRuntimeMetadataSchemas); buildSetSchemaRequestForRuntimeMetadataSchemas( mPackageManager, appRuntimeMetadataSchemas); Objects.requireNonNull( mRuntimeMetadataSearchSession.setSchema(addSetSchemaRequest).get()); runtimeMetadataSearchSession.setSchema(addSetSchemaRequest).get()); PutDocumentsRequest putDocumentsRequest = buildPutRuntimeMetadataRequest(addedFunctionsDiffMap); AppSearchBatchResult<String, Void> putDocumentBatchResult = mRuntimeMetadataSearchSession.put(putDocumentsRequest).get(); runtimeMetadataSearchSession.put(putDocumentsRequest).get(); if (!putDocumentBatchResult.isSuccess()) { throw convertFailedAppSearchResultToException( putDocumentBatchResult.getFailures().values()); Loading Loading @@ -211,6 +234,7 @@ public class MetadataSyncAdapter { @NonNull private SetSchemaRequest buildSetSchemaRequestForRuntimeMetadataSchemas( @NonNull PackageManager packageManager, @NonNull Set<AppSearchSchema> metadataSchemaSet) { Objects.requireNonNull(metadataSchemaSet); SetSchemaRequest.Builder setSchemaRequestBuilder = Loading @@ -220,7 +244,7 @@ public class MetadataSyncAdapter { String packageName = AppFunctionRuntimeMetadata.getPackageNameFromSchema( runtimeMetadataSchema.getSchemaType()); byte[] packageCert = getCertificate(packageName); byte[] packageCert = getCertificate(packageManager, packageName); if (packageCert == null) { continue; } Loading Loading @@ -399,13 +423,15 @@ public class MetadataSyncAdapter { /** Gets the SHA-256 certificate from a {@link PackageManager}, or null if it is not found. */ @Nullable private byte[] getCertificate(@NonNull String packageName) { private byte[] getCertificate( @NonNull PackageManager packageManager, @NonNull String packageName) { Objects.requireNonNull(packageManager); Objects.requireNonNull(packageName); PackageInfo packageInfo; try { packageInfo = Objects.requireNonNull( mPackageManager.getPackageInfo( packageManager.getPackageInfo( packageName, PackageManager.GET_META_DATA | PackageManager.GET_SIGNING_CERTIFICATES)); Loading
services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java 0 → 100644 +80 −0 Original line number Diff line number Diff line /* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.appfunctions; import android.annotation.Nullable; import android.app.appsearch.AppSearchManager; import android.content.Context; import android.content.pm.PackageManager; import android.os.UserHandle; import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; /** A Singleton class that manages per-user metadata sync adapters. */ public final class MetadataSyncPerUser { private static final String TAG = MetadataSyncPerUser.class.getSimpleName(); /** A map of per-user adapter for synchronizing appFunction metadata. */ @GuardedBy("sLock") private static final SparseArray<MetadataSyncAdapter> sPerUserMetadataSyncAdapter = new SparseArray<>(); private static final Object sLock = new Object(); /** * Returns the per-user metadata sync adapter for the given user. * * @param user The user for which to get the metadata sync adapter. * @param userContext The user context for the given user. * @return The metadata sync adapter for the given user. */ @Nullable public static MetadataSyncAdapter getPerUserMetadataSyncAdapter( UserHandle user, Context userContext) { synchronized (sLock) { MetadataSyncAdapter metadataSyncAdapter = sPerUserMetadataSyncAdapter.get(user.getIdentifier(), null); if (metadataSyncAdapter == null) { AppSearchManager perUserAppSearchManager = userContext.getSystemService(AppSearchManager.class); PackageManager perUserPackageManager = userContext.getPackageManager(); if (perUserAppSearchManager != null) { metadataSyncAdapter = new MetadataSyncAdapter( AppFunctionExecutors.getPerUserSyncExecutor(user), perUserPackageManager, perUserAppSearchManager); sPerUserMetadataSyncAdapter.put(user.getIdentifier(), metadataSyncAdapter); return metadataSyncAdapter; } } return metadataSyncAdapter; } } /** * Removes the per-user metadata sync adapter for the given user. * * @param user The user for which to remove the metadata sync adapter. */ public static void removeUserSyncAdapter(UserHandle user) { synchronized (sLock) { sPerUserMetadataSyncAdapter.remove(user.getIdentifier()); } } }