Home | History | Annotate | Download | only in emoji
      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 
     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 
     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 
     76     public void addPendingKey(final Key usedKey) {
     77         synchronized (mLock) {
     78             mPendingKeys.addLast(usedKey);
     79         }
     80     }
     81 
     82     public void flushPendingRecentKeys() {
     83         synchronized (mLock) {
     84             while (!mPendingKeys.isEmpty()) {
     85                 addKey(mPendingKeys.pollFirst(), true);
     86             }
     87             saveRecentKeys();
     88         }
     89     }
     90 
     91     public void addKeyFirst(final Key usedKey) {
     92         addKey(usedKey, true);
     93         if (mIsRecents) {
     94             saveRecentKeys();
     95         }
     96     }
     97 
     98     public void addKeyLast(final Key usedKey) {
     99         addKey(usedKey, false);
    100     }
    101 
    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 
    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 
    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 
    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 
    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 
    188     private int getKeyX0(final int index) {
    189         final int column = index % mColumnsNum;
    190         return column * mHorizontalStep;
    191     }
    192 
    193     private int getKeyX1(final int index) {
    194         final int column = index % mColumnsNum + 1;
    195         return column * mHorizontalStep;
    196     }
    197 
    198     private int getKeyY0(final int index) {
    199         final int row = index / mColumnsNum;
    200         return row * mVerticalStep + mVerticalGap / 2;
    201     }
    202 
    203     private int getKeyY1(final int index) {
    204         final int row = index / mColumnsNum + 1;
    205         return row * mVerticalStep + mVerticalGap / 2;
    206     }
    207 
    208     @Override
    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
    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 
    230         public GridKey(final Key originalKey) {
    231             super(originalKey);
    232         }
    233 
    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
    241         public int getX() {
    242             return mCurrentX;
    243         }
    244 
    245         @Override
    246         public int getY() {
    247             return mCurrentY;
    248         }
    249 
    250         @Override
    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
    260         public String toString() {
    261             return "GridKey: " + super.toString();
    262         }
    263     }
    264 }
    265