| /* |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package androidx.emoji.widget; |
| |
| import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; |
| |
| import android.os.Build; |
| import android.text.method.KeyListener; |
| import android.view.inputmethod.EditorInfo; |
| import android.view.inputmethod.InputConnection; |
| import android.widget.EditText; |
| import android.widget.TextView; |
| |
| import androidx.annotation.IntRange; |
| import androidx.annotation.NonNull; |
| import androidx.annotation.Nullable; |
| import androidx.annotation.RequiresApi; |
| import androidx.annotation.RestrictTo; |
| import androidx.core.util.Preconditions; |
| import androidx.emoji.text.EmojiCompat; |
| import androidx.emoji.text.EmojiSpan; |
| |
| /** |
| * Utility class to enhance custom EditText widgets with {@link EmojiCompat}. |
| * <p/> |
| * <pre> |
| * public class MyEmojiEditText extends EditText { |
| * public MyEmojiEditText(Context context) { |
| * super(context); |
| * init(); |
| * } |
| * // ... |
| * private void init() { |
| * super.setKeyListener(getEmojiEditTextHelper().getKeyListener(getKeyListener())); |
| * } |
| * |
| * {@literal @}Override |
| * public void setKeyListener(android.text.method.KeyListener keyListener) { |
| * super.setKeyListener(getEmojiEditTextHelper().getKeyListener(keyListener)); |
| * } |
| * |
| * {@literal @}Override |
| * public InputConnection onCreateInputConnection(EditorInfo outAttrs) { |
| * InputConnection inputConnection = super.onCreateInputConnection(outAttrs); |
| * return getEmojiEditTextHelper().onCreateInputConnection(inputConnection, outAttrs); |
| * } |
| * |
| * private EmojiEditTextHelper getEmojiEditTextHelper() { |
| * if (mEmojiEditTextHelper == null) { |
| * mEmojiEditTextHelper = new EmojiEditTextHelper(this); |
| * } |
| * return mEmojiEditTextHelper; |
| * } |
| * } |
| * </pre> |
| * |
| */ |
| public final class EmojiEditTextHelper { |
| private final HelperInternal mHelper; |
| private int mMaxEmojiCount = EditTextAttributeHelper.MAX_EMOJI_COUNT; |
| @EmojiCompat.ReplaceStrategy |
| private int mEmojiReplaceStrategy = EmojiCompat.REPLACE_STRATEGY_DEFAULT; |
| |
| /** |
| * Default constructor. |
| * |
| * @param editText EditText instance |
| */ |
| public EmojiEditTextHelper(@NonNull final EditText editText) { |
| Preconditions.checkNotNull(editText, "editText cannot be null"); |
| mHelper = Build.VERSION.SDK_INT >= 19 ? new HelperInternal19(editText) |
| : new HelperInternal(); |
| } |
| |
| /** |
| * Set the maximum number of EmojiSpans to be added to a CharSequence. The number of spans in a |
| * CharSequence affects the performance of the EditText insert/delete operations. Insert/delete |
| * operations slow down as the number of spans increases. |
| * <p/> |
| * |
| * @param maxEmojiCount maximum number of EmojiSpans to be added to a single CharSequence, |
| * should be equal or greater than 0 |
| * |
| * @see EmojiCompat#process(CharSequence, int, int, int) |
| */ |
| public void setMaxEmojiCount(@IntRange(from = 0) int maxEmojiCount) { |
| Preconditions.checkArgumentNonnegative(maxEmojiCount, |
| "maxEmojiCount should be greater than 0"); |
| mMaxEmojiCount = maxEmojiCount; |
| mHelper.setMaxEmojiCount(maxEmojiCount); |
| } |
| |
| /** |
| * Returns the maximum number of EmojiSpans to be added to a CharSequence. |
| * |
| * @see #setMaxEmojiCount(int) |
| * @see EmojiCompat#process(CharSequence, int, int, int) |
| */ |
| public int getMaxEmojiCount() { |
| return mMaxEmojiCount; |
| } |
| |
| /** |
| * Attaches EmojiCompat KeyListener to the widget. Should be called from {@link |
| * TextView#setKeyListener(KeyListener)}. Existing keyListener is wrapped into EmojiCompat |
| * KeyListener. When used on devices running API 18 or below, this method returns |
| * {@code keyListener} that is given as a parameter. |
| * |
| * @param keyListener KeyListener passed into {@link TextView#setKeyListener(KeyListener)} |
| * |
| * @return a new KeyListener instance that wraps {@code keyListener}. |
| */ |
| @NonNull |
| public KeyListener getKeyListener(@NonNull final KeyListener keyListener) { |
| Preconditions.checkNotNull(keyListener, "keyListener cannot be null"); |
| return mHelper.getKeyListener(keyListener); |
| } |
| |
| /** |
| * Updates the InputConnection with emoji support. Should be called from {@link |
| * TextView#onCreateInputConnection(EditorInfo)}. When used on devices running API 18 or below, |
| * this method returns {@code inputConnection} that is given as a parameter. If |
| * {@code inputConnection} is {@code null}, returns {@code null}. |
| * |
| * @param inputConnection InputConnection instance created by TextView |
| * @param outAttrs EditorInfo passed into |
| * {@link TextView#onCreateInputConnection(EditorInfo)} |
| * |
| * @return a new InputConnection instance that wraps {@code inputConnection} |
| */ |
| @Nullable |
| public InputConnection onCreateInputConnection(@Nullable final InputConnection inputConnection, |
| @NonNull final EditorInfo outAttrs) { |
| if (inputConnection == null) return null; |
| return mHelper.onCreateInputConnection(inputConnection, outAttrs); |
| } |
| |
| /** |
| * Sets whether to replace all emoji with {@link EmojiSpan}s. Default value is |
| * {@link EmojiCompat#REPLACE_STRATEGY_DEFAULT}. |
| * |
| * @param replaceStrategy should be one of {@link EmojiCompat#REPLACE_STRATEGY_DEFAULT}, |
| * {@link EmojiCompat#REPLACE_STRATEGY_NON_EXISTENT}, |
| * {@link EmojiCompat#REPLACE_STRATEGY_ALL} |
| * |
| * @hide |
| */ |
| @RestrictTo(LIBRARY_GROUP) |
| void setEmojiReplaceStrategy(@EmojiCompat.ReplaceStrategy int replaceStrategy) { |
| mEmojiReplaceStrategy = replaceStrategy; |
| mHelper.setEmojiReplaceStrategy(replaceStrategy); |
| } |
| |
| /** |
| * Returns whether to replace all emoji with {@link EmojiSpan}s. Default value is |
| * {@link EmojiCompat#REPLACE_STRATEGY_DEFAULT}. |
| * |
| * @return one of {@link EmojiCompat#REPLACE_STRATEGY_DEFAULT}, |
| * {@link EmojiCompat#REPLACE_STRATEGY_NON_EXISTENT}, |
| * {@link EmojiCompat#REPLACE_STRATEGY_ALL} |
| * @hide |
| */ |
| @RestrictTo(LIBRARY_GROUP) |
| int getEmojiReplaceStrategy() { |
| return mEmojiReplaceStrategy; |
| } |
| |
| private static class HelperInternal { |
| |
| KeyListener getKeyListener(@NonNull KeyListener keyListener) { |
| return keyListener; |
| } |
| |
| InputConnection onCreateInputConnection(@NonNull InputConnection inputConnection, |
| @NonNull EditorInfo outAttrs) { |
| return inputConnection; |
| } |
| |
| void setMaxEmojiCount(int maxEmojiCount) { |
| // do nothing |
| } |
| |
| void setEmojiReplaceStrategy(@EmojiCompat.ReplaceStrategy int replaceStrategy) { |
| // do nothing |
| } |
| } |
| |
| @RequiresApi(19) |
| private static class HelperInternal19 extends HelperInternal { |
| private final EditText mEditText; |
| private final EmojiTextWatcher mTextWatcher; |
| |
| HelperInternal19(@NonNull EditText editText) { |
| mEditText = editText; |
| mTextWatcher = new EmojiTextWatcher(mEditText); |
| mEditText.addTextChangedListener(mTextWatcher); |
| mEditText.setEditableFactory(EmojiEditableFactory.getInstance()); |
| } |
| |
| @Override |
| void setMaxEmojiCount(int maxEmojiCount) { |
| mTextWatcher.setMaxEmojiCount(maxEmojiCount); |
| } |
| |
| @Override |
| void setEmojiReplaceStrategy(@EmojiCompat.ReplaceStrategy int replaceStrategy) { |
| mTextWatcher.setEmojiReplaceStrategy(replaceStrategy); |
| } |
| |
| @Override |
| KeyListener getKeyListener(@NonNull final KeyListener keyListener) { |
| if (keyListener instanceof EmojiKeyListener) { |
| return keyListener; |
| } |
| return new EmojiKeyListener(keyListener); |
| } |
| |
| @Override |
| InputConnection onCreateInputConnection(@NonNull final InputConnection inputConnection, |
| @NonNull final EditorInfo outAttrs) { |
| if (inputConnection instanceof EmojiInputConnection) { |
| return inputConnection; |
| } |
| return new EmojiInputConnection(mEditText, inputConnection, outAttrs); |
| } |
| } |
| } |