1 /*
2  * Copyright (C) 2013 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.emoji;
18 
19 import android.content.SharedPreferences;
20 import android.text.TextUtils;
21 import android.util.Log;
22 
23 import com.android.inputmethod.keyboard.Key;
24 import com.android.inputmethod.keyboard.Keyboard;
25 import com.android.inputmethod.latin.settings.Settings;
26 import com.android.inputmethod.latin.utils.JsonUtils;
27 
28 import java.util.ArrayDeque;
29 import java.util.ArrayList;
30 import java.util.Collection;
31 import java.util.Collections;
32 import java.util.List;
33 
34 /**
35  * This is a Keyboard class where you can add keys dynamically shown in a grid layout
36  */
37 final class DynamicGridKeyboard extends Keyboard {
38     private static final String TAG = DynamicGridKeyboard.class.getSimpleName();
39     private static final int TEMPLATE_KEY_CODE_0 = 0x30;
40     private static final int TEMPLATE_KEY_CODE_1 = 0x31;
41     private final Object mLock = new Object();
42 
43     private final SharedPreferences mPrefs;
44     private final int mHorizontalStep;
45     private final int mVerticalStep;
46     private final int mColumnsNum;
47     private final int mMaxKeyCount;
48     private final boolean mIsRecents;
49     private final ArrayDeque<GridKey> mGridKeys = new ArrayDeque<>();
50     private final ArrayDeque<Key> mPendingKeys = new ArrayDeque<>();
51 
52     private List<Key> mCachedGridKeys;
53 
DynamicGridKeyboard(final SharedPreferences prefs, final Keyboard templateKeyboard, final int maxKeyCount, final int categoryId)54     public DynamicGridKeyboard(final SharedPreferences prefs, final Keyboard templateKeyboard,
55             final int maxKeyCount, final int categoryId) {
56         super(templateKeyboard);
57         final Key key0 = getTemplateKey(TEMPLATE_KEY_CODE_0);
58         final Key key1 = getTemplateKey(TEMPLATE_KEY_CODE_1);
59         mHorizontalStep = Math.abs(key1.getX() - key0.getX());
60         mVerticalStep = key0.getHeight() + mVerticalGap;
61         mColumnsNum = mBaseWidth / mHorizontalStep;
62         mMaxKeyCount = maxKeyCount;
63         mIsRecents = categoryId == EmojiCategory.ID_RECENTS;
64         mPrefs = prefs;
65     }
66 
getTemplateKey(final int code)67     private Key getTemplateKey(final int code) {
68         for (final Key key : super.getSortedKeys()) {
69             if (key.getCode() == code) {
70                 return key;
71             }
72         }
73         throw new RuntimeException("Can't find template key: code=" + code);
74     }
75 
addPendingKey(final Key usedKey)76     public void addPendingKey(final Key usedKey) {
77         synchronized (mLock) {
78             mPendingKeys.addLast(usedKey);
79         }
80     }
81 
flushPendingRecentKeys()82     public void flushPendingRecentKeys() {
83         synchronized (mLock) {
84             while (!mPendingKeys.isEmpty()) {
85                 addKey(mPendingKeys.pollFirst(), true);
86             }
87             saveRecentKeys();
88         }
89     }
90 
addKeyFirst(final Key usedKey)91     public void addKeyFirst(final Key usedKey) {
92         addKey(usedKey, true);
93         if (mIsRecents) {
94             saveRecentKeys();
95         }
96     }
97 
addKeyLast(final Key usedKey)98     public void addKeyLast(final Key usedKey) {
99         addKey(usedKey, false);
100     }
101 
addKey(final Key usedKey, final boolean addFirst)102     private void addKey(final Key usedKey, final boolean addFirst) {
103         if (usedKey == null) {
104             return;
105         }
106         synchronized (mLock) {
107             mCachedGridKeys = null;
108             final GridKey key = new GridKey(usedKey);
109             while (mGridKeys.remove(key)) {
110                 // Remove duplicate keys.
111             }
112             if (addFirst) {
113                 mGridKeys.addFirst(key);
114             } else {
115                 mGridKeys.addLast(key);
116             }
117             while (mGridKeys.size() > mMaxKeyCount) {
118                 mGridKeys.removeLast();
119             }
120             int index = 0;
121             for (final GridKey gridKey : mGridKeys) {
122                 final int keyX0 = getKeyX0(index);
123                 final int keyY0 = getKeyY0(index);
124                 final int keyX1 = getKeyX1(index);
125                 final int keyY1 = getKeyY1(index);
126                 gridKey.updateCoordinates(keyX0, keyY0, keyX1, keyY1);
127                 index++;
128             }
129         }
130     }
131 
saveRecentKeys()132     private void saveRecentKeys() {
133         final ArrayList<Object> keys = new ArrayList<>();
134         for (final Key key : mGridKeys) {
135             if (key.getOutputText() != null) {
136                 keys.add(key.getOutputText());
137             } else {
138                 keys.add(key.getCode());
139             }
140         }
141         final String jsonStr = JsonUtils.listToJsonStr(keys);
142         Settings.writeEmojiRecentKeys(mPrefs, jsonStr);
143     }
144 
getKeyByCode(final Collection<DynamicGridKeyboard> keyboards, final int code)145     private static Key getKeyByCode(final Collection<DynamicGridKeyboard> keyboards,
146             final int code) {
147         for (final DynamicGridKeyboard keyboard : keyboards) {
148             for (final Key key : keyboard.getSortedKeys()) {
149                 if (key.getCode() == code) {
150                     return key;
151                 }
152             }
153         }
154         return null;
155     }
156 
getKeyByOutputText(final Collection<DynamicGridKeyboard> keyboards, final String outputText)157     private static Key getKeyByOutputText(final Collection<DynamicGridKeyboard> keyboards,
158             final String outputText) {
159         for (final DynamicGridKeyboard keyboard : keyboards) {
160             for (final Key key : keyboard.getSortedKeys()) {
161                 if (outputText.equals(key.getOutputText())) {
162                     return key;
163                 }
164             }
165         }
166         return null;
167     }
168 
loadRecentKeys(final Collection<DynamicGridKeyboard> keyboards)169     public void loadRecentKeys(final Collection<DynamicGridKeyboard> keyboards) {
170         final String str = Settings.readEmojiRecentKeys(mPrefs);
171         final List<Object> keys = JsonUtils.jsonStrToList(str);
172         for (final Object o : keys) {
173             final Key key;
174             if (o instanceof Integer) {
175                 final int code = (Integer)o;
176                 key = getKeyByCode(keyboards, code);
177             } else if (o instanceof String) {
178                 final String outputText = (String)o;
179                 key = getKeyByOutputText(keyboards, outputText);
180             } else {
181                 Log.w(TAG, "Invalid object: " + o);
182                 continue;
183             }
184             addKeyLast(key);
185         }
186     }
187 
getKeyX0(final int index)188     private int getKeyX0(final int index) {
189         final int column = index % mColumnsNum;
190         return column * mHorizontalStep;
191     }
192 
getKeyX1(final int index)193     private int getKeyX1(final int index) {
194         final int column = index % mColumnsNum + 1;
195         return column * mHorizontalStep;
196     }
197 
getKeyY0(final int index)198     private int getKeyY0(final int index) {
199         final int row = index / mColumnsNum;
200         return row * mVerticalStep + mVerticalGap / 2;
201     }
202 
getKeyY1(final int index)203     private int getKeyY1(final int index) {
204         final int row = index / mColumnsNum + 1;
205         return row * mVerticalStep + mVerticalGap / 2;
206     }
207 
208     @Override
getSortedKeys()209     public List<Key> getSortedKeys() {
210         synchronized (mLock) {
211             if (mCachedGridKeys != null) {
212                 return mCachedGridKeys;
213             }
214             final ArrayList<Key> cachedKeys = new ArrayList<Key>(mGridKeys);
215             mCachedGridKeys = Collections.unmodifiableList(cachedKeys);
216             return mCachedGridKeys;
217         }
218     }
219 
220     @Override
getNearestKeys(final int x, final int y)221     public List<Key> getNearestKeys(final int x, final int y) {
222         // TODO: Calculate the nearest key index in mGridKeys from x and y.
223         return getSortedKeys();
224     }
225 
226     static final class GridKey extends Key {
227         private int mCurrentX;
228         private int mCurrentY;
229 
GridKey(final Key originalKey)230         public GridKey(final Key originalKey) {
231             super(originalKey);
232         }
233 
updateCoordinates(final int x0, final int y0, final int x1, final int y1)234         public void updateCoordinates(final int x0, final int y0, final int x1, final int y1) {
235             mCurrentX = x0;
236             mCurrentY = y0;
237             getHitBox().set(x0, y0, x1, y1);
238         }
239 
240         @Override
getX()241         public int getX() {
242             return mCurrentX;
243         }
244 
245         @Override
getY()246         public int getY() {
247             return mCurrentY;
248         }
249 
250         @Override
equals(final Object o)251         public boolean equals(final Object o) {
252             if (!(o instanceof Key)) return false;
253             final Key key = (Key)o;
254             if (getCode() != key.getCode()) return false;
255             if (!TextUtils.equals(getLabel(), key.getLabel())) return false;
256             return TextUtils.equals(getOutputText(), key.getOutputText());
257         }
258 
259         @Override
toString()260         public String toString() {
261             return "GridKey: " + super.toString();
262         }
263     }
264 }
265