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 = UserHandle.USER_SYSTEM; 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 = UserHandle.USER_SYSTEM; 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 = UserHandle.USER_SYSTEM; 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.Global.getString(context.getContentResolver(), 116 Settings.Global.CUSTOM_BUGREPORT_HANDLER_APP); 117 } 118 getCustomBugReportHandlerUser(Context context)119 private int getCustomBugReportHandlerUser(Context context) { 120 return Settings.Global.getInt(context.getContentResolver(), 121 Settings.Global.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 // Add "Shell with system user" as System default preference on top of screen 168 if (bugreportAllowlistedPackages.contains(SHELL_APP_PACKAGE) 169 && !getBugReportHandlerAppReceivers(context, SHELL_APP_PACKAGE, 170 UserHandle.USER_SYSTEM).isEmpty()) { 171 try { 172 validBugReportHandlerApplicationInfos.add( 173 Pair.create( 174 context.getPackageManager().getApplicationInfo(SHELL_APP_PACKAGE, 175 PackageManager.MATCH_ANY_USER), UserHandle.USER_SYSTEM) 176 ); 177 } catch (PackageManager.NameNotFoundException e) { 178 } 179 } 180 181 final UserManager userManager = context.getSystemService(UserManager.class); 182 final List<UserInfo> profileList = userManager.getProfiles(UserHandle.getCallingUserId()); 183 // Only add non-Shell app as normal preference 184 final List<String> nonShellPackageList = bugreportAllowlistedPackages.stream() 185 .filter(pkg -> !SHELL_APP_PACKAGE.equals(pkg)).collect(Collectors.toList()); 186 Collections.sort(nonShellPackageList); 187 for (String pkg : nonShellPackageList) { 188 for (UserInfo profile : profileList) { 189 final int userId = profile.getUserHandle().getIdentifier(); 190 if (getBugReportHandlerAppReceivers(context, pkg, userId).isEmpty()) { 191 continue; 192 } 193 try { 194 validBugReportHandlerApplicationInfos.add( 195 Pair.create(context.getPackageManager() 196 .getApplicationInfo(pkg, PackageManager.MATCH_ANY_USER), 197 userId)); 198 } catch (PackageManager.NameNotFoundException e) { 199 } 200 } 201 } 202 return validBugReportHandlerApplicationInfos; 203 } 204 isBugreportAllowlistedApp(String app)205 private boolean isBugreportAllowlistedApp(String app) { 206 // Verify the app is bugreport-allowlisted 207 if (TextUtils.isEmpty(app)) { 208 return false; 209 } 210 try { 211 return ActivityManager.getService().getBugreportWhitelistedPackages().contains(app); 212 } catch (RemoteException e) { 213 Log.e(TAG, "Failed to get bugreportAllowlistedPackages:", e); 214 return false; 215 } 216 } 217 getBugReportHandlerAppReceivers(Context context, String handlerApp, int handlerUser)218 private List<ResolveInfo> getBugReportHandlerAppReceivers(Context context, String handlerApp, 219 int handlerUser) { 220 // Use the app package and the user id to retrieve the receiver that can handle a 221 // broadcast of the intent. 222 final Intent intent = new Intent(INTENT_BUGREPORT_REQUESTED); 223 intent.setPackage(handlerApp); 224 return context.getPackageManager() 225 .queryBroadcastReceiversAsUser(intent, PackageManager.MATCH_SYSTEM_ONLY, 226 handlerUser); 227 } 228 setBugreportHandlerAppAndUser(Context context, String handlerApp, int handlerUser)229 private void setBugreportHandlerAppAndUser(Context context, String handlerApp, 230 int handlerUser) { 231 Settings.Global.putString(context.getContentResolver(), 232 Settings.Global.CUSTOM_BUGREPORT_HANDLER_APP, 233 handlerApp); 234 Settings.Global.putInt(context.getContentResolver(), 235 Settings.Global.CUSTOM_BUGREPORT_HANDLER_USER, handlerUser); 236 } 237 238 /** 239 * Show a toast to explain the chosen bug report handler can no longer be chosen. 240 */ showInvalidChoiceToast(Context context)241 public void showInvalidChoiceToast(Context context) { 242 final Toast toast = Toast.makeText(context, 243 R.string.select_invalid_bug_report_handler_toast_text, Toast.LENGTH_SHORT); 244 toast.show(); 245 } 246 } 247