blob: 46b2c3e0fbea8774b35a0713358accee63096ade [file] [log] [blame]
Yohei Yukawa19a80a12016-03-14 22:57:37 -07001/*
2 * Copyright (C) 2016 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
17package android.view.inputmethod;
18
19import android.annotation.IntDef;
20import android.annotation.NonNull;
21import android.annotation.Nullable;
22
23import java.lang.annotation.Retention;
24import java.lang.reflect.Method;
25import java.lang.reflect.Modifier;
26import java.util.Collections;
27import java.util.Map;
28import java.util.WeakHashMap;
29
30import static java.lang.annotation.RetentionPolicy.SOURCE;
31
32/**
33 * @hide
34 */
35public final class InputConnectionInspector {
36
37 @Retention(SOURCE)
38 @IntDef({MissingMethodFlags.GET_SELECTED_TEXT,
39 MissingMethodFlags.SET_COMPOSING_REGION,
40 MissingMethodFlags.COMMIT_CORRECTION,
41 MissingMethodFlags.REQUEST_CURSOR_UPDATES,
42 MissingMethodFlags.DELETE_SURROUNDING_TEXT_IN_CODE_POINTS,
43 MissingMethodFlags.GET_HANDLER,
44 })
45 public @interface MissingMethodFlags {
46 /**
47 * {@link InputConnection#getSelectedText(int)} is available in
48 * {@link android.os.Build.VERSION_CODES#GINGERBREAD} and later.
49 */
50 int GET_SELECTED_TEXT = 1 << 0;
51 /**
52 * {@link InputConnection#setComposingRegion(int, int)} is available in
53 * {@link android.os.Build.VERSION_CODES#GINGERBREAD} and later.
54 */
55 int SET_COMPOSING_REGION = 1 << 1;
56 /**
57 * {@link InputConnection#commitCorrection(CorrectionInfo)} is available in
58 * {@link android.os.Build.VERSION_CODES#HONEYCOMB} and later.
59 */
60 int COMMIT_CORRECTION = 1 << 2;
61 /**
62 * {@link InputConnection#requestCursorUpdates(int)} is available in
63 * {@link android.os.Build.VERSION_CODES#LOLLIPOP} and later.
64 */
65 int REQUEST_CURSOR_UPDATES = 1 << 3;
66 /**
67 * {@link InputConnection#deleteSurroundingTextInCodePoints(int, int)}} is available in
68 * {@link android.os.Build.VERSION_CODES#N} and later.
69 */
70 int DELETE_SURROUNDING_TEXT_IN_CODE_POINTS = 1 << 4;
71 /**
72 * {@link InputConnection#deleteSurroundingTextInCodePoints(int, int)}} is available in
73 * {@link android.os.Build.VERSION_CODES#N} and later.
74 */
75 int GET_HANDLER = 1 << 5;
76 }
77
78 private static final Map<Class, Integer> sMissingMethodsMap = Collections.synchronizedMap(
79 new WeakHashMap<>());
80
81 @MissingMethodFlags
82 public static int getMissingMethodFlags(@Nullable final InputConnection ic) {
83 if (ic == null) {
84 return 0;
85 }
86 // Optimization for a known class.
87 if (ic instanceof BaseInputConnection) {
88 return 0;
89 }
90 // Optimization for a known class.
91 if (ic instanceof InputConnectionWrapper) {
92 return ((InputConnectionWrapper) ic).getMissingMethodFlags();
93 }
94 return getMissingMethodFlagsInternal(ic.getClass());
95 }
96
97 @MissingMethodFlags
98 public static int getMissingMethodFlagsInternal(@NonNull final Class clazz) {
99 final Integer cachedFlags = sMissingMethodsMap.get(clazz);
100 if (cachedFlags != null) {
101 return cachedFlags;
102 }
103 int flags = 0;
104 if (!hasGetSelectedText(clazz)) {
105 flags |= MissingMethodFlags.GET_SELECTED_TEXT;
106 }
107 if (!hasSetComposingRegion(clazz)) {
108 flags |= MissingMethodFlags.SET_COMPOSING_REGION;
109 }
110 if (!hasCommitCorrection(clazz)) {
111 flags |= MissingMethodFlags.COMMIT_CORRECTION;
112 }
113 if (!hasRequestCursorUpdate(clazz)) {
114 flags |= MissingMethodFlags.REQUEST_CURSOR_UPDATES;
115 }
116 if (!hasDeleteSurroundingTextInCodePoints(clazz)) {
117 flags |= MissingMethodFlags.DELETE_SURROUNDING_TEXT_IN_CODE_POINTS;
118 }
119 if (!hasGetHandler(clazz)) {
120 flags |= MissingMethodFlags.GET_HANDLER;
121 }
122 sMissingMethodsMap.put(clazz, flags);
123 return flags;
124 }
125
126 private static boolean hasGetSelectedText(@NonNull final Class clazz) {
127 try {
128 final Method method = clazz.getMethod("getSelectedText", int.class);
129 return !Modifier.isAbstract(method.getModifiers());
130 } catch (NoSuchMethodException e) {
131 return false;
132 }
133 }
134
135 private static boolean hasSetComposingRegion(@NonNull final Class clazz) {
136 try {
137 final Method method = clazz.getMethod("setComposingRegion", int.class, int.class);
138 return !Modifier.isAbstract(method.getModifiers());
139 } catch (NoSuchMethodException e) {
140 return false;
141 }
142 }
143
144 private static boolean hasCommitCorrection(@NonNull final Class clazz) {
145 try {
146 final Method method = clazz.getMethod("commitCorrection", CorrectionInfo.class);
147 return !Modifier.isAbstract(method.getModifiers());
148 } catch (NoSuchMethodException e) {
149 return false;
150 }
151 }
152
153 private static boolean hasRequestCursorUpdate(@NonNull final Class clazz) {
154 try {
155 final Method method = clazz.getMethod("requestCursorUpdates", int.class);
156 return !Modifier.isAbstract(method.getModifiers());
157 } catch (NoSuchMethodException e) {
158 return false;
159 }
160 }
161
162 private static boolean hasDeleteSurroundingTextInCodePoints(@NonNull final Class clazz) {
163 try {
164 final Method method = clazz.getMethod("deleteSurroundingTextInCodePoints", int.class,
165 int.class);
166 return !Modifier.isAbstract(method.getModifiers());
167 } catch (NoSuchMethodException e) {
168 return false;
169 }
170 }
171
172 private static boolean hasGetHandler(@NonNull final Class clazz) {
173 try {
174 final Method method = clazz.getMethod("getHandler");
175 return !Modifier.isAbstract(method.getModifiers());
176 } catch (NoSuchMethodException e) {
177 return false;
178 }
179 }
180
181 public static String getMissingMethodFlagsAsString(@MissingMethodFlags final int flags) {
182 final StringBuilder sb = new StringBuilder();
183 boolean isEmpty = true;
184 if ((flags & MissingMethodFlags.GET_SELECTED_TEXT) != 0) {
185 sb.append("getSelectedText(int)");
186 isEmpty = false;
187 }
188 if ((flags & MissingMethodFlags.SET_COMPOSING_REGION) != 0) {
189 if (!isEmpty) {
190 sb.append(",");
191 }
192 sb.append("setComposingRegion(int, int)");
193 isEmpty = false;
194 }
195 if ((flags & MissingMethodFlags.COMMIT_CORRECTION) != 0) {
196 if (!isEmpty) {
197 sb.append(",");
198 }
199 sb.append("commitCorrection(CorrectionInfo)");
200 isEmpty = false;
201 }
202 if ((flags & MissingMethodFlags.REQUEST_CURSOR_UPDATES) != 0) {
203 if (!isEmpty) {
204 sb.append(",");
205 }
206 sb.append("requestCursorUpdate(int)");
207 isEmpty = false;
208 }
209 if ((flags & MissingMethodFlags.DELETE_SURROUNDING_TEXT_IN_CODE_POINTS) != 0) {
210 if (!isEmpty) {
211 sb.append(",");
212 }
213 sb.append("deleteSurroundingTextInCodePoints(int, int)");
214 isEmpty = false;
215 }
216 if ((flags & MissingMethodFlags.GET_HANDLER) != 0) {
217 if (!isEmpty) {
218 sb.append(",");
219 }
220 sb.append("getHandler()");
221 }
222 return sb.toString();
223 }
224}