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
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index c8fc45d..6ee6a87 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -1974,6 +1974,18 @@
<activity android:name="Settings$WebViewAppPickerActivity"
android:label="@string/select_webview_provider_dialog_title" />
+ <activity android:name="Settings$BugReportHandlerPickerActivity"
+ android:label="@string/bug_report_handler_title"
+ android:exported="true"
+ android:excludeFromRecents="true">
+ <intent-filter>
+ <action android:name="android.settings.BUGREPORT_HANDLER_SETTINGS" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ <meta-data android:name="com.android.settings.FRAGMENT_CLASS"
+ android:value="com.android.settings.bugreporthandler.BugReportHandlerPicker" />
+ </activity>
+
<activity android:name=".bluetooth.BluetoothPairingDialog"
android:excludeFromRecents="true"
android:windowSoftInputMode="stateVisible|adjustResize"
diff --git a/res/values/strings.xml b/res/values/strings.xml
index d9020e8..ccaaf1a 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -11361,4 +11361,22 @@
<!-- DSU Loader Loading. Do not translate. -->
<string name="dsu_loader_loading" translatable="false">Loading...</string>
+ <!-- Name of dev option called "Bug report handler" [CHAR LIMIT=NONE] -->
+ <string name="bug_report_handler_title">Bug report handler</string>
+
+ <!-- Developer Settings: Footer text for bug report handler picker [CHAR LIMIT=NONE] -->
+ <string name="bug_report_handler_picker_footer_text">Determines which app handles the Bug Report shortcut on your device.</string>
+
+ <!-- Label of personal profile app for current setting [CHAR LIMIT=NONE] -->
+ <string name="personal_profile_app">(Personal)</string>
+
+ <!-- Label of work profile app for current setting [CHAR LIMIT=NONE] -->
+ <string name="work_profile_app">(Work)</string>
+
+ <!-- Title of Shell app for current setting [CHAR LIMIT=NONE] -->
+ <string name="shell_app">Android System (Shell)</string>
+
+ <!-- Developer settings: text for the bug report handler selection toast shown if an invalid bug report handler was chosen. [CHAR LIMIT=NONE] -->
+ <string name="select_invalid_bug_report_handler_toast_text">This choice is no longer valid. Try again.</string>
+
</resources>
diff --git a/res/xml/bug_report_handler_settings.xml b/res/xml/bug_report_handler_settings.xml
new file mode 100644
index 0000000..41e8f08
--- /dev/null
+++ b/res/xml/bug_report_handler_settings.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ 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.
+ -->
+
+<PreferenceScreen
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:settings="http://schemas.android.com/apk/res-auto"
+ android:title="@string/bug_report_handler_title"
+ settings:staticPreferenceLocation="append" >
+</PreferenceScreen>
\ No newline at end of file
diff --git a/res/xml/development_settings.xml b/res/xml/development_settings.xml
index ece9822..87eb894 100644
--- a/res/xml/development_settings.xml
+++ b/res/xml/development_settings.xml
@@ -36,6 +36,11 @@
android:dialogTitle="@*android:string/bugreport_title" />
<Preference
+ android:key="bug_report_handler"
+ android:title="@string/bug_report_handler_title"
+ android:fragment="com.android.settings.bugreporthandler.BugReportHandlerPicker" />
+
+ <Preference
android:key="system_server_heap_dump"
android:title="@string/capture_system_heap_dump_title" />
diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java
index e203699..46992ef 100644
--- a/src/com/android/settings/Settings.java
+++ b/src/com/android/settings/Settings.java
@@ -169,6 +169,10 @@
public static class WifiCallingDisclaimerActivity extends SettingsActivity { /* empty */ }
public static class MobileNetworkListActivity extends SettingsActivity {}
public static class GlobalActionsPanelSettingsActivity extends SettingsActivity {}
+ /**
+ * Activity for BugReportHandlerPicker.
+ */
+ public static class BugReportHandlerPickerActivity extends SettingsActivity { /* empty */ }
// Top level categories for new IA
public static class NetworkDashboardActivity extends SettingsActivity {}
diff --git a/src/com/android/settings/bugreporthandler/BugReportHandlerPicker.java b/src/com/android/settings/bugreporthandler/BugReportHandlerPicker.java
new file mode 100644
index 0000000..9c2ac9e
--- /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 0000000..f4acc7d
--- /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();
+ }
+}
diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java
index 4ad4c5f..01b1598 100644
--- a/src/com/android/settings/core/gateway/SettingsGateway.java
+++ b/src/com/android/settings/core/gateway/SettingsGateway.java
@@ -56,6 +56,7 @@
import com.android.settings.biometrics.face.FaceSettings;
import com.android.settings.biometrics.fingerprint.FingerprintSettings;
import com.android.settings.bluetooth.BluetoothDeviceDetailsFragment;
+import com.android.settings.bugreporthandler.BugReportHandlerPicker;
import com.android.settings.connecteddevice.AdvancedConnectedDeviceDashboardFragment;
import com.android.settings.connecteddevice.ConnectedDeviceDashboardFragment;
import com.android.settings.connecteddevice.PreviouslyConnectedDeviceDashboardFragment;
@@ -106,14 +107,14 @@
import com.android.settings.network.NetworkDashboardFragment;
import com.android.settings.nfc.AndroidBeam;
import com.android.settings.nfc.PaymentSettings;
-import com.android.settings.notification.app.AppBubbleNotificationSettings;
-import com.android.settings.notification.app.AppNotificationSettings;
-import com.android.settings.notification.app.ChannelNotificationSettings;
import com.android.settings.notification.ConfigureNotificationSettings;
import com.android.settings.notification.NotificationAccessSettings;
import com.android.settings.notification.NotificationAssistantPicker;
-import com.android.settings.notification.history.NotificationStation;
import com.android.settings.notification.SoundSettings;
+import com.android.settings.notification.app.AppBubbleNotificationSettings;
+import com.android.settings.notification.app.AppNotificationSettings;
+import com.android.settings.notification.app.ChannelNotificationSettings;
+import com.android.settings.notification.history.NotificationStation;
import com.android.settings.notification.zen.ZenAccessSettings;
import com.android.settings.notification.zen.ZenModeAutomationSettings;
import com.android.settings.notification.zen.ZenModeBlockedEffectsSettings;
@@ -287,7 +288,8 @@
BatterySaverScheduleSettings.class.getName(),
MobileNetworkListFragment.class.getName(),
GlobalActionsPanelSettings.class.getName(),
- DarkModeSettingsFragment.class.getName()
+ DarkModeSettingsFragment.class.getName(),
+ BugReportHandlerPicker.class.getName()
};
public static final String[] SETTINGS_FOR_RESTRICTED = {
diff --git a/src/com/android/settings/development/BugReportHandlerPreferenceController.java b/src/com/android/settings/development/BugReportHandlerPreferenceController.java
new file mode 100644
index 0000000..b95d31b
--- /dev/null
+++ b/src/com/android/settings/development/BugReportHandlerPreferenceController.java
@@ -0,0 +1,87 @@
+/*
+ * 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.development;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.UserManager;
+import android.text.TextUtils;
+
+import androidx.annotation.VisibleForTesting;
+import androidx.preference.Preference;
+
+import com.android.settings.R;
+import com.android.settings.bugreporthandler.BugReportHandlerUtil;
+import com.android.settings.core.PreferenceControllerMixin;
+import com.android.settingslib.development.DeveloperOptionsPreferenceController;
+
+/**
+ * PreferenceController for BugReportHandler
+ */
+public class BugReportHandlerPreferenceController extends DeveloperOptionsPreferenceController
+ implements PreferenceControllerMixin {
+
+ private static final String KEY_BUG_REPORT_HANDLER = "bug_report_handler";
+
+ private final UserManager mUserManager;
+ private final BugReportHandlerUtil mBugReportHandlerUtil;
+
+ public BugReportHandlerPreferenceController(Context context) {
+ super(context);
+ mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+ mBugReportHandlerUtil = new BugReportHandlerUtil();
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return !mUserManager.hasUserRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES)
+ && mBugReportHandlerUtil.isBugReportHandlerEnabled(mContext);
+ }
+
+ @Override
+ public String getPreferenceKey() {
+ return KEY_BUG_REPORT_HANDLER;
+ }
+
+ @Override
+ public void updateState(Preference preference) {
+ final CharSequence currentBugReportHandlerAppLabel = getCurrentBugReportHandlerAppLabel();
+ if (!TextUtils.isEmpty(currentBugReportHandlerAppLabel)) {
+ mPreference.setSummary(currentBugReportHandlerAppLabel);
+ } else {
+ mPreference.setSummary(R.string.app_list_preference_none);
+ }
+ }
+
+ @VisibleForTesting
+ CharSequence getCurrentBugReportHandlerAppLabel() {
+ final String handlerApp = mBugReportHandlerUtil.getCurrentBugReportHandlerAppAndUser(
+ mContext).first;
+ if (BugReportHandlerUtil.SHELL_APP_PACKAGE.equals(handlerApp)) {
+ return mContext.getString(R.string.shell_app);
+ }
+ ApplicationInfo applicationInfo;
+ try {
+ applicationInfo = mContext.getPackageManager().getApplicationInfo(handlerApp,
+ PackageManager.MATCH_ANY_USER);
+ } catch (PackageManager.NameNotFoundException e) {
+ return null;
+ }
+ return applicationInfo.loadLabel(mContext.getPackageManager());
+ }
+}
diff --git a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
index 7fc898b..b3bbf75 100644
--- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
+++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java
@@ -419,6 +419,7 @@
final List<AbstractPreferenceController> controllers = new ArrayList<>();
controllers.add(new MemoryUsagePreferenceController(context));
controllers.add(new BugReportPreferenceController(context));
+ controllers.add(new BugReportHandlerPreferenceController(context));
controllers.add(new SystemServerHeapDumpPreferenceController(context));
controllers.add(new LocalBackupPasswordPreferenceController(context));
controllers.add(new StayAwakePreferenceController(context, lifecycle));
diff --git a/tests/robotests/src/com/android/settings/bugreporthandler/BugReportHandlerPickerTest.java b/tests/robotests/src/com/android/settings/bugreporthandler/BugReportHandlerPickerTest.java
new file mode 100644
index 0000000..fb48ad1
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/bugreporthandler/BugReportHandlerPickerTest.java
@@ -0,0 +1,177 @@
+/*
+ * 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 static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.graphics.drawable.ColorDrawable;
+import android.util.Pair;
+
+import androidx.fragment.app.FragmentActivity;
+
+import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
+import com.android.settingslib.widget.RadioButtonPreference;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
+import org.robolectric.shadows.ShadowPackageManager;
+import org.robolectric.util.ReflectionHelpers;
+
+import java.util.Collections;
+
+@RunWith(RobolectricTestRunner.class)
+public class BugReportHandlerPickerTest {
+ private static final String PACKAGE_NAME = "com.example.test";
+ private static final int USER_ID = 0;
+
+ @Mock
+ private FragmentActivity mActivity;
+
+ private Context mContext;
+ private ShadowPackageManager mPackageManager;
+ private BugReportHandlerPicker mPicker;
+ private BugReportHandlerUtil mBugReportHandlerUtil;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = spy(RuntimeEnvironment.application);
+ mPackageManager = Shadows.shadowOf(mContext.getPackageManager());
+
+ final ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.name = PACKAGE_NAME;
+ applicationInfo.uid = 0;
+ applicationInfo.flags = 0;
+ applicationInfo.packageName = PACKAGE_NAME;
+
+ final PackageInfo packageInfo = new PackageInfo();
+ packageInfo.packageName = PACKAGE_NAME;
+ packageInfo.applicationInfo = applicationInfo;
+ mPackageManager.addPackage(packageInfo);
+ mPackageManager.setUnbadgedApplicationIcon(PACKAGE_NAME, new ColorDrawable());
+
+ mPicker = spy(new BugReportHandlerPicker());
+ doNothing().when(mPicker).updateCandidates();
+ doNothing().when(mPicker).updateCheckedState(any());
+ doReturn(mActivity).when(mPicker).getActivity();
+
+ ReflectionHelpers.setField(mPicker, "mMetricsFeatureProvider",
+ mock(MetricsFeatureProvider.class));
+ mBugReportHandlerUtil = mock(BugReportHandlerUtil.class);
+ mPicker.setBugReportHandlerUtil(mBugReportHandlerUtil);
+ }
+
+ @After
+ public void tearDown() {
+ mPackageManager.removePackage(PACKAGE_NAME);
+ }
+
+ @Test
+ public void clickItem_success() {
+ testClickingItemSuccess();
+ }
+
+ @Test
+ public void clickItem_fail() {
+ testClickingItemFail();
+ }
+
+ @Test
+ public void clickItem_usingBugReportHandlerSettingIntent_success() {
+ useBugReportHandlerSettingIntent();
+ testClickingItemSuccess();
+ verify(mActivity, times(1)).finish();
+ }
+
+ @Test
+ public void clickItem_fromBugReportHandlerSettingIntent_fail() {
+ useBugReportHandlerSettingIntent();
+ testClickingItemFail();
+ }
+
+ private static ApplicationInfo createApplicationInfo(String packageName) {
+ ApplicationInfo applicationInfo = new ApplicationInfo();
+ applicationInfo.packageName = packageName;
+ return applicationInfo;
+ }
+
+ private void testClickingItemSuccess() {
+ when(mBugReportHandlerUtil.getValidBugReportHandlerInfos(any()))
+ .thenReturn(Collections.singletonList(Pair.create(
+ createApplicationInfo(PACKAGE_NAME), USER_ID)));
+ when(mBugReportHandlerUtil.setCurrentBugReportHandlerAppAndUser(any(), eq(PACKAGE_NAME),
+ eq(USER_ID))).thenReturn(true);
+
+ RadioButtonPreference defaultPackagePref = mock(RadioButtonPreference.class);
+ when(defaultPackagePref.getKey()).thenReturn(
+ BugReportHandlerPicker.getKey(PACKAGE_NAME, USER_ID));
+ mPicker.onRadioButtonClicked(defaultPackagePref);
+
+ verify(mBugReportHandlerUtil, times(1)).setCurrentBugReportHandlerAppAndUser(any(),
+ eq(PACKAGE_NAME), eq(USER_ID));
+ verify(mPicker, times(1)).updateCheckedState(
+ BugReportHandlerPicker.getKey(PACKAGE_NAME, USER_ID));
+ verify(mBugReportHandlerUtil, never()).showInvalidChoiceToast(any());
+ }
+
+ private void testClickingItemFail() {
+ when(mBugReportHandlerUtil.getValidBugReportHandlerInfos(any()))
+ .thenReturn(Collections.singletonList(Pair.create(
+ createApplicationInfo(PACKAGE_NAME), USER_ID)));
+ when(mBugReportHandlerUtil.setCurrentBugReportHandlerAppAndUser(any(), eq(PACKAGE_NAME),
+ eq(USER_ID))).thenReturn(false);
+
+ RadioButtonPreference defaultPackagePref = mock(RadioButtonPreference.class);
+ when(defaultPackagePref.getKey()).thenReturn(
+ BugReportHandlerPicker.getKey(PACKAGE_NAME, USER_ID));
+ mPicker.onRadioButtonClicked(defaultPackagePref);
+
+ verify(mBugReportHandlerUtil, times(1)).setCurrentBugReportHandlerAppAndUser(any(),
+ eq(PACKAGE_NAME), eq(USER_ID));
+ // Ensure we update the list of packages when we click a non-valid package - the list must
+ // have changed, otherwise this click wouldn't fail.
+ verify(mPicker, times(1)).updateCandidates();
+ verify(mBugReportHandlerUtil, times(1)).showInvalidChoiceToast(any());
+ }
+
+ private void useBugReportHandlerSettingIntent() {
+ Intent intent = new Intent(ACTION_BUGREPORT_HANDLER_SETTINGS);
+ when(mActivity.getIntent()).thenReturn(intent);
+ }
+}
diff --git a/tests/robotests/src/com/android/settings/development/BugReportHandlerPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/development/BugReportHandlerPreferenceControllerTest.java
new file mode 100644
index 0000000..6b47a80
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/development/BugReportHandlerPreferenceControllerTest.java
@@ -0,0 +1,115 @@
+/*
+ * 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.development;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.os.UserManager;
+
+import androidx.preference.Preference;
+import androidx.preference.PreferenceScreen;
+
+import com.android.settings.R;
+import com.android.settings.bugreporthandler.BugReportHandlerUtil;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.util.ReflectionHelpers;
+
+@RunWith(RobolectricTestRunner.class)
+public class BugReportHandlerPreferenceControllerTest {
+
+ @Mock
+ private PreferenceScreen mPreferenceScreen;
+ @Mock
+ private UserManager mUserManager;
+ @Mock
+ private BugReportHandlerUtil mBugReportHandlerUtil;
+ @Mock
+ private Preference mPreference;
+
+ private BugReportHandlerPreferenceController mController;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mController = spy(new BugReportHandlerPreferenceController(RuntimeEnvironment.application));
+ ReflectionHelpers.setField(mController, "mUserManager", mUserManager);
+ ReflectionHelpers.setField(mController, "mBugReportHandlerUtil", mBugReportHandlerUtil);
+ }
+
+ @Test
+ public void isAvailable_hasDebugRestriction_notAvailable() {
+ doReturn(true).when(mUserManager).hasUserRestriction(anyString());
+ doReturn(true).when(mBugReportHandlerUtil).isBugReportHandlerEnabled(any(Context.class));
+
+ assertThat(mController.isAvailable()).isFalse();
+ }
+
+ @Test
+ public void isAvailable_bugReportHandlerDisabled_notAvailable() {
+ doReturn(false).when(mBugReportHandlerUtil).isBugReportHandlerEnabled(any(Context.class));
+ doReturn(false).when(mUserManager).hasUserRestriction(anyString());
+
+ assertThat(mController.isAvailable()).isFalse();
+ }
+
+ @Test
+ public void isAvailable_noDebugRestrictionAndBugReportHandlerEnabled_available() {
+ doReturn(false).when(mUserManager).hasUserRestriction(anyString());
+ doReturn(true).when(mBugReportHandlerUtil).isBugReportHandlerEnabled(any(Context.class));
+
+ assertThat(mController.isAvailable()).isTrue();
+ }
+
+ @Test
+ public void updateState_hasCurrentBugReportHandlerAppLabel_setAppLabel() {
+ when(mPreferenceScreen.findPreference(mController.getPreferenceKey()))
+ .thenReturn(mPreference);
+ mController.displayPreference(mPreferenceScreen);
+ doReturn("SomeRandomAppLabel!!!").when(mController).getCurrentBugReportHandlerAppLabel();
+
+ mController.updateState(mPreference);
+
+ verify(mPreference).setSummary("SomeRandomAppLabel!!!");
+ }
+
+ @Test
+ public void updateState_noCurrentBugReportHandlerAppLabel_setAppDefaultLabel() {
+ when(mPreferenceScreen.findPreference(mController.getPreferenceKey()))
+ .thenReturn(mPreference);
+ mController.displayPreference(mPreferenceScreen);
+ doReturn(null).when(mController).getCurrentBugReportHandlerAppLabel();
+
+ mController.updateState(mPreference);
+
+ verify(mPreference).setSummary(R.string.app_list_preference_none);
+ }
+}