LayoutBase.java revision a94c89e017dac92e2c23dcf05a8e6b4000d021d3
1/*
2 * Copyright (C) 2014 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.layout;
18
19import com.android.inputmethod.keyboard.KeyboardId;
20import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
21import com.android.inputmethod.keyboard.layout.expected.AbstractLayoutBase;
22import com.android.inputmethod.keyboard.layout.expected.ExpectedKey;
23import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder;
24import com.android.inputmethod.latin.Constants;
25
26import java.util.Locale;
27
28/**
29 * The base class of keyboard layout.
30 */
31public abstract class LayoutBase extends AbstractLayoutBase {
32    /**
33     * This class is used to customize common keyboard layout to language specific layout.
34     */
35    public static class LayoutCustomizer {
36        private final Locale mLocale;
37
38        // Empty keys definition to remove keys by adding this.
39        protected static final ExpectedKey[] EMPTY_KEYS = joinKeys();
40
41        public LayoutCustomizer(final Locale locale) {
42            mLocale = locale;
43        }
44
45        public final Locale getLocale() {
46            return mLocale;
47        }
48
49        public int getNumberOfRows() {
50            return 4;
51        }
52
53        /**
54         * Set accented letters to common layout.
55         * @param builder the {@link ExpectedKeyboardBuilder} object that contains common keyboard
56         *        layout.
57         * @return the {@link ExpectedKeyboardBuilder} object that contains accented letters as
58         *        "more keys".
59         */
60        public ExpectedKeyboardBuilder setAccentedLetters(final ExpectedKeyboardBuilder builder) {
61            return builder;
62        }
63
64        /**
65         * Get the function key to switch to alphabet layout.
66         * @return the {@link ExpectedKey} of the alphabet key.
67         */
68        public ExpectedKey getAlphabetKey() { return ALPHABET_KEY; }
69
70        /**
71         * Get the function key to switch to symbols layout.
72         * @return the {@link ExpectedKey} of the symbols key.
73         */
74        public ExpectedKey getSymbolsKey() { return SYMBOLS_KEY; }
75
76        /**
77         * Get the function key to switch to symbols shift layout.
78         * @param isPhone true if requesting phone's key.
79         * @return the {@link ExpectedKey} of the symbols shift key.
80         */
81        public ExpectedKey getSymbolsShiftKey(boolean isPhone) {
82            return isPhone ? SYMBOLS_SHIFT_KEY : TABLET_SYMBOLS_SHIFT_KEY;
83        }
84
85        /**
86         * Get the function key to switch from symbols shift to symbols layout.
87         * @return the {@link ExpectedKey} of the back to symbols key.
88         */
89        public ExpectedKey getBackToSymbolsKey() { return BACK_TO_SYMBOLS_KEY; }
90
91        /**
92         * Get the currency key.
93         * @return the {@link ExpectedKey} of the currency key.
94         */
95        public ExpectedKey getCurrencyKey() { return Symbols.CURRENCY_DOLLAR; }
96
97        /**
98         * Get other currencies keys.
99         * @return the array of {@link ExpectedKey} that represents other currency keys.
100         */
101        public ExpectedKey[] getOtherCurrencyKeys() {
102            return SymbolsShifted.CURRENCIES_OTHER_THAN_DOLLAR;
103        }
104
105        /**
106         * Get "more keys" of double quotation mark.
107         * @return the array of {@link ExpectedKey} of more double quotation marks in natural order.
108         */
109        public ExpectedKey[] getDoubleQuoteMoreKeys() { return Symbols.DOUBLE_QUOTES_9LR; }
110
111        /**
112         * Get "more keys" of single quotation mark.
113         * @return the array of {@link ExpectedKey} of more single quotation marks in natural order.
114         */
115        public ExpectedKey[] getSingleQuoteMoreKeys() { return Symbols.SINGLE_QUOTES_9LR; }
116
117        /**
118         * Get double angle quotation marks in natural order.
119         * @return the array of {@link ExpectedKey} of double angle quotation marks in natural
120         *         order.
121         */
122        public ExpectedKey[] getDoubleAngleQuoteKeys() { return Symbols.DOUBLE_ANGLE_QUOTES_LR; }
123
124        /**
125         * Get single angle quotation marks in natural order.
126         * @return the array of {@link ExpectedKey} of single angle quotation marks in natural
127         *         order.
128         */
129        public ExpectedKey[] getSingleAngleQuoteKeys() { return Symbols.SINGLE_ANGLE_QUOTES_LR; }
130
131        /**
132         * Get the left shift keys.
133         * @param isPhone true if requesting phone's keys.
134         * @return the array of {@link ExpectedKey} that should be placed at left edge of the
135         *         keyboard.
136         */
137        public ExpectedKey[] getLeftShiftKeys(final boolean isPhone) {
138            return joinKeys(SHIFT_KEY);
139        }
140
141        /**
142         * Get the right shift keys.
143         * @param isPhone true if requesting phone's keys.
144         * @return the array of {@link ExpectedKey} that should be placed at right edge of the
145         *         keyboard.
146         */
147        public ExpectedKey[] getRightShiftKeys(final boolean isPhone) {
148            return isPhone ? EMPTY_KEYS : joinKeys(EXCLAMATION_AND_QUESTION_MARKS, SHIFT_KEY);
149        }
150
151        /**
152         * Get the space keys.
153         * @param isPhone true if requesting phone's keys.
154         * @return the array of {@link ExpectedKey} that should be placed at the center of the
155         *         keyboard.
156         */
157        public ExpectedKey[] getSpaceKeys(final boolean isPhone) {
158            return joinKeys(LANGUAGE_SWITCH_KEY, SPACE_KEY);
159        }
160
161        /**
162         * Get the keys left to the spacebar.
163         * @param isPhone true if requesting phone's keys.
164         * @return the array of {@link ExpectedKey} that should be placed at left of the spacebar.
165         */
166        public ExpectedKey[] getKeysLeftToSpacebar(final boolean isPhone) {
167            // U+002C: "," COMMA
168            return isPhone ? joinKeys(key("\u002C", SETTINGS_KEY))
169                    : joinKeys(key("\u002C", SETTINGS_KEY), "_");
170        }
171
172        /**
173         * Get the keys right to the spacebar.
174         * @param isPhone true if requesting phone's keys.
175         * @return the array of {@link ExpectedKey} that should be placed at right of the spacebar.
176         */
177        public ExpectedKey[] getKeysRightToSpacebar(final boolean isPhone) {
178            final ExpectedKey periodKey = key(".", getPunctuationMoreKeys(isPhone));
179            return isPhone ? joinKeys(periodKey) : joinKeys("/", periodKey);
180        }
181
182        /**
183         * Get "more keys" for the punctuation key (usually the period key).
184         * @param isPhone true if requesting phone's keys.
185         * @return the array of {@link ExpectedKey} that are "more keys" of the punctuation key.
186         */
187        public ExpectedKey[] getPunctuationMoreKeys(final boolean isPhone) {
188            return isPhone ? PHONE_PUNCTUATION_MORE_KEYS : TABLET_PUNCTUATION_MORE_KEYS;
189        }
190    }
191
192    /**
193     * The layout customize class for countries that use Euro.
194     */
195    public static class EuroCustomizer extends LayoutCustomizer {
196        public EuroCustomizer(final Locale locale) {
197            super(locale);
198        }
199
200        @Override
201        public final ExpectedKey getCurrencyKey() { return Symbols.CURRENCY_EURO; }
202
203        @Override
204        public final ExpectedKey[] getOtherCurrencyKeys() {
205            return SymbolsShifted.CURRENCIES_OTHER_THAN_EURO;
206        }
207    }
208
209    private final LayoutCustomizer mCustomizer;
210    private final Symbols mSymbols;
211    private final SymbolsShifted mSymbolsShifted;
212
213    LayoutBase(final LayoutCustomizer customizer, final Class<? extends Symbols> symbolsClass,
214            final Class<? extends SymbolsShifted> symbolsShiftedClass) {
215        mCustomizer = customizer;
216        try {
217            mSymbols = symbolsClass.getDeclaredConstructor(LayoutCustomizer.class)
218                    .newInstance(customizer);
219            mSymbolsShifted = symbolsShiftedClass.getDeclaredConstructor(LayoutCustomizer.class)
220                    .newInstance(customizer);
221        } catch (final Exception e) {
222            throw new RuntimeException("Unknown Symbols/SymbolsShifted class", e);
223        }
224    }
225
226    /**
227     * The layout name.
228     * @return the name of this layout.
229     */
230    public abstract String getName();
231
232    /**
233     * The locale of this layout.
234     * @return the locale of this layout.
235     */
236    public final Locale getLocale() { return mCustomizer.getLocale(); }
237
238    /**
239     * The layout customizer for this layout.
240     * @return the layout customizer;
241     */
242    public final LayoutCustomizer getCustomizer() { return mCustomizer; }
243
244    // Icon id.
245    private static final int ICON_SHIFT = KeyboardIconsSet.getIconId(
246            KeyboardIconsSet.NAME_SHIFT_KEY);
247    private static final int ICON_SHIFTED_SHIFT = KeyboardIconsSet.getIconId(
248            KeyboardIconsSet.NAME_SHIFT_KEY_SHIFTED);
249    private static final int ICON_ZWNJ = KeyboardIconsSet.getIconId(
250            KeyboardIconsSet.NAME_ZWNJ_KEY);
251    private static final int ICON_ZWJ = KeyboardIconsSet.getIconId(
252            KeyboardIconsSet.NAME_ZWJ_KEY);
253
254    // Functional key.
255    static final ExpectedKey CAPSLOCK_MORE_KEY = key(" ", Constants.CODE_CAPSLOCK);
256    static final ExpectedKey SHIFT_KEY = key(ICON_SHIFT,
257            Constants.CODE_SHIFT, CAPSLOCK_MORE_KEY);
258    static final ExpectedKey SHIFTED_SHIFT_KEY = key(ICON_SHIFTED_SHIFT,
259            Constants.CODE_SHIFT, CAPSLOCK_MORE_KEY);
260    static final ExpectedKey ALPHABET_KEY = key("ABC", Constants.CODE_SWITCH_ALPHA_SYMBOL);
261    static final ExpectedKey SYMBOLS_KEY = key("?123", Constants.CODE_SWITCH_ALPHA_SYMBOL);
262    static final ExpectedKey BACK_TO_SYMBOLS_KEY = key("?123", Constants.CODE_SHIFT);
263    static final ExpectedKey SYMBOLS_SHIFT_KEY = key("= \\ <", Constants.CODE_SHIFT);
264    static final ExpectedKey TABLET_SYMBOLS_SHIFT_KEY = key("~ [ <", Constants.CODE_SHIFT);
265
266    // U+00A1: "¡" INVERTED EXCLAMATION MARK
267    // U+00BF: "¿" INVERTED QUESTION MARK
268    static final ExpectedKey[] EXCLAMATION_AND_QUESTION_MARKS = joinKeys(
269            key("!", moreKey("\u00A1")), key("?", moreKey("\u00BF")));
270    // U+200C: ZERO WIDTH NON-JOINER
271    // U+200D: ZERO WIDTH JOINER
272    static final ExpectedKey ZWNJ_KEY = key(ICON_ZWNJ, "\u200C");
273    static final ExpectedKey ZWJ_KEY = key(ICON_ZWJ, "\u200D");
274
275    // Punctuation more keys for phone form factor.
276    public static final ExpectedKey[] PHONE_PUNCTUATION_MORE_KEYS = joinKeys(
277            ",", "?", "!", "#", ")", "(", "/", ";",
278            "'", "@", ":", "-", "\"", "+", "%", "&");
279    // Punctuation more keys for tablet form factor.
280    public static final ExpectedKey[] TABLET_PUNCTUATION_MORE_KEYS = joinKeys(
281            ",", "'", "#", ")", "(", "/", ";",
282            "@", ":", "-", "\"", "+", "%", "&");
283
284    /**
285     * Helper method to create alphabet layout adding special function keys.
286     * @param builder the {@link ExpectedKeyboardBuilder} object that contains common keyboard
287     *     layout
288     * @param isPhone true if requesting phone's layout.
289     * @return the {@link ExpectedKeyboardBuilder} object that is customized and have special keys.
290     */
291    ExpectedKeyboardBuilder convertCommonLayoutToKeyboard(final ExpectedKeyboardBuilder builder,
292            final boolean isPhone) {
293        final LayoutCustomizer customizer = getCustomizer();
294        final int numberOfRows = customizer.getNumberOfRows();
295        builder.setKeysOfRow(numberOfRows, (Object[])customizer.getSpaceKeys(isPhone));
296        builder.addKeysOnTheLeftOfRow(
297                numberOfRows, (Object[])customizer.getKeysLeftToSpacebar(isPhone));
298        builder.addKeysOnTheRightOfRow(
299                numberOfRows, (Object[])customizer.getKeysRightToSpacebar(isPhone));
300        if (isPhone) {
301            builder.addKeysOnTheRightOfRow(numberOfRows - 1, DELETE_KEY)
302                    .addKeysOnTheLeftOfRow(numberOfRows, customizer.getSymbolsKey())
303                    .addKeysOnTheRightOfRow(numberOfRows, key(ENTER_KEY, EMOJI_ACTION_KEY));
304        } else {
305            builder.addKeysOnTheRightOfRow(1, DELETE_KEY)
306                    .addKeysOnTheRightOfRow(numberOfRows - 2, ENTER_KEY)
307                    .addKeysOnTheLeftOfRow(numberOfRows, customizer.getSymbolsKey())
308                    .addKeysOnTheRightOfRow(numberOfRows, EMOJI_NORMAL_KEY);
309        }
310        builder.addKeysOnTheLeftOfRow(
311                numberOfRows - 1, (Object[])customizer.getLeftShiftKeys(isPhone));
312        builder.addKeysOnTheRightOfRow(
313                numberOfRows - 1, (Object[])customizer.getRightShiftKeys(isPhone));
314        return builder;
315    }
316
317    /**
318     * Get common alphabet layout. This layout doesn't contain any special keys.
319     *
320     * A keyboard layout is an array of rows, and a row consists of an array of
321     * {@link ExpectedKey}s. Each row may have different number of {@link ExpectedKey}s.
322     *
323     * @param isPhone true if requesting phone's layout.
324     * @return the common alphabet keyboard layout.
325     */
326    abstract ExpectedKey[][] getCommonAlphabetLayout(boolean isPhone);
327
328    /**
329     * Get common alphabet shifted layout. This layout doesn't contain any special keys.
330     *
331     * A keyboard layout is an array of rows, and a row consists of an array of
332     * {@link ExpectedKey}s. Each row may have different number of {@link ExpectedKey}s.
333     *
334     * @param isPhone true if requesting phone's layout.
335     * @param elementId the element id of the requesting shifted mode.
336     * @return the common alphabet shifted keyboard layout.
337     */
338    ExpectedKey[][] getCommonAlphabetShiftLayout(final boolean isPhone, final int elementId) {
339        final ExpectedKeyboardBuilder builder = new ExpectedKeyboardBuilder(
340                getCommonAlphabetLayout(isPhone));
341        getCustomizer().setAccentedLetters(builder);
342        builder.toUpperCase(getLocale());
343        return builder.build();
344    }
345
346    /**
347     * Get the complete expected keyboard layout.
348     *
349     * A keyboard layout is an array of rows, and a row consists of an array of
350     * {@link ExpectedKey}s. Each row may have different number of {@link ExpectedKey}s.
351     *
352     * @param isPhone true if requesting phone's layout.
353     * @param elementId the element id of the requesting keyboard mode.
354     * @return the keyboard layout of the <code>elementId</code>.
355     */
356    public ExpectedKey[][] getLayout(final boolean isPhone, final int elementId) {
357        if (elementId == KeyboardId.ELEMENT_SYMBOLS) {
358            return mSymbols.getLayout(isPhone);
359        }
360        if (elementId == KeyboardId.ELEMENT_SYMBOLS_SHIFTED) {
361            return mSymbolsShifted.getLayout(isPhone);
362        }
363        final ExpectedKeyboardBuilder builder;
364        if (elementId == KeyboardId.ELEMENT_ALPHABET) {
365            builder = new ExpectedKeyboardBuilder(getCommonAlphabetLayout(isPhone));
366            getCustomizer().setAccentedLetters(builder);
367        } else {
368            final ExpectedKey[][] commonLayout = getCommonAlphabetShiftLayout(isPhone, elementId);
369            if (commonLayout == null) {
370                return null;
371            }
372            builder = new ExpectedKeyboardBuilder(commonLayout);
373        }
374        convertCommonLayoutToKeyboard(builder, isPhone);
375        if (elementId != KeyboardId.ELEMENT_ALPHABET) {
376            builder.replaceKeysOfAll(SHIFT_KEY, SHIFTED_SHIFT_KEY);
377        }
378        return builder.build();
379    }
380}
381