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

Commit dc8d0152 authored by Malcolm Chen's avatar Malcolm Chen
Browse files

Add registrants for multi-SIM config change.

Add Telephony internal APIs to register / unregister for multi-SIM
configuration change, in PhoneConfigurationManager.

Bug: 142514392
Test: unittest
Change-Id: I9fa5077d68135c82e09760ae16abcb956d8235d6
Merged-In: I9fa5077d68135c82e09760ae16abcb956d8235d6
parent 32fc480e
Loading
Loading
Loading
Loading
+94 −40
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import android.os.AsyncResult;
import android.os.Handler;
import android.os.Message;
import android.os.PowerManager;
import android.os.RegistrantList;
import android.os.SystemProperties;
import android.os.storage.StorageManager;
import android.telephony.PhoneCapability;
@@ -32,6 +33,8 @@ import android.telephony.Rlog;
import android.telephony.TelephonyManager;
import android.util.Log;

import com.android.internal.annotations.VisibleForTesting;

import java.util.HashMap;
import java.util.Map;
import java.util.NoSuchElementException;
@@ -57,9 +60,12 @@ public class PhoneConfigurationManager {
    private final Context mContext;
    private PhoneCapability mStaticCapability;
    private final RadioConfig mRadioConfig;
    private final MainThreadHandler mHandler;
    private final Handler mHandler;
    private final Phone[] mPhones;
    private final Map<Integer, Boolean> mPhoneStatusMap;
    private MockableInterface mMi = new MockableInterface();
    private TelephonyManager mTelephonyManager;
    private static final RegistrantList sMultiSimConfigChangeRegistrants = new RegistrantList();

    /**
     * Init method to instantiate the object
@@ -83,11 +89,11 @@ public class PhoneConfigurationManager {
    private PhoneConfigurationManager(Context context) {
        mContext = context;
        // TODO: send commands to modem once interface is ready.
        TelephonyManager telephonyManager = new TelephonyManager(context);
        mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
        //initialize with default, it'll get updated when RADIO is ON/AVAILABLE
        mStaticCapability = getDefaultCapability();
        mRadioConfig = RadioConfig.getInstance(mContext);
        mHandler = new MainThreadHandler();
        mHandler = new ConfigManagerHandler();
        mPhoneStatusMap = new HashMap<>();

        notifyCapabilityChanged();
@@ -139,7 +145,7 @@ public class PhoneConfigurationManager {
    /**
     * Handler class to handle callbacks
     */
    private final class MainThreadHandler extends Handler {
    private final class ConfigManagerHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            AsyncResult ar;
@@ -163,7 +169,7 @@ public class PhoneConfigurationManager {
                    ar = (AsyncResult) msg.obj;
                    if (ar != null && ar.exception == null) {
                        int numOfLiveModems = msg.arg1;
                        setMultiSimProperties(numOfLiveModems);
                        onMultiSimConfigChanged(numOfLiveModems);
                    } else {
                        log(msg.what + " failure. Not switching multi-sim config." + ar.exception);
                    }
@@ -287,8 +293,7 @@ public class PhoneConfigurationManager {
     * Returns how many phone objects the device supports.
     */
    public int getPhoneCount() {
        TelephonyManager tm = new TelephonyManager(mContext);
        return tm.getPhoneCount();
        return mTelephonyManager.getActiveModemCount();
    }

    /**
@@ -301,6 +306,7 @@ public class PhoneConfigurationManager {
                    mHandler, EVENT_GET_PHONE_CAPABILITY_DONE);
            mRadioConfig.getPhoneCapability(callback);
        }
        log("getStaticPhoneCapability: mStaticCapability " + mStaticCapability);
        return mStaticCapability;
    }

@@ -348,41 +354,19 @@ public class PhoneConfigurationManager {
     * Return value defaults to true
     */
    public boolean isRebootRequiredForModemConfigChange() {
        String rebootRequired = SystemProperties.get(
                TelephonyProperties.PROPERTY_REBOOT_REQUIRED_ON_MODEM_CHANGE);
        log("isRebootRequiredForModemConfigChange: isRebootRequired = " + rebootRequired);
        return !rebootRequired.equals("false");
        return mMi.isRebootRequiredForModemConfigChange();
    }

    /**
     * Helper method to set system properties for setting multi sim configs,
     * as well as doing the phone reboot
     * NOTE: In order to support more than 3 sims, we need to change this method.
     * @param numOfSims number of active sims
     */
    private void setMultiSimProperties(int numOfSims) {
        String finalMultiSimConfig;
        switch(numOfSims) {
            case 3:
                finalMultiSimConfig = TSTS;
                break;
            case 2:
                finalMultiSimConfig = DSDS;
                break;
            default:
                finalMultiSimConfig = SSSS;
        }
    private void onMultiSimConfigChanged(int numOfActiveModems) {
        setMultiSimProperties(numOfActiveModems);

        SystemProperties.set(TelephonyProperties.PROPERTY_MULTI_SIM_CONFIG, finalMultiSimConfig);
        if (isRebootRequiredForModemConfigChange()) {
            log("setMultiSimProperties: Rebooting due to switching multi-sim config to "
                    + finalMultiSimConfig);
            log("onMultiSimConfigChanged: Rebooting.");
            PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
            pm.reboot("Switching to " + finalMultiSimConfig);
            pm.reboot("Multi-SIM config changed.");
        } else {
            log("setMultiSimProperties: Rebooting is not required to switch multi-sim config to "
                    + finalMultiSimConfig);
            broadcastSimSlotNumChange(numOfSims);
            log("onMultiSimConfigChanged: Rebooting is not required.");
            broadcastMultiSimConfigChange(numOfActiveModems);
            // Register to RIL service if needed.
            for (int i = 0; i < mPhones.length; i++) {
                Phone phone = mPhones[i];
@@ -391,13 +375,83 @@ public class PhoneConfigurationManager {
        }
    }

    private void broadcastSimSlotNumChange(int numOfSims) {
        log("broadcastSimSlotNumChange numOfSims" + numOfSims);
    /**
     * Helper method to set system properties for setting multi sim configs,
     * as well as doing the phone reboot
     * NOTE: In order to support more than 3 sims, we need to change this method.
     * @param numOfActiveModems number of active sims
     */
    private void setMultiSimProperties(int numOfActiveModems) {
        mMi.setMultiSimProperties(numOfActiveModems);
    }

    private static void notifyMultiSimConfigChange(int numOfActiveModems) {
        sMultiSimConfigChangeRegistrants.notifyResult(numOfActiveModems);
    }

    /**
     * Register for multi-SIM configuration change, for example if the devices switched from single
     * SIM to dual-SIM mode.
     *
     * It doesn't trigger callback upon registration as multi-SIM config change is in-frequent.
     */
    public static void registerForMultiSimConfigChange(Handler h, int what, Object obj) {
        sMultiSimConfigChangeRegistrants.addUnique(h, what, obj);
    }

    /**
     * Unregister for multi-SIM configuration change.
     */
    public static void unregisterForMultiSimConfigChange(Handler h) {
        sMultiSimConfigChangeRegistrants.remove(h);
    }

    private void broadcastMultiSimConfigChange(int numOfActiveModems) {
        log("broadcastSimSlotNumChange numOfActiveModems" + numOfActiveModems);
        // Notify internal registrants first.
        notifyMultiSimConfigChange(numOfActiveModems);

        Intent intent = new Intent(ACTION_MULTI_SIM_CONFIG_CHANGED);
        intent.putExtra(EXTRA_NUM_OF_ACTIVE_SIM_SUPPORTED, numOfSims);
        intent.putExtra(EXTRA_NUM_OF_ACTIVE_SIM_SUPPORTED, numOfActiveModems);
        mContext.sendBroadcast(intent);
    }

    /**
     * A wrapper class that wraps some methods so that they can be replaced or mocked in unit-tests.
     *
     * For example, setting or reading system property are static native methods that can't be
     * directly mocked. We can mock it by replacing MockableInterface object with a mock instance
     * in unittest.
     */
    @VisibleForTesting
    public static class MockableInterface {
        @VisibleForTesting
        public boolean isRebootRequiredForModemConfigChange() {
            String rebootRequired = SystemProperties.get(
                    TelephonyProperties.PROPERTY_REBOOT_REQUIRED_ON_MODEM_CHANGE);
            log("isRebootRequiredForModemConfigChange: isRebootRequired = " + rebootRequired);
            return !rebootRequired.equals("false");
        }

        @VisibleForTesting
        public void setMultiSimProperties(int numOfActiveModems) {
            String multiSimConfig;
            switch(numOfActiveModems) {
                case 3:
                    multiSimConfig = TSTS;
                    break;
                case 2:
                    multiSimConfig = DSDS;
                    break;
                default:
                    multiSimConfig = SSSS;
            }

            log("setMultiSimProperties to " + multiSimConfig);
            SystemProperties.set(TelephonyProperties.PROPERTY_MULTI_SIM_CONFIG, multiSimConfig);
        }
    }

    private static void log(String s) {
        Rlog.d(LOG_TAG, s);
    }
+189 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.internal.telephony;

import static android.telephony.TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED;
import static android.telephony.TelephonyManager.EXTRA_NUM_OF_ACTIVE_SIM_SUPPORTED;
import static android.telephony.TelephonyManager.MODEM_COUNT_DUAL_MODEM;
import static android.telephony.TelephonyManager.MODEM_COUNT_SINGLE_MODEM;

import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;

import android.content.Intent;
import android.os.AsyncResult;
import android.os.Handler;
import android.os.Message;
import android.telephony.PhoneCapability;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;

@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
public class PhoneConfigurationManagerTest extends TelephonyTest {
    @Mock
    Handler mHandler;
    @Mock
    CommandsInterface mMockCi;
    @Mock
    PhoneConfigurationManager.MockableInterface mMi;

    private static final int EVENT_MULTI_SIM_CONFIG_CHANGED = 1;
    PhoneConfigurationManager mPcm;

    @Before
    public void setUp() throws Exception {
        super.setUp(getClass().getSimpleName());
        mPhone.mCi = mMockCi;
        mCT.mCi = mMockCi;
    }

    @After
    public void tearDown() throws Exception {
        // Restore system properties.
        super.tearDown();
    }

    private void setRebootRequiredForConfigSwitch(boolean rebootRequired) {
        doReturn(rebootRequired).when(mMi).isRebootRequiredForModemConfigChange();
    }

    private void init(int numOfSim) throws Exception {
        doReturn(numOfSim).when(mTelephonyManager).getActiveModemCount();
        replaceInstance(PhoneConfigurationManager.class, "sInstance", null, null);
        mPcm = PhoneConfigurationManager.init(mContext);
        replaceInstance(PhoneConfigurationManager.class, "mMi", mPcm, mMi);
        processAllMessages();
    }

    /**
     * Test that a single phone case results in our phone being active and the RIL called
     */
    @Test
    @SmallTest
    public void testGetPhoneCount() throws Exception {
        init(MODEM_COUNT_SINGLE_MODEM);
        doReturn(MODEM_COUNT_SINGLE_MODEM).when(mTelephonyManager).getActiveModemCount();
        assertEquals(MODEM_COUNT_SINGLE_MODEM, mPcm.getPhoneCount());
        doReturn(MODEM_COUNT_DUAL_MODEM).when(mTelephonyManager).getActiveModemCount();
        assertEquals(MODEM_COUNT_DUAL_MODEM, mPcm.getPhoneCount());
    }

    @Test
    @SmallTest
    public void testEnablePhone() throws Exception {
        init(MODEM_COUNT_SINGLE_MODEM);
        // Phone is null. No crash.
        mPcm.enablePhone(null, true, null);

        Message message = new Message();
        mPcm.enablePhone(mPhone, false, message);
        verify(mMockCi).enableModem(eq(false), eq(message));
    }

    @Test
    @SmallTest
    public void testGetDsdsCapability() throws Exception {
        init(MODEM_COUNT_SINGLE_MODEM);
        assertEquals(PhoneCapability.DEFAULT_SSSS_CAPABILITY, mPcm.getStaticPhoneCapability());

        ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class);
        verify(mMockRadioConfig).getPhoneCapability(captor.capture());
        Message msg = captor.getValue();
        AsyncResult.forMessage(msg, PhoneCapability.DEFAULT_DSDS_CAPABILITY, null);
        msg.sendToTarget();
        processAllMessages();

        // Not static capability should indicate DSDS capable.
        assertEquals(PhoneCapability.DEFAULT_DSDS_CAPABILITY, mPcm.getStaticPhoneCapability());
    }

    @Test
    @SmallTest
    public void testSwitchMultiSimConfig_notDsdsCapable_shouldFail() throws Exception {
        init(MODEM_COUNT_SINGLE_MODEM);
        assertEquals(PhoneCapability.DEFAULT_SSSS_CAPABILITY, mPcm.getStaticPhoneCapability());

        // Try switching to dual SIM. Shouldn't work as we haven't indicated DSDS is supported.
        mPcm.switchMultiSimConfig(MODEM_COUNT_DUAL_MODEM);
        verify(mMockRadioConfig, never()).setModemsConfig(anyInt(), any());
    }

    @Test
    @SmallTest
    public void testSwitchMultiSimConfig_dsdsCapable_noRebootRequired() throws Exception {
        init(MODEM_COUNT_SINGLE_MODEM);
        // Register for multi SIM config change.
        mPcm.registerForMultiSimConfigChange(mHandler, EVENT_MULTI_SIM_CONFIG_CHANGED, null);
        verify(mHandler, never()).sendMessageAtTime(any(), anyLong());

        // Try switching to dual SIM. Shouldn't work as we haven't indicated DSDS is supported.
        mPcm.switchMultiSimConfig(MODEM_COUNT_DUAL_MODEM);
        verify(mMockRadioConfig, never()).setModemsConfig(anyInt(), any());

        // Send static capability back to indicate DSDS is supported.
        clearInvocations(mMockRadioConfig);
        testGetDsdsCapability();

        // Try to switch to DSDS.
        setRebootRequiredForConfigSwitch(false);
        mPcm.switchMultiSimConfig(MODEM_COUNT_DUAL_MODEM);
        ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class);
        verify(mMockRadioConfig).setModemsConfig(eq(MODEM_COUNT_DUAL_MODEM), captor.capture());

        // Send message back to indicate switch success.
        Message message = captor.getValue();
        AsyncResult.forMessage(message, null, null);
        message.sendToTarget();
        processAllMessages();

        // Verify set system property being called.
        verify(mMi).setMultiSimProperties(MODEM_COUNT_DUAL_MODEM);

        // Capture and verify registration notification.
        verify(mHandler).sendMessageAtTime(captor.capture(), anyLong());
        message = captor.getValue();
        assertEquals(EVENT_MULTI_SIM_CONFIG_CHANGED, message.what);
        assertEquals(MODEM_COUNT_DUAL_MODEM, ((AsyncResult) message.obj).result);

        // Capture and verify broadcast.
        ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
        verify(mContext).sendBroadcast(intentCaptor.capture());
        Intent intent = intentCaptor.getValue();
        assertEquals(ACTION_MULTI_SIM_CONFIG_CHANGED, intent.getAction());
        assertEquals(MODEM_COUNT_DUAL_MODEM, intent.getIntExtra(
                EXTRA_NUM_OF_ACTIVE_SIM_SUPPORTED, 0));

        // Verify RIL notification.
        verify(mMockCi).onSlotActiveStatusChange(true);
    }
}
+3 −1
Original line number Diff line number Diff line
@@ -563,8 +563,10 @@ public abstract class TelephonyTest {
        //SIM
        doReturn(1).when(mTelephonyManager).getSimCount();
        doReturn(1).when(mTelephonyManager).getPhoneCount();
        doReturn(1).when(mTelephonyManager).getActiveModemCount();
        // Have getMaxPhoneCount always return the same value with getPhoneCount by default.
        doAnswer((invocation)->mTelephonyManager.getPhoneCount())
        doAnswer((invocation)->Math.max(mTelephonyManager.getActiveModemCount(),
                mTelephonyManager.getPhoneCount()))
                .when(mTelephonyManager).getSupportedModemCount();

        //Data