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