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:
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user