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 public final ExpectedKeyVisual getVisual() { 109 return mVisual; 110 } 111 getOutput()112 public 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.equalsTo(key) && mOutput.equalsTo(key) && key.getMoreKeys() == null; 166 } 167 equalsTo(final MoreKeySpec moreKeySpec)168 public boolean equalsTo(final MoreKeySpec moreKeySpec) { 169 return mVisual.equalsTo(moreKeySpec) && mOutput.equalsTo(moreKeySpec); 170 } 171 172 @Override equals(final Object object)173 public boolean equals(final Object object) { 174 if (object instanceof ExpectedKey) { 175 final ExpectedKey key = (ExpectedKey) object; 176 return mVisual.equalsTo(key.mVisual) && mOutput.equalsTo(key.mOutput) 177 && Arrays.equals(getMoreKeys(), key.getMoreKeys()); 178 } 179 return false; 180 } 181 hashCode(final Object... objects)182 private static int hashCode(final Object... objects) { 183 return Arrays.hashCode(objects); 184 } 185 186 @Override hashCode()187 public int hashCode() { 188 return hashCode(mVisual, mOutput, getMoreKeys()); 189 } 190 191 @Override toString()192 public String toString() { 193 if (mVisual.equalsTo(mOutput)) { 194 return mVisual.toString(); 195 } 196 return mVisual + "|" + mOutput; 197 } 198 199 /** 200 * This class represents an expected "additional more key". 201 * 202 * The additional more keys can be defined independently from other more keys. The position of 203 * the additional more keys in the long press popup keyboard can be controlled by specifying 204 * special marker "%" in the usual more keys definitions. 205 */ 206 public static class ExpectedAdditionalMoreKey extends ExpectedKey { newInstance(final String label)207 public static ExpectedAdditionalMoreKey newInstance(final String label) { 208 return new ExpectedAdditionalMoreKey(ExpectedKeyVisual.newInstance(label), 209 ExpectedKeyOutput.newInstance(label)); 210 } 211 newInstance(final ExpectedKey key)212 public static ExpectedAdditionalMoreKey newInstance(final ExpectedKey key) { 213 return new ExpectedAdditionalMoreKey(key.getVisual(), key.getOutput()); 214 } 215 ExpectedAdditionalMoreKey(final ExpectedKeyVisual visual, final ExpectedKeyOutput output)216 ExpectedAdditionalMoreKey(final ExpectedKeyVisual visual, final ExpectedKeyOutput output) { 217 super(visual, output); 218 } 219 220 @Override toUpperCase(final Locale locale)221 public ExpectedAdditionalMoreKey toUpperCase(final Locale locale) { 222 final ExpectedKey upperCaseKey = super.toUpperCase(locale); 223 return new ExpectedAdditionalMoreKey( 224 upperCaseKey.getVisual(), upperCaseKey.getOutput()); 225 } 226 } 227 228 /** 229 * This class represents an expected key that has "more keys". 230 */ 231 private static class ExpectedKeyWithMoreKeys extends ExpectedKey { 232 private final ExpectedKey[] mMoreKeys; 233 ExpectedKeyWithMoreKeys(final ExpectedKeyVisual visual, final ExpectedKeyOutput output, final ExpectedKey... moreKeys)234 ExpectedKeyWithMoreKeys(final ExpectedKeyVisual visual, final ExpectedKeyOutput output, 235 final ExpectedKey... moreKeys) { 236 super(visual, output); 237 mMoreKeys = moreKeys; 238 } 239 240 @Override toUpperCase(final Locale locale)241 public ExpectedKey toUpperCase(final Locale locale) { 242 final ExpectedKey[] upperCaseMoreKeys = new ExpectedKey[mMoreKeys.length]; 243 for (int i = 0; i < mMoreKeys.length; i++) { 244 upperCaseMoreKeys[i] = mMoreKeys[i].toUpperCase(locale); 245 } 246 return newInstance(getVisual().toUpperCase(locale), getOutput().toUpperCase(locale), 247 upperCaseMoreKeys); 248 } 249 250 @Override getMoreKeys()251 public ExpectedKey[] getMoreKeys() { 252 return mMoreKeys; 253 } 254 255 @Override setAdditionalMoreKeys( final ExpectedAdditionalMoreKey... additionalMoreKeys)256 public ExpectedKey setAdditionalMoreKeys( 257 final ExpectedAdditionalMoreKey... additionalMoreKeys) { 258 if (additionalMoreKeys.length == 0) { 259 return this; 260 } 261 return new ExpectedKeyWithMoreKeysAndAdditionalMoreKeys( 262 getVisual(), getOutput(), mMoreKeys, 0 /* additionalMoreKeysIndex */, 263 additionalMoreKeys); 264 } 265 266 @Override setAdditionalMoreKeysIndex(final int additionalMoreKeysIndex)267 public ExpectedKey setAdditionalMoreKeysIndex(final int additionalMoreKeysIndex) { 268 if (additionalMoreKeysIndex == 0) { 269 return this; 270 } 271 return new ExpectedKeyWithMoreKeysAndAdditionalMoreKeys( 272 getVisual(), getOutput(), mMoreKeys, additionalMoreKeysIndex); 273 } 274 275 @Override equalsTo(final Key key)276 public boolean equalsTo(final Key key) { 277 if (getVisual().equalsTo(key) && getOutput().equalsTo(key)) { 278 final MoreKeySpec[] moreKeySpecs = key.getMoreKeys(); 279 final ExpectedKey[] moreKeys = getMoreKeys(); 280 // This key should have at least one "more key". 281 if (moreKeySpecs == null || moreKeySpecs.length != moreKeys.length) { 282 return false; 283 } 284 for (int index = 0; index < moreKeySpecs.length; index++) { 285 if (!moreKeys[index].equalsTo(moreKeySpecs[index])) { 286 return false; 287 } 288 } 289 return true; 290 } 291 return false; 292 } 293 294 @Override equalsTo(final MoreKeySpec moreKeySpec)295 public boolean equalsTo(final MoreKeySpec moreKeySpec) { 296 // MoreKeySpec has no "more keys". 297 return false; 298 } 299 300 @Override toString()301 public String toString() { 302 return super.toString() + "^" + Arrays.toString(getMoreKeys()); 303 } 304 } 305 306 /** 307 * This class represents an expected key that has "more keys" and "additional more keys". 308 */ 309 private static final class ExpectedKeyWithMoreKeysAndAdditionalMoreKeys 310 extends ExpectedKeyWithMoreKeys { 311 private final ExpectedAdditionalMoreKey[] mAdditionalMoreKeys; 312 private final int mAdditionalMoreKeysIndex; 313 ExpectedKeyWithMoreKeysAndAdditionalMoreKeys(final ExpectedKeyVisual visual, final ExpectedKeyOutput output, final ExpectedKey[] moreKeys, final int additionalMoreKeysIndex, final ExpectedAdditionalMoreKey... additionalMoreKeys)314 ExpectedKeyWithMoreKeysAndAdditionalMoreKeys(final ExpectedKeyVisual visual, 315 final ExpectedKeyOutput output, final ExpectedKey[] moreKeys, 316 final int additionalMoreKeysIndex, 317 final ExpectedAdditionalMoreKey... additionalMoreKeys) { 318 super(visual, output, moreKeys); 319 mAdditionalMoreKeysIndex = additionalMoreKeysIndex; 320 mAdditionalMoreKeys = additionalMoreKeys; 321 } 322 323 @Override setMoreKeys(final ExpectedKey... moreKeys)324 public ExpectedKey setMoreKeys(final ExpectedKey... moreKeys) { 325 return new ExpectedKeyWithMoreKeysAndAdditionalMoreKeys( 326 getVisual(), getOutput(), moreKeys, mAdditionalMoreKeysIndex, 327 mAdditionalMoreKeys); 328 } 329 330 @Override setAdditionalMoreKeys( final ExpectedAdditionalMoreKey... additionalMoreKeys)331 public ExpectedKey setAdditionalMoreKeys( 332 final ExpectedAdditionalMoreKey... additionalMoreKeys) { 333 return new ExpectedKeyWithMoreKeysAndAdditionalMoreKeys( 334 getVisual(), getOutput(), super.getMoreKeys(), mAdditionalMoreKeysIndex, 335 additionalMoreKeys); 336 } 337 338 @Override setAdditionalMoreKeysIndex(final int additionalMoreKeysIndex)339 public ExpectedKey setAdditionalMoreKeysIndex(final int additionalMoreKeysIndex) { 340 return new ExpectedKeyWithMoreKeysAndAdditionalMoreKeys( 341 getVisual(), getOutput(), super.getMoreKeys(), additionalMoreKeysIndex, 342 mAdditionalMoreKeys); 343 } 344 345 @Override toUpperCase(final Locale locale)346 public ExpectedKey toUpperCase(final Locale locale) { 347 final ExpectedKey[] moreKeys = super.getMoreKeys(); 348 final ExpectedKey[] upperCaseMoreKeys = new ExpectedKey[moreKeys.length]; 349 for (int i = 0; i < moreKeys.length; i++) { 350 upperCaseMoreKeys[i] = moreKeys[i].toUpperCase(locale); 351 } 352 final ExpectedAdditionalMoreKey[] upperCaseAdditionalMoreKeys = 353 new ExpectedAdditionalMoreKey[mAdditionalMoreKeys.length]; 354 for (int i = 0; i < mAdditionalMoreKeys.length; i++) { 355 upperCaseAdditionalMoreKeys[i] = mAdditionalMoreKeys[i].toUpperCase(locale); 356 } 357 return new ExpectedKeyWithMoreKeysAndAdditionalMoreKeys( 358 getVisual().toUpperCase(locale), getOutput().toUpperCase(locale), 359 upperCaseMoreKeys, mAdditionalMoreKeysIndex, upperCaseAdditionalMoreKeys); 360 } 361 362 @Override getMoreKeys()363 public ExpectedKey[] getMoreKeys() { 364 final ExpectedKey[] moreKeys = super.getMoreKeys(); 365 final ExpectedKey[] edittedMoreKeys = Arrays.copyOf( 366 moreKeys, moreKeys.length + mAdditionalMoreKeys.length); 367 System.arraycopy(edittedMoreKeys, mAdditionalMoreKeysIndex, 368 edittedMoreKeys, mAdditionalMoreKeysIndex + mAdditionalMoreKeys.length, 369 moreKeys.length - mAdditionalMoreKeysIndex); 370 System.arraycopy(mAdditionalMoreKeys, 0, edittedMoreKeys, mAdditionalMoreKeysIndex, 371 mAdditionalMoreKeys.length); 372 return edittedMoreKeys; 373 } 374 } 375 } 376