1/*
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
17package com.android.server.policy;
18
19import android.accessibilityservice.AccessibilityService;
20import android.accessibilityservice.AccessibilityServiceInfo;
21import android.annotation.Nullable;
22import android.app.ActivityManager;
23import android.content.ComponentName;
24import android.content.ContentResolver;
25import android.content.Context;
26import android.content.pm.ServiceInfo;
27import android.media.AudioManager;
28import android.media.Ringtone;
29import android.media.RingtoneManager;
30import android.os.Handler;
31import android.os.Message;
32import android.os.RemoteException;
33import android.os.ServiceManager;
34import android.os.UserManager;
35import android.provider.Settings;
36import android.speech.tts.TextToSpeech;
37import android.util.Log;
38import android.util.MathUtils;
39import android.view.IWindowManager;
40import android.view.MotionEvent;
41import android.view.WindowManager;
42import android.view.WindowManagerGlobal;
43import android.view.WindowManagerInternal;
44import android.view.accessibility.AccessibilityManager;
45import android.view.accessibility.IAccessibilityManager;
46
47import com.android.internal.R;
48import com.android.server.LocalServices;
49
50import java.util.ArrayList;
51import java.util.Iterator;
52import java.util.List;
53
54public class EnableAccessibilityController {
55    private static final String TAG = "EnableAccessibilityController";
56
57    private static final int SPEAK_WARNING_DELAY_MILLIS = 2000;
58    private static final int ENABLE_ACCESSIBILITY_DELAY_MILLIS = 6000;
59
60    public static final int MESSAGE_SPEAK_WARNING = 1;
61    public static final int MESSAGE_SPEAK_ENABLE_CANCELED = 2;
62    public static final int MESSAGE_ENABLE_ACCESSIBILITY = 3;
63
64    private final Handler mHandler = new Handler() {
65        @Override
66        public void handleMessage(Message message) {
67            switch (message.what) {
68                case MESSAGE_SPEAK_WARNING: {
69                    String text = mContext.getString(R.string.continue_to_enable_accessibility);
70                    mTts.speak(text, TextToSpeech.QUEUE_FLUSH, null);
71                } break;
72                case MESSAGE_SPEAK_ENABLE_CANCELED: {
73                    String text = mContext.getString(R.string.enable_accessibility_canceled);
74                    mTts.speak(text, TextToSpeech.QUEUE_FLUSH, null);
75                } break;
76                case MESSAGE_ENABLE_ACCESSIBILITY: {
77                    enableAccessibility();
78                    mTone.play();
79                    mTts.speak(mContext.getString(R.string.accessibility_enabled),
80                            TextToSpeech.QUEUE_FLUSH, null);
81                } break;
82            }
83        }
84    };
85
86    private final IAccessibilityManager mAccessibilityManager = IAccessibilityManager
87            .Stub.asInterface(ServiceManager.getService("accessibility"));
88
89
90    private final Context mContext;
91    private final Runnable mOnAccessibilityEnabledCallback;
92    private final UserManager mUserManager;
93    private final TextToSpeech mTts;
94    private final Ringtone mTone;
95
96    private final float mTouchSlop;
97
98    private boolean mDestroyed;
99    private boolean mCanceled;
100
101    private float mFirstPointerDownX;
102    private float mFirstPointerDownY;
103    private float mSecondPointerDownX;
104    private float mSecondPointerDownY;
105
106    public EnableAccessibilityController(Context context, Runnable onAccessibilityEnabledCallback) {
107        mContext = context;
108        mOnAccessibilityEnabledCallback = onAccessibilityEnabledCallback;
109        mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
110        mTts = new TextToSpeech(context, new TextToSpeech.OnInitListener() {
111            @Override
112            public void onInit(int status) {
113                if (mDestroyed) {
114                    mTts.shutdown();
115                }
116            }
117        });
118        mTone = RingtoneManager.getRingtone(context, Settings.System.DEFAULT_NOTIFICATION_URI);
119        mTone.setStreamType(AudioManager.STREAM_MUSIC);
120        mTouchSlop = context.getResources().getDimensionPixelSize(
121                R.dimen.accessibility_touch_slop);
122    }
123
124    public static boolean canEnableAccessibilityViaGesture(Context context) {
125        AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(context);
126        // Accessibility is enabled and there is an enabled speaking
127        // accessibility service, then we have nothing to do.
128        if (accessibilityManager.isEnabled()
129                && !accessibilityManager.getEnabledAccessibilityServiceList(
130                        AccessibilityServiceInfo.FEEDBACK_SPOKEN).isEmpty()) {
131            return false;
132        }
133        // If the global gesture is enabled and there is a speaking service
134        // installed we are good to go, otherwise there is nothing to do.
135        return Settings.Global.getInt(context.getContentResolver(),
136                Settings.Global.ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED, 0) == 1
137                && !getInstalledSpeakingAccessibilityServices(context).isEmpty();
138    }
139
140    public static List<AccessibilityServiceInfo> getInstalledSpeakingAccessibilityServices(
141            Context context) {
142        List<AccessibilityServiceInfo> services = new ArrayList<AccessibilityServiceInfo>();
143        services.addAll(AccessibilityManager.getInstance(context)
144                .getInstalledAccessibilityServiceList());
145        Iterator<AccessibilityServiceInfo> iterator = services.iterator();
146        while (iterator.hasNext()) {
147            AccessibilityServiceInfo service = iterator.next();
148            if ((service.feedbackType & AccessibilityServiceInfo.FEEDBACK_SPOKEN) == 0) {
149                iterator.remove();
150            }
151        }
152        return services;
153    }
154
155    public void onDestroy() {
156        mDestroyed = true;
157    }
158
159    public boolean onInterceptTouchEvent(MotionEvent event) {
160        if (event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN
161                && event.getPointerCount() == 2) {
162            mFirstPointerDownX = event.getX(0);
163            mFirstPointerDownY = event.getY(0);
164            mSecondPointerDownX = event.getX(1);
165            mSecondPointerDownY = event.getY(1);
166            mHandler.sendEmptyMessageDelayed(MESSAGE_SPEAK_WARNING,
167                    SPEAK_WARNING_DELAY_MILLIS);
168            mHandler.sendEmptyMessageDelayed(MESSAGE_ENABLE_ACCESSIBILITY,
169                   ENABLE_ACCESSIBILITY_DELAY_MILLIS);
170            return true;
171        }
172        return false;
173    }
174
175    public boolean onTouchEvent(MotionEvent event) {
176        final int pointerCount = event.getPointerCount();
177        final int action = event.getActionMasked();
178        if (mCanceled) {
179            if (action == MotionEvent.ACTION_UP) {
180                mCanceled = false;
181            }
182            return true;
183        }
184        switch (action) {
185            case MotionEvent.ACTION_POINTER_DOWN: {
186                if (pointerCount > 2) {
187                    cancel();
188                }
189            } break;
190            case MotionEvent.ACTION_MOVE: {
191                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                }
201            } break;
202            case MotionEvent.ACTION_POINTER_UP:
203            case MotionEvent.ACTION_CANCEL: {
204                cancel();
205            } break;
206        }
207        return true;
208    }
209
210    private void cancel() {
211        mCanceled = true;
212        if (mHandler.hasMessages(MESSAGE_SPEAK_WARNING)) {
213            mHandler.removeMessages(MESSAGE_SPEAK_WARNING);
214        } else if (mHandler.hasMessages(MESSAGE_ENABLE_ACCESSIBILITY)) {
215            mHandler.sendEmptyMessage(MESSAGE_SPEAK_ENABLE_CANCELED);
216        }
217        mHandler.removeMessages(MESSAGE_ENABLE_ACCESSIBILITY);
218    }
219
220    private void enableAccessibility() {
221        if (enableAccessibility(mContext)) {
222            mOnAccessibilityEnabledCallback.run();
223        }
224    }
225
226    public static boolean enableAccessibility(Context context) {
227        final IAccessibilityManager accessibilityManager = IAccessibilityManager
228                .Stub.asInterface(ServiceManager.getService("accessibility"));
229        final WindowManagerInternal windowManager = LocalServices.getService(
230                WindowManagerInternal.class);
231        final UserManager userManager = (UserManager) context.getSystemService(
232                Context.USER_SERVICE);
233        ComponentName componentName = getInstalledSpeakingAccessibilityServiceComponent(context);
234        if (componentName == null) {
235            return false;
236        }
237
238        boolean keyguardLocked = windowManager.isKeyguardLocked();
239        final boolean hasMoreThanOneUser = userManager.getUsers().size() > 1;
240        try {
241            if (!keyguardLocked || !hasMoreThanOneUser) {
242                final int userId = ActivityManager.getCurrentUser();
243                accessibilityManager.enableAccessibilityService(componentName, userId);
244            } else if (keyguardLocked) {
245                accessibilityManager.temporaryEnableAccessibilityStateUntilKeyguardRemoved(
246                        componentName, true /* enableTouchExploration */);
247            }
248        } catch (RemoteException e) {
249            Log.e(TAG, "cannot enable accessibilty: " + e);
250        }
251
252        return true;
253    }
254
255    public static void disableAccessibility(Context context) {
256        final IAccessibilityManager accessibilityManager = IAccessibilityManager
257                .Stub.asInterface(ServiceManager.getService("accessibility"));
258        ComponentName componentName = getInstalledSpeakingAccessibilityServiceComponent(context);
259        if (componentName == null) {
260            return;
261        }
262
263        final int userId = ActivityManager.getCurrentUser();
264        try {
265            accessibilityManager.disableAccessibilityService(componentName, userId);
266        } catch (RemoteException e) {
267            Log.e(TAG, "cannot disable accessibility " + e);
268        }
269    }
270
271    public static boolean isAccessibilityEnabled(Context context) {
272        final AccessibilityManager accessibilityManager =
273                context.getSystemService(AccessibilityManager.class);
274        List enabledServices = accessibilityManager.getEnabledAccessibilityServiceList(
275                AccessibilityServiceInfo.FEEDBACK_SPOKEN);
276        return enabledServices != null && !enabledServices.isEmpty();
277    }
278
279    @Nullable
280    public static ComponentName getInstalledSpeakingAccessibilityServiceComponent(
281            Context context) {
282        List<AccessibilityServiceInfo> services =
283                getInstalledSpeakingAccessibilityServices(context);
284        if (services.isEmpty()) {
285            return null;
286        }
287
288        ServiceInfo serviceInfo = services.get(0).getResolveInfo().serviceInfo;
289        return new ComponentName(serviceInfo.packageName, serviceInfo.name);
290    }
291}
292