blob: b44e8595153dbcb4c9cd9f3fc33e5ae0beaa0dc8 [file] [log] [blame]
/*
* 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.text.Selection;
import android.text.Spannable;
import android.text.Spanned;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.annotation.RestrictTo;
import androidx.emoji.text.EmojiCompat;
import androidx.emoji.text.EmojiCompat.InitCallback;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
/**
* InputFilter to add EmojiSpans to the CharSequence set in a TextView. Unlike EditText where a
* TextWatcher is used to enhance the CharSequence, InputFilter is used on TextView. The reason is
* that if you add a TextWatcher to a TextView, its internal layout mechanism change, and therefore
* depending on the CharSequence provided, adding a TextWatcher might have performance side
* effects.
*
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
@RequiresApi(19)
final class EmojiInputFilter implements android.text.InputFilter {
private final TextView mTextView;
private InitCallback mInitCallback;
EmojiInputFilter(@NonNull final TextView textView) {
mTextView = textView;
}
@Override
public CharSequence filter(final CharSequence source, final int sourceStart,
final int sourceEnd, final Spanned dest, final int destStart, final int destEnd) {
if (mTextView.isInEditMode()) {
return source;
}
switch (EmojiCompat.get().getLoadState()){
case EmojiCompat.LOAD_STATE_SUCCEEDED:
boolean process = true;
if (destEnd == 0 && destStart == 0 && dest.length() == 0) {
final CharSequence oldText = mTextView.getText();
if (source == oldText) {
process = false;
}
}
if (process && source != null) {
final CharSequence text;
if (sourceStart == 0 && sourceEnd == source.length()) {
text = source;
} else {
text = source.subSequence(sourceStart, sourceEnd);
}
return EmojiCompat.get().process(text, 0, text.length());
}
return source;
case EmojiCompat.LOAD_STATE_LOADING:
case EmojiCompat.LOAD_STATE_DEFAULT:
EmojiCompat.get().registerInitCallback(getInitCallback());
return source;
case EmojiCompat.LOAD_STATE_FAILED:
default:
return source;
}
}
private InitCallback getInitCallback() {
if (mInitCallback == null) {
mInitCallback = new InitCallbackImpl(mTextView);
}
return mInitCallback;
}
private static class InitCallbackImpl extends InitCallback {
private final Reference<TextView> mViewRef;
InitCallbackImpl(TextView textView) {
mViewRef = new WeakReference<>(textView);
}
@Override
public void onInitialized() {
super.onInitialized();
final TextView textView = mViewRef.get();
if (textView != null && textView.isAttachedToWindow()) {
final CharSequence result = EmojiCompat.get().process(textView.getText());
final int selectionStart = Selection.getSelectionStart(result);
final int selectionEnd = Selection.getSelectionEnd(result);
textView.setText(result);
if (result instanceof Spannable) {
updateSelection((Spannable) result, selectionStart, selectionEnd);
}
}
}
}
static void updateSelection(Spannable spannable, final int start, final int end) {
if (start >= 0 && end >= 0) {
Selection.setSelection(spannable, start, end);
} else if (start >= 0) {
Selection.setSelection(spannable, start);
} else if (end >= 0) {
Selection.setSelection(spannable, end);
}
}
}