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