1803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka/*
2803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka * Copyright (C) 2013 The Android Open Source Project
3803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka *
4803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka * Licensed under the Apache License, Version 2.0 (the "License");
5803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka * you may not use this file except in compliance with the License.
6803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka * You may obtain a copy of the License at
7803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka *
8803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka *      http://www.apache.org/licenses/LICENSE-2.0
9803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka *
10803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka * Unless required by applicable law or agreed to in writing, software
11803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka * distributed under the License is distributed on an "AS IS" BASIS,
12803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka * See the License for the specific language governing permissions and
14803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka * limitations under the License.
15803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka */
16803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka
179f4d62cc42ab66f72ecb23996ffc2f8b039c8c4aTadashi G. Takaokapackage com.android.inputmethod.keyboard.emoji;
18803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka
19f3f00006cbe2046abbad3a901b436d67497a40f9Satoshi Kataokaimport android.content.SharedPreferences;
20803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaokaimport android.text.TextUtils;
21ea7bfa5cd58c4ce0fed5d79c3118d5339ef4e66aSatoshi Kataokaimport android.util.Log;
22803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka
23803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaokaimport com.android.inputmethod.keyboard.Key;
24803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaokaimport com.android.inputmethod.keyboard.Keyboard;
25f3f00006cbe2046abbad3a901b436d67497a40f9Satoshi Kataokaimport com.android.inputmethod.latin.settings.Settings;
262fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasaimport com.android.inputmethod.latin.utils.JsonUtils;
27803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka
28803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaokaimport java.util.ArrayDeque;
29ea7bfa5cd58c4ce0fed5d79c3118d5339ef4e66aSatoshi Kataokaimport java.util.ArrayList;
30f3f00006cbe2046abbad3a901b436d67497a40f9Satoshi Kataokaimport java.util.Collection;
315326dcfb7dbdc1a3fc9cfb94046805f18bf3d3d7Tadashi G. Takaokaimport java.util.Collections;
32ea7bfa5cd58c4ce0fed5d79c3118d5339ef4e66aSatoshi Kataokaimport java.util.List;
33803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka
34803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka/**
352c011c697b70bb8c1ce519b1d38f099ad67a5c5eSatoshi Kataoka * This is a Keyboard class where you can add keys dynamically shown in a grid layout
36803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka */
379f4d62cc42ab66f72ecb23996ffc2f8b039c8c4aTadashi G. Takaokafinal class DynamicGridKeyboard extends Keyboard {
38ea7bfa5cd58c4ce0fed5d79c3118d5339ef4e66aSatoshi Kataoka    private static final String TAG = DynamicGridKeyboard.class.getSimpleName();
39803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka    private static final int TEMPLATE_KEY_CODE_0 = 0x30;
40803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka    private static final int TEMPLATE_KEY_CODE_1 = 0x31;
41e7ed5cae44b52f22d866e60f5e3c7e87f1375a1fSatoshi Kataoka    private final Object mLock = new Object();
42803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka
43f3f00006cbe2046abbad3a901b436d67497a40f9Satoshi Kataoka    private final SharedPreferences mPrefs;
44803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka    private final int mHorizontalStep;
45803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka    private final int mVerticalStep;
46803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka    private final int mColumnsNum;
47b0bf7e729b5a3ec6dc481d72f04d7dad0e12672aSatoshi Kataoka    private final int mMaxKeyCount;
48f3f00006cbe2046abbad3a901b436d67497a40f9Satoshi Kataoka    private final boolean mIsRecents;
49a91561aa58db1c43092c1caecc051a11fa5391c7Tadashi G. Takaoka    private final ArrayDeque<GridKey> mGridKeys = new ArrayDeque<>();
50a91561aa58db1c43092c1caecc051a11fa5391c7Tadashi G. Takaoka    private final ArrayDeque<Key> mPendingKeys = new ArrayDeque<>();
51803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka
525326dcfb7dbdc1a3fc9cfb94046805f18bf3d3d7Tadashi G. Takaoka    private List<Key> mCachedGridKeys;
53803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka
54f3f00006cbe2046abbad3a901b436d67497a40f9Satoshi Kataoka    public DynamicGridKeyboard(final SharedPreferences prefs, final Keyboard templateKeyboard,
552fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa            final int maxKeyCount, final int categoryId) {
56803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka        super(templateKeyboard);
57803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka        final Key key0 = getTemplateKey(TEMPLATE_KEY_CODE_0);
58803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka        final Key key1 = getTemplateKey(TEMPLATE_KEY_CODE_1);
59803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka        mHorizontalStep = Math.abs(key1.getX() - key0.getX());
60803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka        mVerticalStep = key0.getHeight() + mVerticalGap;
61803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka        mColumnsNum = mBaseWidth / mHorizontalStep;
625dfbc8af2439152ca6b6759309aced5bb879b518Satoshi Kataoka        mMaxKeyCount = maxKeyCount;
63ead058b00216339a8688c604886645fce42fee4aTadashi G. Takaoka        mIsRecents = categoryId == EmojiCategory.ID_RECENTS;
64f3f00006cbe2046abbad3a901b436d67497a40f9Satoshi Kataoka        mPrefs = prefs;
65803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka    }
66803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka
67803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka    private Key getTemplateKey(final int code) {
68c13c1adfa72227b0006add5f13f555fbb9c9eb4eTadashi G. Takaoka        for (final Key key : super.getSortedKeys()) {
69803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka            if (key.getCode() == code) {
70803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka                return key;
71803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka            }
72803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka        }
73803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka        throw new RuntimeException("Can't find template key: code=" + code);
74803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka    }
75803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka
76e7ed5cae44b52f22d866e60f5e3c7e87f1375a1fSatoshi Kataoka    public void addPendingKey(final Key usedKey) {
77e7ed5cae44b52f22d866e60f5e3c7e87f1375a1fSatoshi Kataoka        synchronized (mLock) {
78e7ed5cae44b52f22d866e60f5e3c7e87f1375a1fSatoshi Kataoka            mPendingKeys.addLast(usedKey);
79e7ed5cae44b52f22d866e60f5e3c7e87f1375a1fSatoshi Kataoka        }
80e7ed5cae44b52f22d866e60f5e3c7e87f1375a1fSatoshi Kataoka    }
81e7ed5cae44b52f22d866e60f5e3c7e87f1375a1fSatoshi Kataoka
82e7ed5cae44b52f22d866e60f5e3c7e87f1375a1fSatoshi Kataoka    public void flushPendingRecentKeys() {
83e7ed5cae44b52f22d866e60f5e3c7e87f1375a1fSatoshi Kataoka        synchronized (mLock) {
84e7ed5cae44b52f22d866e60f5e3c7e87f1375a1fSatoshi Kataoka            while (!mPendingKeys.isEmpty()) {
85e7ed5cae44b52f22d866e60f5e3c7e87f1375a1fSatoshi Kataoka                addKey(mPendingKeys.pollFirst(), true);
86e7ed5cae44b52f22d866e60f5e3c7e87f1375a1fSatoshi Kataoka            }
87e7ed5cae44b52f22d866e60f5e3c7e87f1375a1fSatoshi Kataoka            saveRecentKeys();
88e7ed5cae44b52f22d866e60f5e3c7e87f1375a1fSatoshi Kataoka        }
89e7ed5cae44b52f22d866e60f5e3c7e87f1375a1fSatoshi Kataoka    }
90e7ed5cae44b52f22d866e60f5e3c7e87f1375a1fSatoshi Kataoka
91b0bf7e729b5a3ec6dc481d72f04d7dad0e12672aSatoshi Kataoka    public void addKeyFirst(final Key usedKey) {
92b0bf7e729b5a3ec6dc481d72f04d7dad0e12672aSatoshi Kataoka        addKey(usedKey, true);
93f3f00006cbe2046abbad3a901b436d67497a40f9Satoshi Kataoka        if (mIsRecents) {
94f3f00006cbe2046abbad3a901b436d67497a40f9Satoshi Kataoka            saveRecentKeys();
95f3f00006cbe2046abbad3a901b436d67497a40f9Satoshi Kataoka        }
96b0bf7e729b5a3ec6dc481d72f04d7dad0e12672aSatoshi Kataoka    }
97b0bf7e729b5a3ec6dc481d72f04d7dad0e12672aSatoshi Kataoka
98b0bf7e729b5a3ec6dc481d72f04d7dad0e12672aSatoshi Kataoka    public void addKeyLast(final Key usedKey) {
99b0bf7e729b5a3ec6dc481d72f04d7dad0e12672aSatoshi Kataoka        addKey(usedKey, false);
100b0bf7e729b5a3ec6dc481d72f04d7dad0e12672aSatoshi Kataoka    }
101803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka
102b0bf7e729b5a3ec6dc481d72f04d7dad0e12672aSatoshi Kataoka    private void addKey(final Key usedKey, final boolean addFirst) {
103ea7bfa5cd58c4ce0fed5d79c3118d5339ef4e66aSatoshi Kataoka        if (usedKey == null) {
104ea7bfa5cd58c4ce0fed5d79c3118d5339ef4e66aSatoshi Kataoka            return;
105ea7bfa5cd58c4ce0fed5d79c3118d5339ef4e66aSatoshi Kataoka        }
106e7ed5cae44b52f22d866e60f5e3c7e87f1375a1fSatoshi Kataoka        synchronized (mLock) {
107b0bf7e729b5a3ec6dc481d72f04d7dad0e12672aSatoshi Kataoka            mCachedGridKeys = null;
108b0bf7e729b5a3ec6dc481d72f04d7dad0e12672aSatoshi Kataoka            final GridKey key = new GridKey(usedKey);
109b0bf7e729b5a3ec6dc481d72f04d7dad0e12672aSatoshi Kataoka            while (mGridKeys.remove(key)) {
110803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka                // Remove duplicate keys.
111803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka            }
112b0bf7e729b5a3ec6dc481d72f04d7dad0e12672aSatoshi Kataoka            if (addFirst) {
113b0bf7e729b5a3ec6dc481d72f04d7dad0e12672aSatoshi Kataoka                mGridKeys.addFirst(key);
114b0bf7e729b5a3ec6dc481d72f04d7dad0e12672aSatoshi Kataoka            } else {
115b0bf7e729b5a3ec6dc481d72f04d7dad0e12672aSatoshi Kataoka                mGridKeys.addLast(key);
116b0bf7e729b5a3ec6dc481d72f04d7dad0e12672aSatoshi Kataoka            }
117b0bf7e729b5a3ec6dc481d72f04d7dad0e12672aSatoshi Kataoka            while (mGridKeys.size() > mMaxKeyCount) {
118b0bf7e729b5a3ec6dc481d72f04d7dad0e12672aSatoshi Kataoka                mGridKeys.removeLast();
119803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka            }
120803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka            int index = 0;
121b0bf7e729b5a3ec6dc481d72f04d7dad0e12672aSatoshi Kataoka            for (final GridKey gridKey : mGridKeys) {
1225d27cb939771b1bacf37843c440bb9488eaa8797Satoshi Kataoka                final int keyX0 = getKeyX0(index);
1235d27cb939771b1bacf37843c440bb9488eaa8797Satoshi Kataoka                final int keyY0 = getKeyY0(index);
1245d27cb939771b1bacf37843c440bb9488eaa8797Satoshi Kataoka                final int keyX1 = getKeyX1(index);
1255d27cb939771b1bacf37843c440bb9488eaa8797Satoshi Kataoka                final int keyY1 = getKeyY1(index);
1262fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa                gridKey.updateCoordinates(keyX0, keyY0, keyX1, keyY1);
127803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka                index++;
128803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka            }
129803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka        }
130803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka    }
131803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka
132f3f00006cbe2046abbad3a901b436d67497a40f9Satoshi Kataoka    private void saveRecentKeys() {
133a91561aa58db1c43092c1caecc051a11fa5391c7Tadashi G. Takaoka        final ArrayList<Object> keys = new ArrayList<>();
134f3f00006cbe2046abbad3a901b436d67497a40f9Satoshi Kataoka        for (final Key key : mGridKeys) {
135ea7bfa5cd58c4ce0fed5d79c3118d5339ef4e66aSatoshi Kataoka            if (key.getOutputText() != null) {
136ea7bfa5cd58c4ce0fed5d79c3118d5339ef4e66aSatoshi Kataoka                keys.add(key.getOutputText());
137ea7bfa5cd58c4ce0fed5d79c3118d5339ef4e66aSatoshi Kataoka            } else {
138ea7bfa5cd58c4ce0fed5d79c3118d5339ef4e66aSatoshi Kataoka                keys.add(key.getCode());
139ea7bfa5cd58c4ce0fed5d79c3118d5339ef4e66aSatoshi Kataoka            }
140f3f00006cbe2046abbad3a901b436d67497a40f9Satoshi Kataoka        }
1412fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa        final String jsonStr = JsonUtils.listToJsonStr(keys);
142ea7bfa5cd58c4ce0fed5d79c3118d5339ef4e66aSatoshi Kataoka        Settings.writeEmojiRecentKeys(mPrefs, jsonStr);
143f3f00006cbe2046abbad3a901b436d67497a40f9Satoshi Kataoka    }
144f3f00006cbe2046abbad3a901b436d67497a40f9Satoshi Kataoka
1452fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa    private static Key getKeyByCode(final Collection<DynamicGridKeyboard> keyboards,
1462fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa            final int code) {
1472fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa        for (final DynamicGridKeyboard keyboard : keyboards) {
1480ea82be889df77546956c0fe93664622fa4ccb29Tadashi G. Takaoka            for (final Key key : keyboard.getSortedKeys()) {
1490ea82be889df77546956c0fe93664622fa4ccb29Tadashi G. Takaoka                if (key.getCode() == code) {
1500ea82be889df77546956c0fe93664622fa4ccb29Tadashi G. Takaoka                    return key;
1510ea82be889df77546956c0fe93664622fa4ccb29Tadashi G. Takaoka                }
1522fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa            }
1532fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa        }
1542fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa        return null;
1552fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa    }
1562fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa
1572fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa    private static Key getKeyByOutputText(final Collection<DynamicGridKeyboard> keyboards,
1582fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa            final String outputText) {
1590ea82be889df77546956c0fe93664622fa4ccb29Tadashi G. Takaoka        for (final DynamicGridKeyboard keyboard : keyboards) {
1600ea82be889df77546956c0fe93664622fa4ccb29Tadashi G. Takaoka            for (final Key key : keyboard.getSortedKeys()) {
1610ea82be889df77546956c0fe93664622fa4ccb29Tadashi G. Takaoka                if (outputText.equals(key.getOutputText())) {
1620ea82be889df77546956c0fe93664622fa4ccb29Tadashi G. Takaoka                    return key;
1630ea82be889df77546956c0fe93664622fa4ccb29Tadashi G. Takaoka                }
164f3f00006cbe2046abbad3a901b436d67497a40f9Satoshi Kataoka            }
165f3f00006cbe2046abbad3a901b436d67497a40f9Satoshi Kataoka        }
166ea7bfa5cd58c4ce0fed5d79c3118d5339ef4e66aSatoshi Kataoka        return null;
167ea7bfa5cd58c4ce0fed5d79c3118d5339ef4e66aSatoshi Kataoka    }
168ea7bfa5cd58c4ce0fed5d79c3118d5339ef4e66aSatoshi Kataoka
1692fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa    public void loadRecentKeys(final Collection<DynamicGridKeyboard> keyboards) {
170ea7bfa5cd58c4ce0fed5d79c3118d5339ef4e66aSatoshi Kataoka        final String str = Settings.readEmojiRecentKeys(mPrefs);
1712fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa        final List<Object> keys = JsonUtils.jsonStrToList(str);
172ea7bfa5cd58c4ce0fed5d79c3118d5339ef4e66aSatoshi Kataoka        for (final Object o : keys) {
1732fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa            final Key key;
1742fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa            if (o instanceof Integer) {
1752fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa                final int code = (Integer)o;
1762fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa                key = getKeyByCode(keyboards, code);
1772fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa            } else if (o instanceof String) {
1782fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa                final String outputText = (String)o;
1792fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa                key = getKeyByOutputText(keyboards, outputText);
1802fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa            } else {
1812fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa                Log.w(TAG, "Invalid object: " + o);
1822fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa                continue;
1832fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa            }
1842fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa            addKeyLast(key);
185ea7bfa5cd58c4ce0fed5d79c3118d5339ef4e66aSatoshi Kataoka        }
186f3f00006cbe2046abbad3a901b436d67497a40f9Satoshi Kataoka    }
187f3f00006cbe2046abbad3a901b436d67497a40f9Satoshi Kataoka
1885d27cb939771b1bacf37843c440bb9488eaa8797Satoshi Kataoka    private int getKeyX0(final int index) {
189803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka        final int column = index % mColumnsNum;
1905d27cb939771b1bacf37843c440bb9488eaa8797Satoshi Kataoka        return column * mHorizontalStep;
191803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka    }
192803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka
1935d27cb939771b1bacf37843c440bb9488eaa8797Satoshi Kataoka    private int getKeyX1(final int index) {
1945d27cb939771b1bacf37843c440bb9488eaa8797Satoshi Kataoka        final int column = index % mColumnsNum + 1;
1955d27cb939771b1bacf37843c440bb9488eaa8797Satoshi Kataoka        return column * mHorizontalStep;
1965d27cb939771b1bacf37843c440bb9488eaa8797Satoshi Kataoka    }
1975d27cb939771b1bacf37843c440bb9488eaa8797Satoshi Kataoka
1985d27cb939771b1bacf37843c440bb9488eaa8797Satoshi Kataoka    private int getKeyY0(final int index) {
199803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka        final int row = index / mColumnsNum;
20030ef03d865ec78469f26983f9c3e74f4e2c1bdd0Satoshi Kataoka        return row * mVerticalStep + mVerticalGap / 2;
2015d27cb939771b1bacf37843c440bb9488eaa8797Satoshi Kataoka    }
2025d27cb939771b1bacf37843c440bb9488eaa8797Satoshi Kataoka
2035d27cb939771b1bacf37843c440bb9488eaa8797Satoshi Kataoka    private int getKeyY1(final int index) {
2045d27cb939771b1bacf37843c440bb9488eaa8797Satoshi Kataoka        final int row = index / mColumnsNum + 1;
20530ef03d865ec78469f26983f9c3e74f4e2c1bdd0Satoshi Kataoka        return row * mVerticalStep + mVerticalGap / 2;
206803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka    }
207803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka
208803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka    @Override
209c13c1adfa72227b0006add5f13f555fbb9c9eb4eTadashi G. Takaoka    public List<Key> getSortedKeys() {
210e7ed5cae44b52f22d866e60f5e3c7e87f1375a1fSatoshi Kataoka        synchronized (mLock) {
211b0bf7e729b5a3ec6dc481d72f04d7dad0e12672aSatoshi Kataoka            if (mCachedGridKeys != null) {
212b0bf7e729b5a3ec6dc481d72f04d7dad0e12672aSatoshi Kataoka                return mCachedGridKeys;
213803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka            }
214965621574dc102a1578636f641e5eb8717fb3601Tadashi G. Takaoka            final ArrayList<Key> cachedKeys = new ArrayList<Key>(mGridKeys);
2155326dcfb7dbdc1a3fc9cfb94046805f18bf3d3d7Tadashi G. Takaoka            mCachedGridKeys = Collections.unmodifiableList(cachedKeys);
216b0bf7e729b5a3ec6dc481d72f04d7dad0e12672aSatoshi Kataoka            return mCachedGridKeys;
217803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka        }
218803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka    }
219803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka
220803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka    @Override
22158d4e610ac705fbfb49d8ec8d893a35ac416668eTadashi G. Takaoka    public List<Key> getNearestKeys(final int x, final int y) {
222b0bf7e729b5a3ec6dc481d72f04d7dad0e12672aSatoshi Kataoka        // TODO: Calculate the nearest key index in mGridKeys from x and y.
223c13c1adfa72227b0006add5f13f555fbb9c9eb4eTadashi G. Takaoka        return getSortedKeys();
224803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka    }
225803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka
226b0bf7e729b5a3ec6dc481d72f04d7dad0e12672aSatoshi Kataoka    static final class GridKey extends Key {
227803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka        private int mCurrentX;
228803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka        private int mCurrentY;
229803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka
230b0bf7e729b5a3ec6dc481d72f04d7dad0e12672aSatoshi Kataoka        public GridKey(final Key originalKey) {
231803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka            super(originalKey);
232803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka        }
233803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka
2342fa3693c264a4c150ac307d9bb7f6f8f18cc4ffcKen Wakasa        public void updateCoordinates(final int x0, final int y0, final int x1, final int y1) {
2355d27cb939771b1bacf37843c440bb9488eaa8797Satoshi Kataoka            mCurrentX = x0;
2365d27cb939771b1bacf37843c440bb9488eaa8797Satoshi Kataoka            mCurrentY = y0;
2375d27cb939771b1bacf37843c440bb9488eaa8797Satoshi Kataoka            getHitBox().set(x0, y0, x1, y1);
238803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka        }
239803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka
240803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka        @Override
241803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka        public int getX() {
242803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka            return mCurrentX;
243803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka        }
244803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka
245803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka        @Override
246803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka        public int getY() {
247803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka            return mCurrentY;
248803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka        }
249803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka
250803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka        @Override
251803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka        public boolean equals(final Object o) {
252803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka            if (!(o instanceof Key)) return false;
253803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka            final Key key = (Key)o;
254803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka            if (getCode() != key.getCode()) return false;
255803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka            if (!TextUtils.equals(getLabel(), key.getLabel())) return false;
256803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka            return TextUtils.equals(getOutputText(), key.getOutputText());
257803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka        }
258803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka
259803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka        @Override
260803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka        public String toString() {
261b0bf7e729b5a3ec6dc481d72f04d7dad0e12672aSatoshi Kataoka            return "GridKey: " + super.toString();
262803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka        }
263803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka    }
264803ea61d552e9c1df34f5d58f1e59e5b7612d6e1Tadashi G. Takaoka}
265