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