Loading services/appfunctions/java/com/android/server/appfunctions/AppFunctionDumpHelper.java 0 → 100644 +186 −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 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("}"); } } services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java +17 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -121,6 +124,20 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { MetadataSyncPerUser.removeUserSyncAdapter(user.getUserHandle()); } @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, Loading services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java +0 −27 Original line number Diff line number Diff line Loading @@ -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. Loading Loading @@ -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(); } } services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSessionImpl.java +1 −28 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading services/appfunctions/java/com/android/server/appfunctions/FutureGlobalSearchSession.java +13 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -49,11 +50,22 @@ 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. * Loading Loading
services/appfunctions/java/com/android/server/appfunctions/AppFunctionDumpHelper.java 0 → 100644 +186 −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 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("}"); } }
services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java +17 −0 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -121,6 +124,20 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { MetadataSyncPerUser.removeUserSyncAdapter(user.getUserHandle()); } @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, Loading
services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSession.java +0 −27 Original line number Diff line number Diff line Loading @@ -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. Loading Loading @@ -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(); } }
services/appfunctions/java/com/android/server/appfunctions/FutureAppSearchSessionImpl.java +1 −28 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading
services/appfunctions/java/com/android/server/appfunctions/FutureGlobalSearchSession.java +13 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -49,11 +50,22 @@ 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. * Loading