/* * Copyright (C) 2015 Actor LLC. <https://actor.im> */ package im.actor.sdk.view.emoji; import android.app.Application; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.os.Build; import android.os.Handler; import android.os.Looper; import android.os.Process; import android.text.Spannable; import android.text.TextPaint; import android.text.style.ReplacementSpan; import android.util.TypedValue; import java.io.File; import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.concurrent.CopyOnWriteArrayList; import im.actor.core.utils.IOUtils; import im.actor.sdk.util.images.common.ImageMetadata; import im.actor.sdk.util.images.ops.ImageLoading; import im.actor.sdk.util.images.sources.FileSource; import im.actor.sdk.view.emoji.smiles.SmilesPack; import im.actor.sdk.view.emoji.smiles.SmilesRecentListener; import im.actor.sdk.view.emoji.smiles.SmilesRecentsController; import im.actor.sdk.view.emoji.smiles.SmileysPack; import im.actor.sdk.view.emoji.keyboard.emoji.smiles.SmilesListener; import im.actor.runtime.Log; public class SmileProcessor { private static final String TAG = "Emoji"; private static final int COUNT_IN_ROW = 32; private static final int COUNT_IN_COL = 26; private static final int SECTION_SIDE = 8; private static final int SECTION_ROW_COUNT = COUNT_IN_ROW / SECTION_SIDE; private static final int SECTION_COL_COUNT = (int) Math.ceil((float) COUNT_IN_COL / SECTION_SIDE); public static final int CONFIGURATION_DIALOGS = 0; public static final int CONFIGURATION_BUBBLES = 1; private static final long[] EMOJI_MAP = {2302179L, 3154147L, 3219683L, 3285219L, 3350755L, 3416291L, 3481827L, 3547363L, 3612899L, 3678435L, 3743971L, 169L, 174L, 8252L, 8265L, 8482L, 8505L, 8596L, 8597L, 8598L, 8599L, 8600L, 8601L, 8617L, 8618L, 8986L, 8987L, 9193L, 9194L, 9195L, 9196L, 9200L, 9203L, 9410L, 9642L, 9643L, 9654L, 9664L, 9723L, 9724L, 9725L, 9726L, 9728L, 9729L, 9742L, 9745L, 9748L, 9749L, 9757L, 9786L, 9800L, 9801L, 9802L, 9803L, 9804L, 9805L, 9806L, 9807L, 9808L, 9809L, 9810L, 9811L, 9824L, 9827L, 9829L, 9830L, 9832L, 9851L, 9855L, 9875L, 9888L, 9889L, 9898L, 9899L, 9917L, 9918L, 9924L, 9925L, 9934L, 9940L, 9962L, 9970L, 9971L, 9973L, 9978L, 9981L, 9986L, 9989L, 9992L, 9993L, 9994L, 9995L, 9996L, 9999L, 10002L, 10004L, 10006L, 10024L, 10035L, 10036L, 10052L, 10055L, 10060L, 10062L, 10067L, 10068L, 10069L, 10071L, 10084L, 10133L, 10134L, 10135L, 10145L, 10160L, 10175L, 10548L, 10549L, 11013L, 11014L, 11015L, 11035L, 11036L, 11088L, 11093L, 12336L, 12349L, 12951L, 12953L, 3627867140L, 3627867343L, 3627867504L, 3627867505L, 3627867518L, 3627867519L, 3627867534L, 3627867537L, 3627867538L, 3627867539L, 3627867540L, 3627867541L, 3627867542L, 3627867543L, 3627867544L, 3627867545L, 3627867546L, -2865171270784459277L, -2865171266489491990L, -2865171262194524680L, -2865171257899557385L, -2865171253604590105L, -2865171245014655495L, -2865171240719688203L, -2865171236424720905L, -2865171206359949830L, -2865171193475047944L, 3627867649L, 3627867650L, 3627867674L, 3627867695L, 3627867699L, 3627867701L, 3627867702L, 3627867703L, 3627867704L, 3627867705L, 3627867706L, 3627867728L, 3627867904L, 3627867905L, 3627867906L, 3627867907L, 3627867908L, 3627867909L, 3627867910L, 3627867911L, 3627867912L, 3627867913L, 3627867914L, 3627867915L, 3627867916L, 3627867917L, 3627867918L, 3627867919L, 3627867920L, 3627867921L, 3627867922L, 3627867923L, 3627867924L, 3627867925L, 3627867926L, 3627867927L, 3627867928L, 3627867929L, 3627867930L, 3627867931L, 3627867932L, 3627867933L, 3627867934L, 3627867935L, 3627867936L, 3627867952L, 3627867953L, 3627867954L, 3627867955L, 3627867956L, 3627867957L, 3627867959L, 3627867960L, 3627867961L, 3627867962L, 3627867963L, 3627867964L, 3627867965L, 3627867966L, 3627867967L, 3627867968L, 3627867969L, 3627867970L, 3627867971L, 3627867972L, 3627867973L, 3627867974L, 3627867975L, 3627867976L, 3627867977L, 3627867978L, 3627867979L, 3627867980L, 3627867981L, 3627867982L, 3627867983L, 3627867984L, 3627867985L, 3627867986L, 3627867987L, 3627867988L, 3627867989L, 3627867990L, 3627867991L, 3627867992L, 3627867993L, 3627867994L, 3627867995L, 3627867996L, 3627867997L, 3627867998L, 3627867999L, 3627868000L, 3627868001L, 3627868002L, 3627868003L, 3627868004L, 3627868005L, 3627868006L, 3627868007L, 3627868008L, 3627868009L, 3627868010L, 3627868011L, 3627868012L, 3627868013L, 3627868014L, 3627868015L, 3627868016L, 3627868017L, 3627868018L, 3627868019L, 3627868020L, 3627868021L, 3627868022L, 3627868023L, 3627868024L, 3627868025L, 3627868026L, 3627868027L, 3627868028L, 3627868032L, 3627868033L, 3627868034L, 3627868035L, 3627868036L, 3627868037L, 3627868038L, 3627868039L, 3627868040L, 3627868041L, 3627868042L, 3627868043L, 3627868044L, 3627868045L, 3627868046L, 3627868047L, 3627868048L, 3627868049L, 3627868050L, 3627868051L, 3627868064L, 3627868065L, 3627868066L, 3627868067L, 3627868068L, 3627868069L, 3627868070L, 3627868071L, 3627868072L, 3627868073L, 3627868074L, 3627868075L, 3627868076L, 3627868077L, 3627868078L, 3627868079L, 3627868080L, 3627868081L, 3627868082L, 3627868083L, 3627868084L, 3627868085L, 3627868086L, 3627868087L, 3627868088L, 3627868089L, 3627868090L, 3627868091L, 3627868092L, 3627868093L, 3627868094L, 3627868095L, 3627868096L, 3627868097L, 3627868098L, 3627868099L, 3627868100L, 3627868102L, 3627868103L, 3627868104L, 3627868105L, 3627868106L, 3627868128L, 3627868129L, 3627868130L, 3627868131L, 3627868132L, 3627868133L, 3627868134L, 3627868135L, 3627868136L, 3627868137L, 3627868138L, 3627868139L, 3627868140L, 3627868141L, 3627868142L, 3627868143L, 3627868144L, 3627932672L, 3627932673L, 3627932674L, 3627932675L, 3627932676L, 3627932677L, 3627932678L, 3627932679L, 3627932680L, 3627932681L, 3627932682L, 3627932683L, 3627932684L, 3627932685L, 3627932686L, 3627932687L, 3627932688L, 3627932689L, 3627932690L, 3627932691L, 3627932692L, 3627932693L, 3627932694L, 3627932695L, 3627932696L, 3627932697L, 3627932698L, 3627932699L, 3627932700L, 3627932701L, 3627932702L, 3627932703L, 3627932704L, 3627932705L, 3627932706L, 3627932707L, 3627932708L, 3627932709L, 3627932710L, 3627932711L, 3627932712L, 3627932713L, 3627932714L, 3627932715L, 3627932716L, 3627932717L, 3627932718L, 3627932719L, 3627932720L, 3627932721L, 3627932722L, 3627932723L, 3627932724L, 3627932725L, 3627932726L, 3627932727L, 3627932728L, 3627932729L, 3627932730L, 3627932731L, 3627932732L, 3627932733L, 3627932734L, 3627932736L, 3627932738L, 3627932739L, 3627932740L, 3627932741L, 3627932742L, 3627932743L, 3627932744L, 3627932745L, 3627932746L, 3627932747L, 3627932748L, 3627932749L, 3627932750L, 3627932751L, 3627932752L, 3627932753L, 3627932754L, 3627932755L, 3627932756L, 3627932757L, 3627932758L, 3627932759L, 3627932760L, 3627932761L, 3627932762L, 3627932763L, 3627932764L, 3627932765L, 3627932766L, 3627932767L, 3627932768L, 3627932769L, 3627932770L, 3627932771L, 3627932772L, 3627932773L, 3627932774L, 3627932775L, 3627932776L, 3627932777L, 3627932778L, 3627932779L, 3627932780L, 3627932781L, 3627932782L, 3627932783L, 3627932784L, 3627932785L, 3627932786L, 3627932787L, 3627932788L, 3627932789L, 3627932790L, 3627932791L, 3627932792L, 3627932793L, 3627932794L, 3627932795L, 3627932796L, 3627932797L, 3627932798L, 3627932799L, 3627932800L, 3627932801L, 3627932802L, 3627932803L, 3627932804L, 3627932805L, 3627932806L, 3627932807L, 3627932808L, 3627932809L, 3627932810L, 3627932811L, 3627932812L, 3627932813L, 3627932814L, 3627932815L, 3627932816L, 3627932817L, 3627932818L, 3627932819L, 3627932820L, 3627932821L, 3627932822L, 3627932823L, 3627932824L, 3627932825L, 3627932826L, 3627932827L, 3627932828L, 3627932829L, 3627932830L, 3627932831L, 3627932832L, 3627932833L, 3627932834L, 3627932835L, 3627932836L, 3627932837L, 3627932838L, 3627932839L, 3627932840L, 3627932841L, 3627932842L, 3627932843L, 3627932844L, 3627932845L, 3627932846L, 3627932847L, 3627932848L, 3627932849L, 3627932850L, 3627932851L, 3627932852L, 3627932853L, 3627932854L, 3627932855L, 3627932856L, 3627932857L, 3627932858L, 3627932859L, 3627932860L, 3627932861L, 3627932862L, 3627932863L, 3627932864L, 3627932865L, 3627932866L, 3627932867L, 3627932868L, 3627932869L, 3627932870L, 3627932871L, 3627932872L, 3627932873L, 3627932874L, 3627932875L, 3627932876L, 3627932877L, 3627932878L, 3627932879L, 3627932880L, 3627932881L, 3627932882L, 3627932883L, 3627932884L, 3627932885L, 3627932886L, 3627932887L, 3627932888L, 3627932889L, 3627932890L, 3627932891L, 3627932892L, 3627932893L, 3627932894L, 3627932895L, 3627932896L, 3627932897L, 3627932898L, 3627932899L, 3627932900L, 3627932901L, 3627932902L, 3627932903L, 3627932904L, 3627932905L, 3627932906L, 3627932907L, 3627932908L, 3627932909L, 3627932910L, 3627932911L, 3627932912L, 3627932913L, 3627932914L, 3627932915L, 3627932916L, 3627932917L, 3627932918L, 3627932919L, 3627932921L, 3627932922L, 3627932923L, 3627932924L, 3627932928L, 3627932929L, 3627932930L, 3627932931L, 3627932932L, 3627932933L, 3627932934L, 3627932935L, 3627932936L, 3627932937L, 3627932938L, 3627932939L, 3627932940L, 3627932941L, 3627932942L, 3627932943L, 3627932944L, 3627932945L, 3627932946L, 3627932947L, 3627932948L, 3627932949L, 3627932950L, 3627932951L, 3627932952L, 3627932953L, 3627932954L, 3627932955L, 3627932956L, 3627932957L, 3627932958L, 3627932959L, 3627932960L, 3627932961L, 3627932962L, 3627932963L, 3627932964L, 3627932965L, 3627932966L, 3627932967L, 3627932968L, 3627932969L, 3627932970L, 3627932971L, 3627932972L, 3627932973L, 3627932974L, 3627932975L, 3627932976L, 3627932977L, 3627932978L, 3627932979L, 3627932980L, 3627932981L, 3627932982L, 3627932983L, 3627932984L, 3627932985L, 3627932986L, 3627932987L, 3627932988L, 3627932989L, 3627933008L, 3627933009L, 3627933010L, 3627933011L, 3627933012L, 3627933013L, 3627933014L, 3627933015L, 3627933016L, 3627933017L, 3627933018L, 3627933019L, 3627933179L, 3627933180L, 3627933181L, 3627933182L, 3627933183L, 3627933184L, 3627933185L, 3627933186L, 3627933187L, 3627933188L, 3627933189L, 3627933190L, 3627933191L, 3627933192L, 3627933193L, 3627933194L, 3627933195L, 3627933196L, 3627933197L, 3627933198L, 3627933199L, 3627933200L, 3627933201L, 3627933202L, 3627933203L, 3627933204L, 3627933205L, 3627933206L, 3627933207L, 3627933208L, 3627933209L, 3627933210L, 3627933211L, 3627933212L, 3627933213L, 3627933214L, 3627933215L, 3627933216L, 3627933217L, 3627933218L, 3627933219L, 3627933220L, 3627933221L, 3627933222L, 3627933223L, 3627933224L, 3627933225L, 3627933226L, 3627933227L, 3627933228L, 3627933229L, 3627933230L, 3627933231L, 3627933232L, 3627933233L, 3627933234L, 3627933235L, 3627933236L, 3627933237L, 3627933238L, 3627933239L, 3627933240L, 3627933241L, 3627933242L, 3627933243L, 3627933244L, 3627933245L, 3627933246L, 3627933247L, 3627933248L, 3627933253L, 3627933254L, 3627933255L, 3627933256L, 3627933257L, 3627933258L, 3627933259L, 3627933260L, 3627933261L, 3627933262L, 3627933263L, 3627933312L, 3627933313L, 3627933314L, 3627933315L, 3627933316L, 3627933317L, 3627933318L, 3627933319L, 3627933320L, 3627933321L, 3627933322L, 3627933323L, 3627933324L, 3627933325L, 3627933326L, 3627933327L, 3627933328L, 3627933329L, 3627933330L, 3627933331L, 3627933332L, 3627933333L, 3627933334L, 3627933335L, 3627933336L, 3627933337L, 3627933338L, 3627933339L, 3627933340L, 3627933341L, 3627933342L, 3627933343L, 3627933344L, 3627933345L, 3627933346L, 3627933347L, 3627933348L, 3627933349L, 3627933350L, 3627933351L, 3627933352L, 3627933353L, 3627933354L, 3627933355L, 3627933356L, 3627933357L, 3627933358L, 3627933359L, 3627933360L, 3627933361L, 3627933362L, 3627933363L, 3627933364L, 3627933365L, 3627933366L, 3627933367L, 3627933368L, 3627933369L, 3627933370L, 3627933371L, 3627933372L, 3627933373L, 3627933374L, 3627933375L, 3627933376L, 3627933377L, 3627933378L, 3627933379L, 3627933380L, 3627933381L}; private static final long[] EMOJI_SORTED; private static final long minEmoji1; private static final long maxEmoji1; private static final long minEmoji2; private static final long maxEmoji2; private static final HashSet<Long> EMOJI_SET = new HashSet<>(EMOJI_MAP.length); static { for (int i = 0; i < EMOJI_MAP.length; i++) { EMOJI_SET.add(EMOJI_MAP[i]); } EMOJI_SORTED = new long[EMOJI_MAP.length]; long min1 = 0xFFFF, max1 = 0; long min2 = 0xFFFFFFFF, max2 = 0; for (int i = 0; i < EMOJI_MAP.length; i++) { EMOJI_SORTED[i] = EMOJI_MAP[i]; if ((EMOJI_SORTED[i] & 0xffff) == EMOJI_SORTED[i]) { if (EMOJI_SORTED[i] < min1) { min1 = EMOJI_SORTED[i]; } if (EMOJI_SORTED[i] > max1) { max1 = EMOJI_SORTED[i]; } } else if ((EMOJI_SORTED[i] & 0xffffffff) == EMOJI_SORTED[i]) { if (EMOJI_SORTED[i] < min2) { min2 = EMOJI_SORTED[i]; } if (EMOJI_SORTED[i] > max2) { max2 = EMOJI_SORTED[i]; } } } minEmoji1 = min1; maxEmoji1 = max1; minEmoji2 = min2; maxEmoji2 = max2; Arrays.sort(EMOJI_SORTED); } private static final int LAYOUT_1X = 1; private static final int LAYOUT_15X_1 = 2; private static final int LAYOUT_15X_2 = 3; private static final int LAYOUT_2X_1 = 4; private static final int LAYOUT_2X_2 = 5; private static SmileProcessor instance; private static SmileProcessor processor; // protected Bitmap emojiImages; protected HashMap<Integer, Bitmap> emojiMap; private static final Spannable.Factory spannableFactory = Spannable.Factory.getInstance(); private Application application; private float density; private HashMap<Long, Integer> indexes; private HashMap<Integer, Paint.FontMetricsInt> originalMetrics; private int layoutType = LAYOUT_1X; private boolean isLoading = false; private boolean isLoaded = false; private Handler handler = new Handler(Looper.getMainLooper()); private CopyOnWriteArrayList<SmilesListener> listeners = new CopyOnWriteArrayList<SmilesListener>(); private int emojiSideSize; private int rectSize = 0; private SmilesRecentListener smilesRecentListener; private SmilesRecentsController recentController; public static final SmileProcessor emoji() { return processor; } public SmileProcessor(Application application) { long start = System.currentTimeMillis(); this.application = application; processor = this; density = application.getResources().getDisplayMetrics().density; emojiSideSize = (int) (density * 20); Log.d(TAG, "Emoji phase 0 in " + (System.currentTimeMillis() - start) + " ms"); if (density >= 2 || density == 1) { if (density >= 2) { // XHDPI and more if (SmileysPack.PACK_2) { layoutType = LAYOUT_2X_1; } else if (SmileysPack.PACK_15) { layoutType = LAYOUT_15X_1; } else if (SmileysPack.PACK_1) { layoutType = LAYOUT_1X; } else { throw new RuntimeException("Unable to find smileys pack"); } } else { // MDPI if (SmileysPack.PACK_1) { layoutType = LAYOUT_1X; } else if (SmileysPack.PACK_15) { layoutType = LAYOUT_15X_1; } else if (SmileysPack.PACK_2) { layoutType = LAYOUT_2X_2; } else { throw new RuntimeException("Unable to find smileys pack"); } } } else { if (density > 1) { // 1.3333 and 1.5 // HDPI & TVDPI if (SmileysPack.PACK_15) { layoutType = LAYOUT_15X_1; } else if (SmileysPack.PACK_2) { layoutType = LAYOUT_2X_1; } else if (SmileysPack.PACK_1) { layoutType = LAYOUT_1X; } else { throw new RuntimeException("Unable to find smileys pack"); } } else { // 0.75 // LDPI if (SmileysPack.PACK_15) { layoutType = LAYOUT_15X_2; } else if (SmileysPack.PACK_1) { layoutType = LAYOUT_1X; } else if (SmileysPack.PACK_2) { layoutType = LAYOUT_2X_2; } else { throw new RuntimeException("Unable to find smileys pack"); } } } Log.d(TAG, "Emoji phase 1 in " + (System.currentTimeMillis() - start) + " ms"); start = System.currentTimeMillis(); switch (layoutType) { default: case LAYOUT_1X: rectSize = 28; break; case LAYOUT_15X_1: rectSize = 36; break; case LAYOUT_15X_2: rectSize = 18; break; case LAYOUT_2X_1: rectSize = 56; break; case LAYOUT_2X_2: rectSize = 28; break; } indexes = new HashMap<Long, Integer>(); emojiMap = new HashMap<Integer, Bitmap>(); originalMetrics = new HashMap<Integer, Paint.FontMetricsInt>(); TextPaint bodyPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG | Paint.SUBPIXEL_TEXT_FLAG); // bodyPaint.setTypeface(FontController.loadTypeface(application, "normal")); bodyPaint.setTextSize(getSp(16)); bodyPaint.setColor(0xff000000); originalMetrics.put(CONFIGURATION_BUBBLES, bodyPaint.getFontMetricsInt()); bodyPaint = new TextPaint(TextPaint.ANTI_ALIAS_FLAG | Paint.SUBPIXEL_TEXT_FLAG); // bodyPaint.setTypeface(FontController.loadTypeface(application, "light")); bodyPaint.setColor(0xff808080); bodyPaint.setTextSize(getSp(15.5f)); originalMetrics.put(CONFIGURATION_DIALOGS, bodyPaint.getFontMetricsInt()); Log.d(TAG, "Emoji phase 2 in " + (System.currentTimeMillis() - start) + " ms"); start = System.currentTimeMillis(); for (int i = 0; i < EMOJI_MAP.length; i++) { indexes.put(EMOJI_MAP[i], i); } Log.d(TAG, "Emoji phase 3 in " + (System.currentTimeMillis() - start) + " ms"); } protected int getSp(float sp) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, application.getResources().getDisplayMetrics()); } public boolean isLoaded() { return isLoaded; } public void registerListener(SmilesListener listener) { if (!listeners.contains(listener)) { listeners.add(listener); } } public void unregisterListener(SmilesListener listener) { listeners.remove(listener); } public void loadEmoji() { if (isLoaded) { return; } if (isLoading) { return; } isLoading = true; new Thread() { @Override public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_LOWEST); long start = System.currentTimeMillis(); Log.d(TAG, "emoji loading start"); try { boolean useScale = false; String fileName = null; String fileNameAlpha = null; switch (layoutType) { default: case LAYOUT_1X: fileName = "emoji_c_1.jpg"; fileNameAlpha = "emoji_a_1.jpg"; useScale = false; break; case LAYOUT_15X_1: fileName = "emoji_c_15.jpg"; fileNameAlpha = "emoji_a_15.jpg"; useScale = false; break; case LAYOUT_15X_2: fileName = "emoji_c_15.jpg"; fileNameAlpha = "emoji_a_15.jpg"; useScale = true; break; case LAYOUT_2X_1: fileName = "emoji_c_2.jpg"; fileNameAlpha = "emoji_a_2.jpg"; useScale = false; break; case LAYOUT_2X_2: fileName = "emoji_c_2.jpg"; fileNameAlpha = "emoji_a_2.jpg"; useScale = true; break; } File sourceFile = application.getFileStreamPath(fileName); if (!sourceFile.exists()) { InputStream colorsIs = SmileProcessor.this.application.getAssets().open(fileName); IOUtils.copy(colorsIs, sourceFile); colorsIs.close(); } File sourceAlphaFile = application.getFileStreamPath(fileNameAlpha); if (!sourceAlphaFile.exists()) { InputStream colorsIs = SmileProcessor.this.application.getAssets().open(fileNameAlpha); IOUtils.copy(colorsIs, sourceAlphaFile); colorsIs.close(); } ImageMetadata metadata = new FileSource(sourceFile.getAbsolutePath()).getImageMetadata(); int w = useScale ? metadata.getW() / 2 : metadata.getW(); int h = useScale ? metadata.getH() / 2 : metadata.getH(); Bitmap colorsBitmap; Bitmap alphaBitmap; if (useScale) { colorsBitmap = ImageLoading.loadBitmap(sourceFile.getAbsolutePath(), 2); alphaBitmap = ImageLoading.loadBitmap(sourceAlphaFile.getAbsolutePath(), 2); } else { colorsBitmap = ImageLoading.loadBitmap(sourceFile.getAbsolutePath(), 1); alphaBitmap = ImageLoading.loadBitmap(sourceAlphaFile.getAbsolutePath(), 1); } // Bitmap colorsBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); // BitmapDecoderEx.decodeReuseBitmapScaled(sourceFile.getAbsolutePath(), colorsBitmap); // colorsBitmap.setHasAlpha(true); // BitmapDecoderEx.decodeReuseBitmapBlend(sourceAlphaFile.getAbsolutePath(), colorsBitmap, useScale); Log.d(TAG, "emoji pre-loaded in " + (System.currentTimeMillis() - start) + " ms"); int[] resultColors = new int[rectSize * SECTION_SIDE * rectSize * SECTION_SIDE]; int[] tmpColors = new int[rectSize * SECTION_SIDE * rectSize * SECTION_SIDE]; int[] order = new int[]{8, 9, 10, 11, 4, 5, 6, 7, 0, 1, 2, 3, 12, 13, 14, 15}; int stride = rectSize * SECTION_SIDE; for (int ordinal : order) { int col = ordinal % SECTION_COL_COUNT; int row = ordinal / SECTION_COL_COUNT; int leftOffset = col * stride; int topOffset = row * stride; int width = stride; int height = stride; if (row == SECTION_ROW_COUNT - 1) { height = colorsBitmap.getHeight() - topOffset; } colorsBitmap.getPixels(tmpColors, 0, stride, leftOffset, topOffset, width, height); for (int ind = 0; ind < resultColors.length; ind++) { resultColors[ind] = 0xFFFFFF & tmpColors[ind]; } alphaBitmap.getPixels(tmpColors, 0, stride, leftOffset, topOffset, width, height); for (int ind = 0; ind < resultColors.length; ind++) { resultColors[ind] = resultColors[ind] | ((tmpColors[ind] & 0xFF) << 24); } Bitmap section = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); /*Canvas canvas = new Canvas(section); canvas.drawBitmap(colorsBitmap, new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height), new Rect(0, 0, width, height), new Paint());*/ section.setPixels(resultColors, 0, stride, 0, 0, width, height); emojiMap.put(ordinal, section); Log.d(TAG, "emoji region loaded in " + (System.currentTimeMillis() - start) + " ms"); } recentController = SmilesRecentsController.getInstance(application); isLoaded = true; notifyEmojiUpdated(true); Log.d(TAG, "emoji loaded in " + (System.currentTimeMillis() - start) + " ms"); } catch (Throwable t) { t.printStackTrace(); Log.d(TAG, "emoji loading error"); isLoaded = false; isLoading = false; } } }.start(); } private void notifyEmojiUpdated(final boolean completed) { handler.post(new Runnable() { @Override public void run() { Log.d(TAG, "notify"); for (SmilesListener listener : listeners) { listener.onSmilesUpdated(completed); } } }); } public void waitForEmoji() { if (isLoaded) { return; } final Object lock = new Object(); synchronized (lock) { listeners.add(new SmilesListener() { @Override public void onSmilesUpdated(boolean completed) { synchronized (lock) { lock.notify(); } } }); try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); return; } } } private long getId(String val) { long id = 0; if (val.length() == 1) { id = val.charAt(0); } else { id = ((long) val.charAt(0) << 16) + (long) val.charAt(1); } return id; } public Bitmap getBitmap(String emojiString) { return getSection(emojiString.charAt(0)); } public void upRecent(long smileId) { SmilesPack.upRecent(smileId); if (smilesRecentListener != null) { smilesRecentListener.onSmilesUpdated(); } } public void setRecentUpdateListener(SmilesRecentListener smilesRecentListener) { this.smilesRecentListener = smilesRecentListener; } public SmilesRecentsController getRecentController() { return recentController; } private class SpanDescription { private SpanDescription(long id, int start, int end) { this.id = id; this.start = start; this.end = end; } public long id; public int start, end; } public String cutEmoji(String s) { StringBuilder stringBuilder = new StringBuilder(); long prev = 0; long prevLong = 0; int prevLongCount = 0; int lastTextPos = 0; ArrayList<SpanDescription> list = new ArrayList<SpanDescription>(); for (int i = 0; i < s.length(); i++) { long current = s.charAt(i); if (prevLongCount == 3) { long prevId = ((prevLong & 0xFFFFFFFF) << 16) + current; if (EMOJI_SET.contains(prevId)) { if (lastTextPos < i - 3) { stringBuilder.append(s.substring(lastTextPos, i - 3)); lastTextPos = i - 3; } stringBuilder.append(":smile:"); lastTextPos += 4; list.add(new SpanDescription(prevId, i - 3, i + 1)); prev = 0; prevLong = 0; prevLongCount = 0; continue; } } if (prev != 0) { long prevId = ((prev & 0xFFFF) << 16) + current; if (EMOJI_SET.contains(prevId)) { if (lastTextPos < i - 1) { stringBuilder.append(s.substring(lastTextPos, i - 1)); lastTextPos = i - 1; } stringBuilder.append(":smile:"); lastTextPos += 2; list.add(new SpanDescription(prevId, i - 1, i + 1)); prev = 0; prevLong = 0; prevLongCount = 0; continue; } } if (EMOJI_SET.contains(current)) { if (lastTextPos < i) { stringBuilder.append(s.substring(lastTextPos, i)); lastTextPos = i; } stringBuilder.append(":smile:"); lastTextPos += 1; list.add(new SpanDescription(current, i, i + 1)); prev = 0; prevLong = 0; prevLongCount = 0; } else { prev = current; prevLong = ((prevLong & 0xFFFFFFFF) << 16) + current; if (prevLongCount < 3) { prevLongCount++; } } } if (lastTextPos < s.length()) { stringBuilder.append(s.substring(lastTextPos, s.length())); } return stringBuilder.toString(); } public String fixStringCompat(String src) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { return src; } else { return cutEmoji(src); } } public Spannable processEmojiCutMutable(String s, int mode) { StringBuilder stringBuilder = new StringBuilder(); long prev = 0; long prevLong = 0; int prevLongCount = 0; int lastTextPos = 0; ArrayList<SpanDescription> list = new ArrayList<SpanDescription>(); for (int i = 0; i < s.length(); i++) { long current = s.charAt(i); if (prevLongCount == 3) { long prevId = ((prevLong & 0xFFFFFFFF) << 16) + current; if (EMOJI_SET.contains(prevId)) { if (lastTextPos < i - 3) { stringBuilder.append(s.substring(lastTextPos, i - 3)); lastTextPos = i - 3; } stringBuilder.append("++++"); lastTextPos += 4; list.add(new SpanDescription(prevId, i - 3, i + 1)); prev = 0; prevLong = 0; prevLongCount = 0; continue; } } if (prev != 0) { long prevId = ((prev & 0xFFFF) << 16) + current; if (EMOJI_SET.contains(prevId)) { if (lastTextPos < i - 1) { stringBuilder.append(s.substring(lastTextPos, i - 1)); lastTextPos = i - 1; } stringBuilder.append("++"); lastTextPos += 2; list.add(new SpanDescription(prevId, i - 1, i + 1)); prev = 0; prevLong = 0; prevLongCount = 0; continue; } } if (EMOJI_SET.contains(current)) { if (lastTextPos < i) { stringBuilder.append(s.substring(lastTextPos, i)); lastTextPos = i; } stringBuilder.append("+"); lastTextPos += 1; list.add(new SpanDescription(current, i, i + 1)); /*stringBuilder.setSpan(new ImageSpan(res, isAlignBottom ? ImageSpan.ALIGN_BOTTOM : ImageSpan.ALIGN_BASELINE), i, i + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);*/ prev = 0; prevLong = 0; prevLongCount = 0; } else { prev = current; prevLong = ((prevLong & 0xFFFFFFFF) << 16) + current; if (prevLongCount < 3) { prevLongCount++; } } } if (lastTextPos < s.length()) { stringBuilder.append(s.substring(lastTextPos, s.length())); } Spannable spannable = spannableFactory.newSpannable(stringBuilder.toString()); for (SpanDescription description : list) { spannable.setSpan(new EmojiSpan(this, indexes.get(description.id), emojiSideSize, originalMetrics.get(mode)), description.start, description.end, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); } return spannable; } public Spannable processEmojiMutable(CharSequence s, int mode) { long prev = 0; long prevLong = 0; int prevLongCount = 0; ArrayList<SpanDescription> list = new ArrayList<SpanDescription>(); for (int i = 0; i < s.length(); i++) { long current = s.charAt(i); if (prevLongCount == 3) { long prevId = ((prevLong & 0xFFFFFFFF) << 16) + current; if (Arrays.binarySearch(EMOJI_SORTED, prevId) >= 0) { list.add(new SpanDescription(prevId, i - 3, i + 1)); prev = 0; prevLong = 0; prevLongCount = 0; continue; } } if (prev != 0) { long prevId = ((prev & 0xFFFF) << 16) + current; if (Arrays.binarySearch(EMOJI_SORTED, prevId) >= 0) { list.add(new SpanDescription(prevId, i - 1, i + 1)); prev = 0; prevLong = 0; prevLongCount = 0; continue; } } if (Arrays.binarySearch(EMOJI_SORTED, current) >= 0) { list.add(new SpanDescription(current, i, i + 1)); prev = 0; prevLong = 0; prevLongCount = 0; } else { prev = current; prevLong = ((prevLong & 0xFFFFFFFF) << 16) + current; if (prevLongCount < 3) { prevLongCount++; } } } Spannable spannable = spannableFactory.newSpannable(s); for (SpanDescription description : list) { spannable.setSpan(new EmojiSpan(this, indexes.get(description.id), emojiSideSize, originalMetrics.get(mode)), description.start, description.end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } return spannable; } public Spannable processEmojiCompatMutable(CharSequence s, int mode) { return processEmojiMutable(s, mode); } public static long[] findFirstUniqEmoji(String s, int count) { long prev = 0; long prevLong = 0; int prevLongCount = 0; long[] res = new long[count]; int index = 0; HashSet<Long> founded = new HashSet<Long>(); for (int i = 0; i < s.length(); i++) { long current = s.charAt(i); if (prevLongCount == 3) { long prevId = ((prevLong & 0xFFFFFFFF) << 16) + current; if (EMOJI_SET.contains(prevId) && !founded.contains(prevId)) { founded.add(prevId); res[index++] = prevId; if (index >= count) break; } } if (prev != 0) { long prevId = ((prev & 0xFFFF) << 16) + current; if (EMOJI_SET.contains(prevId) && !founded.contains(prevId)) { founded.add(prevId); res[index++] = prevId; if (index >= count) break; } } if (EMOJI_SET.contains(current) && !founded.contains(current)) { founded.add(current); res[index++] = current; if (index >= count) break; } else { prev = current; prevLong = ((prevLong & 0xFFFFFFFF) << 16) + current; if (prevLongCount < 3) { prevLongCount++; } } } if (index == count) { return res; } else { long[] res2 = new long[index]; for (int i = 0; i < index; i++) { res2[i] = res[i]; } return res2; } } public static boolean containsEmoji(CharSequence s) { if (s == null) { return false; } long prev = 0; long prevLong = 0; int prevLongCount = 0; for (int i = 0; i < s.length(); i++) { long current = s.charAt(i); // if (prevLongCount == 3) { // long prevId = ((prevLong & 0xFFFFFFFF) << 16) + current; // if (Arrays.binarySearch(EMOJI_SORTED, prevId) > 0) { // return true; // } // } if (prev != 0) { long prevId = ((prev & 0xFFFF) << 16) + current; if ((current >= minEmoji2) && (current <= maxEmoji2) && Arrays.binarySearch(EMOJI_SORTED, prevId) > 0) { return true; } } if ((current >= minEmoji1) && (current <= maxEmoji1) && Arrays.binarySearch(EMOJI_SORTED, current) > 0) { return true; } else { prev = current; prevLong = ((prevLong & 0xFFFFFFFF) << 16) + current; if (prevLongCount < 3) { prevLongCount++; } } } return false; } public int getRectSize() { return rectSize; } public int getSectionIndex(long emoji) { int globalIndex = indexes.get(emoji); int x = globalIndex / COUNT_IN_ROW; int y = globalIndex % COUNT_IN_ROW; return (y / SECTION_SIDE) + (x / SECTION_SIDE) * SECTION_ROW_COUNT; } public int getSectionX(long emoji) { int globalIndex = indexes.get(emoji); return (globalIndex % COUNT_IN_ROW) % SECTION_SIDE; } public int getSectionY(long emoji) { int globalIndex = indexes.get(emoji); return (globalIndex / COUNT_IN_ROW) % SECTION_SIDE; } public Bitmap getSection(int index) { return emojiMap.get(index); } private static Paint bitmapPaint = new Paint(); static { bitmapPaint.setAntiAlias(true); bitmapPaint.setFlags(Paint.FILTER_BITMAP_FLAG); } private static Rect bitmapRect = new Rect(); private static Rect srcRect = new Rect(); private class EmojiSpan extends ReplacementSpan { private SmileProcessor processor; private int offset; private int size; private int padding; private int section; private int sectionX; private int sectionY; private Paint.FontMetricsInt originalMetrics; public EmojiSpan(SmileProcessor processor, int index, int size, Paint.FontMetricsInt original) { this.processor = processor; this.size = size; this.originalMetrics = original; int x = index / COUNT_IN_ROW; int y = index % COUNT_IN_ROW; section = (y / SECTION_SIDE) + (x / SECTION_SIDE) * SECTION_ROW_COUNT; sectionX = y % SECTION_SIDE; sectionY = x % SECTION_SIDE; } @Override public int getSize(Paint paint, CharSequence charSequence, int start, int end, Paint.FontMetricsInt fm) { padding = (int) paint.measureText(" ") / 3; if (fm != null) { fm.ascent = originalMetrics.ascent; fm.descent = originalMetrics.descent; fm.top = originalMetrics.top; fm.bottom = originalMetrics.bottom; } return size + padding * 2; } @Override public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) { Bitmap srcEmoji = processor.emojiMap.get(section); if (srcEmoji != null) { if (paint.getFontMetrics() != null) { offset = (int) (paint.getFontMetrics().descent); } x += padding; bitmapRect.set((int) x, y - size + offset, (int) (x + size), y + offset); srcRect.set(sectionX * rectSize, sectionY * rectSize, (sectionX + 1) * rectSize, (sectionY + 1) * rectSize); canvas.drawBitmap(srcEmoji, srcRect, bitmapRect, bitmapPaint); } } } }