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.expected; 18 19 import com.android.inputmethod.keyboard.Key; 20 import com.android.inputmethod.keyboard.internal.MoreKeySpec; 21 22 import java.util.ArrayList; 23 import java.util.Arrays; 24 import java.util.Locale; 25 26 /** 27 * This class represents an expected key. 28 */ 29 public class ExpectedKey { 30 static ExpectedKey EMPTY_KEY = newInstance(""); 31 32 // A key that has a string label and may have "more keys". newInstance(final String label, final ExpectedKey... moreKeys)33 static ExpectedKey newInstance(final String label, final ExpectedKey... moreKeys) { 34 return newInstance(label, label, moreKeys); 35 } 36 37 // A key that has a string label and a different output text and may have "more keys". newInstance(final String label, final String outputText, final ExpectedKey... moreKeys)38 static ExpectedKey newInstance(final String label, final String outputText, 39 final ExpectedKey... moreKeys) { 40 return newInstance(ExpectedKeyVisual.newInstance(label), 41 ExpectedKeyOutput.newInstance(outputText), moreKeys); 42 } 43 44 // A key that has a string label and a code point output and may have "more keys". newInstance(final String label, final int code, final ExpectedKey... moreKeys)45 static ExpectedKey newInstance(final String label, final int code, 46 final ExpectedKey... moreKeys) { 47 return newInstance(ExpectedKeyVisual.newInstance(label), 48 ExpectedKeyOutput.newInstance(code), moreKeys); 49 } 50 51 // A key that has an icon and an output text and may have "more keys". newInstance(final int iconId, final String outputText, final ExpectedKey... moreKeys)52 static ExpectedKey newInstance(final int iconId, final String outputText, 53 final ExpectedKey... moreKeys) { 54 return newInstance(ExpectedKeyVisual.newInstance(iconId), 55 ExpectedKeyOutput.newInstance(outputText), moreKeys); 56 } 57 58 // A key that has an icon and a code point output and may have "more keys". newInstance(final int iconId, final int code, final ExpectedKey... moreKeys)59 static ExpectedKey newInstance(final int iconId, final int code, 60 final ExpectedKey... moreKeys) { 61 return newInstance(ExpectedKeyVisual.newInstance(iconId), 62 ExpectedKeyOutput.newInstance(code), moreKeys); 63 } 64 newInstance(final ExpectedKeyVisual visual, final ExpectedKeyOutput output, final ExpectedKey... moreKeys)65 static ExpectedKey newInstance(final ExpectedKeyVisual visual, final ExpectedKeyOutput output, 66 final ExpectedKey... moreKeys) { 67 if (moreKeys.length == 0) { 68 return new ExpectedKey(visual, output); 69 } 70 // The more keys are the extra keys that the main keyboard key may have in its long press 71 // popup keyboard. 72 // The additional more keys can be defined independently from other more keys. 73 // The position of the additional more keys in the long press popup keyboard can be 74 // controlled by specifying special marker "%" in the usual more keys definitions. 75 final ArrayList<ExpectedKey> moreKeysList = new ArrayList<>(); 76 final ArrayList<ExpectedAdditionalMoreKey> additionalMoreKeys = new ArrayList<>(); 77 int firstAdditionalMoreKeyIndex = -1; 78 for (int index = 0; index < moreKeys.length; index++) { 79 final ExpectedKey moreKey = moreKeys[index]; 80 if (moreKey instanceof ExpectedAdditionalMoreKey) { 81 additionalMoreKeys.add((ExpectedAdditionalMoreKey) moreKey); 82 if (firstAdditionalMoreKeyIndex < 0) { 83 firstAdditionalMoreKeyIndex = index; 84 } 85 } else { 86 moreKeysList.add(moreKey); 87 } 88 } 89 if (additionalMoreKeys.isEmpty()) { 90 return new ExpectedKeyWithMoreKeys(visual, output, moreKeys); 91 } 92 final ExpectedKey[] moreKeysArray = moreKeysList.toArray( 93 new ExpectedKey[moreKeysList.size()]); 94 final ExpectedAdditionalMoreKey[] additionalMoreKeysArray = additionalMoreKeys.toArray( 95 new ExpectedAdditionalMoreKey[additionalMoreKeys.size()]); 96 return new ExpectedKeyWithMoreKeysAndAdditionalMoreKeys( 97 visual, output, moreKeysArray, firstAdditionalMoreKeyIndex, 98 additionalMoreKeysArray); 99 } 100 101 private static final ExpectedKey[] EMPTY_KEYS = new ExpectedKey[0]; 102 103 // The expected visual outlook of this key. 104 private final ExpectedKeyVisual mVisual; 105 // The expected output of this key. 106 private final ExpectedKeyOutput mOutput; 107 getVisual()108 protected final ExpectedKeyVisual getVisual() { 109 return mVisual; 110 } 111 getOutput()112 protected final ExpectedKeyOutput getOutput() { 113 return mOutput; 114 } 115 getMoreKeys()116 public ExpectedKey[] getMoreKeys() { 117 // This key has no "more keys". 118 return EMPTY_KEYS; 119 } 120 setMoreKeys(final ExpectedKey... moreKeys)121 public ExpectedKey setMoreKeys(final ExpectedKey... moreKeys) { 122 return newInstance(mVisual, mOutput, moreKeys); 123 } 124 setAdditionalMoreKeys( final ExpectedAdditionalMoreKey... additionalMoreKeys)125 public ExpectedKey setAdditionalMoreKeys( 126 final ExpectedAdditionalMoreKey... additionalMoreKeys) { 127 if (additionalMoreKeys.length == 0) { 128 return this; 129 } 130 return new ExpectedKeyWithMoreKeysAndAdditionalMoreKeys( 131 mVisual, mOutput, EMPTY_KEYS, 0 /* additionalMoreKeysIndex */, additionalMoreKeys); 132 } 133 setAdditionalMoreKeysIndex(final int additionalMoreKeysIndex)134 public ExpectedKey setAdditionalMoreKeysIndex(final int additionalMoreKeysIndex) { 135 if (additionalMoreKeysIndex == 0) { 136 return this; 137 } 138 return new ExpectedKeyWithMoreKeysAndAdditionalMoreKeys( 139 mVisual, mOutput, EMPTY_KEYS, additionalMoreKeysIndex); 140 } 141 ExpectedKey(final ExpectedKeyVisual visual, final ExpectedKeyOutput output)142 protected ExpectedKey(final ExpectedKeyVisual visual, final ExpectedKeyOutput output) { 143 mVisual = visual; 144 mOutput = output; 145 } 146 toUpperCase(Locale locale)147 public ExpectedKey toUpperCase(Locale locale) { 148 return newInstance(mVisual.toUpperCase(locale), mOutput.toUpperCase(locale)); 149 } 150 preserveCase()151 public ExpectedKey preserveCase() { 152 final ExpectedKey[] moreKeys = getMoreKeys(); 153 final ExpectedKey[] casePreservedMoreKeys = new ExpectedKey[moreKeys.length]; 154 for (int index = 0; index < moreKeys.length; index++) { 155 final ExpectedKey moreKey = moreKeys[index]; 156 casePreservedMoreKeys[index] = newInstance( 157 moreKey.getVisual().preserveCase(), moreKey.getOutput().preserveCase()); 158 } 159 return newInstance( 160 getVisual().preserveCase(), getOutput().preserveCase(), casePreservedMoreKeys); 161 } 162 equalsTo(final Key key)163 public boolean equalsTo(final Key key) { 164 // This key has no "more keys". 165 return mVisual.hasSameKeyVisual(key) && mOutput.hasSameKeyOutput(key) 166 && key.getMoreKeys() == null; 167 } 168 equalsTo(final MoreKeySpec moreKeySpec)169 public boolean equalsTo(final MoreKeySpec moreKeySpec) { 170 return mVisual.hasSameKeyVisual(moreKeySpec) && mOutput.hasSameKeyOutput(moreKeySpec); 171 } 172 173 @Override equals(final Object object)174 public boolean equals(final Object object) { 175 if (object instanceof ExpectedKey) { 176 final ExpectedKey key = (ExpectedKey) object; 177 return mVisual.hasSameKeyVisual(key.mVisual) && mOutput.hasSameKeyOutput(key.mOutput) 178 && Arrays.equals(getMoreKeys(), key.getMoreKeys()); 179 } 180 return false; 181 } 182 hashCode(final Object... objects)183 private static int hashCode(final Object... objects) { 184 return Arrays.hashCode(objects); 185 } 186 187 @Override hashCode()188 public int hashCode() { 189 return hashCode(mVisual, mOutput, getMoreKeys()); 190 } 191 192 @Override toString()193 public String toString() { 194 if (mVisual.hasSameKeyVisual(mOutput)) { 195 return mVisual.toString(); 196 } 197 return mVisual + "|" + mOutput; 198 } 199 200 /** 201 * This class represents an expected "additional more key". 202 * 203 * The additional more keys can be defined independently from other more keys. The position of 204 * the additional more keys in the long press popup keyboard can be controlled by specifying 205 * special marker "%" in the usual more keys definitions. 206 */ 207 public static class ExpectedAdditionalMoreKey extends ExpectedKey { newInstance(final String label)208 public static ExpectedAdditionalMoreKey newInstance(final String label) { 209 return new ExpectedAdditionalMoreKey(ExpectedKeyVisual.newInstance(label), 210 ExpectedKeyOutput.newInstance(label)); 211 } 212 newInstance(final ExpectedKey key)213 public static ExpectedAdditionalMoreKey newInstance(final ExpectedKey key) { 214 return new ExpectedAdditionalMoreKey(key.getVisual(), key.getOutput()); 215 } 216 ExpectedAdditionalMoreKey(final ExpectedKeyVisual visual, final ExpectedKeyOutput output)217 ExpectedAdditionalMoreKey(final ExpectedKeyVisual visual, final ExpectedKeyOutput output) { 218 super(visual, output); 219 } 220 221 @Override toUpperCase(final Locale locale)222 public ExpectedAdditionalMoreKey toUpperCase(final Locale locale) { 223 final ExpectedKey upperCaseKey = super.toUpperCase(locale); 224 return new ExpectedAdditionalMoreKey( 225 upperCaseKey.getVisual(), upperCaseKey.getOutput()); 226 } 227 } 228 229 /** 230 * This class represents an expected key that has "more keys". 231 */ 232 private static class ExpectedKeyWithMoreKeys extends ExpectedKey { 233 private final ExpectedKey[] mMoreKeys; 234 ExpectedKeyWithMoreKeys(final ExpectedKeyVisual visual, final ExpectedKeyOutput output, final ExpectedKey... moreKeys)235 ExpectedKeyWithMoreKeys(final ExpectedKeyVisual visual, final ExpectedKeyOutput output, 236 final ExpectedKey... moreKeys) { 237 super(visual, output); 238 mMoreKeys = moreKeys; 239 } 240 241 @Override toUpperCase(final Locale locale)242 public ExpectedKey toUpperCase(final Locale locale) { 243 final ExpectedKey[] upperCaseMoreKeys = new ExpectedKey[mMoreKeys.length]; 244 for (int i = 0; i < mMoreKeys.length; i++) { 245 upperCaseMoreKeys[i] = mMoreKeys[i].toUpperCase(locale); 246 } 247 return newInstance(getVisual().toUpperCase(locale), getOutput().toUpperCase(locale), 248 upperCaseMoreKeys); 249 } 250 251 @Override getMoreKeys()252 public ExpectedKey[] getMoreKeys() { 253 return mMoreKeys; 254 } 255 256 @Override setAdditionalMoreKeys( final ExpectedAdditionalMoreKey... additionalMoreKeys)257 public ExpectedKey setAdditionalMoreKeys( 258 final ExpectedAdditionalMoreKey... additionalMoreKeys) { 259 if (additionalMoreKeys.length == 0) { 260 return this; 261 } 262 return new ExpectedKeyWithMoreKeysAndAdditionalMoreKeys( 263 getVisual(), getOutput(), mMoreKeys, 0 /* additionalMoreKeysIndex */, 264 additionalMoreKeys); 265 } 266 267 @Override setAdditionalMoreKeysIndex(final int additionalMoreKeysIndex)268 public ExpectedKey setAdditionalMoreKeysIndex(final int additionalMoreKeysIndex) { 269 if (additionalMoreKeysIndex == 0) { 270 return this; 271 } 272 return new ExpectedKeyWithMoreKeysAndAdditionalMoreKeys( 273 getVisual(), getOutput(), mMoreKeys, additionalMoreKeysIndex); 274 } 275 276 @Override equalsTo(final Key key)277 public boolean equalsTo(final Key key) { 278 if (getVisual().hasSameKeyVisual(key) && getOutput().hasSameKeyOutput(key)) { 279 final MoreKeySpec[] moreKeySpecs = key.getMoreKeys(); 280 final ExpectedKey[] moreKeys = getMoreKeys(); 281 // This key should have at least one "more key". 282 if (moreKeySpecs == null || moreKeySpecs.length != moreKeys.length) { 283 return false; 284 } 285 for (int index = 0; index < moreKeySpecs.length; index++) { 286 if (!moreKeys[index].equalsTo(moreKeySpecs[index])) { 287 return false; 288 } 289 } 290 return true; 291 } 292 return false; 293 } 294 295 @Override equalsTo(final MoreKeySpec moreKeySpec)296 public boolean equalsTo(final MoreKeySpec moreKeySpec) { 297 // MoreKeySpec has no "more keys". 298 return false; 299 } 300 301 @Override toString()302 public String toString() { 303 return super.toString() + "^" + Arrays.toString(getMoreKeys()); 304 } 305 } 306 307 /** 308 * This class represents an expected key that has "more keys" and "additional more keys". 309 */ 310 private static final class ExpectedKeyWithMoreKeysAndAdditionalMoreKeys 311 extends ExpectedKeyWithMoreKeys { 312 private final ExpectedAdditionalMoreKey[] mAdditionalMoreKeys; 313 private final int mAdditionalMoreKeysIndex; 314 ExpectedKeyWithMoreKeysAndAdditionalMoreKeys(final ExpectedKeyVisual visual, final ExpectedKeyOutput output, final ExpectedKey[] moreKeys, final int additionalMoreKeysIndex, final ExpectedAdditionalMoreKey... additionalMoreKeys)315 ExpectedKeyWithMoreKeysAndAdditionalMoreKeys(final ExpectedKeyVisual visual, 316 final ExpectedKeyOutput output, final ExpectedKey[] moreKeys, 317 final int additionalMoreKeysIndex, 318 final ExpectedAdditionalMoreKey... additionalMoreKeys) { 319 super(visual, output, moreKeys); 320 mAdditionalMoreKeysIndex = additionalMoreKeysIndex; 321 mAdditionalMoreKeys = additionalMoreKeys; 322 } 323 324 @Override setMoreKeys(final ExpectedKey... moreKeys)325 public ExpectedKey setMoreKeys(final ExpectedKey... moreKeys) { 326 return new ExpectedKeyWithMoreKeysAndAdditionalMoreKeys( 327 getVisual(), getOutput(), moreKeys, mAdditionalMoreKeysIndex, 328 mAdditionalMoreKeys); 329 } 330 331 @Override setAdditionalMoreKeys( final ExpectedAdditionalMoreKey... additionalMoreKeys)332 public ExpectedKey setAdditionalMoreKeys( 333 final ExpectedAdditionalMoreKey... additionalMoreKeys) { 334 return new ExpectedKeyWithMoreKeysAndAdditionalMoreKeys( 335 getVisual(), getOutput(), super.getMoreKeys(), mAdditionalMoreKeysIndex, 336 additionalMoreKeys); 337 } 338 339 @Override setAdditionalMoreKeysIndex(final int additionalMoreKeysIndex)340 public ExpectedKey setAdditionalMoreKeysIndex(final int additionalMoreKeysIndex) { 341 return new ExpectedKeyWithMoreKeysAndAdditionalMoreKeys( 342 getVisual(), getOutput(), super.getMoreKeys(), additionalMoreKeysIndex, 343 mAdditionalMoreKeys); 344 } 345 346 @Override toUpperCase(final Locale locale)347 public ExpectedKey toUpperCase(final Locale locale) { 348 final ExpectedKey[] moreKeys = super.getMoreKeys(); 349 final ExpectedKey[] upperCaseMoreKeys = new ExpectedKey[moreKeys.length]; 350 for (int i = 0; i < moreKeys.length; i++) { 351 upperCaseMoreKeys[i] = moreKeys[i].toUpperCase(locale); 352 } 353 final ExpectedAdditionalMoreKey[] upperCaseAdditionalMoreKeys = 354 new ExpectedAdditionalMoreKey[mAdditionalMoreKeys.length]; 355 for (int i = 0; i < mAdditionalMoreKeys.length; i++) { 356 upperCaseAdditionalMoreKeys[i] = mAdditionalMoreKeys[i].toUpperCase(locale); 357 } 358 return new ExpectedKeyWithMoreKeysAndAdditionalMoreKeys( 359 getVisual().toUpperCase(locale), getOutput().toUpperCase(locale), 360 upperCaseMoreKeys, mAdditionalMoreKeysIndex, upperCaseAdditionalMoreKeys); 361 } 362 363 @Override getMoreKeys()364 public ExpectedKey[] getMoreKeys() { 365 final ExpectedKey[] moreKeys = super.getMoreKeys(); 366 final ExpectedKey[] edittedMoreKeys = Arrays.copyOf( 367 moreKeys, moreKeys.length + mAdditionalMoreKeys.length); 368 System.arraycopy(edittedMoreKeys, mAdditionalMoreKeysIndex, 369 edittedMoreKeys, mAdditionalMoreKeysIndex + mAdditionalMoreKeys.length, 370 moreKeys.length - mAdditionalMoreKeysIndex); 371 System.arraycopy(mAdditionalMoreKeys, 0, edittedMoreKeys, mAdditionalMoreKeysIndex, 372 mAdditionalMoreKeys.length); 373 return edittedMoreKeys; 374 } 375 } 376 } 377