diff options
author | 2024-09-16 18:57:41 +0000 | |
---|---|---|
committer | 2024-09-17 16:35:18 +0000 | |
commit | e2b61a54b245ed42ccfedd0a72ba88a14edc2d65 (patch) | |
tree | 6dd6a48b30daa34e47dc27db12fecf907444bf2d /services/appfunctions/java | |
parent | 43db9d0d2d5fd85e1ccfe53c2653d415d8b73ae4 (diff) |
Use MetadataSyncAdapter in AppFunctionManagerService.
Flag: android.app.appfunctions.flags.enable_app_function_manager
Test: atest FrameworksAppFunctionsTests -c
Bug: 357551503
Change-Id: Iec9ddab67f40757c72cbf4257f36081189ae40c5
Diffstat (limited to 'services/appfunctions/java')
5 files changed, 374 insertions, 73 deletions
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java index c3b7087a44c3..1f98334bb8ce 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java +++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java @@ -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; @@ -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() {} } diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java index 02800cbf4f3d..c293087defb6 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java +++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerService.java @@ -16,6 +16,7 @@ package com.android.server.appfunctions; +import android.annotation.NonNull; import android.app.appfunctions.AppFunctionManagerConfiguration; import android.content.Context; @@ -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); + } } diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java index 2362b91c826e..cf039df3bc96 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java +++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java @@ -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 { @@ -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), @@ -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); @@ -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) { @@ -132,53 +162,55 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { return; } - var unused = mCallerValidator - .verifyCallerCanExecuteAppFunction( - validatedCallingPackage, - targetPackageName, - requestInternal.getClientRequest().getFunctionIdentifier()) - .thenAccept( - canExecute -> { - if (!canExecute) { - safeExecuteAppFunctionCallback.onResult( - ExecuteAppFunctionResponse.newFailure( - ExecuteAppFunctionResponse.RESULT_DENIED, - "Caller does not have permission to execute the" - + " appfunction", - /* extras= */ null)); - return; - } - Intent serviceIntent = - mInternalServiceHelper.resolveAppFunctionService( - targetPackageName, targetUser); - if (serviceIntent == null) { - safeExecuteAppFunctionCallback.onResult( - ExecuteAppFunctionResponse.newFailure( - ExecuteAppFunctionResponse.RESULT_INTERNAL_ERROR, - "Cannot find the target service.", - /* extras= */ null)); - return; - } - final long token = Binder.clearCallingIdentity(); - try { - bindAppFunctionServiceUnchecked( - requestInternal, - serviceIntent, - targetUser, - safeExecuteAppFunctionCallback, - /* bindFlags= */ Context.BIND_AUTO_CREATE, - /* timeoutInMillis= */ mServiceConfig - .getExecuteAppFunctionTimeoutMillis()); - } finally { - Binder.restoreCallingIdentity(token); - } - }) - .exceptionally( - ex -> { - safeExecuteAppFunctionCallback.onResult( - mapExceptionToExecuteAppFunctionResponse(ex)); - return null; - }); + var unused = + mCallerValidator + .verifyCallerCanExecuteAppFunction( + validatedCallingPackage, + targetPackageName, + requestInternal.getClientRequest().getFunctionIdentifier()) + .thenAccept( + canExecute -> { + if (!canExecute) { + safeExecuteAppFunctionCallback.onResult( + ExecuteAppFunctionResponse.newFailure( + ExecuteAppFunctionResponse.RESULT_DENIED, + "Caller does not have permission to execute" + + " the appfunction", + /* extras= */ null)); + return; + } + Intent serviceIntent = + mInternalServiceHelper.resolveAppFunctionService( + targetPackageName, targetUser); + if (serviceIntent == null) { + safeExecuteAppFunctionCallback.onResult( + ExecuteAppFunctionResponse.newFailure( + ExecuteAppFunctionResponse + .RESULT_INTERNAL_ERROR, + "Cannot find the target service.", + /* extras= */ null)); + return; + } + final long token = Binder.clearCallingIdentity(); + try { + bindAppFunctionServiceUnchecked( + requestInternal, + serviceIntent, + targetUser, + safeExecuteAppFunctionCallback, + /* bindFlags= */ Context.BIND_AUTO_CREATE, + /* timeoutInMillis= */ mServiceConfig + .getExecuteAppFunctionTimeoutMillis()); + } finally { + Binder.restoreCallingIdentity(token); + } + }) + .exceptionally( + ex -> { + safeExecuteAppFunctionCallback.onResult( + mapExceptionToExecuteAppFunctionResponse(ex)); + return null; + }); } private void bindAppFunctionServiceUnchecked( @@ -256,7 +288,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { } private ExecuteAppFunctionResponse mapExceptionToExecuteAppFunctionResponse(Throwable e) { - if(e instanceof CompletionException) { + if (e instanceof CompletionException) { e = e.getCause(); } @@ -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(); + } + } + } + } } diff --git a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java index e2573590bf5d..8c6f50e5c1bd 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java +++ b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java @@ -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; @@ -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 @@ -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); } /** @@ -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); @@ -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()); @@ -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()); @@ -211,6 +234,7 @@ public class MetadataSyncAdapter { @NonNull private SetSchemaRequest buildSetSchemaRequestForRuntimeMetadataSchemas( + @NonNull PackageManager packageManager, @NonNull Set<AppSearchSchema> metadataSchemaSet) { Objects.requireNonNull(metadataSchemaSet); SetSchemaRequest.Builder setSchemaRequestBuilder = @@ -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; } @@ -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)); diff --git a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java new file mode 100644 index 000000000000..f421527e72d0 --- /dev/null +++ b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncPerUser.java @@ -0,0 +1,80 @@ +/* + * 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()); + } + } +} |