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
18c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinirimport android.os.Build;
19f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinirimport android.support.annotation.NonNull;
20c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinirimport android.support.annotation.Nullable;
21c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinirimport android.support.annotation.RequiresApi;
22c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinirimport android.support.text.emoji.EmojiCompat;
23f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinirimport android.support.v4.util.Preconditions;
24f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinirimport android.text.InputFilter;
25f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinirimport android.text.method.PasswordTransformationMethod;
26f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinirimport android.text.method.TransformationMethod;
27f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinirimport android.widget.TextView;
28f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir
29f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir/**
30c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir * Utility class to enhance custom TextView widgets with {@link EmojiCompat}.
31c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir * <pre>
32c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir * public class MyEmojiTextView extends TextView {
33c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir *     public MyEmojiTextView(Context context) {
34c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir *         super(context);
35c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir *         init();
36c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir *     }
37c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir *     // ..
38c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir *     private void init() {
39c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir *         getEmojiTextViewHelper().updateTransformationMethod();
40c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir *     }
41c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir *
42c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir *     {@literal @}Override
43c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir *     public void setFilters(InputFilter[] filters) {
44c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir *         super.setFilters(getEmojiTextViewHelper().getFilters(filters));
45c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir *     }
46c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir *
47c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir *     {@literal @}Override
48c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir *     public void setAllCaps(boolean allCaps) {
49c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir *         super.setAllCaps(allCaps);
50c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir *         getEmojiTextViewHelper().setAllCaps(allCaps);
51c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir *     }
52c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir *
53c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir *     private EmojiTextViewHelper getEmojiTextViewHelper() {
54c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir *         if (mEmojiTextViewHelper == null) {
55c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir *             mEmojiTextViewHelper = new EmojiTextViewHelper(this);
56c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir *         }
57c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir *         return mEmojiTextViewHelper;
58c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir *     }
59c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir * }
60c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir * </pre>
61f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir */
62f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinirpublic final class EmojiTextViewHelper {
63c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir
64c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir    private final HelperInternal mHelper;
65f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir
66f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir    /**
67f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir     * Default constructor.
68f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir     *
69f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir     * @param textView TextView instance
70f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir     */
71f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir    public EmojiTextViewHelper(@NonNull TextView textView) {
72f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir        Preconditions.checkNotNull(textView, "textView cannot be null");
73c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir        mHelper = Build.VERSION.SDK_INT >= 19 ? new HelperInternal19(textView)
74c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir                : new HelperInternal();
75f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir    }
76f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir
77f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir    /**
78f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir     * Updates widget's TransformationMethod so that the transformed text can be processed.
79c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir     * Should be called in the widget constructor. When used on devices running API 18 or below,
80c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir     * this method does nothing.
81f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir     *
82c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir     * @see #wrapTransformationMethod(TransformationMethod)
83f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir     */
84f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir    public void updateTransformationMethod() {
85c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir        mHelper.updateTransformationMethod();
86f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir    }
87f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir
88f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir    /**
89f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir     * Appends EmojiCompat InputFilters to the widget InputFilters. Should be called by {@link
90c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir     * TextView#setFilters(InputFilter[])} to update the InputFilters. When used on devices running
91c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir     * API 18 or below, this method returns {@code filters} that is given as a parameter.
92f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir     *
93f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir     * @param filters InputFilter array passed to {@link TextView#setFilters(InputFilter[])}
94f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir     *
95f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir     * @return same copy if the array already contains EmojiCompat InputFilter. A new array copy if
96f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir     * not.
97f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir     */
98c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir    @NonNull
99f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir    public InputFilter[] getFilters(@NonNull final InputFilter[] filters) {
100c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir        return mHelper.getFilters(filters);
101f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir    }
102f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir
103f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir    /**
104c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir     * Returns transformation method that can update the transformed text to display emojis. When
105c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir     * used on devices running API 18 or below, this method returns {@code transformationMethod}
106c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir     * that is given as a parameter.
107f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir     *
108f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir     * @param transformationMethod instance to be wrapped
109f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir     */
110c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir    @Nullable
111c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir    public TransformationMethod wrapTransformationMethod(
112c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir            @Nullable TransformationMethod transformationMethod) {
113c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir        return mHelper.wrapTransformationMethod(transformationMethod);
114f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir    }
115f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir
116f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir    /**
117c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir     * Call when allCaps is set on TextView. When used on devices running API 18 or below, this
118c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir     * method does nothing.
119f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir     *
120f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir     * @param allCaps allCaps parameter passed to {@link TextView#setAllCaps(boolean)}
121f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir     */
122f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir    public void setAllCaps(boolean allCaps) {
123c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir        mHelper.setAllCaps(allCaps);
124c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir    }
125c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir
126c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir    private static class HelperInternal {
127c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir
128c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir        void updateTransformationMethod() {
129c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir            // do nothing
130c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir        }
131c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir
132c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir        InputFilter[] getFilters(@NonNull final InputFilter[] filters) {
133c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir            return filters;
134c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir        }
135c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir
136c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir        TransformationMethod wrapTransformationMethod(TransformationMethod transformationMethod) {
137c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir            return transformationMethod;
138c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir        }
139c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir
140c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir        void setAllCaps(boolean allCaps) {
141c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir            // do nothing
142f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir        }
143f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir    }
144c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir
145c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir    @RequiresApi(19)
146c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir    private static class HelperInternal19 extends HelperInternal {
147c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir        private final TextView mTextView;
148c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir        private final EmojiInputFilter mEmojiInputFilter;
149c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir
150c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir        HelperInternal19(TextView textView) {
151c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir            mTextView = textView;
152c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir            mEmojiInputFilter = new EmojiInputFilter(textView);
153c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir        }
154c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir
155c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir        @Override
156c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir        void updateTransformationMethod() {
157c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir            final TransformationMethod tm = mTextView.getTransformationMethod();
158c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir            if (tm != null && !(tm instanceof PasswordTransformationMethod)) {
159c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir                mTextView.setTransformationMethod(wrapTransformationMethod(tm));
160c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir            }
161c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir        }
162c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir
163c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir        @Override
164c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir        InputFilter[] getFilters(@NonNull final InputFilter[] filters) {
165c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir            final int count = filters.length;
166c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir            for (int i = 0; i < count; i++) {
167c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir                if (filters[i] instanceof EmojiInputFilter) {
168c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir                    return filters;
169c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir                }
170c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir            }
171c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir            final InputFilter[] newFilters = new InputFilter[filters.length + 1];
172c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir            System.arraycopy(filters, 0, newFilters, 0, count);
173c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir            newFilters[count] = mEmojiInputFilter;
174c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir            return newFilters;
175c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir        }
176c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir
177c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir        @Override
178c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir        TransformationMethod wrapTransformationMethod(TransformationMethod transformationMethod) {
179c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir            if (transformationMethod instanceof EmojiTransformationMethod) {
180c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir                return transformationMethod;
181c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir            }
182c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir            return new EmojiTransformationMethod(transformationMethod);
183c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir        }
184c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir
185c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir        @Override
186c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir        void setAllCaps(boolean allCaps) {
187c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir            // When allCaps is set to false TextView sets the transformation method to be null. We
188c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir            // are only interested when allCaps is set to true in order to wrap the original method.
189c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir            if (allCaps) {
190c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir                updateTransformationMethod();
191c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir            }
192c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir        }
193c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir
194c79a89a2b8631c9ee81ae015ea74a6e1ac10de14Siyamed Sinir    }
195f8ec169d022fbed42fd82091d24c45f3767cdfe7Siyamed Sinir}
196