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

Commit fefa2b93 authored by Brad Ebinger's avatar Brad Ebinger
Browse files

Implement CAPABILITY_EMERGENCY_PREFERRED capability

When a PhoneAccount has CAPABILITY_EMERGENCY_PREFERRED
defined and an emergency call is placed, override
the user's choice of PhoneAccount with this account.

Bug: 131203278
Test: atest TelecomUnitTests
Change-Id: I0381ffede54d0a662feb2dade0859032f200ea8a
parent 1a2f2b89
Loading
Loading
Loading
Loading
+134 −42
Original line number Diff line number Diff line
@@ -22,6 +22,8 @@ import android.telecom.Log;
import android.telecom.ParcelableConnection;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;

// TODO: Needed for move to system service: import com.android.internal.R;

@@ -29,6 +31,8 @@ import com.android.internal.annotations.VisibleForTesting;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
@@ -86,6 +90,28 @@ public class CreateConnectionProcessor implements CreateConnectionResponse {
        }
    }

    @VisibleForTesting
    public interface ITelephonyManagerAdapter {
        int getSubIdForPhoneAccount(Context context, PhoneAccount account);
        int getSlotIndex(int subId);
    }

    private ITelephonyManagerAdapter mTelephonyAdapter = new ITelephonyManagerAdapter() {
        @Override
        public int getSubIdForPhoneAccount(Context context, PhoneAccount account) {
            TelephonyManager manager = context.getSystemService(TelephonyManager.class);
            if (manager == null) {
                return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
            }
            return manager.getSubIdForPhoneAccount(account);
        }

        @Override
        public int getSlotIndex(int subId) {
            return SubscriptionManager.getSlotIndex(subId);
        }
    };

    private final Call mCall;
    private final ConnectionServiceRepository mRepository;
    private List<CallAttemptRecord> mAttemptRecords;
