1 /* 2 * Copyright (C) 2020 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.services.telephony; 18 19 import static android.media.ToneGenerator.TONE_PROP_PROMPT; 20 import static android.media.ToneGenerator.TONE_SUP_BUSY; 21 22 import static junit.framework.Assert.assertNotNull; 23 import static junit.framework.TestCase.assertEquals; 24 25 import static org.mockito.Mockito.mock; 26 import static org.testng.Assert.assertFalse; 27 import static org.testng.Assert.assertTrue; 28 29 import android.content.Context; 30 import android.content.res.Configuration; 31 import android.content.res.Resources; 32 import android.os.PersistableBundle; 33 import android.telephony.CarrierConfigManager; 34 import android.telephony.DisconnectCause; 35 36 import androidx.test.InstrumentationRegistry; 37 import androidx.test.runner.AndroidJUnit4; 38 39 import com.android.TelephonyTestBase; 40 import com.android.internal.telephony.CallFailCause; 41 import com.android.internal.telephony.GsmCdmaPhone; 42 import com.android.internal.telephony.Phone; 43 import com.android.internal.telephony.PhoneFactory; 44 import com.android.phone.R; 45 46 import org.junit.Before; 47 import org.junit.Test; 48 import org.junit.runner.RunWith; 49 import org.mockito.Mock; 50 51 import java.util.Locale; 52 53 @RunWith(AndroidJUnit4.class) 54 public class DisconnectCauseUtilTest extends TelephonyTestBase { 55 56 // constants 57 public static final int PHONE_ID = 123; 58 public static final String EMPTY_STRING = ""; 59 60 // dynamic 61 private Context mContext; 62 63 //Mocks 64 @Mock 65 private GsmCdmaPhone mMockPhone; 66 67 private final FlagsAdapter mFeatureFlags = new FlagsAdapter(){ 68 @Override 69 public boolean doNotOverridePreciseLabel() { 70 return true; 71 } 72 }; 73 74 @Before setUp()75 public void setUp() throws Exception { 76 super.setUp(); 77 // objects that call static getInstance() 78 mMockPhone = mock(GsmCdmaPhone.class); 79 mContext = InstrumentationRegistry.getTargetContext(); 80 // set mocks 81 setSinglePhone(); 82 } 83 84 /** 85 * Verifies that a call drop due to loss of WIFI results in a disconnect cause of error and that 86 * the label, description and tone are all present. 87 */ 88 @Test testDropDueToWifiLoss()89 public void testDropDueToWifiLoss() { 90 android.telecom.DisconnectCause tcCause = DisconnectCauseUtil.toTelecomDisconnectCause( 91 DisconnectCause.WIFI_LOST); 92 assertEquals(android.telecom.DisconnectCause.ERROR, tcCause.getCode()); 93 assertEquals(TONE_PROP_PROMPT, tcCause.getTone()); 94 assertNotNull(tcCause.getDescription()); 95 assertNotNull(tcCause.getReason()); 96 } 97 98 /** 99 * ensure the default behavior was not changed when a disconnect cause comes in as 100 * DisconnectCause.ERROR_UNSPECIFIED 101 */ 102 @Test testDefaultDisconnectCauseBehaviorForCauseNotInCarrierBusyToneArray()103 public void testDefaultDisconnectCauseBehaviorForCauseNotInCarrierBusyToneArray() { 104 android.telecom.DisconnectCause tcCause = DisconnectCauseUtil.toTelecomDisconnectCause( 105 DisconnectCause.ERROR_UNSPECIFIED, -1, EMPTY_STRING, PHONE_ID, null, mFeatureFlags); 106 // CODE 107 assertEquals(android.telecom.DisconnectCause.ERROR, tcCause.getCode()); 108 // LABEL 109 safeAssertLabel(null, tcCause); 110 // TONE 111 assertEquals(TONE_PROP_PROMPT, tcCause.getTone()); 112 } 113 114 /** 115 * verify that if a precise label is given Telephony, the label is not overridden by Telecom 116 */ 117 @Test testDefaultPhoneConfig_NoPreciseLabelGiven()118 public void testDefaultPhoneConfig_NoPreciseLabelGiven() { 119 android.telecom.DisconnectCause tcCause = 120 DisconnectCauseUtil.toTelecomDisconnectCause(DisconnectCause.BUSY, 121 -1 /* precise label is NOT given */, 122 EMPTY_STRING, PHONE_ID, null /* carrier config is NOT set */, 123 mFeatureFlags); 124 assertBusyCauseWithTargetLabel(R.string.callFailed_userBusy, tcCause); 125 } 126 127 /** 128 * verify that if a precise label is given Telephony, the label is not overridden by Telecom 129 */ 130 @Test testDefaultPhoneConfig_PreciseLabelProvided()131 public void testDefaultPhoneConfig_PreciseLabelProvided() { 132 android.telecom.DisconnectCause tcCause = 133 DisconnectCauseUtil.toTelecomDisconnectCause(DisconnectCause.BUSY, 134 CallFailCause.USER_BUSY /* Telephony defined a precise label */, 135 EMPTY_STRING, PHONE_ID, null /* carrier config is NOT set */, 136 mFeatureFlags); 137 // Note: The precise label should not be overridden even though the carrier defined 138 // the cause to play a busy tone 139 assertBusyCauseWithTargetLabel(R.string.clh_callFailed_user_busy_txt, tcCause); 140 } 141 142 /** 143 * special case: The Carrier has re-defined a disconnect code that should play a busy tone. 144 * Thus, the code, label, and tone should be remapped. 145 * <p> 146 * <p> 147 * Verify that if the disconnect cause is in the carrier busy tone array that the expected 148 * label, tone, and code are returned. 149 */ 150 @Test testCarrierSetBusyToneArray_NoPreciseLabelGiven()151 public void testCarrierSetBusyToneArray_NoPreciseLabelGiven() { 152 android.telecom.DisconnectCause tcCause = 153 DisconnectCauseUtil.toTelecomDisconnectCause( 154 DisconnectCause.BUSY, -1 /* precise label is NOT given */, 155 EMPTY_STRING, PHONE_ID, null, getBundleWithBusyToneArray(), mFeatureFlags, 156 false); 157 158 assertBusyCauseWithTargetLabel(R.string.callFailed_userBusy, tcCause); 159 } 160 161 /** 162 * special case: The Carrier has re-defined a disconnect code that should play a busy tone. 163 * Thus, the code, label, and tone should be remapped. 164 * <p> 165 * <p> 166 * Verify that if the disconnect cause is in the carrier busy tone array and the Telephony 167 * stack has provided a precise label, the label is not overridden. 168 */ 169 @Test testCarrierSetBusyToneArray_PreciseLabelProvided()170 public void testCarrierSetBusyToneArray_PreciseLabelProvided() { 171 android.telecom.DisconnectCause tcCause = 172 DisconnectCauseUtil.toTelecomDisconnectCause(DisconnectCause.BUSY, 173 CallFailCause.USER_BUSY /* Telephony defined a precise label */, 174 EMPTY_STRING, PHONE_ID, null, getBundleWithBusyToneArray(), mFeatureFlags, 175 false); 176 // Note: The precise label should not be overridden even though the carrier defined 177 // the cause to play a busy tone 178 assertBusyCauseWithTargetLabel(R.string.clh_callFailed_user_busy_txt, tcCause); 179 } 180 181 /** 182 * Ensure the helper doesCarrierClassifyDisconnectCauseAsBusyCause does not hit a NPE if a 183 * NULL carrier config is passed in. 184 */ 185 @Test testDoesCarrierClassifyDisconnectCauseAsBusyCause_nullConfig()186 public void testDoesCarrierClassifyDisconnectCauseAsBusyCause_nullConfig() { 187 assertFalse(DisconnectCauseUtil.doesCarrierClassifyDisconnectCauseAsBusyCause(-1, null)); 188 } 189 190 /** 191 * Ensure the helper doesCarrierClassifyDisconnectCauseAsBusyCause does not hit a NPE if an 192 * EMPTY carrier config is passed in. 193 */ 194 @Test testDoesCarrierClassifyDisconnectCauseAsBusyCause_ConfigDoesNotDefineArray()195 public void testDoesCarrierClassifyDisconnectCauseAsBusyCause_ConfigDoesNotDefineArray() { 196 PersistableBundle config = new PersistableBundle(); 197 assertFalse(DisconnectCauseUtil.doesCarrierClassifyDisconnectCauseAsBusyCause(-1, config)); 198 } 199 200 /** 201 * Ensure the helper doesCarrierClassifyDisconnectCauseAsBusyCause does not hit a NPE if an 202 * EMPTY array is defined for KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY. 203 */ 204 @Test testDoesCarrierClassifyDisconnectCauseAsBusyCause_ConfigHasEmptyArray()205 public void testDoesCarrierClassifyDisconnectCauseAsBusyCause_ConfigHasEmptyArray() { 206 PersistableBundle config = new PersistableBundle(); 207 int[] carrierBusyArr = {}; // NOTE: This is intentionally let empty 208 209 config.putIntArray( 210 CarrierConfigManager.KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY, 211 carrierBusyArr); 212 213 assertFalse(DisconnectCauseUtil.doesCarrierClassifyDisconnectCauseAsBusyCause(-1, config)); 214 } 215 216 /** 217 * Ensure {@link DisconnectCauseUtil#doesCarrierClassifyDisconnectCauseAsBusyCause} returns 218 * FALSE is the passed in disconnect cause is NOT the busy tone array 219 */ 220 @Test testDoesCarrierClassifyDisconnectCauseAsBusyCause_ConfigHasBusyToneButNotMatch()221 public void testDoesCarrierClassifyDisconnectCauseAsBusyCause_ConfigHasBusyToneButNotMatch() { 222 assertFalse(DisconnectCauseUtil.doesCarrierClassifyDisconnectCauseAsBusyCause(-1, 223 getBundleWithBusyToneArray())); 224 } 225 226 /** 227 * Ensure {@link DisconnectCauseUtil#doesCarrierClassifyDisconnectCauseAsBusyCause} returns 228 * TRUE if the disconnect cause is defined in the busy tone array (by the Carrier) 229 */ 230 @Test testDoesCarrierClassifyDisconnectCauseAsBusyCause_ConfigHasBusyTone()231 public void testDoesCarrierClassifyDisconnectCauseAsBusyCause_ConfigHasBusyTone() { 232 assertTrue(DisconnectCauseUtil.doesCarrierClassifyDisconnectCauseAsBusyCause( 233 DisconnectCause.BUSY, getBundleWithBusyToneArray())); 234 } 235 assertBusyCauseWithTargetLabel(Integer targetLabel, android.telecom.DisconnectCause disconnectCause)236 private void assertBusyCauseWithTargetLabel(Integer targetLabel, 237 android.telecom.DisconnectCause disconnectCause) { 238 // CODE: Describes the cause of a disconnected call 239 assertEquals(android.telecom.DisconnectCause.BUSY, disconnectCause.getCode()); 240 // LABEL: This is the label that the user sees 241 safeAssertLabel(targetLabel, disconnectCause); 242 // TONE: This is the DTMF tone being played to the user 243 assertEquals(TONE_SUP_BUSY, disconnectCause.getTone()); 244 } 245 getBundleWithBusyToneArray()246 private PersistableBundle getBundleWithBusyToneArray() { 247 int[] carrierBusyArr = {DisconnectCause.BUSY}; 248 PersistableBundle config = new PersistableBundle(); 249 250 config.putIntArray( 251 CarrierConfigManager.KEY_DISCONNECT_CAUSE_PLAY_BUSYTONE_INT_ARRAY, 252 carrierBusyArr); 253 return config; 254 } 255 setSinglePhone()256 private void setSinglePhone() throws Exception { 257 Phone[] mPhones = new Phone[]{mMockPhone}; 258 replaceInstance(PhoneFactory.class, "sPhones", null, mPhones); 259 } 260 getResourcesForLocale(Context context, Locale locale)261 private Resources getResourcesForLocale(Context context, Locale locale) { 262 Configuration config = new Configuration(); 263 config.setToDefaults(); 264 config.setLocale(locale); 265 Context localeContext = context.createConfigurationContext(config); 266 return localeContext.getResources(); 267 } 268 safeAssertLabel(Integer resourceId, android.telecom.DisconnectCause disconnectCause)269 private void safeAssertLabel(Integer resourceId, 270 android.telecom.DisconnectCause disconnectCause) { 271 Resources r = getResourcesForLocale(mContext, Locale.US); 272 if (resourceId == null || r == null) { 273 return; 274 } 275 String label = r.getString(resourceId); 276 assertEquals(label, disconnectCause.getLabel()); 277 } 278 279 /** 280 * Verifies that an ICC_ERROR disconnect cause generates a message which mentions there is no 281 * SIM. 282 */ 283 @Test testIccError()284 public void testIccError() { 285 android.telecom.DisconnectCause tcCause = DisconnectCauseUtil.toTelecomDisconnectCause( 286 DisconnectCause.ICC_ERROR); 287 assertEquals(android.telecom.DisconnectCause.ERROR, tcCause.getCode()); 288 assertNotNull(tcCause.getLabel()); 289 assertNotNull(tcCause.getDescription()); 290 } 291 } 292