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