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