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