182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir/* 2ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikas * Copyright 2018 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 */ 16ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikaspackage androidx.emoji.text; 1782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 18ac5fe7c617c66850fff75a9fce9979c6e5674b0fAurimas Liutikasimport static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; 1982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 2082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport android.os.Build; 2182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport android.text.Editable; 2282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport android.text.Selection; 2382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport android.text.Spannable; 2482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport android.text.SpannableString; 2582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport android.text.Spanned; 2682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport android.text.TextPaint; 2782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport android.text.method.KeyListener; 2882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport android.text.method.MetaKeyKeyListener; 2982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport android.view.KeyEvent; 3082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport android.view.inputmethod.InputConnection; 3182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 3238746a682208c764867ffe4415d0b62fb22b5b9aAurimas Liutikasimport androidx.annotation.AnyThread; 3338746a682208c764867ffe4415d0b62fb22b5b9aAurimas Liutikasimport androidx.annotation.IntDef; 3438746a682208c764867ffe4415d0b62fb22b5b9aAurimas Liutikasimport androidx.annotation.IntRange; 3538746a682208c764867ffe4415d0b62fb22b5b9aAurimas Liutikasimport androidx.annotation.NonNull; 3638746a682208c764867ffe4415d0b62fb22b5b9aAurimas Liutikasimport androidx.annotation.Nullable; 3738746a682208c764867ffe4415d0b62fb22b5b9aAurimas Liutikasimport androidx.annotation.RequiresApi; 3838746a682208c764867ffe4415d0b62fb22b5b9aAurimas Liutikasimport androidx.annotation.RestrictTo; 3938746a682208c764867ffe4415d0b62fb22b5b9aAurimas Liutikasimport androidx.core.graphics.PaintCompat; 4038746a682208c764867ffe4415d0b62fb22b5b9aAurimas Liutikasimport androidx.core.util.Preconditions; 4138746a682208c764867ffe4415d0b62fb22b5b9aAurimas Liutikasimport androidx.emoji.widget.SpannableBuilder; 4238746a682208c764867ffe4415d0b62fb22b5b9aAurimas Liutikas 4382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport java.lang.annotation.Retention; 4482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirimport java.lang.annotation.RetentionPolicy; 45ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popaimport java.util.Arrays; 46ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popaimport java.util.List; 4782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 4882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir/** 4982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * Processes the CharSequence and adds the emojis. 5082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * 5182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * @hide 5282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir */ 5382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir@AnyThread 5482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir@RestrictTo(LIBRARY_GROUP) 5577b5c5b734f9f665577d1e3d178615db43ae1d4fSiyamed Sinir@RequiresApi(19) 5682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinirfinal class EmojiProcessor { 5782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 5882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir /** 5982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * State transition commands. 6082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir */ 6182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir @IntDef({ACTION_ADVANCE_BOTH, ACTION_ADVANCE_END, ACTION_FLUSH}) 6282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir @Retention(RetentionPolicy.SOURCE) 63fb15b88b8ff336c2f8c2dff88e55eaba3491349aSiyamed Sinir private @interface Action { 6482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 6582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 6682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir /** 6782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * Advance the end pointer in CharSequence and reset the start to be the end. 6882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir */ 6982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir private static final int ACTION_ADVANCE_BOTH = 1; 7082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 7182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir /** 7282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * Advance end pointer in CharSequence. 7382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir */ 7482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir private static final int ACTION_ADVANCE_END = 2; 7582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 7682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir /** 7782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * Add a new emoji with the metadata in {@link ProcessorSm#getFlushMetadata()}. Advance end 7882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * pointer in CharSequence and reset the start to be the end. 7982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir */ 8082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir private static final int ACTION_FLUSH = 3; 8182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 8282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir /** 830bd2e404e8efbc6408e2bea7531b858bce639b03Siyamed Sinir * Factory used to create EmojiSpans. 8482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir */ 850bd2e404e8efbc6408e2bea7531b858bce639b03Siyamed Sinir private final EmojiCompat.SpanFactory mSpanFactory; 8682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 8782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir /** 8882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * Emoji metadata repository. 8982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir */ 9082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir private final MetadataRepo mMetadataRepo; 9182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 9282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir /** 93fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir * Utility class that checks if the system can render a given glyph. 9482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir */ 95fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir private GlyphChecker mGlyphChecker = new GlyphChecker(); 9682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 97ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa /** 98ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa * @see EmojiCompat.Config#setUseEmojiAsDefaultStyle(boolean) 99ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa */ 100ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa private final boolean mUseEmojiAsDefaultStyle; 101ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa 102ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa /** 103ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa * @see EmojiCompat.Config#setUseEmojiAsDefaultStyle(boolean, List) 104ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa */ 105ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa private final int[] mEmojiAsDefaultStyleExceptions; 106ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa 10782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir EmojiProcessor(@NonNull final MetadataRepo metadataRepo, 108ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa @NonNull final EmojiCompat.SpanFactory spanFactory, 109ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa final boolean useEmojiAsDefaultStyle, 110ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa @Nullable final int[] emojiAsDefaultStyleExceptions) { 11182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir mSpanFactory = spanFactory; 11282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir mMetadataRepo = metadataRepo; 113ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa mUseEmojiAsDefaultStyle = useEmojiAsDefaultStyle; 114ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa mEmojiAsDefaultStyleExceptions = emojiAsDefaultStyleExceptions; 11582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 11682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 11782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir EmojiMetadata getEmojiMetadata(@NonNull final CharSequence charSequence) { 118ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa final ProcessorSm sm = new ProcessorSm(mMetadataRepo.getRootNode(), 119ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa mUseEmojiAsDefaultStyle, mEmojiAsDefaultStyleExceptions); 12082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir final int end = charSequence.length(); 12182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir int currentOffset = 0; 12282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 12382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir while (currentOffset < end) { 12482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir final int codePoint = Character.codePointAt(charSequence, currentOffset); 12582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir final int action = sm.check(codePoint); 12682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir if (action != ACTION_ADVANCE_END) { 12782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir return null; 12882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 12982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir currentOffset += Character.charCount(codePoint); 13082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 13182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 13282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir if (sm.isInFlushableState()) { 13382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir return sm.getCurrentMetadata(); 13482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 13582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 13682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir return null; 13782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 13882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 13982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir /** 14082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * Checks a given CharSequence for emojis, and adds EmojiSpans if any emojis are found. 14182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * <p> 14282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * <ul> 14382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * <li>If no emojis are found, {@code charSequence} given as the input is returned without 14482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * any changes. i.e. charSequence is a String, and no emojis are found, the same String is 14582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * returned.</li> 14682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * <li>If the given input is not a Spannable (such as String), and at least one emoji is found 14782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * a new {@link android.text.Spannable} instance is returned. </li> 14882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * <li>If the given input is a Spannable, the same instance is returned. </li> 14982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * </ul> 15082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * 15182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * @param charSequence CharSequence to add the EmojiSpans, cannot be {@code null} 15282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * @param start start index in the charSequence to look for emojis, should be greater than or 15382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * equal to {@code 0}, also less than {@code charSequence.length()} 15482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * @param end end index in the charSequence to look for emojis, should be greater than or 15582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * equal to {@code start} parameter, also less than {@code charSequence.length()} 1560bd2e404e8efbc6408e2bea7531b858bce639b03Siyamed Sinir * @param maxEmojiCount maximum number of emojis in the {@code charSequence}, should be greater 1570bd2e404e8efbc6408e2bea7531b858bce639b03Siyamed Sinir * than or equal to {@code 0} 158fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir * @param replaceAll whether to replace all emoji with {@link EmojiSpan}s 15982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir */ 16082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir CharSequence process(@NonNull final CharSequence charSequence, @IntRange(from = 0) int start, 161fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir @IntRange(from = 0) int end, @IntRange(from = 0) int maxEmojiCount, 162fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir final boolean replaceAll) { 163f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir final boolean isSpannableBuilder = charSequence instanceof SpannableBuilder; 164f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir if (isSpannableBuilder) { 165f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir ((SpannableBuilder) charSequence).beginBatchEdit(); 16682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 16782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 168f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir try { 169f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir Spannable spannable = null; 170f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir // if it is a spannable already, use the same instance to add/remove EmojiSpans. 171f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir // otherwise wait until the the first EmojiSpan found in order to change the result 172f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir // into a Spannable. 173f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir if (isSpannableBuilder || charSequence instanceof Spannable) { 174f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir spannable = (Spannable) charSequence; 175c59a6c5f501efc56fcfa0fbdb0a52fcb0c5186ebSiyamed Sinir } else if (charSequence instanceof Spanned) { 176c59a6c5f501efc56fcfa0fbdb0a52fcb0c5186ebSiyamed Sinir // check if there are any EmojiSpans as cheap as possible 177c59a6c5f501efc56fcfa0fbdb0a52fcb0c5186ebSiyamed Sinir // start-1, end+1 will return emoji span that starts/ends at start/end indices 178c59a6c5f501efc56fcfa0fbdb0a52fcb0c5186ebSiyamed Sinir final int nextSpanTransition = ((Spanned) charSequence).nextSpanTransition( 179c59a6c5f501efc56fcfa0fbdb0a52fcb0c5186ebSiyamed Sinir start - 1, end + 1, EmojiSpan.class); 180c59a6c5f501efc56fcfa0fbdb0a52fcb0c5186ebSiyamed Sinir 181c59a6c5f501efc56fcfa0fbdb0a52fcb0c5186ebSiyamed Sinir if (nextSpanTransition <= end) { 182c59a6c5f501efc56fcfa0fbdb0a52fcb0c5186ebSiyamed Sinir spannable = new SpannableString(charSequence); 183c59a6c5f501efc56fcfa0fbdb0a52fcb0c5186ebSiyamed Sinir } 184f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir } 185f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir 186f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir if (spannable != null) { 187f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir final EmojiSpan[] spans = spannable.getSpans(start, end, EmojiSpan.class); 188f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir if (spans != null && spans.length > 0) { 189f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir // remove existing spans, and realign the start, end according to spans 190f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir // if start or end is in the middle of an emoji they should be aligned 191f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir final int length = spans.length; 192f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir for (int index = 0; index < length; index++) { 193f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir final EmojiSpan span = spans[index]; 194f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir final int spanStart = spannable.getSpanStart(span); 195f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir final int spanEnd = spannable.getSpanEnd(span); 196f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir // Remove span only when its spanStart is NOT equal to current end. 197f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir // During add operation an emoji at index 0 is added with 0-1 as start and 198f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir // end indices. Therefore if there are emoji spans at [0-1] and [1-2] 199f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir // and end is 1, the span between 0-1 should be deleted, not 1-2. 200f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir if (spanStart != end) { 201f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir spannable.removeSpan(span); 202f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir } 203f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir start = Math.min(spanStart, start); 204f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir end = Math.max(spanEnd, end); 20582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 20682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 20782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 20882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 209f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir if (start == end || start >= charSequence.length()) { 210f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir return charSequence; 211f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir } 21282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 2130bd2e404e8efbc6408e2bea7531b858bce639b03Siyamed Sinir // calculate max number of emojis that can be added. since getSpans call is a relatively 2140bd2e404e8efbc6408e2bea7531b858bce639b03Siyamed Sinir // expensive operation, do it only when maxEmojiCount is not unlimited. 21536d5e1b59cef90bb72936e6641970e04b7bde18cAurimas Liutikas if (maxEmojiCount != EmojiCompat.EMOJI_COUNT_UNLIMITED && spannable != null) { 2160bd2e404e8efbc6408e2bea7531b858bce639b03Siyamed Sinir maxEmojiCount -= spannable.getSpans(0, spannable.length(), EmojiSpan.class).length; 2170bd2e404e8efbc6408e2bea7531b858bce639b03Siyamed Sinir } 218f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir // add new ones 219f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir int addedCount = 0; 220ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa final ProcessorSm sm = new ProcessorSm(mMetadataRepo.getRootNode(), 221ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa mUseEmojiAsDefaultStyle, mEmojiAsDefaultStyleExceptions); 22282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 223f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir int currentOffset = start; 224f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir int codePoint = Character.codePointAt(charSequence, currentOffset); 22582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 2260bd2e404e8efbc6408e2bea7531b858bce639b03Siyamed Sinir while (currentOffset < end && addedCount < maxEmojiCount) { 227f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir final int action = sm.check(codePoint); 22882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 229f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir switch (action) { 230f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir case ACTION_ADVANCE_BOTH: 2317f51b0e4448c80ef32a8c41d90f8bc91c55ed3d4Siyamed Sinir start += Character.charCount(Character.codePointAt(charSequence, start)); 2327f51b0e4448c80ef32a8c41d90f8bc91c55ed3d4Siyamed Sinir currentOffset = start; 233f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir if (currentOffset < end) { 234f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir codePoint = Character.codePointAt(charSequence, currentOffset); 23582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 236f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir break; 237f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir case ACTION_ADVANCE_END: 238f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir currentOffset += Character.charCount(codePoint); 239f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir if (currentOffset < end) { 240f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir codePoint = Character.codePointAt(charSequence, currentOffset); 241f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir } 242f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir break; 243f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir case ACTION_FLUSH: 244fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir if (replaceAll || !hasGlyph(charSequence, start, currentOffset, 245f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir sm.getFlushMetadata())) { 246f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir if (spannable == null) { 247f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir spannable = new SpannableString(charSequence); 248f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir } 249f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir addEmoji(spannable, sm.getFlushMetadata(), start, currentOffset); 250f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir addedCount++; 251f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir } 252f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir start = currentOffset; 253f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir break; 254f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir } 25582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 25682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 257f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir // After the last codepoint is consumed the state machine might be in a state where it 258f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir // identified an emoji before. i.e. abc[women-emoji] when the last codepoint is consumed 259f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir // state machine is waiting to see if there is an emoji sequence (i.e. ZWJ). 260f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir // Need to check if it is in such a state. 2610bd2e404e8efbc6408e2bea7531b858bce639b03Siyamed Sinir if (sm.isInFlushableState() && addedCount < maxEmojiCount) { 262fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir if (replaceAll || !hasGlyph(charSequence, start, currentOffset, 263f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir sm.getCurrentMetadata())) { 264f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir if (spannable == null) { 265f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir spannable = new SpannableString(charSequence); 266f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir } 267f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir addEmoji(spannable, sm.getCurrentMetadata(), start, currentOffset); 268f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir addedCount++; 26982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 270f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir } 271f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir return spannable == null ? charSequence : spannable; 272f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir } finally { 273f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir if (isSpannableBuilder) { 274f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir ((SpannableBuilder) charSequence).endBatchEdit(); 27582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 27682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 27782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 27882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 27982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir /** 28082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * Handles onKeyDown commands from a {@link KeyListener} and if {@code keyCode} is one of 28182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * {@link KeyEvent#KEYCODE_DEL} or {@link KeyEvent#KEYCODE_FORWARD_DEL} it tries to delete an 28282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * {@link EmojiSpan} from an {@link Editable}. Returns {@code true} if an {@link EmojiSpan} is 28382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * deleted with the characters it covers. 28482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * <p/> 28582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * If there is a selection where selection start is not equal to selection end, does not 28682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * delete. 28782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * 28882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * @param editable Editable instance passed to {@link KeyListener#onKeyDown(android.view.View, 28982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * Editable, int, KeyEvent)} 29082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * @param keyCode keyCode passed to {@link KeyListener#onKeyDown(android.view.View, Editable, 29182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * int, KeyEvent)} 29282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * @param event KeyEvent passed to {@link KeyListener#onKeyDown(android.view.View, Editable, 29382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * int, KeyEvent)} 29482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * 29582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * @return {@code true} if an {@link EmojiSpan} is deleted 29682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir */ 29782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir static boolean handleOnKeyDown(@NonNull final Editable editable, final int keyCode, 29882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir final KeyEvent event) { 29982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir final boolean handled; 30082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir switch (keyCode) { 30182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir case KeyEvent.KEYCODE_DEL: 30282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir handled = delete(editable, event, false /*forwardDelete*/); 30382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir break; 30482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir case KeyEvent.KEYCODE_FORWARD_DEL: 30582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir handled = delete(editable, event, true /*forwardDelete*/); 30682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir break; 30782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir default: 30882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir handled = false; 30982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir break; 31082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 31182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 31282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir if (handled) { 31382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir MetaKeyKeyListener.adjustMetaAfterKeypress(editable); 31482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir return true; 31582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 31682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 31782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir return false; 31882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 31982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 32082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir private static boolean delete(final Editable content, final KeyEvent event, 32182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir final boolean forwardDelete) { 32282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir if (hasModifiers(event)) { 32382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir return false; 32482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 32582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 32682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir final int start = Selection.getSelectionStart(content); 32782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir final int end = Selection.getSelectionEnd(content); 32882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir if (hasInvalidSelection(start, end)) { 32982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir return false; 33082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 33182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 33282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir final EmojiSpan[] spans = content.getSpans(start, end, EmojiSpan.class); 33382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir if (spans != null && spans.length > 0) { 33482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir final int length = spans.length; 33582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir for (int index = 0; index < length; index++) { 33682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir final EmojiSpan span = spans[index]; 33782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir final int spanStart = content.getSpanStart(span); 33882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir final int spanEnd = content.getSpanEnd(span); 33982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir if ((forwardDelete && spanStart == start) 34082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir || (!forwardDelete && spanEnd == start) 34182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir || (start > spanStart && start < spanEnd)) { 34282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir content.delete(spanStart, spanEnd); 34382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir return true; 34482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 34582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 34682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 34782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 34882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir return false; 34982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 35082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 35182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir /** 35282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * Handles deleteSurroundingText commands from {@link InputConnection} and tries to delete an 35382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * {@link EmojiSpan} from an {@link Editable}. Returns {@code true} if an {@link EmojiSpan} is 35482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * deleted. 35582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * <p/> 35682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * If there is a selection where selection start is not equal to selection end, does not 35782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * delete. 35882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * 35982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * @param inputConnection InputConnection instance 36082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * @param editable TextView.Editable instance 36182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * @param beforeLength the number of characters before the cursor to be deleted 36282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * @param afterLength the number of characters after the cursor to be deleted 36382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * @param inCodePoints {@code true} if length parameters are in codepoints 36482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * 36582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * @return {@code true} if an {@link EmojiSpan} is deleted 36682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir */ 36782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir static boolean handleDeleteSurroundingText(@NonNull final InputConnection inputConnection, 36882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir @NonNull final Editable editable, @IntRange(from = 0) final int beforeLength, 36982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir @IntRange(from = 0) final int afterLength, final boolean inCodePoints) { 370fb15b88b8ff336c2f8c2dff88e55eaba3491349aSiyamed Sinir //noinspection ConstantConditions 37182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir if (editable == null || inputConnection == null) { 37282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir return false; 37382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 37482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 37582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir if (beforeLength < 0 || afterLength < 0) { 37682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir return false; 37782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 37882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 37982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir final int selectionStart = Selection.getSelectionStart(editable); 38082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir final int selectionEnd = Selection.getSelectionEnd(editable); 38182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 38282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir if (hasInvalidSelection(selectionStart, selectionEnd)) { 38382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir return false; 38482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 38582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 38682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir int start; 38782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir int end; 38882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir if (inCodePoints) { 38982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir // go backwards in terms of codepoints 39082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir start = CodepointIndexFinder.findIndexBackward(editable, selectionStart, 39182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir Math.max(beforeLength, 0)); 39282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir end = CodepointIndexFinder.findIndexForward(editable, selectionEnd, 39382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir Math.max(afterLength, 0)); 39482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 39582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir if (start == CodepointIndexFinder.INVALID_INDEX 39682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir || end == CodepointIndexFinder.INVALID_INDEX) { 39782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir return false; 39882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 39982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } else { 40082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir start = Math.max(selectionStart - beforeLength, 0); 40182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir end = Math.min(selectionEnd + afterLength, editable.length()); 40282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 40382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 40482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir final EmojiSpan[] spans = editable.getSpans(start, end, EmojiSpan.class); 40582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir if (spans != null && spans.length > 0) { 40682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir final int length = spans.length; 40782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir for (int index = 0; index < length; index++) { 40882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir final EmojiSpan span = spans[index]; 40982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir int spanStart = editable.getSpanStart(span); 41082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir int spanEnd = editable.getSpanEnd(span); 41182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir start = Math.min(spanStart, start); 41282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir end = Math.max(spanEnd, end); 41382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 41482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 41582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir start = Math.max(start, 0); 41682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir end = Math.min(end, editable.length()); 41782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 41882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir inputConnection.beginBatchEdit(); 41982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir editable.delete(start, end); 42082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir inputConnection.endBatchEdit(); 42182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir return true; 42282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 42382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 42482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir return false; 42582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 42682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 42782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir private static boolean hasInvalidSelection(final int start, final int end) { 42882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir return start == -1 || end == -1 || start != end; 42982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 43082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 43182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir private static boolean hasModifiers(KeyEvent event) { 43282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir return !KeyEvent.metaStateHasNoModifiers(event.getMetaState()); 43382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 43482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 43582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir private void addEmoji(@NonNull final Spannable spannable, final EmojiMetadata metadata, 43682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir final int start, final int end) { 43782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir final EmojiSpan span = mSpanFactory.createSpan(metadata); 43882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir spannable.setSpan(span, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 43982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 44082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 44182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir /** 44282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * Checks whether the current OS can render a given emoji. Used by the system to decide if an 44382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * emoji span should be added. If the system cannot render it, an emoji span will be added. 44482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * Used only for the case where replaceAll is set to {@code false}. 44582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * 44682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * @param charSequence the CharSequence that the emoji is in 44782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * @param start start index of the emoji in the CharSequence 44882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * @param end end index of the emoji in the CharSequence 44982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * @param metadata EmojiMetadata instance for the emoji 45082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * 45182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * @return {@code true} if the OS can render emoji, {@code false} otherwise 45282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir */ 45382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir private boolean hasGlyph(final CharSequence charSequence, int start, final int end, 45482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir final EmojiMetadata metadata) { 455611b9e77742d232d7eee9e05e95e6db63d3d97e4Siyamed Sinir // For pre M devices, heuristic in PaintCompat can result in false positives. we are 456611b9e77742d232d7eee9e05e95e6db63d3d97e4Siyamed Sinir // adding another heuristic using the sdkAdded field. if the emoji was added to OS 457611b9e77742d232d7eee9e05e95e6db63d3d97e4Siyamed Sinir // at a later version we assume that the system probably cannot render it. 458611b9e77742d232d7eee9e05e95e6db63d3d97e4Siyamed Sinir if (Build.VERSION.SDK_INT < 23 && metadata.getSdkAdded() > Build.VERSION.SDK_INT) { 45982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir return false; 46082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 46182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 46282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir // if the existence is not calculated yet 46382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir if (metadata.getHasGlyph() == EmojiMetadata.HAS_GLYPH_UNKNOWN) { 464fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir final boolean hasGlyph = mGlyphChecker.hasGlyph(charSequence, start, end); 46582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir metadata.setHasGlyph(hasGlyph); 46682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 46782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 46882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir return metadata.getHasGlyph() == EmojiMetadata.HAS_GLYPH_EXISTS; 46982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 47082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 471fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir /** 472fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir * Set the GlyphChecker instance used by EmojiProcessor. Used for testing. 473fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir */ 474fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir void setGlyphChecker(@NonNull final GlyphChecker glyphChecker) { 475fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir Preconditions.checkNotNull(glyphChecker); 476fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir mGlyphChecker = glyphChecker; 47782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 47882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 47982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir /** 48082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * State machine for walking over the metadata trie. 48182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir */ 48282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir static final class ProcessorSm { 48382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 48482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir private static final int STATE_DEFAULT = 1; 48582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir private static final int STATE_WALKING = 2; 48682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 48782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir private int mState = STATE_DEFAULT; 48882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 48982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir /** 49082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * Root of the trie 49182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir */ 49282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir private final MetadataRepo.Node mRootNode; 49382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 49482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir /** 49582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * Pointer to the node after last codepoint. 49682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir */ 49782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir private MetadataRepo.Node mCurrentNode; 49882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 49982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir /** 50082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * The node where ACTION_FLUSH is called. Required since after flush action is 50182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * returned mCurrentNode is reset to be the root. 50282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir */ 50382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir private MetadataRepo.Node mFlushNode; 50482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 50582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir /** 50682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * The code point that was checked. 50782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir */ 50882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir private int mLastCodepoint; 50982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 51082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir /** 51182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * Level for mCurrentNode. Root is 0. 51282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir */ 51382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir private int mCurrentDepth; 51482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 515ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa /** 516ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa * @see EmojiCompat.Config#setUseEmojiAsDefaultStyle(boolean) 517ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa */ 518ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa private final boolean mUseEmojiAsDefaultStyle; 519ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa 520ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa /** 521ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa * @see EmojiCompat.Config#setUseEmojiAsDefaultStyle(boolean, List) 522ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa */ 523ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa private final int[] mEmojiAsDefaultStyleExceptions; 524ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa 525ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa ProcessorSm(MetadataRepo.Node rootNode, boolean useEmojiAsDefaultStyle, 526ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa int[] emojiAsDefaultStyleExceptions) { 52782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir mRootNode = rootNode; 52882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir mCurrentNode = rootNode; 529ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa mUseEmojiAsDefaultStyle = useEmojiAsDefaultStyle; 530ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa mEmojiAsDefaultStyleExceptions = emojiAsDefaultStyleExceptions; 53182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 53282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 53382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir @Action 53482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir int check(final int codePoint) { 53582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir final int action; 53682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir MetadataRepo.Node node = mCurrentNode.get(codePoint); 53782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir switch (mState) { 53882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir case STATE_WALKING: 53982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir if (node != null) { 54082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir mCurrentNode = node; 54182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir mCurrentDepth += 1; 54282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir action = ACTION_ADVANCE_END; 54382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } else { 54482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir if (isTextStyle(codePoint)) { 54582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir action = reset(); 54682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } else if (isEmojiStyle(codePoint)) { 54782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir action = ACTION_ADVANCE_END; 54882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } else if (mCurrentNode.getData() != null) { 54982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir if (mCurrentDepth == 1) { 550ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa if (shouldUseEmojiPresentationStyleForSingleCodepoint()) { 55182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir mFlushNode = mCurrentNode; 55282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir action = ACTION_FLUSH; 55382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir reset(); 55482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } else { 55582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir action = reset(); 55682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 55782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } else { 55882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir mFlushNode = mCurrentNode; 55982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir action = ACTION_FLUSH; 56082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir reset(); 56182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 56282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } else { 56382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir action = reset(); 56482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 56582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 56682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir break; 56782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir case STATE_DEFAULT: 56882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir default: 56982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir if (node == null) { 57082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir action = reset(); 57182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } else { 57282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir mState = STATE_WALKING; 57382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir mCurrentNode = node; 57482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir mCurrentDepth = 1; 57582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir action = ACTION_ADVANCE_END; 57682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 57782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir break; 57882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 57982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 58082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir mLastCodepoint = codePoint; 58182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir return action; 58282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 58382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 58482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir @Action 58582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir private int reset() { 58682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir mState = STATE_DEFAULT; 58782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir mCurrentNode = mRootNode; 58882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir mCurrentDepth = 0; 58982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir return ACTION_ADVANCE_BOTH; 59082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 59182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 59282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir /** 59382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * @return the metadata node when ACTION_FLUSH is returned 59482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir */ 59582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir EmojiMetadata getFlushMetadata() { 59682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir return mFlushNode.getData(); 59782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 59882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 59982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir /** 60082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * @return current pointer to the metadata node in the trie 60182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir */ 60282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir EmojiMetadata getCurrentMetadata() { 60382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir return mCurrentNode.getData(); 60482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 60582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 60682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir /** 60782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * Need for the case where input is consumed, but action_flush was not called. For example 60882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * when the char sequence has single codepoint character which is a default emoji. State 60982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * machine will wait for the next. 61082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * 61182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * @return whether the current state requires an emoji to be added 61282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir */ 61382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir boolean isInFlushableState() { 61482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir return mState == STATE_WALKING && mCurrentNode.getData() != null 615ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa && (mCurrentDepth > 1 || shouldUseEmojiPresentationStyleForSingleCodepoint()); 616ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa } 617ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa 618ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa private boolean shouldUseEmojiPresentationStyleForSingleCodepoint() { 619ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa if (mCurrentNode.getData().isDefaultEmoji()) { 620ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa // The codepoint is emoji style by default. 621ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa return true; 622ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa } 623ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa if (isEmojiStyle(mLastCodepoint)) { 624ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa // The codepoint was followed by the emoji style variation selector. 625ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa return true; 626ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa } 627ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa if (mUseEmojiAsDefaultStyle) { 628ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa // Emoji presentation style for text style default emojis is enabled. We have 629ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa // to check that the current codepoint is not an exception. 630ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa if (mEmojiAsDefaultStyleExceptions == null) { 631ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa return true; 632ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa } 633ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa final int codepoint = mCurrentNode.getData().getCodepointAt(0); 634ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa final int index = Arrays.binarySearch(mEmojiAsDefaultStyleExceptions, codepoint); 635ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa if (index < 0) { 636ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa // Index is negative, so the codepoint was not found in the array of exceptions. 637ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa return true; 638ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa } 639ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa } 640ca429f4bd64320e424b2e984c2e9d615679fa9cdMihai Popa return false; 64182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 64282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 64382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir /** 64482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * @param codePoint CodePoint to check 64582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * 64682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * @return {@code true} if the codepoint is a emoji style standardized variation selector 64782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir */ 64882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir private static boolean isEmojiStyle(int codePoint) { 64982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir return codePoint == 0xFE0F; 65082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 65182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 65282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir /** 65382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * @param codePoint CodePoint to check 65482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * 65582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * @return {@code true} if the codepoint is a text style standardized variation selector 65682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir */ 65782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir private static boolean isTextStyle(int codePoint) { 65882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir return codePoint == 0xFE0E; 65982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 66082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 66182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 66282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir /** 66382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * Copy of BaseInputConnection findIndexBackward and findIndexForward functions. 66482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir */ 66582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir private static final class CodepointIndexFinder { 66682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir private static final int INVALID_INDEX = -1; 66782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 66882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir /** 66982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * Find start index of the character in {@code cs} that is {@code numCodePoints} behind 67082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * starting from {@code from}. 67182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * 67282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * @param cs CharSequence to work on 67382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * @param from the index to start going backwards 67482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * @param numCodePoints the number of codepoints 67582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * 67682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * @return start index of the character 67782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir */ 67882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir private static int findIndexBackward(final CharSequence cs, final int from, 67982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir final int numCodePoints) { 68082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir int currentIndex = from; 68182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir boolean waitingHighSurrogate = false; 68282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir final int length = cs.length(); 68382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir if (currentIndex < 0 || length < currentIndex) { 68482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir return INVALID_INDEX; // The starting point is out of range. 68582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 68682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir if (numCodePoints < 0) { 68782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir return INVALID_INDEX; // Basically this should not happen. 68882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 68982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir int remainingCodePoints = numCodePoints; 69082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir while (true) { 69182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir if (remainingCodePoints == 0) { 69282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir return currentIndex; // Reached to the requested length in code points. 69382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 69482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 69582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir --currentIndex; 69682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir if (currentIndex < 0) { 69782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir if (waitingHighSurrogate) { 69882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir return INVALID_INDEX; // An invalid surrogate pair is found. 69982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 70082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir return 0; // Reached to the beginning of the text w/o any invalid surrogate 70182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir // pair. 70282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 70382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir final char c = cs.charAt(currentIndex); 70482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir if (waitingHighSurrogate) { 70582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir if (!Character.isHighSurrogate(c)) { 70682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir return INVALID_INDEX; // An invalid surrogate pair is found. 70782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 70882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir waitingHighSurrogate = false; 70982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir --remainingCodePoints; 71082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir continue; 71182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 71282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir if (!Character.isSurrogate(c)) { 71382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir --remainingCodePoints; 71482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir continue; 71582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 71682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir if (Character.isHighSurrogate(c)) { 71782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir return INVALID_INDEX; // A invalid surrogate pair is found. 71882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 71982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir waitingHighSurrogate = true; 72082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 72182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 72282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 72382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir /** 72482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * Find start index of the character in {@code cs} that is {@code numCodePoints} ahead 72582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * starting from {@code from}. 72682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * 72782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * @param cs CharSequence to work on 72882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * @param from the index to start going forward 72982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * @param numCodePoints the number of codepoints 73082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * 73182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir * @return start index of the character 73282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir */ 73382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir private static int findIndexForward(final CharSequence cs, final int from, 73482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir final int numCodePoints) { 73582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir int currentIndex = from; 73682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir boolean waitingLowSurrogate = false; 73782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir final int length = cs.length(); 73882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir if (currentIndex < 0 || length < currentIndex) { 73982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir return INVALID_INDEX; // The starting point is out of range. 74082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 74182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir if (numCodePoints < 0) { 74282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir return INVALID_INDEX; // Basically this should not happen. 74382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 74482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir int remainingCodePoints = numCodePoints; 74582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 74682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir while (true) { 74782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir if (remainingCodePoints == 0) { 74882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir return currentIndex; // Reached to the requested length in code points. 74982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 75082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir 75182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir if (currentIndex >= length) { 75282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir if (waitingLowSurrogate) { 75382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir return INVALID_INDEX; // An invalid surrogate pair is found. 75482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 75582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir return length; // Reached to the end of the text w/o any invalid surrogate 75682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir // pair. 75782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 75882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir final char c = cs.charAt(currentIndex); 75982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir if (waitingLowSurrogate) { 76082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir if (!Character.isLowSurrogate(c)) { 76182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir return INVALID_INDEX; // An invalid surrogate pair is found. 76282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 76382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir --remainingCodePoints; 76482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir waitingLowSurrogate = false; 76582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir ++currentIndex; 76682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir continue; 76782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 76882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir if (!Character.isSurrogate(c)) { 76982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir --remainingCodePoints; 77082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir ++currentIndex; 77182d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir continue; 77282d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 77382d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir if (Character.isLowSurrogate(c)) { 77482d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir return INVALID_INDEX; // A invalid surrogate pair is found. 77582d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 77682d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir waitingLowSurrogate = true; 77782d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir ++currentIndex; 77882d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 77982d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 78082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir } 781fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir 782fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir /** 783fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir * Utility class that checks if the system can render a given glyph. 784fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir * 785fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir * @hide 786fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir */ 787fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir @AnyThread 788fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir @RestrictTo(LIBRARY_GROUP) 789fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir public static class GlyphChecker { 790fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir /** 791fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir * Default text size for {@link #mTextPaint}. 792fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir */ 793fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir private static final int PAINT_TEXT_SIZE = 10; 794fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir 795fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir /** 796fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir * Used to create strings required by 797fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir * {@link PaintCompat#hasGlyph(android.graphics.Paint, String)}. 798fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir */ 799fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir private static final ThreadLocal<StringBuilder> sStringBuilder = new ThreadLocal<>(); 800fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir 801fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir /** 802fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir * TextPaint used during {@link PaintCompat#hasGlyph(android.graphics.Paint, String)} check. 803fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir */ 804fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir private final TextPaint mTextPaint; 805fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir 806fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir GlyphChecker() { 807fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir mTextPaint = new TextPaint(); 808fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir mTextPaint.setTextSize(PAINT_TEXT_SIZE); 809fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir } 810fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir 811fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir /** 812fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir * Returns whether the system can render an emoji. 813fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir * 814fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir * @param charSequence the CharSequence that the emoji is in 815fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir * @param start start index of the emoji in the CharSequence 816fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir * @param end end index of the emoji in the CharSequence 817fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir * 818fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir * @return {@code true} if the OS can render emoji, {@code false} otherwise 819fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir */ 820fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir public boolean hasGlyph(final CharSequence charSequence, int start, final int end) { 821fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir final StringBuilder builder = getStringBuilder(); 822fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir builder.setLength(0); 823fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir 824fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir while (start < end) { 825fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir builder.append(charSequence.charAt(start)); 826fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir start++; 827fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir } 828fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir 829fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir return PaintCompat.hasGlyph(mTextPaint, builder.toString()); 830fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir } 831fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir 832fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir private static StringBuilder getStringBuilder() { 833fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir if (sStringBuilder.get() == null) { 834fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir sStringBuilder.set(new StringBuilder()); 835fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir } 836fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir return sStringBuilder.get(); 837fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir } 838fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir 839fdc5f7bbb0e22b5d6e400327d9d4b732b07dec75Siyamed Sinir } 84082d2cc1cf0c2bfdd5121e6d6913dfe9fcaacf439Siyamed Sinir} 841