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; CategoryProperties(final int categoryId, final int pageCount)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 EmojiCategory(final SharedPreferences prefs, final Resources res, final KeyboardLayoutSet layoutSet, final TypedArray emojiPaletteViewAttr)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 addShownCategoryId(final int categoryId)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 getCategoryName(final int categoryId, final int categoryPageId)150 public String getCategoryName(final int categoryId, final int categoryPageId) { 151 return sCategoryName[categoryId] + "-" + categoryPageId; 152 } 153 getCategoryId(final String name)154 public int getCategoryId(final String name) { 155 final String[] strings = name.split("-"); 156 return mCategoryNameToIdMap.get(strings[0]); 157 } 158 getCategoryTabIcon(final int categoryId)159 public int getCategoryTabIcon(final int categoryId) { 160 return mCategoryTabIconId[categoryId]; 161 } 162 getAccessibilityDescription(final int categoryId)163 public String getAccessibilityDescription(final int categoryId) { 164 return mRes.getString(sAccessibilityDescriptionResourceIdsForCategories[categoryId]); 165 } 166 getShownCategories()167 public ArrayList<CategoryProperties> getShownCategories() { 168 return mShownCategories; 169 } 170 getCurrentCategoryId()171 public int getCurrentCategoryId() { 172 return mCurrentCategoryId; 173 } 174 getCurrentCategoryPageSize()175 public int getCurrentCategoryPageSize() { 176 return getCategoryPageSize(mCurrentCategoryId); 177 } 178 getCategoryPageSize(final int categoryId)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 setCurrentCategoryId(final int categoryId)190 public void setCurrentCategoryId(final int categoryId) { 191 mCurrentCategoryId = categoryId; 192 Settings.writeLastShownEmojiCategoryId(mPrefs, categoryId); 193 } 194 setCurrentCategoryPageId(final int id)195 public void setCurrentCategoryPageId(final int id) { 196 mCurrentCategoryPageId = id; 197 } 198 getCurrentCategoryPageId()199 public int getCurrentCategoryPageId() { 200 return mCurrentCategoryPageId; 201 } 202 saveLastTypedCategoryPage()203 public void saveLastTypedCategoryPage() { 204 Settings.writeLastTypedEmojiCategoryPageId( 205 mPrefs, mCurrentCategoryId, mCurrentCategoryPageId); 206 } 207 isInRecentTab()208 public boolean isInRecentTab() { 209 return mCurrentCategoryId == EmojiCategory.ID_RECENTS; 210 } 211 getTabIdFromCategoryId(final int categoryId)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 getPageIdFromCategoryId(final int 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 getRecentTabId()238 public int getRecentTabId() { 239 return getTabIdFromCategoryId(EmojiCategory.ID_RECENTS); 240 } 241 getCategoryPageCount(final int categoryId)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. getCategoryIdAndPageIdFromPagePosition(final int position)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. getKeyboardFromPagePosition(final int 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 getCategoryKeyboardMapKey(final int categoryId, final int id)273 private static final Long getCategoryKeyboardMapKey(final int categoryId, final int id) { 274 return (((long) categoryId) << Constants.MAX_INT_BIT_COUNT) | id; 275 } 276 getKeyboard(final int categoryId, final int id)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 getTotalPageCountOfAllCategories()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 sortKeysIntoPages(final List<Key> inKeys, final int maxPageCount)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