From b67d873372ac7f9b60b883cfda8f7ace2bca2b56 Mon Sep 17 00:00:00 2001 From: jasonwshsu Date: Thu, 13 Feb 2020 15:58:30 +0800 Subject: [PATCH] Accessibility shortcut primary action - setup entry for launched type fragment. * Reduce the complexity of updateServicePreferences. * Introduces RestrictedPreferenceHelper to setup installed AccessibilityServiceInfo and AccessibilityShortcutInfo. Bug: 142531433 Test: make RunSettingsRoboTests Change-Id: I0f5eeac56a93caadeb05c25f1f8c4daa0d775b35 --- .../accessibility/AccessibilitySettings.java | 469 +++++++++++++----- ...cessibilityActivityPreferenceFragment.java | 27 + .../AccessibilitySettingsTest.java | 217 +++++++- 3 files changed, 574 insertions(+), 139 deletions(-) create mode 100644 src/com/android/settings/accessibility/LaunchAccessibilityActivityPreferenceFragment.java diff --git a/src/com/android/settings/accessibility/AccessibilitySettings.java b/src/com/android/settings/accessibility/AccessibilitySettings.java index 94e73fafef5..c2b29a6d407 100644 --- a/src/com/android/settings/accessibility/AccessibilitySettings.java +++ b/src/com/android/settings/accessibility/AccessibilitySettings.java @@ -19,10 +19,13 @@ package com.android.settings.accessibility; import static com.android.settingslib.TwoTargetPreference.ICON_SIZE_MEDIUM; import android.accessibilityservice.AccessibilityServiceInfo; +import android.accessibilityservice.AccessibilityShortcutInfo; import android.app.admin.DevicePolicyManager; import android.app.settings.SettingsEnums; import android.content.ComponentName; import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.graphics.drawable.Drawable; @@ -40,7 +43,6 @@ import androidx.annotation.VisibleForTesting; import androidx.core.content.ContextCompat; import androidx.preference.Preference; import androidx.preference.PreferenceCategory; -import androidx.preference.PreferenceScreen; import androidx.preference.SwitchPreference; import com.android.internal.accessibility.AccessibilityShortcutController; @@ -174,8 +176,6 @@ public class AccessibilitySettings extends DashboardFragment { private Preference mDisplayDaltonizerPreferenceScreen; private Preference mToggleInversionPreference; - private DevicePolicyManager mDpm; - /** * Check if the color transforms are color accelerated. Some transforms are experimental only * on non-accelerated platforms due to the performance implications. @@ -217,8 +217,6 @@ public class AccessibilitySettings extends DashboardFragment { public void onCreate(Bundle icicle) { super.onCreate(icicle); initializeAllPreferences(); - mDpm = (DevicePolicyManager) (getActivity() - .getSystemService(Context.DEVICE_POLICY_SERVICE)); } @Override @@ -255,27 +253,60 @@ public class AccessibilitySettings extends DashboardFragment { return TAG; } - public static CharSequence getServiceSummary(Context context, AccessibilityServiceInfo info, + /** + * Returns the summary for the current state of this accessibilityService. + * + * @param context A valid context + * @param info The accessibilityService's info + * @param serviceEnabled Whether the accessibility service is enabled. + * @return The service summary + */ + @VisibleForTesting + static CharSequence getServiceSummary(Context context, AccessibilityServiceInfo info, boolean serviceEnabled) { - final CharSequence serviceSummary = info.loadSummary(context.getPackageManager()); - final int fragmentType = AccessibilityUtil.getAccessibilityServiceFragmentType(info); + if (serviceEnabled && info.crashed) { + return context.getText(R.string.accessibility_summary_state_stopped); + } + final CharSequence serviceSummary = info.loadSummary(context.getPackageManager()); + + final int fragmentType = AccessibilityUtil.getAccessibilityServiceFragmentType(info); if (fragmentType == AccessibilityServiceFragmentType.INVISIBLE) { return TextUtils.isEmpty(serviceSummary) ? EMPTY_STRING : serviceSummary; } - final String serviceState = serviceEnabled - ? context.getString(R.string.accessibility_summary_state_enabled) - : context.getString(R.string.accessibility_summary_state_disabled); + final CharSequence serviceState = serviceEnabled + ? context.getText(R.string.accessibility_summary_state_enabled) + : context.getText(R.string.accessibility_summary_state_disabled); final String stateSummaryCombo = context.getString( R.string.preference_summary_default_combination, serviceState, serviceSummary); - return (TextUtils.isEmpty(serviceSummary)) ? serviceState : stateSummaryCombo; - + return TextUtils.isEmpty(serviceSummary) ? serviceState : stateSummaryCombo; } + /** + * Returns the description for the current state of this accessibilityService. + * + * @param context A valid context + * @param info The accessibilityService's info + * @param serviceEnabled Whether the accessibility service is enabled. + * @return The service description + */ @VisibleForTesting + static CharSequence getServiceDescription(Context context, AccessibilityServiceInfo info, + boolean serviceEnabled) { + if (serviceEnabled && info.crashed) { + return context.getText(R.string.accessibility_description_state_stopped); + } + + final String description = info.loadDescription(context.getPackageManager()); + + return TextUtils.isEmpty(description) + ? context.getText(R.string.accessibility_service_default_description) + : description; + } + static boolean isRampingRingerEnabled(final Context context) { return Settings.Global.getInt( context.getContentResolver(), Settings.Global.APPLY_RAMPING_RINGER, 0) == 1; @@ -283,7 +314,7 @@ public class AccessibilitySettings extends DashboardFragment { private void initializeAllPreferences() { for (int i = 0; i < CATEGORIES.length; i++) { - PreferenceCategory prefCategory = (PreferenceCategory) findPreference(CATEGORIES[i]); + PreferenceCategory prefCategory = findPreference(CATEGORIES[i]); mCategoryToPrefCategoryMap.put(CATEGORIES[i], prefCategory); } @@ -293,8 +324,7 @@ public class AccessibilitySettings extends DashboardFragment { // Large pointer icon. mToggleLargePointerIconPreference = findPreference(TOGGLE_LARGE_POINTER_ICON); - mToggleDisableAnimationsPreference = - (SwitchPreference) findPreference(TOGGLE_DISABLE_ANIMATIONS); + mToggleDisableAnimationsPreference = findPreference(TOGGLE_DISABLE_ANIMATIONS); // Display magnification. mDisplayMagnificationPreferenceScreen = findPreference( @@ -313,9 +343,7 @@ public class AccessibilitySettings extends DashboardFragment { // Since services category is auto generated we have to do a pass // to generate it since services can come and go and then based on // the global accessibility state to decided whether it is enabled. - - // Generate. - ArrayList servicePreferences = + final ArrayList servicePreferences = new ArrayList<>(mServicePreferenceToPreferenceCategoryMap.keySet()); for (int i = 0; i < servicePreferences.size(); i++) { Preference service = servicePreferences.get(i); @@ -332,125 +360,21 @@ public class AccessibilitySettings extends DashboardFragment { initializePreBundledServicesMapFromArray(CATEGORY_INTERACTION_CONTROL, R.array.config_preinstalled_interaction_control_services); - AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(getActivity()); + final List preferenceList = getInstalledAccessibilityList( + getPrefContext()); - List installedServices = - accessibilityManager.getInstalledAccessibilityServiceList(); - Set enabledServices = AccessibilityUtils.getEnabledServicesFromSettings( - getActivity()); - List permittedServices = mDpm.getPermittedAccessibilityServices( - UserHandle.myUserId()); - - PreferenceCategory downloadedServicesCategory = + final PreferenceCategory downloadedServicesCategory = mCategoryToPrefCategoryMap.get(CATEGORY_DOWNLOADED_SERVICES); - // Temporarily add the downloaded services category back if it was previously removed. - if (findPreference(CATEGORY_DOWNLOADED_SERVICES) == null) { - getPreferenceScreen().addPreference(downloadedServicesCategory); - } - - for (int i = 0, count = installedServices.size(); i < count; ++i) { - final AccessibilityServiceInfo info = installedServices.get(i); - final ResolveInfo resolveInfo = info.getResolveInfo(); - - final RestrictedPreference preference = - new RestrictedPreference(downloadedServicesCategory.getContext()); - final String title = resolveInfo.loadLabel(getPackageManager()).toString(); - - Drawable icon; - if (resolveInfo.getIconResource() == 0) { - icon = ContextCompat.getDrawable(getContext(), R.drawable.ic_accessibility_generic); - } else { - icon = resolveInfo.loadIcon(getPackageManager()); - } - - final ServiceInfo serviceInfo = resolveInfo.serviceInfo; - final String packageName = serviceInfo.packageName; - final ComponentName componentName = new ComponentName(packageName, serviceInfo.name); - - preference.setKey(componentName.flattenToString()); - - preference.setTitle(title); - preference.setIconSize(ICON_SIZE_MEDIUM); - Utils.setSafeIcon(preference, icon); - final boolean serviceEnabled = enabledServices.contains(componentName); - String description = info.loadDescription(getPackageManager()); - if (TextUtils.isEmpty(description)) { - description = getString(R.string.accessibility_service_default_description); - } - - if (serviceEnabled && info.crashed) { - // Update the summaries for services that have crashed. - preference.setSummary(R.string.accessibility_summary_state_stopped); - description = getString(R.string.accessibility_description_state_stopped); - } else { - final CharSequence serviceSummary = getServiceSummary(getContext(), info, - serviceEnabled); - preference.setSummary(serviceSummary); - } - - // Disable all accessibility services that are not permitted. - final boolean serviceAllowed = - permittedServices == null || permittedServices.contains(packageName); - if (!serviceAllowed && !serviceEnabled) { - final EnforcedAdmin admin = - RestrictedLockUtilsInternal.checkIfAccessibilityServiceDisallowed( - getActivity(), packageName, UserHandle.myUserId()); - if (admin != null) { - preference.setDisabledByAdmin(admin); - } else { - preference.setEnabled(false); - } - } else { - preference.setEnabled(true); - } - - final int fragmentType = AccessibilityUtil.getAccessibilityServiceFragmentType(info); - switch (fragmentType) { - case AccessibilityServiceFragmentType.LEGACY: - preference.setFragment( - LegacyAccessibilityServicePreferenceFragment.class.getName()); - break; - case AccessibilityServiceFragmentType.INVISIBLE: - preference.setFragment( - InvisibleToggleAccessibilityServicePreferenceFragment.class.getName()); - break; - case AccessibilityServiceFragmentType.INTUITIVE: - preference.setFragment( - ToggleAccessibilityServicePreferenceFragment.class.getName()); - break; - default: - // impossible status - throw new AssertionError(); - } - - preference.setPersistent(true); - - final Bundle extras = preference.getExtras(); - extras.putString(EXTRA_PREFERENCE_KEY, preference.getKey()); - extras.putBoolean(EXTRA_CHECKED, serviceEnabled); - extras.putString(EXTRA_TITLE, title); - extras.putParcelable(EXTRA_RESOLVE_INFO, resolveInfo); - extras.putString(EXTRA_SUMMARY, description); - extras.putInt(EXTRA_ANIMATED_IMAGE_RES, info.getAnimatedImageRes()); - - final String htmlDescription = info.loadHtmlDescription(getPackageManager()); - extras.putString(AccessibilitySettings.EXTRA_HTML_DESCRIPTION, htmlDescription); - - final String settingsClassName = info.getSettingsActivityName(); - if (!TextUtils.isEmpty(settingsClassName)) { - extras.putString(EXTRA_SETTINGS_TITLE, - getString(R.string.accessibility_menu_item_settings)); - extras.putString(EXTRA_SETTINGS_COMPONENT_NAME, - new ComponentName(packageName, settingsClassName).flattenToString()); - } - extras.putParcelable(EXTRA_COMPONENT_NAME, componentName); + for (int i = 0, count = preferenceList.size(); i < count; ++i) { + final RestrictedPreference preference = preferenceList.get(i); + final ComponentName componentName = preference.getExtras().getParcelable( + EXTRA_COMPONENT_NAME); PreferenceCategory prefCategory = downloadedServicesCategory; // Set the appropriate category if the service comes pre-installed. if (mPreBundledServiceComponentToCategoryMap.containsKey(componentName)) { prefCategory = mPreBundledServiceComponentToCategoryMap.get(componentName); } - preference.setOrder(FIRST_PREFERENCE_IN_CATEGORY_INDEX); prefCategory.addPreference(preference); mServicePreferenceToPreferenceCategoryMap.put(preference, prefCategory); } @@ -465,13 +389,60 @@ public class AccessibilitySettings extends DashboardFragment { updateCategoryOrderFromArray(CATEGORY_DISPLAY, R.array.config_order_display_services); - // If the user has not installed any additional services, hide the category. + // Need to check each time when updateServicePreferences() called. if (downloadedServicesCategory.getPreferenceCount() == 0) { - final PreferenceScreen screen = getPreferenceScreen(); - screen.removePreference(downloadedServicesCategory); + getPreferenceScreen().removePreference(downloadedServicesCategory); + } else { + getPreferenceScreen().addPreference(downloadedServicesCategory); } } + private List getInstalledAccessibilityList(Context context) { + final AccessibilityManager a11yManager = AccessibilityManager.getInstance(context); + final RestrictedPreferenceHelper preferenceHelper = new RestrictedPreferenceHelper(context); + + final List installedShortcutList = + a11yManager.getInstalledAccessibilityShortcutListAsUser(context, + UserHandle.myUserId()); + + // Remove duplicate item here, new a ArrayList to copy unmodifiable list result + // (getInstalledAccessibilityServiceList). + final List installedServiceList = new ArrayList<>( + a11yManager.getInstalledAccessibilityServiceList()); + installedServiceList.removeIf( + target -> containsTargetNameInList(installedShortcutList, target)); + + final List activityList = + preferenceHelper.createAccessibilityActivityPreferenceList(installedShortcutList); + + final List serviceList = + preferenceHelper.createAccessibilityServicePreferenceList(installedServiceList); + + final List preferenceList = new ArrayList<>(); + preferenceList.addAll(activityList); + preferenceList.addAll(serviceList); + + return preferenceList; + } + + private boolean containsTargetNameInList(List shortcutInfos, + AccessibilityServiceInfo targetServiceInfo) { + final ServiceInfo serviceInfo = targetServiceInfo.getResolveInfo().serviceInfo; + final String servicePackageName = serviceInfo.packageName; + final CharSequence serviceLabel = serviceInfo.loadLabel(getPackageManager()); + + for (int i = 0, count = shortcutInfos.size(); i < count; ++i) { + final ActivityInfo activityInfo = shortcutInfos.get(i).getActivityInfo(); + final String activityPackageName = activityInfo.packageName; + final CharSequence activityLabel = activityInfo.loadLabel(getPackageManager()); + if (servicePackageName.equals(activityPackageName) + && serviceLabel.equals(activityLabel)) { + return true; + } + } + return false; + } + private void initializePreBundledServicesMapFromArray(String categoryKey, int key) { String[] services = getResources().getStringArray(key); PreferenceCategory category = mCategoryToPrefCategoryMap.get(categoryKey); @@ -533,4 +504,236 @@ public class AccessibilitySettings extends DashboardFragment { public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = new BaseSearchIndexProvider(R.xml.accessibility_settings); + + /** + * This class helps setup RestrictedPreference. + */ + @VisibleForTesting + static class RestrictedPreferenceHelper { + private final Context mContext; + private final DevicePolicyManager mDpm; + private final PackageManager mPm; + + RestrictedPreferenceHelper(Context context) { + mContext = context; + mDpm = context.getSystemService(DevicePolicyManager.class); + mPm = context.getPackageManager(); + } + + /** + * Creates the list of {@link RestrictedPreference} with the installedServices arguments. + * + * @param installedServices The list of {@link AccessibilityServiceInfo}s of the + * installed accessibility services + * @return The list of {@link RestrictedPreference} + */ + List createAccessibilityServicePreferenceList( + List installedServices) { + + final Set enabledServices = + AccessibilityUtils.getEnabledServicesFromSettings(mContext); + final List permittedServices = mDpm.getPermittedAccessibilityServices( + UserHandle.myUserId()); + final int installedServicesSize = installedServices.size(); + + final List preferenceList = new ArrayList<>( + installedServicesSize); + + for (int i = 0; i < installedServicesSize; ++i) { + final AccessibilityServiceInfo info = installedServices.get(i); + final ResolveInfo resolveInfo = info.getResolveInfo(); + final String packageName = resolveInfo.serviceInfo.packageName; + final ComponentName componentName = new ComponentName(packageName, + resolveInfo.serviceInfo.name); + + final String key = componentName.flattenToString(); + final CharSequence title = resolveInfo.loadLabel(mPm); + final boolean serviceEnabled = enabledServices.contains(componentName); + final CharSequence summary = getServiceSummary(mContext, info, serviceEnabled); + final String fragment = getAccessibilityServiceFragmentTypeName(info); + + Drawable icon = resolveInfo.loadIcon(mPm); + if (resolveInfo.getIconResource() == 0) { + icon = ContextCompat.getDrawable(mContext, + R.drawable.ic_accessibility_generic); + } + + final RestrictedPreference preference = createRestrictedPreference(key, title, + summary, icon, fragment); + + // permittedServices null means all accessibility services are allowed. + final boolean serviceAllowed = + permittedServices == null || permittedServices.contains(packageName); + + setRestrictedPreferenceEnabled(preference, packageName, serviceAllowed, + serviceEnabled); + + final String prefKey = preference.getKey(); + final int imageRes = info.getAnimatedImageRes(); + final CharSequence description = getServiceDescription(mContext, info, + serviceEnabled); + final String htmlDescription = info.loadHtmlDescription(mPm); + final String settingsClassName = info.getSettingsActivityName(); + + putBasicExtras(preference, prefKey, title, description, imageRes, htmlDescription, + componentName); + putServiceExtras(preference, resolveInfo, serviceEnabled); + putSettingsExtras(preference, packageName, settingsClassName); + + preferenceList.add(preference); + } + return preferenceList; + } + + /** + * Create the list of {@link RestrictedPreference} with the installedShortcuts arguments. + * + * @param installedShortcuts The list of {@link AccessibilityShortcutInfo}s of the + * installed accessibility shortcuts + * @return The list of {@link RestrictedPreference} + */ + List createAccessibilityActivityPreferenceList( + List installedShortcuts) { + final Set enabledServices = + AccessibilityUtils.getEnabledServicesFromSettings(mContext); + final List permittedServices = mDpm.getPermittedAccessibilityServices( + UserHandle.myUserId()); + + final int installedShortcutsSize = installedShortcuts.size(); + final List preferenceList = new ArrayList<>( + installedShortcutsSize); + + for (int i = 0; i < installedShortcutsSize; ++i) { + final AccessibilityShortcutInfo info = installedShortcuts.get(i); + final ActivityInfo activityInfo = info.getActivityInfo(); + final ComponentName componentName = info.getComponentName(); + + final String key = componentName.flattenToString(); + final CharSequence title = activityInfo.loadLabel(mPm); + final String summary = info.loadSummary(mPm); + final String fragment = + LaunchAccessibilityActivityPreferenceFragment.class.getName(); + + Drawable icon = activityInfo.loadIcon(mPm); + if (activityInfo.getIconResource() == 0) { + icon = ContextCompat.getDrawable(mContext, R.drawable.ic_accessibility_generic); + } + + 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); + final boolean serviceEnabled = enabledServices.contains(componentName); + + setRestrictedPreferenceEnabled(preference, packageName, serviceAllowed, + serviceEnabled); + + final String prefKey = preference.getKey(); + final String description = info.loadDescription(mPm); + final int imageRes = info.getAnimatedImageRes(); + final String htmlDescription = info.loadHtmlDescription(mPm); + + putBasicExtras(preference, prefKey, title, description, imageRes, htmlDescription, + componentName); + + preferenceList.add(preference); + } + return preferenceList; + } + + private String getAccessibilityServiceFragmentTypeName(AccessibilityServiceInfo info) { + switch (AccessibilityUtil.getAccessibilityServiceFragmentType( + info)) { + case AccessibilityServiceFragmentType.LEGACY: + return LegacyAccessibilityServicePreferenceFragment.class.getName(); + case AccessibilityServiceFragmentType.INVISIBLE: + return InvisibleToggleAccessibilityServicePreferenceFragment.class.getName(); + case AccessibilityServiceFragmentType.INTUITIVE: + return ToggleAccessibilityServicePreferenceFragment.class.getName(); + default: + // impossible status + throw new AssertionError(); + } + } + + private RestrictedPreference createRestrictedPreference(String key, CharSequence title, + CharSequence summary, Drawable icon, String fragment) { + final RestrictedPreference preference = new RestrictedPreference(mContext); + + preference.setKey(key); + preference.setTitle(title); + preference.setSummary(summary); + Utils.setSafeIcon(preference, icon); + preference.setFragment(fragment); + preference.setIconSize(ICON_SIZE_MEDIUM); + preference.setPersistent(false); // Disable SharedPreferences. + preference.setOrder(FIRST_PREFERENCE_IN_CATEGORY_INDEX); + + return preference; + } + + private void setRestrictedPreferenceEnabled(RestrictedPreference preference, + String packageName, boolean serviceAllowed, boolean serviceEnabled) { + if (serviceAllowed || serviceEnabled) { + preference.setEnabled(true); + } else { + // Disable accessibility service that are not permitted. + final EnforcedAdmin admin = + RestrictedLockUtilsInternal.checkIfAccessibilityServiceDisallowed( + mContext, packageName, UserHandle.myUserId()); + if (admin != null) { + preference.setDisabledByAdmin(admin); + } else { + preference.setEnabled(false); + } + } + } + + /** Puts the basic extras into {@link RestrictedPreference}'s getExtras(). */ + private void putBasicExtras(RestrictedPreference preference, String prefKey, + CharSequence title, CharSequence summary, int imageRes, String htmlDescription, + ComponentName componentName) { + final Bundle extras = preference.getExtras(); + + extras.putString(EXTRA_PREFERENCE_KEY, prefKey); + extras.putString(EXTRA_TITLE, title.toString()); + extras.putString(EXTRA_SUMMARY, summary.toString()); + extras.putParcelable(EXTRA_COMPONENT_NAME, componentName); + extras.putInt(EXTRA_ANIMATED_IMAGE_RES, imageRes); + extras.putString(AccessibilitySettings.EXTRA_HTML_DESCRIPTION, htmlDescription); + } + + /** + * Puts the service extras into {@link RestrictedPreference}'s getExtras(). + * + * Called by {@link AccessibilityServiceInfo} for now. + */ + private void putServiceExtras(RestrictedPreference preference, ResolveInfo resolveInfo, + Boolean serviceEnabled) { + final Bundle extras = preference.getExtras(); + + extras.putParcelable(EXTRA_RESOLVE_INFO, resolveInfo); + extras.putBoolean(EXTRA_CHECKED, serviceEnabled); + } + + /** + * Puts the settings extras into {@link RestrictedPreference}'s getExtras(). + * + * Called when settings UI is needed. + */ + private void putSettingsExtras(RestrictedPreference preference, String packageName, + String settingsClassName) { + final Bundle extras = preference.getExtras(); + + if (!TextUtils.isEmpty(settingsClassName)) { + extras.putString(EXTRA_SETTINGS_TITLE, + mContext.getText(R.string.accessibility_menu_item_settings).toString()); + extras.putString(EXTRA_SETTINGS_COMPONENT_NAME, + new ComponentName(packageName, settingsClassName).flattenToString()); + } + } + } } diff --git a/src/com/android/settings/accessibility/LaunchAccessibilityActivityPreferenceFragment.java b/src/com/android/settings/accessibility/LaunchAccessibilityActivityPreferenceFragment.java new file mode 100644 index 00000000000..f443d5fb6cb --- /dev/null +++ b/src/com/android/settings/accessibility/LaunchAccessibilityActivityPreferenceFragment.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2020 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.accessibility; + +/** Fragment for providing open activity button. */ +public class LaunchAccessibilityActivityPreferenceFragment extends + ToggleFeaturePreferenceFragment { + + @Override + protected void onPreferenceToggled(String preferenceKey, boolean enabled) { + // TODO(b/142531433): Added in next CL. + } +} diff --git a/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java index 0adc6d547a2..3528fb6fc3d 100644 --- a/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java @@ -18,49 +18,82 @@ package com.android.settings.accessibility; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; +import android.accessibilityservice.AccessibilityServiceInfo; +import android.accessibilityservice.AccessibilityShortcutInfo; +import android.content.ComponentName; import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.os.Build; import android.provider.Settings; import android.view.accessibility.AccessibilityManager; import com.android.settings.R; import com.android.settings.testutils.XmlTestUtils; import com.android.settings.testutils.shadow.ShadowDeviceConfig; +import com.android.settingslib.RestrictedPreference; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import org.robolectric.shadow.api.Shadow; import org.robolectric.shadows.ShadowAccessibilityManager; +import org.xmlpull.v1.XmlPullParserException; +import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; import java.util.List; @RunWith(RobolectricTestRunner.class) public class AccessibilitySettingsTest { + private static final String DUMMY_PACKAGE_NAME = "com.dummy.example"; + private static final String DUMMY_CLASS_NAME = DUMMY_PACKAGE_NAME + ".dummy_a11y_service"; + private static final ComponentName DUMMY_COMPONENT_NAME = new ComponentName(DUMMY_PACKAGE_NAME, + DUMMY_CLASS_NAME); + private static final int ON = 1; + private static final int OFF = 0; + private static final String EMPTY_STRING = ""; + private static final String DEFAULT_SUMMARY = "default summary"; + private static final String DEFAULT_DESCRIPTION = "default description"; + private static final String DEFAULT_LABEL = "default label"; + private static final Boolean SERVICE_ENABLED = true; + private static final Boolean SERVICE_DISABLED = false; private Context mContext; private AccessibilitySettings mSettings; private ShadowAccessibilityManager mShadowAccessibilityManager; + private AccessibilityServiceInfo mServiceInfo; + @Mock + private AccessibilityShortcutInfo mShortcutInfo; @Before public void setup() { MockitoAnnotations.initMocks(this); - mContext = RuntimeEnvironment.application; + + mContext = spy(RuntimeEnvironment.application); mSettings = spy(new AccessibilitySettings()); + mServiceInfo = spy(getMockAccessibilityServiceInfo()); mShadowAccessibilityManager = Shadow.extract(AccessibilityManager.getInstance(mContext)); mShadowAccessibilityManager.setInstalledAccessibilityServiceList(new ArrayList<>()); doReturn(mContext).when(mSettings).getContext(); } @Test - public void testNonIndexableKeys_existInXmlLayout() { + public void getNonIndexableKeys_existInXmlLayout() { final List niks = AccessibilitySettings.SEARCH_INDEX_DATA_PROVIDER .getNonIndexableKeys(mContext); final List keys = @@ -71,17 +104,189 @@ public class AccessibilitySettingsTest { @Test @Config(shadows = {ShadowDeviceConfig.class}) - public void testIsRampingRingerEnabled_settingsFlagOn_Enabled() { + public void isRampingRingerEnabled_settingsFlagOn_Enabled() { Settings.Global.putInt( - mContext.getContentResolver(), Settings.Global.APPLY_RAMPING_RINGER, 1 /* ON */); + mContext.getContentResolver(), Settings.Global.APPLY_RAMPING_RINGER, ON); assertThat(AccessibilitySettings.isRampingRingerEnabled(mContext)).isTrue(); } @Test @Config(shadows = {ShadowDeviceConfig.class}) - public void testIsRampingRingerEnabled_settingsFlagOff_Disabled() { + public void isRampingRingerEnabled_settingsFlagOff_Disabled() { Settings.Global.putInt( - mContext.getContentResolver(), Settings.Global.APPLY_RAMPING_RINGER, 0 /* OFF */); + mContext.getContentResolver(), Settings.Global.APPLY_RAMPING_RINGER, OFF); assertThat(AccessibilitySettings.isRampingRingerEnabled(mContext)).isFalse(); } + + @Test + public void getServiceSummary_serviceCrash_showsStopped() { + mServiceInfo.crashed = true; + + final CharSequence summary = AccessibilitySettings.getServiceSummary(mContext, + mServiceInfo, SERVICE_ENABLED); + + assertThat(summary).isEqualTo( + mContext.getString(R.string.accessibility_summary_state_stopped)); + } + + @Test + public void getServiceSummary_invisibleType_showsDefaultSummary() { + setInvisibleFragmentType(mServiceInfo); + doReturn(DEFAULT_SUMMARY).when(mServiceInfo).loadSummary(any()); + + final CharSequence summary = AccessibilitySettings.getServiceSummary(mContext, + mServiceInfo, SERVICE_ENABLED); + + assertThat(summary).isEqualTo(DEFAULT_SUMMARY); + } + + @Test + public void getServiceSummary_enableService_showsEnabled() { + doReturn(EMPTY_STRING).when(mServiceInfo).loadSummary(any()); + + final CharSequence summary = AccessibilitySettings.getServiceSummary(mContext, + mServiceInfo, SERVICE_ENABLED); + + assertThat(summary).isEqualTo( + mContext.getString(R.string.accessibility_summary_state_enabled)); + } + + @Test + public void getServiceSummary_disableService_showsDisabled() { + doReturn(EMPTY_STRING).when(mServiceInfo).loadSummary(any()); + + final CharSequence summary = AccessibilitySettings.getServiceSummary(mContext, + mServiceInfo, SERVICE_DISABLED); + + assertThat(summary).isEqualTo( + mContext.getString(R.string.accessibility_summary_state_disabled)); + } + + @Test + public void getServiceSummary_enableServiceAndHasSummary_showsEnabledSummary() { + final String service_enabled = mContext.getString( + R.string.accessibility_summary_state_enabled); + doReturn(DEFAULT_SUMMARY).when(mServiceInfo).loadSummary(any()); + + final CharSequence summary = AccessibilitySettings.getServiceSummary(mContext, + mServiceInfo, SERVICE_ENABLED); + + assertThat(summary).isEqualTo( + mContext.getString(R.string.preference_summary_default_combination, service_enabled, + DEFAULT_SUMMARY)); + } + + @Test + public void getServiceSummary_disableServiceAndHasSummary_showsCombineDisabledSummary() { + final String service_disabled = mContext.getString( + R.string.accessibility_summary_state_disabled); + doReturn(DEFAULT_SUMMARY).when(mServiceInfo).loadSummary(any()); + + final CharSequence summary = AccessibilitySettings.getServiceSummary(mContext, + mServiceInfo, SERVICE_DISABLED); + + assertThat(summary).isEqualTo( + mContext.getString(R.string.preference_summary_default_combination, + service_disabled, DEFAULT_SUMMARY)); + } + + @Test + public void getServiceDescription_serviceCrash_showsStopped() { + mServiceInfo.crashed = true; + + final CharSequence description = AccessibilitySettings.getServiceDescription(mContext, + mServiceInfo, SERVICE_ENABLED); + + assertThat(description).isEqualTo( + mContext.getString(R.string.accessibility_description_state_stopped)); + } + + @Test + public void getServiceDescription_haveDescription_showsDescription() { + doReturn(DEFAULT_DESCRIPTION).when(mServiceInfo).loadDescription(any()); + + final CharSequence description = AccessibilitySettings.getServiceDescription(mContext, + mServiceInfo, SERVICE_ENABLED); + + assertThat(description).isEqualTo(DEFAULT_DESCRIPTION); + } + + @Test + public void getServiceDescription_noDescription_showsDefaultString() { + doReturn(EMPTY_STRING).when(mServiceInfo).loadDescription(any()); + + final CharSequence description = AccessibilitySettings.getServiceDescription(mContext, + mServiceInfo, SERVICE_ENABLED); + + assertThat(description).isEqualTo( + mContext.getString(R.string.accessibility_service_default_description)); + } + + @Test + public void createAccessibilityServicePreferenceList_hasOneInfo_containsSameKey() { + final String key = DUMMY_COMPONENT_NAME.flattenToString(); + final AccessibilitySettings.RestrictedPreferenceHelper helper = + new AccessibilitySettings.RestrictedPreferenceHelper(mContext); + final List infoList = new ArrayList<>( + Collections.singletonList(mServiceInfo)); + + final List preferenceList = + helper.createAccessibilityServicePreferenceList(infoList); + RestrictedPreference preference = preferenceList.get(0); + + assertThat(preference.getKey()).isEqualTo(key); + } + + @Test + public void createAccessibilityActivityPreferenceList_hasOneInfo_containsSameKey() { + final String key = DUMMY_COMPONENT_NAME.flattenToString(); + final AccessibilitySettings.RestrictedPreferenceHelper helper = + new AccessibilitySettings.RestrictedPreferenceHelper(mContext); + setMockAccessibilityShortcutInfo(mShortcutInfo); + final List infoList = new ArrayList<>( + Collections.singletonList(mShortcutInfo)); + + final List preferenceList = + helper.createAccessibilityActivityPreferenceList(infoList); + RestrictedPreference preference = preferenceList.get(0); + + assertThat(preference.getKey()).isEqualTo(key); + } + + private AccessibilityServiceInfo getMockAccessibilityServiceInfo() { + final ApplicationInfo applicationInfo = new ApplicationInfo(); + final ServiceInfo serviceInfo = new ServiceInfo(); + applicationInfo.packageName = DUMMY_PACKAGE_NAME; + serviceInfo.packageName = DUMMY_PACKAGE_NAME; + serviceInfo.name = DUMMY_CLASS_NAME; + serviceInfo.applicationInfo = applicationInfo; + + final ResolveInfo resolveInfo = new ResolveInfo(); + resolveInfo.serviceInfo = serviceInfo; + + try { + final AccessibilityServiceInfo info = new AccessibilityServiceInfo(resolveInfo, + mContext); + info.setComponentName(DUMMY_COMPONENT_NAME); + return info; + } catch (XmlPullParserException | IOException e) { + // Do nothing + } + + return null; + } + + private void setMockAccessibilityShortcutInfo(AccessibilityShortcutInfo mockInfo) { + final ActivityInfo activityInfo = Mockito.mock(ActivityInfo.class); + when(mockInfo.getActivityInfo()).thenReturn(activityInfo); + when(activityInfo.loadLabel(any())).thenReturn(DEFAULT_LABEL); + when(mockInfo.loadSummary(any())).thenReturn(DEFAULT_SUMMARY); + when(mockInfo.loadDescription(any())).thenReturn(DEFAULT_DESCRIPTION); + when(mockInfo.getComponentName()).thenReturn(DUMMY_COMPONENT_NAME); + } + + private void setInvisibleFragmentType(AccessibilityServiceInfo info) { + info.getResolveInfo().serviceInfo.applicationInfo.targetSdkVersion = Build.VERSION_CODES.R; + info.flags |= AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON; + } }