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
This commit is contained in:
Paul Chang
2019-11-28 22:38:49 +08:00
parent 72af90940d
commit 6ae4c4d6ee
12 changed files with 900 additions and 5 deletions

View File

@@ -169,6 +169,10 @@ public class Settings extends SettingsActivity {
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 {}

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}

View File

@@ -56,6 +56,7 @@ import com.android.settings.backup.UserBackupSettingsActivity;
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.MobileNetworkListFragment;
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 @@ public class SettingsGateway {
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 = {

View File

@@ -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());
}
}

View File

@@ -419,6 +419,7 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra
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));