• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.app.appfunctions;
18 
19 import static android.app.appfunctions.AppFunctionManager.APP_FUNCTION_STATE_DEFAULT;
20 import static android.app.appfunctions.AppFunctionManager.APP_FUNCTION_STATE_ENABLED;
21 import static android.app.appfunctions.AppFunctionRuntimeMetadata.PROPERTY_APP_FUNCTION_STATIC_METADATA_QUALIFIED_ID;
22 import static android.app.appfunctions.AppFunctionRuntimeMetadata.PROPERTY_ENABLED;
23 import static android.app.appfunctions.AppFunctionStaticMetadataHelper.APP_FUNCTION_INDEXER_PACKAGE;
24 import static android.app.appfunctions.AppFunctionStaticMetadataHelper.STATIC_PROPERTY_ENABLED_BY_DEFAULT;
25 import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER;
26 
27 import android.Manifest;
28 import android.annotation.FlaggedApi;
29 import android.annotation.NonNull;
30 import android.app.appsearch.AppSearchManager;
31 import android.app.appsearch.AppSearchResult;
32 import android.app.appsearch.GlobalSearchSession;
33 import android.app.appsearch.JoinSpec;
34 import android.app.appsearch.PropertyPath;
35 import android.app.appsearch.SearchResult;
36 import android.app.appsearch.SearchResults;
37 import android.app.appsearch.SearchSpec;
38 import android.os.OutcomeReceiver;
39 import android.text.TextUtils;
40 
41 import java.io.IOException;
42 import java.util.List;
43 import java.util.Objects;
44 import java.util.concurrent.Executor;
45 
46 /**
47  * Helper class containing utilities for {@link AppFunctionManager}.
48  *
49  * @hide
50  */
51 @FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
52 public class AppFunctionManagerHelper {
53 
54     /**
55      * Returns (through a callback) a boolean indicating whether the app function is enabled.
56      *
57      * This method can only check app functions owned by the caller, or those where the caller
58      * has visibility to the owner package and holds the {@link
59      * Manifest.permission#EXECUTE_APP_FUNCTIONS} permission.
60      *
61      * <p>If operation fails, the callback's {@link OutcomeReceiver#onError} is called with errors:
62      *
63      * <ul>
64      *   <li>{@link AppFunctionNotFoundException}, if the function is not found or the caller does
65      *       not have access to it.
66      * </ul>
67      *
68      * @param functionIdentifier the identifier of the app function to check (unique within the
69      *                           target package) and in most cases, these are automatically
70      *                           generated by the AppFunctions
71      *                           SDK
72      * @param targetPackage      the package name of the app function's owner
73      * @param executor           executor the executor to run the request
74      * @param callback           the callback to receive the function enabled check result
75      * @hide
76      */
isAppFunctionEnabled( @onNull String functionIdentifier, @NonNull String targetPackage, @NonNull AppSearchManager appSearchManager, @NonNull Executor executor, @NonNull OutcomeReceiver<Boolean, Exception> callback)77     public static void isAppFunctionEnabled(
78             @NonNull String functionIdentifier,
79             @NonNull String targetPackage,
80             @NonNull AppSearchManager appSearchManager,
81             @NonNull Executor executor,
82             @NonNull OutcomeReceiver<Boolean, Exception> callback) {
83         Objects.requireNonNull(functionIdentifier);
84         Objects.requireNonNull(targetPackage);
85         Objects.requireNonNull(appSearchManager);
86         Objects.requireNonNull(executor);
87         Objects.requireNonNull(callback);
88 
89         appSearchManager.createGlobalSearchSession(
90                 executor,
91                 (searchSessionResult) -> {
92                     if (!searchSessionResult.isSuccess()) {
93                         callback.onError(failedResultToException(searchSessionResult));
94                         return;
95                     }
96                     try (GlobalSearchSession searchSession = searchSessionResult.getResultValue()) {
97                         SearchResults results =
98                                 searchJoinedStaticWithRuntimeAppFunctions(
99                                         Objects.requireNonNull(searchSession),
100                                         targetPackage,
101                                         functionIdentifier);
102                         results.getNextPage(
103                                 executor,
104                                 listAppSearchResult -> {
105                                     if (listAppSearchResult.isSuccess()) {
106                                         callback.onResult(
107                                                 getEffectiveEnabledStateFromSearchResults(
108                                                         Objects.requireNonNull(
109                                                                 listAppSearchResult
110                                                                         .getResultValue())));
111                                     } else {
112                                         callback.onError(
113                                                 failedResultToException(listAppSearchResult));
114                                     }
115                                 });
116                         results.close();
117                     } catch (Exception e) {
118                         callback.onError(e);
119                     }
120                 });
121     }
122 
123     /**
124      * Searches joined app function static and runtime metadata using the function Id and the
125      * package.
126      */
searchJoinedStaticWithRuntimeAppFunctions( @onNull GlobalSearchSession session, @NonNull String targetPackage, @NonNull String functionIdentifier)127     private static @NonNull SearchResults searchJoinedStaticWithRuntimeAppFunctions(
128             @NonNull GlobalSearchSession session,
129             @NonNull String targetPackage,
130             @NonNull String functionIdentifier) {
131         SearchSpec runtimeSearchSpec =
132                 getAppFunctionRuntimeMetadataSearchSpecByPackageName(targetPackage);
133         JoinSpec joinSpec =
134                 new JoinSpec.Builder(PROPERTY_APP_FUNCTION_STATIC_METADATA_QUALIFIED_ID)
135                         .setNestedSearch(
136                                 buildFilerRuntimeMetadataByFunctionIdQuery(functionIdentifier),
137                                 runtimeSearchSpec)
138                         .build();
139         SearchSpec joinedStaticWithRuntimeSearchSpec =
140                 new SearchSpec.Builder()
141                         .addFilterPackageNames(APP_FUNCTION_INDEXER_PACKAGE)
142                         .addFilterSchemas(
143                                 AppFunctionStaticMetadataHelper.getStaticSchemaNameForPackage(
144                                         targetPackage))
145                         .addProjectionPaths(
146                                 SearchSpec.SCHEMA_TYPE_WILDCARD,
147                                 List.of(new PropertyPath(STATIC_PROPERTY_ENABLED_BY_DEFAULT)))
148                         .setJoinSpec(joinSpec)
149                         .setVerbatimSearchEnabled(true)
150                         .build();
151         return session.search(
152                 buildFilerStaticMetadataByFunctionIdQuery(functionIdentifier),
153                 joinedStaticWithRuntimeSearchSpec);
154     }
155 
156     /**
157      * Returns whether the function is effectively enabled or not from the search results returned
158      * by {@link #searchJoinedStaticWithRuntimeAppFunctions}.
159      *
160      * @param joinedStaticRuntimeResults search results joining AppFunctionStaticMetadata
161      *                                   and AppFunctionRuntimeMetadata.
162      * @throws IllegalArgumentException if the function is not found in the results
163      */
getEffectiveEnabledStateFromSearchResults( @onNull List<SearchResult> joinedStaticRuntimeResults)164     private static boolean getEffectiveEnabledStateFromSearchResults(
165             @NonNull List<SearchResult> joinedStaticRuntimeResults) {
166         if (joinedStaticRuntimeResults.isEmpty()) {
167             throw new IllegalArgumentException("App function not found.");
168         } else {
169             List<SearchResult> runtimeMetadataResults =
170                     joinedStaticRuntimeResults.getFirst().getJoinedResults();
171             if (runtimeMetadataResults.isEmpty()) {
172                 throw new IllegalArgumentException("App function not found.");
173             }
174             long enabled =
175                     runtimeMetadataResults
176                             .getFirst()
177                             .getGenericDocument()
178                             .getPropertyLong(PROPERTY_ENABLED);
179             // If enabled is not equal to APP_FUNCTION_STATE_DEFAULT, it means it IS overridden and
180             // we should return the overridden value.
181             if (enabled != APP_FUNCTION_STATE_DEFAULT) {
182                 return enabled == APP_FUNCTION_STATE_ENABLED;
183             }
184             // Runtime metadata not found or enabled is equal to APP_FUNCTION_STATE_DEFAULT.
185             // Using the default value in the static metadata.
186             return joinedStaticRuntimeResults
187                     .getFirst()
188                     .getGenericDocument()
189                     .getPropertyBoolean(STATIC_PROPERTY_ENABLED_BY_DEFAULT);
190         }
191     }
192 
193     /**
194      * Returns search spec that queries app function metadata for a specific package name by its
195      * function identifier.
196      */
getAppFunctionRuntimeMetadataSearchSpecByPackageName( @onNull String targetPackage)197     private static @NonNull SearchSpec getAppFunctionRuntimeMetadataSearchSpecByPackageName(
198             @NonNull String targetPackage) {
199         return new SearchSpec.Builder()
200                 .addFilterPackageNames(APP_FUNCTION_INDEXER_PACKAGE)
201                 .addFilterSchemas(
202                         AppFunctionRuntimeMetadata.getRuntimeSchemaNameForPackage(targetPackage))
203                 .setVerbatimSearchEnabled(true)
204                 .build();
205     }
206 
buildFilerRuntimeMetadataByFunctionIdQuery(String functionIdentifier)207     private static String buildFilerRuntimeMetadataByFunctionIdQuery(String functionIdentifier) {
208         return TextUtils.formatSimple("%s:\"%s\"",
209                 AppFunctionRuntimeMetadata.PROPERTY_FUNCTION_ID,
210                 functionIdentifier);
211     }
212 
buildFilerStaticMetadataByFunctionIdQuery(String functionIdentifier)213     private static String buildFilerStaticMetadataByFunctionIdQuery(String functionIdentifier) {
214         return TextUtils.formatSimple("%s:\"%s\"",
215                 AppFunctionStaticMetadataHelper.PROPERTY_FUNCTION_ID,
216                 functionIdentifier);
217     }
218 
219     /** Converts a failed app search result codes into an exception. */
failedResultToException( @onNull AppSearchResult appSearchResult)220     private static @NonNull Exception failedResultToException(
221             @NonNull AppSearchResult appSearchResult) {
222         return switch (appSearchResult.getResultCode()) {
223             case AppSearchResult.RESULT_INVALID_ARGUMENT -> new AppFunctionNotFoundException(
224                     appSearchResult.getErrorMessage());
225             case AppSearchResult.RESULT_IO_ERROR -> new IOException(
226                     appSearchResult.getErrorMessage());
227             case AppSearchResult.RESULT_SECURITY_ERROR -> new SecurityException(
228                     appSearchResult.getErrorMessage());
229             default -> new IllegalStateException(appSearchResult.getErrorMessage());
230         };
231     }
232 
233     /**
234      * Throws when the app function is not found.
235      *
236      * @hide
237      */
238     public static class AppFunctionNotFoundException extends RuntimeException {
AppFunctionNotFoundException(@onNull String errorMessage)239         private AppFunctionNotFoundException(@NonNull String errorMessage) {
240             super(errorMessage);
241         }
242     }
243 }
244