1 /*
2  * Copyright (C) 2015 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 package com.android.contacts.common.compat;
17 
18 import android.os.Build;
19 import android.os.Build.VERSION;
20 import android.support.annotation.Nullable;
21 import android.support.v4.os.BuildCompat;
22 import android.text.TextUtils;
23 import android.util.Log;
24 
25 import com.android.contacts.common.model.CPOWrapper;
26 
27 import java.lang.reflect.InvocationTargetException;
28 import java.lang.reflect.Method;
29 
30 public final class CompatUtils {
31 
32     private static final String TAG = CompatUtils.class.getSimpleName();
33 
34     /**
35      * These 4 variables are copied from ContentProviderOperation for compatibility.
36      */
37     public final static int TYPE_INSERT = 1;
38 
39     public final static int TYPE_UPDATE = 2;
40 
41     public final static int TYPE_DELETE = 3;
42 
43     public final static int TYPE_ASSERT = 4;
44 
45     /**
46      * Returns whether the operation in CPOWrapper is of TYPE_INSERT;
47      */
isInsertCompat(CPOWrapper cpoWrapper)48     public static boolean isInsertCompat(CPOWrapper cpoWrapper) {
49         if (SdkVersionOverride.getSdkVersion(Build.VERSION_CODES.M) >= Build.VERSION_CODES.M) {
50             return cpoWrapper.getOperation().isInsert();
51         }
52         return (cpoWrapper.getType() == TYPE_INSERT);
53     }
54 
55     /**
56      * Returns whether the operation in CPOWrapper is of TYPE_UPDATE;
57      */
isUpdateCompat(CPOWrapper cpoWrapper)58     public static boolean isUpdateCompat(CPOWrapper cpoWrapper) {
59         if (SdkVersionOverride.getSdkVersion(Build.VERSION_CODES.M) >= Build.VERSION_CODES.M) {
60             return cpoWrapper.getOperation().isUpdate();
61         }
62         return (cpoWrapper.getType() == TYPE_UPDATE);
63     }
64 
65     /**
66      * Returns whether the operation in CPOWrapper is of TYPE_DELETE;
67      */
isDeleteCompat(CPOWrapper cpoWrapper)68     public static boolean isDeleteCompat(CPOWrapper cpoWrapper) {
69         if (SdkVersionOverride.getSdkVersion(Build.VERSION_CODES.M) >= Build.VERSION_CODES.M) {
70             return cpoWrapper.getOperation().isDelete();
71         }
72         return (cpoWrapper.getType() == TYPE_DELETE);
73     }
74     /**
75      * Returns whether the operation in CPOWrapper is of TYPE_ASSERT;
76      */
isAssertQueryCompat(CPOWrapper cpoWrapper)77     public static boolean isAssertQueryCompat(CPOWrapper cpoWrapper) {
78         if (SdkVersionOverride.getSdkVersion(Build.VERSION_CODES.M) >= Build.VERSION_CODES.M) {
79             return cpoWrapper.getOperation().isAssertQuery();
80         }
81         return (cpoWrapper.getType() == TYPE_ASSERT);
82     }
83 
84     /**
85      * PrioritizedMimeType is added in API level 23.
86      */
hasPrioritizedMimeType()87     public static boolean hasPrioritizedMimeType() {
88         return SdkVersionOverride.getSdkVersion(Build.VERSION_CODES.M)
89                 >= Build.VERSION_CODES.M;
90     }
91 
92     /**
93      * Determines if this version is compatible with multi-SIM and the phone account APIs. Can also
94      * force the version to be lower through SdkVersionOverride.
95      *
96      * @return {@code true} if multi-SIM capability is available, {@code false} otherwise.
97      */
isMSIMCompatible()98     public static boolean isMSIMCompatible() {
99         return SdkVersionOverride.getSdkVersion(Build.VERSION_CODES.LOLLIPOP)
100                 >= Build.VERSION_CODES.LOLLIPOP_MR1;
101     }
102 
103     /**
104      * Determines if this version is compatible with video calling. Can also force the version to be
105      * lower through SdkVersionOverride.
106      *
107      * @return {@code true} if video calling is allowed, {@code false} otherwise.
108      */
isVideoCompatible()109     public static boolean isVideoCompatible() {
110         return SdkVersionOverride.getSdkVersion(Build.VERSION_CODES.LOLLIPOP)
111                 >= Build.VERSION_CODES.M;
112     }
113 
114     /**
115      * Determines if this version is capable of using presence checking for video calling. Support
116      * for video call presence indication is added in SDK 24.
117      *
118      * @return {@code true} if video presence checking is allowed, {@code false} otherwise.
119      */
isVideoPresenceCompatible()120     public static boolean isVideoPresenceCompatible() {
121         return SdkVersionOverride.getSdkVersion(Build.VERSION_CODES.M)
122                 > Build.VERSION_CODES.M;
123     }
124 
125     /**
126      * Determines if this version is compatible with call subject. Can also force the version to be
127      * lower through SdkVersionOverride.
128      *
129      * @return {@code true} if call subject is a feature on this device, {@code false} otherwise.
130      */
isCallSubjectCompatible()131     public static boolean isCallSubjectCompatible() {
132         return SdkVersionOverride.getSdkVersion(Build.VERSION_CODES.LOLLIPOP)
133                 >= Build.VERSION_CODES.M;
134     }
135 
136     /**
137      * Determines if this version is compatible with a default dialer. Can also force the version to
138      * be lower through {@link SdkVersionOverride}.
139      *
140      * @return {@code true} if default dialer is a feature on this device, {@code false} otherwise.
141      */
isDefaultDialerCompatible()142     public static boolean isDefaultDialerCompatible() {
143         return isMarshmallowCompatible();
144     }
145 
146     /**
147      * Determines if this version is compatible with Lollipop Mr1-specific APIs. Can also force the
148      * version to be lower through SdkVersionOverride.
149      *
150      * @return {@code true} if runtime sdk is compatible with Lollipop MR1, {@code false} otherwise.
151      */
isLollipopMr1Compatible()152     public static boolean isLollipopMr1Compatible() {
153         return SdkVersionOverride.getSdkVersion(Build.VERSION_CODES.LOLLIPOP_MR1)
154                 >= Build.VERSION_CODES.LOLLIPOP_MR1;
155     }
156 
157     /**
158      * Determines if this version is compatible with Marshmallow-specific APIs. Can also force the
159      * version to be lower through SdkVersionOverride.
160      *
161      * @return {@code true} if runtime sdk is compatible with Marshmallow, {@code false} otherwise.
162      */
isMarshmallowCompatible()163     public static boolean isMarshmallowCompatible() {
164         return SdkVersionOverride.getSdkVersion(Build.VERSION_CODES.LOLLIPOP)
165                 >= Build.VERSION_CODES.M;
166     }
167 
168     /**
169      * Determines if this version is compatible with N-specific APIs.
170      *
171      * @return {@code true} if runtime sdk is compatible with N and the app is built with N, {@code
172      * false} otherwise.
173      */
isNCompatible()174     public static boolean isNCompatible() {
175         return BuildCompat.isAtLeastN();
176     }
177 
178     /**
179      * Determines if the given class is available. Can be used to check if system apis exist at
180      * runtime.
181      *
182      * @param className the name of the class to look for.
183      * @return {@code true} if the given class is available, {@code false} otherwise or if className
184      * is empty.
185      */
isClassAvailable(@ullable String className)186     public static boolean isClassAvailable(@Nullable String className) {
187         if (TextUtils.isEmpty(className)) {
188             return false;
189         }
190         try {
191             Class.forName(className);
192             return true;
193         } catch (ClassNotFoundException e) {
194             return false;
195         } catch (Throwable t) {
196             Log.e(TAG, "Unexpected exception when checking if class:" + className + " exists at "
197                     + "runtime", t);
198             return false;
199         }
200     }
201 
202     /**
203      * Determines if the given class's method is available to call. Can be used to check if system
204      * apis exist at runtime.
205      *
206      * @param className the name of the class to look for
207      * @param methodName the name of the method to look for
208      * @param parameterTypes the needed parameter types for the method to look for
209      * @return {@code true} if the given class is available, {@code false} otherwise or if className
210      * or methodName are empty.
211      */
isMethodAvailable(@ullable String className, @Nullable String methodName, Class<?>... parameterTypes)212     public static boolean isMethodAvailable(@Nullable String className, @Nullable String methodName,
213             Class<?>... parameterTypes) {
214         if (TextUtils.isEmpty(className) || TextUtils.isEmpty(methodName)) {
215             return false;
216         }
217 
218         try {
219             Class.forName(className).getMethod(methodName, parameterTypes);
220             return true;
221         } catch (ClassNotFoundException | NoSuchMethodException e) {
222             Log.v(TAG, "Could not find method: " + className + "#" + methodName);
223             return false;
224         } catch (Throwable t) {
225             Log.e(TAG, "Unexpected exception when checking if method: " + className + "#"
226                     + methodName + " exists at runtime", t);
227             return false;
228         }
229     }
230 
231     /**
232      * Invokes a given class's method using reflection. Can be used to call system apis that exist
233      * at runtime but not in the SDK.
234      *
235      * @param instance The instance of the class to invoke the method on.
236      * @param methodName The name of the method to invoke.
237      * @param parameterTypes The needed parameter types for the method.
238      * @param parameters The parameter values to pass into the method.
239      * @return The result of the invocation or {@code null} if instance or methodName are empty, or
240      * if the reflection fails.
241      */
242     @Nullable
invokeMethod(@ullable Object instance, @Nullable String methodName, Class<?>[] parameterTypes, Object[] parameters)243     public static Object invokeMethod(@Nullable Object instance, @Nullable String methodName,
244             Class<?>[] parameterTypes, Object[] parameters) {
245         if (instance == null || TextUtils.isEmpty(methodName)) {
246             return null;
247         }
248 
249         String className = instance.getClass().getName();
250         try {
251             return Class.forName(className).getMethod(methodName, parameterTypes)
252                     .invoke(instance, parameters);
253         } catch (ClassNotFoundException | NoSuchMethodException | IllegalArgumentException
254                 | IllegalAccessException | InvocationTargetException e) {
255             Log.v(TAG, "Could not invoke method: " + className + "#" + methodName);
256             return null;
257         } catch (Throwable t) {
258             Log.e(TAG, "Unexpected exception when invoking method: " + className
259                     + "#" + methodName + " at runtime", t);
260             return null;
261         }
262     }
263 
264     /**
265      * Determines if this version is compatible with Lollipop-specific APIs. Can also force the
266      * version to be lower through SdkVersionOverride.
267      *
268      * @return {@code true} if call subject is a feature on this device, {@code false} otherwise.
269      */
isLollipopCompatible()270     public static boolean isLollipopCompatible() {
271         return SdkVersionOverride.getSdkVersion(Build.VERSION_CODES.LOLLIPOP)
272                 >= Build.VERSION_CODES.LOLLIPOP;
273     }
274 }
275