From c76a11f0c1f35c1e7c0eddcf7a760fac7fbfa503 Mon Sep 17 00:00:00 2001 From: Ricky Wai Date: Tue, 25 Jan 2022 23:04:26 +0000 Subject: [PATCH] Add restricted settings UI in Settings accessibility screeen If OP_ACCESS_RESTRICTED_SETTINGS is rejected, it means accessibility page for that app is gray out and app info won't show "unlock restricted settings menu" If OP_ACCESS_RESTRICTED_SETTINGS is ignored, it means accessibility page for that app is gray out, but app info shows "unlock restricted settings menu" If OP_ACCESS_RESTRICTED_SETTINGS is allowed(default), it means users can access accessibility page for that app. OP_ACCESS_RESTRICTED_SETTINGS will be changed to ignored if user visited the restricted settings dialog. OP_ACCESS_RESTRICTED_SETTINGS will be changed to allowed if user passes the confirmation screen. Bug: 202130031 Test: Tested the UI and it works correctly Change-Id: I3dfb94cee440658b4726a1c3f7265f93cd19ed3e --- AndroidManifest.xml | 12 +++ ..._dialog.xml => support_details_dialog.xml} | 0 res/values/strings.xml | 9 ++ .../ActionDisabledByAppOpsDialog.java | 65 ++++++++++++++ .../ActionDisabledByAppOpsHelper.java | 88 +++++++++++++++++++ .../AccessibilityDetailsSettingsFragment.java | 23 ++++- .../accessibility/AccessibilitySettings.java | 61 +++++++++---- .../appinfo/AppInfoDashboardFragment.java | 76 ++++++++++++++++ .../ActionDisabledByAdminDialogHelper.java | 2 +- ...essibilityDetailsSettingsFragmentTest.java | 3 +- .../AccessibilitySettingsTest.java | 1 + 11 files changed, 315 insertions(+), 25 deletions(-) rename res/layout/{admin_support_details_dialog.xml => support_details_dialog.xml} (100%) create mode 100644 src/com/android/settings/ActionDisabledByAppOpsDialog.java create mode 100644 src/com/android/settings/ActionDisabledByAppOpsHelper.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 7d47fefc120..20d399a2e1e 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -3618,6 +3618,18 @@ + + + + + + + Clear storage Uninstall updates + + Unlock restricted settings Some activities you\u2019ve selected open in this app by default. @@ -12004,6 +12006,10 @@ " " Learn more + + Restricted Settings + + For your security, this setting is currently unavailable. @@ -12248,6 +12254,9 @@ + + + Update Priority mode diff --git a/src/com/android/settings/ActionDisabledByAppOpsDialog.java b/src/com/android/settings/ActionDisabledByAppOpsDialog.java new file mode 100644 index 00000000000..15b8503a4c8 --- /dev/null +++ b/src/com/android/settings/ActionDisabledByAppOpsDialog.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2018 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; + +import android.app.Activity; +import android.app.AppOpsManager; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; + +public class ActionDisabledByAppOpsDialog extends Activity + implements DialogInterface.OnDismissListener { + + private static final String TAG = "ActionDisabledByAppOpsDialog"; + + private ActionDisabledByAppOpsHelper mDialogHelper; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + mDialogHelper = new ActionDisabledByAppOpsHelper(this); + mDialogHelper.prepareDialogBuilder() + .setOnDismissListener(this) + .show(); + updateAppOps(); + } + + private void updateAppOps() { + final Intent intent = getIntent(); + final String packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME); + final int uid = intent.getIntExtra(Intent.EXTRA_UID, android.os.Process.INVALID_UID); + getSystemService(AppOpsManager.class) + .setMode(AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS, + uid, + packageName, + AppOpsManager.MODE_IGNORED); + } + + @Override + public void onNewIntent(Intent intent) { + super.onNewIntent(intent); + mDialogHelper.updateDialog(); + updateAppOps(); + } + + @Override + public void onDismiss(DialogInterface dialog) { + finish(); + } +} diff --git a/src/com/android/settings/ActionDisabledByAppOpsHelper.java b/src/com/android/settings/ActionDisabledByAppOpsHelper.java new file mode 100644 index 00000000000..271b09a4238 --- /dev/null +++ b/src/com/android/settings/ActionDisabledByAppOpsHelper.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2018 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; + +import android.app.Activity; +import android.content.ActivityNotFoundException; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.Uri; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.VisibleForTesting; +import androidx.appcompat.app.AlertDialog; + +import com.android.settingslib.HelpUtils; + +final class ActionDisabledByAppOpsHelper { + + private final ViewGroup mDialogView; + private final Activity mActivity; + + ActionDisabledByAppOpsHelper(Activity activity) { + mActivity = activity; + mDialogView = (ViewGroup) LayoutInflater.from(mActivity).inflate( + R.layout.support_details_dialog, null); + } + + public AlertDialog.Builder prepareDialogBuilder() { + final String helpUrl = mActivity.getString( + R.string.help_url_action_disabled_by_restricted_settings); + AlertDialog.Builder builder = new AlertDialog.Builder(mActivity) + .setPositiveButton(R.string.okay, null) + .setView(mDialogView); + if (!TextUtils.isEmpty(helpUrl)) { + builder.setNeutralButton(R.string.learn_more, + (DialogInterface.OnClickListener) (dialog, which) -> { + final Intent intent = HelpUtils.getHelpIntent(mActivity, + helpUrl, mActivity.getClass().getName()); + if (intent != null) { + mActivity.startActivity(intent); + } + }); + } + initializeDialogViews(mDialogView); + return builder; + } + + public void updateDialog() { + initializeDialogViews(mDialogView); + } + + private void initializeDialogViews(View root) { + setSupportTitle(root); + setSupportDetails(root); + } + + @VisibleForTesting + void setSupportTitle(View root) { + final TextView titleView = root.findViewById(R.id.admin_support_dialog_title); + if (titleView == null) { + return; + } + titleView.setText(R.string.blocked_by_restricted_settings_title); + } + + void setSupportDetails(final View root) { + final TextView textView = root.findViewById(R.id.admin_support_msg); + textView.setText(R.string.blocked_by_restricted_settings_content); + } +} diff --git a/src/com/android/settings/accessibility/AccessibilityDetailsSettingsFragment.java b/src/com/android/settings/accessibility/AccessibilityDetailsSettingsFragment.java index 7c5297b4ccf..f9b537b16c3 100644 --- a/src/com/android/settings/accessibility/AccessibilityDetailsSettingsFragment.java +++ b/src/com/android/settings/accessibility/AccessibilityDetailsSettingsFragment.java @@ -21,6 +21,7 @@ import static com.android.internal.accessibility.AccessibilityShortcutController import android.accessibilityservice.AccessibilityServiceInfo; import android.app.Activity; +import android.app.AppOpsManager; import android.app.admin.DevicePolicyManager; import android.app.settings.SettingsEnums; import android.content.ComponentName; @@ -49,6 +50,7 @@ import java.util.Set; public class AccessibilityDetailsSettingsFragment extends InstrumentedFragment { private final static String TAG = "A11yDetailsSettings"; + private AppOpsManager mAppOps; @Override public int getMetricsCategory() { @@ -59,6 +61,8 @@ public class AccessibilityDetailsSettingsFragment extends InstrumentedFragment { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + mAppOps = getActivity().getSystemService(AppOpsManager.class); + // In case the Intent doesn't have component name, go to a11y services list. final String extraComponentName = getActivity().getIntent().getStringExtra( Intent.EXTRA_COMPONENT_NAME); @@ -127,10 +131,11 @@ public class AccessibilityDetailsSettingsFragment extends InstrumentedFragment { } // In case this accessibility service isn't permitted, go to a11y services list. - if (!isServiceAllowed(componentName.getPackageName())) { + if (!isServiceAllowed(info.getResolveInfo().serviceInfo.applicationInfo.uid, + componentName.getPackageName())) { Log.w(TAG, "openAccessibilityDetailsSettingsAndFinish: target accessibility service is" - + "prohibited by Device Admin."); + + "prohibited by Device Admin or App Op."); return false; } openSubSettings(ToggleAccessibilityServicePreferenceFragment.class.getName(), @@ -148,11 +153,21 @@ public class AccessibilityDetailsSettingsFragment extends InstrumentedFragment { } @VisibleForTesting - boolean isServiceAllowed(String packageName) { + boolean isServiceAllowed(int uid, String packageName) { final DevicePolicyManager dpm = getContext().getSystemService(DevicePolicyManager.class); final List permittedServices = dpm.getPermittedAccessibilityServices( UserHandle.myUserId()); - return (permittedServices == null || permittedServices.contains(packageName)); + if (permittedServices != null && !permittedServices.contains(packageName)) { + return false; + } + try { + final int mode = mAppOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS, + uid, packageName); + return mode != AppOpsManager.MODE_ERRORED && mode != AppOpsManager.MODE_IGNORED; + } catch (Exception e) { + // Fallback in case if app ops is not available in testing. + return true; + } } private AccessibilityServiceInfo getAccessibilityServiceInfo(ComponentName componentName) { diff --git a/src/com/android/settings/accessibility/AccessibilitySettings.java b/src/com/android/settings/accessibility/AccessibilitySettings.java index c08e6690ac4..b712b9d37bc 100644 --- a/src/com/android/settings/accessibility/AccessibilitySettings.java +++ b/src/com/android/settings/accessibility/AccessibilitySettings.java @@ -20,6 +20,7 @@ import static com.android.settingslib.widget.TwoTargetPreference.ICON_SIZE_MEDIU import android.accessibilityservice.AccessibilityServiceInfo; import android.accessibilityservice.AccessibilityShortcutInfo; +import android.app.AppOpsManager; import android.app.admin.DevicePolicyManager; import android.app.settings.SettingsEnums; import android.content.ComponentName; @@ -200,6 +201,12 @@ public class AccessibilitySettings extends DashboardFragment { registerContentMonitors(); } + @Override + public void onResume() { + super.onResume(); + updateAllPreferences(); + } + @Override public void onStart() { if (mNeedPreferencesUpdate) { @@ -506,11 +513,13 @@ public class AccessibilitySettings extends DashboardFragment { private final Context mContext; private final DevicePolicyManager mDpm; private final PackageManager mPm; + private final AppOpsManager mAppOps; RestrictedPreferenceHelper(Context context) { mContext = context; mDpm = context.getSystemService(DevicePolicyManager.class); mPm = context.getPackageManager(); + mAppOps = context.getSystemService(AppOpsManager.class); } /** @@ -553,14 +562,11 @@ public class AccessibilitySettings extends DashboardFragment { } final RestrictedPreference preference = createRestrictedPreference(key, title, - summary, icon, fragment); + summary, icon, fragment, packageName, + resolveInfo.serviceInfo.applicationInfo.uid); - // permittedServices null means all accessibility services are allowed. - final boolean serviceAllowed = - permittedServices == null || permittedServices.contains(packageName); + setRestrictedPreferenceEnabled(preference, permittedServices, serviceEnabled); - setRestrictedPreferenceEnabled(preference, packageName, serviceAllowed, - serviceEnabled); final String prefKey = preference.getKey(); final int imageRes = info.getAnimatedImageRes(); final CharSequence description = getServiceDescription(mContext, info, @@ -614,16 +620,11 @@ public class AccessibilitySettings extends DashboardFragment { } final RestrictedPreference preference = createRestrictedPreference(key, title, - summary, icon, fragment); - - final String packageName = componentName.getPackageName(); - // permittedServices null means all accessibility services are allowed. - final boolean serviceAllowed = - permittedServices == null || permittedServices.contains(packageName); + summary, icon, fragment, componentName.getPackageName(), + activityInfo.applicationInfo.uid); final boolean serviceEnabled = enabledServices.contains(componentName); - setRestrictedPreferenceEnabled(preference, packageName, serviceAllowed, - serviceEnabled); + setRestrictedPreferenceEnabled(preference, permittedServices, serviceEnabled); final String prefKey = preference.getKey(); final String description = info.loadDescription(mPm); @@ -633,7 +634,7 @@ public class AccessibilitySettings extends DashboardFragment { putBasicExtras(preference, prefKey, title, description, imageRes, htmlDescription, componentName); - putSettingsExtras(preference, packageName, settingsClassName); + putSettingsExtras(preference, componentName.getPackageName(), settingsClassName); preferenceList.add(preference); } @@ -659,8 +660,9 @@ public class AccessibilitySettings extends DashboardFragment { } private RestrictedPreference createRestrictedPreference(String key, CharSequence title, - CharSequence summary, Drawable icon, String fragment) { - final RestrictedPreference preference = new RestrictedPreference(mContext); + CharSequence summary, Drawable icon, String fragment, String packageName, int uid) { + final RestrictedPreference preference = new RestrictedPreference(mContext, packageName, + uid); preference.setKey(key); preference.setTitle(title); @@ -675,16 +677,37 @@ public class AccessibilitySettings extends DashboardFragment { } private void setRestrictedPreferenceEnabled(RestrictedPreference preference, - String packageName, boolean serviceAllowed, boolean serviceEnabled) { + final List permittedServices, boolean serviceEnabled) { + // permittedServices null means all accessibility services are allowed. + boolean serviceAllowed = permittedServices == null || permittedServices.contains( + preference.getPackageName()); + boolean appOpsAllowed; + if (serviceAllowed) { + try { + final int mode = mAppOps.noteOpNoThrow( + AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS, + preference.getUid(), preference.getPackageName()); + appOpsAllowed = mode == AppOpsManager.MODE_ALLOWED; + serviceAllowed = appOpsAllowed; + } catch (Exception e) { + // Allow service in case if app ops is not available in testing. + appOpsAllowed = true; + } + } else { + appOpsAllowed = false; + } if (serviceAllowed || serviceEnabled) { preference.setEnabled(true); } else { // Disable accessibility service that are not permitted. final EnforcedAdmin admin = RestrictedLockUtilsInternal.checkIfAccessibilityServiceDisallowed( - mContext, packageName, UserHandle.myUserId()); + mContext, preference.getPackageName(), UserHandle.myUserId()); + if (admin != null) { preference.setDisabledByAdmin(admin); + } else if (!appOpsAllowed) { + preference.setDisabledByAppOps(true); } else { preference.setEnabled(false); } diff --git a/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java index 243dc564be8..aa2023ce86e 100755 --- a/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java +++ b/src/com/android/settings/applications/appinfo/AppInfoDashboardFragment.java @@ -19,6 +19,8 @@ package com.android.settings.applications.appinfo; import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import android.app.Activity; +import android.app.AppOpsManager; +import android.app.KeyguardManager; import android.app.admin.DevicePolicyManager; import android.app.settings.SettingsEnums; import android.content.BroadcastReceiver; @@ -30,8 +32,13 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.UserInfo; +import android.hardware.biometrics.BiometricManager; +import android.hardware.biometrics.BiometricPrompt; import android.net.Uri; import android.os.Bundle; +import android.os.CancellationSignal; +import android.os.Handler; +import android.os.Looper; import android.os.UserHandle; import android.os.UserManager; import android.text.TextUtils; @@ -82,6 +89,7 @@ public class AppInfoDashboardFragment extends DashboardFragment @VisibleForTesting static final int UNINSTALL_UPDATES = 2; static final int INSTALL_INSTANT_APP_MENU = 3; + static final int ACCESS_RESTRICTED_SETTINGS = 4; // Result code identifiers @VisibleForTesting @@ -261,6 +269,7 @@ public class AppInfoDashboardFragment extends DashboardFragment if (!refreshUi()) { setIntentAndFinish(true, true); } + getActivity().invalidateOptionsMenu(); } @Override @@ -381,6 +390,9 @@ public class AppInfoDashboardFragment extends DashboardFragment .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); menu.add(0, UNINSTALL_ALL_USERS_MENU, 1, R.string.uninstall_all_users_text) .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); + menu.add(0, ACCESS_RESTRICTED_SETTINGS, 0, + R.string.app_restricted_settings_lockscreen_title) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); } @Override @@ -390,6 +402,7 @@ public class AppInfoDashboardFragment extends DashboardFragment } super.onPrepareOptionsMenu(menu); menu.findItem(UNINSTALL_ALL_USERS_MENU).setVisible(shouldShowUninstallForAll(mAppEntry)); + menu.findItem(ACCESS_RESTRICTED_SETTINGS).setVisible(shouldShowAccessRestrictedSettings()); mUpdatedSysApp = (mAppEntry.info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0; final MenuItem uninstallUpdatesItem = menu.findItem(UNINSTALL_UPDATES); final boolean uninstallUpdateDisabled = getContext().getResources().getBoolean( @@ -404,6 +417,46 @@ public class AppInfoDashboardFragment extends DashboardFragment } } + private static void showLockScreen(Context context, Runnable successRunnable) { + final KeyguardManager keyguardManager = context.getSystemService( + KeyguardManager.class); + + if (keyguardManager.isKeyguardSecure()) { + final BiometricPrompt.AuthenticationCallback authenticationCallback = + new BiometricPrompt.AuthenticationCallback() { + @Override + public void onAuthenticationSucceeded( + BiometricPrompt.AuthenticationResult result) { + successRunnable.run(); + } + + @Override + public void onAuthenticationError(int errorCode, CharSequence errString) { + //Do nothing + } + }; + + final BiometricPrompt.Builder builder = new BiometricPrompt.Builder(context) + .setTitle(context.getText(R.string.app_restricted_settings_lockscreen_title)); + + if (context.getSystemService(BiometricManager.class).canAuthenticate( + BiometricManager.Authenticators.DEVICE_CREDENTIAL + | BiometricManager.Authenticators.BIOMETRIC_WEAK) + == BiometricManager.BIOMETRIC_SUCCESS) { + builder.setAllowedAuthenticators(BiometricManager.Authenticators.DEVICE_CREDENTIAL + | BiometricManager.Authenticators.BIOMETRIC_WEAK); + } + + final BiometricPrompt bp = builder.build(); + final Handler handler = new Handler(Looper.getMainLooper()); + bp.authenticate(new CancellationSignal(), + runnable -> handler.post(runnable), + authenticationCallback); + } else { + successRunnable.run(); + } + } + @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { @@ -413,6 +466,17 @@ public class AppInfoDashboardFragment extends DashboardFragment case UNINSTALL_UPDATES: uninstallPkg(mAppEntry.info.packageName, false, false); return true; + case ACCESS_RESTRICTED_SETTINGS: + showLockScreen(getContext(), () -> { + final AppOpsManager appOpsManager = getContext().getSystemService( + AppOpsManager.class); + appOpsManager.setMode(AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS, + getUid(), + getPackageName(), + AppOpsManager.MODE_ALLOWED); + getActivity().invalidateOptionsMenu(); + }); + return true; } return super.onOptionsItemSelected(item); } @@ -436,6 +500,18 @@ public class AppInfoDashboardFragment extends DashboardFragment } } + private boolean shouldShowAccessRestrictedSettings() { + try { + final int mode = getSystemService(AppOpsManager.class).noteOpNoThrow( + AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS, getUid(), + getPackageName()); + return mode == AppOpsManager.MODE_IGNORED; + } catch (Exception e) { + // Fallback in case if app ops is not available in testing. + return false; + } + } + @VisibleForTesting boolean shouldShowUninstallForAll(AppEntry appEntry) { boolean showIt = true; diff --git a/src/com/android/settings/enterprise/ActionDisabledByAdminDialogHelper.java b/src/com/android/settings/enterprise/ActionDisabledByAdminDialogHelper.java index f898ab21408..fe02fdaae80 100644 --- a/src/com/android/settings/enterprise/ActionDisabledByAdminDialogHelper.java +++ b/src/com/android/settings/enterprise/ActionDisabledByAdminDialogHelper.java @@ -62,7 +62,7 @@ public final class ActionDisabledByAdminDialogHelper { public ActionDisabledByAdminDialogHelper(Activity activity, String restriction) { mActivity = activity; mDialogView = (ViewGroup) LayoutInflater.from(mActivity).inflate( - R.layout.admin_support_details_dialog, null); + R.layout.support_details_dialog, null); mActionDisabledByAdminController = ActionDisabledByAdminControllerFactory .createInstance(mActivity, restriction, new DeviceAdminStringProviderImpl(mActivity), diff --git a/tests/robotests/src/com/android/settings/accessibility/AccessibilityDetailsSettingsFragmentTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilityDetailsSettingsFragmentTest.java index f5021730313..b0efbcb1cda 100644 --- a/tests/robotests/src/com/android/settings/accessibility/AccessibilityDetailsSettingsFragmentTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilityDetailsSettingsFragmentTest.java @@ -22,6 +22,7 @@ import static com.android.internal.accessibility.AccessibilityShortcutController import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -134,7 +135,7 @@ public class AccessibilityDetailsSettingsFragmentTest { final Intent intent = new Intent(); intent.putExtra(Intent.EXTRA_COMPONENT_NAME, COMPONENT_NAME); doReturn(intent).when(mActivity).getIntent(); - doReturn(false).when(mFragment).isServiceAllowed(any()); + doReturn(false).when(mFragment).isServiceAllowed(anyInt(), any()); mFragment.onCreate(Bundle.EMPTY); diff --git a/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java index 5843265656e..f7c546fa015 100644 --- a/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java @@ -368,6 +368,7 @@ public class AccessibilitySettingsTest { private void setMockAccessibilityShortcutInfo(AccessibilityShortcutInfo mockInfo) { final ActivityInfo activityInfo = Mockito.mock(ActivityInfo.class); + activityInfo.applicationInfo = new ApplicationInfo(); when(mockInfo.getActivityInfo()).thenReturn(activityInfo); when(activityInfo.loadLabel(any())).thenReturn(DEFAULT_LABEL); when(mockInfo.loadSummary(any())).thenReturn(DEFAULT_SUMMARY);