• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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.settings.accessibility;
18 
19 import android.accessibilityservice.AccessibilityServiceInfo;
20 import android.accessibilityservice.AccessibilityShortcutInfo;
21 import android.app.AppOpsManager;
22 import android.app.admin.DevicePolicyManager;
23 import android.content.ComponentName;
24 import android.content.Context;
25 import android.content.pm.ActivityInfo;
26 import android.content.pm.ResolveInfo;
27 import android.os.Bundle;
28 import android.os.UserHandle;
29 import android.text.TextUtils;
30 
31 import com.android.settings.R;
32 import com.android.settingslib.RestrictedLockUtils;
33 import com.android.settingslib.RestrictedLockUtilsInternal;
34 import com.android.settingslib.RestrictedPreference;
35 import com.android.settingslib.accessibility.AccessibilityUtils;
36 
37 import java.util.ArrayList;
38 import java.util.List;
39 import java.util.Set;
40 
41 /**
42  * This class helps setup RestrictedPreference for accessibility.
43  */
44 public class RestrictedPreferenceHelper {
45     // Index of the first preference in a preference category.
46     private static final int FIRST_PREFERENCE_IN_CATEGORY_INDEX = -1;
47 
48     private final Context mContext;
49     private final DevicePolicyManager mDpm;
50     private final AppOpsManager mAppOps;
51 
RestrictedPreferenceHelper(Context context)52     public RestrictedPreferenceHelper(Context context) {
53         mContext = context;
54         mDpm = context.getSystemService(DevicePolicyManager.class);
55         mAppOps = context.getSystemService(AppOpsManager.class);
56     }
57 
58     /**
59      * Creates the list of {@link RestrictedPreference} with the installedServices arguments.
60      *
61      * @param installedServices The list of {@link AccessibilityServiceInfo}s of the
62      *                          installed accessibility services
63      * @return The list of {@link RestrictedPreference}
64      */
createAccessibilityServicePreferenceList( List<AccessibilityServiceInfo> installedServices)65     public List<RestrictedPreference> createAccessibilityServicePreferenceList(
66             List<AccessibilityServiceInfo> installedServices) {
67 
68         final Set<ComponentName> enabledServices =
69                 AccessibilityUtils.getEnabledServicesFromSettings(mContext);
70         final List<String> permittedServices = mDpm.getPermittedAccessibilityServices(
71                 UserHandle.myUserId());
72         final int installedServicesSize = installedServices.size();
73 
74         final List<RestrictedPreference> preferenceList = new ArrayList<>(
75                 installedServicesSize);
76 
77         for (int i = 0; i < installedServicesSize; ++i) {
78             final AccessibilityServiceInfo info = installedServices.get(i);
79             final ResolveInfo resolveInfo = info.getResolveInfo();
80             final String packageName = resolveInfo.serviceInfo.packageName;
81             final ComponentName componentName = new ComponentName(packageName,
82                     resolveInfo.serviceInfo.name);
83             final boolean serviceEnabled = enabledServices.contains(componentName);
84 
85             RestrictedPreference preference = new AccessibilityServicePreference(
86                     mContext, packageName, resolveInfo.serviceInfo.applicationInfo.uid,
87                     info, serviceEnabled);
88             setRestrictedPreferenceEnabled(preference, permittedServices, serviceEnabled);
89             preferenceList.add(preference);
90         }
91         return preferenceList;
92     }
93 
94     /**
95      * Creates the list of {@link AccessibilityActivityPreference} with the installedShortcuts
96      * arguments.
97      *
98      * @param installedShortcuts The list of {@link AccessibilityShortcutInfo}s of the
99      *                           installed accessibility shortcuts
100      * @return The list of {@link AccessibilityActivityPreference}
101      */
createAccessibilityActivityPreferenceList( List<AccessibilityShortcutInfo> installedShortcuts)102     public List<AccessibilityActivityPreference> createAccessibilityActivityPreferenceList(
103             List<AccessibilityShortcutInfo> installedShortcuts) {
104 
105         final int installedShortcutsSize = installedShortcuts.size();
106         final List<AccessibilityActivityPreference> preferenceList = new ArrayList<>(
107                 installedShortcutsSize);
108 
109         for (int i = 0; i < installedShortcutsSize; ++i) {
110             final AccessibilityShortcutInfo info = installedShortcuts.get(i);
111             final ActivityInfo activityInfo = info.getActivityInfo();
112             final ComponentName componentName = info.getComponentName();
113 
114             AccessibilityActivityPreference preference = new AccessibilityActivityPreference(
115                     mContext, componentName.getPackageName(), activityInfo.applicationInfo.uid,
116                     info);
117             // Accessibility Activities do not have elevated privileges so restricting
118             // them based on ECM or device admin does not give any value.
119             preference.setEnabled(true);
120             preferenceList.add(preference);
121         }
122         return preferenceList;
123     }
124 
getAccessibilityServiceFragmentTypeName(AccessibilityServiceInfo info)125     static String getAccessibilityServiceFragmentTypeName(AccessibilityServiceInfo info) {
126         final int type = AccessibilityUtil.getAccessibilityServiceFragmentType(info);
127         switch (type) {
128             case AccessibilityUtil.AccessibilityServiceFragmentType.VOLUME_SHORTCUT_TOGGLE:
129                 return VolumeShortcutToggleAccessibilityServicePreferenceFragment.class.getName();
130             case AccessibilityUtil.AccessibilityServiceFragmentType.INVISIBLE_TOGGLE:
131                 return InvisibleToggleAccessibilityServicePreferenceFragment.class.getName();
132             case AccessibilityUtil.AccessibilityServiceFragmentType.TOGGLE:
133                 return ToggleAccessibilityServicePreferenceFragment.class.getName();
134             default:
135                 throw new IllegalArgumentException(
136                         "Unsupported accessibility fragment type " + type);
137         }
138     }
139 
setRestrictedPreferenceEnabled(RestrictedPreference preference, final List<String> permittedServices, boolean serviceEnabled)140     private void setRestrictedPreferenceEnabled(RestrictedPreference preference,
141             final List<String> permittedServices, boolean serviceEnabled) {
142         // permittedServices null means all accessibility services are allowed.
143         boolean serviceAllowed = permittedServices == null || permittedServices.contains(
144                 preference.getPackageName());
145 
146         if (android.permission.flags.Flags.enhancedConfirmationModeApisEnabled()
147                 && android.security.Flags.extendEcmToAllSettings()) {
148             preference.checkEcmRestrictionAndSetDisabled(
149                     AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE,
150                     preference.getPackageName(), serviceEnabled);
151             if (preference.isDisabledByEcm()) {
152                 serviceAllowed = false;
153             }
154 
155             if (serviceAllowed || serviceEnabled) {
156                 preference.setEnabled(true);
157             } else {
158                 // Disable accessibility service that are not permitted.
159                 final RestrictedLockUtils.EnforcedAdmin admin =
160                         RestrictedLockUtilsInternal.checkIfAccessibilityServiceDisallowed(
161                                 mContext, preference.getPackageName(), UserHandle.myUserId());
162 
163                 if (admin != null) {
164                     preference.setDisabledByAdmin(admin);
165                 } else if (!preference.isDisabledByEcm()) {
166                     preference.setEnabled(false);
167                 }
168             }
169         } else {
170             boolean appOpsAllowed;
171             if (serviceAllowed) {
172                 try {
173                     final int mode = mAppOps.noteOpNoThrow(
174                             AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS,
175                             preference.getUid(), preference.getPackageName());
176                     final boolean ecmEnabled = mContext.getResources().getBoolean(
177                             com.android.internal.R.bool.config_enhancedConfirmationModeEnabled);
178                     appOpsAllowed = !ecmEnabled || mode == AppOpsManager.MODE_ALLOWED
179                             || mode == AppOpsManager.MODE_DEFAULT;
180                     serviceAllowed = appOpsAllowed;
181                 } catch (Exception e) {
182                     // Allow service in case if app ops is not available in testing.
183                     appOpsAllowed = true;
184                 }
185             } else {
186                 appOpsAllowed = false;
187             }
188             if (serviceAllowed || serviceEnabled) {
189                 preference.setEnabled(true);
190             } else {
191                 // Disable accessibility service that are not permitted.
192                 final RestrictedLockUtils.EnforcedAdmin admin =
193                         RestrictedLockUtilsInternal.checkIfAccessibilityServiceDisallowed(
194                                 mContext, preference.getPackageName(), UserHandle.myUserId());
195 
196                 if (admin != null) {
197                     preference.setDisabledByAdmin(admin);
198                 } else if (!appOpsAllowed) {
199                     preference.setDisabledByAppOps(true);
200                 } else {
201                     preference.setEnabled(false);
202                 }
203             }
204         }
205     }
206 
207     /** Puts the basic extras into {@link RestrictedPreference}'s getExtras(). */
putBasicExtras(RestrictedPreference preference, String prefKey, CharSequence title, CharSequence intro, CharSequence summary, int imageRes, String htmlDescription, ComponentName componentName, int metricsCategory)208     static void putBasicExtras(RestrictedPreference preference, String prefKey,
209             CharSequence title, CharSequence intro, CharSequence summary, int imageRes,
210             String htmlDescription, ComponentName componentName, int metricsCategory) {
211         final Bundle extras = preference.getExtras();
212         extras.putString(AccessibilitySettings.EXTRA_PREFERENCE_KEY, prefKey);
213         extras.putCharSequence(AccessibilitySettings.EXTRA_TITLE, title);
214         extras.putCharSequence(AccessibilitySettings.EXTRA_INTRO, intro);
215         extras.putCharSequence(AccessibilitySettings.EXTRA_SUMMARY, summary);
216         extras.putParcelable(AccessibilitySettings.EXTRA_COMPONENT_NAME, componentName);
217         extras.putInt(AccessibilitySettings.EXTRA_ANIMATED_IMAGE_RES, imageRes);
218         extras.putString(AccessibilitySettings.EXTRA_HTML_DESCRIPTION, htmlDescription);
219         extras.putInt(AccessibilitySettings.EXTRA_METRICS_CATEGORY, metricsCategory);
220         extras.putInt(AccessibilitySettings.EXTRA_FEEDBACK_CATEGORY, metricsCategory);
221     }
222 
223     /**
224      * Puts the service extras into {@link RestrictedPreference}'s getExtras().
225      *
226      * <p><b>Note:</b> Called by {@link AccessibilityServiceInfo}.</p>
227      *
228      * @param preference The preference we are configuring.
229      * @param resolveInfo The service resolve info.
230      * @param serviceEnabled Whether the accessibility service is enabled.
231      */
putServiceExtras(RestrictedPreference preference, ResolveInfo resolveInfo, Boolean serviceEnabled)232     static void putServiceExtras(RestrictedPreference preference, ResolveInfo resolveInfo,
233             Boolean serviceEnabled) {
234         final Bundle extras = preference.getExtras();
235 
236         extras.putParcelable(AccessibilitySettings.EXTRA_RESOLVE_INFO, resolveInfo);
237         extras.putBoolean(AccessibilitySettings.EXTRA_CHECKED, serviceEnabled);
238     }
239 
240     /**
241      * Puts the settings extras into {@link RestrictedPreference}'s getExtras().
242      *
243      * <p><b>Note:</b> Called when settings UI is needed.</p>
244      *
245      * @param preference The preference we are configuring.
246      * @param packageName Package of accessibility feature.
247      * @param settingsClassName The component name of an activity that allows the user to modify
248      *                          the settings for this accessibility feature.
249      */
putSettingsExtras(RestrictedPreference preference, String packageName, String settingsClassName)250     static void putSettingsExtras(RestrictedPreference preference, String packageName,
251             String settingsClassName) {
252         final Bundle extras = preference.getExtras();
253 
254         if (!TextUtils.isEmpty(settingsClassName)) {
255             extras.putString(AccessibilitySettings.EXTRA_SETTINGS_TITLE,
256                     preference.getContext().getText(
257                             R.string.accessibility_menu_item_settings).toString());
258             extras.putString(AccessibilitySettings.EXTRA_SETTINGS_COMPONENT_NAME,
259                     new ComponentName(packageName, settingsClassName).flattenToString());
260         }
261     }
262 
263     /**
264      * Puts the information about a particular application
265      * {@link android.service.quicksettings.TileService} into {@link RestrictedPreference}'s
266      * getExtras().
267      *
268      * <p><b>Note:</b> Called when a tooltip of
269      * {@link android.service.quicksettings.TileService} is needed.</p>
270      *
271      * @param preference The preference we are configuring.
272      * @param packageName Package of accessibility feature.
273      * @param tileServiceClassName The component name of tileService is associated with this
274      *                             accessibility feature.
275      */
putTileServiceExtras(RestrictedPreference preference, String packageName, String tileServiceClassName)276     static void putTileServiceExtras(RestrictedPreference preference, String packageName,
277             String tileServiceClassName) {
278         final Bundle extras = preference.getExtras();
279         if (!TextUtils.isEmpty(tileServiceClassName)) {
280             extras.putString(AccessibilitySettings.EXTRA_TILE_SERVICE_COMPONENT_NAME,
281                     new ComponentName(packageName, tileServiceClassName).flattenToString());
282         }
283     }
284 }
285