• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.tv.settings.accessibility;
18 
19 import static android.content.Context.ACCESSIBILITY_SERVICE;
20 
21 import static com.android.tv.settings.util.InstrumentationUtils.logToggleInteracted;
22 
23 import android.accessibilityservice.AccessibilityServiceInfo;
24 import android.app.admin.DevicePolicyManager;
25 import android.app.tvsettings.TvSettingsEnums;
26 import android.content.ComponentName;
27 import android.content.pm.ServiceInfo;
28 import android.os.Bundle;
29 import android.os.UserHandle;
30 import android.provider.Settings;
31 import android.text.TextUtils;
32 import android.view.accessibility.AccessibilityManager;
33 import android.util.ArrayMap;
34 
35 import androidx.annotation.Keep;
36 import androidx.preference.Preference;
37 import androidx.preference.PreferenceGroup;
38 import androidx.preference.PreferenceCategory;
39 import androidx.preference.SwitchPreference;
40 import androidx.preference.TwoStatePreference;
41 
42 import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
43 import com.android.settingslib.RestrictedLockUtilsInternal;
44 import com.android.settingslib.RestrictedPreference;
45 import com.android.settingslib.accessibility.AccessibilityUtils;
46 import com.android.tv.settings.R;
47 import com.android.tv.settings.SettingsPreferenceFragment;
48 import com.android.tv.settings.overlay.FlavorUtils;
49 
50 import java.util.List;
51 import java.util.Set;
52 import java.util.Map;
53 
54 /**
55  * Fragment for Accessibility settings
56  */
57 @Keep
58 public class AccessibilityFragment extends SettingsPreferenceFragment {
59     private static final String TOGGLE_HIGH_TEXT_CONTRAST_KEY = "toggle_high_text_contrast";
60     private static final String TOGGLE_AUDIO_DESCRIPTION_KEY = "toggle_audio_description";
61     private static final String TOGGLE_BOLD_TEXT_KEY = "toggle_bold_text";
62     private static final String COLOR_CORRECTION_TWOPANEL_KEY = "color_correction_only_twopanel";
63     private static final String COLOR_CORRECTION_CLASSIC_KEY = "color_correction_only_classic";
64     private static final int BOLD_TEXT_ADJUSTMENT = 500;
65     private static final int FIRST_PREFERENCE_IN_CATEGORY_INDEX = -1;
66 
67     PreferenceCategory mServicesPrefCategory;
68 
69     private final Map<ComponentName, PreferenceCategory>
70             mServiceComponentNameToPreferenceCategoryMap = new ArrayMap<>();
71 
72     private enum AccessibilityCategory {
73         SCREEN_READERS("accessibility_screen_readers_category",
74                 R.array.config_preinstalled_screen_reader_services),
75         DISPLAY("accessibility_display_category",
76                 R.array.config_preinstalled_display_services),
77         INTERACTION_CONTROLS("accessibility_interaction_controls_category",
78                 R.array.config_preinstalled_interaction_control_services),
79         AUDIO_AND_ONSCREEN_TEXT("accessibility_audio_and_onscreen_text_category",
80                 R.array.config_preinstalled_audio_and_onscreen_text_services),
81         EXPERIMENTAL("accessibility_experimental_category",
82                 R.array.config_preinstalled_experimental_services),
83         SERVICES("accessibility_services_category",
84                 R.array.config_preinstalled_additional_services);
85 
86         final String key;
87         final int servicesArrayId;
88 
AccessibilityCategory(String key, int servicesArrayId)89         AccessibilityCategory(String key, int servicesArrayId) {
90             this.key = key;
91             this.servicesArrayId = servicesArrayId;
92         }
93 
getKey()94         String getKey() {
95             return this.key;
96         }
97 
getServicesArrayId()98         int getServicesArrayId() {
99             return this.servicesArrayId;
100         }
101     }
102 
103     private AccessibilityManager.AccessibilityStateChangeListener
104             mAccessibilityStateChangeListener = enabled -> refreshServices();
105 
106     /**
107      * Create a new instance of the fragment
108      * @return New fragment instance
109      */
newInstance()110     public static AccessibilityFragment newInstance() {
111         return new AccessibilityFragment();
112     }
113 
114     @Override
onResume()115     public void onResume() {
116         super.onResume();
117         refreshServices();
118     }
119 
120     @Override
onStop()121     public void onStop() {
122         super.onStop();
123         AccessibilityManager am = (AccessibilityManager)
124                 getContext().getSystemService(ACCESSIBILITY_SERVICE);
125         if (am != null) {
126             am.removeAccessibilityStateChangeListener(mAccessibilityStateChangeListener);
127         }
128     }
129 
130     @Override
onCreatePreferences(Bundle savedInstanceState, String rootKey)131     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
132         setPreferencesFromResource(R.xml.accessibility, null);
133 
134         final TwoStatePreference highContrastPreference =
135                 (TwoStatePreference) findPreference(TOGGLE_HIGH_TEXT_CONTRAST_KEY);
136         highContrastPreference.setChecked(Settings.Secure.getInt(getContext().getContentResolver(),
137                 Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED, 0) == 1);
138 
139         final TwoStatePreference audioDescriptionPreference =
140                 (TwoStatePreference) findPreference(TOGGLE_AUDIO_DESCRIPTION_KEY);
141         audioDescriptionPreference.setChecked(Settings.Secure.getInt(
142                 getContext().getContentResolver(),
143                 Settings.Secure.ENABLED_ACCESSIBILITY_AUDIO_DESCRIPTION_BY_DEFAULT, 0) == 1);
144 
145         final TwoStatePreference boldTextPreference =
146                 (TwoStatePreference) findPreference(TOGGLE_BOLD_TEXT_KEY);
147         boldTextPreference.setChecked(Settings.Secure.getInt(
148                 getContext().getContentResolver(),
149                 Settings.Secure.FONT_WEIGHT_ADJUSTMENT, 0) == BOLD_TEXT_ADJUSTMENT);
150 
151         Preference colorCorrectionPreferenceToSetVisible = FlavorUtils.isTwoPanel(getContext())
152                 ? (Preference) findPreference(COLOR_CORRECTION_TWOPANEL_KEY)
153                 : (Preference) findPreference(COLOR_CORRECTION_CLASSIC_KEY);
154         colorCorrectionPreferenceToSetVisible.setVisible(true);
155 
156         mServicesPrefCategory = findPreference(AccessibilityCategory.SERVICES.getKey());
157         populateServiceToPreferenceCategoryMaps();
158         refreshServices();
159         AccessibilityManager am = (AccessibilityManager)
160                 getContext().getSystemService(ACCESSIBILITY_SERVICE);
161         if (am != null) {
162             am.addAccessibilityStateChangeListener(mAccessibilityStateChangeListener);
163         }
164     }
165 
166     @Override
onPreferenceTreeClick(Preference preference)167     public boolean onPreferenceTreeClick(Preference preference) {
168         if (TextUtils.equals(preference.getKey(), TOGGLE_HIGH_TEXT_CONTRAST_KEY)) {
169             logToggleInteracted(
170                     TvSettingsEnums.SYSTEM_A11Y_HIGH_CONTRAST_TEXT,
171                     ((SwitchPreference) preference).isChecked());
172             Settings.Secure.putInt(getActivity().getContentResolver(),
173                     Settings.Secure.ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED,
174                     (((SwitchPreference) preference).isChecked() ? 1 : 0));
175             return true;
176         } else if (TextUtils.equals(preference.getKey(), TOGGLE_AUDIO_DESCRIPTION_KEY)) {
177             logToggleInteracted(
178                     TvSettingsEnums.SYSTEM_A11Y_AUDIO_DESCRIPTION,
179                     ((SwitchPreference) preference).isChecked());
180             Settings.Secure.putInt(getActivity().getContentResolver(),
181                     Settings.Secure.ENABLED_ACCESSIBILITY_AUDIO_DESCRIPTION_BY_DEFAULT,
182                     (((SwitchPreference) preference).isChecked() ? 1 : 0));
183             return true;
184         } else if (TextUtils.equals(preference.getKey(), TOGGLE_BOLD_TEXT_KEY)) {
185             logToggleInteracted(
186                     TvSettingsEnums.SYSTEM_A11Y_BOLD_TEXT,
187                     ((SwitchPreference) preference).isChecked());
188             Settings.Secure.putInt(getActivity().getContentResolver(),
189                     Settings.Secure.FONT_WEIGHT_ADJUSTMENT,
190                     (((SwitchPreference) preference).isChecked() ? BOLD_TEXT_ADJUSTMENT : 0));
191             return true;
192         } else {
193             return super.onPreferenceTreeClick(preference);
194         }
195     }
196 
populateServiceToPreferenceCategoryMaps()197     private void populateServiceToPreferenceCategoryMaps() {
198         for (AccessibilityCategory accessibilityCategory : AccessibilityCategory.values()) {
199             String[] services = getResources().getStringArray(
200                     accessibilityCategory.getServicesArrayId());
201             PreferenceCategory prefCategory = findPreference(accessibilityCategory.getKey());
202             for (int i = 0; i < services.length; i++) {
203                 ComponentName component = ComponentName.unflattenFromString(services[i]);
204                 mServiceComponentNameToPreferenceCategoryMap.put(component, prefCategory);
205             }
206         }
207     }
208 
refreshServices()209     private void refreshServices() {
210         DevicePolicyManager dpm = getContext().getSystemService(DevicePolicyManager.class);
211         final List<AccessibilityServiceInfo> installedServiceInfos =
212                 getActivity().getSystemService(AccessibilityManager.class)
213                         .getInstalledAccessibilityServiceList();
214         final Set<ComponentName> enabledServices =
215                 AccessibilityUtils.getEnabledServicesFromSettings(getActivity());
216         final List<String> permittedServices = dpm.getPermittedAccessibilityServices(
217                 UserHandle.myUserId());
218 
219         final boolean accessibilityEnabled = Settings.Secure.getInt(
220                 getActivity().getContentResolver(),
221                 Settings.Secure.ACCESSIBILITY_ENABLED, 0) == 1;
222 
223         for (final AccessibilityServiceInfo accInfo : installedServiceInfos) {
224             final ServiceInfo serviceInfo = accInfo.getResolveInfo().serviceInfo;
225             final ComponentName componentName = new ComponentName(serviceInfo.packageName,
226                     serviceInfo.name);
227             final boolean serviceEnabled = accessibilityEnabled
228                     && enabledServices.contains(componentName);
229             // permittedServices null means all accessibility services are allowed.
230             final boolean serviceAllowed = permittedServices == null
231                     || permittedServices.contains(serviceInfo.packageName);
232 
233             final String title = accInfo.getResolveInfo()
234                     .loadLabel(getActivity().getPackageManager()).toString();
235 
236             final String key = "ServicePref:" + componentName.flattenToString();
237             RestrictedPreference servicePref = findPreference(key);
238             if (servicePref == null) {
239                 servicePref = new RestrictedPreference(getContext());
240                 servicePref.setKey(key);
241             }
242             servicePref.setTitle(title);
243             servicePref.setSummary(serviceEnabled ? R.string.settings_on : R.string.settings_off);
244             AccessibilityServiceFragment.prepareArgs(servicePref.getExtras(),
245                     serviceInfo.packageName,
246                     serviceInfo.name,
247                     accInfo.getSettingsActivityName(),
248                     title);
249 
250             if (serviceAllowed || serviceEnabled) {
251                 servicePref.setEnabled(true);
252                 servicePref.setFragment(AccessibilityServiceFragment.class.getName());
253             } else {
254                 // Disable accessibility service that are not permitted.
255                 final EnforcedAdmin admin =
256                         RestrictedLockUtilsInternal.checkIfAccessibilityServiceDisallowed(
257                                 getContext(), serviceInfo.packageName, UserHandle.myUserId());
258                 if (admin != null) {
259                     servicePref.setDisabledByAdmin(admin);
260                 } else {
261                     servicePref.setEnabled(false);
262                 }
263                 servicePref.setFragment(null);
264             }
265 
266             // Make the screen reader component be the first preference in its preference category.
267             final String screenReaderFlattenedComponentName = getResources().getString(
268                     R.string.accessibility_screen_reader_flattened_component_name);
269             if (componentName.flattenToString().equals(screenReaderFlattenedComponentName)) {
270                 servicePref.setOrder(FIRST_PREFERENCE_IN_CATEGORY_INDEX);
271             }
272 
273             PreferenceCategory prefCategory = mServicesPrefCategory;
274             if (mServiceComponentNameToPreferenceCategoryMap.containsKey(componentName)) {
275                 prefCategory = mServiceComponentNameToPreferenceCategoryMap.get(componentName);
276             }
277             // The method "addPreference" only adds the preference if it is not there already.
278             prefCategory.addPreference(servicePref);
279         }
280         mServicesPrefCategory.setVisible(mServicesPrefCategory.getPreferenceCount() != 0);
281     }
282 
283     @Override
getPageId()284     protected int getPageId() {
285         return TvSettingsEnums.SYSTEM_A11Y;
286     }
287 }
288