1 /*
2  * Copyright (C) 2023 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.server.telecom.tests;
18 
19 
20 import static android.telephony.TelephonyManager.EmergencyCallDiagnosticData;
21 
22 import static org.junit.Assert.assertEquals;
23 import static org.junit.Assert.assertNotEquals;
24 import static org.junit.Assert.assertNotNull;
25 import static org.junit.Assert.assertTrue;
26 import static org.mockito.ArgumentMatchers.eq;
27 import static org.mockito.Mockito.any;
28 import static org.mockito.Mockito.doReturn;
29 import static org.mockito.Mockito.never;
30 import static org.mockito.Mockito.times;
31 import static org.mockito.Mockito.verify;
32 import static org.mockito.Mockito.when;
33 
34 import android.content.ComponentName;
35 import android.net.Uri;
36 import android.os.BugreportManager;
37 import android.os.DropBoxManager;
38 import android.os.UserHandle;
39 import android.telecom.DisconnectCause;
40 import android.telecom.PhoneAccount;
41 import android.telecom.PhoneAccountHandle;
42 import android.telephony.TelephonyManager;
43 
44 import com.android.server.telecom.Call;
45 import com.android.server.telecom.CallState;
46 import com.android.server.telecom.CallerInfoLookupHelper;
47 import com.android.server.telecom.CallsManager;
48 import com.android.server.telecom.ClockProxy;
49 import com.android.server.telecom.EmergencyCallDiagnosticLogger;
50 import com.android.server.telecom.PhoneAccountRegistrar;
51 import com.android.server.telecom.PhoneNumberUtilsAdapter;
52 import com.android.server.telecom.TelecomSystem;
53 import com.android.server.telecom.Timeouts;
54 import com.android.server.telecom.ui.ToastFactory;
55 
56 import org.junit.After;
57 import org.junit.Before;
58 import org.junit.Test;
59 import org.junit.runner.RunWith;
60 import org.junit.runners.JUnit4;
61 import org.mockito.ArgumentCaptor;
62 import org.mockito.Mock;
63 
64 import java.util.HashSet;
65 import java.util.List;
66 import java.util.Set;
67 
68 @RunWith(JUnit4.class)
69 public class EmergencyCallDiagnosticLoggerTest extends TelecomTestCase {
70 
71     private static final ComponentName COMPONENT_NAME_1 = ComponentName
72             .unflattenFromString("com.foo/.Blah");
73     private static final PhoneAccountHandle SIM_1_HANDLE = new PhoneAccountHandle(
74             COMPONENT_NAME_1, "Sim1");
75     private static final PhoneAccount SIM_1_ACCOUNT = new PhoneAccount.
76             Builder(SIM_1_HANDLE, "Sim1")
77             .setCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION
78                     | PhoneAccount.CAPABILITY_CALL_PROVIDER)
79             .setIsEnabled(true)
80             .build();
81     private static final String DROP_BOX_TAG = "ecall_diagnostic_data";
82 
83     private static final long EMERGENCY_CALL_ACTIVE_TIME_THRESHOLD_MILLIS = 100L;
84 
85     private static final long EMERGENCY_CALL_TIME_BEFORE_USER_DISCONNECT_THRESHOLD_MILLIS = 120L;
86 
87     private static final int DAYS_BACK_TO_SEARCH_EMERGENCY_DIAGNOSTIC_ENTRIES = 1;
88     private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() {
89     };
90     EmergencyCallDiagnosticLogger mEmergencyCallDiagnosticLogger;
91     @Mock
92     private Timeouts.Adapter mTimeouts;
93     @Mock
94     private CallsManager mMockCallsManager;
95     @Mock
96     private CallerInfoLookupHelper mMockCallerInfoLookupHelper;
97     @Mock
98     private PhoneAccountRegistrar mMockPhoneAccountRegistrar;
99     @Mock
100     private ClockProxy mMockClockProxy;
101     @Mock
102     private ToastFactory mMockToastProxy;
103     @Mock
104     private PhoneNumberUtilsAdapter mMockPhoneNumberUtilsAdapter;
105 
106     @Mock
107     private TelephonyManager mTm;
108     @Mock
109     private BugreportManager mBrm;
110     @Mock
111     private DropBoxManager mDbm;
112 
113     @Mock
114     private ClockProxy mClockProxy;
115 
116     @Override
117     @Before
setUp()118     public void setUp() throws Exception {
119         super.setUp();
120 
121         doReturn(mMockCallerInfoLookupHelper).when(mMockCallsManager).getCallerInfoLookupHelper();
122         doReturn(mMockPhoneAccountRegistrar).when(mMockCallsManager).getPhoneAccountRegistrar();
123         doReturn(SIM_1_ACCOUNT).when(mMockPhoneAccountRegistrar).getPhoneAccountUnchecked(
124                 eq(SIM_1_HANDLE));
125         when(mTimeouts.getEmergencyCallActiveTimeThresholdMillis()).
126                 thenReturn(EMERGENCY_CALL_ACTIVE_TIME_THRESHOLD_MILLIS);
127         when(mTimeouts.getEmergencyCallTimeBeforeUserDisconnectThresholdMillis()).
128                 thenReturn(EMERGENCY_CALL_TIME_BEFORE_USER_DISCONNECT_THRESHOLD_MILLIS);
129         when(mTimeouts.getDaysBackToSearchEmergencyDiagnosticEntries()).
130                 thenReturn(DAYS_BACK_TO_SEARCH_EMERGENCY_DIAGNOSTIC_ENTRIES);
131         when(mClockProxy.currentTimeMillis()).thenReturn(System.currentTimeMillis());
132         when(mMockCallsManager.getCurrentUserHandle()).thenReturn(UserHandle.CURRENT);
133 
134         mEmergencyCallDiagnosticLogger = new EmergencyCallDiagnosticLogger(mTm, mBrm,
135                 mTimeouts, mDbm, Runnable::run, mClockProxy);
136     }
137 
138     @Override
139     @After
tearDown()140     public void tearDown() throws Exception {
141         super.tearDown();
142         //reset(mTm);
143     }
144 
145     /**
146      * Helper function that creates the call being tested.
147      * Also invokes onStartCreateConnection
148      */
createCall(boolean isEmergencyCall, int direction)149     private Call createCall(boolean isEmergencyCall, int direction) {
150         Call call = getCall();
151         call.setCallDirection(direction);
152         call.setIsEmergencyCall(isEmergencyCall);
153         mEmergencyCallDiagnosticLogger.onStartCreateConnection(call);
154         return call;
155     }
156 
157     /**
158      * @return an instance of {@link Call} for testing purposes.
159      */
getCall()160     private Call getCall() {
161         return new Call(
162                 "1", /* callId */
163                 mContext,
164                 mMockCallsManager,
165                 mLock,
166                 null /* ConnectionServiceRepository */,
167                 mMockPhoneNumberUtilsAdapter,
168                 Uri.parse("tel:6505551212"),
169                 null /* GatewayInfo */,
170                 null /* connectionManagerPhoneAccountHandle */,
171                 SIM_1_HANDLE,
172                 Call.CALL_DIRECTION_OUTGOING,
173                 false /* shouldAttachToExistingConnection*/,
174                 false /* isConference */,
175                 mMockClockProxy,
176                 mMockToastProxy,
177                 mFeatureFlags);
178     }
179 
180     /**
181      * Test that only outgoing emergency calls are tracked
182      */
183     @Test
testNonEmergencyCallNotTracked()184     public void testNonEmergencyCallNotTracked() {
185         //should not be tracked
186         createCall(false, Call.CALL_DIRECTION_OUTGOING);
187         assertEquals(0, mEmergencyCallDiagnosticLogger.getEmergencyCallsMap().size());
188 
189         //should not be tracked (not in scope)
190         createCall(false, Call.CALL_DIRECTION_INCOMING);
191         assertEquals(0, mEmergencyCallDiagnosticLogger.getEmergencyCallsMap().size());
192     }
193 
194     /**
195      * Test that incoming emergency calls are not tracked (not in scope right now)
196      */
197     @Test
testIncomingEmergencyCallsNotTracked()198     public void testIncomingEmergencyCallsNotTracked() {
199         //should not be tracked
200         createCall(true, Call.CALL_DIRECTION_INCOMING);
201         assertEquals(0, mEmergencyCallDiagnosticLogger.getEmergencyCallsMap().size());
202     }
203 
204 
205     /**
206      * Test getDataCollectionTypes(reason)
207      */
208     @Test
testCollectionTypeForReasonDoesNotReturnUnreasonableValues()209     public void testCollectionTypeForReasonDoesNotReturnUnreasonableValues() {
210         int reason = EmergencyCallDiagnosticLogger.REPORT_REASON_RANGE_START + 1;
211         while (reason < EmergencyCallDiagnosticLogger.REPORT_REASON_RANGE_END) {
212             List<Integer> ctypes = EmergencyCallDiagnosticLogger.getDataCollectionTypes(reason);
213             assertNotNull(ctypes);
214             Set<Integer> ctypesSet = new HashSet<>(ctypes);
215 
216             //assert that list is not empty
217             assertNotEquals(0, ctypes.size());
218 
219             //assert no repeated values
220             assertEquals(ctypes.size(), ctypesSet.size());
221 
222             //if bugreport type is present, that should be the only collection type
223             if (ctypesSet.contains(EmergencyCallDiagnosticLogger.COLLECTION_TYPE_BUGREPORT)) {
224                 assertEquals(1, ctypes.size());
225             }
226             reason++;
227         }
228     }
229 
230 
231     /**
232      * Test emergency call reported stuck
233      */
234     @Test
testStuckEmergencyCall()235     public void testStuckEmergencyCall() {
236         Call call = createCall(true, Call.CALL_DIRECTION_OUTGOING);
237         mEmergencyCallDiagnosticLogger.onCallAdded(call);
238         mEmergencyCallDiagnosticLogger.reportStuckCall(call);
239 
240         //for stuck calls, we should always be persisting some data
241         ArgumentCaptor<EmergencyCallDiagnosticData> captor =
242                 ArgumentCaptor.forClass(EmergencyCallDiagnosticData.class);
243         verify(mTm, times(1)).persistEmergencyCallDiagnosticData(eq(DROP_BOX_TAG),
244                 captor.capture());
245         EmergencyCallDiagnosticData ecdData = captor.getValue();
246 
247         assertNotNull(ecdData);
248         assertTrue(
249                 ecdData.isLogcatCollectionEnabled() || ecdData.isTelecomDumpsysCollectionEnabled()
250                         || ecdData.isTelephonyDumpsysCollectionEnabled());
251 
252         //tracking should end
253         assertEquals(0, mEmergencyCallDiagnosticLogger.getEmergencyCallsMap().size());
254     }
255 
256     @Test
testEmergencyCallNeverWentActiveWithNonLocalDisconnectCause()257     public void testEmergencyCallNeverWentActiveWithNonLocalDisconnectCause() {
258         Call call = createCall(true, Call.CALL_DIRECTION_OUTGOING);
259         mEmergencyCallDiagnosticLogger.onCallAdded(call);
260 
261         //call is tracked
262         assertEquals(1, mEmergencyCallDiagnosticLogger.getEmergencyCallsMap().size());
263 
264         call.setDisconnectCause(new DisconnectCause(DisconnectCause.REJECTED));
265         mEmergencyCallDiagnosticLogger.onCallRemoved(call);
266 
267         //for non-local disconnect of non-active call,  we should always be persisting some data
268         ArgumentCaptor<EmergencyCallDiagnosticData> captor =
269                 ArgumentCaptor.forClass(EmergencyCallDiagnosticData.class);
270         verify(mTm, times(1)).persistEmergencyCallDiagnosticData(eq(DROP_BOX_TAG),
271                 captor.capture());
272         EmergencyCallDiagnosticData ecdData = captor.getValue();
273 
274         assertNotNull(ecdData);
275         assertTrue(
276                 ecdData.isLogcatCollectionEnabled() || ecdData.isTelecomDumpsysCollectionEnabled()
277                         || ecdData.isTelephonyDumpsysCollectionEnabled());
278 
279         //tracking should end
280         assertEquals(0, mEmergencyCallDiagnosticLogger.getEmergencyCallsMap().size());
281     }
282 
283     @Test
testEmergencyCallWentActiveForLongDuration_shouldNotCollectDiagnostics()284     public void testEmergencyCallWentActiveForLongDuration_shouldNotCollectDiagnostics()
285             throws Exception {
286         Call call = createCall(true, Call.CALL_DIRECTION_OUTGOING);
287         mEmergencyCallDiagnosticLogger.onCallAdded(call);
288 
289         //call went active
290         mEmergencyCallDiagnosticLogger.onCallStateChanged(call, CallState.DIALING,
291                 CallState.ACTIVE);
292 
293         //return large value for time when call is disconnected
294         when(mClockProxy.currentTimeMillis()).thenReturn(System.currentTimeMillis() + 10000L);
295 
296         call.setDisconnectCause(new DisconnectCause(DisconnectCause.ERROR));
297         mEmergencyCallDiagnosticLogger.onCallRemoved(call);
298 
299         //no diagnostic data should be persisted
300         verify(mTm, never()).persistEmergencyCallDiagnosticData(eq(DROP_BOX_TAG),
301                 any());
302 
303         //tracking should end
304         assertEquals(0, mEmergencyCallDiagnosticLogger.getEmergencyCallsMap().size());
305     }
306 
307 }
308