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 */ 16 package androidx.emoji.widget; 17 18 import android.annotation.SuppressLint; 19 import android.text.Editable; 20 21 import androidx.annotation.GuardedBy; 22 import androidx.annotation.NonNull; 23 import androidx.annotation.Nullable; 24 25 /** 26 * EditableFactory used to improve editing operations on an EditText. 27 * <p> 28 * EditText uses DynamicLayout, which attaches to the Spannable instance that is being edited using 29 * ChangeWatcher. ChangeWatcher implements SpanWatcher and Textwatcher. Currently every delete/add 30 * operation is reported to DynamicLayout, for every span that has changed. For each change, 31 * DynamicLayout performs some expensive computations. i.e. if there is 100 EmojiSpans and the first 32 * span is deleted, DynamicLayout gets 99 calls about the change of position occurred in the 33 * remaining spans. This causes a huge delay in response time. 34 * <p> 35 * Since "android.text.DynamicLayout$ChangeWatcher" class is not a public class, 36 * EmojiEditableFactory checks if the watcher is in the classpath, and if so uses the modified 37 * Spannable which reduces the total number of calls to DynamicLayout for operations that affect 38 * EmojiSpans. 39 * 40 * @see SpannableBuilder 41 */ 42 final class EmojiEditableFactory extends Editable.Factory { 43 private static final Object sInstanceLock = new Object(); 44 @GuardedBy("sInstanceLock") 45 private static volatile Editable.Factory sInstance; 46 47 @Nullable private static Class<?> sWatcherClass; 48 49 @SuppressLint("PrivateApi") 50 private EmojiEditableFactory() { 51 try { 52 String className = "android.text.DynamicLayout$ChangeWatcher"; 53 sWatcherClass = getClass().getClassLoader().loadClass(className); 54 } catch (Throwable t) { 55 // ignore 56 } 57 } 58 59 @SuppressWarnings("GuardedBy") 60 public static Editable.Factory getInstance() { 61 if (sInstance == null) { 62 synchronized (sInstanceLock) { 63 if (sInstance == null) { 64 sInstance = new EmojiEditableFactory(); 65 } 66 } 67 } 68 return sInstance; 69 } 70 71 @Override 72 public Editable newEditable(@NonNull final CharSequence source) { 73 if (sWatcherClass != null) { 74 return SpannableBuilder.create(sWatcherClass, source); 75 } 76 return super.newEditable(source); 77 } 78 } 79