diff --git a/AndroidManifest.xml b/AndroidManifest.xml index c8fc45d2787..6ee6a876fe0 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -1974,6 +1974,18 @@ + + + + + + + + Loading... + + Bug report handler + + + Determines which app handles the Bug Report shortcut on your device. + + + (Personal) + + + (Work) + + + Android System (Shell) + + + This choice is no longer valid. Try again. + diff --git a/res/xml/bug_report_handler_settings.xml b/res/xml/bug_report_handler_settings.xml new file mode 100644 index 00000000000..41e8f08d899 --- /dev/null +++ b/res/xml/bug_report_handler_settings.xml @@ -0,0 +1,23 @@ + + + + + \ No newline at end of file diff --git a/res/xml/development_settings.xml b/res/xml/development_settings.xml index ece9822a80f..87eb894665f 100644 --- a/res/xml/development_settings.xml +++ b/res/xml/development_settings.xml @@ -35,6 +35,11 @@ android:title="@*android:string/bugreport_title" android:dialogTitle="@*android:string/bugreport_title" /> + + diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java index e20369946f5..46992ef68d9 100644 --- a/src/com/android/settings/Settings.java +++ b/src/com/android/settings/Settings.java @@ -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 {} diff --git a/src/com/android/settings/bugreporthandler/BugReportHandlerPicker.java b/src/com/android/settings/bugreporthandler/BugReportHandlerPicker.java new file mode 100644 index 00000000000..9c2ac9e6c83 --- /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 getCandidates() { + final Context context = getContext(); + final List> validBugReportHandlerInfos = + getBugReportHandlerUtil().getValidBugReportHandlerInfos(context); + final List candidates = new ArrayList<>(); + for (Pair 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 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 00000000000..f4acc7d1282 --- /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 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> getValidBugReportHandlerInfos(Context context) { + final List> validBugReportHandlerApplicationInfos = + new ArrayList<>(); + List 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 profileList = userManager.getProfiles(UserHandle.getCallingUserId()); + // Only add non-Shell app as normal preference + final List 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 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 4ad4c5f4f32..01b15983f45 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.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 = { diff --git a/src/com/android/settings/development/BugReportHandlerPreferenceController.java b/src/com/android/settings/development/BugReportHandlerPreferenceController.java new file mode 100644 index 00000000000..b95d31bb18e --- /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 7fc898bf452..b3bbf756b5b 100644 --- a/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java +++ b/src/com/android/settings/development/DevelopmentSettingsDashboardFragment.java @@ -419,6 +419,7 @@ public class DevelopmentSettingsDashboardFragment extends RestrictedDashboardFra final List 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 00000000000..fb48ad17f05 --- /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 00000000000..6b47a808f4c --- /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); + } +}