summaryrefslogtreecommitdiff
path: root/java/src/com/android/inputmethod/event/Event.java
blob: 58d878fbe55325466da1b574db3b8608402adf49 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.inputmethod.event;

import com.android.inputmethod.annotations.ExternallyReferenced;
import com.android.inputmethod.latin.SuggestedWords.SuggestedWordInfo;
import com.android.inputmethod.latin.common.Constants;
import com.android.inputmethod.latin.common.StringUtils;

import javax.annotation.Nonnull;

/**
 * Class representing a generic input event as handled by Latin IME.
 *
 * This contains information about the origin of the event, but it is generalized and should
 * represent a software keypress, hardware keypress, or d-pad move alike.
 * Very importantly, this does not necessarily result in inputting one character, or even anything
 * at all - it may be a dead key, it may be a partial input, it may be a special key on the
 * keyboard, it may be a cancellation of a keypress (e.g. in a soft keyboard the finger of the
 * user has slid out of the key), etc. It may also be a batch input from a gesture or handwriting
 * for example.
 * The combiner should figure out what to do with this.
 */
public class Event {
    // Should the types below be represented by separate classes instead? It would be cleaner
    // but probably a bit too much
    // An event we don't handle in Latin IME, for example pressing Ctrl on a hardware keyboard.
    final public static int EVENT_TYPE_NOT_HANDLED = 0;
    // A key press that is part of input, for example pressing an alphabetic character on a
    // hardware qwerty keyboard. It may be part of a sequence that will be re-interpreted later
    // through combination.
    final public static int EVENT_TYPE_INPUT_KEYPRESS = 1;
    // A toggle event is triggered by a key that affects the previous character. An example would
    // be a numeric key on a 10-key keyboard, which would toggle between 1 - a - b - c with
    // repeated presses.
    final public static int EVENT_TYPE_TOGGLE = 2;
    // A mode event instructs the combiner to change modes. The canonical example would be the
    // hankaku/zenkaku key on a Japanese keyboard, or even the caps lock key on a qwerty keyboard
    // if handled at the combiner level.
    final public static int EVENT_TYPE_MODE_KEY = 3;
    // An event corresponding to a gesture.
    final public static int EVENT_TYPE_GESTURE = 4;
    // An event corresponding to the manual pick of a suggestion.
    final public static int EVENT_TYPE_SUGGESTION_PICKED = 5;
    // An event corresponding to a string generated by some software process.
    final public static int EVENT_TYPE_SOFTWARE_GENERATED_STRING = 6;
    // An event corresponding to a cursor move
    final public static int EVENT_TYPE_CURSOR_MOVE = 7;

    // 0 is a valid code point, so we use -1 here.
    final public static int NOT_A_CODE_POINT = -1;
    // -1 is a valid key code, so we use 0 here.
    final public static int NOT_A_KEY_CODE = 0;

    final private static int FLAG_NONE = 0;
    // This event is a dead character, usually input by a dead key. Examples include dead-acute
    // or dead-abovering.
    final private static int FLAG_DEAD = 0x1;
    // This event is coming from a key repeat, software or hardware.
    final private static int FLAG_REPEAT = 0x2;
    // This event has already been consumed.
    final private static int FLAG_CONSUMED = 0x4;

    final private int mEventType; // The type of event - one of the constants above
    // The code point associated with the event, if relevant. This is a unicode code point, and
    // has nothing to do with other representations of the key. It is only relevant if this event
    // is of KEYPRESS type, but for a mode key like hankaku/zenkaku or ctrl, there is no code point
    // associated so this should be NOT_A_CODE_POINT to avoid unintentional use of its value when
    // it's not relevant.
    final public int mCodePoint;

    // If applicable, this contains the string that should be input.
    final public CharSequence mText;

    // The key code associated with the event, if relevant. This is relevant whenever this event
    // has been triggered by a key press, but not for a gesture for example. This has conceptually
    // no link to the code point, although keys that enter a straight code point may often set
    // this to be equal to mCodePoint for convenience. If this is not a key, this must contain
    // NOT_A_KEY_CODE.
    final public int mKeyCode;

    // Coordinates of the touch event, if relevant. If useful, we may want to replace this with
    // a MotionEvent or something in the future. This is only relevant when the keypress is from
    // a software keyboard obviously, unless there are touch-sensitive hardware keyboards in the
    // future or some other awesome sauce.
    final public int mX;
    final public int mY;

    // Some flags that can't go into the key code. It's a bit field of FLAG_*
    final private int mFlags;

    // If this is of type EVENT_TYPE_SUGGESTION_PICKED, this must not be null (and must be null in
    // other cases).
    final public SuggestedWordInfo mSuggestedWordInfo;

    // The next event, if any. Null if there is no next event yet.
    final public Event mNextEvent;