@@ -123,6 +149,11 @@ public class CreateConnectionProcessor implements CreateConnectionResponse {
        return mConnectionAttempt;
    }

    @VisibleForTesting
    public void setTelephonyManagerAdapter(ITelephonyManagerAdapter adapter) {
        mTelephonyAdapter = adapter;
    }

    @VisibleForTesting
    public void process() {
        Log.v(this, "process");
@@ -315,20 +346,8 @@ public class CreateConnectionProcessor implements CreateConnectionResponse {
    // do not find any SIM phone accounts with emergency capability.
    // It attempts to add any accounts with CAPABILITY_PLACE_EMERGENCY_CALLS even if
    // accounts are not SIM accounts.
    private void adjustAttemptsForEmergencyNoSimRequired(
            PhoneAccountHandle preferredPAH,
            List<PhoneAccount> allAccounts) {
        // First, possibly add the phone account that the user prefers.
        PhoneAccount preferredPA = mPhoneAccountRegistrar.getPhoneAccountUnchecked(preferredPAH);
        if (preferredPA != null
                && preferredPA.hasCapabilities(PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS)) {
            Log.i(this, "Will try account %s for emergency", preferredPA.getAccountHandle());
            mAttemptRecords.add(new CallAttemptRecord(preferredPAH, preferredPAH));
        }

        // Next, add all phone accounts which can place emergency calls.
        // If preferredPA already has an emergency PhoneAccount, do not add others since the
        // emergency call be redialed in Telephony.
    private void adjustAttemptsForEmergencyNoSimRequired(List<PhoneAccount> allAccounts) {
        // Add all phone accounts which can place emergency calls.
        if (mAttemptRecords.isEmpty()) {
            for (PhoneAccount phoneAccount : allAccounts) {
                if (phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS)) {
@@ -364,36 +383,24 @@ public class CreateConnectionProcessor implements CreateConnectionResponse {
                allAccounts.add(TelephonyUtil.getDefaultEmergencyPhoneAccount());
            }

            // First, possibly add the SIM phone account that the user prefers
            // Get user preferred PA if it exists.
            PhoneAccount preferredPA = mPhoneAccountRegistrar.getPhoneAccountUnchecked(
                    preferredPAH);
            if (preferredPA != null &&
                    preferredPA.hasCapabilities(PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS) &&
                    preferredPA.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
                Log.i(this, "Will try PSTN account %s for emergency",
                        preferredPA.getAccountHandle());
                mAttemptRecords.add(new CallAttemptRecord(preferredPAH, preferredPAH));
            }

            // Next, add all SIM phone accounts which can place emergency calls.
            TelephonyUtil.sortSimPhoneAccounts(mContext, allAccounts);

            // If preferredPA already has an emergency PhoneAccount, do not add others since the
            // emergency call be redialed in Telephony.
            if (mAttemptRecords.isEmpty()) {
            sortSimPhoneAccountsForEmergency(allAccounts, preferredPA);
            // and pick the fist one that can place emergency calls.
            for (PhoneAccount phoneAccount : allAccounts) {
                if (phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS)
                            && phoneAccount.hasCapabilities(
                            PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
                        && phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
                    PhoneAccountHandle phoneAccountHandle = phoneAccount.getAccountHandle();
                    Log.i(this, "Will try PSTN account %s for emergency", phoneAccountHandle);
                    mAttemptRecords.add(new CallAttemptRecord(phoneAccountHandle,
                            phoneAccountHandle));
                        // Add only one emergency SIM PhoneAccount to the attempt list.
                    // Add only one emergency SIM PhoneAccount to the attempt list, telephony will
                    // perform retries if the call fails.
                    break;
                }
            }
            }

            // Next, add the connection manager account as a backup if it can place emergency calls.
            PhoneAccountHandle callManagerHandle =
@@ -417,9 +424,9 @@ public class CreateConnectionProcessor implements CreateConnectionResponse {
            }

            if (mAttemptRecords.isEmpty()) {
                // Last best-effort attempt: choose any account with emergency capability even without
                // sim capability.
                adjustAttemptsForEmergencyNoSimRequired(preferredPAH, allAccounts);
                // Last best-effort attempt: choose any account with emergency capability even
                // without SIM capability.
                adjustAttemptsForEmergencyNoSimRequired(allAccounts);
            }
        }
    }
@@ -508,4 +515,89 @@ public class CreateConnectionProcessor implements CreateConnectionResponse {
        mLastErrorDisconnectCause = errorDisconnectCause;
        attemptNextPhoneAccount();
    }

    public void sortSimPhoneAccountsForEmergency(List<PhoneAccount> accounts,
            PhoneAccount userPreferredAccount) {
        // Sort the accounts according to how we want to display them (ascending order).
        accounts.sort((account1, account2) -> {
            int retval = 0;

            // SIM accounts go first
            boolean isSim1 = account1.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION);
            boolean isSim2 = account2.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION);
            if (isSim1 ^ isSim2) {
                return isSim1 ? -1 : 1;
            }

            // Start with the account that Telephony considers as the "emergency preferred"
            // account, which overrides the user's choice.
            boolean isSim1Preferred = account1.hasCapabilities(
                    PhoneAccount.CAPABILITY_EMERGENCY_PREFERRED);
            boolean isSim2Preferred = account2.hasCapabilities(
                    PhoneAccount.CAPABILITY_EMERGENCY_PREFERRED);
            // Perform XOR, we only sort if one is considered emergency preferred (should
            // always be the case).
            if (isSim1Preferred ^ isSim2Preferred) {
                return isSim1Preferred ? -1 : 1;
            }

            // Return the PhoneAccount associated with a valid logical slot.
            int subId1 = mTelephonyAdapter.getSubIdForPhoneAccount(mContext, account1);
            int subId2 = mTelephonyAdapter.getSubIdForPhoneAccount(mContext, account2);
            int slotId1 = (subId1 != SubscriptionManager.INVALID_SUBSCRIPTION_ID)
                    ? mTelephonyAdapter.getSlotIndex(subId1)
                    : SubscriptionManager.INVALID_SIM_SLOT_INDEX;
            int slotId2 = (subId2 != SubscriptionManager.INVALID_SUBSCRIPTION_ID)
                    ? mTelephonyAdapter.getSlotIndex(subId2)
                    : SubscriptionManager.INVALID_SIM_SLOT_INDEX;
            // Make sure both slots are valid, if one is not, prefer the one that is valid.
            if ((slotId1 == SubscriptionManager.INVALID_SIM_SLOT_INDEX) ^
                    (slotId2 == SubscriptionManager.INVALID_SIM_SLOT_INDEX)) {
                retval = (slotId1 != SubscriptionManager.INVALID_SIM_SLOT_INDEX) ? -1 : 1;
            }
            if (retval != 0) {
                return retval;
            }

            // Prefer the user's choice if all PhoneAccounts are associated with valid logical
            // slots.
            if (userPreferredAccount != null) {
                if (account1.equals(userPreferredAccount)) {
                    return -1;
                } else if (account2.equals(userPreferredAccount)) {
                    return 1;
                }
            }

            // because of the xor above, slotId1 and slotId2 are either both invalid or valid at
            // this point. If valid, prefer the lower slot index.
            if (slotId1 != SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
                // Assuming the slots are different, we should not have slotId1 == slotId2.
                return (slotId1 < slotId2) ? -1 : 1;
            }

            // Then order by package
            String pkg1 = account1.getAccountHandle().getComponentName().getPackageName();
            String pkg2 = account2.getAccountHandle().getComponentName().getPackageName();
            retval = pkg1.compareTo(pkg2);
            if (retval != 0) {
                return retval;
            }

            // then order by label
            String label1 = nullToEmpty(account1.getLabel().toString());
            String label2 = nullToEmpty(account2.getLabel().toString());
            retval = label1.compareTo(label2);
            if (retval != 0) {
                return retval;
            }

            // then by hashcode
            return account1.hashCode() - account2.hashCode();
        });
    }

    private static String nullToEmpty(String str) {
        return str == null ? "" : str;
    }
}
+0 −51
Original line number Diff line number Diff line
@@ -74,55 +74,4 @@ public final class TelephonyUtil {
                Context.TELEPHONY_SERVICE);
        return handle != null && tm.isEmergencyNumber(handle.getSchemeSpecificPart());
    }

    public static void sortSimPhoneAccounts(Context context, List<PhoneAccount> accounts) {
        final TelephonyManager telephonyManager = TelephonyManager.from(context);

        // Sort the accounts according to how we want to display them.
        Collections.sort(accounts, new Comparator<PhoneAccount>() {
            @Override
            public int compare(PhoneAccount account1, PhoneAccount account2) {
                int retval = 0;

                // SIM accounts go first
                boolean isSim1 = account1.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION);
                boolean isSim2 = account2.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION);
                if (isSim1 != isSim2) {
                    retval = isSim1 ? -1 : 1;
                }

                int subId1 = telephonyManager.getSubIdForPhoneAccount(account1);
                int subId2 = telephonyManager.getSubIdForPhoneAccount(account2);
                if (subId1 != SubscriptionManager.INVALID_SUBSCRIPTION_ID &&
                        subId2 != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
                    retval = (SubscriptionManager.getSlotIndex(subId1) <
                            SubscriptionManager.getSlotIndex(subId2)) ? -1 : 1;
                }

                // Then order by package
                if (retval == 0) {
                    String pkg1 = account1.getAccountHandle().getComponentName().getPackageName();
                    String pkg2 = account2.getAccountHandle().getComponentName().getPackageName();
                    retval = pkg1.compareTo(pkg2);
                }

                // Finally, order by label
                if (retval == 0) {
                    String label1 = nullToEmpty(account1.getLabel().toString());
                    String label2 = nullToEmpty(account2.getLabel().toString());
                    retval = label1.compareTo(label2);
                }

                // Then by hashcode
                if (retval == 0) {
                    retval = account1.hashCode() - account2.hashCode();
                }
                return retval;
            }
        });
    }

    private static String nullToEmpty(String str) {
        return str == null ? "" : str;
    }
}
+12 −1
Original line number Diff line number Diff line
@@ -199,6 +199,8 @@ public class ComponentContextFixture implements TestFixture<Context> {
                return Context.ROLE_SERVICE;
            } else if (svcClass == AudioManager.class) {
                return Context.AUDIO_SERVICE;
            } else if (svcClass == TelephonyManager.class) {
                return Context.TELEPHONY_SERVICE;
            }
            throw new UnsupportedOperationException();
        }
