EmojiEditTextHelper.java revision d6e62a9d119a07f4d2bd686f357d8a667085fe71
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.IntRange;
20import android.support.annotation.NonNull;
21import android.support.annotation.RequiresApi;
22import android.support.text.emoji.EmojiCompat;
23import android.support.v4.util.Preconditions;
24import android.text.method.KeyListener;
25import android.view.inputmethod.EditorInfo;
26import android.view.inputmethod.InputConnection;
27import android.widget.EditText;
28import android.widget.TextView;
29
30/**
31 * Utility class to enhance custom EditText widgets with {@link EmojiCompat}.
32 * <p/>
33 * <pre>
34 * public class MyEmojiEditText extends EditText {
35 *      public MyEmojiEditText(Context context) {
36 *          super(context);
37 *          init();
38 *      }
39 *      // ...
40 *      private void init() {
41 *          super.setKeyListener(getEmojiEditTextHelper().getKeyListener(getKeyListener()));
42 *      }
43 *
44 *      {@literal @}Override
45 *      public void setKeyListener(android.text.method.KeyListener keyListener) {
46 *          super.setKeyListener(getEmojiEditTextHelper().getKeyListener(keyListener));
47 *      }
48 *
49 *      {@literal @}Override
50 *      public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
51 *          InputConnection inputConnection = super.onCreateInputConnection(outAttrs);
52 *          return getEmojiEditTextHelper().onCreateInputConnection(inputConnection, outAttrs);
53 *      }
54 *
55 *      private EmojiEditTextHelper getEmojiEditTextHelper() {
56 *          if (mEmojiEditTextHelper == null) {
57 *              mEmojiEditTextHelper = new EmojiEditTextHelper(this);
58 *          }
59 *          return mEmojiEditTextHelper;
60 *      }
61 * }
62 * </pre>
63 *
64 */
65public final class EmojiEditTextHelper {
66
67    private final HelperInternal mHelper;
68    private int mMaxEmojiCount;
69
70    /**
71     * Default constructor.
72     *
73     * @param editText EditText instance
74     */
75    public EmojiEditTextHelper(@NonNull final EditText editText) {
76        Preconditions.checkNotNull(editText, "editText cannot be null");
77        mHelper = Build.VERSION.SDK_INT >= 19 ? new HelperInternal19(editText)
78                : new HelperInternal();
79    }
80
81    /**
82     * Set the maximum number of EmojiSpans to be added to a CharSequence. The number of spans in a
83     * CharSequence affects the performance of the EditText insert/delete operations. Insert/delete
84     * operations slow down as the number of spans increases.
85     * <p/>
86     *
87     * @param maxEmojiCount maximum number of EmojiSpans to be added to a single CharSequence,
88     *                      should be equal or greater than 0
89     *
90     * @see EmojiCompat#process(CharSequence, int, int, int)
91     */
92    public void setMaxEmojiCount(@IntRange(from = 0) int maxEmojiCount) {
93        Preconditions.checkArgumentNonnegative(maxEmojiCount,
94                "maxEmojiCount should be greater than 0");
95        mMaxEmojiCount = maxEmojiCount;
96        mHelper.setMaxEmojiCount(maxEmojiCount);
97    }
98
99
100    /**
101     * Returns the maximum number of EmojiSpans to be added to a CharSequence.
102     *
103     * @see #setMaxEmojiCount(int)
104     * @see EmojiCompat#process(CharSequence, int, int, int)
105     */
106    public int getMaxEmojiCount() {
107        return mMaxEmojiCount;
108    }
109
110    /**
111     * Attaches EmojiCompat KeyListener to the widget. Should be called from {@link
112     * TextView#setKeyListener(KeyListener)}. Existing keyListener is wrapped into EmojiCompat
113     * KeyListener. When used on devices running API 18 or below, this method returns
114     * {@code keyListener} that is given as a parameter.
115     *
116     * @param keyListener KeyListener passed into {@link TextView#setKeyListener(KeyListener)}
117     *
118     * @return a new KeyListener instance that wraps {@code keyListener}.
119     */
120    @NonNull
121    public KeyListener getKeyListener(@NonNull final KeyListener keyListener) {
122        Preconditions.checkNotNull(keyListener, "keyListener cannot be null");
123        return mHelper.getKeyListener(keyListener);
124    }
125
126    /**
127     * Updates the InputConnection with emoji support. Should be called from {@link
128     * TextView#onCreateInputConnection(EditorInfo)}. When used on devices running API 18 or below,
129     * this method returns {@code inputConnection} that is given as a parameter.
130     *
131     * @param inputConnection InputConnection instance created by TextView
132     * @param outAttrs        EditorInfo passed into
133     *                        {@link TextView#onCreateInputConnection(EditorInfo)}
134     *
135     * @return a new InputConnection instance that wraps {@code inputConnection}
136     */
137    @NonNull
138    public InputConnection onCreateInputConnection(@NonNull final InputConnection inputConnection,
139            @NonNull final EditorInfo outAttrs) {
140        Preconditions.checkNotNull(inputConnection, "inputConnection cannot be null");
141        return mHelper.onCreateInputConnection(inputConnection, outAttrs);
142    }
143
144    private static class HelperInternal {
145
146        KeyListener getKeyListener(@NonNull KeyListener keyListener) {
147            return keyListener;
148        }
149
150        InputConnection onCreateInputConnection(@NonNull InputConnection inputConnection,
151                @NonNull EditorInfo outAttrs) {
152            return inputConnection;
153        }
154
155        public void setMaxEmojiCount(int maxEmojiCount) {
156            // do nothing
157        }
158    }
159
160    @RequiresApi(19)
161    private static class HelperInternal19 extends HelperInternal {
162        private final EditText mEditText;
163        private final EmojiTextWatcher mTextWatcher;
164
165        HelperInternal19(@NonNull EditText editText) {
166            mEditText = editText;
167            mTextWatcher = new EmojiTextWatcher(mEditText);
168            mEditText.addTextChangedListener(mTextWatcher);
169            mEditText.setEditableFactory(EmojiEditableFactory.getInstance());
170        }
171
172        @Override
173        public void setMaxEmojiCount(int maxEmojiCount) {
174            mTextWatcher.setMaxEmojiCount(maxEmojiCount);
175        }
176
177        @Override
178        KeyListener getKeyListener(@NonNull final KeyListener keyListener) {
179            if (keyListener instanceof EmojiKeyListener) {
180                return keyListener;
181            }
182            return new EmojiKeyListener(keyListener);
183        }
184
185        @Override
186        InputConnection onCreateInputConnection(@NonNull final InputConnection inputConnection,
187                @NonNull final EditorInfo outAttrs) {
188            if (inputConnection instanceof EmojiInputConnection) {
189                return inputConnection;
190            }
191            return new EmojiInputConnection(mEditText, inputConnection, outAttrs);
192        }
193    }
194}
195