Loading core/java/android/app/appfunctions/AppFunctionStaticMetadataHelper.java +2 −0 Original line number Diff line number Diff line Loading @@ -37,6 +37,8 @@ import java.util.Objects; public class AppFunctionStaticMetadataHelper { public static final String STATIC_SCHEMA_TYPE = "AppFunctionStaticMetadata"; public static final String STATIC_PROPERTY_ENABLED_BY_DEFAULT = "enabledByDefault"; public static final String STATIC_PROPERTY_RESTRICT_CALLERS_WITH_EXECUTE_APP_FUNCTIONS = "restrictCallersWithExecuteAppFunctions"; public static final String APP_FUNCTION_STATIC_NAMESPACE = "app_functions"; public static final String PROPERTY_FUNCTION_ID = "functionId"; Loading services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java 0 → 100644 +37 −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 java.util.concurrent.Executor; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** Executors for App function operations. */ public final class AppFunctionExecutors { /** Executor for operations that do not need to block. */ public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor( /* corePoolSize= */ Runtime.getRuntime().availableProcessors(), /* maxConcurrency= */ Runtime.getRuntime().availableProcessors(), /* keepAliveTime= */ 0L, /* unit= */ TimeUnit.SECONDS, /* workQueue= */ new LinkedBlockingQueue<>()); private AppFunctionExecutors() {} } services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java +44 −41 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.server.appfunctions; import static com.android.server.appfunctions.AppFunctionExecutors.THREAD_POOL_EXECUTOR; import android.annotation.NonNull; import android.app.appfunctions.ExecuteAppFunctionAidlRequest; import android.app.appfunctions.ExecuteAppFunctionResponse; Loading Loading @@ -51,14 +53,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { public AppFunctionManagerServiceImpl(@NonNull Context context) { this( new RemoteServiceCallerImpl<>( context, IAppFunctionService.Stub::asInterface, new ThreadPoolExecutor( /* corePoolSize= */ Runtime.getRuntime().availableProcessors(), /* maxConcurrency= */ Runtime.getRuntime().availableProcessors(), /* keepAliveTime= */ 0L, /* unit= */ TimeUnit.SECONDS, /* workQueue= */ new LinkedBlockingQueue<>())), context, IAppFunctionService.Stub::asInterface, THREAD_POOL_EXECUTOR), new CallerValidatorImpl(context), new ServiceHelperImpl(context), new ServiceConfigImpl()); Loading Loading @@ -124,18 +119,25 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { return; } if (!mCallerValidator.verifyCallerCanExecuteAppFunction( validatedCallingPackage, targetPackageName)) { 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", "Caller does not have permission to execute the" + " appfunction", /* extras= */ null)); return; } Intent serviceIntent = mInternalServiceHelper.resolveAppFunctionService(targetPackageName, targetUser); mInternalServiceHelper.resolveAppFunctionService( targetPackageName, targetUser); if (serviceIntent == null) { safeExecuteAppFunctionCallback.onResult( ExecuteAppFunctionResponse.newFailure( Loading @@ -144,7 +146,6 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { /* extras= */ null)); return; } final long token = Binder.clearCallingIdentity(); try { bindAppFunctionServiceUnchecked( Loading @@ -153,10 +154,12 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { targetUser, safeExecuteAppFunctionCallback, /* bindFlags= */ Context.BIND_AUTO_CREATE, /* timeoutInMillis= */ mServiceConfig.getExecuteAppFunctionTimeoutMillis()); /* timeoutInMillis= */ mServiceConfig .getExecuteAppFunctionTimeoutMillis()); } finally { Binder.restoreCallingIdentity(token); } }); } private void bindAppFunctionServiceUnchecked( Loading services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java +6 −2 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.annotation.NonNull; import android.os.UserHandle; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.infra.AndroidFuture; /** * Interface for validating that the caller has the correct privilege to call an AppFunctionManager Loading Loading @@ -65,10 +66,13 @@ public interface CallerValidator { * * @param callerPackageName The calling package (as previously validated). * @param targetPackageName The package that owns the app function to execute. * @param functionId The id of the app function to execute. * @return Whether the caller can execute the specified app function. */ boolean verifyCallerCanExecuteAppFunction( @NonNull String callerPackageName, @NonNull String targetPackageName); AndroidFuture<Boolean> verifyCallerCanExecuteAppFunction( @NonNull String callerPackageName, @NonNull String targetPackageName, @NonNull String functionId); /** * Checks if the user is organization managed. Loading services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java +64 −6 Original line number Diff line number Diff line Loading @@ -16,19 +16,30 @@ package com.android.server.appfunctions; import static android.app.appfunctions.AppFunctionStaticMetadataHelper.APP_FUNCTION_STATIC_METADATA_DB; import static android.app.appfunctions.AppFunctionStaticMetadataHelper.APP_FUNCTION_STATIC_NAMESPACE; import static android.app.appfunctions.AppFunctionStaticMetadataHelper.STATIC_PROPERTY_RESTRICT_CALLERS_WITH_EXECUTE_APP_FUNCTIONS; import static android.app.appfunctions.AppFunctionStaticMetadataHelper.getDocumentIdForAppFunction; import static com.android.server.appfunctions.AppFunctionExecutors.THREAD_POOL_EXECUTOR; import android.Manifest; import android.annotation.BinderThread; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.app.admin.DevicePolicyManager; import android.app.appsearch.AppSearchBatchResult; import android.app.appsearch.AppSearchManager; import android.app.appsearch.AppSearchManager.SearchContext; import android.app.appsearch.GenericDocument; import android.app.appsearch.GetByDocumentIdRequest; import android.content.Context; import android.content.pm.PackageManager; import android.os.Binder; import android.os.Process; import android.os.UserHandle; import android.os.UserManager; import java.util.Objects; import com.android.internal.infra.AndroidFuture; /* Validates that caller has the correct privilege to call an AppFunctionManager Api. */ class CallerValidatorImpl implements CallerValidator { Loading Loading @@ -76,11 +87,12 @@ class CallerValidatorImpl implements CallerValidator { Manifest.permission.EXECUTE_APP_FUNCTIONS }, conditional = true) // TODO(b/360864791): Add and honor apps that opt-out from EXECUTE_APP_FUNCTIONS caller. public boolean verifyCallerCanExecuteAppFunction( @NonNull String callerPackageName, @NonNull String targetPackageName) { public AndroidFuture<Boolean> verifyCallerCanExecuteAppFunction( @NonNull String callerPackageName, @NonNull String targetPackageName, @NonNull String functionId) { if (callerPackageName.equals(targetPackageName)) { return true; return AndroidFuture.completedFuture(true); } int pid = Binder.getCallingPid(); Loading @@ -89,10 +101,56 @@ class CallerValidatorImpl implements CallerValidator { mContext.checkPermission( Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, pid, uid) == PackageManager.PERMISSION_GRANTED; if (hasTrustedExecutionPermission) { return AndroidFuture.completedFuture(true); } boolean hasExecutionPermission = mContext.checkPermission(Manifest.permission.EXECUTE_APP_FUNCTIONS, pid, uid) == PackageManager.PERMISSION_GRANTED; return hasExecutionPermission || hasTrustedExecutionPermission; final long token = Binder.clearCallingIdentity(); try { FutureAppSearchSession futureAppSearchSession = new FutureAppSearchSessionImpl( mContext.getSystemService(AppSearchManager.class), THREAD_POOL_EXECUTOR, new SearchContext.Builder(APP_FUNCTION_STATIC_METADATA_DB).build()); String documentId = getDocumentIdForAppFunction(targetPackageName, functionId); return futureAppSearchSession .getByDocumentId( new GetByDocumentIdRequest.Builder(APP_FUNCTION_STATIC_NAMESPACE) .addIds(documentId) .build()) .thenApply( batchResult -> getGenericDocumentFromBatchResult(batchResult, documentId)) .thenApply( CallerValidatorImpl::getRestrictCallersWithExecuteAppFunctionsProperty) .thenApply( restrictCallersWithExecuteAppFunctions -> !restrictCallersWithExecuteAppFunctions && hasExecutionPermission); } finally { Binder.restoreCallingIdentity(token); } } private static GenericDocument getGenericDocumentFromBatchResult( AppSearchBatchResult<String, GenericDocument> result, String documentId) { if (result.isSuccess()) { return result.getSuccesses().get(documentId); } throw new IllegalArgumentException("No document in the result for id: " + documentId); } private static boolean getRestrictCallersWithExecuteAppFunctionsProperty( GenericDocument genericDocument) { return genericDocument.getPropertyBoolean( STATIC_PROPERTY_RESTRICT_CALLERS_WITH_EXECUTE_APP_FUNCTIONS); } @Override Loading Loading
core/java/android/app/appfunctions/AppFunctionStaticMetadataHelper.java +2 −0 Original line number Diff line number Diff line Loading @@ -37,6 +37,8 @@ import java.util.Objects; public class AppFunctionStaticMetadataHelper { public static final String STATIC_SCHEMA_TYPE = "AppFunctionStaticMetadata"; public static final String STATIC_PROPERTY_ENABLED_BY_DEFAULT = "enabledByDefault"; public static final String STATIC_PROPERTY_RESTRICT_CALLERS_WITH_EXECUTE_APP_FUNCTIONS = "restrictCallersWithExecuteAppFunctions"; public static final String APP_FUNCTION_STATIC_NAMESPACE = "app_functions"; public static final String PROPERTY_FUNCTION_ID = "functionId"; Loading
services/appfunctions/java/com/android/server/appfunctions/AppFunctionExecutors.java 0 → 100644 +37 −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 java.util.concurrent.Executor; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** Executors for App function operations. */ public final class AppFunctionExecutors { /** Executor for operations that do not need to block. */ public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor( /* corePoolSize= */ Runtime.getRuntime().availableProcessors(), /* maxConcurrency= */ Runtime.getRuntime().availableProcessors(), /* keepAliveTime= */ 0L, /* unit= */ TimeUnit.SECONDS, /* workQueue= */ new LinkedBlockingQueue<>()); private AppFunctionExecutors() {} }
services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java +44 −41 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.server.appfunctions; import static com.android.server.appfunctions.AppFunctionExecutors.THREAD_POOL_EXECUTOR; import android.annotation.NonNull; import android.app.appfunctions.ExecuteAppFunctionAidlRequest; import android.app.appfunctions.ExecuteAppFunctionResponse; Loading Loading @@ -51,14 +53,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { public AppFunctionManagerServiceImpl(@NonNull Context context) { this( new RemoteServiceCallerImpl<>( context, IAppFunctionService.Stub::asInterface, new ThreadPoolExecutor( /* corePoolSize= */ Runtime.getRuntime().availableProcessors(), /* maxConcurrency= */ Runtime.getRuntime().availableProcessors(), /* keepAliveTime= */ 0L, /* unit= */ TimeUnit.SECONDS, /* workQueue= */ new LinkedBlockingQueue<>())), context, IAppFunctionService.Stub::asInterface, THREAD_POOL_EXECUTOR), new CallerValidatorImpl(context), new ServiceHelperImpl(context), new ServiceConfigImpl()); Loading Loading @@ -124,18 +119,25 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { return; } if (!mCallerValidator.verifyCallerCanExecuteAppFunction( validatedCallingPackage, targetPackageName)) { 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", "Caller does not have permission to execute the" + " appfunction", /* extras= */ null)); return; } Intent serviceIntent = mInternalServiceHelper.resolveAppFunctionService(targetPackageName, targetUser); mInternalServiceHelper.resolveAppFunctionService( targetPackageName, targetUser); if (serviceIntent == null) { safeExecuteAppFunctionCallback.onResult( ExecuteAppFunctionResponse.newFailure( Loading @@ -144,7 +146,6 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { /* extras= */ null)); return; } final long token = Binder.clearCallingIdentity(); try { bindAppFunctionServiceUnchecked( Loading @@ -153,10 +154,12 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { targetUser, safeExecuteAppFunctionCallback, /* bindFlags= */ Context.BIND_AUTO_CREATE, /* timeoutInMillis= */ mServiceConfig.getExecuteAppFunctionTimeoutMillis()); /* timeoutInMillis= */ mServiceConfig .getExecuteAppFunctionTimeoutMillis()); } finally { Binder.restoreCallingIdentity(token); } }); } private void bindAppFunctionServiceUnchecked( Loading
services/appfunctions/java/com/android/server/appfunctions/CallerValidator.java +6 −2 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import android.annotation.NonNull; import android.os.UserHandle; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.infra.AndroidFuture; /** * Interface for validating that the caller has the correct privilege to call an AppFunctionManager Loading Loading @@ -65,10 +66,13 @@ public interface CallerValidator { * * @param callerPackageName The calling package (as previously validated). * @param targetPackageName The package that owns the app function to execute. * @param functionId The id of the app function to execute. * @return Whether the caller can execute the specified app function. */ boolean verifyCallerCanExecuteAppFunction( @NonNull String callerPackageName, @NonNull String targetPackageName); AndroidFuture<Boolean> verifyCallerCanExecuteAppFunction( @NonNull String callerPackageName, @NonNull String targetPackageName, @NonNull String functionId); /** * Checks if the user is organization managed. Loading
services/appfunctions/java/com/android/server/appfunctions/CallerValidatorImpl.java +64 −6 Original line number Diff line number Diff line Loading @@ -16,19 +16,30 @@ package com.android.server.appfunctions; import static android.app.appfunctions.AppFunctionStaticMetadataHelper.APP_FUNCTION_STATIC_METADATA_DB; import static android.app.appfunctions.AppFunctionStaticMetadataHelper.APP_FUNCTION_STATIC_NAMESPACE; import static android.app.appfunctions.AppFunctionStaticMetadataHelper.STATIC_PROPERTY_RESTRICT_CALLERS_WITH_EXECUTE_APP_FUNCTIONS; import static android.app.appfunctions.AppFunctionStaticMetadataHelper.getDocumentIdForAppFunction; import static com.android.server.appfunctions.AppFunctionExecutors.THREAD_POOL_EXECUTOR; import android.Manifest; import android.annotation.BinderThread; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.app.admin.DevicePolicyManager; import android.app.appsearch.AppSearchBatchResult; import android.app.appsearch.AppSearchManager; import android.app.appsearch.AppSearchManager.SearchContext; import android.app.appsearch.GenericDocument; import android.app.appsearch.GetByDocumentIdRequest; import android.content.Context; import android.content.pm.PackageManager; import android.os.Binder; import android.os.Process; import android.os.UserHandle; import android.os.UserManager; import java.util.Objects; import com.android.internal.infra.AndroidFuture; /* Validates that caller has the correct privilege to call an AppFunctionManager Api. */ class CallerValidatorImpl implements CallerValidator { Loading Loading @@ -76,11 +87,12 @@ class CallerValidatorImpl implements CallerValidator { Manifest.permission.EXECUTE_APP_FUNCTIONS }, conditional = true) // TODO(b/360864791): Add and honor apps that opt-out from EXECUTE_APP_FUNCTIONS caller. public boolean verifyCallerCanExecuteAppFunction( @NonNull String callerPackageName, @NonNull String targetPackageName) { public AndroidFuture<Boolean> verifyCallerCanExecuteAppFunction( @NonNull String callerPackageName, @NonNull String targetPackageName, @NonNull String functionId) { if (callerPackageName.equals(targetPackageName)) { return true; return AndroidFuture.completedFuture(true); } int pid = Binder.getCallingPid(); Loading @@ -89,10 +101,56 @@ class CallerValidatorImpl implements CallerValidator { mContext.checkPermission( Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, pid, uid) == PackageManager.PERMISSION_GRANTED; if (hasTrustedExecutionPermission) { return AndroidFuture.completedFuture(true); } boolean hasExecutionPermission = mContext.checkPermission(Manifest.permission.EXECUTE_APP_FUNCTIONS, pid, uid) == PackageManager.PERMISSION_GRANTED; return hasExecutionPermission || hasTrustedExecutionPermission; final long token = Binder.clearCallingIdentity(); try { FutureAppSearchSession futureAppSearchSession = new FutureAppSearchSessionImpl( mContext.getSystemService(AppSearchManager.class), THREAD_POOL_EXECUTOR, new SearchContext.Builder(APP_FUNCTION_STATIC_METADATA_DB).build()); String documentId = getDocumentIdForAppFunction(targetPackageName, functionId); return futureAppSearchSession .getByDocumentId( new GetByDocumentIdRequest.Builder(APP_FUNCTION_STATIC_NAMESPACE) .addIds(documentId) .build()) .thenApply( batchResult -> getGenericDocumentFromBatchResult(batchResult, documentId)) .thenApply( CallerValidatorImpl::getRestrictCallersWithExecuteAppFunctionsProperty) .thenApply( restrictCallersWithExecuteAppFunctions -> !restrictCallersWithExecuteAppFunctions && hasExecutionPermission); } finally { Binder.restoreCallingIdentity(token); } } private static GenericDocument getGenericDocumentFromBatchResult( AppSearchBatchResult<String, GenericDocument> result, String documentId) { if (result.isSuccess()) { return result.getSuccesses().get(documentId); } throw new IllegalArgumentException("No document in the result for id: " + documentId); } private static boolean getRestrictCallersWithExecuteAppFunctionsProperty( GenericDocument genericDocument) { return genericDocument.getPropertyBoolean( STATIC_PROPERTY_RESTRICT_CALLERS_WITH_EXECUTE_APP_FUNCTIONS); } @Override Loading