EmojiTextViewHelper.java revision 71505ed0dcbc4ca21d126eee3ccfb616ea35666e
1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package android.support.text.emoji.widget;
17
18import android.os.Build;
19import android.support.annotation.NonNull;
20import android.support.annotation.Nullable;
21import android.support.text.emoji.EmojiCompat;
22import android.support.v4.util.Preconditions;
23import android.text.InputFilter;
24import android.text.method.PasswordTransformationMethod;
25import android.text.method.TransformationMethod;
26import android.widget.TextView;
27
28/**
29 * Utility class to enhance custom TextView widgets with {@link EmojiCompat}.
30 * <pre>
31 * public class MyEmojiTextView extends TextView {
32 *     public MyEmojiTextView(Context context) {
33 *         super(context);
34 *         init();
35 *     }
36 *     // ..
37 *     private void init() {
38 *         getEmojiTextViewHelper().updateTransformationMethod();
39 *     }
40 *
41 *     {@literal @}Override
42 *     public void setFilters(InputFilter[] filters) {
43 *         super.setFilters(getEmojiTextViewHelper().getFilters(filters));
44 *     }
45 *
46 *     {@literal @}Override
47 *     public void setAllCaps(boolean allCaps) {
48 *         super.setAllCaps(allCaps);
49 *         getEmojiTextViewHelper().setAllCaps(allCaps);
50 *     }
51 *
52 *     private EmojiTextViewHelper getEmojiTextViewHelper() {
53 *         if (mEmojiTextViewHelper == null) {
54 *             mEmojiTextViewHelper = new EmojiTextViewHelper(this);
55 *         }
56 *         return mEmojiTextViewHelper;
57 *     }
58 * }
59 * </pre>
60 */
61public final class EmojiTextViewHelper {
62
63    private final HelperInternal mHelper;
64
65    /**
66     * Default constructor.
67     *
68     * @param textView TextView instance
69     */
70    public EmojiTextViewHelper(@NonNull TextView textView) {
71        Preconditions.checkNotNull(textView, "textView cannot be null");
72        mHelper = Build.VERSION.SDK_INT >= 19 ? new HelperInternal19(textView)
73                : new HelperInternal();
74    }
75
76    /**
77     * Updates widget's TransformationMethod so that the transformed text can be processed.
78     * Should be called in the widget constructor. When used on devices running API 18 or below,
79     * this method does nothing.
80     *
81     * @see #wrapTransformationMethod(TransformationMethod)
82     */
83    public void updateTransformationMethod() {
84        mHelper.updateTransformationMethod();
85    }
86
87    /**
88     * Appends EmojiCompat InputFilters to the widget InputFilters. Should be called by {@link
89     * TextView#setFilters(InputFilter[])} to update the InputFilters. When used on devices running
90     * API 18 or below, this method returns {@code filters} that is given as a parameter.
91     *
92     * @param filters InputFilter array passed to {@link TextView#setFilters(InputFilter[])}
93     *
94     * @return same copy if the array already contains EmojiCompat InputFilter. A new array copy if
95     * not.
96     */
97    @NonNull
98    public InputFilter[] getFilters(@NonNull final InputFilter[] filters) {
99        return mHelper.getFilters(filters);
100    }
101
102    /**
103     * Returns transformation method that can update the transformed text to display emojis. When
104     * used on devices running API 18 or below, this method returns {@code transformationMethod}
105     * that is given as a parameter.
106     *
107     * @param transformationMethod instance to be wrapped
108     */
109    @Nullable
110    public TransformationMethod wrapTransformationMethod(
111            @Nullable TransformationMethod transformationMethod) {
112        return mHelper.wrapTransformationMethod(transformationMethod);
113    }
114
115    /**
116     * Call when allCaps is set on TextView. When used on devices running API 18 or below, this
117     * method does nothing.
118     *
119     * @param allCaps allCaps parameter passed to {@link TextView#setAllCaps(boolean)}
120     */
121    public void setAllCaps(boolean allCaps) {
122        mHelper.setAllCaps(allCaps);
123    }
124
125    private static class HelperInternal {
126
127        void updateTransformationMethod() {
128            // do nothing
129        }
130
131        InputFilter[] getFilters(@NonNull final InputFilter[] filters) {
132            return filters;
133        }
134
135        TransformationMethod wrapTransformationMethod(TransformationMethod transformationMethod) {
136            return transformationMethod;
137        }
138
139        void setAllCaps(boolean allCaps) {
140            // do nothing
141        }
142    }
143
144    private static class HelperInternal19 extends HelperInternal {
145        private final TextView mTextView;
146        private final EmojiInputFilter mEmojiInputFilter;
147
148        HelperInternal19(TextView textView) {
149            mTextView = textView;
150            mEmojiInputFilter = new EmojiInputFilter(textView);
151        }
152
153        @Override
154        void updateTransformationMethod() {
155            final TransformationMethod tm = mTextView.getTransformationMethod();
156            if (tm != null && !(tm instanceof PasswordTransformationMethod)) {
157                mTextView.setTransformationMethod(wrapTransformationMethod(tm));
158            }
159        }
160
161        @Override
162        InputFilter[] getFilters(@NonNull final InputFilter[] filters) {
163            final int count = filters.length;
164            for (int i = 0; i < count; i++) {
165                if (filters[i] instanceof EmojiInputFilter) {
166                    return filters;
167                }
168            }
169            final InputFilter[] newFilters = new InputFilter[filters.length + 1];
170            System.arraycopy(filters, 0, newFilters, 0, count);
171            newFilters[count] = mEmojiInputFilter;
172            return newFilters;
173        }
174
175        @Override
176        TransformationMethod wrapTransformationMethod(TransformationMethod transformationMethod) {
177            if (transformationMethod instanceof EmojiTransformationMethod) {
178                return transformationMethod;
179            }
180            return new EmojiTransformationMethod(transformationMethod);
181        }
182
183        @Override
184        void setAllCaps(boolean allCaps) {
185            // When allCaps is set to false TextView sets the transformation method to be null. We
186            // are only interested when allCaps is set to true in order to wrap the original method.
187            if (allCaps) {
188                updateTransformationMethod();
189            }
190        }
191
192    }
193}
194