1 /*
2  * Copyright (C) 2011 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 androidx.core.view.accessibility;
18 
19 import android.accessibilityservice.AccessibilityServiceInfo;
20 import android.os.Build;
21 import android.view.accessibility.AccessibilityManager;
22 
23 import androidx.annotation.RequiresApi;
24 
25 import org.jspecify.annotations.NonNull;
26 
27 import java.util.List;
28 
29 /**
30  * Helper for accessing features in {@link AccessibilityManager}.
31  */
32 public final class AccessibilityManagerCompat {
33     /**
34      * Registers an {@link AccessibilityManager.AccessibilityStateChangeListener} for changes in
35      * the global accessibility state of the system.
36      *
37      * @param manager The accessibility manager.
38      * @param listener The listener.
39      * @return True if successfully registered.
40      *
41      * @deprecated Use {@link AccessibilityManager#addAccessibilityStateChangeListener(
42      *             AccessibilityManager.AccessibilityStateChangeListener)} directly.
43      */
44     @SuppressWarnings("deprecation")
45     @Deprecated
addAccessibilityStateChangeListener(AccessibilityManager manager, AccessibilityStateChangeListener listener)46     public static boolean addAccessibilityStateChangeListener(AccessibilityManager manager,
47             AccessibilityStateChangeListener listener) {
48         if (listener == null) {
49             return false;
50         }
51         return manager.addAccessibilityStateChangeListener(
52                 new AccessibilityStateChangeListenerWrapper(listener));
53     }
54 
55     /**
56      * Unregisters an {@link AccessibilityManager.AccessibilityStateChangeListener}.
57      *
58      * @param manager The accessibility manager.
59      * @param listener The listener.
60      * @return True if successfully unregistered.
61      *
62      * @deprecated Use {@link AccessibilityManager#removeAccessibilityStateChangeListener(
63      *             AccessibilityManager.AccessibilityStateChangeListener)} directly.
64      */
65     @SuppressWarnings("deprecation")
66     @Deprecated
removeAccessibilityStateChangeListener(AccessibilityManager manager, AccessibilityStateChangeListener listener)67     public static boolean removeAccessibilityStateChangeListener(AccessibilityManager manager,
68             AccessibilityStateChangeListener listener) {
69         if (listener == null) {
70             return false;
71         }
72         return manager.removeAccessibilityStateChangeListener(
73                 new AccessibilityStateChangeListenerWrapper(listener));
74     }
75 
76     @SuppressWarnings("deprecation")
77     private static class AccessibilityStateChangeListenerWrapper
78             implements AccessibilityManager.AccessibilityStateChangeListener {
79         AccessibilityStateChangeListener mListener;
80 
AccessibilityStateChangeListenerWrapper( @onNull AccessibilityStateChangeListener listener)81         AccessibilityStateChangeListenerWrapper(
82                 @NonNull AccessibilityStateChangeListener listener) {
83             mListener = listener;
84         }
85 
86         @Override
hashCode()87         public int hashCode() {
88             return mListener.hashCode();
89         }
90 
91         @Override
equals(Object o)92         public boolean equals(Object o) {
93             if (this == o) {
94                 return true;
95             }
96             if (!(o instanceof AccessibilityStateChangeListenerWrapper)) {
97                 return false;
98             }
99             AccessibilityStateChangeListenerWrapper other =
100                     (AccessibilityStateChangeListenerWrapper) o;
101             return mListener.equals(other.mListener);
102         }
103 
104         @Override
onAccessibilityStateChanged(boolean enabled)105         public void onAccessibilityStateChanged(boolean enabled) {
106             mListener.onAccessibilityStateChanged(enabled);
107         }
108     }
109 
110     /**
111      * Returns the {@link AccessibilityServiceInfo}s of the installed accessibility services.
112      *
113      * @param manager The accessibility manager.
114      * @return An unmodifiable list with {@link AccessibilityServiceInfo}s.
115      *
116      * @deprecated Use {@link AccessibilityManager#getInstalledAccessibilityServiceList()} directly.
117      */
118     @androidx.annotation.ReplaceWith(expression = "manager.getInstalledAccessibilityServiceList()")
119     @Deprecated
getInstalledAccessibilityServiceList( AccessibilityManager manager)120     public static List<AccessibilityServiceInfo> getInstalledAccessibilityServiceList(
121             AccessibilityManager manager) {
122         return manager.getInstalledAccessibilityServiceList();
123     }
124 
125     /**
126      * Returns the {@link AccessibilityServiceInfo}s of the enabled accessibility services
127      * for a given feedback type.
128      *
129      * @param manager The accessibility manager.
130      * @param feedbackTypeFlags The feedback type flags.
131      * @return An unmodifiable list with {@link AccessibilityServiceInfo}s.
132      *
133      * @see AccessibilityServiceInfo#FEEDBACK_AUDIBLE
134      * @see AccessibilityServiceInfo#FEEDBACK_GENERIC
135      * @see AccessibilityServiceInfo#FEEDBACK_HAPTIC
136      * @see AccessibilityServiceInfo#FEEDBACK_SPOKEN
137      * @see AccessibilityServiceInfo#FEEDBACK_VISUAL
138      *
139      * @deprecated Use {@link AccessibilityManager#getEnabledAccessibilityServiceList(int)}
140      * directly.
141      */
142     @androidx.annotation.ReplaceWith(expression = "manager.getEnabledAccessibilityServiceList(feedbackTypeFlags)")
143     @Deprecated
getEnabledAccessibilityServiceList( AccessibilityManager manager, int feedbackTypeFlags)144     public static List<AccessibilityServiceInfo> getEnabledAccessibilityServiceList(
145             AccessibilityManager manager, int feedbackTypeFlags) {
146         return manager.getEnabledAccessibilityServiceList(feedbackTypeFlags);
147     }
148 
149     /**
150      * Returns if the touch exploration in the system is enabled.
151      *
152      * @param manager The accessibility manager.
153      * @return True if touch exploration is enabled, false otherwise.
154      *
155      * @deprecated Use {@link AccessibilityManager#isTouchExplorationEnabled()} directly.
156      */
157     @androidx.annotation.ReplaceWith(expression = "manager.isTouchExplorationEnabled()")
158     @Deprecated
isTouchExplorationEnabled(AccessibilityManager manager)159     public static boolean isTouchExplorationEnabled(AccessibilityManager manager) {
160         return manager.isTouchExplorationEnabled();
161     }
162 
163     /**
164      * Registers a {@link TouchExplorationStateChangeListener} for changes in
165      * the global touch exploration state of the system.
166      *
167      * @param manager AccessibilityManager for which to add the listener.
168      * @param listener The listener.
169      * @return True if successfully registered.
170      * @deprecated Call {@link AccessibilityManager#addTouchExplorationStateChangeListener(AccessibilityManager.TouchExplorationStateChangeListener)} directly.
171      */
172     @Deprecated
173     @androidx.annotation.ReplaceWith(expression = "manager.addTouchExplorationStateChangeListener(listener)")
addTouchExplorationStateChangeListener( @onNull AccessibilityManager manager, @NonNull TouchExplorationStateChangeListener listener)174     public static boolean addTouchExplorationStateChangeListener(
175             @NonNull AccessibilityManager manager,
176             @NonNull TouchExplorationStateChangeListener listener) {
177         return manager.addTouchExplorationStateChangeListener(
178                 new TouchExplorationStateChangeListenerWrapper(listener));
179     }
180 
181     /**
182      * Unregisters a {@link TouchExplorationStateChangeListener}.
183      *
184      * @param manager AccessibilityManager for which to remove the listener.
185      * @param listener The listener.
186      * @return True if successfully unregistered.
187      * @deprecated Call {@link AccessibilityManager#removeTouchExplorationStateChangeListener(AccessibilityManager.TouchExplorationStateChangeListener)} directly.
188      */
189     @Deprecated
190     @androidx.annotation.ReplaceWith(expression = "manager.removeTouchExplorationStateChangeListener(listener)")
removeTouchExplorationStateChangeListener( @onNull AccessibilityManager manager, @NonNull TouchExplorationStateChangeListener listener)191     public static boolean removeTouchExplorationStateChangeListener(
192             @NonNull AccessibilityManager manager,
193             @NonNull TouchExplorationStateChangeListener listener) {
194         return manager.removeTouchExplorationStateChangeListener(
195                 new TouchExplorationStateChangeListenerWrapper(listener));
196     }
197 
198 
199     /**
200      * Whether the current accessibility request comes from an
201      * {@link android.accessibilityservice.AccessibilityService} with the
202      * {@link AccessibilityServiceInfo#isAccessibilityTool}
203      * property set to true.
204      *
205      * <p>
206      * You can use this method inside {@link android.view.accessibility.AccessibilityNodeProvider}
207      * to decide how to populate your nodes.
208      * </p>
209      *
210      * <p>
211      * <strong>Note:</strong> The return value is valid only when an
212      * {@link android.view.accessibility.AccessibilityNodeInfo} request is in progress, can
213      * change from one request to another, and has no meaning when a request is not in progress.
214      * </p>
215      *
216      * @return True if the current request is from a tool that sets isAccessibilityTool.
217      */
isRequestFromAccessibilityTool(@onNull AccessibilityManager manager)218     public static boolean isRequestFromAccessibilityTool(@NonNull AccessibilityManager manager) {
219         if (Build.VERSION.SDK_INT >= 34) {
220             return Api34Impl.isRequestFromAccessibilityTool(manager);
221         } else {
222             // To preserve behavior, assume every service isAccessibilityTool if the system does
223             // not support this check.
224             return true;
225         }
226     }
227 
228     private static final class TouchExplorationStateChangeListenerWrapper
229             implements AccessibilityManager.TouchExplorationStateChangeListener {
230         final TouchExplorationStateChangeListener mListener;
231 
TouchExplorationStateChangeListenerWrapper( @onNull TouchExplorationStateChangeListener listener)232         TouchExplorationStateChangeListenerWrapper(
233                 @NonNull TouchExplorationStateChangeListener listener) {
234             mListener = listener;
235         }
236 
237         @Override
hashCode()238         public int hashCode() {
239             return mListener.hashCode();
240         }
241 
242         @Override
equals(Object o)243         public boolean equals(Object o) {
244             if (this == o) {
245                 return true;
246             }
247             if (!(o instanceof TouchExplorationStateChangeListenerWrapper)) {
248                 return false;
249             }
250             TouchExplorationStateChangeListenerWrapper other =
251                     (TouchExplorationStateChangeListenerWrapper) o;
252             return mListener.equals(other.mListener);
253         }
254 
255         @Override
onTouchExplorationStateChanged(boolean enabled)256         public void onTouchExplorationStateChanged(boolean enabled) {
257             mListener.onTouchExplorationStateChanged(enabled);
258         }
259     }
260 
261     /**
262      * Listener for the accessibility state.
263      *
264      * @deprecated Use {@link AccessibilityManager.AccessibilityStateChangeListener} directly
265      * instead of this listener.
266      */
267     @SuppressWarnings("deprecation")
268     @Deprecated
269     public static abstract class AccessibilityStateChangeListenerCompat
270             implements AccessibilityStateChangeListener {
271     }
272 
273     /**
274      * Listener for the accessibility state.
275      *
276      * @deprecated Use {@link AccessibilityManager.AccessibilityStateChangeListener} directly
277      * instead of this listener.
278      */
279     @Deprecated
280     public interface AccessibilityStateChangeListener {
281         /**
282          * Called back on change in the accessibility state.
283          *
284          * @param enabled Whether accessibility is enabled.
285          *
286          * @deprecated Use {@link AccessibilityManager.AccessibilityStateChangeListener} directly.
287          */
288         @Deprecated
onAccessibilityStateChanged(boolean enabled)289         void onAccessibilityStateChanged(boolean enabled);
290     }
291 
292     /**
293      * Listener for the system touch exploration state. To listen for changes to
294      * the touch exploration state on the device, implement this interface and
295      * register it with the system by calling
296      * {@link #addTouchExplorationStateChangeListener}.
297      */
298     public interface TouchExplorationStateChangeListener {
299         /**
300          * Called when the touch exploration enabled state changes.
301          *
302          * @param enabled Whether touch exploration is enabled.
303          */
onTouchExplorationStateChanged(boolean enabled)304         void onTouchExplorationStateChanged(boolean enabled);
305     }
306 
AccessibilityManagerCompat()307     private AccessibilityManagerCompat() {
308     }
309 
310     @RequiresApi(34)
311     static class Api34Impl {
Api34Impl()312         private Api34Impl() {
313             // This class is not instantiable.
314         }
315 
isRequestFromAccessibilityTool(AccessibilityManager accessibilityManager)316         static boolean isRequestFromAccessibilityTool(AccessibilityManager accessibilityManager) {
317             return accessibilityManager.isRequestFromAccessibilityTool();
318         }
319     }
320 }
321