blob: b154d3586880c725736ca38290b02661f7a30a53 [file] [log] [blame]
/*
* Copyright (C) 2006 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 com.android.internal.telephony.PhoneSwitcher.EVENT_DATA_ENABLED_CHANGED;
import static com.android.internal.telephony.PhoneSwitcher.EVENT_PRECISE_CALL_STATE_CHANGED;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.net.StringNetworkSpecifier;
import android.os.AsyncResult;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.os.Messenger;
import android.telephony.PhoneCapability;
import android.telephony.SubscriptionManager;
import android.test.suitebuilder.annotation.SmallTest;
import androidx.test.runner.AndroidJUnit4;
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;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
@RunWith(AndroidJUnit4.class)
public class PhoneSwitcherTest extends TelephonyTest {
private static final String[] sNetworkAttributes = new String[] {
"mobile,0,0,0,-1,true", "mobile_mms,2,0,2,60000,true",
"mobile_supl,3,0,2,60000,true", "mobile_dun,4,0,2,60000,true",
"mobile_hipri,5,0,3,60000,true", "mobile_fota,10,0,2,60000,true",
"mobile_ims,11,0,2,60000,true", "mobile_cbs,12,0,2,60000,true",
"mobile_ia,14,0,2,-1,true", "mobile_emergency,15,0,2,-1,true"};
private static final int ACTIVE_PHONE_SWITCH = 1;
@Mock
private ITelephonyRegistry.Stub mTelRegistryMock;
@Mock
private ITelephonyRegistry mTelRegistryInterfaceMock;
@Mock
private CommandsInterface mCommandsInterface0;
@Mock
private CommandsInterface mCommandsInterface1;
@Mock
private Phone mPhone2; // mPhone as phone 1 is already defined in TelephonyTest.
@Mock
private Handler mActivePhoneSwitchHandler;
@Mock
private GsmCdmaCall mActiveCall;
@Mock
private GsmCdmaCall mInactiveCall;
// The thread that mPhoneSwitcher will handle events in.
private HandlerThread mHandlerThread;
private PhoneSwitcher mPhoneSwitcher;
private IOnSubscriptionsChangedListener mSubChangedListener;
private ConnectivityManager mConnectivityManager;
// The messenger of PhoneSwitcher used to receive network requests.
private Messenger mNetworkFactoryMessenger = null;
private int mDefaultDataSub = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
private CommandsInterface[] mCommandsInterfaces;
private int[][] mSlotIndexToSubId;
private boolean[] mDataAllowed;
@Before
public void setUp() throws Exception {
super.setUp(getClass().getSimpleName());
PhoneCapability phoneCapability = new PhoneCapability(1, 1, 0, null, false);
doReturn(phoneCapability).when(mPhoneConfigurationManager).getCurrentPhoneCapability();
mServiceManagerMockedServices.put("telephony.registry", mTelRegistryMock);
doReturn(mTelRegistryInterfaceMock).when(mTelRegistryMock).queryLocalInterface(any());
doReturn(Call.State.ACTIVE).when(mActiveCall).getState();
doReturn(Call.State.IDLE).when(mInactiveCall).getState();
}
@After
public void tearDown() throws Exception {
super.tearDown();
}
/**
* Test that a single phone case results in our phone being active and the RIL called
*/
@Test
@SmallTest
public void testRegister() throws Exception {
final int numPhones = 2;
final int maxActivePhones = 1;
initialize(numPhones, maxActivePhones);
// verify nothing has been done while there are no inputs
assertFalse("data allowed initially", mDataAllowed[0]);
assertFalse("data allowed initially", mDataAllowed[1]);
NetworkRequest internetNetworkRequest = addInternetNetworkRequest(null, 50);
waitABit();
assertFalse("phone active after request", mPhoneSwitcher
.shouldApplyNetworkRequest(internetNetworkRequest, 0));
// not registered yet - shouldn't inc
verify(mActivePhoneSwitchHandler, never()).sendMessageAtTime(any(), anyLong());
mPhoneSwitcher.registerForActivePhoneSwitch(mActivePhoneSwitchHandler,
ACTIVE_PHONE_SWITCH, null);
verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong());
clearInvocations(mActivePhoneSwitchHandler);
setDefaultDataSubId(0);
verify(mActivePhoneSwitchHandler, never()).sendMessageAtTime(any(), anyLong());
setSlotIndexToSubId(0, 0);
mSubChangedListener.onSubscriptionsChanged();
waitABit();
verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong());
clearInvocations(mActivePhoneSwitchHandler);
assertTrue("data not allowed", mDataAllowed[0]);
// now try various things that should cause the active phone to switch:
// 1 lose default via default sub change
// 2 gain default via default sub change
// 3 lose default via sub->phone change
// 4 gain default via sub->phone change
// 5 lose default network request
// 6 gain subscription-specific request
// 7 lose via sub->phone change
// 8 gain via sub->phone change
// 9 lose subscription-specific request
// 10 don't switch phones when in emergency mode
// 1 lose default via default sub change
setDefaultDataSubId(1);
waitABit();
verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong());
clearInvocations(mActivePhoneSwitchHandler);
assertFalse("data allowed", mDataAllowed[0]);
setSlotIndexToSubId(1, 1);
mSubChangedListener.onSubscriptionsChanged();
waitABit();
verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong());
clearInvocations(mActivePhoneSwitchHandler);
assertFalse("data allowed", mDataAllowed[0]);
assertTrue("data not allowed", mDataAllowed[1]);
// 2 gain default via default sub change
setDefaultDataSubId(0);
waitABit();
verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong());
clearInvocations(mActivePhoneSwitchHandler);
assertFalse("data allowed", mDataAllowed[1]);
assertTrue("data not allowed", mDataAllowed[0]);
// 3 lose default via sub->phone change
setSlotIndexToSubId(0, 2);
mSubChangedListener.onSubscriptionsChanged();
waitABit();
verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong());
clearInvocations(mActivePhoneSwitchHandler);
assertFalse("data allowed", mDataAllowed[0]);
assertFalse("data allowed", mDataAllowed[1]);
// 4 gain default via sub->phone change
setSlotIndexToSubId(0, 0);
mSubChangedListener.onSubscriptionsChanged();
waitABit();
verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong());
clearInvocations(mActivePhoneSwitchHandler);
assertTrue("data not allowed", mDataAllowed[0]);
assertFalse("data allowed", mDataAllowed[1]);
// 5 lose default network request
releaseNetworkRequest(internetNetworkRequest);
waitABit();
verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong());
clearInvocations(mActivePhoneSwitchHandler);
assertFalse("data allowed", mDataAllowed[0]);
assertFalse("data allowed", mDataAllowed[1]);
// 6 gain subscription-specific request
NetworkRequest specificInternetRequest = addInternetNetworkRequest(0, 50);
waitABit();
verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong());
clearInvocations(mActivePhoneSwitchHandler);
assertTrue("data not allowed", mDataAllowed[0]);
assertFalse("data allowed", mDataAllowed[1]);
// 7 lose via sub->phone change
setSlotIndexToSubId(0, 1);
mSubChangedListener.onSubscriptionsChanged();
waitABit();
verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong());
clearInvocations(mActivePhoneSwitchHandler);
assertFalse("data allowed", mDataAllowed[0]);
assertFalse("data allowed", mDataAllowed[1]);
// 8 gain via sub->phone change
setSlotIndexToSubId(0, 0);
mSubChangedListener.onSubscriptionsChanged();
waitABit();
verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong());
clearInvocations(mActivePhoneSwitchHandler);
assertTrue("data not allowed", mDataAllowed[0]);
assertFalse("data allowed", mDataAllowed[1]);
// 9 lose subscription-specific request
releaseNetworkRequest(specificInternetRequest);
waitABit();
verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong());
clearInvocations(mActivePhoneSwitchHandler);
assertFalse("data allowed", mDataAllowed[0]);
assertFalse("data allowed", mDataAllowed[1]);
// 10 don't switch phones when in emergency mode
// not ready yet - Phone turns out to be hard to stub out
// phones[0].setInEmergencyCall(true);
// connectivityServiceMock.addDefaultRequest();
// waitABit();
// if (testHandler.getActivePhoneSwitchCount() != 11) {
// fail("after release of request, ActivePhoneSwitchCount not 11!");
// }
// if (commandsInterfaces[0].isDataAllowed()) fail("data allowed");
// if (commandsInterfaces[1].isDataAllowed()) fail("data allowed");
//
// phones[0].setInEmergencyCall(false);
// connectivityServiceMock.addDefaultRequest();
// waitABit();
// if (testHandler.getActivePhoneSwitchCount() != 12) {
// fail("after release of request, ActivePhoneSwitchCount not 11!");
// }
// if (commandsInterfaces[0].isDataAllowed()) fail("data allowed");
// if (commandsInterfaces[1].isDataAllowed()) fail("data allowed");
mHandlerThread.quit();
}
/**
* Test a multi-sim case with limited active phones:
* - lose default via default sub change
* - lose default via sub->phone change
* - gain default via sub->phone change
* - gain default via default sub change
* - lose default network request
* - gain subscription-specific request
* - lose via sub->phone change
* - gain via sub->phone change
* - lose subscription-specific request
* - tear down low priority phone when new request comes in
* - tear down low priority phone when sub change causes split
* - bring up low priority phone when sub change causes join
* - don't switch phones when in emergency mode
*/
@Test
@SmallTest
public void testPrioritization() throws Exception {
final int numPhones = 2;
final int maxActivePhones = 1;
initialize(numPhones, maxActivePhones);
addInternetNetworkRequest(null, 50);
setSlotIndexToSubId(0, 0);
setSlotIndexToSubId(1, 1);
setDefaultDataSubId(0);
waitABit();
mPhoneSwitcher.registerForActivePhoneSwitch(mActivePhoneSwitchHandler,
ACTIVE_PHONE_SWITCH, null);
waitABit();
// verify initial conditions
verify(mActivePhoneSwitchHandler, times(1)).sendMessageAtTime(any(), anyLong());
assertTrue("data not allowed", mDataAllowed[0]);
assertFalse("data allowed", mDataAllowed[1]);
// now start a higher priority conneciton on the other sub
addMmsNetworkRequest(1);
waitABit();
// After gain of network request, mActivePhoneSwitchHandler should be notified 2 times.
verify(mActivePhoneSwitchHandler, times(2)).sendMessageAtTime(any(), anyLong());
assertFalse("data allowed", mDataAllowed[0]);
assertTrue("data not allowed", mDataAllowed[1]);
mHandlerThread.quit();
}
/**
* Verify we don't send spurious DATA_ALLOWED calls when another NetworkFactory
* wins (ie, switch to wifi).
*/
@Test
@SmallTest
public void testHigherPriorityDefault() throws Exception {
final int numPhones = 2;
final int maxActivePhones = 1;
initialize(numPhones, maxActivePhones);
addInternetNetworkRequest(null, 50);
waitABit();
setSlotIndexToSubId(0, 0);
setSlotIndexToSubId(1, 1);
setDefaultDataSubId(0);
waitABit();
// Phone 0 should be active
assertTrue("data not allowed", mDataAllowed[0]);
assertFalse("data allowed", mDataAllowed[1]);
addInternetNetworkRequest(null, 100);
waitABit();
// should be no change
assertTrue("data not allowed", mDataAllowed[0]);
assertFalse("data allowed", mDataAllowed[1]);
addInternetNetworkRequest(null, 0);
waitABit();
// should be no change
assertTrue("data not allowed", mDataAllowed[0]);
assertFalse("data allowed", mDataAllowed[1]);
mHandlerThread.quit();
}
/**
* Verify testSetPreferredData.
* When preferredData is set, it overwrites defaultData sub to be active sub in single
* active phone mode. If it's unset (to DEFAULT_SUBSCRIPTION_ID), defaultData sub becomes
* active one.
*/
@Test
@SmallTest
public void testSetPreferredData() throws Exception {
final int numPhones = 2;
final int maxActivePhones = 1;
initialize(numPhones, maxActivePhones);
// Phone 0 has sub 1, phone 1 has sub 2.
// Sub 1 is default data sub.
// Both are active subscriptions are active sub, as they are in both active slots.
setSlotIndexToSubId(0, 1);
setSlotIndexToSubId(1, 2);
setDefaultDataSubId(1);
// Notify phoneSwitcher about default data sub and default network request.
addInternetNetworkRequest(null, 50);
waitABit();
// Phone 0 (sub 1) should be activated as it has default data sub.
assertTrue(mDataAllowed[0]);
// Set sub 2 as preferred sub should make phone 1 activated and phone 0 deactivated.
mPhoneSwitcher.trySetOpportunisticDataSubscription(2, false, null);
waitABit();
assertFalse(mDataAllowed[0]);
assertTrue(mDataAllowed[1]);
// Unset preferred sub should make default data sub (phone 0 / sub 1) activated again.
mPhoneSwitcher.trySetOpportunisticDataSubscription(
SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, false, null);
waitABit();
assertTrue(mDataAllowed[0]);
assertFalse(mDataAllowed[1]);
mHandlerThread.quit();
}
@Test
@SmallTest
public void testSetPreferredDataModemCommand() throws Exception {
final int numPhones = 2;
final int maxActivePhones = 1;
doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
initialize(numPhones, maxActivePhones);
mPhoneSwitcher.registerForActivePhoneSwitch(mActivePhoneSwitchHandler,
ACTIVE_PHONE_SWITCH, null);
mPhoneSwitcher.registerForActivePhoneSwitch(mActivePhoneSwitchHandler,
ACTIVE_PHONE_SWITCH, null);
verify(mActivePhoneSwitchHandler, times(2)).sendMessageAtTime(any(), anyLong());
clearInvocations(mMockRadioConfig);
clearInvocations(mActivePhoneSwitchHandler);
// Phone 0 has sub 1, phone 1 has sub 2.
// Sub 1 is default data sub.
// Both are active subscriptions are active sub, as they are in both active slots.
setSlotIndexToSubId(0, 1);
setSlotIndexToSubId(1, 2);
setDefaultDataSubId(1);
waitABit();
// Phone 0 (sub 1) should be preferred data phone as it has default data sub.
verify(mMockRadioConfig).setPreferredDataModem(eq(0), any());
verify(mActivePhoneSwitchHandler, times(2)).sendMessageAtTime(any(), anyLong());
clearInvocations(mMockRadioConfig);
clearInvocations(mActivePhoneSwitchHandler);
// Notify phoneSwitcher about default data sub and default network request.
// It shouldn't change anything.
NetworkRequest internetRequest = addInternetNetworkRequest(null, 50);
NetworkRequest mmsRequest = addMmsNetworkRequest(2);
waitABit();
verify(mMockRadioConfig, never()).setPreferredDataModem(anyInt(), any());
verify(mActivePhoneSwitchHandler, never()).sendMessageAtTime(any(), anyLong());
assertTrue(mPhoneSwitcher.shouldApplyNetworkRequest(internetRequest, 0));
assertFalse(mPhoneSwitcher.shouldApplyNetworkRequest(mmsRequest, 0));
assertFalse(mPhoneSwitcher.shouldApplyNetworkRequest(internetRequest, 1));
assertTrue(mPhoneSwitcher.shouldApplyNetworkRequest(mmsRequest, 1));
// Set sub 2 as preferred sub should make phone 1 preferredDataModem
mPhoneSwitcher.trySetOpportunisticDataSubscription(2, false, null);
waitABit();
verify(mMockRadioConfig).setPreferredDataModem(eq(1), any());
verify(mActivePhoneSwitchHandler, times(2)).sendMessageAtTime(any(), anyLong());
assertFalse(mPhoneSwitcher.shouldApplyNetworkRequest(internetRequest, 0));
assertFalse(mPhoneSwitcher.shouldApplyNetworkRequest(mmsRequest, 0));
assertTrue(mPhoneSwitcher.shouldApplyNetworkRequest(internetRequest, 1));
assertTrue(mPhoneSwitcher.shouldApplyNetworkRequest(mmsRequest, 1));
clearInvocations(mMockRadioConfig);
clearInvocations(mActivePhoneSwitchHandler);
// Unset preferred sub should make phone0 preferredDataModem again.
mPhoneSwitcher.trySetOpportunisticDataSubscription(
SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, false, null);
waitABit();
verify(mMockRadioConfig).setPreferredDataModem(eq(0), any());
verify(mActivePhoneSwitchHandler, times(2)).sendMessageAtTime(any(), anyLong());
assertTrue(mPhoneSwitcher.shouldApplyNetworkRequest(internetRequest, 0));
assertFalse(mPhoneSwitcher.shouldApplyNetworkRequest(mmsRequest, 0));
assertFalse(mPhoneSwitcher.shouldApplyNetworkRequest(internetRequest, 1));
assertTrue(mPhoneSwitcher.shouldApplyNetworkRequest(mmsRequest, 1));
// SetDataAllowed should never be triggered.
verify(mCommandsInterface0, never()).setDataAllowed(anyBoolean(), any());
verify(mCommandsInterface1, never()).setDataAllowed(anyBoolean(), any());
mHandlerThread.quit();
}
@Test
@SmallTest
public void testSetPreferredDataWithValidation() throws Exception {
final int numPhones = 2;
final int maxActivePhones = 1;
doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
initialize(numPhones, maxActivePhones);
// Phone 0 has sub 1, phone 1 has sub 2.
// Sub 1 is default data sub.
// Both are active subscriptions are active sub, as they are in both active slots.
setSlotIndexToSubId(0, 1);
setSlotIndexToSubId(1, 2);
setDefaultDataSubId(1);
// Mark sub 2 as opportunistic.
doReturn(true).when(mSubscriptionController).isOpportunistic(2);
// Phone 0 (sub 1) should be activated as it has default data sub.
assertEquals(0, mPhoneSwitcher.getPreferredDataPhoneId());
// Set sub 2 as preferred sub should make phone 1 activated and phone 0 deactivated.
mPhoneSwitcher.trySetOpportunisticDataSubscription(2, true, null);
waitABit();
verify(mCellularNetworkValidator).validate(eq(2), anyInt(), eq(false),
eq(mPhoneSwitcher.mValidationCallback));
// Validation failed. Preferred data sub should remain 1, data phone should remain 0.
mPhoneSwitcher.mValidationCallback.onValidationResult(false, 2);
waitABit();
assertEquals(0, mPhoneSwitcher.getPreferredDataPhoneId());
// Validation succeeds. Preferred data sub changes to 2, data phone changes to 1.
mPhoneSwitcher.trySetOpportunisticDataSubscription(2, true, null);
waitABit();
mPhoneSwitcher.mValidationCallback.onValidationResult(true, 2);
waitABit();
assertEquals(1, mPhoneSwitcher.getPreferredDataPhoneId());
// Switching data back to primary (subId 1).
mPhoneSwitcher.trySetOpportunisticDataSubscription(
SubscriptionManager.DEFAULT_SUBSCRIPTION_ID, true, null);
waitABit();
verify(mCellularNetworkValidator).validate(eq(1), anyInt(), eq(false),
eq(mPhoneSwitcher.mValidationCallback));
mPhoneSwitcher.mValidationCallback.onValidationResult(true, 1);
waitABit();
assertEquals(0, mPhoneSwitcher.getPreferredDataPhoneId());
mHandlerThread.quit();
}
@Test
@SmallTest
public void testNonDefaultDataPhoneInCall() throws Exception {
final int numPhones = 2;
final int maxActivePhones = 1;
doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
initialize(numPhones, maxActivePhones);
// Phone 0 has sub 1, phone 1 has sub 2.
// Sub 1 is default data sub.
// Both are active subscriptions are active sub, as they are in both active slots.
setSlotIndexToSubId(0, 1);
setSlotIndexToSubId(1, 2);
setDefaultDataSubId(1);
waitABit();
NetworkRequest internetRequest = addInternetNetworkRequest(null, 50);
waitABit();
assertTrue(mPhoneSwitcher.shouldApplyNetworkRequest(internetRequest, 0));
assertFalse(mPhoneSwitcher.shouldApplyNetworkRequest(internetRequest, 1));
clearInvocations(mMockRadioConfig);
setAllPhonesInactive();
// Initialization done.
// Phone2 has active call, but data is turned off. So no data switching should happen.
notifyDataEnabled(false);
notifyPhoneAsInCall(mPhone2);
verify(mMockRadioConfig, never()).setPreferredDataModem(anyInt(), any());
waitABit();
assertTrue(mPhoneSwitcher.shouldApplyNetworkRequest(internetRequest, 0));
assertFalse(mPhoneSwitcher.shouldApplyNetworkRequest(internetRequest, 1));
// Phone2 has active call. So data switch to it.
notifyDataEnabled(true);
verify(mMockRadioConfig).setPreferredDataModem(eq(1), any());
assertTrue(mPhoneSwitcher.shouldApplyNetworkRequest(internetRequest, 1));
assertFalse(mPhoneSwitcher.shouldApplyNetworkRequest(internetRequest, 0));
clearInvocations(mMockRadioConfig);
// Phone2 call ended. So data switch back to default data sub.
notifyPhoneAsInactive(mPhone2);
verify(mMockRadioConfig).setPreferredDataModem(eq(0), any());
assertTrue(mPhoneSwitcher.shouldApplyNetworkRequest(internetRequest, 0));
assertFalse(mPhoneSwitcher.shouldApplyNetworkRequest(internetRequest, 1));
mHandlerThread.quit();
}
@Test
@SmallTest
public void testNetworkRequestOnNonDefaultData() throws Exception {
final int numPhones = 2;
final int maxActivePhones = 1;
doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
initialize(numPhones, maxActivePhones);
// Phone 0 has sub 1, phone 1 has sub 2.
// Sub 1 is default data sub.
// Both are active subscriptions are active sub, as they are in both active slots.
setSlotIndexToSubId(0, 1);
setSlotIndexToSubId(1, 2);
setDefaultDataSubId(1);
waitABit();
NetworkRequest internetRequest = addInternetNetworkRequest(2, 50);
waitABit();
assertFalse(mPhoneSwitcher.shouldApplyNetworkRequest(internetRequest, 0));
assertFalse(mPhoneSwitcher.shouldApplyNetworkRequest(internetRequest, 1));
// Restricted network request will should be applied.
internetRequest = addInternetNetworkRequest(2, 50, true);
waitABit();
assertFalse(mPhoneSwitcher.shouldApplyNetworkRequest(internetRequest, 0));
assertTrue(mPhoneSwitcher.shouldApplyNetworkRequest(internetRequest, 1));
mHandlerThread.quit();
}
@Test
@SmallTest
public void testEmergencyOverrideSuccessBeforeCallStarts() throws Exception {
final int numPhones = 2;
final int maxActivePhones = 1;
doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
initialize(numPhones, maxActivePhones);
// Phone 0 has sub 1, phone 1 has sub 2.
// Sub 1 is default data sub.
// Both are active subscriptions are active sub, as they are in both active slots.
setMsimDefaultDataSubId(numPhones, 1);
clearInvocations(mMockRadioConfig);
// override the phone ID in prep for emergency call
CountDownLatch latch = new CountDownLatch(1);
CompletableFuture<Boolean> futurePhone = new CompletableFuture<>();
futurePhone.whenComplete((result, error) -> {
assertTrue(result);
latch.countDown();
});
mPhoneSwitcher.overrideDefaultDataForEmergency(1, 1, futurePhone);
sendPreferredDataSuccessResult(1);
assertTrue(latch.await(2, TimeUnit.SECONDS));
// Make sure the correct broadcast is sent out for the overridden phone ID
verify(mTelRegistryInterfaceMock).notifyActiveDataSubIdChanged(eq(2));
mHandlerThread.quit();
}
@Test
@SmallTest
public void testEmergencyOverrideNoDdsChange() throws Exception {
final int numPhones = 2;
final int maxActivePhones = 1;
doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
initialize(numPhones, maxActivePhones);
// Phone 0 has sub 1, phone 1 has sub 2.
// Sub 1 is default data sub.
// Both are active subscriptions are active sub, as they are in both active slots.
setMsimDefaultDataSubId(numPhones, 1);
clearInvocations(mMockRadioConfig);
// override the phone ID in prep for emergency call
CountDownLatch latch = new CountDownLatch(1);
CompletableFuture<Boolean> futurePhone = new CompletableFuture<>();
futurePhone.whenComplete((result, error) -> {
assertTrue(result);
latch.countDown();
});
mPhoneSwitcher.overrideDefaultDataForEmergency(0, 1, futurePhone);
waitABit();
// The radio command should never be called because the DDS hasn't changed.
verify(mMockRadioConfig, never()).setPreferredDataModem(eq(0), any());
assertTrue(latch.await(2, TimeUnit.SECONDS));
mHandlerThread.quit();
}
@Test
@SmallTest
public void testEmergencyOverrideEndSuccess() throws Exception {
PhoneSwitcher.ECBM_DEFAULT_DATA_SWITCH_BASE_TIME_MS = 500;
final int numPhones = 2;
final int maxActivePhones = 1;
doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
initialize(numPhones, maxActivePhones);
// Phone 0 has sub 1, phone 1 has sub 2.
// Sub 1 is default data sub.
// Both are active subscriptions are active sub, as they are in both active slots.
setMsimDefaultDataSubId(numPhones, 1);
setAllPhonesInactive();
clearInvocations(mMockRadioConfig);
clearInvocations(mTelRegistryInterfaceMock);
// override the phone ID in prep for emergency call
CountDownLatch latch = new CountDownLatch(1);
CompletableFuture<Boolean> futurePhone = new CompletableFuture<>();
futurePhone.whenComplete((result, error) -> {
assertTrue(result);
latch.countDown();
});
mPhoneSwitcher.overrideDefaultDataForEmergency(1, 1, futurePhone);
sendPreferredDataSuccessResult(1);
assertTrue(latch.await(1, TimeUnit.SECONDS));
// Start and end the emergency call, which will start override timer
notifyPhoneAsInCall(mPhone2);
notifyPhoneAsInactive(mPhone2);
// Verify that the DDS is successfully switched back after 1 second + base ECBM timeout
verify(mMockRadioConfig,
timeout(PhoneSwitcher.ECBM_DEFAULT_DATA_SWITCH_BASE_TIME_MS + 2000))
.setPreferredDataModem(eq(0), any());
// Make sure the correct broadcast is sent out for the phone ID
verify(mTelRegistryInterfaceMock).notifyActiveDataSubIdChanged(eq(1));
mHandlerThread.quit();
}
@Test
@SmallTest
public void testEmergencyOverrideEcbmStartEnd() throws Exception {
PhoneSwitcher.ECBM_DEFAULT_DATA_SWITCH_BASE_TIME_MS = 500;
final int numPhones = 2;
final int maxActivePhones = 1;
doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
initialize(numPhones, maxActivePhones);
// Phone 0 has sub 1, phone 1 has sub 2.
// Sub 1 is default data sub.
// Both are active subscriptions are active sub, as they are in both active slots.
setMsimDefaultDataSubId(numPhones, 1);
setAllPhonesInactive();
clearInvocations(mMockRadioConfig);
clearInvocations(mTelRegistryInterfaceMock);
// override the phone ID in prep for emergency call
CountDownLatch latch = new CountDownLatch(1);
CompletableFuture<Boolean> futurePhone = new CompletableFuture<>();
futurePhone.whenComplete((result, error) -> {
assertTrue(result);
latch.countDown();
});
mPhoneSwitcher.overrideDefaultDataForEmergency(1, 1, futurePhone);
sendPreferredDataSuccessResult(1);
assertTrue(latch.await(1, TimeUnit.SECONDS));
// Start and end the emergency call, which will start override timer
notifyPhoneAsInCall(mPhone2);
notifyPhoneAsInactive(mPhone2);
// Start ECBM
Message ecbmMessage = getEcbmRegistration(mPhone2);
notifyEcbmStart(mPhone2, ecbmMessage);
// DDS should not be switched back until ECBM ends, make sure there is no further
// interaction.
Thread.sleep(PhoneSwitcher.ECBM_DEFAULT_DATA_SWITCH_BASE_TIME_MS + 2000);
verify(mMockRadioConfig, never()).setPreferredDataModem(eq(0), any());
// Make sure the correct broadcast is sent out for the phone ID
verify(mTelRegistryInterfaceMock).notifyActiveDataSubIdChanged(eq(2));
// End ECBM
ecbmMessage = getEcbmRegistration(mPhone2);
notifyEcbmEnd(mPhone2, ecbmMessage);
// Verify that the DDS is successfully switched back after 1 second.
verify(mMockRadioConfig, timeout(2000)).setPreferredDataModem(eq(0), any());
// Make sure the correct broadcast is sent out for the phone ID
verify(mTelRegistryInterfaceMock).notifyActiveDataSubIdChanged(eq(1));
mHandlerThread.quit();
}
@Test
@SmallTest
public void testEmergencyOverrideNoCallStart() throws Exception {
PhoneSwitcher.DEFAULT_DATA_OVERRIDE_TIMEOUT_MS = 500;
final int numPhones = 2;
final int maxActivePhones = 1;
doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
initialize(numPhones, maxActivePhones);
// Phone 0 has sub 1, phone 1 has sub 2.
// Sub 1 is default data sub.
// Both are active subscriptions are active sub, as they are in both active slots.
setMsimDefaultDataSubId(numPhones, 1);
setAllPhonesInactive();
clearInvocations(mMockRadioConfig);
clearInvocations(mTelRegistryInterfaceMock);
// override the phone ID in prep for emergency call
CountDownLatch latch = new CountDownLatch(1);
CompletableFuture<Boolean> futurePhone = new CompletableFuture<>();
futurePhone.whenComplete((result, error) -> {
assertTrue(result);
latch.countDown();
});
mPhoneSwitcher.overrideDefaultDataForEmergency(1, 1, futurePhone);
sendPreferredDataSuccessResult(1);
assertTrue(latch.await(1, TimeUnit.SECONDS));
// Do not start the call and make sure the override is removed once the timeout expires
verify(mMockRadioConfig,
timeout(PhoneSwitcher.DEFAULT_DATA_OVERRIDE_TIMEOUT_MS + 1000))
.setPreferredDataModem(eq(0), any());
// Make sure the correct broadcast is sent out for the phone ID
verify(mTelRegistryInterfaceMock).notifyActiveDataSubIdChanged(eq(1));
mHandlerThread.quit();
}
@Test
@SmallTest
public void testEmergencyOverrideMultipleOverrideRequests() throws Exception {
PhoneSwitcher.ECBM_DEFAULT_DATA_SWITCH_BASE_TIME_MS = 500;
final int numPhones = 2;
final int maxActivePhones = 1;
doReturn(true).when(mMockRadioConfig).isSetPreferredDataCommandSupported();
initialize(numPhones, maxActivePhones);
// Phone 0 has sub 1, phone 1 has sub 2.
// Sub 1 is default data sub.
// Both are active subscriptions are active sub, as they are in both active slots.
setMsimDefaultDataSubId(numPhones, 1);
setAllPhonesInactive();
clearInvocations(mMockRadioConfig);
clearInvocations(mTelRegistryInterfaceMock);
// override the phone ID in prep for emergency call
LinkedBlockingQueue<Boolean> queue = new LinkedBlockingQueue<>();
CompletableFuture<Boolean> futurePhone = new CompletableFuture<>();
futurePhone.whenComplete((r, error) -> queue.offer(r));
mPhoneSwitcher.overrideDefaultDataForEmergency(1, 1, futurePhone);
sendPreferredDataSuccessResult(1);
Boolean result = queue.poll(1, TimeUnit.SECONDS);
assertNotNull(result);
assertTrue(result);
// try override the phone ID again while there is an existing override for a different phone
futurePhone = new CompletableFuture<>();
futurePhone.whenComplete((r, error) -> queue.offer(r));
mPhoneSwitcher.overrideDefaultDataForEmergency(0, 1, futurePhone);
result = queue.poll(1, TimeUnit.SECONDS);
assertNotNull(result);
assertFalse(result);
verify(mMockRadioConfig, never()).setPreferredDataModem(eq(0), any());
// Start and end the emergency call, which will start override timer
notifyPhoneAsInCall(mPhone2);
notifyPhoneAsInactive(mPhone2);
// Verify that the DDS is successfully switched back after 1 second + base ECBM timeout
verify(mMockRadioConfig,
timeout(PhoneSwitcher.ECBM_DEFAULT_DATA_SWITCH_BASE_TIME_MS + 2000))
.setPreferredDataModem(eq(0), any());
// Make sure the correct broadcast is sent out for the phone ID
verify(mTelRegistryInterfaceMock).notifyActiveDataSubIdChanged(eq(1));
mHandlerThread.quit();
}
/* Private utility methods start here */
private void setAllPhonesInactive() {
doReturn(mInactiveCall).when(mPhone).getForegroundCall();
doReturn(mInactiveCall).when(mPhone).getBackgroundCall();
doReturn(mInactiveCall).when(mPhone).getRingingCall();
doReturn(mInactiveCall).when(mPhone2).getForegroundCall();
doReturn(mInactiveCall).when(mPhone2).getBackgroundCall();
doReturn(mInactiveCall).when(mPhone2).getRingingCall();
}
private void notifyPhoneAsInCall(Phone phone) {
doReturn(mActiveCall).when(phone).getForegroundCall();
mPhoneSwitcher.sendEmptyMessage(EVENT_PRECISE_CALL_STATE_CHANGED);
waitABit();
}
private void notifyPhoneAsInactive(Phone phone) {
doReturn(mInactiveCall).when(phone).getForegroundCall();
mPhoneSwitcher.sendEmptyMessage(EVENT_PRECISE_CALL_STATE_CHANGED);
waitABit();
}
private void notifyDataEnabled(boolean dataEnabled) {
doReturn(dataEnabled).when(mDataEnabledSettings).isDataEnabled(anyInt());
mPhoneSwitcher.sendEmptyMessage(EVENT_DATA_ENABLED_CHANGED);
waitABit();
}
private Message getEcbmRegistration(Phone phone) {
ArgumentCaptor<Handler> handlerCaptor = ArgumentCaptor.forClass(Handler.class);
ArgumentCaptor<Integer> intCaptor = ArgumentCaptor.forClass(Integer.class);
verify(phone).registerForEmergencyCallToggle(handlerCaptor.capture(), intCaptor.capture(),
any());
assertNotNull(handlerCaptor.getValue());
assertNotNull(intCaptor.getValue());
Message message = Message.obtain(handlerCaptor.getValue(), intCaptor.getValue());
return message;
}
private void notifyEcbmStart(Phone phone, Message ecmMessage) {
doReturn(mInactiveCall).when(phone).getForegroundCall();
doReturn(true).when(phone).isInEcm();
ecmMessage.sendToTarget();
waitABit();
}
private void notifyEcbmEnd(Phone phone, Message ecmMessage) {
doReturn(false).when(phone).isInEcm();
ecmMessage.sendToTarget();
waitABit();
}
private void sendPreferredDataSuccessResult(int phoneId) {
// make sure the radio command is called and then send a success result
ArgumentCaptor<Message> msgCaptor = ArgumentCaptor.forClass(Message.class);
verify(mMockRadioConfig, timeout(500)).setPreferredDataModem(eq(phoneId),
msgCaptor.capture());
assertNotNull(msgCaptor.getValue());
// Send back successful result
AsyncResult.forMessage(msgCaptor.getValue(), null, null);
msgCaptor.getValue().sendToTarget();
waitABit();
}
private void setMsimDefaultDataSubId(int numPhones, int defaultDataSub) throws Exception {
for (int i = 0; i < numPhones; i++) {
setSlotIndexToSubId(i, i + 1);
}
setDefaultDataSubId(defaultDataSub);
waitABit();
NetworkRequest internetRequest = addInternetNetworkRequest(null, 50);
waitABit();
for (int i = 0; i < numPhones; i++) {
if (defaultDataSub == (i + 1)) {
// sub id is always phoneId+1 for testing
assertTrue(mPhoneSwitcher.shouldApplyNetworkRequest(internetRequest, i));
} else {
assertFalse(mPhoneSwitcher.shouldApplyNetworkRequest(internetRequest, i));
}
}
}
private void sendDefaultDataSubChanged() {
final Intent intent = new Intent(TelephonyIntents.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED);
mContext.sendBroadcast(intent);
}
private void initialize(int numPhones, int maxActivePhones) throws Exception {
mContextFixture.putStringArrayResource(com.android.internal.R.array.networkAttributes,
sNetworkAttributes);
setNumPhones(numPhones);
initializeSubControllerMock();
initializeCommandInterfacesMock(numPhones);
initializeTelRegistryMock();
initializeConnManagerMock();
mHandlerThread = new HandlerThread("PhoneSwitcherTestThread") {
@Override
public void onLooperPrepared() {
mPhoneSwitcher = new PhoneSwitcher(maxActivePhones, numPhones,
mContext, mSubscriptionController, this.getLooper(),
mTelRegistryMock, mCommandsInterfaces, mPhones);
}
};
mHandlerThread.start();
waitABit();
verify(mTelRegistryMock).addOnSubscriptionsChangedListener(
eq(mContext.getOpPackageName()), any());
}
/**
* Certain variables needs initialized depending on number of phones.
*/
private void setNumPhones(int numPhones) {
mDataAllowed = new boolean[numPhones];
mSlotIndexToSubId = new int[numPhones][];
doReturn(0).when(mPhone).getPhoneId();
doReturn(1).when(mPhone2).getPhoneId();
doReturn(true).when(mPhone2).isUserDataEnabled();
doReturn(mDataEnabledSettings).when(mPhone2).getDataEnabledSettings();
for (int i = 0; i < numPhones; i++) {
mSlotIndexToSubId[i] = new int[1];
mSlotIndexToSubId[i][0] = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
}
doReturn(numPhones).when(mTelephonyManager).getPhoneCount();
if (numPhones == 1) {
mCommandsInterfaces = new CommandsInterface[] {mCommandsInterface0};
mPhones = new Phone[] {mPhone};
} else if (numPhones == 2) {
mCommandsInterfaces =
new CommandsInterface[] {mCommandsInterface0, mCommandsInterface1};
mPhones = new Phone[] {mPhone, mPhone2};
}
}
private void initializeCommandInterfacesMock(int numPhones) {
// Tell PhoneSwitcher that radio is on.
doAnswer(invocation -> {
Handler handler = (Handler) invocation.getArguments()[0];
int message = (int) invocation.getArguments()[1];
Object obj = invocation.getArguments()[2];
handler.obtainMessage(message, obj).sendToTarget();
return null;
}).when(mCommandsInterface0).registerForAvailable(any(), anyInt(), any());
// Store values of dataAllowed in mDataAllowed[] for easier checking.
doAnswer(invocation -> {
mDataAllowed[0] = (boolean) invocation.getArguments()[0];
return null;
}).when(mCommandsInterface0).setDataAllowed(anyBoolean(), any());
if (numPhones == 2) {
doAnswer(invocation -> {
mDataAllowed[1] = (boolean) invocation.getArguments()[0];
return null;
}).when(mCommandsInterface1).setDataAllowed(anyBoolean(), any());
}
}
/**
* Store subChangedListener of PhoneSwitcher so that testing can notify
* PhoneSwitcher of sub change.
*/
private void initializeTelRegistryMock() throws Exception {
doAnswer(invocation -> {
IOnSubscriptionsChangedListener subChangedListener =
(IOnSubscriptionsChangedListener) invocation.getArguments()[1];
mSubChangedListener = subChangedListener;
mSubChangedListener.onSubscriptionsChanged();
return null;
}).when(mTelRegistryMock).addOnSubscriptionsChangedListener(any(), any());
}
/**
* Capture mNetworkFactoryMessenger so that testing can request or release
* network requests on PhoneSwitcher.
*/
private void initializeConnManagerMock() {
mConnectivityManager = (ConnectivityManager)
mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
doAnswer(invocation -> {
mNetworkFactoryMessenger = invocation.getArgument(0);
return null;
}).when(mConnectivityManager).registerNetworkFactory(any(), any());
}
/**
* Capture mNetworkFactoryMessenger so that testing can request or release
* network requests on PhoneSwitcher.
*/
private void initializeSubControllerMock() {
doReturn(mDefaultDataSub).when(mSubscriptionController).getDefaultDataSubId();
doAnswer(invocation -> {
int phoneId = (int) invocation.getArguments()[0];
if (phoneId == SubscriptionManager.INVALID_PHONE_INDEX) {
return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
} else if (phoneId == SubscriptionManager.DEFAULT_PHONE_INDEX) {
return mSlotIndexToSubId[0][0];
} else {
return mSlotIndexToSubId[phoneId][0];
}
}).when(mSubscriptionController).getSubIdUsingPhoneId(anyInt());
doAnswer(invocation -> {
int subId = (int) invocation.getArguments()[0];
if (!SubscriptionManager.isUsableSubIdValue(subId)) return false;
for (int i = 0; i < mSlotIndexToSubId.length; i++) {
if (mSlotIndexToSubId[i][0] == subId) return true;
}
return false;
}).when(mSubscriptionController).isActiveSubId(anyInt());
}
private void setDefaultDataSubId(int defaultDataSub) {
mDefaultDataSub = defaultDataSub;
doReturn(mDefaultDataSub).when(mSubscriptionController).getDefaultDataSubId();
sendDefaultDataSubChanged();
}
private void setSlotIndexToSubId(int slotId, int subId) {
mSlotIndexToSubId[slotId][0] = subId;
}
/**
* Create an internet PDN network request and send it to PhoneSwitcher.
*/
private NetworkRequest addInternetNetworkRequest(Integer subId, int score) throws Exception {
return addInternetNetworkRequest(subId, score, false);
}
private NetworkRequest addInternetNetworkRequest(Integer subId, int score, boolean restricted)
throws Exception {
NetworkCapabilities netCap = (new NetworkCapabilities())
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
if (restricted) {
netCap.removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED);
}
if (subId != null) {
netCap.setNetworkSpecifier(new StringNetworkSpecifier(Integer.toString(subId)));
}
NetworkRequest networkRequest = new NetworkRequest(netCap, ConnectivityManager.TYPE_NONE,
0, NetworkRequest.Type.REQUEST);
Message message = Message.obtain();
message.what = android.net.NetworkFactory.CMD_REQUEST_NETWORK;
message.arg1 = score;
message.obj = networkRequest;
mNetworkFactoryMessenger.send(message);
return networkRequest;
}
/**
* Create a mms PDN network request and send it to PhoneSwitcher.
*/
private NetworkRequest addMmsNetworkRequest(Integer subId) throws Exception {
NetworkCapabilities netCap = (new NetworkCapabilities())
.addCapability(NetworkCapabilities.NET_CAPABILITY_MMS)
.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR);
netCap.setNetworkSpecifier(new StringNetworkSpecifier(Integer.toString(subId)));
if (subId != null) {
netCap.setNetworkSpecifier(new StringNetworkSpecifier(Integer.toString(subId)));
}
NetworkRequest networkRequest = new NetworkRequest(netCap, ConnectivityManager.TYPE_NONE,
1, NetworkRequest.Type.REQUEST);
Message message = Message.obtain();
message.what = android.net.NetworkFactory.CMD_REQUEST_NETWORK;
message.arg1 = 50; // Score
message.obj = networkRequest;
mNetworkFactoryMessenger.send(message);
return networkRequest;
}
/**
* Tell PhoneSwitcher to release a network request.
*/
private void releaseNetworkRequest(NetworkRequest networkRequest) throws Exception {
Message message = Message.obtain();
message.what = android.net.NetworkFactory.CMD_CANCEL_REQUEST;
message.obj = networkRequest;
mNetworkFactoryMessenger.send(message);
}
private void waitABit() {
try {
Thread.sleep(250);
} catch (Exception e) {
}
}
}