1f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir/* 2f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir * Copyright (C) 2017 The Android Open Source Project 3f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir * 4f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir * Licensed under the Apache License, Version 2.0 (the "License"); 5f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir * you may not use this file except in compliance with the License. 6f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir * You may obtain a copy of the License at 7f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir * 8f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir * http://www.apache.org/licenses/LICENSE-2.0 9f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir * 10f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir * Unless required by applicable law or agreed to in writing, software 11f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir * distributed under the License is distributed on an "AS IS" BASIS, 12f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir * See the License for the specific language governing permissions and 14f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir * limitations under the License. 15f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir */ 16f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinirpackage android.support.text.emoji.widget; 17f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir 18f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinirimport static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; 19f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir 20f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinirimport android.support.annotation.NonNull; 2177b5c5b734f9f665577d1e3d178615db43ae1d4fSiyamed Sinirimport android.support.annotation.RequiresApi; 22f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinirimport android.support.annotation.RestrictTo; 23f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinirimport android.support.text.emoji.EmojiCompat; 24f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinirimport android.support.text.emoji.EmojiCompat.InitCallback; 25f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinirimport android.text.Selection; 26f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinirimport android.text.Spannable; 27f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinirimport android.text.Spanned; 28f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinirimport android.widget.TextView; 29f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir 30f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinirimport java.lang.ref.Reference; 31f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinirimport java.lang.ref.WeakReference; 32f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir 33f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir/** 34f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir * InputFilter to add EmojiSpans to the CharSequence set in a TextView. Unlike EditText where a 35f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir * TextWatcher is used to enhance the CharSequence, InputFilter is used on TextView. The reason is 36f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir * that if you add a TextWatcher to a TextView, its internal layout mechanism change, and therefore 37f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir * depending on the CharSequence provided, adding a TextWatcher might have performance side 38f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir * effects. 39f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir * 40f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir * @hide 41f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir */ 42f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir@RestrictTo(LIBRARY_GROUP) 4377b5c5b734f9f665577d1e3d178615db43ae1d4fSiyamed Sinir@RequiresApi(19) 44f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinirfinal class EmojiInputFilter implements android.text.InputFilter { 45f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir private final TextView mTextView; 46f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir private InitCallback mInitCallback; 47f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir 48f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir EmojiInputFilter(@NonNull final TextView textView) { 49f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir mTextView = textView; 50f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir } 51f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir 52f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir @Override 53f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir public CharSequence filter(final CharSequence source, final int sourceStart, 54f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir final int sourceEnd, final Spanned dest, final int destStart, final int destEnd) { 55f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir if (mTextView.isInEditMode()) { 56f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir return source; 57f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir } 58f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir 590d1e48d934880b40237ce980d154c3f3ff1c32f0Siyamed Sinir switch (EmojiCompat.get().getLoadState()){ 6034f638e630f75357a5f706f387ee9099c97af26bSiyamed Sinir case EmojiCompat.LOAD_STATE_SUCCEEDED: 610d1e48d934880b40237ce980d154c3f3ff1c32f0Siyamed Sinir boolean process = true; 620d1e48d934880b40237ce980d154c3f3ff1c32f0Siyamed Sinir if (destEnd == 0 && destStart == 0 && dest.length() == 0) { 630d1e48d934880b40237ce980d154c3f3ff1c32f0Siyamed Sinir final CharSequence oldText = mTextView.getText(); 640d1e48d934880b40237ce980d154c3f3ff1c32f0Siyamed Sinir if (source == oldText) { 650d1e48d934880b40237ce980d154c3f3ff1c32f0Siyamed Sinir process = false; 660d1e48d934880b40237ce980d154c3f3ff1c32f0Siyamed Sinir } 67f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir } 68f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir 690d1e48d934880b40237ce980d154c3f3ff1c32f0Siyamed Sinir if (process && source != null) { 700d1e48d934880b40237ce980d154c3f3ff1c32f0Siyamed Sinir final CharSequence text; 710d1e48d934880b40237ce980d154c3f3ff1c32f0Siyamed Sinir if (sourceStart == 0 && sourceEnd == source.length()) { 720d1e48d934880b40237ce980d154c3f3ff1c32f0Siyamed Sinir text = source; 730d1e48d934880b40237ce980d154c3f3ff1c32f0Siyamed Sinir } else { 740d1e48d934880b40237ce980d154c3f3ff1c32f0Siyamed Sinir text = source.subSequence(sourceStart, sourceEnd); 750d1e48d934880b40237ce980d154c3f3ff1c32f0Siyamed Sinir } 760d1e48d934880b40237ce980d154c3f3ff1c32f0Siyamed Sinir return EmojiCompat.get().process(text, 0, text.length()); 77f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir } 78f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir 790d1e48d934880b40237ce980d154c3f3ff1c32f0Siyamed Sinir return source; 800d1e48d934880b40237ce980d154c3f3ff1c32f0Siyamed Sinir case EmojiCompat.LOAD_STATE_LOADING: 810d1e48d934880b40237ce980d154c3f3ff1c32f0Siyamed Sinir EmojiCompat.get().registerInitCallback(getInitCallback()); 820d1e48d934880b40237ce980d154c3f3ff1c32f0Siyamed Sinir return source; 83fb15b88b8ff336c2f8c2dff88e55eaba3491349aSiyamed Sinir 84fb15b88b8ff336c2f8c2dff88e55eaba3491349aSiyamed Sinir case EmojiCompat.LOAD_STATE_FAILED: 85fb15b88b8ff336c2f8c2dff88e55eaba3491349aSiyamed Sinir default: 86fb15b88b8ff336c2f8c2dff88e55eaba3491349aSiyamed Sinir return source; 87f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir } 88f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir } 89f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir 90f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir private InitCallback getInitCallback() { 91f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir if (mInitCallback == null) { 92f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir mInitCallback = new InitCallbackImpl(mTextView); 93f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir } 94f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir return mInitCallback; 95f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir } 96f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir 97f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir private static class InitCallbackImpl extends InitCallback { 98f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir private final Reference<TextView> mViewRef; 99f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir 100f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir InitCallbackImpl(TextView textView) { 101f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir mViewRef = new WeakReference<>(textView); 102f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir } 103f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir 104f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir @Override 105f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir public void onInitialized() { 106f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir super.onInitialized(); 107f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir final TextView textView = mViewRef.get(); 108f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir if (textView != null && textView.isAttachedToWindow()) { 109f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir final CharSequence result = EmojiCompat.get().process(textView.getText()); 110f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir 111f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir final int selectionStart = Selection.getSelectionStart(result); 112f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir final int selectionEnd = Selection.getSelectionEnd(result); 113f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir 114f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir textView.setText(result); 115f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir 116f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir if (result instanceof Spannable) { 117f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir updateSelection((Spannable) result, selectionStart, selectionEnd); 118f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir } 119f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir } 120f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir } 121f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir } 122f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir 123f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir static void updateSelection(Spannable spannable, final int start, final int end) { 124f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir if (start >= 0 && end >= 0) { 125f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir Selection.setSelection(spannable, start, end); 126f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir } else if (start >= 0) { 127f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir Selection.setSelection(spannable, start); 128f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir } else if (end >= 0) { 129f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir Selection.setSelection(spannable, end); 130f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir } 131f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir } 132f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir} 133