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