blob: a4f6589747cf7e496658d05f4efaab3e9a873ab0 [file] [log] [blame]
cretin45d842eed2015-01-27 11:10:09 -08001/*
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
Maarten Derkse901e0c2017-05-22 11:15:44 +020017package com.fairphone.setupwizard.util;
cretin45d842eed2015-01-27 11:10:09 -080018
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
43import java.util.ArrayList;
44import java.util.Iterator;
45import java.util.List;
46
47public class EnableAccessibilityController {
48
cretin459dd952e2015-05-01 13:14:03 -070049 private static final int SPEAK_WARNING_DELAY_MILLIS = 5000;
50 private static final int ENABLE_ACCESSIBILITY_DELAY_MILLIS = 12000;
cretin45d842eed2015-01-27 11:10:09 -080051
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: {
cretin45d842eed2015-01-27 11:10:09 -080061 } break;
62 case MESSAGE_SPEAK_ENABLE_CANCELED: {
cretin45d842eed2015-01-27 11:10:09 -080063 } break;
64 case MESSAGE_ENABLE_ACCESSIBILITY: {
65 enableAccessibility();
cretin45d842eed2015-01-27 11:10:09 -080066 } break;
67 }
68 }
69 };
70
71 private final IWindowManager mWindowManager = IWindowManager.Stub.asInterface(
72 ServiceManager.getService("window"));
73
74 private final IAccessibilityManager mAccessibilityManager = IAccessibilityManager
75 .Stub.asInterface(ServiceManager.getService("accessibility"));
76
77
78 private final Context mContext;
79 private final UserManager mUserManager;
80 private final TextToSpeech mTts;
81 private final Ringtone mTone;
82
83 private final float mTouchSlop;
84
85 private boolean mDestroyed;
86 private boolean mCanceled;
87
88 private float mFirstPointerDownX;
89 private float mFirstPointerDownY;
90 private float mSecondPointerDownX;
91 private float mSecondPointerDownY;
92
cretin45508cfbb2015-02-04 14:00:52 -080093 private static EnableAccessibilityController sInstance;
94
95 private EnableAccessibilityController(Context context) {
cretin45d842eed2015-01-27 11:10:09 -080096 mContext = context;
97 mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
98 mTts = new TextToSpeech(context, new TextToSpeech.OnInitListener() {
99 @Override
100 public void onInit(int status) {
101 if (mDestroyed) {
102 mTts.shutdown();
103 }
104 }
105 });
106 mTone = RingtoneManager.getRingtone(context, Settings.System.DEFAULT_NOTIFICATION_URI);
107 mTone.setStreamType(AudioManager.STREAM_MUSIC);
108 mTouchSlop = context.getResources().getDimensionPixelSize(
109 R.dimen.accessibility_touch_slop);
110 }
111
cretin45508cfbb2015-02-04 14:00:52 -0800112 public static EnableAccessibilityController getInstance(Context context) {
113 if (sInstance == null) {
114 sInstance = new EnableAccessibilityController(context);
115 }
116 return sInstance;
117 }
118
cretin45d842eed2015-01-27 11:10:09 -0800119 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(
cretin456d63e952015-04-08 12:09:55 -0700125 AccessibilityServiceInfo.FEEDBACK_SPOKEN).isEmpty()) {
cretin45d842eed2015-01-27 11:10:09 -0800126 return false;
127 }
cretin45508cfbb2015-02-04 14:00:52 -0800128
129 // If there is a speaking service
cretin45d842eed2015-01-27 11:10:09 -0800130 // installed we are good to go, otherwise there is nothing to do.
cretin45508cfbb2015-02-04 14:00:52 -0800131 return getInstalledSpeakingAccessibilityServices(context).isEmpty();
cretin45d842eed2015-01-27 11:10:09 -0800132 }
133
134 private static List<AccessibilityServiceInfo> getInstalledSpeakingAccessibilityServices(
135 Context context) {
136 List<AccessibilityServiceInfo> services = new ArrayList<AccessibilityServiceInfo>();
137 services.addAll(AccessibilityManager.getInstance(context)
138 .getInstalledAccessibilityServiceList());
139 Iterator<AccessibilityServiceInfo> iterator = services.iterator();
140 while (iterator.hasNext()) {
141 AccessibilityServiceInfo service = iterator.next();
142 if ((service.feedbackType & AccessibilityServiceInfo.FEEDBACK_SPOKEN) == 0) {
143 iterator.remove();
144 }
145 }
146 return services;
147 }
148
149 public void onDestroy() {
150 mDestroyed = true;
151 }
152
153 public boolean onInterceptTouchEvent(MotionEvent event) {
154 if (event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN
155 && event.getPointerCount() == 2) {
156 mFirstPointerDownX = event.getX(0);
157 mFirstPointerDownY = event.getY(0);
158 mSecondPointerDownX = event.getX(1);
159 mSecondPointerDownY = event.getY(1);
160 mHandler.sendEmptyMessageDelayed(MESSAGE_SPEAK_WARNING,
161 SPEAK_WARNING_DELAY_MILLIS);
162 mHandler.sendEmptyMessageDelayed(MESSAGE_ENABLE_ACCESSIBILITY,
cretin456d63e952015-04-08 12:09:55 -0700163 ENABLE_ACCESSIBILITY_DELAY_MILLIS);
cretin45d842eed2015-01-27 11:10:09 -0800164 return true;
165 }
166 return false;
167 }
168
169 public boolean onTouchEvent(MotionEvent event) {
170 final int pointerCount = event.getPointerCount();
171 final int action = event.getActionMasked();
172 if (mCanceled) {
173 if (action == MotionEvent.ACTION_UP) {
174 mCanceled = false;
175 }
176 return true;
177 }
178 switch (action) {
179 case MotionEvent.ACTION_POINTER_DOWN: {
180 if (pointerCount > 2) {
181 cancel();
182 }
cretin456d63e952015-04-08 12:09:55 -0700183 }
184 break;
cretin45d842eed2015-01-27 11:10:09 -0800185 case MotionEvent.ACTION_MOVE: {
cretin456d63e952015-04-08 12:09:55 -0700186 //We only care about a 2 fingered move
187 if (pointerCount < 2) {
188 cancel();
189 return false;
190 }
cretin45d842eed2015-01-27 11:10:09 -0800191 final float firstPointerMove = MathUtils.dist(event.getX(0),
192 event.getY(0), mFirstPointerDownX, mFirstPointerDownY);
193 if (Math.abs(firstPointerMove) > mTouchSlop) {
194 cancel();
195 }
196 final float secondPointerMove = MathUtils.dist(event.getX(1),
197 event.getY(1), mSecondPointerDownX, mSecondPointerDownY);
198 if (Math.abs(secondPointerMove) > mTouchSlop) {
199 cancel();
200 }
cretin456d63e952015-04-08 12:09:55 -0700201 }
202 break;
cretin45d842eed2015-01-27 11:10:09 -0800203 case MotionEvent.ACTION_POINTER_UP:
204 case MotionEvent.ACTION_CANCEL: {
205 cancel();
cretin456d63e952015-04-08 12:09:55 -0700206 }
207 break;
cretin45d842eed2015-01-27 11:10:09 -0800208 }
209 return true;
210 }
211
212 private void cancel() {
213 mCanceled = true;
214 if (mHandler.hasMessages(MESSAGE_SPEAK_WARNING)) {
215 mHandler.removeMessages(MESSAGE_SPEAK_WARNING);
216 } else if (mHandler.hasMessages(MESSAGE_ENABLE_ACCESSIBILITY)) {
217 mHandler.sendEmptyMessage(MESSAGE_SPEAK_ENABLE_CANCELED);
218 }
219 mHandler.removeMessages(MESSAGE_ENABLE_ACCESSIBILITY);
220 }
221
222 private void enableAccessibility() {
223 List<AccessibilityServiceInfo> services = getInstalledSpeakingAccessibilityServices(
224 mContext);
225 if (services.isEmpty()) {
226 return;
227 }
228 boolean keyguardLocked = false;
229 try {
230 keyguardLocked = mWindowManager.isKeyguardLocked();
231 } catch (RemoteException re) {
232 /* ignore */
233 }
234
235 final boolean hasMoreThanOneUser = mUserManager.getUsers().size() > 1;
236
237 AccessibilityServiceInfo service = services.get(0);
238 boolean enableTouchExploration = (service.flags
239 & AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0;
240 // Try to find a service supporting explore by touch.
241 if (!enableTouchExploration) {
242 final int serviceCount = services.size();
243 for (int i = 1; i < serviceCount; i++) {
244 AccessibilityServiceInfo candidate = services.get(i);
245 if ((candidate.flags & AccessibilityServiceInfo
246 .FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0) {
247 enableTouchExploration = true;
248 service = candidate;
249 break;
250 }
251 }
252 }
253
254 ServiceInfo serviceInfo = service.getResolveInfo().serviceInfo;
255 ComponentName componentName = new ComponentName(serviceInfo.packageName, serviceInfo.name);
256 if (!keyguardLocked || !hasMoreThanOneUser) {
257 final int userId = ActivityManager.getCurrentUser();
258 String enabledServiceString = componentName.flattenToString();
259 ContentResolver resolver = mContext.getContentResolver();
260 // Enable one speaking accessibility service.
261 Settings.Secure.putStringForUser(resolver,
262 Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
263 enabledServiceString, userId);
264 // Allow the services we just enabled to toggle touch exploration.
265 Settings.Secure.putStringForUser(resolver,
266 Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
267 enabledServiceString, userId);
268 // Enable touch exploration.
269 if (enableTouchExploration) {
270 Settings.Secure.putIntForUser(resolver, Settings.Secure.TOUCH_EXPLORATION_ENABLED,
271 1, userId);
272 }
cretin45d842eed2015-01-27 11:10:09 -0800273 // Turn on accessibility mode last.
274 Settings.Secure.putIntForUser(resolver, Settings.Secure.ACCESSIBILITY_ENABLED,
275 1, userId);
cretin45d842eed2015-01-27 11:10:09 -0800276 } else if (keyguardLocked) {
277 try {
278 mAccessibilityManager.temporaryEnableAccessibilityStateUntilKeyguardRemoved(
279 componentName, enableTouchExploration);
280 } catch (RemoteException re) {
281 /* ignore */
282 }
283 }
284 }
cretin456d63e952015-04-08 12:09:55 -0700285}