EmojiProcessor.java revision 77b5c5b734f9f665577d1e3d178615db43ae1d4f
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;
2982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport android.text.Editable;
3082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport android.text.Selection;
3182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport android.text.Spannable;
3282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport android.text.SpannableString;
3382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport android.text.Spanned;
3482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport android.text.TextPaint;
3582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport android.text.method.KeyListener;
3682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport android.text.method.MetaKeyKeyListener;
3782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport android.view.KeyEvent;
3882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport android.view.inputmethod.InputConnection;
3982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
4082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport java.lang.annotation.Retention;
4182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport java.lang.annotation.RetentionPolicy;
4282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
4382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir/**
4482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * Processes the CharSequence and adds the emojis.
4582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir *
4682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * @hide
4782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir */
4882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir@AnyThread
4982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir@RestrictTo(LIBRARY_GROUP)
5077b5c5b734f9f665577d1e3d178615db43ae1d4fSiyamed Sinir@RequiresApi(19)
5182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirfinal class EmojiProcessor {
5282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
5382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
5482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * State transition commands.
5582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
5682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    @IntDef({ACTION_ADVANCE_BOTH, ACTION_ADVANCE_END, ACTION_FLUSH})
5782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    @Retention(RetentionPolicy.SOURCE)
5882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    @interface Action {
5982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    }
6082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
6182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
6282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * Advance the end pointer in CharSequence and reset the start to be the end.
6382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
6482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    private static final int ACTION_ADVANCE_BOTH = 1;
6582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
6682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
6782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * Advance end pointer in CharSequence.
6882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
6982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    private static final int ACTION_ADVANCE_END = 2;
7082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
7182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
7282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * Add a new emoji with the metadata in {@link ProcessorSm#getFlushMetadata()}. Advance end
7382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * pointer in CharSequence and reset the start to be the end.
7482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
7582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    private static final int ACTION_FLUSH = 3;
7682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
7782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
7882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * Default text size for {@link #mTextPaint}.
7982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
8082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    private static final int PAINT_TEXT_SIZE = 10;
8182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
8282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
8382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * Used to create strings required by
8482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * {@link PaintCompat#hasGlyph(android.graphics.Paint, String)}.
8582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
8682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    private static final ThreadLocal<StringBuilder> sStringBuilder = new ThreadLocal<>();
8782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
8882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
890bd2e404e8efbc6408e2bea7531b858bce639b03Siyamed Sinir     * @hide
9082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
910bd2e404e8efbc6408e2bea7531b858bce639b03Siyamed Sinir    @RestrictTo(LIBRARY_GROUP)
920bd2e404e8efbc6408e2bea7531b858bce639b03Siyamed Sinir    static final int EMOJI_COUNT_UNLIMITED = Integer.MAX_VALUE;
9382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
9482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
950bd2e404e8efbc6408e2bea7531b858bce639b03Siyamed Sinir     * Factory used to create EmojiSpans.
9682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
970bd2e404e8efbc6408e2bea7531b858bce639b03Siyamed Sinir    private final EmojiCompat.SpanFactory mSpanFactory;
9882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
9982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
10082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @see EmojiCompat.Config#setReplaceAll(boolean)
10182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
10282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    private final boolean mReplaceAll;
10382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
10482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
10582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * Emoji metadata repository.
10682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
10782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    private final MetadataRepo mMetadataRepo;
10882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
10982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
11082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * TextPaint used during {@link PaintCompat#hasGlyph(android.graphics.Paint, String)} check.
11182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
11282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    private final TextPaint mTextPaint;
11382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
11482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    EmojiProcessor(@NonNull final MetadataRepo metadataRepo,
11582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            @NonNull final EmojiCompat.SpanFactory spanFactory,
1160bd2e404e8efbc6408e2bea7531b858bce639b03Siyamed Sinir            final boolean replaceAll) {
11782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        mSpanFactory = spanFactory;
11882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        mMetadataRepo = metadataRepo;
11982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        mReplaceAll = replaceAll;
12082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        mTextPaint = new TextPaint();
12182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        mTextPaint.setTextSize(PAINT_TEXT_SIZE);
12282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    }
12382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
12482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    EmojiMetadata getEmojiMetadata(@NonNull final CharSequence charSequence) {
12582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        final ProcessorSm sm = new ProcessorSm(mMetadataRepo.getRootNode());
12682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        final int end = charSequence.length();
12782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        int currentOffset = 0;
12882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
12982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        while (currentOffset < end) {
13082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            final int codePoint = Character.codePointAt(charSequence, currentOffset);
13182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            final int action = sm.check(codePoint);
13282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            if (action != ACTION_ADVANCE_END) {
13382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                return null;
13482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            }
13582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            currentOffset += Character.charCount(codePoint);
13682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
13782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
13882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        if (sm.isInFlushableState()) {
13982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            return sm.getCurrentMetadata();
14082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
14182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
14282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        return null;
14382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    }
14482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
14582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
14682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * Checks a given CharSequence for emojis, and adds EmojiSpans if any emojis are found.
14782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * <p>
14882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * <ul>
14982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * <li>If no emojis are found, {@code charSequence} given as the input is returned without
15082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * any changes. i.e. charSequence is a String, and no emojis are found, the same String is
15182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * returned.</li>
15282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * <li>If the given input is not a Spannable (such as String), and at least one emoji is found
15382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * a new {@link android.text.Spannable} instance is returned. </li>
15482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * <li>If the given input is a Spannable, the same instance is returned. </li>
15582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * </ul>
15682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     *
15782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @param charSequence CharSequence to add the EmojiSpans, cannot be {@code null}
15882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @param start start index in the charSequence to look for emojis, should be greater than or
15982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     *              equal to {@code 0}, also less than {@code charSequence.length()}
16082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @param end end index in the charSequence to look for emojis, should be greater than or
16182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     *            equal to {@code start} parameter, also less than {@code charSequence.length()}
1620bd2e404e8efbc6408e2bea7531b858bce639b03Siyamed Sinir     * @param maxEmojiCount maximum number of emojis in the {@code charSequence}, should be greater
1630bd2e404e8efbc6408e2bea7531b858bce639b03Siyamed Sinir     *                      than or equal to {@code 0}
16482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
16582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    CharSequence process(@NonNull final CharSequence charSequence, @IntRange(from = 0) int start,
1660bd2e404e8efbc6408e2bea7531b858bce639b03Siyamed Sinir            @IntRange(from = 0) int end, @IntRange(from = 0) int maxEmojiCount) {
167f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir        final boolean isSpannableBuilder = charSequence instanceof SpannableBuilder;
168f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir        if (isSpannableBuilder) {
169f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir            ((SpannableBuilder) charSequence).beginBatchEdit();
17082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
17182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
172f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir        try {
173f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir            Spannable spannable = null;
174f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir            // if it is a spannable already, use the same instance to add/remove EmojiSpans.
175f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir            // otherwise wait until the the first EmojiSpan found in order to change the result
176f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir            // into a Spannable.
177f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir            if (isSpannableBuilder || charSequence instanceof Spannable) {
178f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                spannable = (Spannable) charSequence;
179f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir            }
180f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir
181f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir            if (spannable != null) {
182f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                final EmojiSpan[] spans = spannable.getSpans(start, end, EmojiSpan.class);
183f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                if (spans != null && spans.length > 0) {
184f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                    // remove existing spans, and realign the start, end according to spans
185f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                    // if start or end is in the middle of an emoji they should be aligned
186f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                    final int length = spans.length;
187f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                    for (int index = 0; index < length; index++) {
188f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        final EmojiSpan span = spans[index];
189f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        final int spanStart = spannable.getSpanStart(span);
190f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        final int spanEnd = spannable.getSpanEnd(span);
191f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        // Remove span only when its spanStart is NOT equal to current end.
192f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        // During add operation an emoji at index 0 is added with 0-1 as start and
193f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        // end indices. Therefore if there are emoji spans at [0-1] and [1-2]
194f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        // and end is 1, the span between 0-1 should be deleted, not 1-2.
195f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        if (spanStart != end) {
196f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                            spannable.removeSpan(span);
197f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        }
198f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        start = Math.min(spanStart, start);
199f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        end = Math.max(spanEnd, end);
20082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    }
20182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                }
20282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            }
20382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
204f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir            if (start == end || start >= charSequence.length()) {
205f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                return charSequence;
206f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir            }
20782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
2080bd2e404e8efbc6408e2bea7531b858bce639b03Siyamed Sinir            // calculate max number of emojis that can be added. since getSpans call is a relatively
2090bd2e404e8efbc6408e2bea7531b858bce639b03Siyamed Sinir            // expensive operation, do it only when maxEmojiCount is not unlimited.
2100bd2e404e8efbc6408e2bea7531b858bce639b03Siyamed Sinir            if (maxEmojiCount != EMOJI_COUNT_UNLIMITED && spannable != null) {
2110bd2e404e8efbc6408e2bea7531b858bce639b03Siyamed Sinir                maxEmojiCount -= spannable.getSpans(0, spannable.length(), EmojiSpan.class).length;
2120bd2e404e8efbc6408e2bea7531b858bce639b03Siyamed Sinir            }
213f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir            // add new ones
214f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir            int addedCount = 0;
215f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir            final ProcessorSm sm = new ProcessorSm(mMetadataRepo.getRootNode());
21682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
217f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir            int currentOffset = start;
218f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir            int codePoint = Character.codePointAt(charSequence, currentOffset);
21982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
2200bd2e404e8efbc6408e2bea7531b858bce639b03Siyamed Sinir            while (currentOffset < end && addedCount < maxEmojiCount) {
221f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                final int action = sm.check(codePoint);
22282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
223f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                switch (action) {
224f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                    case ACTION_ADVANCE_BOTH:
225f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        currentOffset += Character.charCount(codePoint);
226f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        start = currentOffset;
227f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        if (currentOffset < end) {
228f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                            codePoint = Character.codePointAt(charSequence, currentOffset);
22982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                        }
230f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        break;
231f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                    case ACTION_ADVANCE_END:
232f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        currentOffset += Character.charCount(codePoint);
233f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        if (currentOffset < end) {
234f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                            codePoint = Character.codePointAt(charSequence, currentOffset);
235f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        }
236f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        break;
237f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                    case ACTION_FLUSH:
238f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        if (mReplaceAll || !hasGlyph(charSequence, start, currentOffset,
239f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                                sm.getFlushMetadata())) {
240f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                            if (spannable == null) {
241f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                                spannable = new SpannableString(charSequence);
242f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                            }
243f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                            addEmoji(spannable, sm.getFlushMetadata(), start, currentOffset);
244f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                            addedCount++;
245f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        }
246f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        start = currentOffset;
247f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        break;
248f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                }
24982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            }
25082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
251f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir            // After the last codepoint is consumed the state machine might be in a state where it
252f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir            // identified an emoji before. i.e. abc[women-emoji] when the last codepoint is consumed
253f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir            // state machine is waiting to see if there is an emoji sequence (i.e. ZWJ).
254f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir            // Need to check if it is in such a state.
2550bd2e404e8efbc6408e2bea7531b858bce639b03Siyamed Sinir            if (sm.isInFlushableState() && addedCount < maxEmojiCount) {
256f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                if (mReplaceAll || !hasGlyph(charSequence, start, currentOffset,
257f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        sm.getCurrentMetadata())) {
258f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                    if (spannable == null) {
259f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                        spannable = new SpannableString(charSequence);
260f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                    }
261f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                    addEmoji(spannable, sm.getCurrentMetadata(), start, currentOffset);
262f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                    addedCount++;
26382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                }
264f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir            }
265f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir            return spannable == null ? charSequence : spannable;
266f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir        } finally {
267f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir            if (isSpannableBuilder) {
268f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir                ((SpannableBuilder) charSequence).endBatchEdit();
26982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            }
27082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
27182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    }
27282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
27382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
27482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * Handles onKeyDown commands from a {@link KeyListener} and if {@code keyCode} is one of
27582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * {@link KeyEvent#KEYCODE_DEL} or {@link KeyEvent#KEYCODE_FORWARD_DEL} it tries to delete an
27682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * {@link EmojiSpan} from an {@link Editable}. Returns {@code true} if an {@link EmojiSpan} is
27782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * deleted with the characters it covers.
27882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * <p/>
27982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * If there is a selection where selection start is not equal to selection end, does not
28082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * delete.
28182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     *
28282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @param editable Editable instance passed to {@link KeyListener#onKeyDown(android.view.View,
28382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     *                 Editable, int, KeyEvent)}
28482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @param keyCode keyCode passed to {@link KeyListener#onKeyDown(android.view.View, Editable,
28582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     *                int, KeyEvent)}
28682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @param event KeyEvent passed to {@link KeyListener#onKeyDown(android.view.View, Editable,
28782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     *              int, KeyEvent)}
28882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     *
28982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @return {@code true} if an {@link EmojiSpan} is deleted
29082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
29182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    static boolean handleOnKeyDown(@NonNull final Editable editable, final int keyCode,
29282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            final KeyEvent event) {
29382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        final boolean handled;
29482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        switch (keyCode) {
29582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            case KeyEvent.KEYCODE_DEL:
29682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                handled = delete(editable, event, false /*forwardDelete*/);
29782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                break;
29882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            case KeyEvent.KEYCODE_FORWARD_DEL:
29982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                handled = delete(editable, event, true /*forwardDelete*/);
30082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                break;
30182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            default:
30282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                handled = false;
30382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                break;
30482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
30582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
30682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        if (handled) {
30782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            MetaKeyKeyListener.adjustMetaAfterKeypress(editable);
30882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            return true;
30982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
31082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
31182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        return false;
31282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    }
31382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
31482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    private static boolean delete(final Editable content, final KeyEvent event,
31582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            final boolean forwardDelete) {
31682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        if (hasModifiers(event)) {
31782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            return false;
31882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
31982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
32082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        final int start = Selection.getSelectionStart(content);
32182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        final int end = Selection.getSelectionEnd(content);
32282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        if (hasInvalidSelection(start, end)) {
32382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            return false;
32482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
32582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
32682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        final EmojiSpan[] spans = content.getSpans(start, end, EmojiSpan.class);
32782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        if (spans != null && spans.length > 0) {
32882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            final int length = spans.length;
32982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            for (int index = 0; index < length; index++) {
33082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                final EmojiSpan span = spans[index];
33182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                final int spanStart = content.getSpanStart(span);
33282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                final int spanEnd = content.getSpanEnd(span);
33382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                if ((forwardDelete && spanStart == start)
33482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                        || (!forwardDelete && spanEnd == start)
33582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                        || (start > spanStart && start < spanEnd)) {
33682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    content.delete(spanStart, spanEnd);
33782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    return true;
33882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                }
33982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            }
34082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
34182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
34282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        return false;
34382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    }
34482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
34582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
34682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * Handles deleteSurroundingText commands from {@link InputConnection} and tries to delete an
34782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * {@link EmojiSpan} from an {@link Editable}. Returns {@code true} if an {@link EmojiSpan} is
34882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * deleted.
34982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * <p/>
35082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * If there is a selection where selection start is not equal to selection end, does not
35182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * delete.
35282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     *
35382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @param inputConnection InputConnection instance
35482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @param editable TextView.Editable instance
35582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @param beforeLength the number of characters before the cursor to be deleted
35682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @param afterLength the number of characters after the cursor to be deleted
35782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @param inCodePoints {@code true} if length parameters are in codepoints
35882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     *
35982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @return {@code true} if an {@link EmojiSpan} is deleted
36082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
36182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    static boolean handleDeleteSurroundingText(@NonNull final InputConnection inputConnection,
36282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            @NonNull final Editable editable, @IntRange(from = 0) final int beforeLength,
36382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            @IntRange(from = 0) final int afterLength, final boolean inCodePoints) {
36482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        if (editable == null || inputConnection == null) {
36582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            return false;
36682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
36782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
36882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        if (beforeLength < 0 || afterLength < 0) {
36982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            return false;
37082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
37182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
37282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        final int selectionStart = Selection.getSelectionStart(editable);
37382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        final int selectionEnd = Selection.getSelectionEnd(editable);
37482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
37582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        if (hasInvalidSelection(selectionStart, selectionEnd)) {
37682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            return false;
37782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
37882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
37982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        int start;
38082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        int end;
38182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        if (inCodePoints) {
38282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            // go backwards in terms of codepoints
38382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            start = CodepointIndexFinder.findIndexBackward(editable, selectionStart,
38482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    Math.max(beforeLength, 0));
38582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            end = CodepointIndexFinder.findIndexForward(editable, selectionEnd,
38682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    Math.max(afterLength, 0));
38782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
38882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            if (start == CodepointIndexFinder.INVALID_INDEX
38982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    || end == CodepointIndexFinder.INVALID_INDEX) {
39082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                return false;
39182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            }
39282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        } else {
39382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            start = Math.max(selectionStart - beforeLength, 0);
39482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            end = Math.min(selectionEnd + afterLength, editable.length());
39582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
39682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
39782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        final EmojiSpan[] spans = editable.getSpans(start, end, EmojiSpan.class);
39882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        if (spans != null && spans.length > 0) {
39982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            final int length = spans.length;
40082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            for (int index = 0; index < length; index++) {
40182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                final EmojiSpan span = spans[index];
40282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                int spanStart = editable.getSpanStart(span);
40382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                int spanEnd = editable.getSpanEnd(span);
40482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                start = Math.min(spanStart, start);
40582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                end = Math.max(spanEnd, end);
40682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            }
40782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
40882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            start = Math.max(start, 0);
40982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            end = Math.min(end, editable.length());
41082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
41182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            inputConnection.beginBatchEdit();
41282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            editable.delete(start, end);
41382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            inputConnection.endBatchEdit();
41482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            return true;
41582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
41682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
41782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        return false;
41882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    }
41982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
42082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    private static boolean hasInvalidSelection(final int start, final int end) {
42182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        return start == -1 || end == -1 || start != end;
42282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    }
42382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
42482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    private static boolean hasModifiers(KeyEvent event) {
42582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        return !KeyEvent.metaStateHasNoModifiers(event.getMetaState());
42682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    }
42782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
42882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    private void addEmoji(@NonNull final Spannable spannable, final EmojiMetadata metadata,
42982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            final int start, final int end) {
43082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        final EmojiSpan span = mSpanFactory.createSpan(metadata);
43182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        spannable.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
43282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    }
43382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
43482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
43582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * Checks whether the current OS can render a given emoji. Used by the system to decide if an
43682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * emoji span should be added. If the system cannot render it, an emoji span will be added.
43782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * Used only for the case where replaceAll is set to {@code false}.
43882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     *
43982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @param charSequence the CharSequence that the emoji is in
44082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @param start start index of the emoji in the CharSequence
44182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @param end end index of the emoji in the CharSequence
44282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @param metadata EmojiMetadata instance for the emoji
44382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     *
44482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * @return {@code true} if the OS can render emoji, {@code false} otherwise
44582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
44682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    private boolean hasGlyph(final CharSequence charSequence, int start, final int end,
44782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            final EmojiMetadata metadata) {
448611b9e77742d232d7eee9e05e95e6db63d3d97e4Siyamed Sinir        // For pre M devices, heuristic in PaintCompat can result in false positives. we are
449611b9e77742d232d7eee9e05e95e6db63d3d97e4Siyamed Sinir        // adding another heuristic using the sdkAdded field. if the emoji was added to OS
450611b9e77742d232d7eee9e05e95e6db63d3d97e4Siyamed Sinir        // at a later version we assume that the system probably cannot render it.
451611b9e77742d232d7eee9e05e95e6db63d3d97e4Siyamed Sinir        if (Build.VERSION.SDK_INT < 23 && metadata.getSdkAdded() > Build.VERSION.SDK_INT) {
45282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            return false;
45382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
45482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
45582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        // if the existence is not calculated yet
45682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        if (metadata.getHasGlyph() == EmojiMetadata.HAS_GLYPH_UNKNOWN) {
45782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            final StringBuilder builder = getStringBuilder();
45882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            builder.setLength(0);
45982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
46082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            while (start < end) {
46182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                builder.append(charSequence.charAt(start));
46282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                start++;
46382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            }
46482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
46582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            final boolean hasGlyph = PaintCompat.hasGlyph(mTextPaint, builder.toString());
46682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            metadata.setHasGlyph(hasGlyph);
46782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
46882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
46982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        return metadata.getHasGlyph() == EmojiMetadata.HAS_GLYPH_EXISTS;
47082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    }
47182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
47282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    private static StringBuilder getStringBuilder() {
47382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        if (sStringBuilder.get() == null) {
47482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            sStringBuilder.set(new StringBuilder());
47582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
47682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        return sStringBuilder.get();
47782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    }
47882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
47982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
48082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * State machine for walking over the metadata trie.
48182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
48282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    static final class ProcessorSm {
48382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
48482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        private static final int STATE_DEFAULT = 1;
48582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        private static final int STATE_WALKING = 2;
48682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
48782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        private int mState = STATE_DEFAULT;
48882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
48982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        /**
49082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * Root of the trie
49182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         */
49282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        private final MetadataRepo.Node mRootNode;
49382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
49482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        /**
49582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * Pointer to the node after last codepoint.
49682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         */
49782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        private MetadataRepo.Node mCurrentNode;
49882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
49982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        /**
50082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * The node where ACTION_FLUSH is called. Required since after flush action is
50182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * returned mCurrentNode is reset to be the root.
50282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         */
50382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        private MetadataRepo.Node mFlushNode;
50482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
50582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        /**
50682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * The code point that was checked.
50782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         */
50882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        private int mLastCodepoint;
50982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
51082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        /**
51182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * Level for mCurrentNode. Root is 0.
51282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         */
51382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        private int mCurrentDepth;
51482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
51582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        ProcessorSm(MetadataRepo.Node rootNode) {
51682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            mRootNode = rootNode;
51782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            mCurrentNode = rootNode;
51882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
51982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
52082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        @Action
52182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        int check(final int codePoint) {
52282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            final int action;
52382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            MetadataRepo.Node node = mCurrentNode.get(codePoint);
52482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            switch (mState) {
52582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                case STATE_WALKING:
52682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    if (node != null) {
52782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                        mCurrentNode = node;
52882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                        mCurrentDepth += 1;
52982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                        action = ACTION_ADVANCE_END;
53082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    } else {
53182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                        if (isTextStyle(codePoint)) {
53282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                            action = reset();
53382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                        } else if (isEmojiStyle(codePoint)) {
53482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                            action = ACTION_ADVANCE_END;
53582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                        } else if (mCurrentNode.getData() != null) {
53682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                            if (mCurrentDepth == 1) {
53782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                                if (mCurrentNode.getData().isDefaultEmoji()
53882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                                        || isEmojiStyle(mLastCodepoint)) {
53982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                                    mFlushNode = mCurrentNode;
54082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                                    action = ACTION_FLUSH;
54182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                                    reset();
54282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                                } else {
54382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                                    action = reset();
54482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                                }
54582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                            } else {
54682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                                mFlushNode = mCurrentNode;
54782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                                action = ACTION_FLUSH;
54882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                                reset();
54982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                            }
55082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                        } else {
55182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                            action = reset();
55282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                        }
55382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    }
55482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    break;
55582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                case STATE_DEFAULT:
55682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                default:
55782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    if (node == null) {
55882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                        action = reset();
55982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    } else {
56082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                        mState = STATE_WALKING;
56182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                        mCurrentNode = node;
56282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                        mCurrentDepth = 1;
56382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                        action = ACTION_ADVANCE_END;
56482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    }
56582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    break;
56682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            }
56782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
56882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            mLastCodepoint = codePoint;
56982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            return action;
57082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
57182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
57282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        @Action
57382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        private int reset() {
57482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            mState = STATE_DEFAULT;
57582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            mCurrentNode = mRootNode;
57682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            mCurrentDepth = 0;
57782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            return ACTION_ADVANCE_BOTH;
57882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
57982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
58082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        /**
58182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * @return the metadata node when ACTION_FLUSH is returned
58282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         */
58382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        EmojiMetadata getFlushMetadata() {
58482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            return mFlushNode.getData();
58582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
58682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
58782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        /**
58882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * @return current pointer to the metadata node in the trie
58982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         */
59082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        EmojiMetadata getCurrentMetadata() {
59182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            return mCurrentNode.getData();
59282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
59382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
59482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        /**
59582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * Need for the case where input is consumed, but action_flush was not called. For example
59682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * when the char sequence has single codepoint character which is a default emoji. State
59782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * machine will wait for the next.
59882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         *
59982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * @return whether the current state requires an emoji to be added
60082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         */
60182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        boolean isInFlushableState() {
60282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            return mState == STATE_WALKING && mCurrentNode.getData() != null
60382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    && (mCurrentNode.getData().isDefaultEmoji()
60482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    || isEmojiStyle(mLastCodepoint)
60582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    || mCurrentDepth > 1);
60682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
60782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
60882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        /**
60982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * @param codePoint CodePoint to check
61082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         *
61182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * @return {@code true} if the codepoint is a emoji style standardized variation selector
61282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         */
61382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        private static boolean isEmojiStyle(int codePoint) {
61482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            return codePoint == 0xFE0F;
61582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
61682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
61782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        /**
61882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * @param codePoint CodePoint to check
61982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         *
62082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * @return {@code true} if the codepoint is a text style standardized variation selector
62182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         */
62282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        private static boolean isTextStyle(int codePoint) {
62382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            return codePoint == 0xFE0E;
62482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
62582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    }
62682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
62782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    /**
62882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     * Copy of BaseInputConnection findIndexBackward and findIndexForward functions.
62982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir     */
63082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    private static final class CodepointIndexFinder {
63182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        private static final int INVALID_INDEX = -1;
63282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
63382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        /**
63482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * Find start index of the character in {@code cs} that is {@code numCodePoints} behind
63582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * starting from {@code from}.
63682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         *
63782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * @param cs CharSequence to work on
63882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * @param from the index to start going backwards
63982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * @param numCodePoints the number of codepoints
64082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         *
64182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * @return start index of the character
64282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         */
64382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        private static int findIndexBackward(final CharSequence cs, final int from,
64482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                final int numCodePoints) {
64582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            int currentIndex = from;
64682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            boolean waitingHighSurrogate = false;
64782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            final int length = cs.length();
64882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            if (currentIndex < 0 || length < currentIndex) {
64982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                return INVALID_INDEX;  // The starting point is out of range.
65082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            }
65182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            if (numCodePoints < 0) {
65282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                return INVALID_INDEX;  // Basically this should not happen.
65382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            }
65482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            int remainingCodePoints = numCodePoints;
65582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            while (true) {
65682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                if (remainingCodePoints == 0) {
65782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    return currentIndex;  // Reached to the requested length in code points.
65882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                }
65982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
66082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                --currentIndex;
66182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                if (currentIndex < 0) {
66282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    if (waitingHighSurrogate) {
66382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                        return INVALID_INDEX;  // An invalid surrogate pair is found.
66482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    }
66582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    return 0;  // Reached to the beginning of the text w/o any invalid surrogate
66682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    // pair.
66782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                }
66882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                final char c = cs.charAt(currentIndex);
66982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                if (waitingHighSurrogate) {
67082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    if (!Character.isHighSurrogate(c)) {
67182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                        return INVALID_INDEX;  // An invalid surrogate pair is found.
67282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    }
67382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    waitingHighSurrogate = false;
67482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    --remainingCodePoints;
67582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    continue;
67682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                }
67782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                if (!Character.isSurrogate(c)) {
67882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    --remainingCodePoints;
67982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    continue;
68082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                }
68182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                if (Character.isHighSurrogate(c)) {
68282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    return INVALID_INDEX;  // A invalid surrogate pair is found.
68382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                }
68482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                waitingHighSurrogate = true;
68582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            }
68682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
68782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
68882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        /**
68982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * Find start index of the character in {@code cs} that is {@code numCodePoints} ahead
69082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * starting from {@code from}.
69182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         *
69282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * @param cs CharSequence to work on
69382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * @param from the index to start going forward
69482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * @param numCodePoints the number of codepoints
69582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         *
69682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         * @return start index of the character
69782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir         */
69882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        private static int findIndexForward(final CharSequence cs, final int from,
69982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                final int numCodePoints) {
70082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            int currentIndex = from;
70182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            boolean waitingLowSurrogate = false;
70282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            final int length = cs.length();
70382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            if (currentIndex < 0 || length < currentIndex) {
70482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                return INVALID_INDEX;  // The starting point is out of range.
70582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            }
70682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            if (numCodePoints < 0) {
70782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                return INVALID_INDEX;  // Basically this should not happen.
70882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            }
70982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            int remainingCodePoints = numCodePoints;
71082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
71182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            while (true) {
71282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                if (remainingCodePoints == 0) {
71382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    return currentIndex;  // Reached to the requested length in code points.
71482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                }
71582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir
71682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                if (currentIndex >= length) {
71782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    if (waitingLowSurrogate) {
71882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                        return INVALID_INDEX;  // An invalid surrogate pair is found.
71982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    }
72082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    return length;  // Reached to the end of the text w/o any invalid surrogate
72182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    // pair.
72282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                }
72382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                final char c = cs.charAt(currentIndex);
72482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                if (waitingLowSurrogate) {
72582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    if (!Character.isLowSurrogate(c)) {
72682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                        return INVALID_INDEX;  // An invalid surrogate pair is found.
72782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    }
72882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    --remainingCodePoints;
72982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    waitingLowSurrogate = false;
73082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    ++currentIndex;
73182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    continue;
73282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                }
73382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                if (!Character.isSurrogate(c)) {
73482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    --remainingCodePoints;
73582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    ++currentIndex;
73682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    continue;
73782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                }
73882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                if (Character.isLowSurrogate(c)) {
73982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                    return INVALID_INDEX;  // A invalid surrogate pair is found.
74082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                }
74182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                waitingLowSurrogate = true;
74282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir                ++currentIndex;
74382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir            }
74482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir        }
74582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir    }
74682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir}
747