Added method to check if methods are available at runtime

Change-Id: Id5fd7b57eb27ce957a93771e9b9297fecd163bf9
diff --git a/src/com/android/contacts/common/compat/CompatUtils.java b/src/com/android/contacts/common/compat/CompatUtils.java
index 5215ac9..58a4eb7 100644
--- a/src/com/android/contacts/common/compat/CompatUtils.java
+++ b/src/com/android/contacts/common/compat/CompatUtils.java
@@ -17,6 +17,7 @@
 
 import android.os.Build;
 import android.support.annotation.Nullable;
+import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.contacts.common.model.CPOWrapper;
@@ -116,10 +117,10 @@
      *
      * @param className the name of the class to look for.
      * @return {@code true} if the given class is available, {@code false} otherwise or if className
-     *    is null.
+     *    is empty.
      */
     public static boolean isClassAvailable(@Nullable String className) {
-        if (className == null) {
+        if (TextUtils.isEmpty(className)) {
             return false;
         }
         try {
@@ -128,7 +129,36 @@
         } catch (ClassNotFoundException e) {
             return false;
         } catch (Throwable t) {
-            Log.e(TAG, "Unexpected exception when checking if class exists at runtime", t);
+            Log.e(TAG, "Unexpected exception when checking if class:" + className + " exists at "
+                    + "runtime", t);
+            return false;
+        }
+    }
+
+    /**
+     * Determines if the given class's method is available to call. Can be used to check if system
+     * apis exist at runtime.
+     *
+     * @param className the name of the class to look for
+     * @param methodName the name of the method to look for
+     * @param parameterTypes the needed parameter types for the method to look for
+     * @return {@code true} if the given class is available, {@code false} otherwise or if className
+     *    or methodName are empty.
+     */
+    public static boolean isMethodAvailable(@Nullable String className, @Nullable String methodName,
+            Class<?>... parameterTypes) {
+        if (TextUtils.isEmpty(className) || TextUtils.isEmpty(methodName)) {
+            return false;
+        }
+
+        try {
+            Class.forName(className).getMethod(methodName, parameterTypes);
+            return true;
+        } catch (ClassNotFoundException | NoSuchMethodException e) {
+            return false;
+        } catch (Throwable t) {
+            Log.e(TAG, "Unexpected exception when checking if method: " + className + "#"
+                    + methodName + " exists at runtime", t);
             return false;
         }
     }
diff --git a/tests/src/com/android/contacts/common/compat/CompatUtilsTest.java b/tests/src/com/android/contacts/common/compat/CompatUtilsTest.java
index 566a831..eaacbba 100644
--- a/tests/src/com/android/contacts/common/compat/CompatUtilsTest.java
+++ b/tests/src/com/android/contacts/common/compat/CompatUtilsTest.java
@@ -20,11 +20,75 @@
 
 public class CompatUtilsTest extends AndroidTestCase {
 
-    public void testClassAvailable() {
-        assertTrue(CompatUtils.isClassAvailable(CompatUtils.class.getName()));
+    public void testIsClassAvailable_NullClassName() {
+        assertFalse(CompatUtils.isClassAvailable(null));
     }
 
-    public void testClassNotAvailable() {
-        assertFalse(CompatUtils.isClassAvailable("com.android.contacts.common.NonexistantClass"));
+    public void testIsClassAvailable_EmptyClassName() {
+        assertFalse(CompatUtils.isClassAvailable(""));
+    }
+
+    public void testIsClassAvailable_NonexistentClass() {
+        assertFalse(CompatUtils.isClassAvailable("com.android.contacts.common.NonexistentClass"));
+    }
+
+    public void testIsClassAvailable() {
+        assertTrue(CompatUtils.isClassAvailable(BaseClass.class.getName()));
+    }
+
+    public void testIsMethodAvailable_NullClassName() {
+        assertFalse(CompatUtils.isMethodAvailable(null, "methodName"));
+    }
+
+    public void testIsMethodAvailable_EmptyClassName() {
+        assertFalse(CompatUtils.isMethodAvailable("", "methodName"));
+    }
+
+    public void testIsMethodAvailable_NullMethodName() {
+        assertFalse(CompatUtils.isMethodAvailable("className", null));
+    }
+
+    public void testIsMethodAvailable_EmptyMethodName() {
+        assertFalse(CompatUtils.isMethodAvailable("className", ""));
+    }
+
+    public void testIsMethodAvailable_NonexistentClass() {
+        assertFalse(CompatUtils.isMethodAvailable("com.android.contacts.common.NonexistentClass",
+                ""));
+    }
+
+    public void testIsMethodAvailable_NonexistentMethod() {
+        assertFalse(CompatUtils.isMethodAvailable(BaseClass.class.getName(), "derivedMethod"));
+    }
+
+    public void testIsMethodAvailable() {
+        assertTrue(CompatUtils.isMethodAvailable(BaseClass.class.getName(), "baseMethod"));
+    }
+
+    public void testIsMethodAvailable_InheritedMethod() {
+        assertTrue(CompatUtils.isMethodAvailable(DerivedClass.class.getName(), "baseMethod"));
+    }
+
+    public void testIsMethodAvailable_OverloadedMethod() {
+        assertTrue(CompatUtils.isMethodAvailable(DerivedClass.class.getName(), "overloadedMethod"));
+        assertTrue(CompatUtils.isMethodAvailable(DerivedClass.class.getName(), "overloadedMethod",
+                Integer.TYPE));
+    }
+
+    public void testIsMethodAvailable_NonexistentOverload() {
+        assertFalse(CompatUtils.isMethodAvailable(DerivedClass.class.getName(), "overloadedMethod",
+                Boolean.TYPE));
+    }
+
+    private class BaseClass {
+        public void baseMethod() {}
+    }
+
+    private class DerivedClass extends BaseClass {
+        public void derivedMethod() {}
+
+        public void overloadedMethod() {}
+
+        public void overloadedMethod(int i) {}
     }
 }