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