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