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 
17 package com.android.inputmethod.keyboard.layout;
18 
19 import com.android.inputmethod.keyboard.KeyboardId;
20 import com.android.inputmethod.keyboard.internal.KeyboardIconsSet;
21 import com.android.inputmethod.keyboard.layout.expected.AbstractLayoutBase;
22 import com.android.inputmethod.keyboard.layout.expected.ExpectedKey;
23 import com.android.inputmethod.keyboard.layout.expected.ExpectedKeyboardBuilder;
24 import com.android.inputmethod.latin.Constants;
25 
26 import java.util.Locale;
27 
28 /**
29  * The base class of keyboard layout.
30  */
31 public 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 
LayoutCustomizer(final Locale locale)41         public LayoutCustomizer(final Locale locale) {
42             mLocale = locale;
43         }
44 
getLocale()45         public final Locale getLocale() {
46             return mLocale;
47         }
48 
getNumberOfRows()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          */
setAccentedLetters(final ExpectedKeyboardBuilder builder)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          */
getAlphabetKey()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          */
getSymbolsKey()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          */
getSymbolsShiftKey(boolean isPhone)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          */
getBackToSymbolsKey()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          */
getCurrencyKey()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          */
getOtherCurrencyKeys()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          */
getDoubleQuoteMoreKeys()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          */
getSingleQuoteMoreKeys()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          */
getDoubleAngleQuoteKeys()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          */
getSingleAngleQuoteKeys()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          */
getLeftShiftKeys(final boolean isPhone)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          */
getRightShiftKeys(final boolean isPhone)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 enter key.
153          * @param isPhone true if requesting phone's key.
154          * @return the array of {@link ExpectedKey} that should be placed as an enter key.
155          */
getEnterKey(final boolean isPhone)156         public ExpectedKey getEnterKey(final boolean isPhone) {
157             return isPhone ? key(ENTER_KEY, EMOJI_ACTION_KEY) : ENTER_KEY;
158         }
159 
160         /**
161          * Get the emoji key.
162          * @param isPhone true if requesting phone's key.
163          * @return the array of {@link ExpectedKey} that should be placed as an emoji key.
164          */
getEmojiKey(final boolean isPhone)165         public ExpectedKey getEmojiKey(final boolean isPhone) {
166             return EMOJI_NORMAL_KEY;
167         }
168 
169         /**
170          * Get the space keys.
171          * @param isPhone true if requesting phone's keys.
172          * @return the array of {@link ExpectedKey} that should be placed at the center of the
173          *         keyboard.
174          */
getSpaceKeys(final boolean isPhone)175         public ExpectedKey[] getSpaceKeys(final boolean isPhone) {
176             return joinKeys(LANGUAGE_SWITCH_KEY, SPACE_KEY);
177         }
178 
179         /**
180          * Get the keys left to the spacebar.
181          * @param isPhone true if requesting phone's keys.
182          * @return the array of {@link ExpectedKey} that should be placed at left of the spacebar.
183          */
getKeysLeftToSpacebar(final boolean isPhone)184         public ExpectedKey[] getKeysLeftToSpacebar(final boolean isPhone) {
185             // U+002C: "," COMMA
186             return joinKeys(key("\u002C", SETTINGS_KEY));
187         }
188 
189         /**
190          * Get the keys right to the spacebar.
191          * @param isPhone true if requesting phone's keys.
192          * @return the array of {@link ExpectedKey} that should be placed at right of the spacebar.
193          */
getKeysRightToSpacebar(final boolean isPhone)194         public ExpectedKey[] getKeysRightToSpacebar(final boolean isPhone) {
195             final ExpectedKey periodKey = key(".", getPunctuationMoreKeys(isPhone));
196             return joinKeys(periodKey);
197         }
198 
199         /**
200          * Get "more keys" for the punctuation key (usually the period key).
201          * @param isPhone true if requesting phone's keys.
202          * @return the array of {@link ExpectedKey} that are "more keys" of the punctuation key.
203          */
getPunctuationMoreKeys(final boolean isPhone)204         public ExpectedKey[] getPunctuationMoreKeys(final boolean isPhone) {
205             return isPhone ? PHONE_PUNCTUATION_MORE_KEYS : TABLET_PUNCTUATION_MORE_KEYS;
206         }
207     }
208 
209     /**
210      * The layout customize class for countries that use Euro.
211      */
212     public static class EuroCustomizer extends LayoutCustomizer {
EuroCustomizer(final Locale locale)213         public EuroCustomizer(final Locale locale) {
214             super(locale);
215         }
216 
217         @Override
getCurrencyKey()218         public final ExpectedKey getCurrencyKey() { return Symbols.CURRENCY_EURO; }
219 
220         @Override
getOtherCurrencyKeys()221         public final ExpectedKey[] getOtherCurrencyKeys() {
222             return SymbolsShifted.CURRENCIES_OTHER_THAN_EURO;
223         }
224     }
225 
226     private final LayoutCustomizer mCustomizer;
227     private final Symbols mSymbols;
228     private final SymbolsShifted mSymbolsShifted;
229 
LayoutBase(final LayoutCustomizer customizer, final Class<? extends Symbols> symbolsClass, final Class<? extends SymbolsShifted> symbolsShiftedClass)230     LayoutBase(final LayoutCustomizer customizer, final Class<? extends Symbols> symbolsClass,
231             final Class<? extends SymbolsShifted> symbolsShiftedClass) {
232         mCustomizer = customizer;
233         try {
234             mSymbols = symbolsClass.getDeclaredConstructor(LayoutCustomizer.class)
235                     .newInstance(customizer);
236             mSymbolsShifted = symbolsShiftedClass.getDeclaredConstructor(LayoutCustomizer.class)
237                     .newInstance(customizer);
238         } catch (final Exception e) {
239             throw new RuntimeException("Unknown Symbols/SymbolsShifted class", e);
240         }
241     }
242 
243     /**
244      * The layout name.
245      * @return the name of this layout.
246      */
getName()247     public abstract String getName();
248 
249     /**
250      * The locale of this layout.
251      * @return the locale of this layout.
252      */
getLocale()253     public final Locale getLocale() { return mCustomizer.getLocale(); }
254 
255     /**
256      * The layout customizer for this layout.
257      * @return the layout customizer;
258      */
getCustomizer()259     public final LayoutCustomizer getCustomizer() { return mCustomizer; }
260 
261     // Icon ids.
262     private static final int ICON_DELETE = KeyboardIconsSet.getIconId(
263             KeyboardIconsSet.NAME_DELETE_KEY);
264     private static final int ICON_SPACE = KeyboardIconsSet.getIconId(
265             KeyboardIconsSet.NAME_SPACE_KEY);
266     private static final int ICON_TAB = KeyboardIconsSet.getIconId(
267             KeyboardIconsSet.NAME_TAB_KEY);
268     private static final int ICON_SHORTCUT = KeyboardIconsSet.getIconId(
269             KeyboardIconsSet.NAME_SHORTCUT_KEY);
270     private static final int ICON_SETTINGS = KeyboardIconsSet.getIconId(
271             KeyboardIconsSet.NAME_SETTINGS_KEY);
272     private static final int ICON_LANGUAGE_SWITCH = KeyboardIconsSet.getIconId(
273             KeyboardIconsSet.NAME_LANGUAGE_SWITCH_KEY);
274     private static final int ICON_ENTER = KeyboardIconsSet.getIconId(
275             KeyboardIconsSet.NAME_ENTER_KEY);
276     private static final int ICON_EMOJI_ACTION = KeyboardIconsSet.getIconId(
277             KeyboardIconsSet.NAME_EMOJI_ACTION_KEY);
278     private static final int ICON_EMOJI_NORMAL = KeyboardIconsSet.getIconId(
279             KeyboardIconsSet.NAME_EMOJI_NORMAL_KEY);
280     private static final int ICON_SHIFT = KeyboardIconsSet.getIconId(
281             KeyboardIconsSet.NAME_SHIFT_KEY);
282     private static final int ICON_SHIFTED_SHIFT = KeyboardIconsSet.getIconId(
283             KeyboardIconsSet.NAME_SHIFT_KEY_SHIFTED);
284     private static final int ICON_ZWNJ = KeyboardIconsSet.getIconId(
285             KeyboardIconsSet.NAME_ZWNJ_KEY);
286     private static final int ICON_ZWJ = KeyboardIconsSet.getIconId(
287             KeyboardIconsSet.NAME_ZWJ_KEY);
288 
289     // Functional keys.
290     public static final ExpectedKey DELETE_KEY = key(ICON_DELETE, Constants.CODE_DELETE);
291     public static final ExpectedKey TAB_KEY = key(ICON_TAB, Constants.CODE_TAB);
292     public static final ExpectedKey SHORTCUT_KEY = key(ICON_SHORTCUT, Constants.CODE_SHORTCUT);
293     public static final ExpectedKey SETTINGS_KEY = key(ICON_SETTINGS, Constants.CODE_SETTINGS);
294     public static final ExpectedKey LANGUAGE_SWITCH_KEY = key(
295             ICON_LANGUAGE_SWITCH, Constants.CODE_LANGUAGE_SWITCH);
296     public static final ExpectedKey ENTER_KEY = key(ICON_ENTER, Constants.CODE_ENTER);
297     public static final ExpectedKey EMOJI_ACTION_KEY = key(ICON_EMOJI_ACTION, Constants.CODE_EMOJI);
298     public static final ExpectedKey EMOJI_NORMAL_KEY = key(ICON_EMOJI_NORMAL, Constants.CODE_EMOJI);
299     public static final ExpectedKey SPACE_KEY = key(ICON_SPACE, Constants.CODE_SPACE);
300     static final ExpectedKey CAPSLOCK_MORE_KEY = key(" ", Constants.CODE_CAPSLOCK);
301     public static final ExpectedKey SHIFT_KEY = key(ICON_SHIFT,
302             Constants.CODE_SHIFT, CAPSLOCK_MORE_KEY);
303     public static final ExpectedKey SHIFTED_SHIFT_KEY = key(ICON_SHIFTED_SHIFT,
304             Constants.CODE_SHIFT, CAPSLOCK_MORE_KEY);
305     static final ExpectedKey ALPHABET_KEY = key("ABC", Constants.CODE_SWITCH_ALPHA_SYMBOL);
306     static final ExpectedKey SYMBOLS_KEY = key("?123", Constants.CODE_SWITCH_ALPHA_SYMBOL);
307     static final ExpectedKey BACK_TO_SYMBOLS_KEY = key("?123", Constants.CODE_SHIFT);
308     static final ExpectedKey SYMBOLS_SHIFT_KEY = key("= \\ <", Constants.CODE_SHIFT);
309     static final ExpectedKey TABLET_SYMBOLS_SHIFT_KEY = key("~ [ <", Constants.CODE_SHIFT);
310 
311     // U+00A1: "¡" INVERTED EXCLAMATION MARK
312     // U+00BF: "¿" INVERTED QUESTION MARK
313     static final ExpectedKey[] EXCLAMATION_AND_QUESTION_MARKS = joinKeys(
314             key("!", moreKey("\u00A1")), key("?", moreKey("\u00BF")));
315     // U+200C: ZERO WIDTH NON-JOINER
316     // U+200D: ZERO WIDTH JOINER
317     static final ExpectedKey ZWNJ_KEY = key(ICON_ZWNJ, "\u200C");
318     static final ExpectedKey ZWJ_KEY = key(ICON_ZWJ, "\u200D");
319     // Domain key
320     public static final ExpectedKey DOMAIN_KEY =
321             key(".com", joinMoreKeys(".net", ".org", ".gov", ".edu")).preserveCase();
322 
323     // Punctuation more keys for phone form factor.
324     public static final ExpectedKey[] PHONE_PUNCTUATION_MORE_KEYS = joinKeys(
325             ",", "?", "!", "#", ")", "(", "/", ";",
326             "'", "@", ":", "-", "\"", "+", "%", "&");
327     // Punctuation more keys for tablet form factor.
328     public static final ExpectedKey[] TABLET_PUNCTUATION_MORE_KEYS = joinKeys(
329             ",", "'", "#", ")", "(", "/", ";",
330             "@", ":", "-", "\"", "+", "%", "&");
331 
332     /**
333      * Helper method to create alphabet layout adding special function keys.
334      * @param builder the {@link ExpectedKeyboardBuilder} object that contains common keyboard
335      *     layout
336      * @param isPhone true if requesting phone's layout.
337      * @return the {@link ExpectedKeyboardBuilder} object that is customized and have special keys.
338      */
convertCommonLayoutToKeyboard(final ExpectedKeyboardBuilder builder, final boolean isPhone)339     ExpectedKeyboardBuilder convertCommonLayoutToKeyboard(final ExpectedKeyboardBuilder builder,
340             final boolean isPhone) {
341         final LayoutCustomizer customizer = getCustomizer();
342         final int numberOfRows = customizer.getNumberOfRows();
343         builder.setKeysOfRow(numberOfRows, (Object[])customizer.getSpaceKeys(isPhone));
344         builder.addKeysOnTheLeftOfRow(
345                 numberOfRows, (Object[])customizer.getKeysLeftToSpacebar(isPhone));
346         builder.addKeysOnTheRightOfRow(
347                 numberOfRows, (Object[])customizer.getKeysRightToSpacebar(isPhone));
348         if (isPhone) {
349             builder.addKeysOnTheRightOfRow(numberOfRows - 1, DELETE_KEY)
350                     .addKeysOnTheLeftOfRow(numberOfRows, customizer.getSymbolsKey())
351                     .addKeysOnTheRightOfRow(numberOfRows, customizer.getEnterKey(isPhone));
352         } else {
353             builder.addKeysOnTheRightOfRow(1, DELETE_KEY)
354                     .addKeysOnTheRightOfRow(numberOfRows - 2, customizer.getEnterKey(isPhone))
355                     .addKeysOnTheLeftOfRow(numberOfRows, customizer.getSymbolsKey())
356                     .addKeysOnTheRightOfRow(numberOfRows, customizer.getEmojiKey(isPhone));
357         }
358         builder.addKeysOnTheLeftOfRow(
359                 numberOfRows - 1, (Object[])customizer.getLeftShiftKeys(isPhone));
360         builder.addKeysOnTheRightOfRow(
361                 numberOfRows - 1, (Object[])customizer.getRightShiftKeys(isPhone));
362         return builder;
363     }
364 
365     /**
366      * Get common alphabet layout. This layout doesn't contain any special keys.
367      *
368      * A keyboard layout is an array of rows, and a row consists of an array of
369      * {@link ExpectedKey}s. Each row may have different number of {@link ExpectedKey}s.
370      *
371      * @param isPhone true if requesting phone's layout.
372      * @return the common alphabet keyboard layout.
373      */
getCommonAlphabetLayout(boolean isPhone)374     abstract ExpectedKey[][] getCommonAlphabetLayout(boolean isPhone);
375 
376     /**
377      * Get common alphabet shifted layout. This layout doesn't contain any special keys.
378      *
379      * A keyboard layout is an array of rows, and a row consists of an array of
380      * {@link ExpectedKey}s. Each row may have different number of {@link ExpectedKey}s.
381      *
382      * @param isPhone true if requesting phone's layout.
383      * @param elementId the element id of the requesting shifted mode.
384      * @return the common alphabet shifted keyboard layout.
385      */
getCommonAlphabetShiftLayout(final boolean isPhone, final int elementId)386     ExpectedKey[][] getCommonAlphabetShiftLayout(final boolean isPhone, final int elementId) {
387         final ExpectedKeyboardBuilder builder = new ExpectedKeyboardBuilder(
388                 getCommonAlphabetLayout(isPhone));
389         getCustomizer().setAccentedLetters(builder);
390         builder.toUpperCase(getLocale());
391         return builder.build();
392     }
393 
394     /**
395      * Get the complete expected keyboard layout.
396      *
397      * A keyboard layout is an array of rows, and a row consists of an array of
398      * {@link ExpectedKey}s. Each row may have different number of {@link ExpectedKey}s.
399      *
400      * @param isPhone true if requesting phone's layout.
401      * @param elementId the element id of the requesting keyboard mode.
402      * @return the keyboard layout of the <code>elementId</code>.
403      */
getLayout(final boolean isPhone, final int elementId)404     public ExpectedKey[][] getLayout(final boolean isPhone, final int elementId) {
405         if (elementId == KeyboardId.ELEMENT_SYMBOLS) {
406             return mSymbols.getLayout(isPhone);
407         }
408         if (elementId == KeyboardId.ELEMENT_SYMBOLS_SHIFTED) {
409             return mSymbolsShifted.getLayout(isPhone);
410         }
411         final ExpectedKeyboardBuilder builder;
412         if (elementId == KeyboardId.ELEMENT_ALPHABET) {
413             builder = new ExpectedKeyboardBuilder(getCommonAlphabetLayout(isPhone));
414             getCustomizer().setAccentedLetters(builder);
415         } else {
416             final ExpectedKey[][] commonLayout = getCommonAlphabetShiftLayout(isPhone, elementId);
417             if (commonLayout == null) {
418                 return null;
419             }
420             builder = new ExpectedKeyboardBuilder(commonLayout);
421         }
422         convertCommonLayoutToKeyboard(builder, isPhone);
423         if (elementId != KeyboardId.ELEMENT_ALPHABET) {
424             builder.replaceKeysOfAll(SHIFT_KEY, SHIFTED_SHIFT_KEY);
425         }
426         return builder.build();
427     }
428 }
429