Home | History | Annotate | Download | only in widget
      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