    // This method is private - to create a new event, use one of the create* utility methods.
    private Event(final int type, final CharSequence text, final int codePoint, final int keyCode,
            final int x, final int y, final SuggestedWordInfo suggestedWordInfo, final int flags,
            final Event next) {
        mEventType = type;
        mText = text;
        mCodePoint = codePoint;
        mKeyCode = keyCode;
        mX = x;
        mY = y;
        mSuggestedWordInfo = suggestedWordInfo;
        mFlags = flags;
        mNextEvent = next;
        // Validity checks
        // mSuggestedWordInfo is non-null if and only if the type is SUGGESTION_PICKED
        if (EVENT_TYPE_SUGGESTION_PICKED == mEventType) {
            if (null == mSuggestedWordInfo) {
                throw new RuntimeException("Wrong event: SUGGESTION_PICKED event must have a "
                        + "non-null SuggestedWordInfo");
            }
        } else {
            if (null != mSuggestedWordInfo) {
                throw new RuntimeException("Wrong event: only SUGGESTION_PICKED events may have " +
                        "a non-null SuggestedWordInfo");
            }
        }
    }

    @Nonnull
    public static Event createSoftwareKeypressEvent(final int codePoint, final int keyCode,
            final int x, final int y, final boolean isKeyRepeat) {
        return new Event(EVENT_TYPE_INPUT_KEYPRESS, null /* text */, codePoint, keyCode, x, y,
                null /* suggestedWordInfo */, isKeyRepeat ? FLAG_REPEAT : FLAG_NONE, null);
    }

    @Nonnull
    public static Event createHardwareKeypressEvent(final int codePoint, final int keyCode,
            final Event next, final boolean isKeyRepeat) {
        return new Event(EVENT_TYPE_INPUT_KEYPRESS, null /* text */, codePoint, keyCode,
                Constants.EXTERNAL_KEYBOARD_COORDINATE, Constants.EXTERNAL_KEYBOARD_COORDINATE,
                null /* suggestedWordInfo */, isKeyRepeat ? FLAG_REPEAT : FLAG_NONE, next);
    }

    // This creates an input event for a dead character. @see {@link #FLAG_DEAD}
    @ExternallyReferenced
    @Nonnull
    public static Event createDeadEvent(final int codePoint, final int keyCode, final Event next) {
        // TODO: add an argument or something if we ever create a software layout with dead keys.
        return new Event(EVENT_TYPE_INPUT_KEYPRESS, null /* text */, codePoint, keyCode,
                Constants.EXTERNAL_KEYBOARD_COORDINATE, Constants.EXTERNAL_KEYBOARD_COORDINATE,
                null /* suggestedWordInfo */, FLAG_DEAD, next);
    }

    /**
     * Create an input event with nothing but a code point. This is the most basic possible input
     * event; it contains no information on many things the IME requires to function correctly,
     * so avoid using it unless really nothing is known about this input.
     * @param codePoint the code point.
     * @return an event for this code point.
     */
    @Nonnull
    public static Event createEventForCodePointFromUnknownSource(final int codePoint) {
        // TODO: should we have a different type of event for this? After all, it's not a key press.
        return new Event(EVENT_TYPE_INPUT_KEYPRESS, null /* text */, codePoint, NOT_A_KEY_CODE,
                Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE,
                null /* suggestedWordInfo */, FLAG_NONE, null /* next */);
    }

    /**
     * Creates an input event with a code point and x, y coordinates. This is typically used when
     * resuming a previously-typed word, when the coordinates are still known.
     * @param codePoint the code point to input.
     * @param x the X coordinate.
     * @param y the Y coordinate.
     * @return an event for this code point and coordinates.
     */
    @Nonnull
    public static Event createEventForCodePointFromAlreadyTypedText(final int codePoint,
            final int x, final int y) {
        // TODO: should we have a different type of event for this? After all, it's not a key press.
        return new Event(EVENT_TYPE_INPUT_KEYPRESS, null /* text */, codePoint, NOT_A_KEY_CODE,
                x, y, null /* suggestedWordInfo */, FLAG_NONE, null /* next */);
    }

    /**
     * Creates an input event representing the manual pick of a suggestion.
     * @return an event for this suggestion pick.
     */
    @Nonnull
    public static Event createSuggestionPickedEvent(final SuggestedWordInfo suggestedWordInfo) {
        return new Event(EVENT_TYPE_SUGGESTION_PICKED, suggestedWordInfo.mWord,
                NOT_A_CODE_POINT, NOT_A_KEY_CODE,
                Constants.SUGGESTION_STRIP_COORDINATE, Constants.SUGGESTION_STRIP_COORDINATE,
                suggestedWordInfo, FLAG_NONE, null /* next */);
    }

