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