Merge "export broadcast receiver in BlockedNumberActivity" into udc-dev
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 32b5556..e4dc6af 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -3914,6 +3914,10 @@
return mCallDirection == CALL_DIRECTION_UNKNOWN;
}
+ public boolean isOutgoing() {
+ return mCallDirection == CALL_DIRECTION_OUTGOING;
+ }
+
/**
* Determines if this call is in a disconnecting state.
*
diff --git a/src/com/android/server/telecom/CallAnomalyWatchdog.java b/src/com/android/server/telecom/CallAnomalyWatchdog.java
index f7020f4..045671e 100644
--- a/src/com/android/server/telecom/CallAnomalyWatchdog.java
+++ b/src/com/android/server/telecom/CallAnomalyWatchdog.java
@@ -43,6 +43,8 @@
* Watchdog class responsible for detecting potential anomalous conditions for {@link Call}s.
*/
public class CallAnomalyWatchdog extends CallsManagerListenerBase implements Call.Listener {
+ private final EmergencyCallDiagnosticLogger mEmergencyCallDiagnosticLogger;
+
/**
* Class used to track the call state as it pertains to the watchdog. The watchdog cares about
* both the call state and whether a {@link ConnectionService} has finished creating the
@@ -146,11 +148,13 @@
public CallAnomalyWatchdog(ScheduledExecutorService executorService,
TelecomSystem.SyncRoot lock,
- Timeouts.Adapter timeoutAdapter, ClockProxy clockProxy) {
+ Timeouts.Adapter timeoutAdapter, ClockProxy clockProxy,
+ EmergencyCallDiagnosticLogger emergencyCallDiagnosticLogger) {
mScheduledExecutorService = executorService;
mLock = lock;
mTimeoutAdapter = timeoutAdapter;
mClockProxy = clockProxy;
+ mEmergencyCallDiagnosticLogger = emergencyCallDiagnosticLogger;
}
/**
@@ -357,6 +361,7 @@
mAnomalyReporter.reportAnomaly(
WATCHDOG_DISCONNECTED_STUCK_EMERGENCY_CALL_UUID,
WATCHDOG_DISCONNECTED_STUCK_EMERGENCY_CALL_MSG);
+ mEmergencyCallDiagnosticLogger.reportStuckCall(call);
} else {
mAnomalyReporter.reportAnomaly(
WATCHDOG_DISCONNECTED_STUCK_CALL_UUID,
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index f36ba1b..b9a83e5 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -117,6 +117,7 @@
import com.android.server.telecom.bluetooth.BluetoothStateReceiver;
import com.android.server.telecom.callfiltering.BlockCheckerAdapter;
import com.android.server.telecom.callfiltering.BlockCheckerFilter;
+import com.android.server.telecom.callfiltering.BlockedNumbersAdapter;
import com.android.server.telecom.callfiltering.CallFilterResultCallback;
import com.android.server.telecom.callfiltering.CallFilteringResult;
import com.android.server.telecom.callfiltering.CallFilteringResult.Builder;
@@ -124,11 +125,9 @@
import com.android.server.telecom.callfiltering.DirectToVoicemailFilter;
import com.android.server.telecom.callfiltering.DndCallFilter;
import com.android.server.telecom.callfiltering.IncomingCallFilterGraph;
-import com.android.server.telecom.callfiltering.BlockedNumbersAdapter;
import com.android.server.telecom.callredirection.CallRedirectionProcessor;
import com.android.server.telecom.components.ErrorDialogActivity;
import com.android.server.telecom.components.TelecomBroadcastReceiver;
-import com.android.server.telecom.settings.BlockedNumbersUtil;
import com.android.server.telecom.stats.CallFailureCause;
import com.android.server.telecom.ui.AudioProcessingNotification;
import com.android.server.telecom.ui.CallRedirectionTimeoutDialogActivity;
@@ -440,6 +439,8 @@
private final RoleManagerAdapter mRoleManagerAdapter;
private final CallEndpointController mCallEndpointController;
private final CallAnomalyWatchdog mCallAnomalyWatchdog;
+
+ private final EmergencyCallDiagnosticLogger mEmergencyCallDiagnosticLogger;
private final CallStreamingController mCallStreamingController;
private final BlockedNumbersAdapter mBlockedNumbersAdapter;
private final TransactionManager mTransactionManager;
@@ -552,7 +553,9 @@
Ringer.AccessibilityManagerAdapter accessibilityManagerAdapter,
Executor asyncTaskExecutor,
BlockedNumbersAdapter blockedNumbersAdapter,
- TransactionManager transactionManager) {
+ TransactionManager transactionManager,
+ EmergencyCallDiagnosticLogger emergencyCallDiagnosticLogger) {
+
mContext = context;
mLock = lock;
mPhoneNumberUtilsAdapter = phoneNumberUtilsAdapter;
@@ -569,6 +572,7 @@
mTimeoutsAdapter = timeoutsAdapter;
mEmergencyCallHelper = emergencyCallHelper;
mCallerInfoLookupHelper = callerInfoLookupHelper;
+ mEmergencyCallDiagnosticLogger = emergencyCallDiagnosticLogger;
mDtmfLocalTonePlayer =
new DtmfLocalTonePlayer(new DtmfLocalTonePlayer.ToneGeneratorProxy());
@@ -653,6 +657,7 @@
mListeners.add(mProximitySensorManager);
mListeners.add(audioProcessingNotification);
mListeners.add(callAnomalyWatchdog);
+ mListeners.add(mEmergencyCallDiagnosticLogger);
mListeners.add(mCallStreamingController);
// this needs to be after the mCallAudioManager
@@ -1277,6 +1282,10 @@
return mEmergencyCallHelper;
}
+ EmergencyCallDiagnosticLogger getEmergencyCallDiagnosticLogger() {
+ return mEmergencyCallDiagnosticLogger;
+ }
+
public DefaultDialerCache getDefaultDialerCache() {
return mDefaultDialerCache;
}
@@ -3178,7 +3187,10 @@
isEmergency);
// Only one SIM PhoneAccount can be active at one time for DSDS. Only that SIM PhoneAccount
// should be available if a call is already active on the SIM account.
- if (!isDsdaCallingPossible()) {
+ // Similarly, the emergency call should be attempted over the same PhoneAccount as the
+ // ongoing call. However, if the ongoing call is over cross-SIM registration, then the
+ // emergency call will be attempted over a different Phone object at a later stage.
+ if (isEmergency || !isDsdaCallingPossible()) {
List<PhoneAccountHandle> simAccounts =
mPhoneAccountRegistrar.getSimPhoneAccountsOfCurrentUser();
PhoneAccountHandle ongoingCallAccount = null;
@@ -5378,7 +5390,7 @@
*
* @param pw The {@code IndentingPrintWriter} to write the state to.
*/
- public void dump(IndentingPrintWriter pw) {
+ public void dump(IndentingPrintWriter pw, String[] args) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
if (mCalls != null) {
pw.println("mCalls: ");
@@ -5440,6 +5452,14 @@
mCallAnomalyWatchdog.dump(pw);
pw.decreaseIndent();
}
+
+ if (mEmergencyCallDiagnosticLogger != null) {
+ pw.println("mEmergencyCallDiagnosticLogger:");
+ pw.increaseIndent();
+ mEmergencyCallDiagnosticLogger.dump(pw, args);
+ pw.decreaseIndent();
+ }
+
if (mDefaultDialerCache != null) {
pw.println("mDefaultDialerCache:");
pw.increaseIndent();
diff --git a/src/com/android/server/telecom/EmergencyCallDiagnosticLogger.java b/src/com/android/server/telecom/EmergencyCallDiagnosticLogger.java
new file mode 100644
index 0000000..af79da3
--- /dev/null
+++ b/src/com/android/server/telecom/EmergencyCallDiagnosticLogger.java
@@ -0,0 +1,438 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom;
+
+import static android.telephony.TelephonyManager.EmergencyCallDiagnosticParams;
+
+import android.os.BugreportManager;
+import android.os.DropBoxManager;
+import android.provider.DeviceConfig;
+import android.telecom.DisconnectCause;
+import android.telecom.Log;
+import android.telephony.TelephonyManager;
+import android.util.LocalLog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executor;
+
+/**
+ * The EmergencyCallDiagnosticsLogger monitors information required to diagnose potential outgoing
+ * ecall failures on the device. When a potential failure is detected, it calls a Telephony API to
+ * persist relevant information (dumpsys, logcat etc.) to the dropbox. This acts as a central place
+ * to determine when and what to collect.
+ *
+ * <p>When a bugreport is triggered, this module will read the dropbox entries and add them to the
+ * telecom dump.
+ */
+public class EmergencyCallDiagnosticLogger extends CallsManagerListenerBase
+ implements Call.Listener {
+
+ public static final int REPORT_REASON_RANGE_START = -1; //!!DO NOT CHANGE
+ public static final int REPORT_REASON_RANGE_END = 5; //increment this and add new reason above
+ public static final int COLLECTION_TYPE_BUGREPORT = 10;
+ public static final int COLLECTION_TYPE_TELECOM_STATE = 11;
+ public static final int COLLECTION_TYPE_TELEPHONY_STATE = 12;
+ public static final int COLLECTION_TYPE_LOGCAT_BUFFERS = 13;
+ private static final int REPORT_REASON_STUCK_CALL_DETECTED = 0;
+ private static final int REPORT_REASON_INACTIVE_CALL_TERMINATED_BY_USER_AFTER_DELAY = 1;
+ private static final int REPORT_REASON_CALL_FAILED = 2;
+ private static final int REPORT_REASON_CALL_CREATED_BUT_NEVER_ADDED = 3;
+ private static final int REPORT_REASON_SHORT_DURATION_AFTER_GOING_ACTIVE = 4;
+ private static final String DROPBOX_TAG = "ecall_diagnostic_data";
+ private static final String ENABLE_BUGREPORT_COLLECTION_FOR_EMERGENCY_CALL_DIAGNOSTICS =
+ "enable_bugreport_collection_for_emergency_call_diagnostics";
+ private static final String ENABLE_TELECOM_DUMP_COLLECTION_FOR_EMERGENCY_CALL_DIAGNOSTICS =
+ "enable_telecom_dump_collection_for_emergency_call_diagnostics";
+
+ private static final String ENABLE_LOGCAT_COLLECTION_FOR_EMERGENCY_CALL_DIAGNOSTICS =
+ "enable_logcat_collection_for_emergency_call_diagnostics";
+ private static final String ENABLE_TELEPHONY_DUMP_COLLECTION_FOR_EMERGENCY_CALL_DIAGNOSTICS =
+ "enable_telephony_dump_collection_for_emergency_call_diagnostics";
+
+ private static final String DUMPSYS_ARG_FOR_DIAGNOSTICS = "EmergencyDiagnostics";
+
+ // max text size to read from dropbox entry
+ private static final int DEFAULT_MAX_READ_BYTES_PER_DROP_BOX_ENTRY = 500000;
+ private static final String MAX_BYTES_PER_DROP_BOX_ENTRY = "max_bytes_per_dropbox_entry";
+ private static final int MAX_DROPBOX_ENTRIES_TO_DUMP = 6;
+
+ private final Timeouts.Adapter mTimeoutAdapter;
+ // This map holds all calls, but keeps pruning non-emergency calls when we can determine it
+ private final Map<Call, CallEventTimestamps> mEmergencyCallsMap = new ConcurrentHashMap<>(2);
+ private final DropBoxManager mDropBoxManager;
+ private final LocalLog mLocalLog = new LocalLog(10);
+ private final TelephonyManager mTelephonyManager;
+ private final BugreportManager mBugreportManager;
+ private final Executor mAsyncTaskExecutor;
+ private final ClockProxy mClockProxy;
+
+ public EmergencyCallDiagnosticLogger(
+ TelephonyManager tm,
+ BugreportManager brm,
+ Timeouts.Adapter timeoutAdapter, DropBoxManager dropBoxManager,
+ Executor asyncTaskExecutor, ClockProxy clockProxy) {
+ mTimeoutAdapter = timeoutAdapter;
+ mDropBoxManager = dropBoxManager;
+ mTelephonyManager = tm;
+ mBugreportManager = brm;
+ mAsyncTaskExecutor = asyncTaskExecutor;
+ mClockProxy = clockProxy;
+ }
+
+ // this calculates time from ACTIVE --> removed
+ private static long getCallTimeInActiveStateSec(CallEventTimestamps ts) {
+ if (ts.getCallActiveTime() == 0 || ts.getCallRemovedTime() == 0) {
+ return 0;
+ } else {
+ return (ts.getCallRemovedTime() - ts.getCallActiveTime()) / 1000;
+ }
+ }
+
+ // this calculates time from call created --> removed
+ private static long getTotalCallTimeSec(CallEventTimestamps ts) {
+ if (ts.getCallRemovedTime() == 0 || ts.getCallCreatedTime() == 0) {
+ return 0;
+ } else {
+ return (ts.getCallRemovedTime() - ts.getCallCreatedTime()) / 1000;
+ }
+ }
+
+ //determines what to collect based on fail reason
+ //if COLLECTION_TYPE_BUGREPORT is present in the returned list, then that
+ //should be the only collection type in the list
+ @VisibleForTesting
+ public static List<Integer> getDataCollectionTypes(int reason) {
+ switch (reason) {
+ case REPORT_REASON_SHORT_DURATION_AFTER_GOING_ACTIVE:
+ return Arrays.asList(COLLECTION_TYPE_TELECOM_STATE);
+ case REPORT_REASON_CALL_CREATED_BUT_NEVER_ADDED:
+ return Arrays.asList(
+ COLLECTION_TYPE_TELECOM_STATE, COLLECTION_TYPE_TELEPHONY_STATE);
+ case REPORT_REASON_CALL_FAILED:
+ case REPORT_REASON_INACTIVE_CALL_TERMINATED_BY_USER_AFTER_DELAY:
+ case REPORT_REASON_STUCK_CALL_DETECTED:
+ return Arrays.asList(
+ COLLECTION_TYPE_TELECOM_STATE,
+ COLLECTION_TYPE_TELEPHONY_STATE,
+ COLLECTION_TYPE_LOGCAT_BUFFERS);
+ default:
+ }
+ return new ArrayList<>();
+ }
+
+ private int getMaxBytesPerDropboxEntry() {
+ return DeviceConfig.getInt(DeviceConfig.NAMESPACE_TELEPHONY,
+ MAX_BYTES_PER_DROP_BOX_ENTRY, DEFAULT_MAX_READ_BYTES_PER_DROP_BOX_ENTRY);
+ }
+
+ @VisibleForTesting
+ public Map<Call, CallEventTimestamps> getEmergencyCallsMap() {
+ return mEmergencyCallsMap;
+ }
+
+ private void triggerDiagnosticsCollection(Call call, int reason) {
+ Log.i(this, "Triggering diagnostics for call %s reason: %d", call.getId(), reason);
+ List<Integer> dataCollectionTypes = getDataCollectionTypes(reason);
+ boolean invokeTelephonyPersistApi = false;
+ CallEventTimestamps ts = mEmergencyCallsMap.get(call);
+ EmergencyCallDiagnosticParams dp =
+ new EmergencyCallDiagnosticParams();
+ for (Integer dataCollectionType : dataCollectionTypes) {
+ switch (dataCollectionType) {
+ case COLLECTION_TYPE_TELECOM_STATE:
+ if (isTelecomDumpCollectionEnabled()) {
+ dp.setTelecomDumpSysCollection(true);
+ invokeTelephonyPersistApi = true;
+ }
+ break;
+ case COLLECTION_TYPE_TELEPHONY_STATE:
+ if (isTelephonyDumpCollectionEnabled()) {
+ dp.setTelephonyDumpSysCollection(true);
+ invokeTelephonyPersistApi = true;
+ }
+ break;
+ case COLLECTION_TYPE_LOGCAT_BUFFERS:
+ if (isLogcatCollectionEnabled()) {
+ dp.setLogcatCollection(true, ts.getCallCreatedTime());
+ invokeTelephonyPersistApi = true;
+ }
+ break;
+ case COLLECTION_TYPE_BUGREPORT:
+ if (isBugreportCollectionEnabled()) {
+ mAsyncTaskExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ persistBugreport();
+ }
+ });
+ }
+ break;
+ default:
+ }
+ }
+ if (invokeTelephonyPersistApi) {
+ mAsyncTaskExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ Log.i(this, "Requesting Telephony to persist data %s", dp.toString());
+ try {
+ mTelephonyManager.persistEmergencyCallDiagnosticData(DROPBOX_TAG, dp);
+ } catch (Exception e) {
+ Log.w(this,
+ "Exception while invoking "
+ + "Telephony#persistEmergencyCallDiagnosticData %s",
+ e.toString());
+ }
+ }
+ });
+ }
+ }
+
+ private boolean isBugreportCollectionEnabled() {
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_TELEPHONY,
+ ENABLE_BUGREPORT_COLLECTION_FOR_EMERGENCY_CALL_DIAGNOSTICS,
+ false);
+ }
+
+ private boolean isTelecomDumpCollectionEnabled() {
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_TELEPHONY,
+ ENABLE_TELECOM_DUMP_COLLECTION_FOR_EMERGENCY_CALL_DIAGNOSTICS,
+ true);
+ }
+
+ private boolean isLogcatCollectionEnabled() {
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_TELEPHONY,
+ ENABLE_LOGCAT_COLLECTION_FOR_EMERGENCY_CALL_DIAGNOSTICS,
+ true);
+ }
+
+ private boolean isTelephonyDumpCollectionEnabled() {
+ return DeviceConfig.getBoolean(
+ DeviceConfig.NAMESPACE_TELEPHONY,
+ ENABLE_TELEPHONY_DUMP_COLLECTION_FOR_EMERGENCY_CALL_DIAGNOSTICS,
+ true);
+ }
+
+ private void persistBugreport() {
+ if (isBugreportCollectionEnabled()) {
+ // TODO:
+ }
+ }
+
+ private boolean shouldTrackCall(Call call) {
+ return (call != null && call.isEmergencyCall() && call.isOutgoing());
+ }
+
+ public void reportStuckCall(Call call) {
+ if (shouldTrackCall(call)) {
+ Log.i(this, "Triggering diagnostics for stuck call %s", call.getId());
+ triggerDiagnosticsCollection(call, REPORT_REASON_STUCK_CALL_DETECTED);
+ call.removeListener(this);
+ mEmergencyCallsMap.remove(call);
+ }
+ }
+
+ @Override
+ public void onStartCreateConnection(Call call) {
+ if (shouldTrackCall(call)) {
+ long currentTime = mClockProxy.currentTimeMillis();
+ call.addListener(this);
+ Log.i(this, "Tracking call %s timestamp: %d", call.getId(), currentTime);
+ mEmergencyCallsMap.put(call, new CallEventTimestamps(currentTime));
+ }
+ }
+
+ @Override
+ public void onCreateConnectionFailed(Call call) {
+ if (shouldTrackCall(call)) {
+ Log.i(this, "Triggering diagnostics for call %s that was never added", call.getId());
+ triggerDiagnosticsCollection(call, REPORT_REASON_CALL_CREATED_BUT_NEVER_ADDED);
+ call.removeListener(this);
+ mEmergencyCallsMap.remove(call);
+ }
+ }
+
+ /**
+ * Override of {@link CallsManagerListenerBase} to track when calls are removed
+ *
+ * @param call the call
+ */
+ @Override
+ public void onCallRemoved(Call call) {
+ if (call != null && (mEmergencyCallsMap.get(call) != null)) {
+ call.removeListener(this);
+
+ CallEventTimestamps ts = mEmergencyCallsMap.get(call);
+ long currentTime = mClockProxy.currentTimeMillis();
+ ts.setCallRemovedTime(currentTime);
+
+ maybeTriggerDiagnosticsCollection(call, ts);
+ mEmergencyCallsMap.remove(call);
+ }
+ }
+
+ // !NOTE!: this method should only be called after we get onCallRemoved
+ private void maybeTriggerDiagnosticsCollection(Call removedCall, CallEventTimestamps ts) {
+ Log.i(this, "Evaluating emergency call for diagnostic logging: %s", removedCall.getId());
+ boolean wentActive = (ts.getCallActiveTime() != 0);
+ long callActiveTimeSec = (wentActive ? getCallTimeInActiveStateSec(ts) : 0);
+ long timeSinceCallCreatedSec = getTotalCallTimeSec(ts);
+ int dc = removedCall.getDisconnectCause().getCode();
+
+ if (wentActive) {
+ if (callActiveTimeSec
+ < mTimeoutAdapter.getEmergencyCallActiveTimeThresholdMillis() / 1000) {
+ // call connected but did not go on for long
+ triggerDiagnosticsCollection(
+ removedCall, REPORT_REASON_SHORT_DURATION_AFTER_GOING_ACTIVE);
+ }
+ } else {
+
+ if (dc == DisconnectCause.LOCAL
+ && timeSinceCallCreatedSec
+ > mTimeoutAdapter.getEmergencyCallTimeBeforeUserDisconnectThresholdMillis()
+ / 1000) {
+ // call was disconnected by the user (but not immediately)
+ triggerDiagnosticsCollection(
+ removedCall, REPORT_REASON_INACTIVE_CALL_TERMINATED_BY_USER_AFTER_DELAY);
+ } else if (dc != DisconnectCause.LOCAL) {
+ // this can be a case for a full bugreport
+ triggerDiagnosticsCollection(removedCall, REPORT_REASON_CALL_FAILED);
+ }
+ }
+ }
+
+ /**
+ * Override of {@link com.android.server.telecom.CallsManager.CallsManagerListener} to track
+ * call state changes.
+ *
+ * @param call the call
+ * @param oldState its old state
+ * @param newState the new state
+ */
+ @Override
+ public void onCallStateChanged(Call call, int oldState, int newState) {
+
+ if (call != null && mEmergencyCallsMap.get(call) != null && newState == CallState.ACTIVE) {
+ CallEventTimestamps ts = mEmergencyCallsMap.get(call);
+ if (ts != null) {
+ long currentTime = mClockProxy.currentTimeMillis();
+ ts.setCallActiveTime(currentTime);
+ }
+ }
+ }
+
+ private void dumpDiagnosticDataFromDropbox(IndentingPrintWriter pw) {
+ pw.increaseIndent();
+ pw.println("PERSISTED DIAGNOSTIC DATA FROM DROP BOX");
+ int totalEntriesDumped = 0;
+ long currentTime = mClockProxy.currentTimeMillis();
+ long entriesAfterTime =
+ currentTime - (mTimeoutAdapter.getDaysBackToSearchEmergencyDiagnosticEntries() * 24
+ * 60L * 60L * 1000L);
+ Log.i(this, "current time: %d entriesafter: %d", currentTime, entriesAfterTime);
+ DropBoxManager.Entry entry;
+ entry = mDropBoxManager.getNextEntry(DROPBOX_TAG, entriesAfterTime);
+ while (entry != null) {
+ Log.i(this, "found entry with ts: %d", entry.getTimeMillis());
+ String content[] = entry.getText(getMaxBytesPerDropboxEntry()).split(
+ System.lineSeparator());
+ long entryTime = entry.getTimeMillis();
+ if (content != null) {
+ pw.increaseIndent();
+ pw.println("------------BEGIN ENTRY (" + entryTime + ")--------");
+ for (String line : content) {
+ pw.println(line);
+ }
+ pw.println("--------END ENTRY--------");
+ pw.decreaseIndent();
+ totalEntriesDumped++;
+ }
+ entry = mDropBoxManager.getNextEntry(DROPBOX_TAG, entryTime);
+ if (totalEntriesDumped > MAX_DROPBOX_ENTRIES_TO_DUMP) {
+ /*
+ Since Emergency calls are a rare/once in a lifetime time occurrence for most users,
+ we should not be seeing too many entries. This code just guards against edge case
+ like load testing, b2b failures etc. We may accumulate a lot of dropbox entries in
+ such cases, but we limit to dumping only MAX_DROPBOX_ENTRIES_TO_DUMP in the
+ bugreport
+
+ The Dropbox API in its current state does not allow to query Entries in reverse
+ chronological order efficiently.
+ */
+
+ Log.i(this, "Skipping dump for remaining entries. dumped :%d", totalEntriesDumped);
+ break;
+ }
+ }
+ pw.println("END OF PERSISTED DIAGNOSTIC DATA FROM DROP BOX");
+ pw.decreaseIndent();
+ }
+
+ public void dump(IndentingPrintWriter pw, String[] args) {
+ pw.increaseIndent();
+ mLocalLog.dump(pw);
+ pw.decreaseIndent();
+ if (args != null && args.length > 0 && args[0].equals(DUMPSYS_ARG_FOR_DIAGNOSTICS)) {
+ //dont read dropbox entries since this dump is triggered by telephony for diagnostics
+ Log.i(this, "skipped dumping diagnostic data");
+ return;
+ }
+ dumpDiagnosticDataFromDropbox(pw);
+ }
+
+ private static class CallEventTimestamps {
+
+ private final long mCallCreatedTime;
+ private long mCallActiveTime;
+ private long mCallRemovedTime;
+
+ public CallEventTimestamps(long createdTime) {
+ mCallCreatedTime = createdTime;
+ }
+
+ public long getCallActiveTime() {
+ return mCallActiveTime;
+ }
+
+ public void setCallActiveTime(long callActiveTime) {
+ this.mCallActiveTime = callActiveTime;
+ }
+
+ public long getCallCreatedTime() {
+ return mCallCreatedTime;
+ }
+
+ public long getCallRemovedTime() {
+ return mCallRemovedTime;
+ }
+
+ public void setCallRemovedTime(long callRemovedTime) {
+ this.mCallRemovedTime = callRemovedTime;
+ }
+ }
+}
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index 253f5c5..c5f50e5 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -19,6 +19,7 @@
import static android.Manifest.permission.CALL_PHONE;
import static android.Manifest.permission.CALL_PRIVILEGED;
import static android.Manifest.permission.DUMP;
+import static android.Manifest.permission.MANAGE_OWN_CALLS;
import static android.Manifest.permission.MODIFY_PHONE_STATE;
import static android.Manifest.permission.READ_PHONE_NUMBERS;
import static android.Manifest.permission.READ_PHONE_STATE;
@@ -26,11 +27,10 @@
import static android.Manifest.permission.READ_SMS;
import static android.Manifest.permission.REGISTER_SIM_SUBSCRIPTION;
import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
-import static android.Manifest.permission.MANAGE_OWN_CALLS;
import static android.telecom.CallAttributes.DIRECTION_INCOMING;
import static android.telecom.CallAttributes.DIRECTION_OUTGOING;
-import static android.telecom.TelecomManager.TELECOM_TRANSACTION_SUCCESS;
import static android.telecom.CallException.CODE_ERROR_UNKNOWN;
+import static android.telecom.TelecomManager.TELECOM_TRANSACTION_SUCCESS;
import android.Manifest;
import android.app.ActivityManager;
@@ -58,7 +58,6 @@
import android.provider.BlockedNumberContract;
import android.provider.Settings;
import android.telecom.CallAttributes;
-
import android.telecom.CallException;
import android.telecom.Log;
import android.telecom.PhoneAccount;
@@ -1929,19 +1928,21 @@
}
- if (args.length > 0 && Analytics.ANALYTICS_DUMPSYS_ARG.equals(args[0])) {
+ if (args != null && args.length > 0 && Analytics.ANALYTICS_DUMPSYS_ARG.equals(
+ args[0])) {
Binder.withCleanCallingIdentity(() ->
Analytics.dumpToEncodedProto(mContext, writer, args));
return;
}
- boolean isTimeLineView = (args.length > 0 && TIME_LINE_ARG.equalsIgnoreCase(args[0]));
+ boolean isTimeLineView =
+ (args != null && args.length > 0 && TIME_LINE_ARG.equalsIgnoreCase(args[0]));
final IndentingPrintWriter pw = new IndentingPrintWriter(writer, " ");
if (mCallsManager != null) {
pw.println("CallsManager: ");
pw.increaseIndent();
- mCallsManager.dump(pw);
+ mCallsManager.dump(pw, args);
pw.decreaseIndent();
pw.println("PhoneAccountRegistrar: ");
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index 8984b2b..8477d49 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -26,11 +26,14 @@
import android.content.ServiceConnection;
import android.content.pm.ResolveInfo;
import android.net.Uri;
+import android.os.BugreportManager;
+import android.os.DropBoxManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.telecom.Log;
import android.telecom.PhoneAccountHandle;
import android.telephony.AnomalyReporter;
+import android.telephony.TelephonyManager;
import android.widget.Toast;
import androidx.annotation.NonNull;
@@ -336,11 +339,17 @@
}
};
+ EmergencyCallDiagnosticLogger emergencyCallDiagnosticLogger =
+ new EmergencyCallDiagnosticLogger(mContext.getSystemService(
+ TelephonyManager.class), mContext.getSystemService(
+ BugreportManager.class), timeoutsAdapter, mContext.getSystemService(
+ DropBoxManager.class), asyncTaskExecutor, clockProxy);
+
CallAnomalyWatchdog callAnomalyWatchdog = new CallAnomalyWatchdog(
Executors.newSingleThreadScheduledExecutor(),
- mLock, timeoutsAdapter, clockProxy);
- TransactionManager transactionManager = TransactionManager.getInstance();
+ mLock, timeoutsAdapter, clockProxy, emergencyCallDiagnosticLogger);
+ TransactionManager transactionManager = TransactionManager.getInstance();
mCallsManager = new CallsManager(
mContext,
mLock,
@@ -376,7 +385,9 @@
accessibilityManagerAdapter,
asyncTaskExecutor,
blockedNumbersAdapter,
- transactionManager);
+ transactionManager,
+ emergencyCallDiagnosticLogger);
+
mIncomingCallNotifier = incomingCallNotifier;
incomingCallNotifier.setCallsManagerProxy(new IncomingCallNotifier.CallsManagerProxy() {
@Override
diff --git a/src/com/android/server/telecom/Timeouts.java b/src/com/android/server/telecom/Timeouts.java
index 39c86ad..1ba06ec 100644
--- a/src/com/android/server/telecom/Timeouts.java
+++ b/src/com/android/server/telecom/Timeouts.java
@@ -20,8 +20,8 @@
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.telecom.CallDiagnosticService;
-import android.telecom.CallRedirectionService;
import android.telecom.CallDiagnostics;
+import android.telecom.CallRedirectionService;
import android.telephony.ims.ImsReasonInfo;
import java.util.concurrent.TimeUnit;
@@ -112,12 +112,46 @@
public long getNonVoipEmergencyCallIntermediateStateTimeoutMillis() {
return Timeouts.getNonVoipEmergencyCallIntermediateStateTimeoutMillis();
}
+
+ public long getEmergencyCallTimeBeforeUserDisconnectThresholdMillis(){
+ return Timeouts.getEmergencyCallTimeBeforeUserDisconnectThresholdMillis();
+ }
+
+ public long getEmergencyCallActiveTimeThresholdMillis(){
+ return Timeouts.getEmergencyCallActiveTimeThresholdMillis();
+ }
+
+ public int getDaysBackToSearchEmergencyDiagnosticEntries(){
+ return Timeouts.getDaysBackToSearchEmergencyDiagnosticEntries();
+
+ }
}
/** A prefix to use for all keys so to not clobber the global namespace. */
private static final String PREFIX = "telecom.";
/**
+ * threshold used to filter out ecalls that the user may have dialed by mistake
+ * It is used only when the disconnect cause is LOCAL by EmergencyDiagnosticLogger
+ */
+ private static final String EMERGENCY_CALL_TIME_BEFORE_USER_DISCONNECT_THRESHOLD_MILLIS =
+ "emergency_call_time_before_user_disconnect_threshold_millis";
+
+ /**
+ * Returns the threshold used to detect ecalls that transition to active but only for a very
+ * short duration. These short duration active calls can result in Diagnostic data collection.
+ */
+ private static final String EMERGENCY_CALL_ACTIVE_TIME_THRESHOLD_MILLIS =
+ "emergency_call_active_time_threshold_millis";
+
+ /**
+ * Time in Days that is used to filter out old dropbox entries for emergency call diagnostic
+ * data. Entries older than this are ignored
+ */
+ private static final String DAYS_BACK_TO_SEARCH_EMERGENCY_DROP_BOX_ENTRIES =
+ "days_back_to_search_emergency_drop_box_entries";
+
+ /**
* A prefix to use for {@link DeviceConfig} for the transitory state timeout of
* VoIP Call, in millis.
*/
@@ -335,6 +369,36 @@
TRANSITORY_STATE_VOIP_NORMAL_TIMEOUT_MILLIS, 5000L);
}
+
+ /**
+ * Returns the threshold used to filter out ecalls that the user may have dialed by mistake
+ * It is used only when the disconnect cause is LOCAL by EmergencyDiagnosticLogger
+ * @return the threshold in milliseconds
+ */
+ public static long getEmergencyCallTimeBeforeUserDisconnectThresholdMillis() {
+ return DeviceConfig.getLong(DeviceConfig.NAMESPACE_TELEPHONY,
+ EMERGENCY_CALL_TIME_BEFORE_USER_DISCONNECT_THRESHOLD_MILLIS, 20000L);
+ }
+
+ /**
+ * Returns the threshold used to detect ecalls that transition to active but only for a very
+ * short duration. These short duration active calls can result in Diagnostic data collection.
+ * @return the threshold in milliseconds
+ */
+ public static long getEmergencyCallActiveTimeThresholdMillis() {
+ return DeviceConfig.getLong(DeviceConfig.NAMESPACE_TELEPHONY,
+ EMERGENCY_CALL_ACTIVE_TIME_THRESHOLD_MILLIS, 15000L);
+ }
+
+ /**
+ * Time in Days that is used to filter out old dropbox entries for emergency call diagnostic
+ * data. Entries older than this are ignored
+ */
+ public static int getDaysBackToSearchEmergencyDiagnosticEntries() {
+ return DeviceConfig.getInt(DeviceConfig.NAMESPACE_TELEPHONY,
+ DAYS_BACK_TO_SEARCH_EMERGENCY_DROP_BOX_ENTRIES, 30);
+ }
+
/**
* Returns the duration of time an emergency VoIP call can be in a transitory state before
* Telecom will try to clean up the call.
diff --git a/tests/src/com/android/server/telecom/tests/CallAnomalyWatchdogTest.java b/tests/src/com/android/server/telecom/tests/CallAnomalyWatchdogTest.java
index 531d194..7e197fe 100644
--- a/tests/src/com/android/server/telecom/tests/CallAnomalyWatchdogTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAnomalyWatchdogTest.java
@@ -39,6 +39,7 @@
import com.android.server.telecom.CallsManager;
import com.android.server.telecom.ClockProxy;
import com.android.server.telecom.ConnectionServiceWrapper;
+import com.android.server.telecom.EmergencyCallDiagnosticLogger;
import com.android.server.telecom.PhoneAccountRegistrar;
import com.android.server.telecom.PhoneNumberUtilsAdapter;
import com.android.server.telecom.TelecomSystem;
@@ -87,6 +88,8 @@
@Mock private ConnectionServiceWrapper mMockConnectionService;
@Mock private AnomalyReporterAdapter mAnomalyReporterAdapter;
+ @Mock private EmergencyCallDiagnosticLogger mMockEmergencyCallDiagnosticLogger;
+
@Override
@Before
public void setUp() throws Exception {
@@ -118,7 +121,7 @@
doReturn(new ComponentName(mContext, CallTest.class))
.when(mMockConnectionService).getComponentName();
mCallAnomalyWatchdog = new CallAnomalyWatchdog(mTestScheduledExecutorService, mLock,
- mTimeouts, mMockClockProxy);
+ mTimeouts, mMockClockProxy, mMockEmergencyCallDiagnosticLogger);
mCallAnomalyWatchdog.setAnomalyReporterAdapter(mAnomalyReporterAdapter);
}
diff --git a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
index c467671..7ea3568 100644
--- a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
@@ -59,17 +59,18 @@
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.BlockedNumberContract;
-import android.telecom.CallerInfo;
+import android.telecom.CallException;
import android.telecom.CallScreeningService;
+import android.telecom.CallerInfo;
import android.telecom.Connection;
import android.telecom.DisconnectCause;
import android.telecom.GatewayInfo;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
-import android.telecom.CallException;
import android.telecom.VideoProfile;
import android.telephony.CarrierConfigManager;
+import android.telephony.PhoneCapability;
import android.telephony.TelephonyManager;
import android.test.suitebuilder.annotation.MediumTest;
import android.test.suitebuilder.annotation.SmallTest;
@@ -83,9 +84,9 @@
import com.android.server.telecom.CallAudioManager;
import com.android.server.telecom.CallAudioModeStateMachine;
import com.android.server.telecom.CallAudioRouteStateMachine;
+import com.android.server.telecom.CallDiagnosticServiceController;
import com.android.server.telecom.CallEndpointController;
import com.android.server.telecom.CallEndpointControllerFactory;
-import com.android.server.telecom.CallDiagnosticServiceController;
import com.android.server.telecom.CallState;
import com.android.server.telecom.CallerInfoLookupHelper;
import com.android.server.telecom.CallsManager;
@@ -94,6 +95,7 @@
import com.android.server.telecom.ConnectionServiceFocusManager.ConnectionServiceFocusManagerFactory;
import com.android.server.telecom.ConnectionServiceWrapper;
import com.android.server.telecom.DefaultDialerCache;
+import com.android.server.telecom.EmergencyCallDiagnosticLogger;
import com.android.server.telecom.EmergencyCallHelper;
import com.android.server.telecom.HandoverState;
import com.android.server.telecom.HeadsetMediaButton;
@@ -116,8 +118,8 @@
import com.android.server.telecom.WiredHeadsetManager;
import com.android.server.telecom.bluetooth.BluetoothRouteManager;
import com.android.server.telecom.bluetooth.BluetoothStateReceiver;
-import com.android.server.telecom.callfiltering.CallFilteringResult;
import com.android.server.telecom.callfiltering.BlockedNumbersAdapter;
+import com.android.server.telecom.callfiltering.CallFilteringResult;
import com.android.server.telecom.ui.AudioProcessingNotification;
import com.android.server.telecom.ui.DisconnectedCallNotifier;
import com.android.server.telecom.ui.ToastFactory;
@@ -237,9 +239,12 @@
@Mock private ToastFactory mToastFactory;
@Mock private Toast mToast;
@Mock private CallAnomalyWatchdog mCallAnomalyWatchdog;
+
+ @Mock private EmergencyCallDiagnosticLogger mEmergencyCallDiagnosticLogger;
@Mock private AnomalyReporterAdapter mAnomalyReporterAdapter;
@Mock private Ringer.AccessibilityManagerAdapter mAccessibilityManagerAdapter;
@Mock private BlockedNumbersAdapter mBlockedNumbersAdapter;
+ @Mock private PhoneCapability mPhoneCapability;
private CallsManager mCallsManager;
@@ -306,7 +311,8 @@
// Just do async tasks synchronously to support testing.
command -> command.run(),
mBlockedNumbersAdapter,
- TransactionManager.getTestInstance());
+ TransactionManager.getTestInstance(),
+ mEmergencyCallDiagnosticLogger);
when(mPhoneAccountRegistrar.getPhoneAccount(
eq(SELF_MANAGED_HANDLE), any())).thenReturn(SELF_MANAGED_ACCOUNT);
@@ -331,6 +337,26 @@
assertEquals(0, mCallsManager.constructPossiblePhoneAccounts(null, null, false, false).size());
}
+ private Call constructOngoingCall(String callId, PhoneAccountHandle phoneAccountHandle) {
+ Call ongoingCall = new Call(
+ callId,
+ mContext,
+ mCallsManager,
+ mLock,
+ null /* ConnectionServiceRepository */,
+ mPhoneNumberUtilsAdapter,
+ TEST_ADDRESS,
+ null /* GatewayInfo */,
+ null /* connectionManagerPhoneAccountHandle */,
+ phoneAccountHandle,
+ Call.CALL_DIRECTION_INCOMING,
+ false /* shouldAttachToExistingConnection*/,
+ false /* isConference */,
+ mClockProxy,
+ mToastFactory);
+ ongoingCall.setState(CallState.ACTIVE, "just cuz");
+ return ongoingCall;
+ }
/**
* Verify behavior for multisim devices where we want to ensure that the active sim is used for
* placing a new call.
@@ -341,23 +367,7 @@
public void testConstructPossiblePhoneAccountsMultiSimActive() throws Exception {
setupMsimAccounts();
- Call ongoingCall = new Call(
- "1", /* callId */
- mContext,
- mCallsManager,
- mLock,
- null /* ConnectionServiceRepository */,
- mPhoneNumberUtilsAdapter,
- TEST_ADDRESS,
- null /* GatewayInfo */,
- null /* connectionManagerPhoneAccountHandle */,
- SIM_2_HANDLE,
- Call.CALL_DIRECTION_INCOMING,
- false /* shouldAttachToExistingConnection*/,
- false /* isConference */,
- mClockProxy,
- mToastFactory);
- ongoingCall.setState(CallState.ACTIVE, "just cuz");
+ Call ongoingCall = constructOngoingCall("1", SIM_2_HANDLE);
mCallsManager.addCall(ongoingCall);
List<PhoneAccountHandle> phoneAccountHandles = mCallsManager.constructPossiblePhoneAccounts(
@@ -380,6 +390,47 @@
assertEquals(2, phoneAccountHandles.size());
}
+ /**
+ * For DSDA-enabled multisim devices with an ongoing call, verify that both SIMs'
+ * PhoneAccountHandles are constructed while placing a new call.
+ * @throws Exception
+ */
+ @MediumTest
+ @Test
+ public void testConstructPossiblePhoneAccountsMultiSimActive_dsdaCallingPossible() throws
+ Exception {
+ setupMsimAccounts();
+ setMaxActiveVoiceSubscriptions(2);
+
+ Call ongoingCall = constructOngoingCall("1", SIM_2_HANDLE);
+ mCallsManager.addCall(ongoingCall);
+
+ List<PhoneAccountHandle> phoneAccountHandles = mCallsManager.constructPossiblePhoneAccounts(
+ TEST_ADDRESS, null, false, false);
+ assertEquals(2, phoneAccountHandles.size());
+ }
+
+ /**
+ * For DSDA-enabled multisim devices with an ongoing call, verify that only the active SIMs'
+ * PhoneAccountHandle is constructed while placing an emergency call.
+ * @throws Exception
+ */
+ @MediumTest
+ @Test
+ public void testConstructPossiblePhoneAccountsMultiSimActive_dsdaCallingPossible_emergencyCall()
+ throws Exception {
+ setupMsimAccounts();
+ setMaxActiveVoiceSubscriptions(2);
+
+ Call ongoingCall = constructOngoingCall("1", SIM_2_HANDLE);
+ mCallsManager.addCall(ongoingCall);
+
+ List<PhoneAccountHandle> phoneAccountHandles = mCallsManager.constructPossiblePhoneAccounts(
+ TEST_ADDRESS, null, false, true /* isEmergency */);
+ assertEquals(1, phoneAccountHandles.size());
+ assertEquals(SIM_2_HANDLE, phoneAccountHandles.get(0));
+ }
+
private void setupCallerInfoLookupHelper() {
doAnswer(invocation -> {
Uri handle = invocation.getArgument(0);
@@ -2862,4 +2913,10 @@
when(mPhoneAccountRegistrar.getSimPhoneAccountsOfCurrentUser()).thenReturn(
new ArrayList<>(Arrays.asList(SIM_1_HANDLE, SIM_2_HANDLE)));
}
+
+ private void setMaxActiveVoiceSubscriptions(int num) {
+ TelephonyManager mockTelephonyManager = mComponentContextFixture.getTelephonyManager();
+ when(mockTelephonyManager.getPhoneCapability()).thenReturn(mPhoneCapability);
+ when(mPhoneCapability.getMaxActiveVoiceSubscriptions()).thenReturn(num);
+ }
}
diff --git a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
index 8ff882b..49ad6bb 100644
--- a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
@@ -55,7 +55,9 @@
import android.location.LocationManager;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
+import android.os.BugreportManager;
import android.os.Bundle;
+import android.os.DropBoxManager;
import android.os.Handler;
import android.os.IInterface;
import android.os.PersistableBundle;
@@ -275,8 +277,12 @@
return Context.NOTIFICATION_SERVICE;
} else if (svcClass == AccessibilityManager.class) {
return Context.ACCESSIBILITY_SERVICE;
+ } else if (svcClass == DropBoxManager.class) {
+ return Context.DROPBOX_SERVICE;
+ } else if (svcClass == BugreportManager.class) {
+ return Context.BUGREPORT_SERVICE;
}
- throw new UnsupportedOperationException();
+ throw new UnsupportedOperationException(svcClass.getName());
}
@Override
diff --git a/tests/src/com/android/server/telecom/tests/EmergencyCallDiagnosticLoggerTest.java b/tests/src/com/android/server/telecom/tests/EmergencyCallDiagnosticLoggerTest.java
new file mode 100644
index 0000000..3cb8196
--- /dev/null
+++ b/tests/src/com/android/server/telecom/tests/EmergencyCallDiagnosticLoggerTest.java
@@ -0,0 +1,305 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.telecom.tests;
+
+
+import static android.telephony.TelephonyManager.EmergencyCallDiagnosticParams;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.ComponentName;
+import android.net.Uri;
+import android.os.BugreportManager;
+import android.os.DropBoxManager;
+import android.telecom.DisconnectCause;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.telephony.TelephonyManager;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallState;
+import com.android.server.telecom.CallerInfoLookupHelper;
+import com.android.server.telecom.CallsManager;
+import com.android.server.telecom.ClockProxy;
+import com.android.server.telecom.EmergencyCallDiagnosticLogger;
+import com.android.server.telecom.PhoneAccountRegistrar;
+import com.android.server.telecom.PhoneNumberUtilsAdapter;
+import com.android.server.telecom.TelecomSystem;
+import com.android.server.telecom.Timeouts;
+import com.android.server.telecom.ui.ToastFactory;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+@RunWith(JUnit4.class)
+public class EmergencyCallDiagnosticLoggerTest extends TelecomTestCase {
+
+ private static final ComponentName COMPONENT_NAME_1 = ComponentName
+ .unflattenFromString("com.foo/.Blah");
+ private static final PhoneAccountHandle SIM_1_HANDLE = new PhoneAccountHandle(
+ COMPONENT_NAME_1, "Sim1");
+ private static final PhoneAccount SIM_1_ACCOUNT = new PhoneAccount.
+ Builder(SIM_1_HANDLE, "Sim1")
+ .setCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION
+ | PhoneAccount.CAPABILITY_CALL_PROVIDER)
+ .setIsEnabled(true)
+ .build();
+ private static final String DROP_BOX_TAG = "ecall_diagnostic_data";
+
+ private static final long EMERGENCY_CALL_ACTIVE_TIME_THRESHOLD_MILLIS = 100L;
+
+ private static final long EMERGENCY_CALL_TIME_BEFORE_USER_DISCONNECT_THRESHOLD_MILLIS = 120L;
+
+ private static final int DAYS_BACK_TO_SEARCH_EMERGENCY_DIAGNOSTIC_ENTRIES = 1;
+ private final TelecomSystem.SyncRoot mLock = new TelecomSystem.SyncRoot() {
+ };
+ EmergencyCallDiagnosticLogger mEmergencyCallDiagnosticLogger;
+ @Mock
+ private Timeouts.Adapter mTimeouts;
+ @Mock
+ private CallsManager mMockCallsManager;
+ @Mock
+ private CallerInfoLookupHelper mMockCallerInfoLookupHelper;
+ @Mock
+ private PhoneAccountRegistrar mMockPhoneAccountRegistrar;
+ @Mock
+ private ClockProxy mMockClockProxy;
+ @Mock
+ private ToastFactory mMockToastProxy;
+ @Mock
+ private PhoneNumberUtilsAdapter mMockPhoneNumberUtilsAdapter;
+
+ @Mock
+ private TelephonyManager mTm;
+ @Mock
+ private BugreportManager mBrm;
+ @Mock
+ private DropBoxManager mDbm;
+
+ @Mock
+ private ClockProxy mClockProxy;
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+
+ doReturn(mMockCallerInfoLookupHelper).when(mMockCallsManager).getCallerInfoLookupHelper();
+ doReturn(mMockPhoneAccountRegistrar).when(mMockCallsManager).getPhoneAccountRegistrar();
+ doReturn(SIM_1_ACCOUNT).when(mMockPhoneAccountRegistrar).getPhoneAccountUnchecked(
+ eq(SIM_1_HANDLE));
+ when(mTimeouts.getEmergencyCallActiveTimeThresholdMillis()).
+ thenReturn(EMERGENCY_CALL_ACTIVE_TIME_THRESHOLD_MILLIS);
+ when(mTimeouts.getEmergencyCallTimeBeforeUserDisconnectThresholdMillis()).
+ thenReturn(EMERGENCY_CALL_TIME_BEFORE_USER_DISCONNECT_THRESHOLD_MILLIS);
+ when(mTimeouts.getDaysBackToSearchEmergencyDiagnosticEntries()).
+ thenReturn(DAYS_BACK_TO_SEARCH_EMERGENCY_DIAGNOSTIC_ENTRIES);
+ when(mClockProxy.currentTimeMillis()).thenReturn(System.currentTimeMillis());
+
+ mEmergencyCallDiagnosticLogger = new EmergencyCallDiagnosticLogger(mTm, mBrm,
+ mTimeouts, mDbm, Runnable::run, mClockProxy);
+ }
+
+ @Override
+ @After
+ public void tearDown() throws Exception {
+ super.tearDown();
+ //reset(mTm);
+ }
+
+ /**
+ * Helper function that creates the call being tested.
+ * Also invokes onStartCreateConnection
+ */
+ private Call createCall(boolean isEmergencyCall, int direction) {
+ Call call = getCall();
+ call.setCallDirection(direction);
+ call.setIsEmergencyCall(isEmergencyCall);
+ mEmergencyCallDiagnosticLogger.onStartCreateConnection(call);
+ return call;
+ }
+
+ /**
+ * @return an instance of {@link Call} for testing purposes.
+ */
+ private Call getCall() {
+ return new Call(
+ "1", /* callId */
+ mContext,
+ mMockCallsManager,
+ mLock,
+ null /* ConnectionServiceRepository */,
+ mMockPhoneNumberUtilsAdapter,
+ Uri.parse("tel:6505551212"),
+ null /* GatewayInfo */,
+ null /* connectionManagerPhoneAccountHandle */,
+ SIM_1_HANDLE,
+ Call.CALL_DIRECTION_OUTGOING,
+ false /* shouldAttachToExistingConnection*/,
+ false /* isConference */,
+ mMockClockProxy,
+ mMockToastProxy);
+ }
+
+ /**
+ * Test that only outgoing emergency calls are tracked
+ */
+ @Test
+ public void testNonEmergencyCallNotTracked() {
+ //should not be tracked
+ createCall(false, Call.CALL_DIRECTION_OUTGOING);
+ assertEquals(0, mEmergencyCallDiagnosticLogger.getEmergencyCallsMap().size());
+
+ //should not be tracked (not in scope)
+ createCall(false, Call.CALL_DIRECTION_INCOMING);
+ assertEquals(0, mEmergencyCallDiagnosticLogger.getEmergencyCallsMap().size());
+ }
+
+ /**
+ * Test that incoming emergency calls are not tracked (not in scope right now)
+ */
+ @Test
+ public void testIncomingEmergencyCallsNotTracked() {
+ //should not be tracked
+ createCall(true, Call.CALL_DIRECTION_INCOMING);
+ assertEquals(0, mEmergencyCallDiagnosticLogger.getEmergencyCallsMap().size());
+ }
+
+
+ /**
+ * Test getDataCollectionTypes(reason)
+ */
+ @Test
+ public void testCollectionTypeForReasonDoesNotReturnUnreasonableValues() {
+ int reason = EmergencyCallDiagnosticLogger.REPORT_REASON_RANGE_START + 1;
+ while (reason < EmergencyCallDiagnosticLogger.REPORT_REASON_RANGE_END) {
+ List<Integer> ctypes = EmergencyCallDiagnosticLogger.getDataCollectionTypes(reason);
+ assertNotNull(ctypes);
+ Set<Integer> ctypesSet = new HashSet<>(ctypes);
+
+ //assert that list is not empty
+ assertNotEquals(0, ctypes.size());
+
+ //assert no repeated values
+ assertEquals(ctypes.size(), ctypesSet.size());
+
+ //if bugreport type is present, that should be the only collection type
+ if (ctypesSet.contains(EmergencyCallDiagnosticLogger.COLLECTION_TYPE_BUGREPORT)) {
+ assertEquals(1, ctypes.size());
+ }
+ reason++;
+ }
+ }
+
+
+ /**
+ * Test emergency call reported stuck
+ */
+ @Test
+ public void testStuckEmergencyCall() {
+ Call call = createCall(true, Call.CALL_DIRECTION_OUTGOING);
+ mEmergencyCallDiagnosticLogger.onCallAdded(call);
+ mEmergencyCallDiagnosticLogger.reportStuckCall(call);
+
+ //for stuck calls, we should always be persisting some data
+ ArgumentCaptor<EmergencyCallDiagnosticParams> captor =
+ ArgumentCaptor.forClass(EmergencyCallDiagnosticParams.class);
+ verify(mTm, times(1)).persistEmergencyCallDiagnosticData(eq(DROP_BOX_TAG),
+ captor.capture());
+ EmergencyCallDiagnosticParams dp = captor.getValue();
+
+ assertNotNull(dp);
+ assertTrue(
+ dp.isLogcatCollectionEnabled() || dp.isTelecomDumpSysCollectionEnabled()
+ || dp.isTelephonyDumpSysCollectionEnabled());
+
+ //tracking should end
+ assertEquals(0, mEmergencyCallDiagnosticLogger.getEmergencyCallsMap().size());
+ }
+
+ @Test
+ public void testEmergencyCallNeverWentActiveWithNonLocalDisconnectCause() {
+ Call call = createCall(true, Call.CALL_DIRECTION_OUTGOING);
+ mEmergencyCallDiagnosticLogger.onCallAdded(call);
+
+ //call is tracked
+ assertEquals(1, mEmergencyCallDiagnosticLogger.getEmergencyCallsMap().size());
+
+ call.setDisconnectCause(new DisconnectCause(DisconnectCause.REJECTED));
+ mEmergencyCallDiagnosticLogger.onCallRemoved(call);
+
+ //for non-local disconnect of non-active call, we should always be persisting some data
+ ArgumentCaptor<TelephonyManager.EmergencyCallDiagnosticParams> captor =
+ ArgumentCaptor.forClass(
+ TelephonyManager.EmergencyCallDiagnosticParams.class);
+ verify(mTm, times(1)).persistEmergencyCallDiagnosticData(eq(DROP_BOX_TAG),
+ captor.capture());
+ TelephonyManager.EmergencyCallDiagnosticParams dp = captor.getValue();
+
+ assertNotNull(dp);
+ assertTrue(
+ dp.isLogcatCollectionEnabled() || dp.isTelecomDumpSysCollectionEnabled()
+ || dp.isTelephonyDumpSysCollectionEnabled());
+
+ //tracking should end
+ assertEquals(0, mEmergencyCallDiagnosticLogger.getEmergencyCallsMap().size());
+ }
+
+ @Test
+ public void testEmergencyCallWentActiveForLongDuration_shouldNotCollectDiagnostics()
+ throws Exception {
+ Call call = createCall(true, Call.CALL_DIRECTION_OUTGOING);
+ mEmergencyCallDiagnosticLogger.onCallAdded(call);
+
+ //call went active
+ mEmergencyCallDiagnosticLogger.onCallStateChanged(call, CallState.DIALING,
+ CallState.ACTIVE);
+
+ //return large value for time when call is disconnected
+ when(mClockProxy.currentTimeMillis()).thenReturn(System.currentTimeMillis() + 10000L);
+
+ call.setDisconnectCause(new DisconnectCause(DisconnectCause.ERROR));
+ mEmergencyCallDiagnosticLogger.onCallRemoved(call);
+
+ //no diagnostic data should be persisted
+ verify(mTm, never()).persistEmergencyCallDiagnosticData(eq(DROP_BOX_TAG),
+ any());
+
+ //tracking should end
+ assertEquals(0, mEmergencyCallDiagnosticLogger.getEmergencyCallsMap().size());
+ }
+
+}