diff --git a/src/com/android/settings/accessibility/AccessibilitySettings.java b/src/com/android/settings/accessibility/AccessibilitySettings.java index 680ddb74dac..ef7c2ba02e1 100644 --- a/src/com/android/settings/accessibility/AccessibilitySettings.java +++ b/src/com/android/settings/accessibility/AccessibilitySettings.java @@ -16,21 +16,13 @@ package com.android.settings.accessibility; -import static com.android.settingslib.widget.TwoTargetPreference.ICON_SIZE_MEDIUM; - 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; 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.Color; -import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Handler; import android.os.UserHandle; @@ -40,22 +32,17 @@ import android.util.ArrayMap; import android.view.accessibility.AccessibilityManager; import androidx.annotation.VisibleForTesting; -import androidx.core.content.ContextCompat; import androidx.preference.Preference; import androidx.preference.PreferenceCategory; import com.android.internal.accessibility.AccessibilityShortcutController; import com.android.internal.content.PackageMonitor; import com.android.settings.R; -import com.android.settings.Utils; import com.android.settings.accessibility.AccessibilityUtil.AccessibilityServiceFragmentType; import com.android.settings.dashboard.DashboardFragment; import com.android.settings.overlay.FeatureFactory; import com.android.settings.search.BaseSearchIndexProvider; -import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; -import com.android.settingslib.RestrictedLockUtilsInternal; import com.android.settingslib.RestrictedPreference; -import com.android.settingslib.accessibility.AccessibilityUtils; import com.android.settingslib.search.SearchIndexable; import com.android.settingslib.search.SearchIndexableRaw; @@ -63,7 +50,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; -import java.util.Set; /** Activity with the accessibility settings. */ @SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC) @@ -71,9 +57,6 @@ public class AccessibilitySettings extends DashboardFragment { private static final String TAG = "AccessibilitySettings"; - // Index of the first preference in a preference category. - private static final int FIRST_PREFERENCE_IN_CATEGORY_INDEX = -1; - // Preference categories private static final String CATEGORY_SCREEN_READER = "screen_reader_category"; private static final String CATEGORY_CAPTIONS = "captions_category"; @@ -249,8 +232,7 @@ public class AccessibilitySettings extends DashboardFragment { * @param serviceEnabled Whether the accessibility service is enabled. * @return The service summary */ - @VisibleForTesting - static CharSequence getServiceSummary(Context context, AccessibilityServiceInfo info, + public static CharSequence getServiceSummary(Context context, AccessibilityServiceInfo info, boolean serviceEnabled) { if (serviceEnabled && info.crashed) { return context.getText(R.string.accessibility_summary_state_stopped); @@ -289,8 +271,7 @@ public class AccessibilitySettings extends DashboardFragment { * @param serviceEnabled Whether the accessibility service is enabled. * @return The service description */ - @VisibleForTesting - static CharSequence getServiceDescription(Context context, AccessibilityServiceInfo info, + public static CharSequence getServiceDescription(Context context, AccessibilityServiceInfo info, boolean serviceEnabled) { if (serviceEnabled && info.crashed) { return context.getText(R.string.accessibility_description_state_stopped); @@ -506,296 +487,4 @@ public class AccessibilitySettings extends DashboardFragment { context); } }; - - /** - * This class helps setup RestrictedPreference. - */ - @VisibleForTesting - static class RestrictedPreferenceHelper { - 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); - } - - /** - * 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} - */ - @VisibleForTesting - 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, packageName, - resolveInfo.serviceInfo.applicationInfo.uid); - - setRestrictedPreferenceEnabled(preference, permittedServices, serviceEnabled); - - final String prefKey = preference.getKey(); - final int imageRes = info.getAnimatedImageRes(); - final CharSequence intro = info.loadIntro(mPm); - final CharSequence description = getServiceDescription(mContext, info, - serviceEnabled); - final String htmlDescription = info.loadHtmlDescription(mPm); - final String settingsClassName = info.getSettingsActivityName(); - final String tileServiceClassName = info.getTileServiceName(); - - putBasicExtras(preference, prefKey, title, intro, description, imageRes, - htmlDescription, componentName); - putServiceExtras(preference, resolveInfo, serviceEnabled); - putSettingsExtras(preference, packageName, settingsClassName); - putTileServiceExtras(preference, packageName, tileServiceClassName); - - 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} - */ - @VisibleForTesting - 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, componentName.getPackageName(), - activityInfo.applicationInfo.uid); - final boolean serviceEnabled = enabledServices.contains(componentName); - - setRestrictedPreferenceEnabled(preference, permittedServices, serviceEnabled); - - final String prefKey = preference.getKey(); - final CharSequence intro = info.loadIntro(mPm); - final String description = info.loadDescription(mPm); - final int imageRes = info.getAnimatedImageRes(); - final String htmlDescription = info.loadHtmlDescription(mPm); - final String settingsClassName = info.getSettingsActivityName(); - final String tileServiceClassName = info.getTileServiceName(); - - putBasicExtras(preference, prefKey, title, intro, description, imageRes, - htmlDescription, componentName); - putSettingsExtras(preference, componentName.getPackageName(), settingsClassName); - putTileServiceExtras(preference, componentName.getPackageName(), - tileServiceClassName); - - preferenceList.add(preference); - } - return preferenceList; - } - - private String getAccessibilityServiceFragmentTypeName(AccessibilityServiceInfo info) { - // Shorten the name to avoid exceeding 100 characters in one line. - final String volumeShortcutToggleAccessibilityServicePreferenceFragment = - VolumeShortcutToggleAccessibilityServicePreferenceFragment.class.getName(); - - switch (AccessibilityUtil.getAccessibilityServiceFragmentType(info)) { - case AccessibilityServiceFragmentType.VOLUME_SHORTCUT_TOGGLE: - return volumeShortcutToggleAccessibilityServicePreferenceFragment; - case AccessibilityServiceFragmentType.INVISIBLE_TOGGLE: - return InvisibleToggleAccessibilityServicePreferenceFragment.class.getName(); - case AccessibilityServiceFragmentType.TOGGLE: - return ToggleAccessibilityServicePreferenceFragment.class.getName(); - default: - // impossible status - throw new AssertionError(); - } - } - - private RestrictedPreference createRestrictedPreference(String key, CharSequence title, - CharSequence summary, Drawable icon, String fragment, String packageName, int uid) { - final RestrictedPreference preference = new RestrictedPreference(mContext, packageName, - uid); - - preference.setKey(key); - preference.setTitle(title); - preference.setSummary(summary); - preference.setIcon(Utils.getAdaptiveIcon(mContext, icon, Color.WHITE)); - 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, - 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, preference.getPackageName(), UserHandle.myUserId()); - - if (admin != null) { - preference.setDisabledByAdmin(admin); - } else if (!appOpsAllowed) { - preference.setDisabledByAppOps(true); - } else { - preference.setEnabled(false); - } - } - } - - /** Puts the basic extras into {@link RestrictedPreference}'s getExtras(). */ - private void putBasicExtras(RestrictedPreference preference, String prefKey, - CharSequence title, CharSequence intro, CharSequence summary, int imageRes, - String htmlDescription, ComponentName componentName) { - final Bundle extras = preference.getExtras(); - extras.putString(EXTRA_PREFERENCE_KEY, prefKey); - extras.putCharSequence(EXTRA_TITLE, title); - extras.putCharSequence(EXTRA_INTRO, intro); - extras.putCharSequence(EXTRA_SUMMARY, summary); - 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(). - * - *

Note: Called by {@link AccessibilityServiceInfo}.

- * - * @param preference The preference we are configuring. - * @param resolveInfo The service resolve info. - * @param serviceEnabled Whether the accessibility service is enabled. - */ - 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(). - * - *

Note: Called when settings UI is needed.

- * - * @param preference The preference we are configuring. - * @param packageName Package of accessibility feature. - * @param settingsClassName The component name of an activity that allows the user to modify - * the settings for this accessibility feature. - */ - 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()); - } - } - - /** - * Puts the information about a particular application - * {@link android.service.quicksettings.TileService} into {@link RestrictedPreference}'s - * getExtras(). - * - *

Note: Called when a tooltip of - * {@link android.service.quicksettings.TileService} is needed.

- * - * @param preference The preference we are configuring. - * @param packageName Package of accessibility feature. - * @param tileServiceClassName The component name of tileService is associated with this - * accessibility feature. - */ - private void putTileServiceExtras(RestrictedPreference preference, String packageName, - String tileServiceClassName) { - final Bundle extras = preference.getExtras(); - if (!TextUtils.isEmpty(tileServiceClassName)) { - extras.putString(EXTRA_TILE_SERVICE_COMPONENT_NAME, - new ComponentName(packageName, tileServiceClassName).flattenToString()); - } - } - } } diff --git a/src/com/android/settings/accessibility/RestrictedPreferenceHelper.java b/src/com/android/settings/accessibility/RestrictedPreferenceHelper.java new file mode 100644 index 00000000000..3e42e21c932 --- /dev/null +++ b/src/com/android/settings/accessibility/RestrictedPreferenceHelper.java @@ -0,0 +1,337 @@ +/* + * Copyright (C) 2022 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; + +import static com.android.settingslib.widget.TwoTargetPreference.ICON_SIZE_MEDIUM; + +import android.accessibilityservice.AccessibilityServiceInfo; +import android.accessibilityservice.AccessibilityShortcutInfo; +import android.app.AppOpsManager; +import android.app.admin.DevicePolicyManager; +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.graphics.Color; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.os.UserHandle; +import android.text.TextUtils; + +import androidx.core.content.ContextCompat; + +import com.android.settings.R; +import com.android.settings.Utils; +import com.android.settingslib.RestrictedLockUtils; +import com.android.settingslib.RestrictedLockUtilsInternal; +import com.android.settingslib.RestrictedPreference; +import com.android.settingslib.accessibility.AccessibilityUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * This class helps setup RestrictedPreference for accessibility. + */ +public class RestrictedPreferenceHelper { + // Index of the first preference in a preference category. + private static final int FIRST_PREFERENCE_IN_CATEGORY_INDEX = -1; + + private final Context mContext; + private final DevicePolicyManager mDpm; + private final PackageManager mPm; + private final AppOpsManager mAppOps; + + public RestrictedPreferenceHelper(Context context) { + mContext = context; + mDpm = context.getSystemService(DevicePolicyManager.class); + mPm = context.getPackageManager(); + mAppOps = context.getSystemService(AppOpsManager.class); + } + + /** + * 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} + */ + public 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 = AccessibilitySettings.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, packageName, + resolveInfo.serviceInfo.applicationInfo.uid); + + setRestrictedPreferenceEnabled(preference, permittedServices, serviceEnabled); + + final String prefKey = preference.getKey(); + final int imageRes = info.getAnimatedImageRes(); + final CharSequence intro = info.loadIntro(mPm); + final CharSequence description = AccessibilitySettings.getServiceDescription( + mContext, info, serviceEnabled); + final String htmlDescription = info.loadHtmlDescription(mPm); + final String settingsClassName = info.getSettingsActivityName(); + final String tileServiceClassName = info.getTileServiceName(); + + putBasicExtras(preference, prefKey, title, intro, description, imageRes, + htmlDescription, componentName); + putServiceExtras(preference, resolveInfo, serviceEnabled); + putSettingsExtras(preference, packageName, settingsClassName); + putTileServiceExtras(preference, packageName, tileServiceClassName); + + preferenceList.add(preference); + } + return preferenceList; + } + + /** + * Creates 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} + */ + public 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, componentName.getPackageName(), + activityInfo.applicationInfo.uid); + final boolean serviceEnabled = enabledServices.contains(componentName); + + setRestrictedPreferenceEnabled(preference, permittedServices, serviceEnabled); + + final String prefKey = preference.getKey(); + final CharSequence intro = info.loadIntro(mPm); + final String description = info.loadDescription(mPm); + final int imageRes = info.getAnimatedImageRes(); + final String htmlDescription = info.loadHtmlDescription(mPm); + final String settingsClassName = info.getSettingsActivityName(); + final String tileServiceClassName = info.getTileServiceName(); + + putBasicExtras(preference, prefKey, title, intro, description, imageRes, + htmlDescription, componentName); + putSettingsExtras(preference, componentName.getPackageName(), settingsClassName); + putTileServiceExtras(preference, componentName.getPackageName(), + tileServiceClassName); + + preferenceList.add(preference); + } + return preferenceList; + } + + private String getAccessibilityServiceFragmentTypeName(AccessibilityServiceInfo info) { + final int type = AccessibilityUtil.getAccessibilityServiceFragmentType(info); + switch (type) { + case AccessibilityUtil.AccessibilityServiceFragmentType.VOLUME_SHORTCUT_TOGGLE: + return VolumeShortcutToggleAccessibilityServicePreferenceFragment.class.getName(); + case AccessibilityUtil.AccessibilityServiceFragmentType.INVISIBLE_TOGGLE: + return InvisibleToggleAccessibilityServicePreferenceFragment.class.getName(); + case AccessibilityUtil.AccessibilityServiceFragmentType.TOGGLE: + return ToggleAccessibilityServicePreferenceFragment.class.getName(); + default: + throw new IllegalArgumentException( + "Unsupported accessibility fragment type " + type); + } + } + + private RestrictedPreference createRestrictedPreference(String key, CharSequence title, + CharSequence summary, Drawable icon, String fragment, String packageName, int uid) { + final RestrictedPreference preference = new RestrictedPreference(mContext, packageName, + uid); + + preference.setKey(key); + preference.setTitle(title); + preference.setSummary(summary); + preference.setIcon(Utils.getAdaptiveIcon(mContext, icon, Color.WHITE)); + 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, + 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 RestrictedLockUtils.EnforcedAdmin admin = + RestrictedLockUtilsInternal.checkIfAccessibilityServiceDisallowed( + mContext, preference.getPackageName(), UserHandle.myUserId()); + + if (admin != null) { + preference.setDisabledByAdmin(admin); + } else if (!appOpsAllowed) { + preference.setDisabledByAppOps(true); + } else { + preference.setEnabled(false); + } + } + } + + /** Puts the basic extras into {@link RestrictedPreference}'s getExtras(). */ + private void putBasicExtras(RestrictedPreference preference, String prefKey, + CharSequence title, CharSequence intro, CharSequence summary, int imageRes, + String htmlDescription, ComponentName componentName) { + final Bundle extras = preference.getExtras(); + extras.putString(AccessibilitySettings.EXTRA_PREFERENCE_KEY, prefKey); + extras.putCharSequence(AccessibilitySettings.EXTRA_TITLE, title); + extras.putCharSequence(AccessibilitySettings.EXTRA_INTRO, intro); + extras.putCharSequence(AccessibilitySettings.EXTRA_SUMMARY, summary); + extras.putParcelable(AccessibilitySettings.EXTRA_COMPONENT_NAME, componentName); + extras.putInt(AccessibilitySettings.EXTRA_ANIMATED_IMAGE_RES, imageRes); + extras.putString(AccessibilitySettings.EXTRA_HTML_DESCRIPTION, htmlDescription); + } + + /** + * Puts the service extras into {@link RestrictedPreference}'s getExtras(). + * + *

Note: Called by {@link AccessibilityServiceInfo}.

+ * + * @param preference The preference we are configuring. + * @param resolveInfo The service resolve info. + * @param serviceEnabled Whether the accessibility service is enabled. + */ + private void putServiceExtras(RestrictedPreference preference, ResolveInfo resolveInfo, + Boolean serviceEnabled) { + final Bundle extras = preference.getExtras(); + + extras.putParcelable(AccessibilitySettings.EXTRA_RESOLVE_INFO, resolveInfo); + extras.putBoolean(AccessibilitySettings.EXTRA_CHECKED, serviceEnabled); + } + + /** + * Puts the settings extras into {@link RestrictedPreference}'s getExtras(). + * + *

Note: Called when settings UI is needed.

+ * + * @param preference The preference we are configuring. + * @param packageName Package of accessibility feature. + * @param settingsClassName The component name of an activity that allows the user to modify + * the settings for this accessibility feature. + */ + private void putSettingsExtras(RestrictedPreference preference, String packageName, + String settingsClassName) { + final Bundle extras = preference.getExtras(); + + if (!TextUtils.isEmpty(settingsClassName)) { + extras.putString(AccessibilitySettings.EXTRA_SETTINGS_TITLE, + mContext.getText(R.string.accessibility_menu_item_settings).toString()); + extras.putString(AccessibilitySettings.EXTRA_SETTINGS_COMPONENT_NAME, + new ComponentName(packageName, settingsClassName).flattenToString()); + } + } + + /** + * Puts the information about a particular application + * {@link android.service.quicksettings.TileService} into {@link RestrictedPreference}'s + * getExtras(). + * + *

Note: Called when a tooltip of + * {@link android.service.quicksettings.TileService} is needed.

+ * + * @param preference The preference we are configuring. + * @param packageName Package of accessibility feature. + * @param tileServiceClassName The component name of tileService is associated with this + * accessibility feature. + */ + private void putTileServiceExtras(RestrictedPreference preference, String packageName, + String tileServiceClassName) { + final Bundle extras = preference.getExtras(); + if (!TextUtils.isEmpty(tileServiceClassName)) { + extras.putString(AccessibilitySettings.EXTRA_TILE_SERVICE_COMPONENT_NAME, + new ComponentName(packageName, tileServiceClassName).flattenToString()); + } + } +} diff --git a/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java index c1950be2506..7c52754765f 100644 --- a/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java @@ -87,8 +87,6 @@ public class AccessibilitySettingsTest { private static final String PACKAGE_NAME = "com.android.test"; private static final String CLASS_NAME = PACKAGE_NAME + ".test_a11y_service"; private static final ComponentName COMPONENT_NAME = new ComponentName(PACKAGE_NAME, 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"; @@ -246,37 +244,6 @@ public class AccessibilitySettingsTest { assertThat(description).isEqualTo(DEFAULT_DESCRIPTION); } - @Test - public void createAccessibilityServicePreferenceList_hasOneInfo_containsSameKey() { - final String key = COMPONENT_NAME.flattenToString(); - final AccessibilitySettings.RestrictedPreferenceHelper helper = - new AccessibilitySettings.RestrictedPreferenceHelper(mContext); - final List infoList = new ArrayList<>( - 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 = COMPONENT_NAME.flattenToString(); - final AccessibilitySettings.RestrictedPreferenceHelper helper = - new AccessibilitySettings.RestrictedPreferenceHelper(mContext); - setMockAccessibilityShortcutInfo(mShortcutInfo); - final List infoList = new ArrayList<>( - singletonList(mShortcutInfo)); - - final List preferenceList = - helper.createAccessibilityActivityPreferenceList(infoList); - RestrictedPreference preference = preferenceList.get(0); - - assertThat(preference.getKey()).isEqualTo(key); - } - @Test @Config(shadows = {ShadowFragment.class, ShadowUserManager.class}) public void onCreate_haveRegisterToSpecificUrisAndActions() { diff --git a/tests/robotests/src/com/android/settings/accessibility/RestrictedPreferenceHelperTest.java b/tests/robotests/src/com/android/settings/accessibility/RestrictedPreferenceHelperTest.java new file mode 100644 index 00000000000..99a78cfc408 --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/RestrictedPreferenceHelperTest.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2022 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; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import static java.util.Collections.singletonList; + +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 androidx.test.core.app.ApplicationProvider; + +import com.android.settingslib.RestrictedPreference; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** Test for {@link RestrictedPreferenceHelper}. */ +@RunWith(RobolectricTestRunner.class) +public class RestrictedPreferenceHelperTest { + + private static final String PACKAGE_NAME = "com.android.test"; + private static final String CLASS_NAME = PACKAGE_NAME + ".test_a11y_service"; + private static final ComponentName COMPONENT_NAME = new ComponentName(PACKAGE_NAME, CLASS_NAME); + private static final String DEFAULT_SUMMARY = "default summary"; + private static final String DEFAULT_DESCRIPTION = "default description"; + private static final String DEFAULT_LABEL = "default label"; + + @Rule + public final MockitoRule mocks = MockitoJUnit.rule(); + private final Context mContext = ApplicationProvider.getApplicationContext(); + @Spy + private final AccessibilityServiceInfo mServiceInfo = getMockAccessibilityServiceInfo( + PACKAGE_NAME, CLASS_NAME); + @Mock + private AccessibilityShortcutInfo mShortcutInfo; + private final RestrictedPreferenceHelper mHelper = new RestrictedPreferenceHelper(mContext); + + @Test + public void createAccessibilityServicePreferenceList_hasOneInfo_containsSameKey() { + final String key = COMPONENT_NAME.flattenToString(); + final List infoList = new ArrayList<>( + singletonList(mServiceInfo)); + + final List preferenceList = + mHelper.createAccessibilityServicePreferenceList(infoList); + final RestrictedPreference preference = preferenceList.get(0); + + assertThat(preference.getKey()).isEqualTo(key); + } + + @Test + public void createAccessibilityActivityPreferenceList_hasOneInfo_containsSameKey() { + final String key = COMPONENT_NAME.flattenToString(); + setMockAccessibilityShortcutInfo(mShortcutInfo); + final List infoList = new ArrayList<>( + singletonList(mShortcutInfo)); + + final List preferenceList = + mHelper.createAccessibilityActivityPreferenceList(infoList); + final RestrictedPreference preference = preferenceList.get(0); + + assertThat(preference.getKey()).isEqualTo(key); + } + + private AccessibilityServiceInfo getMockAccessibilityServiceInfo(String packageName, + String className) { + final ApplicationInfo applicationInfo = new ApplicationInfo(); + final ServiceInfo serviceInfo = new ServiceInfo(); + applicationInfo.packageName = packageName; + serviceInfo.packageName = packageName; + serviceInfo.name = className; + serviceInfo.applicationInfo = applicationInfo; + + final ResolveInfo resolveInfo = new ResolveInfo(); + resolveInfo.serviceInfo = serviceInfo; + try { + final AccessibilityServiceInfo info = new AccessibilityServiceInfo(resolveInfo, + mContext); + info.setComponentName(new ComponentName(packageName, className)); + return info; + } catch (XmlPullParserException | IOException e) { + // Do nothing + } + return null; + } + + 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); + when(mockInfo.loadDescription(any())).thenReturn(DEFAULT_DESCRIPTION); + when(mockInfo.getComponentName()).thenReturn(COMPONENT_NAME); + } +}