diff options
author | Paul Chang <changpa@google.com> | 2019-11-28 22:38:49 +0800 |
---|---|---|
committer | Paul Chang <changpa@google.com> | 2019-12-13 11:15:53 +0800 |
commit | 6ae4c4d6ee6802927834e1f14ff1474c05d4efcd (patch) | |
tree | 267cc51fda7cc5bfdabdad65082bfae8dbe4feb1 /src/com/android/settings/bugreporthandler | |
parent | 72af90940d456e5f129407ba7c4f554f549fcf92 (diff) | |
download | packages_apps_Settings-6ae4c4d6ee6802927834e1f14ff1474c05d4efcd.tar.gz packages_apps_Settings-6ae4c4d6ee6802927834e1f14ff1474c05d4efcd.tar.bz2 packages_apps_Settings-6ae4c4d6ee6802927834e1f14ff1474c05d4efcd.zip |
Have a setting in Developer Options to choose bug report handler
- This setting let user determines which app handles the Bug Report
shortcut on device.
BUG:143017534
Test: make -j56 RunSettingsRoboTests
Test: Ensure bug report handler setting shows correct handler apps.
Change-Id: I6160dadcd048e6c79f422e58fcd8defa04f991bb
Diffstat (limited to 'src/com/android/settings/bugreporthandler')
-rw-r--r-- | src/com/android/settings/bugreporthandler/BugReportHandlerPicker.java | 205 | ||||
-rw-r--r-- | src/com/android/settings/bugreporthandler/BugReportHandlerUtil.java | 246 |
2 files changed, 451 insertions, 0 deletions
diff --git a/src/com/android/settings/bugreporthandler/BugReportHandlerPicker.java b/src/com/android/settings/bugreporthandler/BugReportHandlerPicker.java new file mode 100644 index 0000000000..9c2ac9e6c8 --- /dev/null +++ b/src/com/android/settings/bugreporthandler/BugReportHandlerPicker.java @@ -0,0 +1,205 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.bugreporthandler; + +import static android.provider.Settings.ACTION_BUGREPORT_HANDLER_SETTINGS; + +import android.app.Activity; +import android.app.settings.SettingsEnums; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageItemInfo; +import android.content.pm.PackageManager; +import android.os.UserHandle; +import android.util.Log; +import android.util.Pair; + +import androidx.annotation.VisibleForTesting; +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.Utils; +import com.android.settings.applications.defaultapps.DefaultAppPickerFragment; +import com.android.settingslib.applications.DefaultAppInfo; +import com.android.settingslib.widget.FooterPreference; + +import java.util.ArrayList; +import java.util.List; + +/** + * Picker for BugReportHandler. + */ +public class BugReportHandlerPicker extends DefaultAppPickerFragment { + private static final String TAG = "BugReportHandlerPicker"; + + private BugReportHandlerUtil mBugReportHandlerUtil; + private FooterPreference mFooter; + + private static String getHandlerApp(String key) { + int index = key.lastIndexOf('#'); + String handlerApp = key.substring(0, index); + return handlerApp; + } + + private static int getHandlerUser(String key) { + int index = key.lastIndexOf('#'); + int handlerUser = 0; + try { + handlerUser = Integer.parseInt(key.substring(index + 1)); + } catch (NumberFormatException nfe) { + Log.e(TAG, "Failed to get handlerUser"); + } + return handlerUser; + } + + @VisibleForTesting + static String getKey(String handlerApp, int handlerUser) { + return handlerApp + "#" + handlerUser; + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.bug_report_handler_settings; + } + + @Override + protected void addStaticPreferences(PreferenceScreen screen) { + if (mFooter == null) { + mFooter = new FooterPreference(screen.getContext()); + mFooter.setIcon(R.drawable.ic_info_outline_24dp); + mFooter.setSingleLineTitle(false); + mFooter.setTitle(R.string.bug_report_handler_picker_footer_text); + mFooter.setSelectable(false); + } + screen.addPreference(mFooter); + } + + @Override + protected List<DefaultAppInfo> getCandidates() { + final Context context = getContext(); + final List<Pair<ApplicationInfo, Integer>> validBugReportHandlerInfos = + getBugReportHandlerUtil().getValidBugReportHandlerInfos(context); + final List<DefaultAppInfo> candidates = new ArrayList<>(); + for (Pair<ApplicationInfo, Integer> info : validBugReportHandlerInfos) { + candidates.add(createDefaultAppInfo(context, mPm, info.second, info.first)); + } + return candidates; + } + + private BugReportHandlerUtil getBugReportHandlerUtil() { + if (mBugReportHandlerUtil == null) { + setBugReportHandlerUtil(createDefaultBugReportHandlerUtil()); + } + return mBugReportHandlerUtil; + } + + @VisibleForTesting + void setBugReportHandlerUtil(BugReportHandlerUtil bugReportHandlerUtil) { + mBugReportHandlerUtil = bugReportHandlerUtil; + } + + @VisibleForTesting + BugReportHandlerUtil createDefaultBugReportHandlerUtil() { + return new BugReportHandlerUtil(); + } + + @Override + protected String getDefaultKey() { + final Pair<String, Integer> pair = + getBugReportHandlerUtil().getCurrentBugReportHandlerAppAndUser(getContext()); + return getKey(pair.first, pair.second); + } + + @Override + protected boolean setDefaultKey(String key) { + return getBugReportHandlerUtil().setCurrentBugReportHandlerAppAndUser(getContext(), + getHandlerApp(key), + getHandlerUser(key)); + } + + @Override + protected void onSelectionPerformed(boolean success) { + super.onSelectionPerformed(success); + if (success) { + final Activity activity = getActivity(); + final Intent intent = activity == null ? null : activity.getIntent(); + if (intent != null && ACTION_BUGREPORT_HANDLER_SETTINGS.equals(intent.getAction())) { + // If this was started through ACTION_BUGREPORT_HANDLER_SETTINGS then return once + // we have chosen a new handler. + getActivity().finish(); + } + } else { + getBugReportHandlerUtil().showInvalidChoiceToast(getContext()); + updateCandidates(); + } + } + + @Override + public int getMetricsCategory() { + return SettingsEnums.SETTINGS_BUGREPORT_HANDLER; + } + + @VisibleForTesting + DefaultAppInfo createDefaultAppInfo(Context context, PackageManager pm, int userId, + PackageItemInfo packageItemInfo) { + return new BugreportHandlerAppInfo(context, pm, userId, packageItemInfo, + getDescription(packageItemInfo.packageName, userId)); + } + + private String getDescription(String handlerApp, int handlerUser) { + final Context context = getContext(); + if (BugReportHandlerUtil.SHELL_APP_PACKAGE.equals(handlerApp)) { + return context.getString(R.string.system_default_app); + } + final UserHandle managedProfile = Utils.getManagedProfile(mUserManager); + if (managedProfile != null && managedProfile.getIdentifier() == handlerUser) { + return context.getString(R.string.work_profile_app); + } + return context.getString(R.string.personal_profile_app); + } + + private static class BugreportHandlerAppInfo extends DefaultAppInfo { + private final Context mContext; + + BugreportHandlerAppInfo(Context context, PackageManager pm, int userId, + PackageItemInfo packageItemInfo, String summary) { + super(context, pm, userId, packageItemInfo, summary, true /* enabled */); + mContext = context; + } + + @Override + public String getKey() { + if (packageItemInfo != null) { + return BugReportHandlerPicker.getKey(packageItemInfo.packageName, userId); + } else { + return null; + } + } + + @Override + public CharSequence loadLabel() { + if (mContext == null || packageItemInfo == null) { + return null; + } + if (BugReportHandlerUtil.SHELL_APP_PACKAGE.equals(packageItemInfo.packageName)) { + return mContext.getString(R.string.shell_app); + } + return super.loadLabel(); + } + } +} diff --git a/src/com/android/settings/bugreporthandler/BugReportHandlerUtil.java b/src/com/android/settings/bugreporthandler/BugReportHandlerUtil.java new file mode 100644 index 0000000000..f4acc7d128 --- /dev/null +++ b/src/com/android/settings/bugreporthandler/BugReportHandlerUtil.java @@ -0,0 +1,246 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.bugreporthandler; + +import android.app.ActivityManager; +import android.content.Context; +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.UserInfo; +import android.os.RemoteException; +import android.os.UserHandle; +import android.os.UserManager; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.Log; +import android.util.Pair; +import android.widget.Toast; + +import com.android.settings.R; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Utility methods related to BugReportHandler. + */ +public class BugReportHandlerUtil { + private static final String TAG = "BugReportHandlerUtil"; + private static final String INTENT_BUGREPORT_REQUESTED = + "com.android.internal.intent.action.BUGREPORT_REQUESTED"; + + public static final String SHELL_APP_PACKAGE = "com.android.shell"; + + public BugReportHandlerUtil() { + } + + /** + * Check is BugReportHandler enabled on the device. + * + * @param context Context + * @return true if BugReportHandler is enabled, or false otherwise + */ + public boolean isBugReportHandlerEnabled(Context context) { + return context.getResources().getBoolean( + com.android.internal.R.bool.config_bugReportHandlerEnabled); + } + + /** + * Fetch the package name currently used as bug report handler app and the user. + * + * @param context Context + * @return a pair of two values, first one is the current bug report handler app, second is + * the user. + */ + public Pair<String, Integer> getCurrentBugReportHandlerAppAndUser(Context context) { + + String handlerApp = getCustomBugReportHandlerApp(context); + int handlerUser = getCustomBugReportHandlerUser(context); + + boolean needToResetOutdatedSettings = false; + if (!isBugreportWhitelistedApp(handlerApp)) { + handlerApp = getDefaultBugReportHandlerApp(context); + handlerUser = UserHandle.USER_SYSTEM; + } else if (getBugReportHandlerAppReceivers(context, handlerApp, handlerUser).isEmpty()) { + // It looks like the settings are outdated, need to reset outdated settings. + // + // i.e. + // If user chooses which profile and which bugreport-whitelisted app in that + // profile to handle a bugreport, then user remove the profile. + // === RESULT === + // The chosen bugreport handler app is outdated because the profile is removed, + // so need to reset outdated settings + handlerApp = getDefaultBugReportHandlerApp(context); + handlerUser = UserHandle.USER_SYSTEM; + needToResetOutdatedSettings = true; + } + + if (!isBugreportWhitelistedApp(handlerApp) + || getBugReportHandlerAppReceivers(context, handlerApp, handlerUser).isEmpty()) { + // It looks like current handler app may be too old and doesn't support to handle a + // bugreport, so change to let shell to handle a bugreport and need to reset + // settings. + handlerApp = SHELL_APP_PACKAGE; + handlerUser = UserHandle.USER_SYSTEM; + needToResetOutdatedSettings = true; + } + + if (needToResetOutdatedSettings) { + setBugreportHandlerAppAndUser(context, handlerApp, handlerUser); + } + + return Pair.create(handlerApp, handlerUser); + } + + private String getCustomBugReportHandlerApp(Context context) { + // Get the package of custom bugreport handler app + return Settings.Global.getString(context.getContentResolver(), + Settings.Global.CUSTOM_BUGREPORT_HANDLER_APP); + } + + private int getCustomBugReportHandlerUser(Context context) { + return Settings.Global.getInt(context.getContentResolver(), + Settings.Global.CUSTOM_BUGREPORT_HANDLER_USER, UserHandle.USER_NULL); + } + + private String getDefaultBugReportHandlerApp(Context context) { + return context.getResources().getString( + com.android.internal.R.string.config_defaultBugReportHandlerApp); + } + + /** + * Change current bug report handler app and user. + * + * @param context Context + * @param handlerApp the package name of the handler app + * @param handlerUser the id of the handler user + * @return whether the change succeeded + */ + public boolean setCurrentBugReportHandlerAppAndUser(Context context, String handlerApp, + int handlerUser) { + if (!isBugreportWhitelistedApp(handlerApp)) { + return false; + } else if (getBugReportHandlerAppReceivers(context, handlerApp, handlerUser).isEmpty()) { + return false; + } + setBugreportHandlerAppAndUser(context, handlerApp, handlerUser); + return true; + } + + /** + * Fetches ApplicationInfo objects and user ids for all currently valid BugReportHandler. + * A BugReportHandler is considered valid if it can receive BUGREPORT_REQUESTED intent. + * + * @param context Context + * @return pair objects for all currently valid BugReportHandler packages and user id + */ + public List<Pair<ApplicationInfo, Integer>> getValidBugReportHandlerInfos(Context context) { + final List<Pair<ApplicationInfo, Integer>> validBugReportHandlerApplicationInfos = + new ArrayList<>(); + List<String> bugreportWhitelistedPackages; + try { + bugreportWhitelistedPackages = + ActivityManager.getService().getBugreportWhitelistedPackages(); + } catch (RemoteException e) { + Log.e(TAG, "Failed to get bugreportWhitelistedPackages:", e); + return validBugReportHandlerApplicationInfos; + } + + // Add "Shell with system user" as System default preference on top of screen + if (bugreportWhitelistedPackages.contains(SHELL_APP_PACKAGE) + && !getBugReportHandlerAppReceivers(context, SHELL_APP_PACKAGE, + UserHandle.USER_SYSTEM).isEmpty()) { + try { + validBugReportHandlerApplicationInfos.add( + Pair.create( + context.getPackageManager().getApplicationInfo(SHELL_APP_PACKAGE, + PackageManager.MATCH_ANY_USER), UserHandle.USER_SYSTEM) + ); + } catch (PackageManager.NameNotFoundException e) { + } + } + + final UserManager userManager = context.getSystemService(UserManager.class); + final List<UserInfo> profileList = userManager.getProfiles(UserHandle.getCallingUserId()); + // Only add non-Shell app as normal preference + final List<String> nonShellPackageList = bugreportWhitelistedPackages.stream() + .filter(pkg -> !SHELL_APP_PACKAGE.equals(pkg)).collect(Collectors.toList()); + Collections.sort(nonShellPackageList); + for (String pkg : nonShellPackageList) { + for (UserInfo profile : profileList) { + final int userId = profile.getUserHandle().getIdentifier(); + if (getBugReportHandlerAppReceivers(context, pkg, userId).isEmpty()) { + continue; + } + try { + validBugReportHandlerApplicationInfos.add( + Pair.create(context.getPackageManager() + .getApplicationInfo(pkg, PackageManager.MATCH_ANY_USER), + userId)); + } catch (PackageManager.NameNotFoundException e) { + } + } + } + return validBugReportHandlerApplicationInfos; + } + + private boolean isBugreportWhitelistedApp(String app) { + // Verify the app is bugreport-whitelisted + if (TextUtils.isEmpty(app)) { + return false; + } + try { + return ActivityManager.getService().getBugreportWhitelistedPackages().contains(app); + } catch (RemoteException e) { + Log.e(TAG, "Failed to get bugreportWhitelistedPackages:", e); + return false; + } + } + + private List<ResolveInfo> getBugReportHandlerAppReceivers(Context context, String handlerApp, + int handlerUser) { + // Use the app package and the user id to retrieve the receiver that can handle a + // broadcast of the intent. + final Intent intent = new Intent(INTENT_BUGREPORT_REQUESTED); + intent.setPackage(handlerApp); + return context.getPackageManager() + .queryBroadcastReceiversAsUser(intent, PackageManager.MATCH_SYSTEM_ONLY, + handlerUser); + } + + private void setBugreportHandlerAppAndUser(Context context, String handlerApp, + int handlerUser) { + Settings.Global.putString(context.getContentResolver(), + Settings.Global.CUSTOM_BUGREPORT_HANDLER_APP, + handlerApp); + Settings.Global.putInt(context.getContentResolver(), + Settings.Global.CUSTOM_BUGREPORT_HANDLER_USER, handlerUser); + } + + /** + * Show a toast to explain the chosen bug report handler can no longer be chosen. + */ + public void showInvalidChoiceToast(Context context) { + final Toast toast = Toast.makeText(context, + R.string.select_invalid_bug_report_handler_toast_text, Toast.LENGTH_SHORT); + toast.show(); + } +} |