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 com.android.server.appfunctions; 18 19 import static android.app.appfunctions.AppFunctionRuntimeMetadata.PROPERTY_APP_FUNCTION_STATIC_METADATA_QUALIFIED_ID; 20 import static android.app.appfunctions.AppFunctionStaticMetadataHelper.APP_FUNCTION_INDEXER_PACKAGE; 21 import static android.app.appfunctions.AppFunctionStaticMetadataHelper.PROPERTY_FUNCTION_ID; 22 23 import android.Manifest; 24 import android.annotation.BinderThread; 25 import android.annotation.RequiresPermission; 26 import android.annotation.NonNull; 27 import android.content.Context; 28 import android.content.pm.UserInfo; 29 import android.os.UserManager; 30 import android.util.IndentingPrintWriter; 31 import android.app.appfunctions.AppFunctionRuntimeMetadata; 32 import android.app.appfunctions.AppFunctionStaticMetadataHelper; 33 import android.app.appsearch.AppSearchManager; 34 import android.app.appsearch.AppSearchManager.SearchContext; 35 import android.app.appsearch.JoinSpec; 36 import android.app.appsearch.GenericDocument; 37 import android.app.appsearch.SearchResult; 38 import android.app.appsearch.SearchSpec; 39 40 import java.io.PrintWriter; 41 import java.lang.reflect.Array; 42 import java.util.Arrays; 43 import java.util.List; 44 import java.util.Set; 45 46 public final class AppFunctionDumpHelper { 47 private static final String TAG = AppFunctionDumpHelper.class.getSimpleName(); 48 AppFunctionDumpHelper()49 private AppFunctionDumpHelper() {} 50 51 /** Dumps the state of all app functions for all users. */ 52 @BinderThread 53 @RequiresPermission( 54 anyOf = {Manifest.permission.CREATE_USERS, Manifest.permission.MANAGE_USERS}) dumpAppFunctionsState(@onNull Context context, @NonNull PrintWriter w)55 public static void dumpAppFunctionsState(@NonNull Context context, @NonNull PrintWriter w) { 56 UserManager userManager = context.getSystemService(UserManager.class); 57 if (userManager == null) { 58 w.println("Couldn't retrieve UserManager."); 59 return; 60 } 61 62 IndentingPrintWriter pw = new IndentingPrintWriter(w); 63 64 List<UserInfo> userInfos = userManager.getAliveUsers(); 65 for (UserInfo userInfo : userInfos) { 66 pw.println( 67 "AppFunction state for user " + userInfo.getUserHandle().getIdentifier() + ":"); 68 pw.increaseIndent(); 69 dumpAppFunctionsStateForUser( 70 context.createContextAsUser(userInfo.getUserHandle(), /* flags= */ 0), pw); 71 pw.decreaseIndent(); 72 } 73 } 74 dumpAppFunctionsStateForUser( @onNull Context context, @NonNull IndentingPrintWriter pw)75 private static void dumpAppFunctionsStateForUser( 76 @NonNull Context context, @NonNull IndentingPrintWriter pw) { 77 AppSearchManager appSearchManager = context.getSystemService(AppSearchManager.class); 78 if (appSearchManager == null) { 79 pw.println("Couldn't retrieve AppSearchManager."); 80 return; 81 } 82 83 try (FutureGlobalSearchSession searchSession = 84 new FutureGlobalSearchSession(appSearchManager, Runnable::run)) { 85 pw.println(); 86 87 try (FutureSearchResults futureSearchResults = 88 searchSession.search("", buildAppFunctionMetadataSearchSpec()).get(); ) { 89 List<SearchResult> searchResultsList; 90 do { 91 searchResultsList = futureSearchResults.getNextPage().get(); 92 for (SearchResult searchResult : searchResultsList) { 93 dumpAppFunctionMetadata(pw, searchResult); 94 } 95 } while (!searchResultsList.isEmpty()); 96 } 97 98 } catch (Exception e) { 99 pw.println("Failed to dump AppFunction state: " + e); 100 } 101 } 102 buildAppFunctionMetadataSearchSpec()103 private static SearchSpec buildAppFunctionMetadataSearchSpec() { 104 SearchSpec runtimeMetadataSearchSpec = 105 new SearchSpec.Builder() 106 .addFilterPackageNames(APP_FUNCTION_INDEXER_PACKAGE) 107 .addFilterSchemas(AppFunctionRuntimeMetadata.RUNTIME_SCHEMA_TYPE) 108 .build(); 109 JoinSpec joinSpec = 110 new JoinSpec.Builder(PROPERTY_APP_FUNCTION_STATIC_METADATA_QUALIFIED_ID) 111 .setNestedSearch(/* queryExpression= */ "", runtimeMetadataSearchSpec) 112 .build(); 113 114 return new SearchSpec.Builder() 115 .addFilterPackageNames(APP_FUNCTION_INDEXER_PACKAGE) 116 .addFilterSchemas(AppFunctionStaticMetadataHelper.STATIC_SCHEMA_TYPE) 117 .setJoinSpec(joinSpec) 118 .build(); 119 } 120 dumpAppFunctionMetadata( IndentingPrintWriter pw, SearchResult joinedSearchResult)121 private static void dumpAppFunctionMetadata( 122 IndentingPrintWriter pw, SearchResult joinedSearchResult) { 123 pw.println( 124 "AppFunctionMetadata for: " 125 + joinedSearchResult 126 .getGenericDocument() 127 .getPropertyString(PROPERTY_FUNCTION_ID)); 128 pw.increaseIndent(); 129 130 pw.println("Static Metadata:"); 131 pw.increaseIndent(); 132 writeGenericDocumentProperties(pw, joinedSearchResult.getGenericDocument()); 133 pw.decreaseIndent(); 134 135 pw.println("Runtime Metadata:"); 136 pw.increaseIndent(); 137 if (!joinedSearchResult.getJoinedResults().isEmpty()) { 138 writeGenericDocumentProperties( 139 pw, joinedSearchResult.getJoinedResults().getFirst().getGenericDocument()); 140 } else { 141 pw.println("No runtime metadata found."); 142 } 143 pw.decreaseIndent(); 144 145 pw.decreaseIndent(); 146 } 147 writeGenericDocumentProperties( IndentingPrintWriter pw, GenericDocument genericDocument)148 private static void writeGenericDocumentProperties( 149 IndentingPrintWriter pw, GenericDocument genericDocument) { 150 Set<String> propertyNames = genericDocument.getPropertyNames(); 151 pw.println("{"); 152 pw.increaseIndent(); 153 for (String propertyName : propertyNames) { 154 Object propertyValue = genericDocument.getProperty(propertyName); 155 pw.print("\"" + propertyName + "\"" + ": ["); 156 157 if (propertyValue instanceof GenericDocument[]) { 158 GenericDocument[] documentValues = (GenericDocument[]) propertyValue; 159 for (int i = 0; i < documentValues.length; i++) { 160 GenericDocument documentValue = documentValues[i]; 161 writeGenericDocumentProperties(pw, documentValue); 162 if (i != documentValues.length - 1) { 163 pw.print(", "); 164 } 165 pw.println(); 166 } 167 } else { 168 int propertyArrLength = Array.getLength(propertyValue); 169 for (int i = 0; i < propertyArrLength; i++) { 170 Object propertyElement = Array.get(propertyValue, i); 171 if (propertyElement instanceof String) { 172 pw.print("\"" + propertyElement + "\""); 173 } else if (propertyElement instanceof byte[]) { 174 pw.print(Arrays.toString((byte[]) propertyElement)); 175 } else if (propertyElement != null) { 176 pw.print(propertyElement.toString()); 177 } 178 if (i != propertyArrLength - 1) { 179 pw.print(", "); 180 } 181 } 182 } 183 pw.println("]"); 184 } 185 pw.decreaseIndent(); 186 pw.println("}"); 187 } 188 } 189