@@ -483,7 +485,11 @@ public class ComponentContextFixture implements TestFixture<Context> {
                matches(Manifest.permission.CALL_COMPANION_APP), anyString()))
                .thenReturn(PackageManager.PERMISSION_DENIED);

        when(mTelephonyManager.getSubIdForPhoneAccount((PhoneAccount) any())).thenReturn(1);
        // Used in CreateConnectionProcessor to rank emergency numbers by viability.
        // For the test, make them all equal to INVALID so that the preferred PhoneAccount will be
        // chosen.
        when(mTelephonyManager.getSubIdForPhoneAccount((PhoneAccount) any())).thenReturn(
                SubscriptionManager.INVALID_SUBSCRIPTION_ID);

        when(mTelephonyManager.getNetworkOperatorName()).thenReturn("label1");
        when(mTelephonyManager.getMultiSimConfiguration()).thenReturn(
@@ -502,6 +508,11 @@ public class ComponentContextFixture implements TestFixture<Context> {

        doReturn(null).when(mApplicationContextSpy).registerReceiver(any(BroadcastReceiver.class),
                any(IntentFilter.class));

        // Make sure we do not hide PII during testing.
        Log.setTag("TelecomTEST");
        Log.setIsExtendedLoggingEnabled(true);
        Log.VERBOSE = true;
    }

    @Override
+271 −28

File changed.

Preview size limit exceeded, changes collapsed.

+6 −0
Original line number Diff line number Diff line
@@ -230,6 +230,10 @@ public class EventManagerTest extends TelecomTestCase {
        if (Build.IS_USER) {
            return;
        }
        // Cache old setting for verbose
        boolean verboseBeforeTest = Log.VERBOSE;
        // testing PII, so turn off verbose.
        Log.VERBOSE = false;
        assertEquals("tel:**********12",
                Log.piiHandle(Uri.fromParts("tel", "+16505551212", null)));
        assertEquals("tel:*****12",
@@ -238,5 +242,7 @@ public class EventManagerTest extends TelecomTestCase {
                Log.piiHandle(Uri.fromParts("tel", "411", null)));
        assertEquals("tel:1",
                Log.piiHandle(Uri.fromParts("tel", "1", null)));
        // Restore old config.
        Log.VERBOSE = verboseBeforeTest;
    }
}
 No newline at end of file