blob: 118a61f8d571c3d447b694971144bffd5dcd6d85 [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;
Yohei Yukawa9f9afe522016-03-30 12:03:51 -070076 /**
77 * {@link InputConnection#closeConnection()}} is available in
78 * {@link android.os.Build.VERSION_CODES#N} and later.
79 */
80 int CLOSE_CONNECTION = 1 << 6;
Yohei Yukawa19a80a12016-03-14 22:57:37 -070081 }
82
83 private static final Map<Class, Integer> sMissingMethodsMap = Collections.synchronizedMap(
84 new WeakHashMap<>());
85
86 @MissingMethodFlags
87 public static int getMissingMethodFlags(@Nullable final InputConnection ic) {
88 if (ic == null) {
89 return 0;
90 }
91 // Optimization for a known class.
92 if (ic instanceof BaseInputConnection) {
93 return 0;
94 }
95 // Optimization for a known class.
96 if (ic instanceof InputConnectionWrapper) {
97 return ((InputConnectionWrapper) ic).getMissingMethodFlags();
98 }
99 return getMissingMethodFlagsInternal(ic.getClass());
100 }
101
102 @MissingMethodFlags
103 public static int getMissingMethodFlagsInternal(@NonNull final Class clazz) {
104 final Integer cachedFlags = sMissingMethodsMap.get(clazz);
105 if (cachedFlags != null) {
106 return cachedFlags;
107 }
108 int flags = 0;
109 if (!hasGetSelectedText(clazz)) {
110 flags |= MissingMethodFlags.GET_SELECTED_TEXT;
111 }
112 if (!hasSetComposingRegion(clazz)) {
113 flags |= MissingMethodFlags.SET_COMPOSING_REGION;
114 }
115 if (!hasCommitCorrection(clazz)) {
116 flags |= MissingMethodFlags.COMMIT_CORRECTION;
117 }
118 if (!hasRequestCursorUpdate(clazz)) {
119 flags |= MissingMethodFlags.REQUEST_CURSOR_UPDATES;
120 }
121 if (!hasDeleteSurroundingTextInCodePoints(clazz)) {
122 flags |= MissingMethodFlags.DELETE_SURROUNDING_TEXT_IN_CODE_POINTS;
123 }
124 if (!hasGetHandler(clazz)) {
125 flags |= MissingMethodFlags.GET_HANDLER;
126 }
Yohei Yukawa9f9afe522016-03-30 12:03:51 -0700127 if (!hasCloseConnection(clazz)) {
128 flags |= MissingMethodFlags.CLOSE_CONNECTION;
129 }
Yohei Yukawa19a80a12016-03-14 22:57:37 -0700130 sMissingMethodsMap.put(clazz, flags);
131 return flags;
132 }
133
134 private static boolean hasGetSelectedText(@NonNull final Class clazz) {
135 try {
136 final Method method = clazz.getMethod("getSelectedText", int.class);
137 return !Modifier.isAbstract(method.getModifiers());
138 } catch (NoSuchMethodException e) {
139 return false;
140 }
141 }
142
143 private static boolean hasSetComposingRegion(@NonNull final Class clazz) {
144 try {
145 final Method method = clazz.getMethod("setComposingRegion", int.class, int.class);
146 return !Modifier.isAbstract(method.getModifiers());
147 } catch (NoSuchMethodException e) {
148 return false;
149 }
150 }
151
152 private static boolean hasCommitCorrection(@NonNull final Class clazz) {
153 try {
154 final Method method = clazz.getMethod("commitCorrection", CorrectionInfo.class);
155 return !Modifier.isAbstract(method.getModifiers());
156 } catch (NoSuchMethodException e) {
157 return false;
158 }
159 }
160
161 private static boolean hasRequestCursorUpdate(@NonNull final Class clazz) {
162 try {
163 final Method method = clazz.getMethod("requestCursorUpdates", int.class);
164 return !Modifier.isAbstract(method.getModifiers());
165 } catch (NoSuchMethodException e) {
166 return false;
167 }
168 }
169
170 private static boolean hasDeleteSurroundingTextInCodePoints(@NonNull final Class clazz) {
171 try {
172 final Method method = clazz.getMethod("deleteSurroundingTextInCodePoints", int.class,
173 int.class);
174 return !Modifier.isAbstract(method.getModifiers());
175 } catch (NoSuchMethodException e) {
176 return false;
177 }
178 }
179
180 private static boolean hasGetHandler(@NonNull final Class clazz) {
181 try {
182 final Method method = clazz.getMethod("getHandler");
183 return !Modifier.isAbstract(method.getModifiers());
184 } catch (NoSuchMethodException e) {
185 return false;
186 }
187 }
188
Yohei Yukawa9f9afe522016-03-30 12:03:51 -0700189 private static boolean hasCloseConnection(@NonNull final Class clazz) {
190 try {
191 final Method method = clazz.getMethod("closeConnection");
192 return !Modifier.isAbstract(method.getModifiers());
193 } catch (NoSuchMethodException e) {
194 return false;
195 }
196 }
197
Yohei Yukawa19a80a12016-03-14 22:57:37 -0700198 public static String getMissingMethodFlagsAsString(@MissingMethodFlags final int flags) {
199 final StringBuilder sb = new StringBuilder();
200 boolean isEmpty = true;
201 if ((flags & MissingMethodFlags.GET_SELECTED_TEXT) != 0) {
202 sb.append("getSelectedText(int)");
203 isEmpty = false;
204 }
205 if ((flags & MissingMethodFlags.SET_COMPOSING_REGION) != 0) {
206 if (!isEmpty) {
207 sb.append(",");
208 }
209 sb.append("setComposingRegion(int, int)");
210 isEmpty = false;
211 }
212 if ((flags & MissingMethodFlags.COMMIT_CORRECTION) != 0) {
213 if (!isEmpty) {
214 sb.append(",");
215 }
216 sb.append("commitCorrection(CorrectionInfo)");
217 isEmpty = false;
218 }
219 if ((flags & MissingMethodFlags.REQUEST_CURSOR_UPDATES) != 0) {
220 if (!isEmpty) {
221 sb.append(",");
222 }
223 sb.append("requestCursorUpdate(int)");
224 isEmpty = false;
225 }
226 if ((flags & MissingMethodFlags.DELETE_SURROUNDING_TEXT_IN_CODE_POINTS) != 0) {
227 if (!isEmpty) {
228 sb.append(",");
229 }
230 sb.append("deleteSurroundingTextInCodePoints(int, int)");
231 isEmpty = false;
232 }
233 if ((flags & MissingMethodFlags.GET_HANDLER) != 0) {
234 if (!isEmpty) {
235 sb.append(",");
236 }
237 sb.append("getHandler()");
238 }
Yohei Yukawa9f9afe522016-03-30 12:03:51 -0700239 if ((flags & MissingMethodFlags.CLOSE_CONNECTION) != 0) {
240 if (!isEmpty) {
241 sb.append(",");
242 }
243 sb.append("closeConnection()");
244 }
Yohei Yukawa19a80a12016-03-14 22:57:37 -0700245 return sb.toString();
246 }
247}