    /**
     * Creates an input event with a CharSequence. This is used by some software processes whose
     * output is a string, possibly with styling. Examples include press on a multi-character key,
     * or combination that outputs a string.
     * @param text the CharSequence associated with this event.
     * @param keyCode the key code, or NOT_A_KEYCODE if not applicable.
     * @return an event for this text.
     */
    @Nonnull
    public static Event createSoftwareTextEvent(final CharSequence text, final int keyCode) {
        return new Event(EVENT_TYPE_SOFTWARE_GENERATED_STRING, text, NOT_A_CODE_POINT, keyCode,
                Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE,
                null /* suggestedWordInfo */, FLAG_NONE, null /* next */);
    }

    /**
     * Creates an input event representing the manual pick of a punctuation suggestion.
     * @return an event for this suggestion pick.
     */
    @Nonnull
    public static Event createPunctuationSuggestionPickedEvent(
            final SuggestedWordInfo suggestedWordInfo) {
        final int primaryCode = suggestedWordInfo.mWord.charAt(0);
        return new Event(EVENT_TYPE_SUGGESTION_PICKED, suggestedWordInfo.mWord, primaryCode,
                NOT_A_KEY_CODE, Constants.SUGGESTION_STRIP_COORDINATE,
                Constants.SUGGESTION_STRIP_COORDINATE, suggestedWordInfo, FLAG_NONE,
                null /* next */);
    }

    /**
     * Creates an input event representing moving the cursor. The relative move amount is stored
     * in mX.
     * @param moveAmount the relative move amount.
     * @return an event for this cursor move.
     */
    @Nonnull
    public static Event createCursorMovedEvent(final int moveAmount) {
        return new Event(EVENT_TYPE_CURSOR_MOVE, null, NOT_A_CODE_POINT, NOT_A_KEY_CODE,
                moveAmount, Constants.NOT_A_COORDINATE, null, FLAG_NONE, null);
    }

    /**
     * Creates an event identical to the passed event, but that has already been consumed.
     * @param source the event to copy the properties of.
     * @return an identical event marked as consumed.
     */
    @Nonnull
    public static Event createConsumedEvent(final Event source) {
        // A consumed event should not input any text at all, so we pass the empty string as text.
        return new Event(source.mEventType, source.mText, source.mCodePoint, source.mKeyCode,
                source.mX, source.mY, source.mSuggestedWordInfo, source.mFlags | FLAG_CONSUMED,
                source.mNextEvent);
    }

    @Nonnull
    public static Event createNotHandledEvent() {
        return new Event(EVENT_TYPE_NOT_HANDLED, null /* text */, NOT_A_CODE_POINT, NOT_A_KEY_CODE,
                Constants.NOT_A_COORDINATE, Constants.NOT_A_COORDINATE,
                null /* suggestedWordInfo */, FLAG_NONE, null);
    }

    // Returns whether this is a function key like backspace, ctrl, settings... as opposed to keys
    // that result in input like letters or space.
    public boolean isFunctionalKeyEvent() {
        // This logic may need to be refined in the future
        return NOT_A_CODE_POINT == mCodePoint;
    }

    // Returns whether this event is for a dead character. @see {@link #FLAG_DEAD}
    public boolean isDead() {
        return 0 != (FLAG_DEAD & mFlags);
    }

    public boolean isKeyRepeat() {
        return 0 != (FLAG_REPEAT & mFlags);
    }

    public boolean isConsumed() { return 0 != (FLAG_CONSUMED & mFlags); }

    public boolean isGesture() { return EVENT_TYPE_GESTURE == mEventType; }

    // Returns whether this is a fake key press from the suggestion strip. This happens with
    // punctuation signs selected from the suggestion strip.
    public boolean isSuggestionStripPress() {
        return EVENT_TYPE_SUGGESTION_PICKED == mEventType;
    }

    public boolean isHandled() {
        return EVENT_TYPE_NOT_HANDLED != mEventType;
    }

    public CharSequence getTextToCommit() {
        if (isConsumed()) {
            return ""; // A consumed event should input no text.
        }
        switch (mEventType) {
        case EVENT_TYPE_MODE_KEY:
        case EVENT_TYPE_NOT_HANDLED:
        case EVENT_TYPE_TOGGLE:
        case EVENT_TYPE_CURSOR_MOVE:
            return "";
        case EVENT_TYPE_INPUT_KEYPRESS:
            return StringUtils.newSingleCodePointString(mCodePoint);
        case EVENT_TYPE_GESTURE:
        case EVENT_TYPE_SOFTWARE_GENERATED_STRING:
        case EVENT_TYPE_SUGGESTION_PICKED:
            return mText;
        }
        throw new RuntimeException("Unknown event type: " + mEventType);
    }
}