1/*
2 * Copyright (C) 2013 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
17package com.android.inputmethod.keyboard.internal;
18
19import android.content.Context;
20import android.util.AttributeSet;
21import android.view.GestureDetector;
22import android.view.MotionEvent;
23import android.widget.ScrollView;
24import android.widget.Scroller;
25
26import com.android.inputmethod.keyboard.Key;
27import com.android.inputmethod.keyboard.KeyDetector;
28import com.android.inputmethod.keyboard.Keyboard;
29import com.android.inputmethod.keyboard.KeyboardView;
30import com.android.inputmethod.latin.R;
31
32/**
33 * This is an extended {@link KeyboardView} class that hosts a vertical scroll keyboard.
34 * Multi-touch unsupported. No {@link PointerTracker}s. No gesture support.
35 * TODO: Vertical scroll capability should be removed from this class because it's no longer used.
36 */
37// TODO: Implement key popup preview.
38public final class ScrollKeyboardView extends KeyboardView implements
39        ScrollViewWithNotifier.ScrollListener, GestureDetector.OnGestureListener {
40    private static final boolean PAGINATION = false;
41
42    public interface OnKeyClickListener {
43        public void onKeyClick(Key key);
44    }
45
46    private static final OnKeyClickListener EMPTY_LISTENER = new OnKeyClickListener() {
47        @Override
48        public void onKeyClick(final Key key) {}
49    };
50
51    private OnKeyClickListener mListener = EMPTY_LISTENER;
52    private final KeyDetector mKeyDetector = new KeyDetector(0.0f /*keyHysteresisDistance */);
53    private final GestureDetector mGestureDetector;
54
55    private final Scroller mScroller;
56    private ScrollViewWithNotifier mScrollView;
57
58    public ScrollKeyboardView(final Context context, final AttributeSet attrs) {
59        this(context, attrs, R.attr.keyboardViewStyle);
60    }
61
62    public ScrollKeyboardView(final Context context, final AttributeSet attrs, final int defStyle) {
63        super(context, attrs, defStyle);
64        mGestureDetector = new GestureDetector(context, this);
65        mGestureDetector.setIsLongpressEnabled(false /* isLongpressEnabled */);
66        mScroller = new Scroller(context);
67    }
68
69    public void setScrollView(final ScrollViewWithNotifier scrollView) {
70        mScrollView = scrollView;
71        scrollView.setScrollListener(this);
72    }
73
74    private final Runnable mScrollTask = new Runnable() {
75        @Override
76        public void run() {
77            final Scroller scroller = mScroller;
78            final ScrollView scrollView = mScrollView;
79            scroller.computeScrollOffset();
80            scrollView.scrollTo(0, scroller.getCurrY());
81            if (!scroller.isFinished()) {
82                scrollView.post(this);
83            }
84        }
85    };
86
87    // {@link ScrollViewWithNotified#ScrollListener} methods.
88    @Override
89    public void notifyScrollChanged(final int scrollX, final int scrollY, final int oldX,
90            final int oldY) {
91        if (PAGINATION) {
92            mScroller.forceFinished(true /* finished */);
93            mScrollView.removeCallbacks(mScrollTask);
94            final int currentTop = mScrollView.getScrollY();
95            final int pageHeight = getKeyboard().mBaseHeight;
96            final int lastPageNo = currentTop / pageHeight;
97            final int lastPageTop = lastPageNo * pageHeight;
98            final int nextPageNo = lastPageNo + 1;
99            final int nextPageTop = Math.min(nextPageNo * pageHeight, getHeight() - pageHeight);
100            final int scrollTo = (currentTop - lastPageTop) < (nextPageTop - currentTop)
101                    ? lastPageTop : nextPageTop;
102            final int deltaY = scrollTo - currentTop;
103            mScroller.startScroll(0, currentTop, 0, deltaY, 300);
104            mScrollView.post(mScrollTask);
105        }
106    }
107
108    @Override
109    public void notifyOverScrolled(final int scrollX, final int scrollY, final boolean clampedX,
110            final boolean clampedY) {
111        releaseCurrentKey();
112    }
113
114    public void setOnKeyClickListener(final OnKeyClickListener listener) {
115        mListener = listener;
116    }
117
118    /**
119     * {@inheritDoc}
120     */
121    @Override
122    public void setKeyboard(final Keyboard keyboard) {
123        super.setKeyboard(keyboard);
124        mKeyDetector.setKeyboard(keyboard, 0 /* correctionX */, 0 /* correctionY */);
125    }
126
127    /**
128     * {@inheritDoc}
129     */
130    @Override
131    public boolean onTouchEvent(final MotionEvent e) {
132        if (mGestureDetector.onTouchEvent(e)) {
133            return true;
134        }
135        final Key key = getKey(e);
136        if (key != null && key != mCurrentKey) {
137            releaseCurrentKey();
138        }
139        return true;
140    }
141
142    // {@link GestureDetector#OnGestureListener} methods.
143    private Key mCurrentKey;
144
145    private Key getKey(final MotionEvent e) {
146        final int index = e.getActionIndex();
147        final int x = (int)e.getX(index);
148        final int y = (int)e.getY(index);
149        return mKeyDetector.detectHitKey(x, y);
150    }
151
152    public void releaseCurrentKey() {
153        final Key currentKey = mCurrentKey;
154        if (currentKey == null) {
155            return;
156        }
157        currentKey.onReleased();
158        invalidateKey(currentKey);
159        mCurrentKey = null;
160    }
161
162    @Override
163    public boolean onDown(final MotionEvent e) {
164        final Key key = getKey(e);
165        releaseCurrentKey();
166        mCurrentKey = key;
167        if (key == null) {
168            return false;
169        }
170        // TODO: May call {@link KeyboardActionListener#onPressKey(int,int,boolean)}.
171        key.onPressed();
172        invalidateKey(key);
173        return false;
174    }
175
176    @Override
177    public void onShowPress(final MotionEvent e) {
178        // User feedback is done at {@link #onDown(MotionEvent)}.
179    }
180
181    @Override
182    public boolean onSingleTapUp(final MotionEvent e) {
183        final Key key = getKey(e);
184        releaseCurrentKey();
185        if (key == null) {
186            return false;
187        }
188        // TODO: May call {@link KeyboardActionListener#onReleaseKey(int,boolean)}.
189        key.onReleased();
190        invalidateKey(key);
191        mListener.onKeyClick(key);
192        return true;
193    }
194
195    @Override
196    public boolean onScroll(final MotionEvent e1, final MotionEvent e2, final float distanceX,
197           final float distanceY) {
198        releaseCurrentKey();
199        return false;
200    }
201
202    @Override
203    public boolean onFling(final MotionEvent e1, final MotionEvent e2, final float velocityX,
204            final float velocityY) {
205        releaseCurrentKey();
206        return false;
207    }
208
209    @Override
210    public void onLongPress(final MotionEvent e) {
211        // Long press detection of {@link #mGestureDetector} is disabled and not used.
212    }
213}
214