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