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