Home | History | Annotate | Download | only in inputmethod
      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
     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
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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