• 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 static com.android.settingslib.widget.TwoTargetPreference.ICON_SIZE_MEDIUM;
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.PackageManager;
29 import android.content.pm.ResolveInfo;
30 import android.graphics.Color;
31 import android.graphics.drawable.Drawable;
32 import android.os.Bundle;
33 import android.os.UserHandle;
34 import android.text.TextUtils;
35 
36 import androidx.core.content.ContextCompat;
37 
38 import com.android.settings.R;
39 import com.android.settings.Utils;
40 import com.android.settings.overlay.FeatureFactory;
41 import com.android.settingslib.RestrictedLockUtils;
42 import com.android.settingslib.RestrictedLockUtilsInternal;
43 import com.android.settingslib.RestrictedPreference;
44 import com.android.settingslib.accessibility.AccessibilityUtils;
45 
46 import java.util.ArrayList;
47 import java.util.List;
48 import java.util.Set;
49 
50 /**
51  * This class helps setup RestrictedPreference for accessibility.
52  */
53 public class RestrictedPreferenceHelper {
54     // Index of the first preference in a preference category.
55     private static final int FIRST_PREFERENCE_IN_CATEGORY_INDEX = -1;
56 
57     private final Context mContext;
58     private final DevicePolicyManager mDpm;
59     private final PackageManager mPm;
60     private final AppOpsManager mAppOps;
61 
RestrictedPreferenceHelper(Context context)62     public RestrictedPreferenceHelper(Context context) {
63         mContext = context;
64         mDpm = context.getSystemService(DevicePolicyManager.class);
65         mPm = context.getPackageManager();
66         mAppOps = context.getSystemService(AppOpsManager.class);
67     }
68 
69     /**
70      * Creates the list of {@link RestrictedPreference} with the installedServices arguments.
71      *
72      * @param installedServices The list of {@link AccessibilityServiceInfo}s of the
73      *                          installed accessibility services
74      * @return The list of {@link RestrictedPreference}
75      */
createAccessibilityServicePreferenceList( List<AccessibilityServiceInfo> installedServices)76     public List<RestrictedPreference> createAccessibilityServicePreferenceList(
77             List<AccessibilityServiceInfo> installedServices) {
78 
79         final Set<ComponentName> enabledServices =
80                 AccessibilityUtils.getEnabledServicesFromSettings(mContext);
81         final List<String> permittedServices = mDpm.getPermittedAccessibilityServices(
82                 UserHandle.myUserId());
83         final int installedServicesSize = installedServices.size();
84 
85         final List<RestrictedPreference> preferenceList = new ArrayList<>(
86                 installedServicesSize);
87 
88         for (int i = 0; i < installedServicesSize; ++i) {
89             final AccessibilityServiceInfo info = installedServices.get(i);
90             final ResolveInfo resolveInfo = info.getResolveInfo();
91             final String packageName = resolveInfo.serviceInfo.packageName;
92             final ComponentName componentName = new ComponentName(packageName,
93                     resolveInfo.serviceInfo.name);
94 
95             final String key = componentName.flattenToString();
96             final CharSequence title = resolveInfo.loadLabel(mPm);
97             final boolean serviceEnabled = enabledServices.contains(componentName);
98             final CharSequence summary = AccessibilitySettings.getServiceSummary(
99                     mContext, info, serviceEnabled);
100             final String fragment = getAccessibilityServiceFragmentTypeName(info);
101 
102             Drawable icon = resolveInfo.loadIcon(mPm);
103             if (resolveInfo.getIconResource() == 0) {
104                 icon = ContextCompat.getDrawable(mContext,
105                         R.drawable.ic_accessibility_generic);
106             }
107 
108             final RestrictedPreference preference = createRestrictedPreference(key, title,
109                     summary, icon, fragment, packageName,
110                     resolveInfo.serviceInfo.applicationInfo.uid);
111 
112             setRestrictedPreferenceEnabled(preference, permittedServices, serviceEnabled);
113 
114             final String prefKey = preference.getKey();
115             final int imageRes = info.getAnimatedImageRes();
116             final CharSequence intro = info.loadIntro(mPm);
117             final CharSequence description = AccessibilitySettings.getServiceDescription(
118                     mContext, info, serviceEnabled);
119             final String htmlDescription = info.loadHtmlDescription(mPm);
120             final String settingsClassName = info.getSettingsActivityName();
121             final String tileServiceClassName = info.getTileServiceName();
122             final int metricsCategory = FeatureFactory.getFactory(mContext)
123                     .getAccessibilityMetricsFeatureProvider()
124                     .getDownloadedFeatureMetricsCategory(componentName);
125 
126             putBasicExtras(preference, prefKey, title, intro, description, imageRes,
127                     htmlDescription, componentName, metricsCategory);
128             putServiceExtras(preference, resolveInfo, serviceEnabled);
129             putSettingsExtras(preference, packageName, settingsClassName);
130             putTileServiceExtras(preference, packageName, tileServiceClassName);
131 
132             preferenceList.add(preference);
133         }
134         return preferenceList;
135     }
136 
137     /**
138      * Creates the list of {@link RestrictedPreference} with the installedShortcuts arguments.
139      *
140      * @param installedShortcuts The list of {@link AccessibilityShortcutInfo}s of the
141      *                           installed accessibility shortcuts
142      * @return The list of {@link RestrictedPreference}
143      */
createAccessibilityActivityPreferenceList( List<AccessibilityShortcutInfo> installedShortcuts)144     public List<RestrictedPreference> createAccessibilityActivityPreferenceList(
145             List<AccessibilityShortcutInfo> installedShortcuts) {
146         final Set<ComponentName> enabledServices =
147                 AccessibilityUtils.getEnabledServicesFromSettings(mContext);
148         final List<String> permittedServices = mDpm.getPermittedAccessibilityServices(
149                 UserHandle.myUserId());
150 
151         final int installedShortcutsSize = installedShortcuts.size();
152         final List<RestrictedPreference> preferenceList = new ArrayList<>(
153                 installedShortcutsSize);
154 
155         for (int i = 0; i < installedShortcutsSize; ++i) {
156             final AccessibilityShortcutInfo info = installedShortcuts.get(i);
157             final ActivityInfo activityInfo = info.getActivityInfo();
158             final ComponentName componentName = info.getComponentName();
159 
160             final String key = componentName.flattenToString();
161             final CharSequence title = activityInfo.loadLabel(mPm);
162             final String summary = info.loadSummary(mPm);
163             final String fragment =
164                     LaunchAccessibilityActivityPreferenceFragment.class.getName();
165 
166             Drawable icon = activityInfo.loadIcon(mPm);
167             if (activityInfo.getIconResource() == 0) {
168                 icon = ContextCompat.getDrawable(mContext, R.drawable.ic_accessibility_generic);
169             }
170 
171             final RestrictedPreference preference = createRestrictedPreference(key, title,
172                     summary, icon, fragment, componentName.getPackageName(),
173                     activityInfo.applicationInfo.uid);
174             final boolean serviceEnabled = enabledServices.contains(componentName);
175 
176             setRestrictedPreferenceEnabled(preference, permittedServices, serviceEnabled);
177 
178             final String prefKey = preference.getKey();
179             final CharSequence intro = info.loadIntro(mPm);
180             final String description = info.loadDescription(mPm);
181             final int imageRes = info.getAnimatedImageRes();
182             final String htmlDescription = info.loadHtmlDescription(mPm);
183             final String settingsClassName = info.getSettingsActivityName();
184             final String tileServiceClassName = info.getTileServiceName();
185             final int metricsCategory = FeatureFactory.getFactory(mContext)
186                     .getAccessibilityMetricsFeatureProvider()
187                     .getDownloadedFeatureMetricsCategory(componentName);
188 
189             putBasicExtras(preference, prefKey, title, intro, description, imageRes,
190                     htmlDescription, componentName, metricsCategory);
191             putSettingsExtras(preference, componentName.getPackageName(), settingsClassName);
192             putTileServiceExtras(preference, componentName.getPackageName(),
193                     tileServiceClassName);
194 
195             preferenceList.add(preference);
196         }
197         return preferenceList;
198     }
199 
getAccessibilityServiceFragmentTypeName(AccessibilityServiceInfo info)200     private String getAccessibilityServiceFragmentTypeName(AccessibilityServiceInfo info) {
201         final int type = AccessibilityUtil.getAccessibilityServiceFragmentType(info);
202         switch (type) {
203             case AccessibilityUtil.AccessibilityServiceFragmentType.VOLUME_SHORTCUT_TOGGLE:
204                 return VolumeShortcutToggleAccessibilityServicePreferenceFragment.class.getName();
205             case AccessibilityUtil.AccessibilityServiceFragmentType.INVISIBLE_TOGGLE:
206                 return InvisibleToggleAccessibilityServicePreferenceFragment.class.getName();
207             case AccessibilityUtil.AccessibilityServiceFragmentType.TOGGLE:
208                 return ToggleAccessibilityServicePreferenceFragment.class.getName();
209             default:
210                 throw new IllegalArgumentException(
211                         "Unsupported accessibility fragment type " + type);
212         }
213     }
214 
createRestrictedPreference(String key, CharSequence title, CharSequence summary, Drawable icon, String fragment, String packageName, int uid)215     private RestrictedPreference createRestrictedPreference(String key, CharSequence title,
216             CharSequence summary, Drawable icon, String fragment, String packageName, int uid) {
217         final RestrictedPreference preference = new RestrictedPreference(mContext, packageName,
218                 uid);
219 
220         preference.setKey(key);
221         preference.setTitle(title);
222         preference.setSummary(summary);
223         preference.setIcon(Utils.getAdaptiveIcon(mContext, icon, Color.WHITE));
224         preference.setFragment(fragment);
225         preference.setIconSize(ICON_SIZE_MEDIUM);
226         preference.setPersistent(false); // Disable SharedPreferences.
227         preference.setOrder(FIRST_PREFERENCE_IN_CATEGORY_INDEX);
228 
229         return preference;
230     }
231 
setRestrictedPreferenceEnabled(RestrictedPreference preference, final List<String> permittedServices, boolean serviceEnabled)232     private void setRestrictedPreferenceEnabled(RestrictedPreference preference,
233             final List<String> permittedServices, boolean serviceEnabled) {
234         // permittedServices null means all accessibility services are allowed.
235         boolean serviceAllowed = permittedServices == null || permittedServices.contains(
236                 preference.getPackageName());
237         boolean appOpsAllowed;
238         if (serviceAllowed) {
239             try {
240                 final int mode = mAppOps.noteOpNoThrow(
241                         AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS,
242                         preference.getUid(), preference.getPackageName());
243                 final boolean ecmEnabled = mContext.getResources().getBoolean(
244                         com.android.internal.R.bool.config_enhancedConfirmationModeEnabled);
245                 appOpsAllowed = !ecmEnabled || mode == AppOpsManager.MODE_ALLOWED;
246                 serviceAllowed = appOpsAllowed;
247             } catch (Exception e) {
248                 // Allow service in case if app ops is not available in testing.
249                 appOpsAllowed = true;
250             }
251         } else {
252             appOpsAllowed = false;
253         }
254         if (serviceAllowed || serviceEnabled) {
255             preference.setEnabled(true);
256         } else {
257             // Disable accessibility service that are not permitted.
258             final RestrictedLockUtils.EnforcedAdmin admin =
259                     RestrictedLockUtilsInternal.checkIfAccessibilityServiceDisallowed(
260                             mContext, preference.getPackageName(), UserHandle.myUserId());
261 
262             if (admin != null) {
263                 preference.setDisabledByAdmin(admin);
264             } else if (!appOpsAllowed) {
265                 preference.setDisabledByAppOps(true);
266             } else {
267                 preference.setEnabled(false);
268             }
269         }
270     }
271 
272     /** 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)273     private void putBasicExtras(RestrictedPreference preference, String prefKey,
274             CharSequence title, CharSequence intro, CharSequence summary, int imageRes,
275             String htmlDescription, ComponentName componentName, int metricsCategory) {
276         final Bundle extras = preference.getExtras();
277         extras.putString(AccessibilitySettings.EXTRA_PREFERENCE_KEY, prefKey);
278         extras.putCharSequence(AccessibilitySettings.EXTRA_TITLE, title);
279         extras.putCharSequence(AccessibilitySettings.EXTRA_INTRO, intro);
280         extras.putCharSequence(AccessibilitySettings.EXTRA_SUMMARY, summary);
281         extras.putParcelable(AccessibilitySettings.EXTRA_COMPONENT_NAME, componentName);
282         extras.putInt(AccessibilitySettings.EXTRA_ANIMATED_IMAGE_RES, imageRes);
283         extras.putString(AccessibilitySettings.EXTRA_HTML_DESCRIPTION, htmlDescription);
284         extras.putInt(AccessibilitySettings.EXTRA_METRICS_CATEGORY, metricsCategory);
285     }
286 
287     /**
288      * Puts the service extras into {@link RestrictedPreference}'s getExtras().
289      *
290      * <p><b>Note:</b> Called by {@link AccessibilityServiceInfo}.</p>
291      *
292      * @param preference The preference we are configuring.
293      * @param resolveInfo The service resolve info.
294      * @param serviceEnabled Whether the accessibility service is enabled.
295      */
putServiceExtras(RestrictedPreference preference, ResolveInfo resolveInfo, Boolean serviceEnabled)296     private void putServiceExtras(RestrictedPreference preference, ResolveInfo resolveInfo,
297             Boolean serviceEnabled) {
298         final Bundle extras = preference.getExtras();
299 
300         extras.putParcelable(AccessibilitySettings.EXTRA_RESOLVE_INFO, resolveInfo);
301         extras.putBoolean(AccessibilitySettings.EXTRA_CHECKED, serviceEnabled);
302     }
303 
304     /**
305      * Puts the settings extras into {@link RestrictedPreference}'s getExtras().
306      *
307      * <p><b>Note:</b> Called when settings UI is needed.</p>
308      *
309      * @param preference The preference we are configuring.
310      * @param packageName Package of accessibility feature.
311      * @param settingsClassName The component name of an activity that allows the user to modify
312      *                          the settings for this accessibility feature.
313      */
putSettingsExtras(RestrictedPreference preference, String packageName, String settingsClassName)314     private void putSettingsExtras(RestrictedPreference preference, String packageName,
315             String settingsClassName) {
316         final Bundle extras = preference.getExtras();
317 
318         if (!TextUtils.isEmpty(settingsClassName)) {
319             extras.putString(AccessibilitySettings.EXTRA_SETTINGS_TITLE,
320                     mContext.getText(R.string.accessibility_menu_item_settings).toString());
321             extras.putString(AccessibilitySettings.EXTRA_SETTINGS_COMPONENT_NAME,
322                     new ComponentName(packageName, settingsClassName).flattenToString());
323         }
324     }
325 
326     /**
327      * Puts the information about a particular application
328      * {@link android.service.quicksettings.TileService} into {@link RestrictedPreference}'s
329      * getExtras().
330      *
331      * <p><b>Note:</b> Called when a tooltip of
332      * {@link android.service.quicksettings.TileService} is needed.</p>
333      *
334      * @param preference The preference we are configuring.
335      * @param packageName Package of accessibility feature.
336      * @param tileServiceClassName The component name of tileService is associated with this
337      *                             accessibility feature.
338      */
putTileServiceExtras(RestrictedPreference preference, String packageName, String tileServiceClassName)339     private void putTileServiceExtras(RestrictedPreference preference, String packageName,
340             String tileServiceClassName) {
341         final Bundle extras = preference.getExtras();
342         if (!TextUtils.isEmpty(tileServiceClassName)) {
343             extras.putString(AccessibilitySettings.EXTRA_TILE_SERVICE_COMPONENT_NAME,
344                     new ComponentName(packageName, tileServiceClassName).flattenToString());
345         }
346     }
347 }
348