Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 78384189 authored by Nancy Chen's avatar Nancy Chen
Browse files

Make TelecomManager APIs compatible with Lollipop (2/3)

+ Move TelecomManagerCompat to ContactsCommon because it is called by
  CallSubjectDialog
+ Move isDefaultDialerCompatible to CompatUtils because it is called in
  TelecomManagerCompat
+ Add invokeMethod method to CompatUtils
+ Use TELEPHONY_MANAGER_CLASS and TELECOM_MANAGER_CLASS constants
+ Add @Nullable annotations

Bug: 25776171

Change-Id: I91ebaf59fa8234e52aeac733c424bd4bdfc6d8a2
parent bbf3596e
Loading
Loading
Loading
Loading
+47 −0
Original line number Diff line number Diff line
@@ -22,6 +22,9 @@ import android.util.Log;

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();
@@ -89,6 +92,16 @@ public final class CompatUtils {
                >= Build.VERSION_CODES.M;
    }

    /**
     * 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 +168,7 @@ public final class CompatUtils {
            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 + "#"
@@ -163,6 +177,39 @@ public final class CompatUtils {
        }
    }

    /**
     * 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.
+8 −7
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import android.support.annotation.Nullable;
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 @@ public class TelephonyManagerCompat {
        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 @@ public class TelephonyManagerCompat {
            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 @@ public class TelephonyManagerCompat {
            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 @@ public class TelephonyManagerCompat {
            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 @@ public class TelephonyManagerCompat {
            return false;
        }
        if (CompatUtils.isMarshmallowCompatible()
                || CompatUtils.isMethodAvailable("android.telephony.TelephonyManager",
                        "isTtyModeSupported")) {
                || CompatUtils.isMethodAvailable(TELEPHONY_MANAGER_CLASS,
                        "isHearingAidCompatibilitySupported")) {
            return telephonyManager.isHearingAidCompatibilitySupported();
        }
        return false;
+186 −17
Original line number Diff line number Diff line
@@ -17,19 +17,29 @@ package com.android.contacts.common.compat.telecom;

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 android.util.Log;

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 +47,12 @@ public class TelecomManagerCompat {
     * @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;
@@ -47,6 +60,86 @@ public class TelecomManagerCompat {
        activity.startActivityForResult(intent, 0);
    }

    /**
     * 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.
     *
@@ -56,15 +149,19 @@ public class TelecomManagerCompat {
     *    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);
        }
        if (telephonyManager != null) {
            return telephonyManager.getLine1Number();
        }
        return null;
    }

    /**
     * Return whether a given phone number is the configured voicemail number for a
@@ -73,26 +170,98 @@ public class TelecomManagerCompat {
     * @param telecomManager the {@link TelecomManager} to use
     * @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 accountHandle The PhoneAccountHandle.
     * @return The 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();
        }
    }
+5 −3
Original line number Diff line number Diff line
@@ -48,6 +48,7 @@ import com.android.contacts.common.CallUtil;
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 @@ public class CallSubjectDialog extends Activity {
            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);
+48 −5
Original line number Diff line number Diff line
@@ -80,15 +80,58 @@ public class CompatUtilsTest extends AndroidTestCase {
                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;
        }
    }
}