Merge "Introduce AppCompatActivity and modify account filter activities" into ub-contactsdialer-b-dev
diff --git a/src/com/android/contacts/common/CallUtil.java b/src/com/android/contacts/common/CallUtil.java
index 38b50d8..7640246 100644
--- a/src/com/android/contacts/common/CallUtil.java
+++ b/src/com/android/contacts/common/CallUtil.java
@@ -41,6 +41,22 @@
 public class CallUtil {
 
     /**
+     * Indicates that the video calling is not available.
+     */
+    public static final int VIDEO_CALLING_DISABLED = 0;
+
+    /**
+     * Indicates that video calling is enabled, regardless of presence status.
+     */
+    public static final int VIDEO_CALLING_ENABLED = 1;
+
+    /**
+     * Indicates that video calling is enabled, but the availability of video call affordances is
+     * determined by the presence status associated with contacts.
+     */
+    public static final int VIDEO_CALLING_PRESENCE = 2;
+
+    /**
      * Return an Intent for making a phone call. Scheme (e.g. tel, sip) will be determined
      * automatically.
      */
@@ -103,6 +119,49 @@
     }
 
     /**
+     * Determines if video calling is available, and if so whether presence checking is available
+     * as well.
+     *
+     * Returns a bitmask with {@link #VIDEO_CALLING_ENABLED} to indicate that video calling is
+     * available, and {@link #VIDEO_CALLING_PRESENCE} if presence indication is also available.
+     *
+     * @param context The context
+     * @return A bit-mask describing the current video capabilities.
+     */
+    public static int getVideoCallingAvailability(Context context) {
+        if (!PermissionsUtil.hasPermission(context, android.Manifest.permission.READ_PHONE_STATE)
+                || !CompatUtils.isVideoCompatible()) {
+            return VIDEO_CALLING_DISABLED;
+        }
+        TelecomManager telecommMgr = (TelecomManager)
+                context.getSystemService(Context.TELECOM_SERVICE);
+        if (telecommMgr == null) {
+            return VIDEO_CALLING_DISABLED;
+        }
+
+        List<PhoneAccountHandle> accountHandles = telecommMgr.getCallCapablePhoneAccounts();
+        for (PhoneAccountHandle accountHandle : accountHandles) {
+            PhoneAccount account = telecommMgr.getPhoneAccount(accountHandle);
+            if (account != null) {
+                if (account.hasCapabilities(PhoneAccount.CAPABILITY_VIDEO_CALLING)) {
+                    // Builds prior to N do not have presence support.
+                    if (!CompatUtils.isVideoPresenceCompatible()) {
+                        return VIDEO_CALLING_ENABLED;
+                    }
+
+                    int videoCapabilities = VIDEO_CALLING_ENABLED;
+                    if (account.hasCapabilities(
+                            PhoneAccount.CAPABILITY_VIDEO_CALLING_RELIES_ON_PRESENCE)) {
+                        videoCapabilities |= VIDEO_CALLING_PRESENCE;
+                    }
+                    return videoCapabilities;
+                }
+            }
+        }
+        return VIDEO_CALLING_DISABLED;
+    }
+
+    /**
      * Determines if one of the call capable phone accounts defined supports video calling.
      *
      * @param context The context.
@@ -110,24 +169,7 @@
      *      {@code false} otherwise.
      */
     public static boolean isVideoEnabled(Context context) {
-        if (!PermissionsUtil.hasPermission(context, android.Manifest.permission.READ_PHONE_STATE)
-                || !CompatUtils.isVideoCompatible()) {
-            return false;
-        }
-        TelecomManager telecommMgr = (TelecomManager)
-                context.getSystemService(Context.TELECOM_SERVICE);
-        if (telecommMgr == null) {
-            return false;
-        }
-
-        List<PhoneAccountHandle> accountHandles = telecommMgr.getCallCapablePhoneAccounts();
-        for (PhoneAccountHandle accountHandle : accountHandles) {
-            PhoneAccount account = telecommMgr.getPhoneAccount(accountHandle);
-            if (account != null && account.hasCapabilities(PhoneAccount.CAPABILITY_VIDEO_CALLING)) {
-                return true;
-            }
-        }
-        return false;
+        return (getVideoCallingAvailability(context) & VIDEO_CALLING_ENABLED) != 0;
     }
 
     /**
diff --git a/src/com/android/contacts/common/compat/CompatUtils.java b/src/com/android/contacts/common/compat/CompatUtils.java
index 58a4eb7..c84bcfa 100644
--- a/src/com/android/contacts/common/compat/CompatUtils.java
+++ b/src/com/android/contacts/common/compat/CompatUtils.java
@@ -22,6 +22,9 @@
 
 import com.android.contacts.common.model.CPOWrapper;
 
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
 public final class CompatUtils {
 
     private static final String TAG = CompatUtils.class.getSimpleName();
@@ -79,6 +82,17 @@
     }
 
     /**
+     * Determines if this version is capable of using presence checking for video calling.
+     * Support for video call presence indication is added in SDK 24.
+     *
+     * @return {@code true} if video presence checking is allowed, {@code false} otherwise.
+     */
+    public static boolean isVideoPresenceCompatible() {
+        return SdkVersionOverride.getSdkVersion(Build.VERSION_CODES.M)
+                > Build.VERSION_CODES.M;
+    }
+
+    /**
      * Determines if this version is compatible with call subject. Can also force the version to
      * be lower through SdkVersionOverride.
      *
@@ -90,6 +104,16 @@
     }
 
     /**
+     * Determines if this version is compatible with a default dialer. Can also force the version to
+     * be lower through {@link SdkVersionOverride}.
+     *
+     * @return {@code true} if default dialer is a feature on this device, {@code false} otherwise.
+     */
+    public static boolean isDefaultDialerCompatible() {
+        return isMarshmallowCompatible();
+    }
+
+    /**
      * Determines if this version is compatible with Lollipop Mr1-specific APIs. Can also force the
      * version to be lower through SdkVersionOverride.
      *
@@ -155,6 +179,7 @@
             Class.forName(className).getMethod(methodName, parameterTypes);
             return true;
         } catch (ClassNotFoundException | NoSuchMethodException e) {
+            Log.v(TAG, "Could not find method: " + className + "#" + methodName);
             return false;
         } catch (Throwable t) {
             Log.e(TAG, "Unexpected exception when checking if method: " + className + "#"
@@ -164,6 +189,39 @@
     }
 
     /**
+     * Invokes a given class's method using reflection. Can be used to call system apis that exist
+     * at runtime but not in the SDK.
+     *
+     * @param instance The instance of the class to invoke the method on.
+     * @param methodName The name of the method to invoke.
+     * @param parameterTypes The needed parameter types for the method.
+     * @param parameters The parameter values to pass into the method.
+     * @return The result of the invocation or {@code null} if instance or methodName are
+     * empty, or if the reflection fails.
+     */
+    @Nullable
+    public static Object invokeMethod(@Nullable Object instance, @Nullable String methodName,
+            Class<?>[] parameterTypes, Object[] parameters) {
+        if (instance == null || TextUtils.isEmpty(methodName)) {
+            return null;
+        }
+
+        String className = instance.getClass().getName();
+        try {
+            return Class.forName(className).getMethod(methodName, parameterTypes)
+                    .invoke(instance, parameters);
+        } catch (ClassNotFoundException | NoSuchMethodException | IllegalArgumentException
+                | IllegalAccessException | InvocationTargetException e) {
+            Log.v(TAG, "Could not invoke method: " + className + "#" + methodName);
+            return null;
+        } catch (Throwable t) {
+            Log.e(TAG, "Unexpected exception when invoking method: " + className
+                    + "#" + methodName + " at runtime", t);
+            return null;
+        }
+    }
+
+    /**
      * Determines if this version is compatible with Lollipop-specific APIs. Can also force the
      * version to be lower through SdkVersionOverride.
      *
diff --git a/src/com/android/contacts/common/compat/TelephonyManagerCompat.java b/src/com/android/contacts/common/compat/TelephonyManagerCompat.java
index d2a189d..0f55c48 100644
--- a/src/com/android/contacts/common/compat/TelephonyManagerCompat.java
+++ b/src/com/android/contacts/common/compat/TelephonyManagerCompat.java
@@ -21,6 +21,7 @@
 import android.telephony.TelephonyManager;
 
 public class TelephonyManagerCompat {
+    public static final String TELEPHONY_MANAGER_CLASS = "android.telephony.TelephonyManager";
 
     /**
      * @param telephonyManager The telephony manager instance to use for method calls.
@@ -41,7 +42,8 @@
         if (telephonyManager == null) {
             return false;
         }
-        if (CompatUtils.isLollipopMr1Compatible()) {
+        if (CompatUtils.isLollipopMr1Compatible()
+                || CompatUtils.isMethodAvailable(TELEPHONY_MANAGER_CLASS, "isVoiceCapable")) {
             // isVoiceCapable was unhidden in L-MR1
             return telephonyManager.isVoiceCapable();
         }
@@ -64,7 +66,7 @@
             return 1;
         }
         if (CompatUtils.isMarshmallowCompatible() || CompatUtils
-                .isMethodAvailable("android.telephony.TelephonyManager", "getPhoneCount")) {
+                .isMethodAvailable(TELEPHONY_MANAGER_CLASS, "getPhoneCount")) {
             return telephonyManager.getPhoneCount();
         }
         return 1;
@@ -85,7 +87,7 @@
             return null;
         }
         if (CompatUtils.isMarshmallowCompatible()
-                || CompatUtils.isMethodAvailable("android.telephony.TelephonyManager",
+                || CompatUtils.isMethodAvailable(TELEPHONY_MANAGER_CLASS,
                         "getDeviceId", Integer.class)) {
             return telephonyManager.getDeviceId(slotId);
         }
@@ -104,8 +106,7 @@
             return false;
         }
         if (CompatUtils.isMarshmallowCompatible()
-                || CompatUtils.isMethodAvailable("android.telephony.TelephonyManager",
-                        "isTtyModeSupported")) {
+                || CompatUtils.isMethodAvailable(TELEPHONY_MANAGER_CLASS, "isTtyModeSupported")) {
             return telephonyManager.isTtyModeSupported();
         }
         return false;
@@ -124,8 +125,8 @@
             return false;
         }
         if (CompatUtils.isMarshmallowCompatible()
-                || CompatUtils.isMethodAvailable("android.telephony.TelephonyManager",
-                        "isTtyModeSupported")) {
+                || CompatUtils.isMethodAvailable(TELEPHONY_MANAGER_CLASS,
+                        "isHearingAidCompatibilitySupported")) {
             return telephonyManager.isHearingAidCompatibilitySupported();
         }
         return false;
diff --git a/src/com/android/contacts/common/compat/telecom/TelecomManagerCompat.java b/src/com/android/contacts/common/compat/telecom/TelecomManagerCompat.java
index 10c2ac3..6292b7f 100644
--- a/src/com/android/contacts/common/compat/telecom/TelecomManagerCompat.java
+++ b/src/com/android/contacts/common/compat/telecom/TelecomManagerCompat.java
@@ -17,19 +17,27 @@
 
 import android.app.Activity;
 import android.content.Intent;
+import android.content.Context;
+import android.net.Uri;
 import android.support.annotation.Nullable;
+import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
 import android.telephony.PhoneNumberUtils;
 import android.telephony.TelephonyManager;
+import android.text.TextUtils;
 
 import com.android.contacts.common.compat.CompatUtils;
 
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+
 /**
- * Compatibility class for {@link android.telecom.TelecomManager}
+ * Compatibility class for {@link android.telecom.TelecomManager}.
  */
 public class TelecomManagerCompat {
-
+    public static final String TELECOM_MANAGER_CLASS = "android.telecom.TelecomManager";
     /**
      * Places a new outgoing call to the provided address using the system telecom service with
      * the specified intent.
@@ -37,9 +45,12 @@
      * @param activity {@link Activity} used to start another activity for the given intent
      * @param telecomManager the {@link TelecomManager} used to place a call, if possible
      * @param intent the intent for the call
-     * @throws NullPointerException if activity, telecomManager, or intent are null
      */
-    public static void placeCall(Activity activity, TelecomManager telecomManager, Intent intent) {
+    public static void placeCall(@Nullable Activity activity,
+            @Nullable TelecomManager telecomManager, @Nullable Intent intent) {
+        if (activity == null || telecomManager == null || intent == null) {
+            return;
+        }
         if (CompatUtils.isMarshmallowCompatible()) {
             telecomManager.placeCall(intent.getData(), intent.getExtras());
             return;
@@ -48,6 +59,86 @@
     }
 
     /**
+     * Get the URI for running an adn query.
+     *
+     * @param telecomManager the {@link TelecomManager} used for method calls, if possible.
+     * @param accountHandle The handle for the account to derive an adn query URI for or
+     * {@code null} to return a URI which will use the default account.
+     * @return The URI (with the content:// scheme) specific to the specified {@link PhoneAccount}
+     * for the the content retrieve.
+     */
+    public static Uri getAdnUriForPhoneAccount(@Nullable TelecomManager telecomManager,
+            PhoneAccountHandle accountHandle) {
+        if (telecomManager != null && (CompatUtils.isMarshmallowCompatible()
+                || CompatUtils.isMethodAvailable(TELECOM_MANAGER_CLASS, "getAdnUriForPhoneAccount",
+                        PhoneAccountHandle.class))) {
+            return telecomManager.getAdnUriForPhoneAccount(accountHandle);
+        }
+        return Uri.parse("content://icc/adn");
+    }
+
+    /**
+     * Returns a list of {@link PhoneAccountHandle}s which can be used to make and receive phone
+     * calls. The returned list includes only those accounts which have been explicitly enabled
+     * by the user.
+     *
+     * @param telecomManager the {@link TelecomManager} used for method calls, if possible.
+     * @return A list of PhoneAccountHandle objects.
+     */
+    public static List<PhoneAccountHandle> getCallCapablePhoneAccounts(
+            @Nullable TelecomManager telecomManager) {
+        if (telecomManager != null && (CompatUtils.isMarshmallowCompatible()
+                || CompatUtils.isMethodAvailable(TELECOM_MANAGER_CLASS,
+                        "getCallCapablePhoneAccounts"))) {
+            return telecomManager.getCallCapablePhoneAccounts();
+        }
+        return new ArrayList<>();
+    }
+
+    /**
+     * Used to determine the currently selected default dialer package.
+     *
+     * @param telecomManager the {@link TelecomManager} used for method calls, if possible.
+     * @return package name for the default dialer package or null if no package has been
+     *         selected as the default dialer.
+     */
+    @Nullable
+    public static String getDefaultDialerPackage(@Nullable TelecomManager telecomManager) {
+        if (telecomManager != null && CompatUtils.isDefaultDialerCompatible()) {
+            return telecomManager.getDefaultDialerPackage();
+        }
+        return null;
+    }
+
+    /**
+     * Return the {@link PhoneAccount} which will be used to place outgoing calls to addresses with
+     * the specified {@code uriScheme}. This PhoneAccount will always be a member of the
+     * list which is returned from invoking {@link TelecomManager#getCallCapablePhoneAccounts()}.
+     * The specific account returned depends on the following priorities:
+     *
+     * 1. If the user-selected default PhoneAccount supports the specified scheme, it will
+     * be returned.
+     * 2. If there exists only one PhoneAccount that supports the specified scheme, it
+     * will be returned.
+     *
+     * If no PhoneAccount fits the criteria above, this method will return {@code null}.
+     *
+     * @param telecomManager the {@link TelecomManager} used for method calls, if possible.
+     * @param uriScheme The URI scheme.
+     * @return The {@link PhoneAccountHandle} corresponding to the account to be used.
+     */
+    @Nullable
+    public static PhoneAccountHandle getDefaultOutgoingPhoneAccount(
+            @Nullable TelecomManager telecomManager, @Nullable String uriScheme) {
+        if (telecomManager != null && (CompatUtils.isMarshmallowCompatible()
+                || CompatUtils.isMethodAvailable(TELECOM_MANAGER_CLASS,
+                        "getDefaultOutgoingPhoneAccount", String.class))) {
+            return telecomManager.getDefaultOutgoingPhoneAccount(uriScheme);
+        }
+        return null;
+    }
+
+    /**
      * Return the line 1 phone number for given phone account.
      *
      * @param telecomManager the {@link TelecomManager} to use in the event that
@@ -56,44 +147,136 @@
      *    is unavailable
      * @param phoneAccountHandle the phoneAccountHandle upon which to check the line one number
      * @return the line one number
-     * @throws NullPointerException if telecomManager or telephonyManager are null
      */
-    public static String getLine1Number(TelecomManager telecomManager,
-            TelephonyManager telephonyManager, @Nullable PhoneAccountHandle phoneAccountHandle) {
-        if (CompatUtils.isMarshmallowCompatible()) {
+    @Nullable
+    public static String getLine1Number(@Nullable TelecomManager telecomManager,
+            @Nullable TelephonyManager telephonyManager,
+            @Nullable PhoneAccountHandle phoneAccountHandle) {
+        if (telecomManager != null && CompatUtils.isMarshmallowCompatible()) {
             return telecomManager.getLine1Number(phoneAccountHandle);
         }
-        return telephonyManager.getLine1Number();
+        if (telephonyManager != null) {
+            return telephonyManager.getLine1Number();
+        }
+        return null;
     }
 
     /**
      * Return whether a given phone number is the configured voicemail number for a
      * particular phone account.
      *
-     * @param telecomManager the {@link TelecomManager} to use
+     * @param telecomManager the {@link TelecomManager} to use for checking the number.
      * @param accountHandle The handle for the account to check the voicemail number against
      * @param number The number to look up.
-     * @throws NullPointerException if telecomManager is null
      */
-    public static boolean isVoiceMailNumber(TelecomManager telecomManager,
+    public static boolean isVoiceMailNumber(@Nullable TelecomManager telecomManager,
             @Nullable PhoneAccountHandle accountHandle, @Nullable String number) {
-        if (CompatUtils.isMarshmallowCompatible()) {
+        if (telecomManager != null && (CompatUtils.isMarshmallowCompatible()
+                || CompatUtils.isMethodAvailable(TELECOM_MANAGER_CLASS, "isVoiceMailNumber",
+                        PhoneAccountHandle.class, String.class))) {
             return telecomManager.isVoiceMailNumber(accountHandle, number);
         }
         return PhoneNumberUtils.isVoiceMailNumber(number);
     }
 
     /**
+     * Return the {@link PhoneAccount} for a specified {@link PhoneAccountHandle}. Object includes
+     * resources which can be used in a user interface.
+     *
+     * @param telecomManager the {@link TelecomManager} used for method calls, if possible.
+     * @param account The {@link PhoneAccountHandle}.
+     * @return The {@link PhoneAccount} object or null if it doesn't exist.
+     */
+    @Nullable
+    public static PhoneAccount getPhoneAccount(@Nullable TelecomManager telecomManager,
+            @Nullable PhoneAccountHandle accountHandle) {
+        if (telecomManager != null && (CompatUtils.isMethodAvailable(
+                TELECOM_MANAGER_CLASS, "getPhoneAccount", PhoneAccountHandle.class))) {
+            return telecomManager.getPhoneAccount(accountHandle);
+        }
+        return null;
+    }
+
+    /**
+     * Return the voicemail number for a given phone account.
+     *
+     * @param telecomManager The {@link TelecomManager} object to use for retrieving the voicemail
+     * number if accountHandle is specified.
+     * @param telephonyManager The {@link TelephonyManager} object to use for retrieving the
+     * voicemail number if accountHandle is null.
+     * @param accountHandle The handle for the phone account.
+     * @return The voicemail number for the phone account, and {@code null} if one has not been
+     *         configured.
+     */
+    @Nullable
+    public static String getVoiceMailNumber(@Nullable TelecomManager telecomManager,
+            @Nullable TelephonyManager telephonyManager,
+            @Nullable PhoneAccountHandle accountHandle) {
+        if (telecomManager != null && (CompatUtils.isMethodAvailable(
+                TELECOM_MANAGER_CLASS, "getVoiceMailNumber", PhoneAccountHandle.class))) {
+            return telecomManager.getVoiceMailNumber(accountHandle);
+        } else if (telephonyManager != null){
+            return telephonyManager.getVoiceMailNumber();
+        }
+        return null;
+    }
+
+    /**
+     * Processes the specified dial string as an MMI code.
+     * MMI codes are any sequence of characters entered into the dialpad that contain a "*" or "#".
+     * Some of these sequences launch special behavior through handled by Telephony.
+     *
+     * @param telecomManager The {@link TelecomManager} object to use for handling MMI.
+     * @param dialString The digits to dial.
+     * @return {@code true} if the digits were processed as an MMI code, {@code false} otherwise.
+     */
+    public static boolean handleMmi(@Nullable TelecomManager telecomManager,
+            @Nullable String dialString, @Nullable PhoneAccountHandle accountHandle) {
+        if (telecomManager == null || TextUtils.isEmpty(dialString)) {
+            return false;
+        }
+        if (CompatUtils.isMarshmallowCompatible()) {
+            return telecomManager.handleMmi(dialString, accountHandle);
+        }
+
+        Object handleMmiResult = CompatUtils.invokeMethod(
+                telecomManager,
+                "handleMmi",
+                new Class<?>[] {PhoneAccountHandle.class, String.class},
+                new Object[] {accountHandle, dialString});
+        if (handleMmiResult != null) {
+            return (boolean) handleMmiResult;
+        }
+
+        return telecomManager.handleMmi(dialString);
+    }
+
+    /**
      * Silences the ringer if a ringing call exists. Noop if {@link TelecomManager#silenceRinger()}
      * is unavailable.
      *
-     * @param telecomManager the {@link TelecomManager} to use to silence the ringer
-     * @throws NullPointerException if telecomManager is null
+     * @param telecomManager the TelecomManager to use to silence the ringer.
      */
-    public static void silenceRinger(TelecomManager telecomManager) {
-        if (CompatUtils.isMarshmallowCompatible() || CompatUtils
-                .isMethodAvailable("android.telecom.TelecomManager", "silenceRinger")) {
+    public static void silenceRinger(@Nullable TelecomManager telecomManager) {
+        if (telecomManager != null && (CompatUtils.isMarshmallowCompatible() || CompatUtils
+                .isMethodAvailable(TELECOM_MANAGER_CLASS, "silenceRinger"))) {
             telecomManager.silenceRinger();
         }
     }
+
+    /**
+     * Returns the current SIM call manager. Apps must be prepared for this method to return null,
+     * indicating that there currently exists no registered SIM call manager.
+     *
+     * @param telecomManager the {@link TelecomManager} to use to fetch the SIM call manager.
+     * @return The phone account handle of the current sim call manager.
+     */
+    @Nullable
+    public static PhoneAccountHandle getSimCallManager(TelecomManager telecomManager) {
+        if (telecomManager != null && (CompatUtils.isMarshmallowCompatible() || CompatUtils
+                .isMethodAvailable(TELECOM_MANAGER_CLASS, "getSimCallManager"))) {
+            return telecomManager.getSimCallManager();
+        }
+        return null;
+    }
 }
diff --git a/src/com/android/contacts/common/dialog/CallSubjectDialog.java b/src/com/android/contacts/common/dialog/CallSubjectDialog.java
index ecaa1a5..3c08b37 100644
--- a/src/com/android/contacts/common/dialog/CallSubjectDialog.java
+++ b/src/com/android/contacts/common/dialog/CallSubjectDialog.java
@@ -48,6 +48,7 @@
 import com.android.contacts.common.ContactPhotoManager;
 import com.android.contacts.common.R;
 import com.android.contacts.common.compat.CompatUtils;
+import com.android.contacts.common.compat.telecom.TelecomManagerCompat;
 import com.android.contacts.common.util.UriUtils;
 import com.android.phone.common.animation.AnimUtils;
 
@@ -161,9 +162,10 @@
             Intent intent = CallUtil.getCallWithSubjectIntent(mNumber, mPhoneAccountHandle,
                     subject);
 
-            final TelecomManager tm =
-                    (TelecomManager) getSystemService(Context.TELECOM_SERVICE);
-            tm.placeCall(intent.getData(), intent.getExtras());
+            TelecomManagerCompat.placeCall(
+                    CallSubjectDialog.this,
+                    (TelecomManager) getSystemService(Context.TELECOM_SERVICE),
+                    intent);
 
             mSubjectHistory.add(subject);
             saveSubjectHistory(mSubjectHistory);
diff --git a/src/com/android/contacts/common/list/ContactListItemView.java b/src/com/android/contacts/common/list/ContactListItemView.java
index 10a4a99..18e82ad 100644
--- a/src/com/android/contacts/common/list/ContactListItemView.java
+++ b/src/com/android/contacts/common/list/ContactListItemView.java
@@ -36,6 +36,7 @@
 import android.text.TextUtils;
 import android.text.TextUtils.TruncateAt;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.util.TypedValue;
 import android.view.Gravity;
 import android.view.MotionEvent;
@@ -84,6 +85,8 @@
 public class ContactListItemView extends ViewGroup
         implements SelectionBoundsAdjuster {
 
+    private static final String TAG = "ContactListItemView";
+
     // Style values for layout and appearance
     // The initialized values are defaults if none is provided through xml.
     private int mPreferredHeight = 0;
@@ -96,6 +99,8 @@
     private int mNameTextViewTextSize;
     private int mHeaderWidth;
     private Drawable mActivatedBackgroundDrawable;
+    private int mVideoCallIconSize = 32;
+    private int mVideoCallIconMargin = 16;
 
     // Set in onLayout. Represent left and right position of the View on the screen.
     private int mLeftOffset;
@@ -127,6 +132,21 @@
     private String mHighlightedPrefix;
 
     /**
+     * Used to notify listeners when a video call icon is clicked.
+     */
+    private PhoneNumberListAdapter.Listener mPhoneNumberListAdapterListener;
+
+    /**
+     * Indicates whether to show the "video call" icon, used to initiate a video call.
+     */
+    private boolean mShowVideoCallIcon = false;
+
+    /**
+     * Indicates whether the view should leave room for the "video call" icon.
+     */
+    private boolean mSupportVideoCallIcon = false;
+
+    /**
      * Where to put contact photo. This affects the other Views' layout or look-and-feel.
      *
      * TODO: replace enum with int constants
@@ -166,11 +186,10 @@
     private TextView mStatusView;
     private ImageView mPresenceIcon;
     private CheckBox mCheckBox;
+    private ImageView mVideoCallIcon;
 
     private ColorStateList mSecondaryTextColor;
 
-
-
     private int mDefaultPhotoViewSize = 0;
     /**
      * Can be effective even when {@link #mPhotoView} is null, as we want to have horizontal padding
@@ -230,6 +249,7 @@
     /** A helper used to highlight a prefix in a text field. */
     private final TextHighlighter mTextHighlighter;
     private CharSequence mUnknownNameText;
+    private int mPosition;
 
     public ContactListItemView(Context context) {
         super(context);
@@ -239,6 +259,12 @@
         mNumberHighlightSequence = new ArrayList<HighlightSequence>();
     }
 
+    public ContactListItemView(Context context, AttributeSet attrs, boolean supportVideoCallIcon) {
+        this(context, attrs);
+
+        mSupportVideoCallIcon = supportVideoCallIcon;
+    }
+
     public ContactListItemView(Context context, AttributeSet attrs) {
         super(context, attrs);
 
@@ -282,6 +308,13 @@
             mNameTextViewTextSize = (int) a.getDimension(
                     R.styleable.ContactListItemView_list_item_name_text_size,
                     (int) getResources().getDimension(R.dimen.contact_browser_list_item_text_size));
+            mVideoCallIconSize = a.getDimensionPixelOffset(
+                    R.styleable.ContactListItemView_list_item_video_call_icon_size,
+                    mVideoCallIconSize);
+            mVideoCallIconMargin = a.getDimensionPixelOffset(
+                    R.styleable.ContactListItemView_list_item_video_call_icon_margin,
+                    mVideoCallIconMargin);
+
 
             setPaddingRelative(
                     a.getDimensionPixelOffset(
@@ -325,6 +358,59 @@
         mQuickContactEnabled = flag;
     }
 
+    /**
+     * Sets whether the video calling icon is shown.  For the video calling icon to be shown,
+     * {@link #mSupportVideoCallIcon} must be {@code true}.
+     *
+     * @param showVideoCallIcon {@code true} if the video calling icon is shown, {@code false}
+     *      otherwise.
+     * @param listener Listener to notify when the video calling icon is clicked.
+     * @param position The position in the adapater of the video calling icon.
+     */
+    public void setShowVideoCallIcon(boolean showVideoCallIcon,
+            PhoneNumberListAdapter.Listener listener, int position) {
+        mShowVideoCallIcon = showVideoCallIcon;
+        mPhoneNumberListAdapterListener = listener;
+        mPosition = position;
+
+        if (mShowVideoCallIcon) {
+            if (mVideoCallIcon == null) {
+                mVideoCallIcon = new ImageView(getContext());
+                addView(mVideoCallIcon);
+            }
+            mVideoCallIcon.setContentDescription(getContext().getString(
+                    R.string.description_search_video_call));
+            mVideoCallIcon.setImageResource(R.drawable.ic_search_video_call);
+            mVideoCallIcon.setScaleType(ScaleType.CENTER);
+            mVideoCallIcon.setVisibility(View.VISIBLE);
+            mVideoCallIcon.setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    // Inform the adapter that the video calling icon was clicked.
+                    if (mPhoneNumberListAdapterListener != null) {
+                        mPhoneNumberListAdapterListener.onVideoCallIconClicked(mPosition);
+                    }
+                }
+            });
+        } else {
+            if (mVideoCallIcon != null) {
+                mVideoCallIcon.setVisibility(View.GONE);
+            }
+        }
+    }
+
+    /**
+     * Sets whether the view supports a video calling icon.  This is independent of whether the view
+     * is actually showing an icon.  Support for the video calling icon ensures that the layout
+     * leaves space for the video icon, should it be shown.
+     *
+     * @param supportVideoCallIcon {@code true} if the video call icon is supported, {@code false}
+     *      otherwise.
+     */
+    public void setSupportVideoCallIcon(boolean supportVideoCallIcon) {
+        mSupportVideoCallIcon = supportVideoCallIcon;
+    }
+
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         // We will match parent's width and wrap content vertically, but make sure
@@ -358,6 +444,10 @@
             effectiveWidth -= mHeaderWidth + mGapBetweenImageAndText;
         }
 
+        if (mSupportVideoCallIcon) {
+            effectiveWidth -= (mVideoCallIconSize + mVideoCallIconMargin);
+        }
+
         // Go over all visible text views and measure actual width of each of them.
         // Also calculate their heights to get the total height for this entire view.
 
@@ -420,11 +510,7 @@
         }
 
         if (isVisible(mLabelView)) {
-            // For performance reason we don't want AT_MOST usually, but when the picture is
-            // on right, we need to use it anyway because mDataView is next to mLabelView.
-            final int mode = (mPhotoPosition == PhotoPosition.LEFT
-                    ? MeasureSpec.EXACTLY : MeasureSpec.AT_MOST);
-            mLabelView.measure(MeasureSpec.makeMeasureSpec(labelWidth, mode),
+            mLabelView.measure(MeasureSpec.makeMeasureSpec(labelWidth, MeasureSpec.AT_MOST),
                     MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
             mLabelViewHeight = mLabelView.getMeasuredHeight();
         }
@@ -445,6 +531,12 @@
             mStatusTextViewHeight = mPresenceIcon.getMeasuredHeight();
         }
 
+        if (mSupportVideoCallIcon && isVisible(mVideoCallIcon)) {
+            mVideoCallIcon.measure(
+                    MeasureSpec.makeMeasureSpec(mVideoCallIconSize, MeasureSpec.EXACTLY),
+                    MeasureSpec.makeMeasureSpec(mVideoCallIconSize, MeasureSpec.EXACTLY));
+        }
+
         if (isVisible(mStatusView)) {
             // Presence and status are in a same row, so status will be affected by icon size.
             final int statusWidth;
@@ -579,6 +671,36 @@
             leftBound += mTextIndent;
         }
 
+        if (mSupportVideoCallIcon) {
+            // Place the video call button at the end of the list (e.g. take into account RTL mode).
+            if (isVisible(mVideoCallIcon)) {
+                // Center the video icon vertically
+                final int videoIconTop = topBound +
+                        (bottomBound - topBound - mVideoCallIconSize) / 2;
+
+                if (!isLayoutRtl) {
+                    // When photo is on left, video icon is placed on the right edge.
+                    mVideoCallIcon.layout(rightBound - mVideoCallIconSize,
+                            videoIconTop,
+                            rightBound,
+                            videoIconTop + mVideoCallIconSize);
+                } else {
+                    // When photo is on right, video icon is placed on the left edge.
+                    mVideoCallIcon.layout(leftBound,
+                            videoIconTop,
+                            leftBound + mVideoCallIconSize,
+                            videoIconTop + mVideoCallIconSize);
+                }
+            }
+
+            if (mPhotoPosition == PhotoPosition.LEFT) {
+                rightBound -= (mVideoCallIconSize + mVideoCallIconMargin);
+            } else {
+                leftBound += mVideoCallIconSize + mVideoCallIconMargin;
+            }
+        }
+
+
         // Center text vertically, then apply the top offset.
         final int totalTextHeight = mNameTextViewHeight + mPhoneticNameTextViewHeight +
                 mLabelAndDataViewMaxHeight + mSnippetTextViewHeight + mStatusTextViewHeight;
@@ -658,29 +780,34 @@
 
         // Label and Data align bottom.
         if (isVisible(mLabelView)) {
-            if (mPhotoPosition == PhotoPosition.LEFT) {
-                // When photo is on left, label is placed on the right edge of the list item.
+            if (!isLayoutRtl) {
+                mLabelView.layout(dataLeftBound,
+                        textTopBound + mLabelAndDataViewMaxHeight - mLabelViewHeight,
+                        rightBound,
+                        textTopBound + mLabelAndDataViewMaxHeight);
+                dataLeftBound += mLabelView.getMeasuredWidth() + mGapBetweenLabelAndData;
+            } else {
+                dataLeftBound = leftBound + mLabelView.getMeasuredWidth();
                 mLabelView.layout(rightBound - mLabelView.getMeasuredWidth(),
                         textTopBound + mLabelAndDataViewMaxHeight - mLabelViewHeight,
                         rightBound,
                         textTopBound + mLabelAndDataViewMaxHeight);
-                rightBound -= mLabelView.getMeasuredWidth();
-            } else {
-                // When photo is on right, label is placed on the left of data view.
-                dataLeftBound = leftBound + mLabelView.getMeasuredWidth();
-                mLabelView.layout(leftBound,
-                        textTopBound + mLabelAndDataViewMaxHeight - mLabelViewHeight,
-                        dataLeftBound,
-                        textTopBound + mLabelAndDataViewMaxHeight);
-                dataLeftBound += mGapBetweenLabelAndData;
+                rightBound -= (mLabelView.getMeasuredWidth() + mGapBetweenLabelAndData);
             }
         }
 
         if (isVisible(mDataView)) {
-            mDataView.layout(dataLeftBound,
-                    textTopBound + mLabelAndDataViewMaxHeight - mDataViewHeight,
-                    rightBound,
-                    textTopBound + mLabelAndDataViewMaxHeight);
+            if (!isLayoutRtl) {
+                mDataView.layout(dataLeftBound,
+                        textTopBound + mLabelAndDataViewMaxHeight - mDataViewHeight,
+                        rightBound,
+                        textTopBound + mLabelAndDataViewMaxHeight);
+            } else {
+                mDataView.layout(rightBound - mDataView.getMeasuredWidth(),
+                        textTopBound + mLabelAndDataViewMaxHeight - mDataViewHeight,
+                        rightBound,
+                        textTopBound + mLabelAndDataViewMaxHeight);
+            }
         }
         if (isVisible(mLabelView) || isVisible(mDataView)) {
             textTopBound += mLabelAndDataViewMaxHeight;
@@ -981,12 +1108,14 @@
     public TextView getLabelView() {
         if (mLabelView == null) {
             mLabelView = new TextView(getContext());
+            mLabelView.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
+                    LayoutParams.WRAP_CONTENT));
+
             mLabelView.setSingleLine(true);
             mLabelView.setEllipsize(getTextEllipsis());
             mLabelView.setTextAppearance(getContext(), R.style.TextAppearanceSmall);
             if (mPhotoPosition == PhotoPosition.LEFT) {
                 mLabelView.setAllCaps(true);
-                mLabelView.setGravity(Gravity.END);
             } else {
                 mLabelView.setTypeface(mLabelView.getTypeface(), Typeface.BOLD);
             }
diff --git a/src/com/android/contacts/common/list/OnPhoneNumberPickerActionListener.java b/src/com/android/contacts/common/list/OnPhoneNumberPickerActionListener.java
index 819736b..fe23054 100644
--- a/src/com/android/contacts/common/list/OnPhoneNumberPickerActionListener.java
+++ b/src/com/android/contacts/common/list/OnPhoneNumberPickerActionListener.java
@@ -28,7 +28,7 @@
     /**
      * Returns the selected phone number uri to the requester.
      */
-    void onPickDataUri(Uri dataUri, int callInitiationType);
+    void onPickDataUri(Uri dataUri, boolean isVideoCall, int callInitiationType);
 
     /**
      * Returns the specified phone number to the requester.
diff --git a/src/com/android/contacts/common/list/PhoneNumberListAdapter.java b/src/com/android/contacts/common/list/PhoneNumberListAdapter.java
index fc99c1d..fc90fbf 100644
--- a/src/com/android/contacts/common/list/PhoneNumberListAdapter.java
+++ b/src/com/android/contacts/common/list/PhoneNumberListAdapter.java
@@ -33,8 +33,8 @@
 import android.view.View;
 import android.view.ViewGroup;
 
+import com.android.contacts.common.CallUtil;
 import com.android.contacts.common.ContactPhotoManager.DefaultImageRequest;
-import com.android.contacts.common.ContactsUtils;
 import com.android.contacts.common.GeoUtil;
 import com.android.contacts.common.R;
 import com.android.contacts.common.compat.CallableCompat;
@@ -60,6 +60,10 @@
 
     private static final String TAG = PhoneNumberListAdapter.class.getSimpleName();
 
+    public interface Listener {
+        void onVideoCallIconClicked(int position);
+    }
+
     // A list of extended directories to add to the directories from the database
     private final List<DirectoryPartition> mExtendedDirectories;
 
@@ -98,6 +102,7 @@
             Phone.PHOTO_ID,                     // 6
             Phone.DISPLAY_NAME_PRIMARY,         // 7
             Phone.PHOTO_THUMBNAIL_URI,          // 8
+            Phone.CARRIER_PRESENCE,             // 9
         };
 
         public static final String[] PROJECTION_ALTERNATIVE = new String[] {
@@ -110,6 +115,7 @@
             Phone.PHOTO_ID,                     // 6
             Phone.DISPLAY_NAME_ALTERNATIVE,     // 7
             Phone.PHOTO_THUMBNAIL_URI,          // 8
+            Phone.CARRIER_PRESENCE,             // 9
         };
 
         public static final int PHONE_ID                = 0;
@@ -121,6 +127,7 @@
         public static final int PHOTO_ID                = 6;
         public static final int DISPLAY_NAME            = 7;
         public static final int PHOTO_URI               = 8;
+        public static final int CARRIER_PRESENCE        = 9;
     }
 
     private static final String IGNORE_NUMBER_TOO_LONG_CLAUSE =
@@ -133,6 +140,11 @@
 
     private boolean mUseCallableUri;
 
+    private Listener mListener;
+
+    private boolean mIsVideoEnabled;
+    private boolean mIsPresenceEnabled;
+
     public PhoneNumberListAdapter(Context context) {
         super(context);
         setDefaultFilterHeaderText(R.string.list_filter_phones);
@@ -147,6 +159,10 @@
             // Empty list to avoid sticky NPE's
             mExtendedDirectories = new ArrayList<DirectoryPartition>();
         }
+
+        int videoCapabilities = CallUtil.getVideoCallingAvailability(context);
+        mIsVideoEnabled = (videoCapabilities & CallUtil.VIDEO_CALLING_ENABLED) != 0;
+        mIsPresenceEnabled = (videoCapabilities & CallUtil.VIDEO_CALLING_PRESENCE) != 0;
     }
 
     protected CharSequence getUnknownNameText() {
@@ -413,10 +429,11 @@
         }
 
         final DirectoryPartition directory = (DirectoryPartition) getPartition(partition);
-        bindPhoneNumber(view, cursor, directory.isDisplayNumber());
+        bindPhoneNumber(view, cursor, directory.isDisplayNumber(), position);
     }
 
-    protected void bindPhoneNumber(ContactListItemView view, Cursor cursor, boolean displayNumber) {
+    protected void bindPhoneNumber(ContactListItemView view, Cursor cursor, boolean displayNumber,
+            int position) {
         CharSequence label = null;
         if (displayNumber &&  !cursor.isNull(PhoneQuery.PHONE_TYPE)) {
             final int type = cursor.getInt(PhoneQuery.PHONE_TYPE);
@@ -440,6 +457,14 @@
             }
         }
         view.setPhoneNumber(text, mCountryIso);
+
+        // Determine if carrier presence indicates the number supports video calling.
+        int carrierPresence = cursor.getInt(PhoneQuery.CARRIER_PRESENCE);
+        boolean isPresent = (carrierPresence & Phone.CARRIER_PRESENCE_VT_CAPABLE) != 0;
+
+        boolean isVideoIconShown = mIsVideoEnabled && (
+                mIsPresenceEnabled && isPresent || !mIsPresenceEnabled);
+        view.setShowVideoCallIcon(isVideoIconShown, mListener, position);
     }
 
     protected void bindSectionHeaderAndDivider(final ContactListItemView view, int position) {
@@ -576,4 +601,12 @@
                 .encodedFragment(cursor.getString(lookUpKeyColumn))
                 .build();
     }
+
+    public Listener getListener() {
+        return mListener;
+    }
+
+    public void setListener(Listener listener) {
+        mListener = listener;
+    }
 }
diff --git a/src/com/android/contacts/common/list/PhoneNumberPickerFragment.java b/src/com/android/contacts/common/list/PhoneNumberPickerFragment.java
index 3788a97..aa99155 100644
--- a/src/com/android/contacts/common/list/PhoneNumberPickerFragment.java
+++ b/src/com/android/contacts/common/list/PhoneNumberPickerFragment.java
@@ -40,7 +40,7 @@
  * Fragment containing a phone number list for picking.
  */
 public class PhoneNumberPickerFragment extends ContactEntryListFragment<ContactEntryListAdapter>
-        implements OnShortcutIntentCreatedListener {
+        implements OnShortcutIntentCreatedListener, PhoneNumberListAdapter.Listener {
     private static final String TAG = PhoneNumberPickerFragment.class.getSimpleName();
 
     private static final int REQUEST_CODE_ACCOUNT_FILTER = 1;
@@ -69,6 +69,16 @@
     private ContactListItemView.PhotoPosition mPhotoPosition =
             ContactListItemView.getDefaultPhotoPosition(false /* normal/non opposite */);
 
+    /**
+     * Handles a click on the video call icon for a row in the list.
+     *
+     * @param position The position in the list where the click ocurred.
+     */
+    @Override
+    public void onVideoCallIconClicked(int position) {
+        callNumber(position, true /* isVideoCall */);
+    }
+
     private class FilterHeaderClickListener implements OnClickListener {
         @Override
         public void onClick(View view) {
@@ -187,15 +197,26 @@
 
     @Override
     protected void onItemClick(int position, long id) {
+        callNumber(position, false /* isVideoCall */);
+    }
+
+    /**
+     * Initiates a call to the number at the specified position.
+     *
+     * @param position The position.
+     * @param isVideoCall {@code true} if the call should be initiated as a video call,
+     *      {@code false} otherwise.
+     */
+    private void callNumber(int position, boolean isVideoCall) {
         final Uri phoneUri = getPhoneUri(position);
 
         if (phoneUri != null) {
-            pickPhoneNumber(phoneUri);
+            pickPhoneNumber(phoneUri, isVideoCall);
         } else {
             final String number = getPhoneNumber(position);
             if (!TextUtils.isEmpty(number)) {
                 cacheContactInfo(position);
-                mListener.onPickPhoneNumber(number, false /* isVideoCall */,
+                mListener.onPickPhoneNumber(number, isVideoCall,
                         getCallInitiationType(true /* isRemoteDirectory */));
             } else {
                 Log.w(TAG, "Item at " + position + " was clicked before"
@@ -284,16 +305,16 @@
         return inflater.inflate(R.layout.contact_list_content, null);
     }
 
-    public void pickPhoneNumber(Uri uri) {
+    public void pickPhoneNumber(Uri uri, boolean isVideoCall) {
         if (mShortcutAction == null) {
-            mListener.onPickDataUri(uri,
+            mListener.onPickDataUri(uri, isVideoCall,
                     getCallInitiationType(false /* isRemoteDirectory */));
         } else {
-            startPhoneNumberShortcutIntent(uri);
+            startPhoneNumberShortcutIntent(uri, isVideoCall);
         }
     }
 
-    protected void startPhoneNumberShortcutIntent(Uri uri) {
+    protected void startPhoneNumberShortcutIntent(Uri uri, boolean isVideoCall) {
         ShortcutIntentBuilder builder = new ShortcutIntentBuilder(getActivity(), this);
         builder.createPhoneNumberShortcutIntent(uri, mShortcutAction);
     }
@@ -305,7 +326,7 @@
 
     @Override
     public void onPickerResult(Intent data) {
-        mListener.onPickDataUri(data.getData(),
+        mListener.onPickDataUri(data.getData(), false /* isVideoCall */,
                 getCallInitiationType(false /* isRemoteDirectory */));
     }
 
diff --git a/src/com/android/contacts/common/model/ContactLoader.java b/src/com/android/contacts/common/model/ContactLoader.java
index 59ab292..923e2f9 100644
--- a/src/com/android/contacts/common/model/ContactLoader.java
+++ b/src/com/android/contacts/common/model/ContactLoader.java
@@ -184,6 +184,7 @@
 
                 Data.TIMES_USED,
                 Data.LAST_TIME_USED,
+                Data.CARRIER_PRESENCE
         };
 
         public static final int NAME_RAW_CONTACT_ID = 0;
@@ -256,6 +257,7 @@
 
         public static final int TIMES_USED = 62;
         public static final int LAST_TIME_USED = 63;
+        public static final int CARRIER_PRESENCE = 64;
     }
 
     /**
@@ -714,6 +716,7 @@
         cursorColumnToContentValues(cursor, cv, ContactQuery.CHAT_CAPABILITY);
         cursorColumnToContentValues(cursor, cv, ContactQuery.TIMES_USED);
         cursorColumnToContentValues(cursor, cv, ContactQuery.LAST_TIME_USED);
+        cursorColumnToContentValues(cursor, cv, ContactQuery.CARRIER_PRESENCE);
 
         return cv;
     }
diff --git a/src/com/android/contacts/common/model/dataitem/DataItem.java b/src/com/android/contacts/common/model/dataitem/DataItem.java
index 48ca0e5..780f4f5 100644
--- a/src/com/android/contacts/common/model/dataitem/DataItem.java
+++ b/src/com/android/contacts/common/model/dataitem/DataItem.java
@@ -146,6 +146,16 @@
     }
 
     /**
+     * Indicates the carrier presence value for the current {@link DataItem}.
+     *
+     * @return {@link Data#CARRIER_PRESENCE_VT_CAPABLE} if the {@link DataItem} supports carrier
+     *      video calling, {@code 0} otherwise.
+     */
+    public int getCarrierPresence() {
+        return mContentValues.getAsInteger(Data.CARRIER_PRESENCE);
+    }
+
+    /**
      * This builds the data string depending on the type of data item by using the generic
      * DataKind object underneath.
      */
diff --git a/tests/src/com/android/contacts/common/compat/CompatUtilsTest.java b/tests/src/com/android/contacts/common/compat/CompatUtilsTest.java
index eaacbba..3386a00 100644
--- a/tests/src/com/android/contacts/common/compat/CompatUtilsTest.java
+++ b/tests/src/com/android/contacts/common/compat/CompatUtilsTest.java
@@ -80,15 +80,58 @@
                 Boolean.TYPE));
     }
 
+    public void testInvokeMethod_NullMethodName() {
+        assertNull(CompatUtils.invokeMethod(new BaseClass(), null, null, null));
+    }
+
+    public void testInvokeMethod_EmptyMethodName() {
+        assertNull(CompatUtils.invokeMethod(new BaseClass(), "", null, null));
+    }
+
+    public void testInvokeMethod_NullClassInstance() {
+        assertNull(CompatUtils.invokeMethod(null, "", null, null));
+    }
+
+    public void testInvokeMethod_NonexistentMethod() {
+        assertNull(CompatUtils.invokeMethod(new BaseClass(), "derivedMethod", null, null));
+    }
+
+    public void testInvokeMethod_MethodWithNoParameters() {
+        assertEquals(1, CompatUtils.invokeMethod(new DerivedClass(), "overloadedMethod", null, null));
+    }
+
+    public void testInvokeMethod_MethodWithNoParameters_WithParameters() {
+        assertNull(CompatUtils.invokeMethod(new DerivedClass(), "derivedMethod",
+                new Class<?>[] {Integer.TYPE}, new Object[] {1}));
+    }
+
+    public void testInvokeMethod_MethodWithParameters_WithEmptyParameterList() {
+        assertNull(CompatUtils.invokeMethod(new DerivedClass(), "overloadedMethod",
+                new Class<?>[] {Integer.TYPE}, new Object[] {}));
+    }
+
+    public void testInvokeMethod_InvokeSimpleMethod() {
+        assertEquals(2, CompatUtils.invokeMethod(new DerivedClass(), "overloadedMethod",
+                new Class<?>[] {Integer.TYPE}, new Object[] {2}));
+    }
+
     private class BaseClass {
         public void baseMethod() {}
     }
 
     private class DerivedClass extends BaseClass {
-        public void derivedMethod() {}
+        public int derivedMethod() {
+            // This method needs to return something to differentiate a successful invocation from
+            // an unsuccessful one.
+            return 0;
+        }
 
-        public void overloadedMethod() {}
+        public int overloadedMethod() {
+            return 1;
+        }
 
-        public void overloadedMethod(int i) {}
+        public int overloadedMethod(int i) {
+            return i;
+        }
     }
 }
diff --git a/tests/src/com/android/contacts/common/model/ContactLoaderTest.java b/tests/src/com/android/contacts/common/model/ContactLoaderTest.java
index 1c65d7b..094e51c 100644
--- a/tests/src/com/android/contacts/common/model/ContactLoaderTest.java
+++ b/tests/src/com/android/contacts/common/model/ContactLoaderTest.java
@@ -154,6 +154,7 @@
         assertEquals(lookupUri, contact.getLookupUri());
         assertEquals(1, contact.getRawContacts().size());
         assertEquals(1, contact.getStatuses().size());
+        assertEquals(1, contact.getRawContacts().get(0).getDataItems().get(0).getCarrierPresence());
         mContactsProvider.verify();
     }
 
@@ -342,6 +343,7 @@
 
                         Data.TIMES_USED,
                         Data.LAST_TIME_USED,
+                        Data.CARRIER_PRESENCE
                     })
                     .withSortOrder(Contacts.Entity.RAW_CONTACT_ID)
                     .returnRow(
@@ -382,7 +384,8 @@
                         0,
 
                         0,
-                        0
+                        0,
+                        Data.CARRIER_PRESENCE_VT_CAPABLE
                     );
         }