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.settings.bugreporthandler; 18 19 import android.app.ActivityManager; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.pm.ApplicationInfo; 23 import android.content.pm.PackageManager; 24 import android.content.pm.ResolveInfo; 25 import android.content.pm.UserInfo; 26 import android.os.RemoteException; 27 import android.os.UserHandle; 28 import android.os.UserManager; 29 import android.provider.Settings; 30 import android.text.TextUtils; 31 import android.util.Log; 32 import android.util.Pair; 33 import android.widget.Toast; 34 35 import com.android.settings.R; 36 37 import java.util.ArrayList; 38 import java.util.Collections; 39 import java.util.List; 40 import java.util.stream.Collectors; 41 42 /** 43 * Utility methods related to BugReportHandler. 44 */ 45 public class BugReportHandlerUtil { 46 private static final String TAG = "BugReportHandlerUtil"; 47 private static final String INTENT_BUGREPORT_REQUESTED = 48 "com.android.internal.intent.action.BUGREPORT_REQUESTED"; 49 50 public static final String SHELL_APP_PACKAGE = "com.android.shell"; 51 BugReportHandlerUtil()52 public BugReportHandlerUtil() { 53 } 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 public boolean isBugReportHandlerEnabled(Context context) { 62 return context.getResources().getBoolean( 63 com.android.internal.R.bool.config_bugReportHandlerEnabled); 64 } 65 66 /** 67 * Fetch the package name currently used as bug report handler app and the user. 68 * 69 * @param context Context 70 * @return a pair of two values, first one is the current bug report handler app, second is 71 * the user. 72 */ getCurrentBugReportHandlerAppAndUser(Context context)73 public Pair<String, Integer> getCurrentBugReportHandlerAppAndUser(Context context) { 74 75 String handlerApp = getCustomBugReportHandlerApp(context); 76 int handlerUser = getCustomBugReportHandlerUser(context); 77 78 boolean needToResetOutdatedSettings = false; 79 if (!isBugreportAllowlistedApp(handlerApp)) { 80 handlerApp = getDefaultBugReportHandlerApp(context); 81 handlerUser = context.getUserId(); 82 } else if (getBugReportHandlerAppReceivers(context, handlerApp, handlerUser).isEmpty()) { 83 // It looks like the settings are outdated, need to reset outdated settings. 84 // 85 // i.e. 86 // If user chooses which profile and which bugreport-allowlisted app in that 87 // profile to handle a bugreport, then user remove the profile. 88 // === RESULT === 89 // The chosen bugreport handler app is outdated because the profile is removed, 90 // so need to reset outdated settings 91 handlerApp = getDefaultBugReportHandlerApp(context); 92 handlerUser = context.getUserId(); 93 needToResetOutdatedSettings = true; 94 } 95 96 if (!isBugreportAllowlistedApp(handlerApp) 97 || getBugReportHandlerAppReceivers(context, handlerApp, handlerUser).isEmpty()) { 98 // It looks like current handler app may be too old and doesn't support to handle a 99 // bugreport, so change to let shell to handle a bugreport and need to reset 100 // settings. 101 handlerApp = SHELL_APP_PACKAGE; 102 handlerUser = context.getUserId(); 103 needToResetOutdatedSettings = true; 104 } 105 106 if (needToResetOutdatedSettings) { 107 setBugreportHandlerAppAndUser(context, handlerApp, handlerUser); 108 } 109 110 return Pair.create(handlerApp, handlerUser); 111 } 112 getCustomBugReportHandlerApp(Context context)113 private String getCustomBugReportHandlerApp(Context context) { 114 // Get the package of custom bugreport handler app 115 return Settings.Secure.getString(context.getContentResolver(), 116 Settings.Secure.CUSTOM_BUGREPORT_HANDLER_APP); 117 } 118 getCustomBugReportHandlerUser(Context context)119 private int getCustomBugReportHandlerUser(Context context) { 120 return Settings.Secure.getInt(context.getContentResolver(), 121 Settings.Secure.CUSTOM_BUGREPORT_HANDLER_USER, UserHandle.USER_NULL); 122 } 123 getDefaultBugReportHandlerApp(Context context)124 private String getDefaultBugReportHandlerApp(Context context) { 125 return context.getResources().getString( 126 com.android.internal.R.string.config_defaultBugReportHandlerApp); 127 } 128 129 /** 130 * Change current bug report handler app and user. 131 * 132 * @param context Context 133 * @param handlerApp the package name of the handler app 134 * @param handlerUser the id of the handler user 135 * @return whether the change succeeded 136 */ setCurrentBugReportHandlerAppAndUser(Context context, String handlerApp, int handlerUser)137 public boolean setCurrentBugReportHandlerAppAndUser(Context context, String handlerApp, 138 int handlerUser) { 139 if (!isBugreportAllowlistedApp(handlerApp)) { 140 return false; 141 } else if (getBugReportHandlerAppReceivers(context, handlerApp, handlerUser).isEmpty()) { 142 return false; 143 } 144 setBugreportHandlerAppAndUser(context, handlerApp, handlerUser); 145 return true; 146 } 147 148 /** 149 * Fetches ApplicationInfo objects and user ids for all currently valid BugReportHandler. 150 * A BugReportHandler is considered valid if it can receive BUGREPORT_REQUESTED intent. 151 * 152 * @param context Context 153 * @return pair objects for all currently valid BugReportHandler packages and user id 154 */ getValidBugReportHandlerInfos(Context context)155 public List<Pair<ApplicationInfo, Integer>> getValidBugReportHandlerInfos(Context context) { 156 final List<Pair<ApplicationInfo, Integer>> validBugReportHandlerApplicationInfos = 157 new ArrayList<>(); 158 List<String> bugreportAllowlistedPackages; 159 try { 160 bugreportAllowlistedPackages = 161 ActivityManager.getService().getBugreportWhitelistedPackages(); 162 } catch (RemoteException e) { 163 Log.e(TAG, "Failed to get bugreportAllowlistedPackages:", e); 164 return validBugReportHandlerApplicationInfos; 165 } 166 167 int currentUser = UserHandle.getCallingUserId(); 168 169 // Add "Shell" as user's default preference on top of screen 170 if (bugreportAllowlistedPackages.contains(SHELL_APP_PACKAGE) 171 && !getBugReportHandlerAppReceivers(context, SHELL_APP_PACKAGE, 172 currentUser).isEmpty()) { 173 try { 174 validBugReportHandlerApplicationInfos.add( 175 Pair.create( 176 context.getPackageManager().getApplicationInfo(SHELL_APP_PACKAGE, 177 PackageManager.MATCH_ANY_USER), currentUser) 178 ); 179 } catch (PackageManager.NameNotFoundException e) { 180 } 181 } 182 183 final UserManager userManager = context.getSystemService(UserManager.class); 184 final List<UserInfo> profileList = userManager.getProfiles(currentUser); 185 // Only add non-Shell app as normal preference 186 final List<String> nonShellPackageList = bugreportAllowlistedPackages.stream() 187 .filter(pkg -> !SHELL_APP_PACKAGE.equals(pkg)).collect(Collectors.toList()); 188 Collections.sort(nonShellPackageList); 189 for (String pkg : nonShellPackageList) { 190 for (UserInfo profile : profileList) { 191 final int userId = profile.getUserHandle().getIdentifier(); 192 if (getBugReportHandlerAppReceivers(context, pkg, userId).isEmpty()) { 193 continue; 194 } 195 try { 196 validBugReportHandlerApplicationInfos.add( 197 Pair.create(context.getPackageManager() 198 .getApplicationInfo(pkg, PackageManager.MATCH_ANY_USER), 199 userId)); 200 } catch (PackageManager.NameNotFoundException e) { 201 } 202 } 203 } 204 return validBugReportHandlerApplicationInfos; 205 } 206 isBugreportAllowlistedApp(String app)207 private boolean isBugreportAllowlistedApp(String app) { 208 // Verify the app is bugreport-allowlisted 209 if (TextUtils.isEmpty(app)) { 210 return false; 211 } 212 try { 213 return ActivityManager.getService().getBugreportWhitelistedPackages().contains(app); 214 } catch (RemoteException e) { 215 Log.e(TAG, "Failed to get bugreportAllowlistedPackages:", e); 216 return false; 217 } 218 } 219 getBugReportHandlerAppReceivers(Context context, String handlerApp, int handlerUser)220 private List<ResolveInfo> getBugReportHandlerAppReceivers(Context context, String handlerApp, 221 int handlerUser) { 222 // Use the app package and the user id to retrieve the receiver that can handle a 223 // broadcast of the intent. 224 final Intent intent = new Intent(INTENT_BUGREPORT_REQUESTED); 225 intent.setPackage(handlerApp); 226 return context.getPackageManager() 227 .queryBroadcastReceiversAsUser(intent, PackageManager.MATCH_SYSTEM_ONLY, 228 handlerUser); 229 } 230 setBugreportHandlerAppAndUser(Context context, String handlerApp, int handlerUser)231 private void setBugreportHandlerAppAndUser(Context context, String handlerApp, 232 int handlerUser) { 233 Settings.Secure.putString(context.getContentResolver(), 234 Settings.Secure.CUSTOM_BUGREPORT_HANDLER_APP, 235 handlerApp); 236 Settings.Secure.putInt(context.getContentResolver(), 237 Settings.Secure.CUSTOM_BUGREPORT_HANDLER_USER, handlerUser); 238 } 239 240 /** 241 * Show a toast to explain the chosen bug report handler can no longer be chosen. 242 */ showInvalidChoiceToast(Context context)243 public void showInvalidChoiceToast(Context context) { 244 final Toast toast = Toast.makeText(context, 245 R.string.select_invalid_bug_report_handler_toast_text, Toast.LENGTH_SHORT); 246 toast.show(); 247 } 248 } 249