1/*
2 * Copyright (C) 2011 The Android Open Source Project
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.inputmethod.accessibility;
18
19import android.content.Context;
20import android.content.SharedPreferences;
21import android.graphics.Color;
22import android.graphics.Paint;
23import android.inputmethodservice.InputMethodService;
24import android.util.Log;
25import android.view.MotionEvent;
26import android.view.accessibility.AccessibilityEvent;
27import android.view.inputmethod.EditorInfo;
28
29import com.android.inputmethod.compat.AccessibilityEventCompatUtils;
30import com.android.inputmethod.compat.MotionEventCompatUtils;
31import com.android.inputmethod.keyboard.Key;
32import com.android.inputmethod.keyboard.KeyDetector;
33import com.android.inputmethod.keyboard.LatinKeyboardView;
34import com.android.inputmethod.keyboard.PointerTracker;
35
36public class AccessibleKeyboardViewProxy {
37    private static final String TAG = AccessibleKeyboardViewProxy.class.getSimpleName();
38    private static final AccessibleKeyboardViewProxy sInstance = new AccessibleKeyboardViewProxy();
39
40    private InputMethodService mInputMethod;
41    private FlickGestureDetector mGestureDetector;
42    private LatinKeyboardView mView;
43    private AccessibleKeyboardActionListener mListener;
44
45    private int mLastHoverKeyIndex = KeyDetector.NOT_A_KEY;
46
47    public static void init(InputMethodService inputMethod, SharedPreferences prefs) {
48        sInstance.initInternal(inputMethod, prefs);
49        sInstance.mListener = AccessibleInputMethodServiceProxy.getInstance();
50    }
51
52    public static AccessibleKeyboardViewProxy getInstance() {
53        return sInstance;
54    }
55
56    public static void setView(LatinKeyboardView view) {
57        sInstance.mView = view;
58    }
59
60    private AccessibleKeyboardViewProxy() {
61        // Not publicly instantiable.
62    }
63
64    private void initInternal(InputMethodService inputMethod, SharedPreferences prefs) {
65        final Paint paint = new Paint();
66        paint.setTextAlign(Paint.Align.LEFT);
67        paint.setTextSize(14.0f);
68        paint.setAntiAlias(true);
69        paint.setColor(Color.YELLOW);
70
71        mInputMethod = inputMethod;
72        mGestureDetector = new KeyboardFlickGestureDetector(inputMethod);
73    }
74
75    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event,
76            PointerTracker tracker) {
77        if (mView == null) {
78            Log.e(TAG, "No keyboard view set!");
79            return false;
80        }
81
82        switch (event.getEventType()) {
83        case AccessibilityEventCompatUtils.TYPE_VIEW_HOVER_ENTER:
84            final Key key = tracker.getKey(mLastHoverKeyIndex);
85
86            if (key == null)
87                break;
88
89            final EditorInfo info = mInputMethod.getCurrentInputEditorInfo();
90            final boolean shouldObscure = AccessibilityUtils.getInstance().shouldObscureInput(info);
91            final CharSequence description = KeyCodeDescriptionMapper.getInstance()
92                    .getDescriptionForKey(mView.getContext(), mView.getKeyboard(), key,
93                            shouldObscure);
94
95            if (description == null)
96                return false;
97
98            event.getText().add(description);
99
100            break;
101        }
102
103        return true;
104    }
105
106    /**
107     * Receives hover events when accessibility is turned on in SDK versions ICS
108     * and higher.
109     *
110     * @param event The hover event.
111     * @return {@code true} if the event is handled
112     */
113    public boolean dispatchHoverEvent(MotionEvent event, PointerTracker tracker) {
114        if (mGestureDetector.onHoverEvent(event, this, tracker))
115            return true;
116
117        return onHoverEventInternal(event, tracker);
118    }
119
120    /**
121     * Handles touch exploration events when Accessibility is turned on.
122     *
123     * @param event The touch exploration hover event.
124     * @return {@code true} if the event was handled
125     */
126    /*package*/ boolean onHoverEventInternal(MotionEvent event, PointerTracker tracker) {
127        final int x = (int) event.getX();
128        final int y = (int) event.getY();
129
130        switch (event.getAction()) {
131        case MotionEventCompatUtils.ACTION_HOVER_ENTER:
132        case MotionEventCompatUtils.ACTION_HOVER_MOVE:
133            final int keyIndex = tracker.getKeyIndexOn(x, y);
134
135            if (keyIndex != mLastHoverKeyIndex) {
136                fireKeyHoverEvent(tracker, mLastHoverKeyIndex, false);
137                mLastHoverKeyIndex = keyIndex;
138                fireKeyHoverEvent(tracker, mLastHoverKeyIndex, true);
139            }
140
141            return true;
142        }
143
144        return false;
145    }
146
147    private void fireKeyHoverEvent(PointerTracker tracker, int keyIndex, boolean entering) {
148        if (mListener == null) {
149            Log.e(TAG, "No accessible keyboard action listener set!");
150            return;
151        }
152
153        if (mView == null) {
154            Log.e(TAG, "No keyboard view set!");
155            return;
156        }
157
158        if (keyIndex == KeyDetector.NOT_A_KEY)
159            return;
160
161        final Key key = tracker.getKey(keyIndex);
162
163        if (key == null)
164            return;
165
166        if (entering) {
167            mListener.onHoverEnter(key.mCode);
168            mView.sendAccessibilityEvent(AccessibilityEventCompatUtils.TYPE_VIEW_HOVER_ENTER);
169        } else {
170            mListener.onHoverExit(key.mCode);
171            mView.sendAccessibilityEvent(AccessibilityEventCompatUtils.TYPE_VIEW_HOVER_EXIT);
172        }
173    }
174
175    private class KeyboardFlickGestureDetector extends FlickGestureDetector {
176        public KeyboardFlickGestureDetector(Context context) {
177            super(context);
178        }
179
180        @Override
181        public boolean onFlick(MotionEvent e1, MotionEvent e2, int direction) {
182            if (mListener != null) {
183                mListener.onFlickGesture(direction);
184            }
185            return true;
186        }
187    }
188}
189