Home | History | Annotate | Download | only in emoji
      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.emoji;
     18 
     19 import android.content.SharedPreferences;
     20 import android.content.res.Resources;
     21 import android.content.res.TypedArray;
     22 import android.graphics.Rect;
     23 import android.os.Build;
     24 import android.util.Log;
     25 import android.util.Pair;
     26 
     27 import com.android.inputmethod.compat.BuildCompatUtils;
     28 import com.android.inputmethod.keyboard.Key;
     29 import com.android.inputmethod.keyboard.Keyboard;
     30 import com.android.inputmethod.keyboard.KeyboardId;
     31 import com.android.inputmethod.keyboard.KeyboardLayoutSet;
     32 import com.android.inputmethod.latin.Constants;
     33 import com.android.inputmethod.latin.R;
     34 import com.android.inputmethod.latin.settings.Settings;
     35 
     36 import java.util.ArrayList;
     37 import java.util.Collections;
     38 import java.util.Comparator;
     39 import java.util.HashMap;
     40 import java.util.List;
     41 import java.util.concurrent.ConcurrentHashMap;
     42 
     43 final class EmojiCategory {
     44     private final String TAG = EmojiCategory.class.getSimpleName();
     45 
     46     private static final int ID_UNSPECIFIED = -1;
     47     public static final int ID_RECENTS = 0;
     48     private static final int ID_PEOPLE = 1;
     49     private static final int ID_OBJECTS = 2;
     50     private static final int ID_NATURE = 3;
     51     private static final int ID_PLACES = 4;
     52     private static final int ID_SYMBOLS = 5;
     53     private static final int ID_EMOTICONS = 6;
     54 
     55     public final class CategoryProperties {
     56         public final int mCategoryId;
     57         public final int mPageCount;
     58         public CategoryProperties(final int categoryId, final int pageCount) {
     59             mCategoryId = categoryId;
     60             mPageCount = pageCount;
     61         }
     62     }
     63 
     64     private static final String[] sCategoryName = {
     65             "recents",
     66             "people",
     67             "objects",
     68             "nature",
     69             "places",
     70             "symbols",
     71             "emoticons" };
     72 
     73     private static final int[] sCategoryTabIconAttr = {
     74             R.styleable.EmojiPalettesView_iconEmojiRecentsTab,
     75             R.styleable.EmojiPalettesView_iconEmojiCategory1Tab,
     76             R.styleable.EmojiPalettesView_iconEmojiCategory2Tab,
     77             R.styleable.EmojiPalettesView_iconEmojiCategory3Tab,
     78             R.styleable.EmojiPalettesView_iconEmojiCategory4Tab,
     79             R.styleable.EmojiPalettesView_iconEmojiCategory5Tab,
     80             R.styleable.EmojiPalettesView_iconEmojiCategory6Tab };
     81 
     82     private static final int[] sAccessibilityDescriptionResourceIdsForCategories = {
     83             R.string.spoken_descrption_emoji_category_recents,
     84             R.string.spoken_descrption_emoji_category_people,
     85             R.string.spoken_descrption_emoji_category_objects,
     86             R.string.spoken_descrption_emoji_category_nature,
     87             R.string.spoken_descrption_emoji_category_places,
     88             R.string.spoken_descrption_emoji_category_symbols,
     89             R.string.spoken_descrption_emoji_category_emoticons };
     90 
     91     private static final int[] sCategoryElementId = {
     92             KeyboardId.ELEMENT_EMOJI_RECENTS,
     93             KeyboardId.ELEMENT_EMOJI_CATEGORY1,
     94             KeyboardId.ELEMENT_EMOJI_CATEGORY2,
     95             KeyboardId.ELEMENT_EMOJI_CATEGORY3,
     96             KeyboardId.ELEMENT_EMOJI_CATEGORY4,
     97             KeyboardId.ELEMENT_EMOJI_CATEGORY5,
     98             KeyboardId.ELEMENT_EMOJI_CATEGORY6 };
     99 
    100     private final SharedPreferences mPrefs;
    101     private final Resources mRes;
    102     private final int mMaxPageKeyCount;
    103     private final KeyboardLayoutSet mLayoutSet;
    104     private final HashMap<String, Integer> mCategoryNameToIdMap = new HashMap<>();
    105     private final int[] mCategoryTabIconId = new int[sCategoryName.length];
    106     private final ArrayList<CategoryProperties> mShownCategories = new ArrayList<>();
    107     private final ConcurrentHashMap<Long, DynamicGridKeyboard> mCategoryKeyboardMap =
    108             new ConcurrentHashMap<>();
    109 
    110     private int mCurrentCategoryId = EmojiCategory.ID_UNSPECIFIED;
    111     private int mCurrentCategoryPageId = 0;
    112 
    113     public EmojiCategory(final SharedPreferences prefs, final Resources res,
    114             final KeyboardLayoutSet layoutSet, final TypedArray emojiPaletteViewAttr) {
    115         mPrefs = prefs;
    116         mRes = res;
    117         mMaxPageKeyCount = res.getInteger(R.integer.config_emoji_keyboard_max_page_key_count);
    118         mLayoutSet = layoutSet;
    119         for (int i = 0; i < sCategoryName.length; ++i) {
    120             mCategoryNameToIdMap.put(sCategoryName[i], i);
    121             mCategoryTabIconId[i] = emojiPaletteViewAttr.getResourceId(
    122                     sCategoryTabIconAttr[i], 0);
    123         }
    124         addShownCategoryId(EmojiCategory.ID_RECENTS);
    125         if (BuildCompatUtils.EFFECTIVE_SDK_INT >= Build.VERSION_CODES.KITKAT) {
    126             addShownCategoryId(EmojiCategory.ID_PEOPLE);
    127             addShownCategoryId(EmojiCategory.ID_OBJECTS);
    128             addShownCategoryId(EmojiCategory.ID_NATURE);
    129             addShownCategoryId(EmojiCategory.ID_PLACES);
    130             mCurrentCategoryId =
    131                     Settings.readLastShownEmojiCategoryId(mPrefs, EmojiCategory.ID_PEOPLE);
    132         } else {
    133             mCurrentCategoryId =
    134                     Settings.readLastShownEmojiCategoryId(mPrefs, EmojiCategory.ID_SYMBOLS);
    135         }
    136         addShownCategoryId(EmojiCategory.ID_SYMBOLS);
    137         addShownCategoryId(EmojiCategory.ID_EMOTICONS);
    138         getKeyboard(EmojiCategory.ID_RECENTS, 0 /* cagetoryPageId */)
    139                 .loadRecentKeys(mCategoryKeyboardMap.values());
    140     }
    141 
    142     private void addShownCategoryId(final int categoryId) {
    143         // Load a keyboard of categoryId
    144         getKeyboard(categoryId, 0 /* cagetoryPageId */);
    145         final CategoryProperties properties =
    146                 new CategoryProperties(categoryId, getCategoryPageCount(categoryId));
    147         mShownCategories.add(properties);
    148     }
    149 
    150     public String getCategoryName(final int categoryId, final int categoryPageId) {
    151         return sCategoryName[categoryId] + "-" + categoryPageId;
    152     }
    153 
    154     public int getCategoryId(final String name) {
    155         final String[] strings = name.split("-");
    156         return mCategoryNameToIdMap.get(strings[0]);
    157     }
    158 
    159     public int getCategoryTabIcon(final int categoryId) {
    160         return mCategoryTabIconId[categoryId];
    161     }
    162 
    163     public String getAccessibilityDescription(final int categoryId) {
    164         return mRes.getString(sAccessibilityDescriptionResourceIdsForCategories[categoryId]);
    165     }
    166 
    167     public ArrayList<CategoryProperties> getShownCategories() {
    168         return mShownCategories;
    169     }
    170 
    171     public int getCurrentCategoryId() {
    172         return mCurrentCategoryId;
    173     }
    174 
    175     public int getCurrentCategoryPageSize() {
    176         return getCategoryPageSize(mCurrentCategoryId);
    177     }
    178 
    179     public int getCategoryPageSize(final int categoryId) {
    180         for (final CategoryProperties prop : mShownCategories) {
    181             if (prop.mCategoryId == categoryId) {
    182                 return prop.mPageCount;
    183             }
    184         }
    185         Log.w(TAG, "Invalid category id: " + categoryId);
    186         // Should not reach here.
    187         return 0;
    188     }
    189 
    190     public void setCurrentCategoryId(final int categoryId) {
    191         mCurrentCategoryId = categoryId;
    192         Settings.writeLastShownEmojiCategoryId(mPrefs, categoryId);
    193     }
    194 
    195     public void setCurrentCategoryPageId(final int id) {
    196         mCurrentCategoryPageId = id;
    197     }
    198 
    199     public int getCurrentCategoryPageId() {
    200         return mCurrentCategoryPageId;
    201     }
    202 
    203     public void saveLastTypedCategoryPage() {
    204         Settings.writeLastTypedEmojiCategoryPageId(
    205                 mPrefs, mCurrentCategoryId, mCurrentCategoryPageId);
    206     }
    207 
    208     public boolean isInRecentTab() {
    209         return mCurrentCategoryId == EmojiCategory.ID_RECENTS;
    210     }
    211 
    212     public int getTabIdFromCategoryId(final int categoryId) {
    213         for (int i = 0; i < mShownCategories.size(); ++i) {
    214             if (mShownCategories.get(i).mCategoryId == categoryId) {
    215                 return i;
    216             }
    217         }
    218         Log.w(TAG, "categoryId not found: " + categoryId);
    219         return 0;
    220     }
    221 
    222     // Returns the view pager's page position for the categoryId
    223     public int getPageIdFromCategoryId(final int categoryId) {
    224         final int lastSavedCategoryPageId =
    225                 Settings.readLastTypedEmojiCategoryPageId(mPrefs, categoryId);
    226         int sum = 0;
    227         for (int i = 0; i < mShownCategories.size(); ++i) {
    228             final CategoryProperties props = mShownCategories.get(i);
    229             if (props.mCategoryId == categoryId) {
    230                 return sum + lastSavedCategoryPageId;
    231             }
    232             sum += props.mPageCount;
    233         }
    234         Log.w(TAG, "categoryId not found: " + categoryId);
    235         return 0;
    236     }
    237 
    238     public int getRecentTabId() {
    239         return getTabIdFromCategoryId(EmojiCategory.ID_RECENTS);
    240     }
    241 
    242     private int getCategoryPageCount(final int categoryId) {
    243         final Keyboard keyboard = mLayoutSet.getKeyboard(sCategoryElementId[categoryId]);
    244         return (keyboard.getSortedKeys().size() - 1) / mMaxPageKeyCount + 1;
    245     }
    246 
    247     // Returns a pair of the category id and the category page id from the view pager's page
    248     // position. The category page id is numbered in each category. And the view page position
    249     // is the position of the current shown page in the view pager which contains all pages of
    250     // all categories.
    251     public Pair<Integer, Integer> getCategoryIdAndPageIdFromPagePosition(final int position) {
    252         int sum = 0;
    253         for (final CategoryProperties properties : mShownCategories) {
    254             final int temp = sum;
    255             sum += properties.mPageCount;
    256             if (sum > position) {
    257                 return new Pair<>(properties.mCategoryId, position - temp);
    258             }
    259         }
    260         return null;
    261     }
    262 
    263     // Returns a keyboard from the view pager's page position.
    264     public DynamicGridKeyboard getKeyboardFromPagePosition(final int position) {
    265         final Pair<Integer, Integer> categoryAndId =
    266                 getCategoryIdAndPageIdFromPagePosition(position);
    267         if (categoryAndId != null) {
    268             return getKeyboard(categoryAndId.first, categoryAndId.second);
    269         }
    270         return null;
    271     }
    272 
    273     private static final Long getCategoryKeyboardMapKey(final int categoryId, final int id) {
    274         return (((long) categoryId) << Constants.MAX_INT_BIT_COUNT) | id;
    275     }
    276 
    277     public DynamicGridKeyboard getKeyboard(final int categoryId, final int id) {
    278         synchronized (mCategoryKeyboardMap) {
    279             final Long categotyKeyboardMapKey = getCategoryKeyboardMapKey(categoryId, id);
    280             if (mCategoryKeyboardMap.containsKey(categotyKeyboardMapKey)) {
    281                 return mCategoryKeyboardMap.get(categotyKeyboardMapKey);
    282             }
    283 
    284             if (categoryId == EmojiCategory.ID_RECENTS) {
    285                 final DynamicGridKeyboard kbd = new DynamicGridKeyboard(mPrefs,
    286                         mLayoutSet.getKeyboard(KeyboardId.ELEMENT_EMOJI_RECENTS),
    287                         mMaxPageKeyCount, categoryId);
    288                 mCategoryKeyboardMap.put(categotyKeyboardMapKey, kbd);
    289                 return kbd;
    290             }
    291 
    292             final Keyboard keyboard = mLayoutSet.getKeyboard(sCategoryElementId[categoryId]);
    293             final Key[][] sortedKeys = sortKeysIntoPages(
    294                     keyboard.getSortedKeys(), mMaxPageKeyCount);
    295             for (int pageId = 0; pageId < sortedKeys.length; ++pageId) {
    296                 final DynamicGridKeyboard tempKeyboard = new DynamicGridKeyboard(mPrefs,
    297                         mLayoutSet.getKeyboard(KeyboardId.ELEMENT_EMOJI_RECENTS),
    298                         mMaxPageKeyCount, categoryId);
    299                 for (final Key emojiKey : sortedKeys[pageId]) {
    300                     if (emojiKey == null) {
    301                         break;
    302                     }
    303                     tempKeyboard.addKeyLast(emojiKey);
    304                 }
    305                 mCategoryKeyboardMap.put(
    306                         getCategoryKeyboardMapKey(categoryId, pageId), tempKeyboard);
    307             }
    308             return mCategoryKeyboardMap.get(categotyKeyboardMapKey);
    309         }
    310     }
    311 
    312     public int getTotalPageCountOfAllCategories() {
    313         int sum = 0;
    314         for (CategoryProperties properties : mShownCategories) {
    315             sum += properties.mPageCount;
    316         }
    317         return sum;
    318     }
    319 
    320     private static Comparator<Key> EMOJI_KEY_COMPARATOR = new Comparator<Key>() {
    321         @Override
    322         public int compare(final Key lhs, final Key rhs) {
    323             final Rect lHitBox = lhs.getHitBox();
    324             final Rect rHitBox = rhs.getHitBox();
    325             if (lHitBox.top < rHitBox.top) {
    326                 return -1;
    327             } else if (lHitBox.top > rHitBox.top) {
    328                 return 1;
    329             }
    330             if (lHitBox.left < rHitBox.left) {
    331                 return -1;
    332             } else if (lHitBox.left > rHitBox.left) {
    333                 return 1;
    334             }
    335             if (lhs.getCode() == rhs.getCode()) {
    336                 return 0;
    337             }
    338             return lhs.getCode() < rhs.getCode() ? -1 : 1;
    339         }
    340     };
    341 
    342     private static Key[][] sortKeysIntoPages(final List<Key> inKeys, final int maxPageCount) {
    343         final ArrayList<Key> keys = new ArrayList<>(inKeys);
    344         Collections.sort(keys, EMOJI_KEY_COMPARATOR);
    345         final int pageCount = (keys.size() - 1) / maxPageCount + 1;
    346         final Key[][] retval = new Key[pageCount][maxPageCount];
    347         for (int i = 0; i < keys.size(); ++i) {
    348             retval[i / maxPageCount][i % maxPageCount] = keys.get(i);
    349         }
    350         return retval;
    351     }
    352 }
    353