182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir/*
282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * Copyright (C) 2017 The Android Open Source Project
382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir *
482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * Licensed under the Apache License, Version 2.0 (the "License");
582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * you may not use this file except in compliance with the License.
682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * You may obtain a copy of the License at
782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir *
882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir *      http://www.apache.org/licenses/LICENSE-2.0
982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir *
1082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * Unless required by applicable law or agreed to in writing, software
1182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * distributed under the License is distributed on an "AS IS" BASIS,
1282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * See the License for the specific language governing permissions and
1482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * limitations under the License.
1582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir */
1682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirpackage android.support.text.emoji;
1782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
1882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
1982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
2082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport android.os.Build;
2182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport android.support.annotation.AnyThread;
2282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport android.support.annotation.IntDef;
2382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport android.support.annotation.IntRange;
2482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport android.support.annotation.NonNull;
2577b5c5b734f9f665577d1e3d178615db43ae1d4fSiyamed Sinirimport android.support.annotation.RequiresApi;
2682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport android.support.annotation.RestrictTo;
27f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinirimport android.support.text.emoji.widget.SpannableBuilder;
2882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport android.support.v4.graphics.PaintCompat;
29fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinirimport android.support.v4.util.Preconditions;
3082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport android.text.Editable;
3182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport android.text.Selection;
3282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport android.text.Spannable;
3382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport android.text.SpannableString;
3482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport android.text.Spanned;
3582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport android.text.TextPaint;
3682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport android.text.method.KeyListener;
3782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport android.text.method.MetaKeyKeyListener;
3882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport android.view.KeyEvent;
3982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport android.view.inputmethod.InputConnection;
4082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
4182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport java.lang.annotation.Retention;
4282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport java.lang.annotation.RetentionPolicy;
4382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
4482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir/**
4582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * Processes the CharSequence and adds the emojis.
4682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir *
4782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * @hide
4882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir */
4982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir@AnyThread
5082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir@RestrictTo(LIBRARY_GROUP)
5177b5c5b734f9f665577d1e3d178615db43ae1d4fSiyamed Sinir@RequiresApi(19)
5282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirfinal class EmojiProcessor {
5382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
5482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
5582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * State transition commands.
5682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
5782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    @IntDef({ACTION_ADVANCE_BOTH, ACTION_ADVANCE_END, ACTION_FLUSH})
5882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    @Retention(RetentionPolicy.SOURCE)
5982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    @interface Action {
6082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    }
6182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
6282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
6382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * Advance the end pointer in CharSequence and reset the start to be the end.
6482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
6582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    private static final int ACTION_ADVANCE_BOTH = 1;
6682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
6782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
6882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * Advance end pointer in CharSequence.
6982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
7082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    private static final int ACTION_ADVANCE_END = 2;
7182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
7282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
7382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * Add a new emoji with the metadata in {@link ProcessorSm#getFlushMetadata()}. Advance end
7482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * pointer in CharSequence and reset the start to be the end.
7582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
7682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    private static final int ACTION_FLUSH = 3;
7782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
7882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
790bd2e404e8efbc6408e2bea7531b858bce639b03Siyamed Sinir     * @hide
8082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
810bd2e404e8efbc6408e2bea7531b858bce639b03Siyamed Sinir    @RestrictTo(LIBRARY_GROUP)
820bd2e404e8efbc6408e2bea7531b858bce639b03Siyamed Sinir    static final int EMOJI_COUNT_UNLIMITED = Integer.MAX_VALUE;
8382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
8482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
850bd2e404e8efbc6408e2bea7531b858bce639b03Siyamed Sinir     * Factory used to create EmojiSpans.
8682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
870bd2e404e8efbc6408e2bea7531b858bce639b03Siyamed Sinir    private final EmojiCompat.SpanFactory mSpanFactory;
8882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
8982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
9082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * Emoji metadata repository.
9182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
9282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    private final MetadataRepo mMetadataRepo;
9382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
9482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
95fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir     * Utility class that checks if the system can render a given glyph.
9682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
97fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir    private GlyphChecker mGlyphChecker = new GlyphChecker();
9882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
9982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    EmojiProcessor(@NonNull final MetadataRepo metadataRepo,
100fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir            @NonNull final EmojiCompat.SpanFactory spanFactory) {
10182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        mSpanFactory = spanFactory;
10282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        mMetadataRepo = metadataRepo;
10382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    }
10482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
10582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    EmojiMetadata getEmojiMetadata(@NonNull final CharSequence charSequence) {
10682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        final ProcessorSm sm = new ProcessorSm(mMetadataRepo.getRootNode());
10782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        final int end = charSequence.length();
10882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        int currentOffset = 0;
10982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
11082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        while (currentOffset < end) {
11182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            final int codePoint = Character.codePointAt(charSequence, currentOffset);
11282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            final int action = sm.check(codePoint);
11382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            if (action != ACTION_ADVANCE_END) {
11482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                return null;
11582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            }
11682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            currentOffset += Character.charCount(codePoint);
11782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
11882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
11982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        if (sm.isInFlushableState()) {
12082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            return sm.getCurrentMetadata();
12182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
12282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
12382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        return null;
12482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    }
12582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
12682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
12782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * Checks a given CharSequence for emojis, and adds EmojiSpans if any emojis are found.
12882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * <p>
12982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * <ul>
13082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * <li>If no emojis are found, {@code charSequence} given as the input is returned without
13182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * any changes. i.e. charSequence is a String, and no emojis are found, the same String is
13282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * returned.</li>
13382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * <li>If the given input is not a Spannable (such as String), and at least one emoji is found
13482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * a new {@link android.text.Spannable} instance is returned. </li>
13582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * <li>If the given input is a Spannable, the same instance is returned. </li>
13682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * </ul>
13782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     *
13882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @param charSequence CharSequence to add the EmojiSpans, cannot be {@code null}
13982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @param start start index in the charSequence to look for emojis, should be greater than or
14082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     *              equal to {@code 0}, also less than {@code charSequence.length()}
14182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @param end end index in the charSequence to look for emojis, should be greater than or
14282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     *            equal to {@code start} parameter, also less than {@code charSequence.length()}
1430bd2e404e8efbc6408e2bea7531b858bce639b03Siyamed Sinir     * @param maxEmojiCount maximum number of emojis in the {@code charSequence}, should be greater
1440bd2e404e8efbc6408e2bea7531b858bce639b03Siyamed Sinir     *                      than or equal to {@code 0}
145fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir     * @param replaceAll whether to replace all emoji with {@link EmojiSpan}s
14682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
14782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    CharSequence process(@NonNull final CharSequence charSequence, @IntRange(from = 0) int start,
148fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir            @IntRange(from = 0) int end, @IntRange(from = 0) int maxEmojiCount,
149fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir            final boolean replaceAll) {
150f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir        final boolean isSpannableBuilder = charSequence instanceof SpannableBuilder;
151f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir        if (isSpannableBuilder) {
152f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir            ((SpannableBuilder) charSequence).beginBatchEdit();
15382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
15482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
155f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir        try {
156f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir            Spannable spannable = null;
157f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir            // if it is a spannable already, use the same instance to add/remove EmojiSpans.
158f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir            // otherwise wait until the the first EmojiSpan found in order to change the result
159f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir            // into a Spannable.
160f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir            if (isSpannableBuilder || charSequence instanceof Spannable) {
161f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                spannable = (Spannable) charSequence;
162f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir            }
163f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir
164f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir            if (spannable != null) {
165f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                final EmojiSpan[] spans = spannable.getSpans(start, end, EmojiSpan.class);
166f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                if (spans != null && spans.length > 0) {
167f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                    // remove existing spans, and realign the start, end according to spans
168f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                    // if start or end is in the middle of an emoji they should be aligned
169f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                    final int length = spans.length;
170f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                    for (int index = 0; index < length; index++) {
171f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        final EmojiSpan span = spans[index];
172f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        final int spanStart = spannable.getSpanStart(span);
173f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        final int spanEnd = spannable.getSpanEnd(span);
174f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        // Remove span only when its spanStart is NOT equal to current end.
175f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        // During add operation an emoji at index 0 is added with 0-1 as start and
176f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        // end indices. Therefore if there are emoji spans at [0-1] and [1-2]
177f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        // and end is 1, the span between 0-1 should be deleted, not 1-2.
178f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        if (spanStart != end) {
179f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                            spannable.removeSpan(span);
180f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        }
181f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        start = Math.min(spanStart, start);
182f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        end = Math.max(spanEnd, end);
18382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    }
18482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                }
18582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            }
18682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
187f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir            if (start == end || start >= charSequence.length()) {
188f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                return charSequence;
189f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir            }
19082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
1910bd2e404e8efbc6408e2bea7531b858bce639b03Siyamed Sinir            // calculate max number of emojis that can be added. since getSpans call is a relatively
1920bd2e404e8efbc6408e2bea7531b858bce639b03Siyamed Sinir            // expensive operation, do it only when maxEmojiCount is not unlimited.
1930bd2e404e8efbc6408e2bea7531b858bce639b03Siyamed Sinir            if (maxEmojiCount != EMOJI_COUNT_UNLIMITED && spannable != null) {
1940bd2e404e8efbc6408e2bea7531b858bce639b03Siyamed Sinir                maxEmojiCount -= spannable.getSpans(0, spannable.length(), EmojiSpan.class).length;
1950bd2e404e8efbc6408e2bea7531b858bce639b03Siyamed Sinir            }
196f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir            // add new ones
197f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir            int addedCount = 0;
198f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir            final ProcessorSm sm = new ProcessorSm(mMetadataRepo.getRootNode());
19982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
200f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir            int currentOffset = start;
201f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir            int codePoint = Character.codePointAt(charSequence, currentOffset);
20282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
2030bd2e404e8efbc6408e2bea7531b858bce639b03Siyamed Sinir            while (currentOffset < end && addedCount < maxEmojiCount) {
204f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                final int action = sm.check(codePoint);
20582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
206f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                switch (action) {
207f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                    case ACTION_ADVANCE_BOTH:
208f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        currentOffset += Character.charCount(codePoint);
209f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        start = currentOffset;
210f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        if (currentOffset < end) {
211f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                            codePoint = Character.codePointAt(charSequence, currentOffset);
21282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                        }
213f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        break;
214f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                    case ACTION_ADVANCE_END:
215f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        currentOffset += Character.charCount(codePoint);
216f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        if (currentOffset < end) {
217f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                            codePoint = Character.codePointAt(charSequence, currentOffset);
218f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        }
219f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        break;
220f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                    case ACTION_FLUSH:
221fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir                        if (replaceAll || !hasGlyph(charSequence, start, currentOffset,
222f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                                sm.getFlushMetadata())) {
223f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                            if (spannable == null) {
224f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                                spannable = new SpannableString(charSequence);
225f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                            }
226f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                            addEmoji(spannable, sm.getFlushMetadata(), start, currentOffset);
227f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                            addedCount++;
228f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        }
229f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        start = currentOffset;
230f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        break;
231f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                }
23282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            }
23382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
234f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir            // After the last codepoint is consumed the state machine might be in a state where it
235f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir            // identified an emoji before. i.e. abc[women-emoji] when the last codepoint is consumed
236f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir            // state machine is waiting to see if there is an emoji sequence (i.e. ZWJ).
237f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir            // Need to check if it is in such a state.
2380bd2e404e8efbc6408e2bea7531b858bce639b03Siyamed Sinir            if (sm.isInFlushableState() && addedCount < maxEmojiCount) {
239fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir                if (replaceAll || !hasGlyph(charSequence, start, currentOffset,
240f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        sm.getCurrentMetadata())) {
241f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                    if (spannable == null) {
242f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        spannable = new SpannableString(charSequence);
243f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                    }
244f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                    addEmoji(spannable, sm.getCurrentMetadata(), start, currentOffset);
245f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                    addedCount++;
24682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                }
247f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir            }
248f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir            return spannable == null ? charSequence : spannable;
249f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir        } finally {
250f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir            if (isSpannableBuilder) {
251f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                ((SpannableBuilder) charSequence).endBatchEdit();
25282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            }
25382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
25482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    }
25582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
25682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
25782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * Handles onKeyDown commands from a {@link KeyListener} and if {@code keyCode} is one of
25882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * {@link KeyEvent#KEYCODE_DEL} or {@link KeyEvent#KEYCODE_FORWARD_DEL} it tries to delete an
25982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * {@link EmojiSpan} from an {@link Editable}. Returns {@code true} if an {@link EmojiSpan} is
26082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * deleted with the characters it covers.
26182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * <p/>
26282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * If there is a selection where selection start is not equal to selection end, does not
26382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * delete.
26482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     *
26582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @param editable Editable instance passed to {@link KeyListener#onKeyDown(android.view.View,
26682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     *                 Editable, int, KeyEvent)}
26782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @param keyCode keyCode passed to {@link KeyListener#onKeyDown(android.view.View, Editable,
26882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     *                int, KeyEvent)}
26982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @param event KeyEvent passed to {@link KeyListener#onKeyDown(android.view.View, Editable,
27082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     *              int, KeyEvent)}
27182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     *
27282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @return {@code true} if an {@link EmojiSpan} is deleted
27382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
27482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    static boolean handleOnKeyDown(@NonNull final Editable editable, final int keyCode,
27582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            final KeyEvent event) {
27682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        final boolean handled;
27782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        switch (keyCode) {
27882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            case KeyEvent.KEYCODE_DEL:
27982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                handled = delete(editable, event, false /*forwardDelete*/);
28082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                break;
28182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            case KeyEvent.KEYCODE_FORWARD_DEL:
28282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                handled = delete(editable, event, true /*forwardDelete*/);
28382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                break;
28482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            default:
28582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                handled = false;
28682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                break;
28782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
28882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
28982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        if (handled) {
29082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            MetaKeyKeyListener.adjustMetaAfterKeypress(editable);
29182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            return true;
29282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
29382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
29482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        return false;
29582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    }
29682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
29782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    private static boolean delete(final Editable content, final KeyEvent event,
29882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            final boolean forwardDelete) {
29982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        if (hasModifiers(event)) {
30082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            return false;
30182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
30282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
30382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        final int start = Selection.getSelectionStart(content);
30482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        final int end = Selection.getSelectionEnd(content);
30582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        if (hasInvalidSelection(start, end)) {
30682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            return false;
30782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
30882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
30982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        final EmojiSpan[] spans = content.getSpans(start, end, EmojiSpan.class);
31082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        if (spans != null && spans.length > 0) {
31182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            final int length = spans.length;
31282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            for (int index = 0; index < length; index++) {
31382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                final EmojiSpan span = spans[index];
31482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                final int spanStart = content.getSpanStart(span);
31582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                final int spanEnd = content.getSpanEnd(span);
31682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                if ((forwardDelete && spanStart == start)
31782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                        || (!forwardDelete && spanEnd == start)
31882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                        || (start > spanStart && start < spanEnd)) {
31982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    content.delete(spanStart, spanEnd);
32082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    return true;
32182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                }
32282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            }
32382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
32482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
32582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        return false;
32682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    }
32782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
32882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
32982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * Handles deleteSurroundingText commands from {@link InputConnection} and tries to delete an
33082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * {@link EmojiSpan} from an {@link Editable}. Returns {@code true} if an {@link EmojiSpan} is
33182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * deleted.
33282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * <p/>
33382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * If there is a selection where selection start is not equal to selection end, does not
33482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * delete.
33582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     *
33682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @param inputConnection InputConnection instance
33782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @param editable TextView.Editable instance
33882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @param beforeLength the number of characters before the cursor to be deleted
33982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @param afterLength the number of characters after the cursor to be deleted
34082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @param inCodePoints {@code true} if length parameters are in codepoints
34182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     *
34282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @return {@code true} if an {@link EmojiSpan} is deleted
34382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
34482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    static boolean handleDeleteSurroundingText(@NonNull final InputConnection inputConnection,
34582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            @NonNull final Editable editable, @IntRange(from = 0) final int beforeLength,
34682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            @IntRange(from = 0) final int afterLength, final boolean inCodePoints) {
34782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        if (editable == null || inputConnection == null) {
34882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            return false;
34982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
35082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
35182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        if (beforeLength < 0 || afterLength < 0) {
35282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            return false;
35382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
35482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
35582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        final int selectionStart = Selection.getSelectionStart(editable);
35682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        final int selectionEnd = Selection.getSelectionEnd(editable);
35782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
35882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        if (hasInvalidSelection(selectionStart, selectionEnd)) {
35982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            return false;
36082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
36182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
36282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        int start;
36382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        int end;
36482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        if (inCodePoints) {
36582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            // go backwards in terms of codepoints
36682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            start = CodepointIndexFinder.findIndexBackward(editable, selectionStart,
36782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    Math.max(beforeLength, 0));
36882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            end = CodepointIndexFinder.findIndexForward(editable, selectionEnd,
36982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    Math.max(afterLength, 0));
37082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
37182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            if (start == CodepointIndexFinder.INVALID_INDEX
37282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    || end == CodepointIndexFinder.INVALID_INDEX) {
37382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                return false;
37482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            }
37582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        } else {
37682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            start = Math.max(selectionStart - beforeLength, 0);
37782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            end = Math.min(selectionEnd + afterLength, editable.length());
37882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
37982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
38082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        final EmojiSpan[] spans = editable.getSpans(start, end, EmojiSpan.class);
38182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        if (spans != null && spans.length > 0) {
38282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            final int length = spans.length;
38382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            for (int index = 0; index < length; index++) {
38482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                final EmojiSpan span = spans[index];
38582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                int spanStart = editable.getSpanStart(span);
38682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                int spanEnd = editable.getSpanEnd(span);
38782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                start = Math.min(spanStart, start);
38882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                end = Math.max(spanEnd, end);
38982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            }
39082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
39182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            start = Math.max(start, 0);
39282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            end = Math.min(end, editable.length());
39382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
39482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            inputConnection.beginBatchEdit();
39582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            editable.delete(start, end);
39682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            inputConnection.endBatchEdit();
39782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            return true;
39882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
39982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
40082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        return false;
40182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    }
40282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
40382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    private static boolean hasInvalidSelection(final int start, final int end) {
40482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        return start == -1 || end == -1 || start != end;
40582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    }
40682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
40782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    private static boolean hasModifiers(KeyEvent event) {
40882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        return !KeyEvent.metaStateHasNoModifiers(event.getMetaState());
40982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    }
41082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
41182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    private void addEmoji(@NonNull final Spannable spannable, final EmojiMetadata metadata,
41282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            final int start, final int end) {
41382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        final EmojiSpan span = mSpanFactory.createSpan(metadata);
41482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        spannable.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
41582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    }
41682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
41782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
41882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * Checks whether the current OS can render a given emoji. Used by the system to decide if an
41982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * emoji span should be added. If the system cannot render it, an emoji span will be added.
42082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * Used only for the case where replaceAll is set to {@code false}.
42182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     *
42282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @param charSequence the CharSequence that the emoji is in
42382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @param start start index of the emoji in the CharSequence
42482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @param end end index of the emoji in the CharSequence
42582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @param metadata EmojiMetadata instance for the emoji
42682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     *
42782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @return {@code true} if the OS can render emoji, {@code false} otherwise
42882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
42982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    private boolean hasGlyph(final CharSequence charSequence, int start, final int end,
43082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            final EmojiMetadata metadata) {
431611b9e77742d232d7eee9e05e95e6db63d3d97e4Siyamed Sinir        // For pre M devices, heuristic in PaintCompat can result in false positives. we are
432611b9e77742d232d7eee9e05e95e6db63d3d97e4Siyamed Sinir        // adding another heuristic using the sdkAdded field. if the emoji was added to OS
433611b9e77742d232d7eee9e05e95e6db63d3d97e4Siyamed Sinir        // at a later version we assume that the system probably cannot render it.
434611b9e77742d232d7eee9e05e95e6db63d3d97e4Siyamed Sinir        if (Build.VERSION.SDK_INT < 23 && metadata.getSdkAdded() > Build.VERSION.SDK_INT) {
43582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            return false;
43682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
43782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
43882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        // if the existence is not calculated yet
43982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        if (metadata.getHasGlyph() == EmojiMetadata.HAS_GLYPH_UNKNOWN) {
440fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir            final boolean hasGlyph = mGlyphChecker.hasGlyph(charSequence, start, end);
44182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            metadata.setHasGlyph(hasGlyph);
44282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
44382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
44482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        return metadata.getHasGlyph() == EmojiMetadata.HAS_GLYPH_EXISTS;
44582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    }
44682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
447fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir    /**
448fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir     * Set the GlyphChecker instance used by EmojiProcessor. Used for testing.
449fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir     */
450fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir    void setGlyphChecker(@NonNull final GlyphChecker glyphChecker) {
451fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir        Preconditions.checkNotNull(glyphChecker);
452fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir        mGlyphChecker = glyphChecker;
45382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    }
45482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
45582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
45682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * State machine for walking over the metadata trie.
45782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
45882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    static final class ProcessorSm {
45982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
46082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        private static final int STATE_DEFAULT = 1;
46182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        private static final int STATE_WALKING = 2;
46282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
46382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        private int mState = STATE_DEFAULT;
46482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
46582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        /**
46682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * Root of the trie
46782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         */
46882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        private final MetadataRepo.Node mRootNode;
46982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
47082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        /**
47182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * Pointer to the node after last codepoint.
47282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         */
47382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        private MetadataRepo.Node mCurrentNode;
47482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
47582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        /**
47682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * The node where ACTION_FLUSH is called. Required since after flush action is
47782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * returned mCurrentNode is reset to be the root.
47882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         */
47982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        private MetadataRepo.Node mFlushNode;
48082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
48182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        /**
48282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * The code point that was checked.
48382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         */
48482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        private int mLastCodepoint;
48582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
48682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        /**
48782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * Level for mCurrentNode. Root is 0.
48882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         */
48982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        private int mCurrentDepth;
49082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
49182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        ProcessorSm(MetadataRepo.Node rootNode) {
49282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            mRootNode = rootNode;
49382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            mCurrentNode = rootNode;
49482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
49582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
49682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        @Action
49782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        int check(final int codePoint) {
49882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            final int action;
49982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            MetadataRepo.Node node = mCurrentNode.get(codePoint);
50082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            switch (mState) {
50182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                case STATE_WALKING:
50282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    if (node != null) {
50382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                        mCurrentNode = node;
50482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                        mCurrentDepth += 1;
50582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                        action = ACTION_ADVANCE_END;
50682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    } else {
50782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                        if (isTextStyle(codePoint)) {
50882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                            action = reset();
50982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                        } else if (isEmojiStyle(codePoint)) {
51082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                            action = ACTION_ADVANCE_END;
51182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                        } else if (mCurrentNode.getData() != null) {
51282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                            if (mCurrentDepth == 1) {
51382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                                if (mCurrentNode.getData().isDefaultEmoji()
51482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                                        || isEmojiStyle(mLastCodepoint)) {
51582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                                    mFlushNode = mCurrentNode;
51682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                                    action = ACTION_FLUSH;
51782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                                    reset();
51882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                                } else {
51982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                                    action = reset();
52082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                                }
52182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                            } else {
52282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                                mFlushNode = mCurrentNode;
52382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                                action = ACTION_FLUSH;
52482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                                reset();
52582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                            }
52682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                        } else {
52782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                            action = reset();
52882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                        }
52982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    }
53082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    break;
53182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                case STATE_DEFAULT:
53282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                default:
53382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    if (node == null) {
53482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                        action = reset();
53582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    } else {
53682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                        mState = STATE_WALKING;
53782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                        mCurrentNode = node;
53882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                        mCurrentDepth = 1;
53982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                        action = ACTION_ADVANCE_END;
54082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    }
54182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    break;
54282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            }
54382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
54482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            mLastCodepoint = codePoint;
54582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            return action;
54682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
54782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
54882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        @Action
54982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        private int reset() {
55082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            mState = STATE_DEFAULT;
55182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            mCurrentNode = mRootNode;
55282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            mCurrentDepth = 0;
55382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            return ACTION_ADVANCE_BOTH;
55482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
55582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
55682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        /**
55782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * @return the metadata node when ACTION_FLUSH is returned
55882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         */
55982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        EmojiMetadata getFlushMetadata() {
56082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            return mFlushNode.getData();
56182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
56282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
56382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        /**
56482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * @return current pointer to the metadata node in the trie
56582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         */
56682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        EmojiMetadata getCurrentMetadata() {
56782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            return mCurrentNode.getData();
56882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
56982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
57082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        /**
57182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * Need for the case where input is consumed, but action_flush was not called. For example
57282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * when the char sequence has single codepoint character which is a default emoji. State
57382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * machine will wait for the next.
57482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         *
57582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * @return whether the current state requires an emoji to be added
57682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         */
57782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        boolean isInFlushableState() {
57882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            return mState == STATE_WALKING && mCurrentNode.getData() != null
57982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    && (mCurrentNode.getData().isDefaultEmoji()
58082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    || isEmojiStyle(mLastCodepoint)
58182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    || mCurrentDepth > 1);
58282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
58382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
58482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        /**
58582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * @param codePoint CodePoint to check
58682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         *
58782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * @return {@code true} if the codepoint is a emoji style standardized variation selector
58882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         */
58982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        private static boolean isEmojiStyle(int codePoint) {
59082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            return codePoint == 0xFE0F;
59182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
59282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
59382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        /**
59482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * @param codePoint CodePoint to check
59582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         *
59682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * @return {@code true} if the codepoint is a text style standardized variation selector
59782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         */
59882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        private static boolean isTextStyle(int codePoint) {
59982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            return codePoint == 0xFE0E;
60082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
60182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    }
60282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
60382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
60482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * Copy of BaseInputConnection findIndexBackward and findIndexForward functions.
60582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
60682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    private static final class CodepointIndexFinder {
60782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        private static final int INVALID_INDEX = -1;
60882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
60982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        /**
61082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * Find start index of the character in {@code cs} that is {@code numCodePoints} behind
61182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * starting from {@code from}.
61282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         *
61382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * @param cs CharSequence to work on
61482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * @param from the index to start going backwards
61582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * @param numCodePoints the number of codepoints
61682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         *
61782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * @return start index of the character
61882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         */
61982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        private static int findIndexBackward(final CharSequence cs, final int from,
62082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                final int numCodePoints) {
62182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            int currentIndex = from;
62282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            boolean waitingHighSurrogate = false;
62382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            final int length = cs.length();
62482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            if (currentIndex < 0 || length < currentIndex) {
62582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                return INVALID_INDEX;  // The starting point is out of range.
62682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            }
62782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            if (numCodePoints < 0) {
62882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                return INVALID_INDEX;  // Basically this should not happen.
62982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            }
63082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            int remainingCodePoints = numCodePoints;
63182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            while (true) {
63282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                if (remainingCodePoints == 0) {
63382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    return currentIndex;  // Reached to the requested length in code points.
63482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                }
63582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
63682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                --currentIndex;
63782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                if (currentIndex < 0) {
63882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    if (waitingHighSurrogate) {
63982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                        return INVALID_INDEX;  // An invalid surrogate pair is found.
64082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    }
64182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    return 0;  // Reached to the beginning of the text w/o any invalid surrogate
64282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    // pair.
64382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                }
64482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                final char c = cs.charAt(currentIndex);
64582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                if (waitingHighSurrogate) {
64682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    if (!Character.isHighSurrogate(c)) {
64782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                        return INVALID_INDEX;  // An invalid surrogate pair is found.
64882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    }
64982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    waitingHighSurrogate = false;
65082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    --remainingCodePoints;
65182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    continue;
65282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                }
65382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                if (!Character.isSurrogate(c)) {
65482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    --remainingCodePoints;
65582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    continue;
65682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                }
65782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                if (Character.isHighSurrogate(c)) {
65882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    return INVALID_INDEX;  // A invalid surrogate pair is found.
65982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                }
66082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                waitingHighSurrogate = true;
66182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            }
66282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
66382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
66482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        /**
66582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * Find start index of the character in {@code cs} that is {@code numCodePoints} ahead
66682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * starting from {@code from}.
66782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         *
66882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * @param cs CharSequence to work on
66982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * @param from the index to start going forward
67082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * @param numCodePoints the number of codepoints
67182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         *
67282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * @return start index of the character
67382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         */
67482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        private static int findIndexForward(final CharSequence cs, final int from,
67582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                final int numCodePoints) {
67682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            int currentIndex = from;
67782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            boolean waitingLowSurrogate = false;
67882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            final int length = cs.length();
67982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            if (currentIndex < 0 || length < currentIndex) {
68082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                return INVALID_INDEX;  // The starting point is out of range.
68182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            }
68282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            if (numCodePoints < 0) {
68382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                return INVALID_INDEX;  // Basically this should not happen.
68482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            }
68582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            int remainingCodePoints = numCodePoints;
68682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
68782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            while (true) {
68882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                if (remainingCodePoints == 0) {
68982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    return currentIndex;  // Reached to the requested length in code points.
69082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                }
69182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
69282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                if (currentIndex >= length) {
69382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    if (waitingLowSurrogate) {
69482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                        return INVALID_INDEX;  // An invalid surrogate pair is found.
69582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    }
69682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    return length;  // Reached to the end of the text w/o any invalid surrogate
69782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    // pair.
69882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                }
69982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                final char c = cs.charAt(currentIndex);
70082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                if (waitingLowSurrogate) {
70182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    if (!Character.isLowSurrogate(c)) {
70282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                        return INVALID_INDEX;  // An invalid surrogate pair is found.
70382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    }
70482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    --remainingCodePoints;
70582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    waitingLowSurrogate = false;
70682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    ++currentIndex;
70782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    continue;
70882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                }
70982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                if (!Character.isSurrogate(c)) {
71082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    --remainingCodePoints;
71182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    ++currentIndex;
71282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    continue;
71382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                }
71482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                if (Character.isLowSurrogate(c)) {
71582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    return INVALID_INDEX;  // A invalid surrogate pair is found.
71682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                }
71782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                waitingLowSurrogate = true;
71882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                ++currentIndex;
71982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            }
72082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
72182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    }
722fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir
723fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir    /**
724fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir     * Utility class that checks if the system can render a given glyph.
725fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir     *
726fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir     * @hide
727fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir     */
728fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir    @AnyThread
729fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir    @RestrictTo(LIBRARY_GROUP)
730fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir    public static class GlyphChecker {
731fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir        /**
732fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir         * Default text size for {@link #mTextPaint}.
733fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir         */
734fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir        private static final int PAINT_TEXT_SIZE = 10;
735fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir
736fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir        /**
737fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir         * Used to create strings required by
738fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir         * {@link PaintCompat#hasGlyph(android.graphics.Paint, String)}.
739fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir         */
740fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir        private static final ThreadLocal<StringBuilder> sStringBuilder = new ThreadLocal<>();
741fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir
742fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir        /**
743fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir         * TextPaint used during {@link PaintCompat#hasGlyph(android.graphics.Paint, String)} check.
744fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir         */
745fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir        private final TextPaint mTextPaint;
746fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir
747fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir        GlyphChecker() {
748fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir            mTextPaint = new TextPaint();
749fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir            mTextPaint.setTextSize(PAINT_TEXT_SIZE);
750fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir        }
751fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir
752fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir        /**
753fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir         * Returns whether the system can render an emoji.
754fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir         *
755fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir         * @param charSequence the CharSequence that the emoji is in
756fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir         * @param start start index of the emoji in the CharSequence
757fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir         * @param end end index of the emoji in the CharSequence
758fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir         *
759fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir         * @return {@code true} if the OS can render emoji, {@code false} otherwise
760fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir         */
761fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir        public boolean hasGlyph(final CharSequence charSequence, int start, final int end) {
762fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir            final StringBuilder builder = getStringBuilder();
763fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir            builder.setLength(0);
764fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir
765fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir            while (start < end) {
766fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir                builder.append(charSequence.charAt(start));
767fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir                start++;
768fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir            }
769fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir
770fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir            return PaintCompat.hasGlyph(mTextPaint, builder.toString());
771fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir        }
772fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir
773fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir        private static StringBuilder getStringBuilder() {
774fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir            if (sStringBuilder.get() == null) {
775fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir                sStringBuilder.set(new StringBuilder());
776fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir            }
777fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir            return sStringBuilder.get();
778fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir        }
779fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir
780fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir    }
78182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir}
782