Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit be67b0f9 authored by Treehugger Robot's avatar Treehugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Add support for dumpsys in AppFunctionManagerServiceImpl." into main

parents a99624cb 19cb1d78
Loading
Loading
Loading
Loading
+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("}");
    }
}
+17 −0
Original line number Diff line number Diff line
@@ -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;
@@ -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,
+0 −27
Original line number Diff line number Diff line
@@ -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();
    }
}
+1 −28
Original line number Diff line number Diff line
@@ -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;
+13 −1
Original line number Diff line number Diff line
@@ -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,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