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 public final ExpectedKeyVisual getVisual() { 109 return mVisual; 110 } 111 112 public 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.equalsTo(key) && mOutput.equalsTo(key) && key.getMoreKeys() == null; 166 } 167 168 public boolean equalsTo(final MoreKeySpec moreKeySpec) { 169 return mVisual.equalsTo(moreKeySpec) && mOutput.equalsTo(moreKeySpec); 170 } 171 172 @Override 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 182 private static int hashCode(final Object... objects) { 183 return Arrays.hashCode(objects); 184 } 185 186 @Override 187 public int hashCode() { 188 return hashCode(mVisual, mOutput, getMoreKeys()); 189 } 190 191 @Override 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 { 207 public static ExpectedAdditionalMoreKey newInstance(final String label) { 208 return new ExpectedAdditionalMoreKey(ExpectedKeyVisual.newInstance(label), 209 ExpectedKeyOutput.newInstance(label)); 210 } 211 212 public static ExpectedAdditionalMoreKey newInstance(final ExpectedKey key) { 213 return new ExpectedAdditionalMoreKey(key.getVisual(), key.getOutput()); 214 } 215 216 ExpectedAdditionalMoreKey(final ExpectedKeyVisual visual, final ExpectedKeyOutput output) { 217 super(visual, output); 218 } 219 220 @Override 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 234 ExpectedKeyWithMoreKeys(final ExpectedKeyVisual visual, final ExpectedKeyOutput output, 235 final ExpectedKey... moreKeys) { 236 super(visual, output); 237 mMoreKeys = moreKeys; 238 } 239 240 @Override 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 251 public ExpectedKey[] getMoreKeys() { 252 return mMoreKeys; 253 } 254 255 @Override 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 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 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 295 public boolean equalsTo(final MoreKeySpec moreKeySpec) { 296 // MoreKeySpec has no "more keys". 297 return false; 298 } 299 300 @Override 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 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 324 public ExpectedKey setMoreKeys(final ExpectedKey... moreKeys) { 325 return new ExpectedKeyWithMoreKeysAndAdditionalMoreKeys( 326 getVisual(), getOutput(), moreKeys, mAdditionalMoreKeysIndex, 327 mAdditionalMoreKeys); 328 } 329 330 @Override 331 public ExpectedKey setAdditionalMoreKeys( 332 final ExpectedAdditionalMoreKey... additionalMoreKeys) { 333 return new ExpectedKeyWithMoreKeysAndAdditionalMoreKeys( 334 getVisual(), getOutput(), super.getMoreKeys(), mAdditionalMoreKeysIndex, 335 additionalMoreKeys); 336 } 337 338 @Override 339 public ExpectedKey setAdditionalMoreKeysIndex(final int additionalMoreKeysIndex) { 340 return new ExpectedKeyWithMoreKeysAndAdditionalMoreKeys( 341 getVisual(), getOutput(), super.getMoreKeys(), additionalMoreKeysIndex, 342 mAdditionalMoreKeys); 343 } 344 345 @Override 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 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