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

Commit 19cb1d78 authored by Utkarsh Nigam's avatar Utkarsh Nigam
Browse files

Add support for dumpsys in AppFunctionManagerServiceImpl.

Change-Id: I80ed6006632788e8b14e3d0ed05c22e383ee88d4
Flag: android.app.appfunctions.flags.enable_app_function_manager
Test: Tested Manually https://paste.googleplex.com/6293573751537664
Bug: 357551503
parent 33208267
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