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