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