• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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.internal.accessibility.util;
18 
19 import static com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType;
20 import static com.android.internal.accessibility.common.ShortcutConstants.SERVICES_SEPARATOR;
21 
22 import android.accessibilityservice.AccessibilityServiceInfo;
23 import android.annotation.IntDef;
24 import android.annotation.NonNull;
25 import android.content.ComponentName;
26 import android.content.Context;
27 import android.os.Build;
28 import android.os.UserHandle;
29 import android.provider.Settings;
30 import android.text.ParcelableSpan;
31 import android.text.Spanned;
32 import android.text.TextUtils;
33 import android.util.ArraySet;
34 import android.view.accessibility.AccessibilityManager;
35 
36 import libcore.util.EmptyArray;
37 
38 import java.lang.annotation.Retention;
39 import java.lang.annotation.RetentionPolicy;
40 import java.util.Collections;
41 import java.util.HashSet;
42 import java.util.List;
43 import java.util.Set;
44 
45 /**
46  * Collection of utilities for accessibility service.
47  */
48 public final class AccessibilityUtils {
AccessibilityUtils()49     private AccessibilityUtils() {
50     }
51 
52     /** @hide */
53     @IntDef(value = {
54             NONE,
55             TEXT,
56             PARCELABLE_SPAN
57     })
58     @Retention(RetentionPolicy.SOURCE)
59     public @interface A11yTextChangeType {
60     }
61 
62     /** Specifies no content has been changed for accessibility. */
63     public static final int NONE = 0;
64     /** Specifies some readable sequence has been changed. */
65     public static final int TEXT = 1;
66     /** Specifies some parcelable spans has been changed. */
67     public static final int PARCELABLE_SPAN = 2;
68 
69     /**
70      * Returns the set of enabled accessibility services for userId. If there are no
71      * services, it returns the unmodifiable {@link Collections#emptySet()}.
72      */
getEnabledServicesFromSettings(Context context, int userId)73     public static Set<ComponentName> getEnabledServicesFromSettings(Context context, int userId) {
74         final String enabledServicesSetting = Settings.Secure.getStringForUser(
75                 context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
76                 userId);
77         if (TextUtils.isEmpty(enabledServicesSetting)) {
78             return Collections.emptySet();
79         }
80 
81         final Set<ComponentName> enabledServices = new HashSet<>();
82         final TextUtils.StringSplitter colonSplitter =
83                 new TextUtils.SimpleStringSplitter(SERVICES_SEPARATOR);
84         colonSplitter.setString(enabledServicesSetting);
85 
86         for (String componentNameString : colonSplitter) {
87             final ComponentName enabledService = ComponentName.unflattenFromString(
88                     componentNameString);
89             if (enabledService != null) {
90                 enabledServices.add(enabledService);
91             }
92         }
93 
94         return enabledServices;
95     }
96 
97     /**
98      * Changes an accessibility component's state.
99      */
setAccessibilityServiceState(Context context, ComponentName componentName, boolean enabled)100     public static void setAccessibilityServiceState(Context context, ComponentName componentName,
101             boolean enabled) {
102         setAccessibilityServiceState(context, componentName, enabled, UserHandle.myUserId());
103     }
104 
105     /**
106      * Changes an accessibility component's state for {@param userId}.
107      */
setAccessibilityServiceState(Context context, ComponentName componentName, boolean enabled, int userId)108     public static void setAccessibilityServiceState(Context context, ComponentName componentName,
109             boolean enabled, int userId) {
110         Set<ComponentName> enabledServices = getEnabledServicesFromSettings(
111                 context, userId);
112 
113         if (enabledServices.isEmpty()) {
114             enabledServices = new ArraySet<>(/* capacity= */ 1);
115         }
116 
117         if (enabled) {
118             enabledServices.add(componentName);
119         } else {
120             enabledServices.remove(componentName);
121         }
122 
123         final StringBuilder enabledServicesBuilder = new StringBuilder();
124         for (ComponentName enabledService : enabledServices) {
125             enabledServicesBuilder.append(enabledService.flattenToString());
126             enabledServicesBuilder.append(
127                     SERVICES_SEPARATOR);
128         }
129 
130         final int enabledServicesBuilderLength = enabledServicesBuilder.length();
131         if (enabledServicesBuilderLength > 0) {
132             enabledServicesBuilder.deleteCharAt(enabledServicesBuilderLength - 1);
133         }
134 
135         Settings.Secure.putStringForUser(context.getContentResolver(),
136                 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
137                 enabledServicesBuilder.toString(), userId);
138     }
139 
140     /**
141      * Gets the corresponding fragment type of a given accessibility service.
142      *
143      * @param accessibilityServiceInfo The accessibilityService's info.
144      * @return int from {@link AccessibilityFragmentType}.
145      */
getAccessibilityServiceFragmentType( @onNull AccessibilityServiceInfo accessibilityServiceInfo)146     public static @AccessibilityFragmentType int getAccessibilityServiceFragmentType(
147             @NonNull AccessibilityServiceInfo accessibilityServiceInfo) {
148         final int targetSdk = accessibilityServiceInfo.getResolveInfo()
149                 .serviceInfo.applicationInfo.targetSdkVersion;
150         final boolean requestA11yButton = (accessibilityServiceInfo.flags
151                 & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
152 
153         if (targetSdk <= Build.VERSION_CODES.Q) {
154             return AccessibilityFragmentType.VOLUME_SHORTCUT_TOGGLE;
155         }
156         return requestA11yButton
157                 ? AccessibilityFragmentType.INVISIBLE_TOGGLE
158                 : AccessibilityFragmentType.TOGGLE;
159     }
160 
161     /**
162      * Returns if a {@code componentId} service is enabled.
163      *
164      * @param context The current context.
165      * @param componentId The component id that need to be checked.
166      * @return {@code true} if a {@code componentId} service is enabled.
167      */
isAccessibilityServiceEnabled(Context context, @NonNull String componentId)168     public static boolean isAccessibilityServiceEnabled(Context context,
169             @NonNull String componentId) {
170         final AccessibilityManager am = (AccessibilityManager) context.getSystemService(
171                 Context.ACCESSIBILITY_SERVICE);
172         final List<AccessibilityServiceInfo> enabledServices =
173                 am.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
174 
175         for (AccessibilityServiceInfo info : enabledServices) {
176             final String id = info.getComponentName().flattenToString();
177             if (id.equals(componentId)) {
178                 return true;
179             }
180         }
181 
182         return false;
183     }
184 
185     /**
186      * Indicates whether the current user has completed setup via the setup wizard.
187      * {@link android.provider.Settings.Secure#USER_SETUP_COMPLETE}
188      *
189      * @return {@code true} if the setup is completed.
190      */
isUserSetupCompleted(Context context)191     public static boolean isUserSetupCompleted(Context context) {
192         return Settings.Secure.getIntForUser(context.getContentResolver(),
193                 Settings.Secure.USER_SETUP_COMPLETE, /* def= */ 0, UserHandle.USER_CURRENT)
194                 != /* false */ 0;
195     }
196 
197     /**
198      * Returns the text change type for accessibility. It only cares about readable sequence changes
199      * or {@link ParcelableSpan} changes which are able to pass via IPC.
200      *
201      * @param before The CharSequence before changing
202      * @param after  The CharSequence after changing
203      * @return Returns {@code TEXT} for readable sequence changes or {@code PARCELABLE_SPAN} for
204      * ParcelableSpan changes. Otherwise, returns {@code NONE}.
205      */
206     @A11yTextChangeType
textOrSpanChanged(CharSequence before, CharSequence after)207     public static int textOrSpanChanged(CharSequence before, CharSequence after) {
208         if (!TextUtils.equals(before, after)) {
209             return TEXT;
210         }
211         if (before instanceof Spanned || after instanceof Spanned) {
212             if (!parcelableSpansEquals(before, after)) {
213                 return PARCELABLE_SPAN;
214             }
215         }
216         return NONE;
217     }
218 
parcelableSpansEquals(CharSequence before, CharSequence after)219     private static boolean parcelableSpansEquals(CharSequence before, CharSequence after) {
220         Object[] spansA = EmptyArray.OBJECT;
221         Object[] spansB = EmptyArray.OBJECT;
222         Spanned a = null;
223         Spanned b = null;
224         if (before instanceof Spanned) {
225             a = (Spanned) before;
226             spansA = a.getSpans(0, a.length(), ParcelableSpan.class);
227         }
228         if (after instanceof Spanned) {
229             b = (Spanned) after;
230             spansB = b.getSpans(0, b.length(), ParcelableSpan.class);
231         }
232         if (spansA.length != spansB.length) {
233             return false;
234         }
235         for (int i = 0; i < spansA.length; ++i) {
236             final Object thisSpan = spansA[i];
237             final Object otherSpan = spansB[i];
238             if ((thisSpan.getClass() != otherSpan.getClass())
239                     || (a.getSpanStart(thisSpan) != b.getSpanStart(otherSpan))
240                     || (a.getSpanEnd(thisSpan) != b.getSpanEnd(otherSpan))
241                     || (a.getSpanFlags(thisSpan) != b.getSpanFlags(otherSpan))) {
242                 return false;
243             }
244         }
245         return true;
246     }
247 }
248