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     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