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 android.annotation.IntDef; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 23 import java.lang.annotation.Retention; 24 import java.lang.reflect.Method; 25 import java.lang.reflect.Modifier; 26 import java.util.Collections; 27 import java.util.Map; 28 import java.util.WeakHashMap; 29 30 import static java.lang.annotation.RetentionPolicy.SOURCE; 31 32 /** 33 * @hide 34 */ 35 public 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 * {@link InputConnection#closeConnection()}} is available in 78 * {@link android.os.Build.VERSION_CODES#N} and later. 79 */ 80 int CLOSE_CONNECTION = 1 << 6; 81 } 82 83 private static final Map<Class, Integer> sMissingMethodsMap = Collections.synchronizedMap( 84 new WeakHashMap<>()); 85 86 @MissingMethodFlags getMissingMethodFlags(@ullable final InputConnection ic)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 getMissingMethodFlagsInternal(@onNull final Class clazz)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 } 127 if (!hasCloseConnection(clazz)) { 128 flags |= MissingMethodFlags.CLOSE_CONNECTION; 129 } 130 sMissingMethodsMap.put(clazz, flags); 131 return flags; 132 } 133 hasGetSelectedText(@onNull final Class clazz)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 hasSetComposingRegion(@onNull final Class clazz)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 hasCommitCorrection(@onNull final Class clazz)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 hasRequestCursorUpdate(@onNull final Class clazz)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 hasDeleteSurroundingTextInCodePoints(@onNull final Class clazz)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 hasGetHandler(@onNull final Class clazz)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 hasCloseConnection(@onNull final Class clazz)189 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 getMissingMethodFlagsAsString(@issingMethodFlags final int flags)198 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 } 239 if ((flags & MissingMethodFlags.CLOSE_CONNECTION) != 0) { 240 if (!isEmpty) { 241 sb.append(","); 242 } 243 sb.append("closeConnection()"); 244 } 245 return sb.toString(); 246 } 247 } 248