Loading core/api/current.txt +6 −0 Original line number Diff line number Diff line Loading @@ -8777,6 +8777,11 @@ package android.app.appfunctions { @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public final class AppFunctionManager { method @Deprecated @RequiresPermission(anyOf={"android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED", "android.permission.EXECUTE_APP_FUNCTIONS"}, conditional=true) public void executeAppFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>); method @RequiresPermission(anyOf={"android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED", "android.permission.EXECUTE_APP_FUNCTIONS"}, conditional=true) public void executeAppFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>); method public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>); method public void setAppFunctionEnabled(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,java.lang.Exception>); field public static final int APP_FUNCTION_STATE_DEFAULT = 0; // 0x0 field public static final int APP_FUNCTION_STATE_DISABLED = 2; // 0x2 field public static final int APP_FUNCTION_STATE_ENABLED = 1; // 0x1 } @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public abstract class AppFunctionService extends android.app.Service { Loading Loading @@ -8818,6 +8823,7 @@ package android.app.appfunctions { field public static final String PROPERTY_RETURN_VALUE = "returnValue"; field public static final int RESULT_APP_UNKNOWN_ERROR = 2; // 0x2 field public static final int RESULT_DENIED = 1; // 0x1 field public static final int RESULT_DISABLED = 6; // 0x6 field public static final int RESULT_INTERNAL_ERROR = 3; // 0x3 field public static final int RESULT_INVALID_ARGUMENT = 4; // 0x4 field public static final int RESULT_OK = 0; // 0x0 core/java/android/app/appfunctions/AppFunctionManager.java +154 −0 Original line number Diff line number Diff line Loading @@ -22,15 +22,21 @@ import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANA import android.Manifest; import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemService; import android.annotation.UserHandleAware; import android.app.appsearch.AppSearchManager; import android.content.Context; import android.os.CancellationSignal; import android.os.ICancellationSignal; import android.os.OutcomeReceiver; import android.os.ParcelableException; import android.os.RemoteException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Objects; import java.util.concurrent.Executor; import java.util.function.Consumer; Loading @@ -45,9 +51,43 @@ import java.util.function.Consumer; @FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER) @SystemService(Context.APP_FUNCTION_SERVICE) public final class AppFunctionManager { /** * The default state of the app function. Call {@link #setAppFunctionEnabled} with this to reset * enabled state to the default value. */ public static final int APP_FUNCTION_STATE_DEFAULT = 0; /** * The app function is enabled. To enable an app function, call {@link #setAppFunctionEnabled} * with this value. */ public static final int APP_FUNCTION_STATE_ENABLED = 1; /** * The app function is disabled. To disable an app function, call {@link #setAppFunctionEnabled} * with this value. */ public static final int APP_FUNCTION_STATE_DISABLED = 2; private final IAppFunctionManager mService; private final Context mContext; /** * The enabled state of the app function. * * @hide */ @IntDef( prefix = {"APP_FUNCTION_STATE_"}, value = { APP_FUNCTION_STATE_DEFAULT, APP_FUNCTION_STATE_ENABLED, APP_FUNCTION_STATE_DISABLED }) @Retention(RetentionPolicy.SOURCE) public @interface EnabledState {} /** * Creates an instance. * Loading Loading @@ -164,4 +204,118 @@ public final class AppFunctionManager { throw e.rethrowFromSystemServer(); } } /** * Returns a boolean through a callback, indicating whether the app function is enabled. * * <p>* This method can only check app functions owned by the caller, or those where the caller * has visibility to the owner package and holds either the {@link * Manifest.permission#EXECUTE_APP_FUNCTIONS} or {@link * Manifest.permission#EXECUTE_APP_FUNCTIONS_TRUSTED} permission. * * <p>If operation fails, the callback's {@link OutcomeReceiver#onError} is called with errors: * * <ul> * <li>{@link IllegalArgumentException}, if the function is not found or the caller does not * have access to it. * </ul> * * @param functionIdentifier the identifier of the app function to check (unique within the * target package) and in most cases, these are automatically generated by the AppFunctions * SDK * @param targetPackage the package name of the app function's owner * @param executor the executor to run the request * @param callback the callback to receive the function enabled check result */ public void isAppFunctionEnabled( @NonNull String functionIdentifier, @NonNull String targetPackage, @NonNull Executor executor, @NonNull OutcomeReceiver<Boolean, Exception> callback) { Objects.requireNonNull(functionIdentifier); Objects.requireNonNull(targetPackage); Objects.requireNonNull(executor); Objects.requireNonNull(callback); AppSearchManager appSearchManager = mContext.getSystemService(AppSearchManager.class); if (appSearchManager == null) { callback.onError(new IllegalStateException("Failed to get AppSearchManager.")); return; } AppFunctionManagerHelper.isAppFunctionEnabled( functionIdentifier, targetPackage, appSearchManager, executor, callback); } /** * Sets the enabled state of the app function owned by the calling package. * * <p>If operation fails, the callback's {@link OutcomeReceiver#onError} is called with errors: * * <ul> * <li>{@link IllegalArgumentException}, if the function is not found or the caller does not * have access to it. * </ul> * * @param functionIdentifier the identifier of the app function to enable (unique within the * calling package). In most cases, identifiers are automatically generated by the * AppFunctions SDK * @param newEnabledState the new state of the app function * @param executor the executor to run the callback * @param callback the callback to receive the result of the function enablement. The call was * successful if no exception was thrown. */ @UserHandleAware public void setAppFunctionEnabled( @NonNull String functionIdentifier, @EnabledState int newEnabledState, @NonNull Executor executor, @NonNull OutcomeReceiver<Void, Exception> callback) { Objects.requireNonNull(functionIdentifier); Objects.requireNonNull(executor); Objects.requireNonNull(callback); CallbackWrapper callbackWrapper = new CallbackWrapper(executor, callback); try { mService.setAppFunctionEnabled( mContext.getPackageName(), functionIdentifier, mContext.getUser(), newEnabledState, callbackWrapper); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } private static class CallbackWrapper extends IAppFunctionEnabledCallback.Stub { private final OutcomeReceiver<Void, Exception> mCallback; private final Executor mExecutor; CallbackWrapper( @NonNull Executor callbackExecutor, @NonNull OutcomeReceiver<Void, Exception> callback) { mCallback = callback; mExecutor = callbackExecutor; } @Override public void onSuccess() { mExecutor.execute(() -> mCallback.onResult(null)); } @Override public void onError(@NonNull ParcelableException exception) { mExecutor.execute(() -> { if (IllegalArgumentException.class.isAssignableFrom( exception.getCause().getClass())) { mCallback.onError((IllegalArgumentException) exception.getCause()); } else if (SecurityException.class.isAssignableFrom( exception.getCause().getClass())) { mCallback.onError((SecurityException) exception.getCause()); } else { mCallback.onError(exception); } }); } } } core/java/android/app/appfunctions/AppFunctionManagerHelper.java +81 −79 Original line number Diff line number Diff line Loading @@ -22,7 +22,7 @@ import static android.app.appfunctions.AppFunctionStaticMetadataHelper.APP_FUNCT import static android.app.appfunctions.AppFunctionStaticMetadataHelper.STATIC_PROPERTY_ENABLED_BY_DEFAULT; import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER; import android.annotation.CallbackExecutor; import android.Manifest; import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.app.appsearch.AppSearchManager; Loading @@ -33,6 +33,7 @@ import android.app.appsearch.SearchResult; import android.app.appsearch.SearchResults; import android.app.appsearch.SearchSpec; import android.os.OutcomeReceiver; import android.text.TextUtils; import java.io.IOException; import java.util.List; Loading @@ -50,23 +51,24 @@ public class AppFunctionManagerHelper { /** * Returns (through a callback) a boolean indicating whether the app function is enabled. * * <p>This method can only check app functions that are owned by the caller owned by packages * visible to the caller. * This method can only check app functions owned by the caller, or those where the caller * has visibility to the owner package and holds either the {@link * Manifest.permission#EXECUTE_APP_FUNCTIONS} or {@link * Manifest.permission#EXECUTE_APP_FUNCTIONS_TRUSTED} permission. * * <p>If operation fails, the callback's {@link OutcomeReceiver#onError} is called with errors: * * <ul> * <li>{@link IllegalArgumentException}, if the function is not found * <li>{@link SecurityException}, if the caller does not have permission to query the target * package * <li>{@link IllegalArgumentException}, if the function is not found or the caller does not * have access to it. * </ul> * * @param functionIdentifier the identifier of the app function to check (unique within the * target package) and in most cases, these are automatically generated by the AppFunctions * target package) and in most cases, these are automatically * generated by the AppFunctions * SDK * @param targetPackage the package name of the app function's owner * @param appSearchExecutor the executor to run the metadata search mechanism through AppSearch * @param callbackExecutor the executor to run the callback * @param executor executor the executor to run the request * @param callback the callback to receive the function enabled check result * @hide */ Loading @@ -74,49 +76,44 @@ public class AppFunctionManagerHelper { @NonNull String functionIdentifier, @NonNull String targetPackage, @NonNull AppSearchManager appSearchManager, @NonNull Executor appSearchExecutor, @NonNull @CallbackExecutor Executor callbackExecutor, @NonNull Executor executor, @NonNull OutcomeReceiver<Boolean, Exception> callback) { Objects.requireNonNull(functionIdentifier); Objects.requireNonNull(targetPackage); Objects.requireNonNull(appSearchManager); Objects.requireNonNull(appSearchExecutor); Objects.requireNonNull(callbackExecutor); Objects.requireNonNull(executor); Objects.requireNonNull(callback); appSearchManager.createGlobalSearchSession( appSearchExecutor, executor, (searchSessionResult) -> { if (!searchSessionResult.isSuccess()) { callbackExecutor.execute( () -> callback.onError( failedResultToException(searchSessionResult))); callback.onError(failedResultToException(searchSessionResult)); return; } try (GlobalSearchSession searchSession = searchSessionResult.getResultValue()) { SearchResults results = searchJoinedStaticWithRuntimeAppFunctions( searchSession, targetPackage, functionIdentifier); Objects.requireNonNull(searchSession), targetPackage, functionIdentifier); results.getNextPage( appSearchExecutor, listAppSearchResult -> callbackExecutor.execute( () -> { executor, listAppSearchResult -> { if (listAppSearchResult.isSuccess()) { callback.onResult( getEnabledStateFromSearchResults( getEffectiveEnabledStateFromSearchResults( Objects.requireNonNull( listAppSearchResult .getResultValue()))); } else { callback.onError( failedResultToException( listAppSearchResult)); failedResultToException(listAppSearchResult)); } })); }); results.close(); } catch (Exception e) { callbackExecutor.execute(() -> callback.onError(e)); callback.onError(e); } }); } Loading @@ -124,56 +121,58 @@ public class AppFunctionManagerHelper { /** * Searches joined app function static and runtime metadata using the function Id and the * package. * * @hide */ private static @NonNull SearchResults searchJoinedStaticWithRuntimeAppFunctions( @NonNull GlobalSearchSession session, @NonNull String targetPackage, @NonNull String functionIdentifier) { SearchSpec runtimeSearchSpec = getAppFunctionRuntimeMetadataSearchSpecByFunctionId(targetPackage); getAppFunctionRuntimeMetadataSearchSpecByPackageName(targetPackage); JoinSpec joinSpec = new JoinSpec.Builder(PROPERTY_APP_FUNCTION_STATIC_METADATA_QUALIFIED_ID) .setNestedSearch(functionIdentifier, runtimeSearchSpec) .setNestedSearch( buildFilerRuntimeMetadataByFunctionIdQuery(functionIdentifier), runtimeSearchSpec) .build(); SearchSpec joinedStaticWithRuntimeSearchSpec = new SearchSpec.Builder() .setJoinSpec(joinSpec) .addFilterPackageNames(APP_FUNCTION_INDEXER_PACKAGE) .addFilterSchemas( AppFunctionStaticMetadataHelper.getStaticSchemaNameForPackage( targetPackage)) .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY) .setJoinSpec(joinSpec) .setVerbatimSearchEnabled(true) .build(); return session.search(functionIdentifier, joinedStaticWithRuntimeSearchSpec); return session.search( buildFilerStaticMetadataByFunctionIdQuery(functionIdentifier), joinedStaticWithRuntimeSearchSpec); } /** * Finds whether the function is enabled or not from the search results returned by {@link * #searchJoinedStaticWithRuntimeAppFunctions}. * Returns whether the function is effectively enabled or not from the search results returned * by {@link #searchJoinedStaticWithRuntimeAppFunctions}. * * @param joinedStaticRuntimeResults search results joining AppFunctionStaticMetadata * and AppFunctionRuntimeMetadata. * @throws IllegalArgumentException if the function is not found in the results * @hide */ private static boolean getEnabledStateFromSearchResults( private static boolean getEffectiveEnabledStateFromSearchResults( @NonNull List<SearchResult> joinedStaticRuntimeResults) { if (joinedStaticRuntimeResults.isEmpty()) { // Function not found. throw new IllegalArgumentException("App function not found."); } else { List<SearchResult> runtimeMetadataResults = joinedStaticRuntimeResults.getFirst().getJoinedResults(); if (!runtimeMetadataResults.isEmpty()) { Boolean result = (Boolean) if (runtimeMetadataResults.isEmpty()) { throw new IllegalArgumentException("App function not found."); } boolean[] enabled = runtimeMetadataResults .getFirst() .getGenericDocument() .getProperty(PROPERTY_ENABLED); if (result != null) { return result; } .getPropertyBooleanArray(PROPERTY_ENABLED); if (enabled != null && enabled.length != 0) { return enabled[0]; } // Runtime metadata not found. Using the default value in the static metadata. return joinedStaticRuntimeResults Loading @@ -186,36 +185,39 @@ public class AppFunctionManagerHelper { /** * Returns search spec that queries app function metadata for a specific package name by its * function identifier. * * @hide */ public static @NonNull SearchSpec getAppFunctionRuntimeMetadataSearchSpecByFunctionId( private static @NonNull SearchSpec getAppFunctionRuntimeMetadataSearchSpecByPackageName( @NonNull String targetPackage) { return new SearchSpec.Builder() .addFilterPackageNames(APP_FUNCTION_INDEXER_PACKAGE) .addFilterSchemas( AppFunctionRuntimeMetadata.getRuntimeSchemaNameForPackage(targetPackage)) .addFilterProperties( AppFunctionRuntimeMetadata.getRuntimeSchemaNameForPackage(targetPackage), List.of(AppFunctionRuntimeMetadata.PROPERTY_FUNCTION_ID)) .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY) .setVerbatimSearchEnabled(true) .build(); } /** * Converts a failed app search result codes into an exception. * * @hide */ public static @NonNull Exception failedResultToException( private static String buildFilerRuntimeMetadataByFunctionIdQuery(String functionIdentifier) { return TextUtils.formatSimple("%s:\"%s\"", AppFunctionRuntimeMetadata.PROPERTY_FUNCTION_ID, functionIdentifier); } private static String buildFilerStaticMetadataByFunctionIdQuery(String functionIdentifier) { return TextUtils.formatSimple("%s:\"%s\"", AppFunctionStaticMetadataHelper.PROPERTY_FUNCTION_ID, functionIdentifier); } /** Converts a failed app search result codes into an exception. */ private static @NonNull Exception failedResultToException( @NonNull AppSearchResult appSearchResult) { return switch (appSearchResult.getResultCode()) { case AppSearchResult.RESULT_INVALID_ARGUMENT -> new IllegalArgumentException(appSearchResult.getErrorMessage()); case AppSearchResult.RESULT_IO_ERROR -> new IOException(appSearchResult.getErrorMessage()); case AppSearchResult.RESULT_SECURITY_ERROR -> new SecurityException(appSearchResult.getErrorMessage()); case AppSearchResult.RESULT_INVALID_ARGUMENT -> new IllegalArgumentException( appSearchResult.getErrorMessage()); case AppSearchResult.RESULT_IO_ERROR -> new IOException( appSearchResult.getErrorMessage()); case AppSearchResult.RESULT_SECURITY_ERROR -> new SecurityException( appSearchResult.getErrorMessage()); default -> new IllegalStateException(appSearchResult.getErrorMessage()); }; } Loading core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java +8 −2 Original line number Diff line number Diff line Loading @@ -204,11 +204,17 @@ public class AppFunctionRuntimeMetadata extends GenericDocument { packageName, functionId)); } public Builder(AppFunctionRuntimeMetadata original) { this(original.getPackageName(), original.getFunctionId()); setEnabled(original.getEnabled()); } /** * Sets an indicator specifying if the function is enabled or not. This would override the * default enabled state in the static metadata ({@link * AppFunctionStaticMetadataHelper#STATIC_PROPERTY_ENABLED_BY_DEFAULT}). Sets this to * null to clear the override. * AppFunctionStaticMetadataHelper#STATIC_PROPERTY_ENABLED_BY_DEFAULT}). Sets this to null * to clear the override. * TODO(369683073) Replace the tristate Boolean with IntDef EnabledState. */ @NonNull public Builder setEnabled(@Nullable Boolean enabled) { Loading core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java +4 −0 Original line number Diff line number Diff line Loading @@ -99,6 +99,9 @@ public final class ExecuteAppFunctionResponse implements Parcelable { /** The operation was timed out. */ public static final int RESULT_TIMED_OUT = 5; /** The caller tried to execute a disabled app function. */ public static final int RESULT_DISABLED = 6; /** The result code of the app function execution. */ @ResultCode private final int mResultCode; Loading Loading @@ -274,6 +277,7 @@ public final class ExecuteAppFunctionResponse implements Parcelable { RESULT_INTERNAL_ERROR, RESULT_INVALID_ARGUMENT, RESULT_TIMED_OUT, RESULT_DISABLED, }) @Retention(RetentionPolicy.SOURCE) public @interface ResultCode {} Loading Loading
core/api/current.txt +6 −0 Original line number Diff line number Diff line Loading @@ -8777,6 +8777,11 @@ package android.app.appfunctions { @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public final class AppFunctionManager { method @Deprecated @RequiresPermission(anyOf={"android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED", "android.permission.EXECUTE_APP_FUNCTIONS"}, conditional=true) public void executeAppFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>); method @RequiresPermission(anyOf={"android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED", "android.permission.EXECUTE_APP_FUNCTIONS"}, conditional=true) public void executeAppFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>); method public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>); method public void setAppFunctionEnabled(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,java.lang.Exception>); field public static final int APP_FUNCTION_STATE_DEFAULT = 0; // 0x0 field public static final int APP_FUNCTION_STATE_DISABLED = 2; // 0x2 field public static final int APP_FUNCTION_STATE_ENABLED = 1; // 0x1 } @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public abstract class AppFunctionService extends android.app.Service { Loading Loading @@ -8818,6 +8823,7 @@ package android.app.appfunctions { field public static final String PROPERTY_RETURN_VALUE = "returnValue"; field public static final int RESULT_APP_UNKNOWN_ERROR = 2; // 0x2 field public static final int RESULT_DENIED = 1; // 0x1 field public static final int RESULT_DISABLED = 6; // 0x6 field public static final int RESULT_INTERNAL_ERROR = 3; // 0x3 field public static final int RESULT_INVALID_ARGUMENT = 4; // 0x4 field public static final int RESULT_OK = 0; // 0x0
core/java/android/app/appfunctions/AppFunctionManager.java +154 −0 Original line number Diff line number Diff line Loading @@ -22,15 +22,21 @@ import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANA import android.Manifest; import android.annotation.CallbackExecutor; import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemService; import android.annotation.UserHandleAware; import android.app.appsearch.AppSearchManager; import android.content.Context; import android.os.CancellationSignal; import android.os.ICancellationSignal; import android.os.OutcomeReceiver; import android.os.ParcelableException; import android.os.RemoteException; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Objects; import java.util.concurrent.Executor; import java.util.function.Consumer; Loading @@ -45,9 +51,43 @@ import java.util.function.Consumer; @FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER) @SystemService(Context.APP_FUNCTION_SERVICE) public final class AppFunctionManager { /** * The default state of the app function. Call {@link #setAppFunctionEnabled} with this to reset * enabled state to the default value. */ public static final int APP_FUNCTION_STATE_DEFAULT = 0; /** * The app function is enabled. To enable an app function, call {@link #setAppFunctionEnabled} * with this value. */ public static final int APP_FUNCTION_STATE_ENABLED = 1; /** * The app function is disabled. To disable an app function, call {@link #setAppFunctionEnabled} * with this value. */ public static final int APP_FUNCTION_STATE_DISABLED = 2; private final IAppFunctionManager mService; private final Context mContext; /** * The enabled state of the app function. * * @hide */ @IntDef( prefix = {"APP_FUNCTION_STATE_"}, value = { APP_FUNCTION_STATE_DEFAULT, APP_FUNCTION_STATE_ENABLED, APP_FUNCTION_STATE_DISABLED }) @Retention(RetentionPolicy.SOURCE) public @interface EnabledState {} /** * Creates an instance. * Loading Loading @@ -164,4 +204,118 @@ public final class AppFunctionManager { throw e.rethrowFromSystemServer(); } } /** * Returns a boolean through a callback, indicating whether the app function is enabled. * * <p>* This method can only check app functions owned by the caller, or those where the caller * has visibility to the owner package and holds either the {@link * Manifest.permission#EXECUTE_APP_FUNCTIONS} or {@link * Manifest.permission#EXECUTE_APP_FUNCTIONS_TRUSTED} permission. * * <p>If operation fails, the callback's {@link OutcomeReceiver#onError} is called with errors: * * <ul> * <li>{@link IllegalArgumentException}, if the function is not found or the caller does not * have access to it. * </ul> * * @param functionIdentifier the identifier of the app function to check (unique within the * target package) and in most cases, these are automatically generated by the AppFunctions * SDK * @param targetPackage the package name of the app function's owner * @param executor the executor to run the request * @param callback the callback to receive the function enabled check result */ public void isAppFunctionEnabled( @NonNull String functionIdentifier, @NonNull String targetPackage, @NonNull Executor executor, @NonNull OutcomeReceiver<Boolean, Exception> callback) { Objects.requireNonNull(functionIdentifier); Objects.requireNonNull(targetPackage); Objects.requireNonNull(executor); Objects.requireNonNull(callback); AppSearchManager appSearchManager = mContext.getSystemService(AppSearchManager.class); if (appSearchManager == null) { callback.onError(new IllegalStateException("Failed to get AppSearchManager.")); return; } AppFunctionManagerHelper.isAppFunctionEnabled( functionIdentifier, targetPackage, appSearchManager, executor, callback); } /** * Sets the enabled state of the app function owned by the calling package. * * <p>If operation fails, the callback's {@link OutcomeReceiver#onError} is called with errors: * * <ul> * <li>{@link IllegalArgumentException}, if the function is not found or the caller does not * have access to it. * </ul> * * @param functionIdentifier the identifier of the app function to enable (unique within the * calling package). In most cases, identifiers are automatically generated by the * AppFunctions SDK * @param newEnabledState the new state of the app function * @param executor the executor to run the callback * @param callback the callback to receive the result of the function enablement. The call was * successful if no exception was thrown. */ @UserHandleAware public void setAppFunctionEnabled( @NonNull String functionIdentifier, @EnabledState int newEnabledState, @NonNull Executor executor, @NonNull OutcomeReceiver<Void, Exception> callback) { Objects.requireNonNull(functionIdentifier); Objects.requireNonNull(executor); Objects.requireNonNull(callback); CallbackWrapper callbackWrapper = new CallbackWrapper(executor, callback); try { mService.setAppFunctionEnabled( mContext.getPackageName(), functionIdentifier, mContext.getUser(), newEnabledState, callbackWrapper); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } private static class CallbackWrapper extends IAppFunctionEnabledCallback.Stub { private final OutcomeReceiver<Void, Exception> mCallback; private final Executor mExecutor; CallbackWrapper( @NonNull Executor callbackExecutor, @NonNull OutcomeReceiver<Void, Exception> callback) { mCallback = callback; mExecutor = callbackExecutor; } @Override public void onSuccess() { mExecutor.execute(() -> mCallback.onResult(null)); } @Override public void onError(@NonNull ParcelableException exception) { mExecutor.execute(() -> { if (IllegalArgumentException.class.isAssignableFrom( exception.getCause().getClass())) { mCallback.onError((IllegalArgumentException) exception.getCause()); } else if (SecurityException.class.isAssignableFrom( exception.getCause().getClass())) { mCallback.onError((SecurityException) exception.getCause()); } else { mCallback.onError(exception); } }); } } }
core/java/android/app/appfunctions/AppFunctionManagerHelper.java +81 −79 Original line number Diff line number Diff line Loading @@ -22,7 +22,7 @@ import static android.app.appfunctions.AppFunctionStaticMetadataHelper.APP_FUNCT import static android.app.appfunctions.AppFunctionStaticMetadataHelper.STATIC_PROPERTY_ENABLED_BY_DEFAULT; import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER; import android.annotation.CallbackExecutor; import android.Manifest; import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.app.appsearch.AppSearchManager; Loading @@ -33,6 +33,7 @@ import android.app.appsearch.SearchResult; import android.app.appsearch.SearchResults; import android.app.appsearch.SearchSpec; import android.os.OutcomeReceiver; import android.text.TextUtils; import java.io.IOException; import java.util.List; Loading @@ -50,23 +51,24 @@ public class AppFunctionManagerHelper { /** * Returns (through a callback) a boolean indicating whether the app function is enabled. * * <p>This method can only check app functions that are owned by the caller owned by packages * visible to the caller. * This method can only check app functions owned by the caller, or those where the caller * has visibility to the owner package and holds either the {@link * Manifest.permission#EXECUTE_APP_FUNCTIONS} or {@link * Manifest.permission#EXECUTE_APP_FUNCTIONS_TRUSTED} permission. * * <p>If operation fails, the callback's {@link OutcomeReceiver#onError} is called with errors: * * <ul> * <li>{@link IllegalArgumentException}, if the function is not found * <li>{@link SecurityException}, if the caller does not have permission to query the target * package * <li>{@link IllegalArgumentException}, if the function is not found or the caller does not * have access to it. * </ul> * * @param functionIdentifier the identifier of the app function to check (unique within the * target package) and in most cases, these are automatically generated by the AppFunctions * target package) and in most cases, these are automatically * generated by the AppFunctions * SDK * @param targetPackage the package name of the app function's owner * @param appSearchExecutor the executor to run the metadata search mechanism through AppSearch * @param callbackExecutor the executor to run the callback * @param executor executor the executor to run the request * @param callback the callback to receive the function enabled check result * @hide */ Loading @@ -74,49 +76,44 @@ public class AppFunctionManagerHelper { @NonNull String functionIdentifier, @NonNull String targetPackage, @NonNull AppSearchManager appSearchManager, @NonNull Executor appSearchExecutor, @NonNull @CallbackExecutor Executor callbackExecutor, @NonNull Executor executor, @NonNull OutcomeReceiver<Boolean, Exception> callback) { Objects.requireNonNull(functionIdentifier); Objects.requireNonNull(targetPackage); Objects.requireNonNull(appSearchManager); Objects.requireNonNull(appSearchExecutor); Objects.requireNonNull(callbackExecutor); Objects.requireNonNull(executor); Objects.requireNonNull(callback); appSearchManager.createGlobalSearchSession( appSearchExecutor, executor, (searchSessionResult) -> { if (!searchSessionResult.isSuccess()) { callbackExecutor.execute( () -> callback.onError( failedResultToException(searchSessionResult))); callback.onError(failedResultToException(searchSessionResult)); return; } try (GlobalSearchSession searchSession = searchSessionResult.getResultValue()) { SearchResults results = searchJoinedStaticWithRuntimeAppFunctions( searchSession, targetPackage, functionIdentifier); Objects.requireNonNull(searchSession), targetPackage, functionIdentifier); results.getNextPage( appSearchExecutor, listAppSearchResult -> callbackExecutor.execute( () -> { executor, listAppSearchResult -> { if (listAppSearchResult.isSuccess()) { callback.onResult( getEnabledStateFromSearchResults( getEffectiveEnabledStateFromSearchResults( Objects.requireNonNull( listAppSearchResult .getResultValue()))); } else { callback.onError( failedResultToException( listAppSearchResult)); failedResultToException(listAppSearchResult)); } })); }); results.close(); } catch (Exception e) { callbackExecutor.execute(() -> callback.onError(e)); callback.onError(e); } }); } Loading @@ -124,56 +121,58 @@ public class AppFunctionManagerHelper { /** * Searches joined app function static and runtime metadata using the function Id and the * package. * * @hide */ private static @NonNull SearchResults searchJoinedStaticWithRuntimeAppFunctions( @NonNull GlobalSearchSession session, @NonNull String targetPackage, @NonNull String functionIdentifier) { SearchSpec runtimeSearchSpec = getAppFunctionRuntimeMetadataSearchSpecByFunctionId(targetPackage); getAppFunctionRuntimeMetadataSearchSpecByPackageName(targetPackage); JoinSpec joinSpec = new JoinSpec.Builder(PROPERTY_APP_FUNCTION_STATIC_METADATA_QUALIFIED_ID) .setNestedSearch(functionIdentifier, runtimeSearchSpec) .setNestedSearch( buildFilerRuntimeMetadataByFunctionIdQuery(functionIdentifier), runtimeSearchSpec) .build(); SearchSpec joinedStaticWithRuntimeSearchSpec = new SearchSpec.Builder() .setJoinSpec(joinSpec) .addFilterPackageNames(APP_FUNCTION_INDEXER_PACKAGE) .addFilterSchemas( AppFunctionStaticMetadataHelper.getStaticSchemaNameForPackage( targetPackage)) .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY) .setJoinSpec(joinSpec) .setVerbatimSearchEnabled(true) .build(); return session.search(functionIdentifier, joinedStaticWithRuntimeSearchSpec); return session.search( buildFilerStaticMetadataByFunctionIdQuery(functionIdentifier), joinedStaticWithRuntimeSearchSpec); } /** * Finds whether the function is enabled or not from the search results returned by {@link * #searchJoinedStaticWithRuntimeAppFunctions}. * Returns whether the function is effectively enabled or not from the search results returned * by {@link #searchJoinedStaticWithRuntimeAppFunctions}. * * @param joinedStaticRuntimeResults search results joining AppFunctionStaticMetadata * and AppFunctionRuntimeMetadata. * @throws IllegalArgumentException if the function is not found in the results * @hide */ private static boolean getEnabledStateFromSearchResults( private static boolean getEffectiveEnabledStateFromSearchResults( @NonNull List<SearchResult> joinedStaticRuntimeResults) { if (joinedStaticRuntimeResults.isEmpty()) { // Function not found. throw new IllegalArgumentException("App function not found."); } else { List<SearchResult> runtimeMetadataResults = joinedStaticRuntimeResults.getFirst().getJoinedResults(); if (!runtimeMetadataResults.isEmpty()) { Boolean result = (Boolean) if (runtimeMetadataResults.isEmpty()) { throw new IllegalArgumentException("App function not found."); } boolean[] enabled = runtimeMetadataResults .getFirst() .getGenericDocument() .getProperty(PROPERTY_ENABLED); if (result != null) { return result; } .getPropertyBooleanArray(PROPERTY_ENABLED); if (enabled != null && enabled.length != 0) { return enabled[0]; } // Runtime metadata not found. Using the default value in the static metadata. return joinedStaticRuntimeResults Loading @@ -186,36 +185,39 @@ public class AppFunctionManagerHelper { /** * Returns search spec that queries app function metadata for a specific package name by its * function identifier. * * @hide */ public static @NonNull SearchSpec getAppFunctionRuntimeMetadataSearchSpecByFunctionId( private static @NonNull SearchSpec getAppFunctionRuntimeMetadataSearchSpecByPackageName( @NonNull String targetPackage) { return new SearchSpec.Builder() .addFilterPackageNames(APP_FUNCTION_INDEXER_PACKAGE) .addFilterSchemas( AppFunctionRuntimeMetadata.getRuntimeSchemaNameForPackage(targetPackage)) .addFilterProperties( AppFunctionRuntimeMetadata.getRuntimeSchemaNameForPackage(targetPackage), List.of(AppFunctionRuntimeMetadata.PROPERTY_FUNCTION_ID)) .setTermMatch(SearchSpec.TERM_MATCH_EXACT_ONLY) .setVerbatimSearchEnabled(true) .build(); } /** * Converts a failed app search result codes into an exception. * * @hide */ public static @NonNull Exception failedResultToException( private static String buildFilerRuntimeMetadataByFunctionIdQuery(String functionIdentifier) { return TextUtils.formatSimple("%s:\"%s\"", AppFunctionRuntimeMetadata.PROPERTY_FUNCTION_ID, functionIdentifier); } private static String buildFilerStaticMetadataByFunctionIdQuery(String functionIdentifier) { return TextUtils.formatSimple("%s:\"%s\"", AppFunctionStaticMetadataHelper.PROPERTY_FUNCTION_ID, functionIdentifier); } /** Converts a failed app search result codes into an exception. */ private static @NonNull Exception failedResultToException( @NonNull AppSearchResult appSearchResult) { return switch (appSearchResult.getResultCode()) { case AppSearchResult.RESULT_INVALID_ARGUMENT -> new IllegalArgumentException(appSearchResult.getErrorMessage()); case AppSearchResult.RESULT_IO_ERROR -> new IOException(appSearchResult.getErrorMessage()); case AppSearchResult.RESULT_SECURITY_ERROR -> new SecurityException(appSearchResult.getErrorMessage()); case AppSearchResult.RESULT_INVALID_ARGUMENT -> new IllegalArgumentException( appSearchResult.getErrorMessage()); case AppSearchResult.RESULT_IO_ERROR -> new IOException( appSearchResult.getErrorMessage()); case AppSearchResult.RESULT_SECURITY_ERROR -> new SecurityException( appSearchResult.getErrorMessage()); default -> new IllegalStateException(appSearchResult.getErrorMessage()); }; } Loading
core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java +8 −2 Original line number Diff line number Diff line Loading @@ -204,11 +204,17 @@ public class AppFunctionRuntimeMetadata extends GenericDocument { packageName, functionId)); } public Builder(AppFunctionRuntimeMetadata original) { this(original.getPackageName(), original.getFunctionId()); setEnabled(original.getEnabled()); } /** * Sets an indicator specifying if the function is enabled or not. This would override the * default enabled state in the static metadata ({@link * AppFunctionStaticMetadataHelper#STATIC_PROPERTY_ENABLED_BY_DEFAULT}). Sets this to * null to clear the override. * AppFunctionStaticMetadataHelper#STATIC_PROPERTY_ENABLED_BY_DEFAULT}). Sets this to null * to clear the override. * TODO(369683073) Replace the tristate Boolean with IntDef EnabledState. */ @NonNull public Builder setEnabled(@Nullable Boolean enabled) { Loading
core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java +4 −0 Original line number Diff line number Diff line Loading @@ -99,6 +99,9 @@ public final class ExecuteAppFunctionResponse implements Parcelable { /** The operation was timed out. */ public static final int RESULT_TIMED_OUT = 5; /** The caller tried to execute a disabled app function. */ public static final int RESULT_DISABLED = 6; /** The result code of the app function execution. */ @ResultCode private final int mResultCode; Loading Loading @@ -274,6 +277,7 @@ public final class ExecuteAppFunctionResponse implements Parcelable { RESULT_INTERNAL_ERROR, RESULT_INVALID_ARGUMENT, RESULT_TIMED_OUT, RESULT_DISABLED, }) @Retention(RetentionPolicy.SOURCE) public @interface ResultCode {} Loading