1 /* 2 * Copyright (C) 2019 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.am; 18 19 import static android.app.AppOpsManager.OP_NONE; 20 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM; 21 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME; 22 23 import android.app.Activity; 24 import android.app.BroadcastOptions; 25 import android.content.BroadcastReceiver; 26 import android.content.Context; 27 import android.content.Intent; 28 import android.content.pm.PackageManager; 29 import android.content.pm.ResolveInfo; 30 import android.os.Binder; 31 import android.os.BugreportManager; 32 import android.os.BugreportParams; 33 import android.os.UserHandle; 34 import android.provider.Settings; 35 import android.text.TextUtils; 36 import android.util.ArraySet; 37 import android.util.Slog; 38 39 import com.android.server.SystemConfig; 40 41 import java.util.List; 42 43 /** 44 * Static utility methods related to BugReportHandler. 45 */ 46 public final class BugReportHandlerUtil { 47 private static final String TAG = TAG_WITH_CLASS_NAME ? "BugReportHandlerUtil" : TAG_AM; 48 private static final String SHELL_APP_PACKAGE = "com.android.shell"; 49 private static final String INTENT_BUGREPORT_REQUESTED = 50 "com.android.internal.intent.action.BUGREPORT_REQUESTED"; 51 private static final String INTENT_GET_BUGREPORT_HANDLER_RESPONSE = 52 "com.android.internal.intent.action.GET_BUGREPORT_HANDLER_RESPONSE"; 53 54 /** 55 * Check is BugReportHandler enabled on the device. 56 * 57 * @param context Context 58 * @return true if BugReportHandler is enabled, or false otherwise 59 */ isBugReportHandlerEnabled(Context context)60 static boolean isBugReportHandlerEnabled(Context context) { 61 return context.getResources().getBoolean( 62 com.android.internal.R.bool.config_bugReportHandlerEnabled); 63 } 64 65 /** 66 * Launches a bugreport-allowlisted app to handle a bugreport. 67 * 68 * <p>Allows a bug report handler app to take bugreports on the user's behalf. The handler can 69 * be predefined in the config, meant to be launched with the primary user. The user can 70 * override this with a different (or same) handler app on possibly a different user. This is 71 * useful for capturing bug reports from work profile, for instance. 72 * 73 * @param context Context 74 * @return true if there is a bugreport-allowlisted app to handle a bugreport, or false 75 * otherwise 76 */ launchBugReportHandlerApp(Context context)77 static boolean launchBugReportHandlerApp(Context context) { 78 if (!isBugReportHandlerEnabled(context)) { 79 return false; 80 } 81 82 String handlerApp = getCustomBugReportHandlerApp(context); 83 if (isShellApp(handlerApp)) { 84 return false; 85 } 86 87 int handlerUser = getCustomBugReportHandlerUser(context); 88 if (!isValidBugReportHandlerApp(handlerApp)) { 89 handlerApp = getDefaultBugReportHandlerApp(context); 90 handlerUser = UserHandle.USER_SYSTEM; 91 } else if (getBugReportHandlerAppReceivers(context, handlerApp, handlerUser).isEmpty()) { 92 // It looks like the settings are outdated, reset outdated settings. 93 // 94 // i.e. 95 // If user chooses which profile and which bugreport-allowlisted app in that 96 // profile to handle a bugreport, then user remove the profile. 97 // === RESULT === 98 // The chosen bugreport handler app is outdated because the profile is removed, 99 // so reset the chosen app and profile 100 handlerApp = getDefaultBugReportHandlerApp(context); 101 handlerUser = UserHandle.USER_SYSTEM; 102 resetCustomBugreportHandlerAppAndUser(context); 103 } 104 105 if (isShellApp(handlerApp) || !isValidBugReportHandlerApp(handlerApp) 106 || getBugReportHandlerAppReceivers(context, handlerApp, handlerUser).isEmpty()) { 107 return false; 108 } 109 110 if (getBugReportHandlerAppResponseReceivers(context, handlerApp, handlerUser).isEmpty()) { 111 // Just try to launch bugreport handler app to handle bugreport request 112 // because the bugreport handler app is old and not support to provide response to 113 // let BugReportHandlerUtil know it is available or not. 114 launchBugReportHandlerApp(context, handlerApp, handlerUser); 115 return true; 116 } 117 118 Slog.i(TAG, "Getting response from bug report handler app: " + handlerApp); 119 Intent intent = new Intent(INTENT_GET_BUGREPORT_HANDLER_RESPONSE); 120 intent.setPackage(handlerApp); 121 intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); 122 intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); 123 final long identity = Binder.clearCallingIdentity(); 124 try { 125 // Handler app's BroadcastReceiver should call setResultCode(Activity.RESULT_OK) to 126 // let BugreportHandlerResponseBroadcastReceiver know the handler app is available. 127 context.sendOrderedBroadcastAsUser(intent, 128 UserHandle.of(handlerUser), 129 android.Manifest.permission.DUMP, 130 OP_NONE, /* options= */ null, 131 new BugreportHandlerResponseBroadcastReceiver(handlerApp, handlerUser), 132 /* scheduler= */ null, 133 Activity.RESULT_CANCELED, 134 /* initialData= */ null, 135 /* initialExtras= */ null); 136 } catch (RuntimeException e) { 137 Slog.e(TAG, "Error while trying to get response from bug report handler app.", e); 138 return false; 139 } finally { 140 Binder.restoreCallingIdentity(identity); 141 } 142 return true; 143 } 144 launchBugReportHandlerApp(Context context, String handlerApp, int handlerUser)145 private static void launchBugReportHandlerApp(Context context, String handlerApp, 146 int handlerUser) { 147 Slog.i(TAG, "Launching bug report handler app: " + handlerApp); 148 Intent intent = new Intent(INTENT_BUGREPORT_REQUESTED); 149 intent.setPackage(handlerApp); 150 intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); 151 intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); 152 // Send broadcast to the receiver while allowing starting activity from background 153 final BroadcastOptions options = BroadcastOptions.makeBasic(); 154 options.setBackgroundActivityStartsAllowed(true); 155 final long identity = Binder.clearCallingIdentity(); 156 try { 157 context.sendBroadcastAsUser(intent, UserHandle.of(handlerUser), 158 android.Manifest.permission.DUMP, 159 options.toBundle()); 160 } catch (RuntimeException e) { 161 Slog.e(TAG, "Error while trying to launch bugreport handler app.", e); 162 } finally { 163 Binder.restoreCallingIdentity(identity); 164 } 165 } 166 getCustomBugReportHandlerApp(Context context)167 private static String getCustomBugReportHandlerApp(Context context) { 168 // Get the package of custom bugreport handler app 169 return Settings.Global.getString(context.getContentResolver(), 170 Settings.Global.CUSTOM_BUGREPORT_HANDLER_APP); 171 } 172 getCustomBugReportHandlerUser(Context context)173 private static int getCustomBugReportHandlerUser(Context context) { 174 return Settings.Global.getInt(context.getContentResolver(), 175 Settings.Global.CUSTOM_BUGREPORT_HANDLER_USER, UserHandle.USER_NULL); 176 } 177 isShellApp(String app)178 private static boolean isShellApp(String app) { 179 return SHELL_APP_PACKAGE.equals(app); 180 } 181 isValidBugReportHandlerApp(String app)182 private static boolean isValidBugReportHandlerApp(String app) { 183 return !TextUtils.isEmpty(app) && isBugreportWhitelistedApp(app); 184 } 185 isBugreportWhitelistedApp(String app)186 private static boolean isBugreportWhitelistedApp(String app) { 187 // Verify the app is bugreport-allowlisted 188 final ArraySet<String> whitelistedApps = SystemConfig.getInstance() 189 .getBugreportWhitelistedPackages(); 190 return whitelistedApps.contains(app); 191 } 192 getBugReportHandlerAppReceivers(Context context, String handlerApp, int handlerUser)193 private static List<ResolveInfo> getBugReportHandlerAppReceivers(Context context, 194 String handlerApp, int handlerUser) { 195 // Use the app package and the user id to retrieve the receiver that can handle a 196 // broadcast of the intent. 197 Intent intent = new Intent(INTENT_BUGREPORT_REQUESTED); 198 intent.setPackage(handlerApp); 199 return context.getPackageManager() 200 .queryBroadcastReceiversAsUser(intent, PackageManager.MATCH_SYSTEM_ONLY, 201 handlerUser); 202 } 203 getBugReportHandlerAppResponseReceivers(Context context, String handlerApp, int handlerUser)204 private static List<ResolveInfo> getBugReportHandlerAppResponseReceivers(Context context, 205 String handlerApp, int handlerUser) { 206 // Use the app package and the user id to retrieve the receiver that can provide response 207 Intent intent = new Intent(INTENT_GET_BUGREPORT_HANDLER_RESPONSE); 208 intent.setPackage(handlerApp); 209 return context.getPackageManager() 210 .queryBroadcastReceiversAsUser(intent, PackageManager.MATCH_SYSTEM_ONLY, 211 handlerUser); 212 } 213 getDefaultBugReportHandlerApp(Context context)214 private static String getDefaultBugReportHandlerApp(Context context) { 215 return context.getResources().getString( 216 com.android.internal.R.string.config_defaultBugReportHandlerApp); 217 } 218 resetCustomBugreportHandlerAppAndUser(Context context)219 private static void resetCustomBugreportHandlerAppAndUser(Context context) { 220 final long identity = Binder.clearCallingIdentity(); 221 try { 222 Settings.Global.putString(context.getContentResolver(), 223 Settings.Global.CUSTOM_BUGREPORT_HANDLER_APP, 224 getDefaultBugReportHandlerApp(context)); 225 Settings.Global.putInt(context.getContentResolver(), 226 Settings.Global.CUSTOM_BUGREPORT_HANDLER_USER, UserHandle.USER_SYSTEM); 227 } finally { 228 Binder.restoreCallingIdentity(identity); 229 } 230 } 231 232 private static class BugreportHandlerResponseBroadcastReceiver extends BroadcastReceiver { 233 private final String handlerApp; 234 private final int handlerUser; 235 BugreportHandlerResponseBroadcastReceiver(String handlerApp, int handlerUser)236 BugreportHandlerResponseBroadcastReceiver(String handlerApp, int handlerUser) { 237 this.handlerApp = handlerApp; 238 this.handlerUser = handlerUser; 239 } 240 241 @Override onReceive(Context context, Intent intent)242 public void onReceive(Context context, Intent intent) { 243 if (getResultCode() == Activity.RESULT_OK) { 244 // Try to launch bugreport handler app to handle bugreport request because the 245 // bugreport handler app is available. 246 launchBugReportHandlerApp(context, handlerApp, handlerUser); 247 return; 248 } 249 250 Slog.w(TAG, "Request bug report because no response from handler app."); 251 BugreportManager bugreportManager = context.getSystemService(BugreportManager.class); 252 bugreportManager.requestBugreport( 253 new BugreportParams(BugreportParams.BUGREPORT_MODE_INTERACTIVE), 254 /* shareTitle= */null, /* shareDescription= */ null); 255 } 256 } 257 } 258