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