Home | History | Annotate | Download | only in expected
      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