diff options
Diffstat (limited to 'services/appfunctions/java')
8 files changed, 329 insertions, 57 deletions
diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionDumpHelper.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionDumpHelper.java new file mode 100644 index 000000000000..9fc413f6224b --- /dev/null +++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionDumpHelper.java @@ -0,0 +1,186 @@ +/* + * 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 static android.app.appfunctions.AppFunctionRuntimeMetadata.PROPERTY_APP_FUNCTION_STATIC_METADATA_QUALIFIED_ID; +import static android.app.appfunctions.AppFunctionStaticMetadataHelper.APP_FUNCTION_INDEXER_PACKAGE; +import static android.app.appfunctions.AppFunctionStaticMetadataHelper.PROPERTY_FUNCTION_ID; + +import android.Manifest; +import android.annotation.BinderThread; +import android.annotation.RequiresPermission; +import android.annotation.NonNull; +import android.content.Context; +import android.content.pm.UserInfo; +import android.os.UserManager; +import android.util.IndentingPrintWriter; +import android.app.appfunctions.AppFunctionRuntimeMetadata; +import android.app.appfunctions.AppFunctionStaticMetadataHelper; +import android.app.appsearch.AppSearchManager; +import android.app.appsearch.AppSearchManager.SearchContext; +import android.app.appsearch.JoinSpec; +import android.app.appsearch.GenericDocument; +import android.app.appsearch.SearchResult; +import android.app.appsearch.SearchSpec; + +import java.io.PrintWriter; +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +public final class AppFunctionDumpHelper { + private static final String TAG = AppFunctionDumpHelper.class.getSimpleName(); + + private AppFunctionDumpHelper() {} + + /** Dumps the state of all app functions for all users. */ + @BinderThread + @RequiresPermission( + anyOf = {Manifest.permission.CREATE_USERS, Manifest.permission.MANAGE_USERS}) + public static void dumpAppFunctionsState(@NonNull Context context, @NonNull PrintWriter w) { + UserManager userManager = context.getSystemService(UserManager.class); + if (userManager == null) { + w.println("Couldn't retrieve UserManager."); + return; + } + + IndentingPrintWriter pw = new IndentingPrintWriter(w); + + List<UserInfo> userInfos = userManager.getAliveUsers(); + for (UserInfo userInfo : userInfos) { + pw.println( + "AppFunction state for user " + userInfo.getUserHandle().getIdentifier() + ":"); + pw.increaseIndent(); + dumpAppFunctionsStateForUser( + context.createContextAsUser(userInfo.getUserHandle(), /* flags= */ 0), pw); + pw.decreaseIndent(); + } + } + + private static void dumpAppFunctionsStateForUser( + @NonNull Context context, @NonNull IndentingPrintWriter pw) { + AppSearchManager appSearchManager = context.getSystemService(AppSearchManager.class); + if (appSearchManager == null) { + pw.println("Couldn't retrieve AppSearchManager."); + return; + } + + try (FutureGlobalSearchSession searchSession = + new FutureGlobalSearchSession(appSearchManager, Runnable::run)) { + pw.println(); + + FutureSearchResults futureSearchResults = + searchSession.search("", buildAppFunctionMetadataSearchSpec()).get(); + List<SearchResult> searchResultsList; + do { + searchResultsList = futureSearchResults.getNextPage().get(); + for (SearchResult searchResult : searchResultsList) { + dumpAppFunctionMetadata(pw, searchResult); + } + } while (!searchResultsList.isEmpty()); + } catch (Exception e) { + pw.println("Failed to dump AppFunction state: " + e); + } + } + + private static SearchSpec buildAppFunctionMetadataSearchSpec() { + SearchSpec runtimeMetadataSearchSpec = + new SearchSpec.Builder() + .addFilterPackageNames(APP_FUNCTION_INDEXER_PACKAGE) + .addFilterSchemas(AppFunctionRuntimeMetadata.RUNTIME_SCHEMA_TYPE) + .build(); + JoinSpec joinSpec = + new JoinSpec.Builder(PROPERTY_APP_FUNCTION_STATIC_METADATA_QUALIFIED_ID) + .setNestedSearch(/* queryExpression= */ "", runtimeMetadataSearchSpec) + .build(); + + return new SearchSpec.Builder() + .addFilterPackageNames(APP_FUNCTION_INDEXER_PACKAGE) + .addFilterSchemas(AppFunctionStaticMetadataHelper.STATIC_SCHEMA_TYPE) + .setJoinSpec(joinSpec) + .build(); + } + + private static void dumpAppFunctionMetadata( + IndentingPrintWriter pw, SearchResult joinedSearchResult) { + pw.println( + "AppFunctionMetadata for: " + + joinedSearchResult + .getGenericDocument() + .getPropertyString(PROPERTY_FUNCTION_ID)); + pw.increaseIndent(); + + pw.println("Static Metadata:"); + pw.increaseIndent(); + writeGenericDocumentProperties(pw, joinedSearchResult.getGenericDocument()); + pw.decreaseIndent(); + + pw.println("Runtime Metadata:"); + pw.increaseIndent(); + if (!joinedSearchResult.getJoinedResults().isEmpty()) { + writeGenericDocumentProperties( + pw, joinedSearchResult.getJoinedResults().getFirst().getGenericDocument()); + } else { + pw.println("No runtime metadata found."); + } + pw.decreaseIndent(); + + pw.decreaseIndent(); + } + + private static void writeGenericDocumentProperties( + IndentingPrintWriter pw, GenericDocument genericDocument) { + Set<String> propertyNames = genericDocument.getPropertyNames(); + pw.println("{"); + pw.increaseIndent(); + for (String propertyName : propertyNames) { + Object propertyValue = genericDocument.getProperty(propertyName); + pw.print("\"" + propertyName + "\"" + ": ["); + + if (propertyValue instanceof GenericDocument[]) { + GenericDocument[] documentValues = (GenericDocument[]) propertyValue; + for (int i = 0; i < documentValues.length; i++) { + GenericDocument documentValue = documentValues[i]; + writeGenericDocumentProperties(pw, documentValue); + if (i != documentValues.length - 1) { + pw.print(", "); + } + pw.println(); + } + } else { + int propertyArrLength = Array.getLength(propertyValue); + for (int i = 0; i < propertyArrLength; i++) { + Object propertyElement = Array.get(propertyValue, i); + if (propertyElement instanceof String) { + pw.print("\"" + propertyElement + "\""); + } else if (propertyElement instanceof byte[]) { + pw.print(Arrays.toString((byte[]) propertyElement)); + } else if (propertyElement != null) { + pw.print(propertyElement.toString()); + } + if (i != propertyArrLength - 1) { + pw.print(", "); + } + } + } + pw.println("]"); + } + pw.decreaseIndent(); + pw.println("}"); + } +} diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java index d31ced3f2ce3..6d350e6b2a4a 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java +++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java @@ -63,10 +63,13 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.infra.AndroidFuture; +import com.android.internal.util.DumpUtils; import com.android.server.SystemService.TargetUser; import com.android.server.appfunctions.RemoteServiceCaller.RunServiceCallCallback; import com.android.server.appfunctions.RemoteServiceCaller.ServiceUsageCompleteListener; +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.util.Objects; import java.util.concurrent.CompletionException; import java.util.concurrent.Executor; @@ -122,6 +125,20 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { } @Override + public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { + if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) { + return; + } + + final long token = Binder.clearCallingIdentity(); + try { + AppFunctionDumpHelper.dumpAppFunctionsState(mContext, pw); + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override public ICancellationSignal executeAppFunction( @NonNull ExecuteAppFunctionAidlRequest requestInternal, @NonNull IExecuteAppFunctionCallback executeAppFunctionCallback) { diff --git a/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java b/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java index de2034b5be2f..b89348c038fa 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java +++ b/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java @@ -39,20 +39,6 @@ import java.util.List; /** A future API wrapper of {@link AppSearchSession} APIs. */ public interface FutureAppSearchSession extends Closeable { - /** Converts a failed app search result codes into an exception. */ - @NonNull - static 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()); - default -> new IllegalStateException(appSearchResult.getErrorMessage()); - }; - } - /** * Sets the schema that represents the organizational structure of data within the AppSearch * database. @@ -86,17 +72,4 @@ public interface FutureAppSearchSession extends Closeable { @Override void close(); - - /** A future API wrapper of {@link android.app.appsearch.SearchResults}. */ - interface FutureSearchResults { - - /** - * Retrieves the next page of {@link SearchResult} objects from the {@link AppSearchSession} - * database. - * - * <p>Continue calling this method to access results until it returns an empty list, - * signifying there are no more results. - */ - AndroidFuture<List<SearchResult>> getNextPage(); - } } diff --git a/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSessionImpl.java b/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSessionImpl.java index d24bb871c393..87589f51cf1b 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSessionImpl.java +++ b/services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSessionImpl.java @@ -16,7 +16,7 @@ package com.android.server.appfunctions; -import static com.android.server.appfunctions.FutureAppSearchSession.failedResultToException; +import static com.android.server.appfunctions.FutureSearchResults.failedResultToException; import android.annotation.NonNull; import android.app.appsearch.AppSearchBatchResult; @@ -192,33 +192,6 @@ public class FutureAppSearchSessionImpl implements FutureAppSearchSession { }); } - private static final class FutureSearchResultsImpl implements FutureSearchResults { - private final SearchResults mSearchResults; - private final Executor mExecutor; - - private FutureSearchResultsImpl( - @NonNull SearchResults searchResults, @NonNull Executor executor) { - this.mSearchResults = searchResults; - this.mExecutor = executor; - } - - @Override - public AndroidFuture<List<SearchResult>> getNextPage() { - AndroidFuture<AppSearchResult<List<SearchResult>>> nextPageFuture = - new AndroidFuture<>(); - - mSearchResults.getNextPage(mExecutor, nextPageFuture::complete); - return nextPageFuture.thenApply( - result -> { - if (result.isSuccess()) { - return result.getResultValue(); - } else { - throw new RuntimeException(failedResultToException(result)); - } - }); - } - } - private static final class BatchResultCallbackAdapter<K, V> implements BatchResultCallback<K, V> { private final AndroidFuture<AppSearchBatchResult<K, V>> mFuture; diff --git a/services/appfunctions/java/com/android/server/appfunctions/FutureGlobalSearchSession.java b/services/appfunctions/java/com/android/server/appfunctions/FutureGlobalSearchSession.java index 874c5daa1662..4cc08173ac97 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/FutureGlobalSearchSession.java +++ b/services/appfunctions/java/com/android/server/appfunctions/FutureGlobalSearchSession.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.app.appsearch.AppSearchManager; import android.app.appsearch.AppSearchResult; import android.app.appsearch.GlobalSearchSession; +import android.app.appsearch.SearchSpec; import android.app.appsearch.exceptions.AppSearchException; import android.app.appsearch.observer.ObserverCallback; import android.app.appsearch.observer.ObserverSpec; @@ -49,12 +50,23 @@ public class FutureGlobalSearchSession implements Closeable { return result.getResultValue(); } else { throw new RuntimeException( - FutureAppSearchSession.failedResultToException(result)); + FutureSearchResults.failedResultToException(result)); } }); } /** + * Retrieves documents from the open {@link GlobalSearchSession} that match a given query string + * and type of search provided. + */ + public AndroidFuture<FutureSearchResults> search( + String queryExpression, SearchSpec searchSpec) { + return getSessionAsync() + .thenApply(session -> session.search(queryExpression, searchSpec)) + .thenApply(result -> new FutureSearchResultsImpl(result, mExecutor)); + } + + /** * Registers an observer callback for the given target package name. * * @param targetPackageName The package name of the target app. diff --git a/services/appfunctions/java/com/android/server/appfunctions/FutureSearchResults.java b/services/appfunctions/java/com/android/server/appfunctions/FutureSearchResults.java new file mode 100644 index 000000000000..45cbdb4021e5 --- /dev/null +++ b/services/appfunctions/java/com/android/server/appfunctions/FutureSearchResults.java @@ -0,0 +1,55 @@ +/* + * 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.NonNull; +import android.app.appsearch.AppSearchResult; +import android.app.appsearch.AppSearchSession; +import android.app.appsearch.SearchResult; +import android.app.appsearch.SearchResults; + +import com.android.internal.infra.AndroidFuture; + +import java.io.IOException; +import java.util.List; + +/** A future API wrapper of {@link android.app.appsearch.SearchResults}. */ +public interface FutureSearchResults { + + /** Converts a failed app search result codes into an exception. */ + @NonNull + public static 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()); + default -> new IllegalStateException(appSearchResult.getErrorMessage()); + }; + } + + /** + * Retrieves the next page of {@link SearchResult} objects from the {@link AppSearchSession} + * database. + * + * <p>Continue calling this method to access results until it returns an empty list, signifying + * there are no more results. + */ + AndroidFuture<List<SearchResult>> getNextPage(); +} diff --git a/services/appfunctions/java/com/android/server/appfunctions/FutureSearchResultsImpl.java b/services/appfunctions/java/com/android/server/appfunctions/FutureSearchResultsImpl.java new file mode 100644 index 000000000000..c3be342043ba --- /dev/null +++ b/services/appfunctions/java/com/android/server/appfunctions/FutureSearchResultsImpl.java @@ -0,0 +1,57 @@ +/* + * 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.NonNull; +import android.app.appsearch.AppSearchResult; +import android.app.appsearch.AppSearchSession; +import android.app.appsearch.SearchResult; +import android.app.appsearch.SearchResults; + +import com.android.internal.infra.AndroidFuture; + +import java.io.IOException; +import java.util.List; +import java.util.concurrent.Executor; + +public class FutureSearchResultsImpl implements FutureSearchResults { + private final SearchResults mSearchResults; + private final Executor mExecutor; + + public FutureSearchResultsImpl( + @NonNull SearchResults searchResults, @NonNull Executor executor) { + this.mSearchResults = searchResults; + this.mExecutor = executor; + } + + @Override + public AndroidFuture<List<SearchResult>> getNextPage() { + AndroidFuture<AppSearchResult<List<SearchResult>>> nextPageFuture = new AndroidFuture<>(); + + mSearchResults.getNextPage(mExecutor, nextPageFuture::complete); + return nextPageFuture + .thenApply( + result -> { + if (result.isSuccess()) { + return result.getResultValue(); + } else { + throw new RuntimeException( + FutureSearchResults.failedResultToException(result)); + } + }); + } +} diff --git a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java index d84b20556053..bbf6c0beb163 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java +++ b/services/appfunctions/java/com/android/server/appfunctions/MetadataSyncAdapter.java @@ -45,7 +45,6 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.infra.AndroidFuture; -import com.android.server.appfunctions.FutureAppSearchSession.FutureSearchResults; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; |