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