blob: da9c001ea58fb95ddd9132db3fcb7041b1c595c8 [file] [log] [blame]
Svetoslav Ganov7befb7d2012-09-27 16:49:23 -07001/*
2 * Copyright (C) 2012 Google Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * 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, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16
Jorim Jaggib10e33f2015-02-04 21:57:40 +010017package com.android.server.policy;
Svetoslav Ganov7befb7d2012-09-27 16:49:23 -070018
19import android.accessibilityservice.AccessibilityServiceInfo;
20import android.app.ActivityManager;
21import android.content.ComponentName;
22import android.content.ContentResolver;
23import android.content.Context;
24import android.content.pm.ServiceInfo;
25import android.media.AudioManager;
26import android.media.Ringtone;
27import android.media.RingtoneManager;
28import android.os.Handler;
29import android.os.Message;
30import android.os.RemoteException;
31import android.os.ServiceManager;
32import android.os.UserManager;
33import android.provider.Settings;
34import android.speech.tts.TextToSpeech;
35import android.util.MathUtils;
36import android.view.IWindowManager;
37import android.view.MotionEvent;
38import android.view.accessibility.AccessibilityManager;
39import android.view.accessibility.IAccessibilityManager;
40
41import com.android.internal.R;
42
Svetoslav Ganovd2883372012-10-03 16:30:31 -070043import java.util.ArrayList;
Svetoslav Ganov7befb7d2012-09-27 16:49:23 -070044import java.util.Iterator;
45import java.util.List;
46
47public class EnableAccessibilityController {
48
49 private static final int SPEAK_WARNING_DELAY_MILLIS = 2000;
50 private static final int ENABLE_ACCESSIBILITY_DELAY_MILLIS = 6000;
51
52 public static final int MESSAGE_SPEAK_WARNING = 1;
53 public static final int MESSAGE_SPEAK_ENABLE_CANCELED = 2;
54 public static final int MESSAGE_ENABLE_ACCESSIBILITY = 3;
55
56 private final Handler mHandler = new Handler() {
57 @Override
58 public void handleMessage(Message message) {
59 switch (message.what) {
60 case MESSAGE_SPEAK_WARNING: {
61 String text = mContext.getString(R.string.continue_to_enable_accessibility);
62 mTts.speak(text, TextToSpeech.QUEUE_FLUSH, null);
63 } break;
64 case MESSAGE_SPEAK_ENABLE_CANCELED: {
65 String text = mContext.getString(R.string.enable_accessibility_canceled);
66 mTts.speak(text, TextToSpeech.QUEUE_FLUSH, null);
67 } break;
68 case MESSAGE_ENABLE_ACCESSIBILITY: {
69 enableAccessibility();
70 mTone.play();
71 mTts.speak(mContext.getString(R.string.accessibility_enabled),
72 TextToSpeech.QUEUE_FLUSH, null);
73 } break;
74 }
75 }
76 };
77
78 private final IWindowManager mWindowManager = IWindowManager.Stub.asInterface(
79 ServiceManager.getService("window"));
80
81 private final IAccessibilityManager mAccessibilityManager = IAccessibilityManager
82 .Stub.asInterface(ServiceManager.getService("accessibility"));
83
84
85 private final Context mContext;
Svetoslavdc6d1a92014-08-28 18:40:57 -070086 private final Runnable mOnAccessibilityEnabledCallback;
Svetoslav Ganov7befb7d2012-09-27 16:49:23 -070087 private final UserManager mUserManager;
88 private final TextToSpeech mTts;
89 private final Ringtone mTone;
90
91 private final float mTouchSlop;
92
93 private boolean mDestroyed;
94 private boolean mCanceled;
95
96 private float mFirstPointerDownX;
97 private float mFirstPointerDownY;
98 private float mSecondPointerDownX;
99 private float mSecondPointerDownY;
100
Svetoslavdc6d1a92014-08-28 18:40:57 -0700101 public EnableAccessibilityController(Context context, Runnable onAccessibilityEnabledCallback) {
Svetoslav Ganov7befb7d2012-09-27 16:49:23 -0700102 mContext = context;
Svetoslavdc6d1a92014-08-28 18:40:57 -0700103 mOnAccessibilityEnabledCallback = onAccessibilityEnabledCallback;
Svetoslav Ganov7befb7d2012-09-27 16:49:23 -0700104 mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
105 mTts = new TextToSpeech(context, new TextToSpeech.OnInitListener() {
106 @Override
107 public void onInit(int status) {
108 if (mDestroyed) {
109 mTts.shutdown();
110 }
111 }
112 });
113 mTone = RingtoneManager.getRingtone(context, Settings.System.DEFAULT_NOTIFICATION_URI);
114 mTone.setStreamType(AudioManager.STREAM_MUSIC);
115 mTouchSlop = context.getResources().getDimensionPixelSize(
116 R.dimen.accessibility_touch_slop);
117 }
118
119 public static boolean canEnableAccessibilityViaGesture(Context context) {
120 AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(context);
121 // Accessibility is enabled and there is an enabled speaking
122 // accessibility service, then we have nothing to do.
123 if (accessibilityManager.isEnabled()
124 && !accessibilityManager.getEnabledAccessibilityServiceList(
125 AccessibilityServiceInfo.FEEDBACK_SPOKEN).isEmpty()) {
126 return false;
127 }
128 // If the global gesture is enabled and there is a speaking service
129 // installed we are good to go, otherwise there is nothing to do.
130 return Settings.Global.getInt(context.getContentResolver(),
131 Settings.Global.ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED, 0) == 1
132 && !getInstalledSpeakingAccessibilityServices(context).isEmpty();
133 }
134
135 private static List<AccessibilityServiceInfo> getInstalledSpeakingAccessibilityServices(
136 Context context) {
Svetoslav Ganovd2883372012-10-03 16:30:31 -0700137 List<AccessibilityServiceInfo> services = new ArrayList<AccessibilityServiceInfo>();
138 services.addAll(AccessibilityManager.getInstance(context)
139 .getInstalledAccessibilityServiceList());
Svetoslav Ganov7befb7d2012-09-27 16:49:23 -0700140 Iterator<AccessibilityServiceInfo> iterator = services.iterator();
141 while (iterator.hasNext()) {
142 AccessibilityServiceInfo service = iterator.next();
143 if ((service.feedbackType & AccessibilityServiceInfo.FEEDBACK_SPOKEN) == 0) {
144 iterator.remove();
145 }
146 }
147 return services;
148 }
149
150 public void onDestroy() {
151 mDestroyed = true;
152 }
153
154 public boolean onInterceptTouchEvent(MotionEvent event) {
155 if (event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN
156 && event.getPointerCount() == 2) {
157 mFirstPointerDownX = event.getX(0);
158 mFirstPointerDownY = event.getY(0);
159 mSecondPointerDownX = event.getX(1);
160 mSecondPointerDownY = event.getY(1);
161 mHandler.sendEmptyMessageDelayed(MESSAGE_SPEAK_WARNING,
162 SPEAK_WARNING_DELAY_MILLIS);
163 mHandler.sendEmptyMessageDelayed(MESSAGE_ENABLE_ACCESSIBILITY,
164 ENABLE_ACCESSIBILITY_DELAY_MILLIS);
165 return true;
166 }
167 return false;
168 }
169
170 public boolean onTouchEvent(MotionEvent event) {
171 final int pointerCount = event.getPointerCount();
172 final int action = event.getActionMasked();
173 if (mCanceled) {
174 if (action == MotionEvent.ACTION_UP) {
175 mCanceled = false;
176 }
177 return true;
178 }
179 switch (action) {
180 case MotionEvent.ACTION_POINTER_DOWN: {
181 if (pointerCount > 2) {
182 cancel();
183 }
184 } break;
185 case MotionEvent.ACTION_MOVE: {
186 final float firstPointerMove = MathUtils.dist(event.getX(0),
187 event.getY(0), mFirstPointerDownX, mFirstPointerDownY);
188 if (Math.abs(firstPointerMove) > mTouchSlop) {
189 cancel();
190 }
191 final float secondPointerMove = MathUtils.dist(event.getX(1),
192 event.getY(1), mSecondPointerDownX, mSecondPointerDownY);
193 if (Math.abs(secondPointerMove) > mTouchSlop) {
194 cancel();
195 }
196 } break;
197 case MotionEvent.ACTION_POINTER_UP:
198 case MotionEvent.ACTION_CANCEL: {
199 cancel();
200 } break;
201 }
202 return true;
203 }
204
205 private void cancel() {
206 mCanceled = true;
207 if (mHandler.hasMessages(MESSAGE_SPEAK_WARNING)) {
208 mHandler.removeMessages(MESSAGE_SPEAK_WARNING);
209 } else if (mHandler.hasMessages(MESSAGE_ENABLE_ACCESSIBILITY)) {
210 mHandler.sendEmptyMessage(MESSAGE_SPEAK_ENABLE_CANCELED);
211 }
212 mHandler.removeMessages(MESSAGE_ENABLE_ACCESSIBILITY);
213 }
214
215 private void enableAccessibility() {
216 List<AccessibilityServiceInfo> services = getInstalledSpeakingAccessibilityServices(
217 mContext);
218 if (services.isEmpty()) {
219 return;
220 }
221 boolean keyguardLocked = false;
222 try {
223 keyguardLocked = mWindowManager.isKeyguardLocked();
224 } catch (RemoteException re) {
225 /* ignore */
226 }
227
228 final boolean hasMoreThanOneUser = mUserManager.getUsers().size() > 1;
229
230 AccessibilityServiceInfo service = services.get(0);
231 boolean enableTouchExploration = (service.flags
232 & AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0;
233 // Try to find a service supporting explore by touch.
234 if (!enableTouchExploration) {
235 final int serviceCount = services.size();
236 for (int i = 1; i < serviceCount; i++) {
237 AccessibilityServiceInfo candidate = services.get(i);
238 if ((candidate.flags & AccessibilityServiceInfo
239 .FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0) {
240 enableTouchExploration = true;
241 service = candidate;
242 break;
243 }
244 }
245 }
246
247 ServiceInfo serviceInfo = service.getResolveInfo().serviceInfo;
248 ComponentName componentName = new ComponentName(serviceInfo.packageName, serviceInfo.name);
249 if (!keyguardLocked || !hasMoreThanOneUser) {
250 final int userId = ActivityManager.getCurrentUser();
251 String enabledServiceString = componentName.flattenToString();
252 ContentResolver resolver = mContext.getContentResolver();
253 // Enable one speaking accessibility service.
254 Settings.Secure.putStringForUser(resolver,
255 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
256 enabledServiceString, userId);
257 // Allow the services we just enabled to toggle touch exploration.
258 Settings.Secure.putStringForUser(resolver,
259 Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
260 enabledServiceString, userId);
261 // Enable touch exploration.
262 if (enableTouchExploration) {
263 Settings.Secure.putIntForUser(resolver, Settings.Secure.TOUCH_EXPLORATION_ENABLED,
264 1, userId);
265 }
266 // Enable accessibility script injection (AndroidVox) for web content.
267 Settings.Secure.putIntForUser(resolver, Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION,
268 1, userId);
269 // Turn on accessibility mode last.
270 Settings.Secure.putIntForUser(resolver, Settings.Secure.ACCESSIBILITY_ENABLED,
271 1, userId);
272 } else if (keyguardLocked) {
273 try {
274 mAccessibilityManager.temporaryEnableAccessibilityStateUntilKeyguardRemoved(
275 componentName, enableTouchExploration);
276 } catch (RemoteException re) {
277 /* ignore */
278 }
279 }
Svetoslavdc6d1a92014-08-28 18:40:57 -0700280
281 mOnAccessibilityEnabledCallback.run();
Svetoslav Ganov7befb7d2012-09-27 16:49:23 -0700282 }
283}