1 /* 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 17 package android.view.inputmethod; 18 19 import static java.lang.annotation.RetentionPolicy.SOURCE; 20 21 import android.annotation.IntDef; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.os.Bundle; 25 26 import java.lang.annotation.Retention; 27 import java.lang.reflect.Method; 28 import java.lang.reflect.Modifier; 29 import java.util.Collections; 30 import java.util.Map; 31 import java.util.WeakHashMap; 32 33 /** 34 * @hide 35 */ 36 public final class InputConnectionInspector { 37 38 @Retention(SOURCE) 39 @IntDef({MissingMethodFlags.GET_SELECTED_TEXT, 40 MissingMethodFlags.SET_COMPOSING_REGION, 41 MissingMethodFlags.COMMIT_CORRECTION, 42 MissingMethodFlags.REQUEST_CURSOR_UPDATES, 43 MissingMethodFlags.DELETE_SURROUNDING_TEXT_IN_CODE_POINTS, 44 MissingMethodFlags.GET_HANDLER, 45 MissingMethodFlags.CLOSE_CONNECTION, 46 MissingMethodFlags.COMMIT_CONTENT, 47 MissingMethodFlags.GET_SURROUNDING_TEXT 48 }) 49 public @interface MissingMethodFlags { 50 /** 51 * {@link InputConnection#getSelectedText(int)} is available in 52 * {@link android.os.Build.VERSION_CODES#GINGERBREAD} and later. 53 */ 54 int GET_SELECTED_TEXT = 1 << 0; 55 /** 56 * {@link InputConnection#setComposingRegion(int, int)} is available in 57 * {@link android.os.Build.VERSION_CODES#GINGERBREAD} and later. 58 */ 59 int SET_COMPOSING_REGION = 1 << 1; 60 /** 61 * {@link InputConnection#commitCorrection(CorrectionInfo)} is available in 62 * {@link android.os.Build.VERSION_CODES#HONEYCOMB} and later. 63 */ 64 int COMMIT_CORRECTION = 1 << 2; 65 /** 66 * {@link InputConnection#requestCursorUpdates(int)} is available in 67 * {@link android.os.Build.VERSION_CODES#LOLLIPOP} and later. 68 */ 69 int REQUEST_CURSOR_UPDATES = 1 << 3; 70 /** 71 * {@link InputConnection#deleteSurroundingTextInCodePoints(int, int)}} is available in 72 * {@link android.os.Build.VERSION_CODES#N} and later. 73 */ 74 int DELETE_SURROUNDING_TEXT_IN_CODE_POINTS = 1 << 4; 75 /** 76 * {@link InputConnection#deleteSurroundingTextInCodePoints(int, int)}} is available in 77 * {@link android.os.Build.VERSION_CODES#N} and later. 78 */ 79 int GET_HANDLER = 1 << 5; 80 /** 81 * {@link InputConnection#closeConnection()}} is available in 82 * {@link android.os.Build.VERSION_CODES#N} and later. 83 */ 84 int CLOSE_CONNECTION = 1 << 6; 85 /** 86 * {@link InputConnection#commitContent(InputContentInfo, int, Bundle)} is available in 87 * {@link android.os.Build.VERSION_CODES#N} MR-1 and later. 88 */ 89 int COMMIT_CONTENT = 1 << 7; 90 /** 91 * {@link InputConnection#getSurroundingText(int, int, int)} is available in 92 * {@link android.os.Build.VERSION_CODES#S} and later. 93 */ 94 int GET_SURROUNDING_TEXT = 1 << 8; 95 } 96 97 private static final Map<Class, Integer> sMissingMethodsMap = Collections.synchronizedMap( 98 new WeakHashMap<>()); 99 100 @MissingMethodFlags getMissingMethodFlags(@ullable final InputConnection ic)101 public static int getMissingMethodFlags(@Nullable final InputConnection ic) { 102 if (ic == null) { 103 return 0; 104 } 105 // Optimization for a known class. 106 if (ic instanceof BaseInputConnection) { 107 return 0; 108 } 109 // Optimization for a known class. 110 if (ic instanceof InputConnectionWrapper) { 111 return ((InputConnectionWrapper) ic).getMissingMethodFlags(); 112 } 113 return getMissingMethodFlagsInternal(ic.getClass()); 114 } 115 116 @MissingMethodFlags getMissingMethodFlagsInternal(@onNull final Class clazz)117 public static int getMissingMethodFlagsInternal(@NonNull final Class clazz) { 118 final Integer cachedFlags = sMissingMethodsMap.get(clazz); 119 if (cachedFlags != null) { 120 return cachedFlags; 121 } 122 int flags = 0; 123 if (!hasGetSelectedText(clazz)) { 124 flags |= MissingMethodFlags.GET_SELECTED_TEXT; 125 } 126 if (!hasSetComposingRegion(clazz)) { 127 flags |= MissingMethodFlags.SET_COMPOSING_REGION; 128 } 129 if (!hasCommitCorrection(clazz)) { 130 flags |= MissingMethodFlags.COMMIT_CORRECTION; 131 } 132 if (!hasRequestCursorUpdate(clazz)) { 133 flags |= MissingMethodFlags.REQUEST_CURSOR_UPDATES; 134 } 135 if (!hasDeleteSurroundingTextInCodePoints(clazz)) { 136 flags |= MissingMethodFlags.DELETE_SURROUNDING_TEXT_IN_CODE_POINTS; 137 } 138 if (!hasGetHandler(clazz)) { 139 flags |= MissingMethodFlags.GET_HANDLER; 140 } 141 if (!hasCloseConnection(clazz)) { 142 flags |= MissingMethodFlags.CLOSE_CONNECTION; 143 } 144 if (!hasCommitContent(clazz)) { 145 flags |= MissingMethodFlags.COMMIT_CONTENT; 146 } 147 if (!hasGetSurroundingText(clazz)) { 148 flags |= MissingMethodFlags.GET_SURROUNDING_TEXT; 149 } 150 sMissingMethodsMap.put(clazz, flags); 151 return flags; 152 } 153 hasGetSelectedText(@onNull final Class clazz)154 private static boolean hasGetSelectedText(@NonNull final Class clazz) { 155 try { 156 final Method method = clazz.getMethod("getSelectedText", int.class); 157 return !Modifier.isAbstract(method.getModifiers()); 158 } catch (NoSuchMethodException e) { 159 return false; 160 } 161 } 162 hasSetComposingRegion(@onNull final Class clazz)163 private static boolean hasSetComposingRegion(@NonNull final Class clazz) { 164 try { 165 final Method method = clazz.getMethod("setComposingRegion", int.class, int.class); 166 return !Modifier.isAbstract(method.getModifiers()); 167 } catch (NoSuchMethodException e) { 168 return false; 169 } 170 } 171 hasCommitCorrection(@onNull final Class clazz)172 private static boolean hasCommitCorrection(@NonNull final Class clazz) { 173 try { 174 final Method method = clazz.getMethod("commitCorrection", CorrectionInfo.class); 175 return !Modifier.isAbstract(method.getModifiers()); 176 } catch (NoSuchMethodException e) { 177 return false; 178 } 179 } 180 hasRequestCursorUpdate(@onNull final Class clazz)181 private static boolean hasRequestCursorUpdate(@NonNull final Class clazz) { 182 try { 183 final Method method = clazz.getMethod("requestCursorUpdates", int.class); 184 return !Modifier.isAbstract(method.getModifiers()); 185 } catch (NoSuchMethodException e) { 186 return false; 187 } 188 } 189 hasDeleteSurroundingTextInCodePoints(@onNull final Class clazz)190 private static boolean hasDeleteSurroundingTextInCodePoints(@NonNull final Class clazz) { 191 try { 192 final Method method = clazz.getMethod("deleteSurroundingTextInCodePoints", int.class, 193 int.class); 194 return !Modifier.isAbstract(method.getModifiers()); 195 } catch (NoSuchMethodException e) { 196 return false; 197 } 198 } 199 hasGetHandler(@onNull final Class clazz)200 private static boolean hasGetHandler(@NonNull final Class clazz) { 201 try { 202 final Method method = clazz.getMethod("getHandler"); 203 return !Modifier.isAbstract(method.getModifiers()); 204 } catch (NoSuchMethodException e) { 205 return false; 206 } 207 } 208 hasCloseConnection(@onNull final Class clazz)209 private static boolean hasCloseConnection(@NonNull final Class clazz) { 210 try { 211 final Method method = clazz.getMethod("closeConnection"); 212 return !Modifier.isAbstract(method.getModifiers()); 213 } catch (NoSuchMethodException e) { 214 return false; 215 } 216 } 217 hasCommitContent(@onNull final Class clazz)218 private static boolean hasCommitContent(@NonNull final Class clazz) { 219 try { 220 final Method method = clazz.getMethod("commitContent", InputContentInfo.class, 221 int.class, Bundle.class); 222 return !Modifier.isAbstract(method.getModifiers()); 223 } catch (NoSuchMethodException e) { 224 return false; 225 } 226 } 227 hasGetSurroundingText(@onNull final Class clazz)228 private static boolean hasGetSurroundingText(@NonNull final Class clazz) { 229 try { 230 final Method method = clazz.getMethod("getSurroundingText", int.class, int.class, 231 int.class); 232 return !Modifier.isAbstract(method.getModifiers()); 233 } catch (NoSuchMethodException e) { 234 return false; 235 } 236 } 237 getMissingMethodFlagsAsString(@issingMethodFlags final int flags)238 public static String getMissingMethodFlagsAsString(@MissingMethodFlags final int flags) { 239 final StringBuilder sb = new StringBuilder(); 240 boolean isEmpty = true; 241 if ((flags & MissingMethodFlags.GET_SELECTED_TEXT) != 0) { 242 sb.append("getSelectedText(int)"); 243 isEmpty = false; 244 } 245 if ((flags & MissingMethodFlags.SET_COMPOSING_REGION) != 0) { 246 if (!isEmpty) { 247 sb.append(","); 248 } 249 sb.append("setComposingRegion(int, int)"); 250 isEmpty = false; 251 } 252 if ((flags & MissingMethodFlags.COMMIT_CORRECTION) != 0) { 253 if (!isEmpty) { 254 sb.append(","); 255 } 256 sb.append("commitCorrection(CorrectionInfo)"); 257 isEmpty = false; 258 } 259 if ((flags & MissingMethodFlags.REQUEST_CURSOR_UPDATES) != 0) { 260 if (!isEmpty) { 261 sb.append(","); 262 } 263 sb.append("requestCursorUpdate(int)"); 264 isEmpty = false; 265 } 266 if ((flags & MissingMethodFlags.DELETE_SURROUNDING_TEXT_IN_CODE_POINTS) != 0) { 267 if (!isEmpty) { 268 sb.append(","); 269 } 270 sb.append("deleteSurroundingTextInCodePoints(int, int)"); 271 isEmpty = false; 272 } 273 if ((flags & MissingMethodFlags.GET_HANDLER) != 0) { 274 if (!isEmpty) { 275 sb.append(","); 276 } 277 sb.append("getHandler()"); 278 } 279 if ((flags & MissingMethodFlags.CLOSE_CONNECTION) != 0) { 280 if (!isEmpty) { 281 sb.append(","); 282 } 283 sb.append("closeConnection()"); 284 } 285 if ((flags & MissingMethodFlags.COMMIT_CONTENT) != 0) { 286 if (!isEmpty) { 287 sb.append(","); 288 } 289 sb.append("commitContent(InputContentInfo, Bundle)"); 290 } 291 return sb.toString(); 292 } 293 } 294