1 /* 2 * Copyright (C) 2019 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.internal.telephony; 18 19 import static android.telephony.TelephonyManager.ACTION_MULTI_SIM_CONFIG_CHANGED; 20 import static android.telephony.TelephonyManager.EXTRA_ACTIVE_SIM_SUPPORTED_COUNT; 21 22 import static org.junit.Assert.assertEquals; 23 import static org.mockito.ArgumentMatchers.any; 24 import static org.mockito.ArgumentMatchers.anyBoolean; 25 import static org.mockito.ArgumentMatchers.anyInt; 26 import static org.mockito.ArgumentMatchers.anyLong; 27 import static org.mockito.ArgumentMatchers.eq; 28 import static org.mockito.Mockito.clearInvocations; 29 import static org.mockito.Mockito.doAnswer; 30 import static org.mockito.Mockito.doReturn; 31 import static org.mockito.Mockito.mock; 32 import static org.mockito.Mockito.never; 33 import static org.mockito.Mockito.times; 34 import static org.mockito.Mockito.verify; 35 36 import android.content.Intent; 37 import android.os.AsyncResult; 38 import android.os.Handler; 39 import android.os.Message; 40 import android.telephony.PhoneCapability; 41 import android.telephony.SubscriptionManager; 42 import android.test.suitebuilder.annotation.SmallTest; 43 import android.testing.AndroidTestingRunner; 44 import android.testing.TestableLooper; 45 46 import org.junit.After; 47 import org.junit.Before; 48 import org.junit.Test; 49 import org.junit.runner.RunWith; 50 import org.mockito.ArgumentCaptor; 51 import org.mockito.Mockito; 52 53 @RunWith(AndroidTestingRunner.class) 54 @TestableLooper.RunWithLooper 55 public class PhoneConfigurationManagerTest extends TelephonyTest { 56 // Mocked classes 57 Handler mHandler; 58 CommandsInterface mMockCi0; 59 CommandsInterface mMockCi1; 60 private Phone mPhone1; // mPhone as phone 0 is already defined in TelephonyTest. 61 PhoneConfigurationManager.MockableInterface mMi; 62 63 private static final int EVENT_MULTI_SIM_CONFIG_CHANGED = 1; 64 PhoneConfigurationManager mPcm; 65 66 @Before setUp()67 public void setUp() throws Exception { 68 super.setUp(getClass().getSimpleName()); 69 mHandler = mock(Handler.class); 70 mMockCi0 = mock(CommandsInterface.class); 71 mMockCi1 = mock(CommandsInterface.class); 72 mPhone1 = mock(Phone.class); 73 mMi = mock(PhoneConfigurationManager.MockableInterface.class); 74 mPhone.mCi = mMockCi0; 75 mCT.mCi = mMockCi0; 76 mPhone1.mCi = mMockCi1; 77 } 78 79 @After tearDown()80 public void tearDown() throws Exception { 81 mPcm = null; 82 super.tearDown(); 83 } 84 setRebootRequiredForConfigSwitch(boolean rebootRequired)85 private void setRebootRequiredForConfigSwitch(boolean rebootRequired) { 86 doReturn(rebootRequired).when(mMi).isRebootRequiredForModemConfigChange(); 87 } 88 init(int numOfSim)89 private void init(int numOfSim) throws Exception { 90 doReturn(numOfSim).when(mTelephonyManager).getActiveModemCount(); 91 replaceInstance(PhoneConfigurationManager.class, "sInstance", null, null); 92 mPcm = PhoneConfigurationManager.init(mContext); 93 replaceInstance(PhoneConfigurationManager.class, "mMi", mPcm, mMi); 94 processAllMessages(); 95 } 96 97 /** 98 * Test that a single phone case results in our phone being active and the RIL called 99 */ 100 @Test 101 @SmallTest testGetPhoneCount()102 public void testGetPhoneCount() throws Exception { 103 init(1); 104 doReturn(1).when(mTelephonyManager).getActiveModemCount(); 105 assertEquals(1, mPcm.getPhoneCount()); 106 doReturn(2).when(mTelephonyManager).getActiveModemCount(); 107 assertEquals(2, mPcm.getPhoneCount()); 108 } 109 110 @Test 111 @SmallTest testEnablePhone()112 public void testEnablePhone() throws Exception { 113 init(1); 114 // Phone is null. No crash. 115 mPcm.enablePhone(null, true, null); 116 117 Message message = new Message(); 118 mPcm.enablePhone(mPhone, false, message); 119 verify(mMockCi0).enableModem(eq(false), eq(message)); 120 } 121 122 @Test 123 @SmallTest testGetDsdsCapability()124 public void testGetDsdsCapability() throws Exception { 125 init(1); 126 assertEquals(PhoneCapability.DEFAULT_SSSS_CAPABILITY, mPcm.getStaticPhoneCapability()); 127 128 ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class); 129 verify(mMockRadioConfig).getPhoneCapability(captor.capture()); 130 Message msg = captor.getValue(); 131 AsyncResult.forMessage(msg, PhoneCapability.DEFAULT_DSDS_CAPABILITY, null); 132 msg.sendToTarget(); 133 processAllMessages(); 134 135 // Not static capability should indicate DSDS capable. 136 assertEquals(PhoneCapability.DEFAULT_DSDS_CAPABILITY, mPcm.getStaticPhoneCapability()); 137 } 138 139 @Test 140 @SmallTest testConfigureAndGetMaxActiveVoiceSubscriptions()141 public void testConfigureAndGetMaxActiveVoiceSubscriptions() throws Exception { 142 init(2); 143 assertEquals(1, mPcm.getStaticPhoneCapability().getMaxActiveVoiceSubscriptions()); 144 145 PhoneCapability dualActiveVoiceSubCapability = new PhoneCapability.Builder( 146 PhoneCapability.DEFAULT_DSDS_CAPABILITY) 147 .setMaxActiveVoiceSubscriptions(2) 148 .build(); 149 150 ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class); 151 verify(mMockRadioConfig).getPhoneCapability(captor.capture()); 152 Message msg = captor.getValue(); 153 AsyncResult.forMessage(msg, dualActiveVoiceSubCapability, null); 154 msg.sendToTarget(); 155 processAllMessages(); 156 157 assertEquals(2, mPcm.getStaticPhoneCapability().getMaxActiveVoiceSubscriptions()); 158 } 159 160 @Test 161 @SmallTest testSwitchMultiSimConfig_notDsdsCapable_shouldFail()162 public void testSwitchMultiSimConfig_notDsdsCapable_shouldFail() throws Exception { 163 init(1); 164 assertEquals(PhoneCapability.DEFAULT_SSSS_CAPABILITY, mPcm.getStaticPhoneCapability()); 165 166 // Try switching to dual SIM. Shouldn't work as we haven't indicated DSDS is supported. 167 mPcm.switchMultiSimConfig(2); 168 verify(mMockRadioConfig, never()).setNumOfLiveModems(anyInt(), any()); 169 } 170 171 @Test 172 @SmallTest testSwitchMultiSimConfig_dsdsCapable_noRebootRequired()173 public void testSwitchMultiSimConfig_dsdsCapable_noRebootRequired() throws Exception { 174 init(1); 175 testSwitchFromSingleToDualSimModeNoReboot(); 176 } 177 178 @Test 179 @SmallTest testSwitchMultiSimConfig_multiSimToSingleSim()180 public void testSwitchMultiSimConfig_multiSimToSingleSim() throws Exception { 181 mPhones = new Phone[]{mPhone, mPhone1}; 182 replaceInstance(PhoneFactory.class, "sPhones", null, mPhones); 183 init(2); 184 verify(mMockCi0, times(1)).registerForAvailable(any(), anyInt(), any()); 185 verify(mMockCi1, times(1)).registerForAvailable(any(), anyInt(), any()); 186 187 // Register for multi SIM config change. 188 mPcm.registerForMultiSimConfigChange(mHandler, EVENT_MULTI_SIM_CONFIG_CHANGED, null); 189 verify(mHandler, never()).sendMessageAtTime(any(), anyLong()); 190 191 // Switch to single sim. 192 setRebootRequiredForConfigSwitch(false); 193 mPcm.switchMultiSimConfig(1); 194 ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class); 195 verify(mMockRadioConfig).setNumOfLiveModems(eq(1), captor.capture()); 196 197 // Send message back to indicate switch success. 198 Message message = captor.getValue(); 199 AsyncResult.forMessage(message, null, null); 200 message.sendToTarget(); 201 processAllMessages(); 202 203 // Verify set system property being called. 204 verify(mMi).setMultiSimProperties(1); 205 verify(mMi).notifyPhoneFactoryOnMultiSimConfigChanged(any(), eq(1)); 206 207 // Capture and verify registration notification. 208 verify(mHandler).sendMessageAtTime(captor.capture(), anyLong()); 209 message = captor.getValue(); 210 assertEquals(EVENT_MULTI_SIM_CONFIG_CHANGED, message.what); 211 assertEquals(1, ((AsyncResult) message.obj).result); 212 213 // Capture and verify broadcast. 214 ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); 215 verify(mContext).sendBroadcast(intentCaptor.capture()); 216 Intent intent = intentCaptor.getValue(); 217 assertEquals(ACTION_MULTI_SIM_CONFIG_CHANGED, intent.getAction()); 218 assertEquals(1, intent.getIntExtra( 219 EXTRA_ACTIVE_SIM_SUPPORTED_COUNT, 0)); 220 221 // Verify clearSubInfoRecord() and onSlotActiveStatusChange() are called for second phone, 222 // and not for the first one 223 verify(mSubscriptionManagerService).markSubscriptionsInactive(1); 224 verify(mMockCi1).onSlotActiveStatusChange(anyBoolean()); 225 verify(mSubscriptionManagerService, never()).markSubscriptionsInactive(0); 226 verify(mMockCi0, never()).onSlotActiveStatusChange(anyBoolean()); 227 228 // Verify onPhoneRemoved() gets called on MultiSimSettingController phone 229 verify(mMultiSimSettingController).onPhoneRemoved(); 230 } 231 232 @Test 233 @SmallTest testNoCallPreferenceIsSetAfterSwitchToDsdsMode()234 public void testNoCallPreferenceIsSetAfterSwitchToDsdsMode() throws Exception { 235 final int startingDefaultSubscriptionId = 2; // arbitrary value (can't be -1 which 236 // represents the "No Call Preference" value) 237 238 /* 239 TL;DR: the following mockito code block dynamically changes the last call to the getter 240 241 doAnswer(invocation -> { 242 Integer value = (Integer) invocation.getArguments()[0]; 243 Mockito.when(object.getter()).thenReturn(value); 244 return null; 245 }).when(object).setter(anyInt()); 246 247 read the code block as, whenever we call the setter, change the Mock call 248 of the getter to whatever argument value we last passed to the setter. 249 250 ex.) object.set( 2 ) --> next call to object.get() will return 2 251 */ 252 253 // setup mocks for VOICE mSubscriptionManagerService. getter/setter 254 doAnswer(invocation -> { 255 Integer value = (Integer) invocation.getArguments()[0]; 256 Mockito.when(mSubscriptionManagerService.getDefaultVoiceSubId()).thenReturn(value); 257 return null; 258 }).when(mSubscriptionManagerService).setDefaultVoiceSubId(anyInt()); 259 260 261 // start off the phone stat with 1 active sim. reset values for new test. 262 init(1); 263 264 mSubscriptionManagerService.setDefaultVoiceSubId(startingDefaultSubscriptionId); 265 assertEquals(startingDefaultSubscriptionId, 266 mSubscriptionManagerService.getDefaultVoiceSubId()); 267 268 // Perform the switch to DSDS mode and ensure all existing checks are not altered 269 testSwitchFromSingleToDualSimModeNoReboot(); 270 271 // VOICE check 272 assertEquals(SubscriptionManager.INVALID_SUBSCRIPTION_ID /* No CALL Preference value */, 273 mSubscriptionManagerService.getDefaultVoiceSubId()); 274 // Now, when the user goes to place a CALL, they will be prompted on which sim to use. 275 } 276 277 /** 278 * must call init(1) from the parent test before calling this helper test 279 * @throws Exception 280 */ testSwitchFromSingleToDualSimModeNoReboot()281 public void testSwitchFromSingleToDualSimModeNoReboot() throws Exception { 282 verify(mMockCi0, times(1)).registerForAvailable(any(), anyInt(), any()); 283 284 // Register for multi SIM config change. 285 mPcm.registerForMultiSimConfigChange(mHandler, EVENT_MULTI_SIM_CONFIG_CHANGED, null); 286 verify(mHandler, never()).sendMessageAtTime(any(), anyLong()); 287 288 // Try switching to dual SIM. Shouldn't work as we haven't indicated DSDS is supported. 289 mPcm.switchMultiSimConfig(2); 290 verify(mMockRadioConfig, never()).setNumOfLiveModems(anyInt(), any()); 291 292 // Send static capability back to indicate DSDS is supported. 293 clearInvocations(mMockRadioConfig); 294 testGetDsdsCapability(); 295 // testGetDsdsCapability leads to another call to registerForAvailable() 296 verify(mMockCi0, times(2)).registerForAvailable(any(), anyInt(), any()); 297 298 // Try to switch to DSDS. 299 setRebootRequiredForConfigSwitch(false); 300 mPhones = new Phone[]{mPhone, mPhone1}; 301 replaceInstance(PhoneFactory.class, "sPhones", null, mPhones); 302 mPcm.switchMultiSimConfig(2); 303 ArgumentCaptor<Message> captor = ArgumentCaptor.forClass(Message.class); 304 verify(mMockRadioConfig).setNumOfLiveModems(eq(2), captor.capture()); 305 306 // Send message back to indicate switch success. 307 Message message = captor.getValue(); 308 AsyncResult.forMessage(message, null, null); 309 message.sendToTarget(); 310 processAllMessages(); 311 312 // Verify set system property being called. 313 verify(mMi).setMultiSimProperties(2); 314 verify(mMi).notifyPhoneFactoryOnMultiSimConfigChanged(any(), eq(2)); 315 316 // Capture and verify registration notification. 317 verify(mHandler).sendMessageAtTime(captor.capture(), anyLong()); 318 message = captor.getValue(); 319 assertEquals(EVENT_MULTI_SIM_CONFIG_CHANGED, message.what); 320 assertEquals(2, ((AsyncResult) message.obj).result); 321 322 // Capture and verify broadcast. 323 ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class); 324 verify(mContext).sendBroadcast(intentCaptor.capture()); 325 Intent intent = intentCaptor.getValue(); 326 assertEquals(ACTION_MULTI_SIM_CONFIG_CHANGED, intent.getAction()); 327 assertEquals(2, intent.getIntExtra( 328 EXTRA_ACTIVE_SIM_SUPPORTED_COUNT, 0)); 329 330 // Verify registerForAvailable() and onSlotActiveStatusChange() are called for the second 331 // phone, and not for the first phone (registerForAvailable() was already called twice 332 // earlier so verify that the count is still at 2) 333 verify(mMockCi0, times(2)).registerForAvailable(any(), anyInt(), any()); 334 verify(mMockCi0, never()).onSlotActiveStatusChange(anyBoolean()); 335 verify(mMockCi1, times(1)).registerForAvailable(any(), anyInt(), any()); 336 verify(mMockCi1, times(1)).onSlotActiveStatusChange(anyBoolean()); 337 } 338 } 339