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". 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". 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". 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". 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". 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 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 108 protected final ExpectedKeyVisual getVisual() { 109 return mVisual; 110 } 111 112 protected final ExpectedKeyOutput getOutput() { 113 return mOutput; 114 } 115 116 public ExpectedKey[] getMoreKeys() { 117 // This key has no "more keys". 118 return EMPTY_KEYS; 119 } 120 121 public ExpectedKey setMoreKeys(final ExpectedKey... moreKeys) { 122 return newInstance(mVisual, mOutput, moreKeys); 123 } 124 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 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 142 protected ExpectedKey(final ExpectedKeyVisual visual, final ExpectedKeyOutput output) { 143 mVisual = visual; 144 mOutput = output; 145 } 146 147 public ExpectedKey toUpperCase(Locale locale) { 148 return newInstance(mVisual.toUpperCase(locale), mOutput.toUpperCase(locale)); 149 } 150 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 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 169 public boolean equalsTo(final MoreKeySpec moreKeySpec) { 170 return mVisual.hasSameKeyVisual(moreKeySpec) && mOutput.hasSameKeyOutput(moreKeySpec); 171 } 172 173 @Override 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 183 private static int hashCode(final Object... objects) { 184 return Arrays.hashCode(objects); 185 } 186 187 @Override 188 public int hashCode() { 189 return hashCode(mVisual, mOutput, getMoreKeys()); 190 } 191 192 @Override 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 { 208 public static ExpectedAdditionalMoreKey newInstance(final String label) { 209 return new ExpectedAdditionalMoreKey(ExpectedKeyVisual.newInstance(label), 210 ExpectedKeyOutput.newInstance(label)); 211 } 212 213 public static ExpectedAdditionalMoreKey newInstance(final ExpectedKey key) { 214 return new ExpectedAdditionalMoreKey(key.getVisual(), key.getOutput()); 215 } 216 217 ExpectedAdditionalMoreKey(final ExpectedKeyVisual visual, final ExpectedKeyOutput output) { 218 super(visual, output); 219 } 220 221 @Override 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 235 ExpectedKeyWithMoreKeys(final ExpectedKeyVisual visual, final ExpectedKeyOutput output, 236 final ExpectedKey... moreKeys) { 237 super(visual, output); 238 mMoreKeys = moreKeys; 239 } 240 241 @Override 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 252 public ExpectedKey[] getMoreKeys() { 253 return mMoreKeys; 254 } 255 256 @Override 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 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 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 296 public boolean equalsTo(final MoreKeySpec moreKeySpec) { 297 // MoreKeySpec has no "more keys". 298 return false; 299 } 300 301 @Override 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 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 325 public ExpectedKey setMoreKeys(final ExpectedKey... moreKeys) { 326 return new ExpectedKeyWithMoreKeysAndAdditionalMoreKeys( 327 getVisual(), getOutput(), moreKeys, mAdditionalMoreKeysIndex, 328 mAdditionalMoreKeys); 329 } 330 331 @Override 332 public ExpectedKey setAdditionalMoreKeys( 333 final ExpectedAdditionalMoreKey... additionalMoreKeys) { 334 return new ExpectedKeyWithMoreKeysAndAdditionalMoreKeys( 335 getVisual(), getOutput(), super.getMoreKeys(), mAdditionalMoreKeysIndex, 336 additionalMoreKeys); 337 } 338 339 @Override 340 public ExpectedKey setAdditionalMoreKeysIndex(final int additionalMoreKeysIndex) { 341 return new ExpectedKeyWithMoreKeysAndAdditionalMoreKeys( 342 getVisual(), getOutput(), super.getMoreKeys(), additionalMoreKeysIndex, 343 mAdditionalMoreKeys); 344 } 345 346 @Override 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 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