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