Merge "Flag guard switch to work profile dialog for call" into udc-dev
diff --git a/src/com/android/server/telecom/Call.java b/src/com/android/server/telecom/Call.java
index 5847960..32b5556 100644
--- a/src/com/android/server/telecom/Call.java
+++ b/src/com/android/server/telecom/Call.java
@@ -17,6 +17,7 @@
package com.android.server.telecom;
import static android.provider.CallLog.Calls.MISSED_REASON_NOT_MISSED;
+import static android.telecom.Call.EVENT_DISPLAY_SOS_MESSAGE;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -2397,6 +2398,8 @@
@Override
public void handleCreateConferenceFailure(DisconnectCause disconnectCause) {
+ Log.i(this, "handleCreateConferenceFailure; callid=%s, disconnectCause=%s",
+ getId(), disconnectCause);
clearConnectionService();
setDisconnectCause(disconnectCause);
mCallsManager.markCallAsDisconnected(this, disconnectCause);
@@ -2417,6 +2420,8 @@
@Override
public void handleCreateConnectionFailure(DisconnectCause disconnectCause) {
+ Log.i(this, "handleCreateConnectionFailure; callid=%s, disconnectCause=%s",
+ getId(), disconnectCause);
clearConnectionService();
setDisconnectCause(disconnectCause);
mCallsManager.markCallAsDisconnected(this, disconnectCause);
@@ -2701,7 +2706,8 @@
// hangup, not reject.
setOverrideDisconnectCauseCode(new DisconnectCause(DisconnectCause.REJECTED));
if (mTransactionalService != null) {
- mTransactionalService.onReject(this, DisconnectCause.REJECTED);
+ mTransactionalService.onDisconnect(this,
+ new DisconnectCause(DisconnectCause.REJECTED));
} else if (mConnectionService != null) {
mConnectionService.disconnect(this);
} else {
@@ -2714,7 +2720,8 @@
mVideoStateHistory |= mVideoState;
if (mTransactionalService != null) {
- mTransactionalService.onReject(this, DisconnectCause.REJECTED);
+ mTransactionalService.onDisconnect(this,
+ new DisconnectCause(DisconnectCause.REJECTED));
} else if (mConnectionService != null) {
mConnectionService.reject(this, rejectWithMessage, textMessage);
} else {
@@ -2737,7 +2744,8 @@
// Since its simulated reason we can't pass along the reject reason.
setOverrideDisconnectCauseCode(new DisconnectCause(DisconnectCause.REJECTED));
if (mTransactionalService != null) {
- mTransactionalService.onReject(this, DisconnectCause.REJECTED);
+ mTransactionalService.onDisconnect(this,
+ new DisconnectCause(DisconnectCause.REJECTED));
} else if (mConnectionService != null) {
mConnectionService.disconnect(this);
} else {
@@ -2749,7 +2757,8 @@
// Ensure video state history tracks video state at time of rejection.
mVideoStateHistory |= mVideoState;
if (mTransactionalService != null) {
- mTransactionalService.onReject(this, rejectReason);
+ mTransactionalService.onDisconnect(this,
+ new DisconnectCause(DisconnectCause.REJECTED));
} else if (mConnectionService != null) {
mConnectionService.rejectWithReason(this, rejectReason);
} else {
@@ -3205,9 +3214,7 @@
* @param extras Associated extras.
*/
public void sendCallEvent(String event, int targetSdkVer, Bundle extras) {
- if (mTransactionalService != null) {
- Log.i(this, "sendCallEvent: called on TransactionalService. doing nothing");
- } else if (mConnectionService != null) {
+ if (mConnectionService != null || mTransactionalService != null) {
if (android.telecom.Call.EVENT_REQUEST_HANDOVER.equals(event)) {
if (targetSdkVer > Build.VERSION_CODES.P) {
Log.e(this, new Exception(), "sendCallEvent failed. Use public api handoverTo" +
@@ -3230,8 +3237,8 @@
if (extras == null) {
Log.w(this, "sendCallEvent: %s event received with null extras.",
android.telecom.Call.EVENT_REQUEST_HANDOVER);
- mConnectionService.sendCallEvent(this,
- android.telecom.Call.EVENT_HANDOVER_FAILED, null);
+ sendEventToService(this, android.telecom.Call.EVENT_HANDOVER_FAILED,
+ null);
return;
}
Parcelable parcelable = extras.getParcelable(
@@ -3239,8 +3246,7 @@
if (!(parcelable instanceof PhoneAccountHandle) || parcelable == null) {
Log.w(this, "sendCallEvent: %s event received with invalid handover acct.",
android.telecom.Call.EVENT_REQUEST_HANDOVER);
- mConnectionService.sendCallEvent(this,
- android.telecom.Call.EVENT_HANDOVER_FAILED, null);
+ sendEventToService(this, android.telecom.Call.EVENT_HANDOVER_FAILED, null);
return;
}
PhoneAccountHandle phoneAccountHandle = (PhoneAccountHandle) parcelable;
@@ -3263,7 +3269,7 @@
));
}
Log.addEvent(this, LogUtils.Events.CALL_EVENT, event);
- mConnectionService.sendCallEvent(this, event, extras);
+ sendEventToService(this, event, extras);
}
} else {
Log.e(this, new NullPointerException(),
@@ -3272,6 +3278,17 @@
}
/**
+ * This method should only be called from sendCallEvent(String, int, Bundle).
+ */
+ private void sendEventToService(Call call, String event, Bundle extras) {
+ if (mConnectionService != null) {
+ mConnectionService.sendCallEvent(call, event, extras);
+ } else if (mTransactionalService != null) {
+ mTransactionalService.onEvent(call, event, extras);
+ }
+ }
+
+ /**
* Notifies listeners when a bluetooth quality report is received.
* @param report The bluetooth quality report.
*/
@@ -3643,6 +3660,7 @@
if (mTransactionalService != null) {
Log.i(this, "stopRtt: called on TransactionalService. doing nothing");
} else if (mConnectionService != null) {
+ Log.addEvent(this, LogUtils.Events.REQUEST_RTT, "stop");
mConnectionService.stopRtt(this);
} else {
// If this gets called by the in-call app before the connection service is set, we'll
@@ -3656,6 +3674,7 @@
Log.i(this, "sendRttRequest: called on TransactionalService. doing nothing");
return;
}
+ Log.addEvent(this, LogUtils.Events.REQUEST_RTT, "start");
createRttStreams();
mConnectionService.startRtt(this, getInCallToCsRttPipeForCs(), getCsToInCallRttPipeForCs());
}
@@ -3679,12 +3698,14 @@
public void onRttConnectionFailure(int reason) {
Log.i(this, "Got RTT initiation failure with reason %d", reason);
+ Log.addEvent(this, LogUtils.Events.ON_RTT_FAILED, "reason=" + reason);
for (Listener l : mListeners) {
l.onRttInitiationFailure(this, reason);
}
}
public void onRemoteRttRequest() {
+ Log.addEvent(this, LogUtils.Events.ON_RTT_REQUEST);
if (isRttCall()) {
Log.w(this, "Remote RTT request on a call that's already RTT");
return;
@@ -3709,6 +3730,8 @@
Log.i(this, "handleRttRequestResponse: called on TransactionalService. doing nothing");
return;
}
+ Log.addEvent(this, LogUtils.Events.RESPOND_TO_RTT_REQUEST, "id=" + id + ", accept="
+ + accept);
if (accept) {
createRttStreams();
Log.i(this, "RTT request %d accepted.", id);
@@ -3985,7 +4008,8 @@
public void setRttMode(int mode) {
mRttMode = mode;
- // TODO: hook this up to CallAudioManager
+ Log.addEvent(this, LogUtils.Events.SET_RRT_MODE, "mode=" + mode);
+ // TODO: hook this up to CallAudioManager.
}
/**
@@ -4060,6 +4084,12 @@
l.onReceivedCallQualityReport(this, callQuality);
}
} else {
+ if (event.equals(EVENT_DISPLAY_SOS_MESSAGE) && !isEmergencyCall()) {
+ Log.w(this, "onConnectionEvent: EVENT_DISPLAY_SOS_MESSAGE is sent "
+ + "without an emergency call");
+ return;
+ }
+
for (Listener l : mListeners) {
l.onConnectionEvent(this, event, extras);
}
diff --git a/src/com/android/server/telecom/CallAnomalyWatchdog.java b/src/com/android/server/telecom/CallAnomalyWatchdog.java
index d3dbbc9..b46a6cf 100644
--- a/src/com/android/server/telecom/CallAnomalyWatchdog.java
+++ b/src/com/android/server/telecom/CallAnomalyWatchdog.java
@@ -165,23 +165,26 @@
}
/**
+ * Override of {@link CallsManagerListenerBase} to track when calls are created but
+ * intentionally not added to mCalls. These calls should no longer be tracked by the
+ * CallAnomalyWatchdog.
+ * @param call the call
+ */
+
+ @Override
+ public void onCallCreatedButNeverAdded(Call call) {
+ Log.i(this, "onCallCreatedButNeverAdded: call=%s", call.toString());
+ stopTrackingCall(call);
+ }
+
+ /**
* Override of {@link CallsManagerListenerBase} to track when calls are removed
* @param call the call
*/
@Override
public void onCallRemoved(Call call) {
- if (mScheduledFutureMap.containsKey(call)) {
- ScheduledFuture<?> existingTimeout = mScheduledFutureMap.get(call);
- existingTimeout.cancel(false /* cancelIfRunning */);
- mScheduledFutureMap.remove(call);
- }
- if (mCallsPendingDestruction.contains(call)) {
- mCallsPendingDestruction.remove(call);
- }
- if (mWatchdogCallStateMap.containsKey(call)) {
- mWatchdogCallStateMap.remove(call);
- }
- call.removeListener(this);
+ Log.i(this, "onCallRemoved: call=%s", call.toString());
+ stopTrackingCall(call);
}
/**
@@ -192,7 +195,10 @@
* @param newState the new state
*/
@Override
- public void onCallStateChanged(Call call, int oldState, int newState) { maybeTrackCall(call); }
+ public void onCallStateChanged(Call call, int oldState, int newState) {
+ Log.i(this, "onCallStateChanged: call=%s", call.toString());
+ maybeTrackCall(call);
+ }
/**
* Override of {@link Call.Listener} so we can capture successful creation of calls.
@@ -211,7 +217,8 @@
*/
@Override
public void onFailedOutgoingCall(Call call, DisconnectCause disconnectCause) {
- maybeTrackCall(call);
+ Log.i(this, "onFailedOutgoingCall: call=%s", call.toString());
+ stopTrackingCall(call);
}
/**
@@ -229,7 +236,27 @@
*/
@Override
public void onFailedIncomingCall(Call call) {
- maybeTrackCall(call);
+ Log.i(this, "onFailedIncomingCall: call=%s", call.toString());
+ stopTrackingCall(call);
+ }
+
+ /**
+ * Helper method used to stop CallAnomalyWatchdog from tracking or destroying the call.
+ * @param call the call.
+ */
+ private void stopTrackingCall(Call call) {
+ if (mScheduledFutureMap.containsKey(call)) {
+ ScheduledFuture<?> existingTimeout = mScheduledFutureMap.get(call);
+ existingTimeout.cancel(false /* cancelIfRunning */);
+ mScheduledFutureMap.remove(call);
+ }
+ if (mCallsPendingDestruction.contains(call)) {
+ mCallsPendingDestruction.remove(call);
+ }
+ if (mWatchdogCallStateMap.containsKey(call)) {
+ mWatchdogCallStateMap.remove(call);
+ }
+ call.removeListener(this);
}
/**
@@ -273,7 +300,7 @@
}
}
- private long getTimeoutMillis(Call call, WatchdogCallState state) {
+ public long getTimeoutMillis(Call call, WatchdogCallState state) {
boolean isVoip = call.getIsVoipAudioMode();
boolean isEmergency = call.isEmergencyCall();
diff --git a/src/com/android/server/telecom/CallAudioRouteStateMachine.java b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
index f56320f..cccade3 100644
--- a/src/com/android/server/telecom/CallAudioRouteStateMachine.java
+++ b/src/com/android/server/telecom/CallAudioRouteStateMachine.java
@@ -49,6 +49,7 @@
import java.util.HashMap;
import java.util.Objects;
import java.util.Set;
+import java.util.concurrent.Executor;
/**
* This class describes the available routes of a call as a state machine.
@@ -81,14 +82,16 @@
WiredHeadsetManager wiredHeadsetManager,
StatusBarNotifier statusBarNotifier,
CallAudioManager.AudioServiceFactory audioServiceFactory,
- int earpieceControl) {
+ int earpieceControl,
+ Executor asyncTaskExecutor) {
return new CallAudioRouteStateMachine(context,
callsManager,
bluetoothManager,
wiredHeadsetManager,
statusBarNotifier,
audioServiceFactory,
- earpieceControl);
+ earpieceControl,
+ asyncTaskExecutor);
}
}
/** Values for CallAudioRouteStateMachine constructor's earPieceRouting arg. */
@@ -1478,6 +1481,8 @@
private final QuiescentSpeakerRoute mQuiescentSpeakerRoute = new QuiescentSpeakerRoute();
private final StreamingState mStreamingState = new StreamingState();
+ private final Executor mAsyncTaskExecutor;
+
/**
* A few pieces of hidden state. Used to avoid exponential explosion of number of explicit
* states
@@ -1516,7 +1521,8 @@
WiredHeadsetManager wiredHeadsetManager,
StatusBarNotifier statusBarNotifier,
CallAudioManager.AudioServiceFactory audioServiceFactory,
- int earpieceControl) {
+ int earpieceControl,
+ Executor asyncTaskExecutor) {
super(NAME);
mContext = context;
mCallsManager = callsManager;
@@ -1526,7 +1532,7 @@
mStatusBarNotifier = statusBarNotifier;
mAudioServiceFactory = audioServiceFactory;
mLock = callsManager.getLock();
-
+ mAsyncTaskExecutor = asyncTaskExecutor;
createStates(earpieceControl);
}
@@ -1538,7 +1544,7 @@
WiredHeadsetManager wiredHeadsetManager,
StatusBarNotifier statusBarNotifier,
CallAudioManager.AudioServiceFactory audioServiceFactory,
- int earpieceControl, Looper looper) {
+ int earpieceControl, Looper looper, Executor asyncTaskExecutor) {
super(NAME, looper);
mContext = context;
mCallsManager = callsManager;
@@ -1548,6 +1554,7 @@
mStatusBarNotifier = statusBarNotifier;
mAudioServiceFactory = audioServiceFactory;
mLock = callsManager.getLock();
+ mAsyncTaskExecutor = asyncTaskExecutor;
createStates(earpieceControl);
}
@@ -1629,7 +1636,8 @@
new IntentFilter(AudioManager.ACTION_SPEAKERPHONE_STATE_CHANGED));
mStatusBarNotifier.notifyMute(initState.isMuted());
- mStatusBarNotifier.notifySpeakerphone(initState.getRoute() == CallAudioState.ROUTE_SPEAKER);
+ // We used to call mStatusBarNotifier.notifySpeakerphone, but that makes no sense as there
+ // is never a call at this boot (init) time.
setInitialState(mRouteCodeToQuiescentState.get(initState.getRoute()));
start();
}
@@ -1722,26 +1730,32 @@
private void setSpeakerphoneOn(boolean on) {
Log.i(this, "turning speaker phone %s", on);
- AudioDeviceInfo speakerDevice = null;
- for (AudioDeviceInfo info : mAudioManager.getAvailableCommunicationDevices()) {
- if (info.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
- speakerDevice = info;
- break;
+ final boolean hasAnyCalls = mCallsManager.hasAnyCalls();
+ // These APIs are all via two-way binder calls so can potentially block Telecom. Since none
+ // of this has to happen in the Telecom lock we'll offload it to the async executor.
+ mAsyncTaskExecutor.execute(() -> {
+ AudioDeviceInfo speakerDevice = null;
+ for (AudioDeviceInfo info : mAudioManager.getAvailableCommunicationDevices()) {
+ if (info.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
+ speakerDevice = info;
+ break;
+ }
}
- }
- boolean speakerOn = false;
- if (speakerDevice != null && on) {
- boolean result = mAudioManager.setCommunicationDevice(speakerDevice);
- if (result) {
- speakerOn = true;
+ boolean speakerOn = false;
+ if (speakerDevice != null && on) {
+ boolean result = mAudioManager.setCommunicationDevice(speakerDevice);
+ if (result) {
+ speakerOn = true;
+ }
+ } else {
+ AudioDeviceInfo curDevice = mAudioManager.getCommunicationDevice();
+ if (curDevice != null
+ && curDevice.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
+ mAudioManager.clearCommunicationDevice();
+ }
}
- } else {
- AudioDeviceInfo curDevice = mAudioManager.getCommunicationDevice();
- if (curDevice != null && curDevice.getType() == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER) {
- mAudioManager.clearCommunicationDevice();
- }
- }
- mStatusBarNotifier.notifySpeakerphone(speakerOn);
+ mStatusBarNotifier.notifySpeakerphone(hasAnyCalls && speakerOn);
+ });
}
private void setBluetoothOn(String address) {
diff --git a/src/com/android/server/telecom/CallStreamingController.java b/src/com/android/server/telecom/CallStreamingController.java
index 56ce17b..e494d8e 100644
--- a/src/com/android/server/telecom/CallStreamingController.java
+++ b/src/com/android/server/telecom/CallStreamingController.java
@@ -111,11 +111,11 @@
if (mCallsManager.getCallStreamingController().isStreaming()) {
future.complete(new VoipCallTransactionResult(
- VoipCallTransactionResult.RESULT_SUCCEED, null));
- } else {
- future.complete(new VoipCallTransactionResult(
VoipCallTransactionResult.RESULT_FAILED,
"STREAMING_FAILED_ALREADY_STREAMING"));
+ } else {
+ future.complete(new VoipCallTransactionResult(
+ VoipCallTransactionResult.RESULT_SUCCEED, null));
}
return future;
@@ -177,7 +177,7 @@
CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
RoleManager roleManager = mContext.getSystemService(RoleManager.class);
- PackageManager packageManager = mContext.getSystemService(PackageManager.class);
+ PackageManager packageManager = mContext.getPackageManager();
if (roleManager == null || packageManager == null) {
Log.e(TAG, "Can't find system service");
future.complete(new VoipCallTransactionResult(
diff --git a/src/com/android/server/telecom/CallsManager.java b/src/com/android/server/telecom/CallsManager.java
index 5d5a103..5a7d3de 100644
--- a/src/com/android/server/telecom/CallsManager.java
+++ b/src/com/android/server/telecom/CallsManager.java
@@ -155,6 +155,7 @@
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@@ -182,6 +183,7 @@
*/
default void onCallCreated(Call call) {}
void onCallAdded(Call call);
+ void onCallCreatedButNeverAdded(Call call);
void onCallRemoved(Call call);
void onCallStateChanged(Call call, int oldState, int newState);
void onConnectionServiceChanged(
@@ -279,6 +281,15 @@
public static final String EXCEPTION_RETRIEVING_PHONE_ACCOUNTS_EMERGENCY_ERROR_MSG =
"Exception thrown while retrieving list of potential phone accounts when placing an "
+ "emergency call.";
+ public static final UUID EMERGENCY_CALL_DISCONNECTED_BEFORE_BEING_ADDED_ERROR_UUID =
+ UUID.fromString("f9a916c8-8d61-4550-9ad3-11c2e84f6364");
+ public static final String EMERGENCY_CALL_DISCONNECTED_BEFORE_BEING_ADDED_ERROR_MSG =
+ "An emergency call was disconnected after the connection was created but before the "
+ + "call was successfully added to CallsManager.";
+ public static final UUID EMERGENCY_CALL_ABORTED_NO_PHONE_ACCOUNTS_ERROR_UUID =
+ UUID.fromString("2e994acb-1997-4345-8bf3-bad04303de26");
+ public static final String EMERGENCY_CALL_ABORTED_NO_PHONE_ACCOUNTS_ERROR_MSG =
+ "An emergency call was aborted since there were no available phone accounts.";
private static final int[] OUTGOING_CALL_STATES =
{CallState.CONNECTING, CallState.SELECT_PHONE_ACCOUNT, CallState.DIALING,
@@ -450,12 +461,13 @@
private boolean mCanAddCall = true;
- private int mMaxNumberOfSimultaneouslyActiveSims = -1;
-
private Runnable mStopTone;
private LinkedList<HandlerThread> mGraphHandlerThreads;
+ // An executor that can be used to fire off async tasks that do not block Telecom in any manner.
+ private final Executor mAsyncTaskExecutor;
+
private boolean mHasActiveRttCall = false;
private AnomalyReporterAdapter mAnomalyReporter = new AnomalyReporterAdapterImpl();
@@ -554,7 +566,9 @@
RoleManagerAdapter roleManagerAdapter,
ToastFactory toastFactory,
CallEndpointControllerFactory callEndpointControllerFactory,
- CallAnomalyWatchdog callAnomalyWatchdog) {
+ CallAnomalyWatchdog callAnomalyWatchdog,
+ Ringer.AccessibilityManagerAdapter accessibilityManagerAdapter,
+ Executor asyncTaskExecutor) {
mContext = context;
mLock = lock;
mPhoneNumberUtilsAdapter = phoneNumberUtilsAdapter;
@@ -582,7 +596,8 @@
wiredHeadsetManager,
statusBarNotifier,
audioServiceFactory,
- CallAudioRouteStateMachine.EARPIECE_AUTO_DETECT
+ CallAudioRouteStateMachine.EARPIECE_AUTO_DETECT,
+ asyncTaskExecutor
);
callAudioRouteStateMachine.initialize();
@@ -615,7 +630,7 @@
ringtoneFactory, systemVibrator,
new Ringer.VibrationEffectProxy(), mInCallController,
mContext.getSystemService(NotificationManager.class),
- mContext.getSystemService(AccessibilityManager.class));
+ accessibilityManagerAdapter);
mCallRecordingTonePlayer = new CallRecordingTonePlayer(mContext, audioManager,
mTimeoutsAdapter, mLock);
mCallAudioManager = new CallAudioManager(callAudioRouteStateMachine,
@@ -671,6 +686,7 @@
mGraphHandlerThreads = new LinkedList<>();
mCallAnomalyWatchdog = callAnomalyWatchdog;
+ mAsyncTaskExecutor = asyncTaskExecutor;
}
public void setIncomingCallNotifier(IncomingCallNotifier incomingCallNotifier) {
@@ -729,8 +745,7 @@
@Override
public void onFailedOutgoingCall(Call call, DisconnectCause disconnectCause) {
- Log.v(this, "onFailedOutgoingCall, call: %s", call);
-
+ Log.i(this, "onFailedOutgoingCall for call %s", call);
markCallAsRemoved(call);
}
@@ -985,14 +1000,15 @@
@Override
public void onFailedIncomingCall(Call call) {
+ Log.i(this, "onFailedIncomingCall for call %s", call);
setCallState(call, CallState.DISCONNECTED, "failed incoming call");
call.removeListener(this);
}
@Override
public void onSuccessfulUnknownCall(Call call, int callState) {
- setCallState(call, callState, "successful unknown call");
Log.i(this, "onSuccessfulUnknownCall for call %s", call);
+ setCallState(call, callState, "successful unknown call");
addCall(call);
}
@@ -1545,6 +1561,8 @@
// transactional calls should skip Call#startCreateConnection below
// as that is meant for Call objects with a ConnectionServiceWrapper
call.setState(CallState.RINGING, "explicitly set new incoming to ringing");
+ // Transactional calls don't get created via a connection service; they are added now.
+ call.setIsCreateConnectionComplete(true);
addCall(call);
} else {
call.startCreateConnection(mPhoneAccountRegistrar);
@@ -1941,6 +1959,12 @@
Log.i(CallsManager.this, "Aborting call since there are no"
+ " available accounts.");
showErrorMessage(R.string.cant_call_due_to_no_supported_service);
+ mListeners.forEach(l -> l.onCallCreatedButNeverAdded(callToPlace));
+ if (callToPlace.isEmergencyCall()){
+ mAnomalyReporter.reportAnomaly(
+ EMERGENCY_CALL_ABORTED_NO_PHONE_ACCOUNTS_ERROR_UUID,
+ EMERGENCY_CALL_ABORTED_NO_PHONE_ACCOUNTS_ERROR_MSG);
+ }
return CompletableFuture.completedFuture(null);
}
boolean needsAccountSelection = accountSuggestions.size() > 1
@@ -3120,6 +3144,18 @@
return constructPossiblePhoneAccounts(handle, user, isVideo, isEmergency, false);
}
+ // Returns whether the device is capable of 2 simultaneous active voice calls on different subs.
+ private boolean isDsdaCallingPossible() {
+ try {
+ return getTelephonyManager().getMaxNumberOfSimultaneouslyActiveSims() > 1
+ || getTelephonyManager().getPhoneCapability()
+ .getMaxActiveVoiceSubscriptions() > 1;
+ } catch (Exception e) {
+ Log.w(this, "exception in isDsdaCallingPossible(): ", e);
+ return false;
+ }
+ }
+
public List<PhoneAccountHandle> constructPossiblePhoneAccounts(Uri handle, UserHandle user,
boolean isVideo, boolean isEmergency, boolean isConference) {
@@ -3136,13 +3172,9 @@
capabilities,
isEmergency ? 0 : PhoneAccount.CAPABILITY_EMERGENCY_CALLS_ONLY,
isEmergency);
- if (mMaxNumberOfSimultaneouslyActiveSims < 0) {
- mMaxNumberOfSimultaneouslyActiveSims =
- getTelephonyManager().getMaxNumberOfSimultaneouslyActiveSims();
- }
// 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 (mMaxNumberOfSimultaneouslyActiveSims == 1) {
+ if (!isDsdaCallingPossible()) {
List<PhoneAccountHandle> simAccounts =
mPhoneAccountRegistrar.getSimPhoneAccountsOfCurrentUser();
PhoneAccountHandle ongoingCallAccount = null;
@@ -3337,7 +3369,8 @@
setCallState(call, CallState.RINGING, "ringing set explicitly");
}
- void markCallAsDialing(Call call) {
+ @VisibleForTesting
+ public void markCallAsDialing(Call call) {
setCallState(call, CallState.DIALING, "dialing set explicitly");
maybeMoveToSpeakerPhone(call);
maybeTurnOffMute(call);
@@ -3493,6 +3526,8 @@
* @param disconnectCause The disconnect cause, see {@link android.telecom.DisconnectCause}.
*/
public void markCallAsDisconnected(Call call, DisconnectCause disconnectCause) {
+ Log.i(this, "markCallAsDisconnected: call=%s; disconnectCause=%s",
+ call.toString(), disconnectCause.toString());
int oldState = call.getState();
if (call.getState() == CallState.SIMULATED_RINGING
&& disconnectCause.getCode() == DisconnectCause.REMOTE) {
@@ -3517,6 +3552,17 @@
}
}
+ // Notify listeners that the call was disconnected before being added to CallsManager.
+ // Listeners will not receive onAdded or onRemoved callbacks.
+ if (!mCalls.contains(call)) {
+ if (call.isEmergencyCall()) {
+ mAnomalyReporter.reportAnomaly(
+ EMERGENCY_CALL_DISCONNECTED_BEFORE_BEING_ADDED_ERROR_UUID,
+ EMERGENCY_CALL_DISCONNECTED_BEFORE_BEING_ADDED_ERROR_MSG);
+ }
+ mListeners.forEach(l -> l.onCallCreatedButNeverAdded(call));
+ }
+
// If a call diagnostic service is in use, we will log the original telephony-provided
// disconnect cause, inform the CDS of the disconnection, and then chain the update of the
// call state until AFTER the CDS reports it's result back.
@@ -3932,7 +3978,11 @@
connectElapsedTime,
mClockProxy,
mToastFactory);
- notifyCallCreated(call);
+
+ // Unlike connections, conferences are not created first and then notified as create
+ // connection complete from the CS. They originate from the CS and are reported directly to
+ // telecom where they're added (see below).
+ call.setIsCreateConnectionComplete(true);
setCallState(call, Call.getStateFromConnectionState(parcelableConference.getState()),
"new conference call");
@@ -4903,17 +4953,28 @@
}
}
+ /**
+ * Ensures that the call will be audible to the user by checking if the voice call stream is
+ * audible, and if not increasing the volume to the default value.
+ */
private void ensureCallAudible() {
- AudioManager am = mContext.getSystemService(AudioManager.class);
- if (am == null) {
- Log.w(this, "ensureCallAudible: audio manager is null");
- return;
- }
- if (am.getStreamVolume(AudioManager.STREAM_VOICE_CALL) == 0) {
- Log.i(this, "ensureCallAudible: voice call stream has volume 0. Adjusting to default.");
- am.setStreamVolume(AudioManager.STREAM_VOICE_CALL,
- AudioSystem.getDefaultStreamVolume(AudioManager.STREAM_VOICE_CALL), 0);
- }
+ // Audio manager APIs can be somewhat slow. To prevent a potential ANR we will fire off
+ // this opreation on the async task executor. Note that this operation does not have any
+ // dependency on any Telecom state, so we can safely launch this on a different thread
+ // without worrying that it is in the Telecom sync lock.
+ mAsyncTaskExecutor.execute(() -> {
+ AudioManager am = mContext.getSystemService(AudioManager.class);
+ if (am == null) {
+ Log.w(this, "ensureCallAudible: audio manager is null");
+ return;
+ }
+ if (am.getStreamVolume(AudioManager.STREAM_VOICE_CALL) == 0) {
+ Log.i(this,
+ "ensureCallAudible: voice call stream has volume 0. Adjusting to default.");
+ am.setStreamVolume(AudioManager.STREAM_VOICE_CALL,
+ AudioSystem.getDefaultStreamVolume(AudioManager.STREAM_VOICE_CALL), 0);
+ }
+ });
}
/**
@@ -4980,6 +5041,9 @@
call.setParentCall(parentCall);
}
}
+ // Existing connections originate from a connection service, so they are completed creation
+ // by the ConnectionService implicitly.
+ call.setIsCreateConnectionComplete(true);
addCall(call);
if (parentCall != null) {
// Now, set the call as a child of the parent since it has been added to Telecom. This
@@ -5459,6 +5523,9 @@
} else {
call.setConnectionService(service);
service.createConnectionFailed(call);
+ if (!mCalls.contains(call)){
+ mListeners.forEach(l -> l.onCallCreatedButNeverAdded(call));
+ }
}
}
@@ -5481,6 +5548,9 @@
} else {
call.setConnectionService(service);
service.createConferenceFailed(call);
+ if (!mCalls.contains(call)){
+ mListeners.forEach(l -> l.onCallCreatedButNeverAdded(call));
+ }
}
}
@@ -5951,18 +6021,24 @@
}
}
- // driver method to create and execute a new TransactionalFocusRequestCallback
- public void transactionRequestNewFocusCall(Call call,
+ /**
+ * Intended for ongoing or new calls that would like to go active/answered and need to
+ * update the mConnectionSvrFocusMgr before setting the state
+ */
+ public void transactionRequestNewFocusCall(Call call, int newCallState,
OutcomeReceiver<Boolean, CallException> callback) {
Log.d(this, "transactionRequestNewFocusCall");
- PendingAction pendingAction = new ActionSetCallState(call, CallState.ACTIVE,
+ PendingAction pendingAction = new ActionSetCallState(call, newCallState,
"transactional ActionSetCallState");
mConnectionSvrFocusMgr
.requestFocus(call,
new TransactionalFocusRequestCallback(pendingAction, call, callback));
}
- // request a new call focus and ensure the request was successful
+ /**
+ * Request a new call focus and ensure the request was successful via an OutcomeReceiver. Also,
+ * include a PendingAction that will execute if the call focus change is successful.
+ */
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
public class TransactionalFocusRequestCallback implements
ConnectionServiceFocusManager.RequestFocusCallback {
@@ -5981,26 +6057,18 @@
@Override
public void onRequestFocusDone(ConnectionServiceFocusManager.CallFocus call) {
Call currentCallFocus = (Call) mConnectionSvrFocusMgr.getCurrentFocusCall();
-
- if (currentCallFocus == null) {
- Log.i(this, "TransactionalFocusRequestCallback: "
- + "currentCallFocus is null.");
- mCallback.onError(new CallException("currentCallFocus is null",
+ // verify the update was successful before updating the state
+ Log.i(this, "tFRC: currentCallFocus=[%s], targetFocus=[%s]",
+ mTargetCallFocus, currentCallFocus);
+ if (currentCallFocus == null ||
+ !currentCallFocus.getId().equals(mTargetCallFocus.getId())) {
+ mCallback.onError(new CallException("failed to switch focus to requested call",
CallException.CODE_CALL_CANNOT_BE_SET_TO_ACTIVE));
return;
}
-
- Log.i(this, "TransactionalFocusRequestCallback: targetId=[%s], "
- + "currentId=[%s]", mTargetCallFocus.getId(), currentCallFocus.getId());
- if (!currentCallFocus.getId().equals(mTargetCallFocus.getId())) {
- Log.i(this, "TransactionalFocusRequestCallback: "
- + "currentCallFocus is not equal to targetCallFocus.");
- mCallback.onError(new CallException("current focus is not target focus",
- CallException.CODE_CALL_CANNOT_BE_SET_TO_ACTIVE));
- return;
- }
- mPendingAction.performAction();
- mCallback.onResult(true);
+ // at this point, we know the FocusManager is able to update successfully
+ mPendingAction.performAction(); // set the call state
+ mCallback.onResult(true); // complete the transaction
}
}
diff --git a/src/com/android/server/telecom/CallsManagerListenerBase.java b/src/com/android/server/telecom/CallsManagerListenerBase.java
index 6b8e2fe..6754237 100644
--- a/src/com/android/server/telecom/CallsManagerListenerBase.java
+++ b/src/com/android/server/telecom/CallsManagerListenerBase.java
@@ -35,6 +35,10 @@
}
@Override
+ public void onCallCreatedButNeverAdded(Call call) {
+ }
+
+ @Override
public void onCallStateChanged(Call call, int oldState, int newState) {
}
diff --git a/src/com/android/server/telecom/CarModeTracker.java b/src/com/android/server/telecom/CarModeTracker.java
index 737ce5a..ae8febf 100644
--- a/src/com/android/server/telecom/CarModeTracker.java
+++ b/src/com/android/server/telecom/CarModeTracker.java
@@ -150,7 +150,9 @@
Log.i(this, "handleExitCarMode: packageName=%s, priority=%d", packageName, priority);
mCarModeChangeLog.log("exitCarMode: packageName=" + packageName + ", priority="
+ priority);
- mCarModeApps.removeIf(c -> c.getPriority() == priority);
+
+ //Remove the car mode app with specified priority without clearing out the projection entry.
+ mCarModeApps.removeIf(c -> c.getPriority() == priority && !c.hasSetAutomotiveProjection());
}
public void handleSetAutomotiveProjection(@NonNull String packageName) {
diff --git a/src/com/android/server/telecom/ConnectionServiceWrapper.java b/src/com/android/server/telecom/ConnectionServiceWrapper.java
index 874b77f..59a84f9 100644
--- a/src/com/android/server/telecom/ConnectionServiceWrapper.java
+++ b/src/com/android/server/telecom/ConnectionServiceWrapper.java
@@ -1597,7 +1597,7 @@
public void onSuccess() {
String callId = mCallIdMapper.getCallId(call);
if (callId == null) {
- Log.w(ConnectionServiceWrapper.this, "Call not present"
+ Log.i(ConnectionServiceWrapper.this, "Call not present"
+ " in call id mapper, maybe it was aborted before the bind"
+ " completed successfully?");
response.handleCreateConnectionFailure(
diff --git a/src/com/android/server/telecom/HeadsetMediaButton.java b/src/com/android/server/telecom/HeadsetMediaButton.java
index 7458f54..8e9caff 100644
--- a/src/com/android/server/telecom/HeadsetMediaButton.java
+++ b/src/com/android/server/telecom/HeadsetMediaButton.java
@@ -23,11 +23,16 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.telecom.CallAudioState;
+import android.telecom.CallEndpoint;
import android.telecom.Log;
+import android.util.ArraySet;
import android.view.KeyEvent;
import com.android.internal.annotations.VisibleForTesting;
+import java.util.Set;
+
/**
* Static class to handle listening to the headset media buttons.
*/
@@ -149,8 +154,10 @@
private final Context mContext;
private final CallsManager mCallsManager;
private final TelecomSystem.SyncRoot mLock;
+ private final Set<Call> mCalls = new ArraySet<>();
private MediaSessionAdapter mSession;
private KeyEvent mLastHookEvent;
+ private @CallEndpoint.EndpointType int mCurrentEndpointType;
/**
* Constructor used for testing purposes to initialize a {@link HeadsetMediaButton} with a
@@ -212,7 +219,7 @@
return mCallsManager.onMediaButton(LONG_PRESS);
} else if (event.getAction() == KeyEvent.ACTION_UP) {
// We should not judge SHORT_PRESS by ACTION_UP event repeatCount, because it always
- // return 0.
+ // returns 0.
// Actually ACTION_DOWN event repeatCount only increases when LONG_PRESS performed.
if (mLastHookEvent != null && mLastHookEvent.getRepeatCount() == 0) {
return mCallsManager.onMediaButton(SHORT_PRESS);
@@ -226,52 +233,72 @@
return true;
}
+ @Override
+ public void onCallEndpointChanged(CallEndpoint callEndpoint) {
+ mCurrentEndpointType = callEndpoint.getEndpointType();
+ Log.i(this, "onCallEndpointChanged: endPoint=%s", callEndpoint);
+ maybeChangeSessionState();
+ }
+
/** ${inheritDoc} */
@Override
public void onCallAdded(Call call) {
- if (call.isExternalCall()) {
- return;
- }
- handleCallAddition();
+ handleCallAddition(call);
}
/**
* Triggers session activation due to call addition.
*/
- private void handleCallAddition() {
- mMediaSessionHandler.obtainMessage(MSG_MEDIA_SESSION_SET_ACTIVE, 1, 0).sendToTarget();
- }
-
- /** ${inheritDoc} */
- @Override
- public void onCallRemoved(Call call) {
- if (call.isExternalCall()) {
- return;
- }
- handleCallRemoval();
+ private void handleCallAddition(Call call) {
+ mCalls.add(call);
+ maybeChangeSessionState();
}
/**
- * Triggers session deactivation due to call removal.
+ * Based on whether there are tracked calls and the audio is routed to a wired headset,
+ * potentially activate or deactive the media session.
*/
- private void handleCallRemoval() {
- if (!mCallsManager.hasAnyCalls()) {
+ private void maybeChangeSessionState() {
+ boolean hasNonExternalCalls = !mCalls.isEmpty()
+ && mCalls.stream().anyMatch(c -> !c.isExternalCall());
+ if (hasNonExternalCalls && mCurrentEndpointType == CallEndpoint.TYPE_WIRED_HEADSET) {
+ Log.i(this, "maybeChangeSessionState: hasCalls=%b, currentEndpointType=%s, ACTIVATE",
+ hasNonExternalCalls, CallEndpoint.endpointTypeToString(mCurrentEndpointType));
+ mMediaSessionHandler.obtainMessage(MSG_MEDIA_SESSION_SET_ACTIVE, 1, 0).sendToTarget();
+ } else {
+ Log.i(this, "maybeChangeSessionState: hasCalls=%b, currentEndpointType=%s, DEACTIVATE",
+ hasNonExternalCalls, CallEndpoint.endpointTypeToString(mCurrentEndpointType));
mMediaSessionHandler.obtainMessage(MSG_MEDIA_SESSION_SET_ACTIVE, 0, 0).sendToTarget();
}
}
/** ${inheritDoc} */
@Override
- public void onExternalCallChanged(Call call, boolean isExternalCall) {
- // Note: We don't use the onCallAdded/onCallRemoved methods here since they do checks to see
- // if the call is external or not and would skip the session activation/deactivation.
- if (isExternalCall) {
- handleCallRemoval();
- } else {
- handleCallAddition();
+ public void onCallRemoved(Call call) {
+ handleCallRemoval(call);
+ }
+
+ /**
+ * Triggers session deactivation due to call removal.
+ */
+ private void handleCallRemoval(Call call) {
+ // If we were tracking the call, potentially change session state.
+ if (mCalls.remove(call)) {
+ if (mCalls.isEmpty()) {
+ // When there are no calls, don't cache that we previously had a wired headset
+ // connected; we'll be updated on the next call.
+ mCurrentEndpointType = CallEndpoint.TYPE_UNKNOWN;
+ }
+ maybeChangeSessionState();
}
}
+ /** ${inheritDoc} */
+ @Override
+ public void onExternalCallChanged(Call call, boolean isExternalCall) {
+ maybeChangeSessionState();
+ }
+
@VisibleForTesting
/**
* @return the handler this class instance uses for operation; used for unit testing.
diff --git a/src/com/android/server/telecom/InCallAdapter.java b/src/com/android/server/telecom/InCallAdapter.java
index f5214ed..9ce10bd 100755
--- a/src/com/android/server/telecom/InCallAdapter.java
+++ b/src/com/android/server/telecom/InCallAdapter.java
@@ -695,7 +695,7 @@
@Override
public void sendRttRequest(String callId) {
try {
- Log.startSession("ICA.sRR");
+ Log.startSession("ICA.sRR", mOwnerPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -717,7 +717,7 @@
@Override
public void respondToRttRequest(String callId, int id, boolean accept) {
try {
- Log.startSession("ICA.rTRR");
+ Log.startSession("ICA.rTRR", mOwnerPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -739,7 +739,7 @@
@Override
public void stopRtt(String callId) {
try {
- Log.startSession("ICA.sRTT");
+ Log.startSession("ICA.sRTT", mOwnerPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
@@ -761,11 +761,16 @@
@Override
public void setRttMode(String callId, int mode) {
try {
- Log.startSession("ICA.sRM");
+ Log.startSession("ICA.sRM", mOwnerPackageAbbreviation);
long token = Binder.clearCallingIdentity();
try {
synchronized (mLock) {
- // TODO
+ Call call = mCallIdMapper.getCall(callId);
+ if (call != null) {
+ call.setRttMode(mode);
+ } else {
+ Log.w(this, "setRttMode(): call %s not found", callId);
+ }
}
} finally {
Binder.restoreCallingIdentity(token);
diff --git a/src/com/android/server/telecom/InCallController.java b/src/com/android/server/telecom/InCallController.java
index 33ddf0a..01eb319 100644
--- a/src/com/android/server/telecom/InCallController.java
+++ b/src/com/android/server/telecom/InCallController.java
@@ -2600,8 +2600,17 @@
private UserHandle getUserFromCall(Call call) {
// Call may never be specified, so we can fall back to using the CallManager current user.
- return call == null
- ? mCallsManager.getCurrentUserHandle()
- : call.getUserHandleFromTargetPhoneAccount();
+ if (call == null) {
+ return mCallsManager.getCurrentUserHandle();
+ } else {
+ UserHandle userFromCall = call.getUserHandleFromTargetPhoneAccount();
+ UserManager userManager = mContext.getSystemService(UserManager.class);
+ // Emergency call should never be blocked, so if the user associated with call is in
+ // quite mode, use the primary user for the emergency call.
+ if (call.isEmergencyCall() && userManager.isQuietModeEnabled(userFromCall)) {
+ return mCallsManager.getCurrentUserHandle();
+ }
+ return userFromCall;
+ }
}
}
diff --git a/src/com/android/server/telecom/InCallTonePlayer.java b/src/com/android/server/telecom/InCallTonePlayer.java
index 0957eb4..3cc4aac 100644
--- a/src/com/android/server/telecom/InCallTonePlayer.java
+++ b/src/com/android/server/telecom/InCallTonePlayer.java
@@ -170,7 +170,7 @@
private static final int RELATIVE_VOLUME_EMERGENCY = 100;
private static final int RELATIVE_VOLUME_HIPRI = 80;
- private static final int RELATIVE_VOLUME_LOPRI = 50;
+ private static final int RELATIVE_VOLUME_LOPRI = 30;
private static final int RELATIVE_VOLUME_UNDEFINED = -1;
// Buffer time (in msec) to add on to the tone timeout value. Needed mainly when the timeout
diff --git a/src/com/android/server/telecom/LogUtils.java b/src/com/android/server/telecom/LogUtils.java
index 35f8d1e..f233ccb 100644
--- a/src/com/android/server/telecom/LogUtils.java
+++ b/src/com/android/server/telecom/LogUtils.java
@@ -109,6 +109,11 @@
public static final String SET_SELECT_PHONE_ACCOUNT = "SET_SELECT_PHONE_ACCOUNT";
public static final String SET_AUDIO_PROCESSING = "SET_AUDIO_PROCESSING";
public static final String SET_SIMULATED_RINGING = "SET_SIMULATED_RINGING";
+ public static final String REQUEST_RTT = "REQUEST_RTT";
+ public static final String RESPOND_TO_RTT_REQUEST = "RESPOND_TO_RTT_REQUEST";
+ public static final String SET_RRT_MODE = "SET_RTT_MODE";
+ public static final String ON_RTT_FAILED = "ON_RTT_FAILED";
+ public static final String ON_RTT_REQUEST = "ON_RTT_REQUEST";
public static final String REQUEST_HOLD = "REQUEST_HOLD";
public static final String REQUEST_UNHOLD = "REQUEST_UNHOLD";
public static final String REQUEST_DISCONNECT = "REQUEST_DISCONNECT";
diff --git a/src/com/android/server/telecom/PhoneAccountRegistrar.java b/src/com/android/server/telecom/PhoneAccountRegistrar.java
index a7824b4..3c6934a 100644
--- a/src/com/android/server/telecom/PhoneAccountRegistrar.java
+++ b/src/com/android/server/telecom/PhoneAccountRegistrar.java
@@ -962,18 +962,20 @@
String[] fields =
{"Package Name", "Class Name", "PhoneAccountHandle Id", "Label", "ShortDescription",
- "GroupId", "Address"};
+ "GroupId", "Address", "SubscriptionAddress"};
CharSequence[] args = {handle.getComponentName().getPackageName(),
handle.getComponentName().getClassName(), handle.getId(), account.getLabel(),
account.getShortDescription(), account.getGroupId(),
- (account.getAddress() != null ? account.getAddress().toString() : "")};
+ (account.getAddress() != null ? account.getAddress().toString() : ""),
+ (account.getSubscriptionAddress() != null ?
+ account.getSubscriptionAddress().toString() : "")};
for (int i = 0; i < fields.length; i++) {
if (args[i] != null && args[i].length() > MAX_PHONE_ACCOUNT_FIELD_CHAR_LIMIT) {
EventLog.writeEvent(0x534e4554, "259064622", Binder.getCallingUid(),
"enforceCharacterLimit");
- throw new IllegalArgumentException("The PhoneAccount or PhoneAccountHandle"
- + fields[i] + " field has an invalid character count. PhoneAccount and "
+ throw new IllegalArgumentException("The PhoneAccount or PhoneAccountHandle ["
+ + fields[i] + "] field has an invalid character count. PhoneAccount and "
+ "PhoneAccountHandle String and Char-Sequence fields are limited to "
+ MAX_PHONE_ACCOUNT_FIELD_CHAR_LIMIT + " characters.");
}
diff --git a/src/com/android/server/telecom/Ringer.java b/src/com/android/server/telecom/Ringer.java
index 171745f..3b45355 100644
--- a/src/com/android/server/telecom/Ringer.java
+++ b/src/com/android/server/telecom/Ringer.java
@@ -21,6 +21,7 @@
import static android.provider.CallLog.Calls.USER_MISSED_NO_VIBRATE;
import static android.provider.Settings.Global.ZEN_MODE_OFF;
+import android.annotation.NonNull;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.Person;
@@ -32,6 +33,8 @@
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
+import android.os.UserHandle;
+import android.os.UserManager;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
@@ -54,20 +57,25 @@
*/
@VisibleForTesting
public class Ringer {
- /**
- * Flag only for local debugging. Do not submit enabled.
- */
- private static final boolean DEBUG_RINGER = false;
-
- public static class VibrationEffectProxy {
- public VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) {
- return VibrationEffect.createWaveform(timings, amplitudes, repeat);
+ public interface AccessibilityManagerAdapter {
+ boolean startFlashNotificationSequence(@NonNull Context context,
+ @AccessibilityManager.FlashNotificationReason int reason);
+ boolean stopFlashNotificationSequence(@NonNull Context context);
}
+ /**
+ * Flag only for local debugging. Do not submit enabled.
+ */
+ private static final boolean DEBUG_RINGER = false;
- public VibrationEffect get(Uri ringtoneUri, Context context) {
- return VibrationEffect.get(ringtoneUri, context);
+ public static class VibrationEffectProxy {
+ public VibrationEffect createWaveform(long[] timings, int[] amplitudes, int repeat) {
+ return VibrationEffect.createWaveform(timings, amplitudes, repeat);
+ }
+
+ public VibrationEffect get(Uri ringtoneUri, Context context) {
+ return VibrationEffect.get(ringtoneUri, context);
+ }
}
- }
@VisibleForTesting
public VibrationEffect mDefaultVibrationEffect;
@@ -162,7 +170,7 @@
private RingtoneFactory mRingtoneFactory;
private AudioManager mAudioManager;
private NotificationManager mNotificationManager;
- private AccessibilityManager mAccessibilityManager;
+ private AccessibilityManagerAdapter mAccessibilityManagerAdapter;
/**
* Call objects that are ringing, vibrating or call-waiting. These are used only for logging
@@ -197,7 +205,7 @@
VibrationEffectProxy vibrationEffectProxy,
InCallController inCallController,
NotificationManager notificationManager,
- AccessibilityManager accessibilityManager) {
+ AccessibilityManagerAdapter accessibilityManagerAdapter) {
mLock = new Object();
mSystemSettingsUtil = systemSettingsUtil;
@@ -211,7 +219,7 @@
mInCallController = inCallController;
mVibrationEffectProxy = vibrationEffectProxy;
mNotificationManager = notificationManager;
- mAccessibilityManager = accessibilityManager;
+ mAccessibilityManagerAdapter = accessibilityManagerAdapter;
if (mContext.getResources().getBoolean(R.bool.use_simple_vibration_pattern)) {
mDefaultVibrationEffect = mVibrationEffectProxy.createWaveform(SIMPLE_VIBRATION_PATTERN,
@@ -286,6 +294,10 @@
Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING, "Silent ringing "
+ "requested");
}
+ if (attributes.isWorkProfileInQuietMode()) {
+ Log.addEvent(foregroundCall, LogUtils.Events.SKIP_RINGING,
+ "Work profile in quiet mode");
+ }
if (mBlockOnRingingFuture != null) {
mBlockOnRingingFuture.complete(null);
}
@@ -295,10 +307,11 @@
stopCallWaiting();
final boolean shouldFlash = attributes.shouldRingForContact();
- if (mAccessibilityManager != null && shouldFlash) {
+ if (mAccessibilityManagerAdapter != null && shouldFlash) {
Log.addEvent(foregroundCall, LogUtils.Events.FLASH_NOTIFICATION_START);
- getHandler().post(() -> mAccessibilityManager.startFlashNotificationSequence(mContext,
- AccessibilityManager.FLASH_REASON_CALL));
+ getHandler().post(() ->
+ mAccessibilityManagerAdapter.startFlashNotificationSequence(mContext,
+ AccessibilityManager.FLASH_REASON_CALL));
}
// Determine if the settings and DND mode indicate that the vibrator can be used right now.
@@ -512,9 +525,10 @@
public void stopRinging() {
final Call foregroundCall = mRingingCall != null ? mRingingCall : mVibratingCall;
- if (mAccessibilityManager != null) {
+ if (mAccessibilityManagerAdapter != null) {
Log.addEvent(foregroundCall, LogUtils.Events.FLASH_NOTIFICATION_STOP);
- getHandler().post(() -> mAccessibilityManager.stopFlashNotificationSequence(mContext));
+ getHandler().post(() ->
+ mAccessibilityManagerAdapter.stopFlashNotificationSequence(mContext));
}
synchronized (mLock) {
@@ -632,16 +646,20 @@
boolean letDialerHandleRinging = mInCallController.doesConnectedDialerSupportRinging(
call.getUserHandleFromTargetPhoneAccount());
timer.record("letDialerHandleRinging");
+ boolean isWorkProfileInQuietMode =
+ isProfileInQuietMode(call.getUserHandleFromTargetPhoneAccount());
+ timer.record("isWorkProfileInQuietMode");
Log.i(this, "startRinging timings: " + timer);
boolean endEarly = isTheaterModeOn || letDialerHandleRinging || isSelfManaged ||
- hasExternalRinger || isSilentRingingRequested;
+ hasExternalRinger || isSilentRingingRequested || isWorkProfileInQuietMode;
if (endEarly) {
Log.i(this, "Ending early -- isTheaterModeOn=%s, letDialerHandleRinging=%s, " +
- "isSelfManaged=%s, hasExternalRinger=%s, silentRingingRequested=%s",
+ "isSelfManaged=%s, hasExternalRinger=%s, silentRingingRequested=%s, " +
+ "isWorkProfileInQuietMode=%s",
isTheaterModeOn, letDialerHandleRinging, isSelfManaged, hasExternalRinger,
- isSilentRingingRequested);
+ isSilentRingingRequested, isWorkProfileInQuietMode);
}
// Acquire audio focus under any of the following conditions:
@@ -649,8 +667,8 @@
// 2. Volume is over zero, we should ring for the contact, and there's a audible ringtone
// present. (This check is deferred until ringer knows the ringtone)
// 3. The call is self-managed.
- boolean shouldAcquireAudioFocus =
- (isHfpDeviceAttached && shouldRingForContact) || isSelfManaged;
+ boolean shouldAcquireAudioFocus = !isWorkProfileInQuietMode &&
+ ((isHfpDeviceAttached && shouldRingForContact) || isSelfManaged);
// Set missed reason according to attributes
if (!isVolumeOverZero) {
@@ -668,9 +686,15 @@
.setInaudibleReason(inaudibleReason)
.setShouldRingForContact(shouldRingForContact)
.setSilentRingingRequested(isSilentRingingRequested)
+ .setWorkProfileQuietMode(isWorkProfileInQuietMode)
.build();
}
+ private boolean isProfileInQuietMode(UserHandle user) {
+ UserManager um = mContext.getSystemService(UserManager.class);
+ return um.isManagedProfile(user.getIdentifier()) && um.isQuietModeEnabled(user);
+ }
+
private Handler getHandler() {
if (mHandler == null) {
HandlerThread handlerThread = new HandlerThread("Ringer");
diff --git a/src/com/android/server/telecom/RingerAttributes.java b/src/com/android/server/telecom/RingerAttributes.java
index 840d815..e0d3e1c 100644
--- a/src/com/android/server/telecom/RingerAttributes.java
+++ b/src/com/android/server/telecom/RingerAttributes.java
@@ -25,6 +25,7 @@
private String mInaudibleReason;
private boolean mShouldRingForContact;
private boolean mSilentRingingRequested;
+ private boolean mWorkProfileQuietMode;
public RingerAttributes.Builder setEndEarly(boolean endEarly) {
mEndEarly = endEarly;
@@ -61,10 +62,15 @@
return this;
}
+ public RingerAttributes.Builder setWorkProfileQuietMode(boolean workProfileQuietMode) {
+ mWorkProfileQuietMode = workProfileQuietMode;
+ return this;
+ }
+
public RingerAttributes build() {
return new RingerAttributes(mEndEarly, mLetDialerHandleRinging, mAcquireAudioFocus,
mRingerAudible, mInaudibleReason, mShouldRingForContact,
- mSilentRingingRequested);
+ mSilentRingingRequested, mWorkProfileQuietMode);
}
}
@@ -75,10 +81,12 @@
private String mInaudibleReason;
private boolean mShouldRingForContact;
private boolean mSilentRingingRequested;
+ private boolean mWorkProfileQuietMode;
private RingerAttributes(boolean endEarly, boolean letDialerHandleRinging,
boolean acquireAudioFocus, boolean ringerAudible, String inaudibleReason,
- boolean shouldRingForContact, boolean silentRingingRequested) {
+ boolean shouldRingForContact, boolean silentRingingRequested,
+ boolean workProfileQuietMode) {
mEndEarly = endEarly;
mLetDialerHandleRinging = letDialerHandleRinging;
mAcquireAudioFocus = acquireAudioFocus;
@@ -86,6 +94,7 @@
mInaudibleReason = inaudibleReason;
mShouldRingForContact = shouldRingForContact;
mSilentRingingRequested = silentRingingRequested;
+ mWorkProfileQuietMode = workProfileQuietMode;
}
public boolean isEndEarly() {
@@ -115,4 +124,8 @@
public boolean isSilentRingingRequested() {
return mSilentRingingRequested;
}
+
+ public boolean isWorkProfileInQuietMode() {
+ return mWorkProfileQuietMode;
+ }
}
diff --git a/src/com/android/server/telecom/StatusBarNotifier.java b/src/com/android/server/telecom/StatusBarNotifier.java
index d1de958..772335e 100644
--- a/src/com/android/server/telecom/StatusBarNotifier.java
+++ b/src/com/android/server/telecom/StatusBarNotifier.java
@@ -79,13 +79,15 @@
mIsShowingMute = isMuted;
}
+ /**
+ * Update the status bar manager with the new speakerphone state.
+ *
+ * IMPORTANT: DO NOT call into any Telecom code here; this is usually scheduled on an async
+ * executor to save Telecom from blocking on outgoing binder calls.
+ * @param isSpeakerphone
+ */
@VisibleForTesting
public void notifySpeakerphone(boolean isSpeakerphone) {
- // Never display anything if there are no calls.
- if (!mCallsManager.hasAnyCalls()) {
- isSpeakerphone = false;
- }
-
if (mIsShowingSpeakerphone == isSpeakerphone) {
return;
}
diff --git a/src/com/android/server/telecom/TelecomServiceImpl.java b/src/com/android/server/telecom/TelecomServiceImpl.java
index 6826290..90e7164 100644
--- a/src/com/android/server/telecom/TelecomServiceImpl.java
+++ b/src/com/android/server/telecom/TelecomServiceImpl.java
@@ -1813,7 +1813,7 @@
}
mUserCallIntentProcessorFactory.create(mContext, userHandle)
.processIntent(
- intent, callingPackage, isSelfManaged ||
+ intent, callingPackage, isSelfManaged,
(hasCallAppOp && hasCallPermission),
true /* isLocalInvocation */);
} finally {
diff --git a/src/com/android/server/telecom/TelecomSystem.java b/src/com/android/server/telecom/TelecomSystem.java
index 63852f3..8354d5e 100644
--- a/src/com/android/server/telecom/TelecomSystem.java
+++ b/src/com/android/server/telecom/TelecomSystem.java
@@ -31,6 +31,7 @@
import android.telecom.Log;
import android.telecom.PhoneAccountHandle;
import android.telephony.AnomalyReporter;
+import android.view.accessibility.AccessibilityManager;
import android.widget.Toast;
import androidx.annotation.NonNull;
@@ -52,6 +53,7 @@
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.List;
+import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
/**
@@ -208,7 +210,9 @@
ClockProxy clockProxy,
RoleManagerAdapter roleManagerAdapter,
ContactsAsyncHelper.Factory contactsAsyncHelperFactory,
- DeviceIdleControllerAdapter deviceIdleControllerAdapter) {
+ DeviceIdleControllerAdapter deviceIdleControllerAdapter,
+ Ringer.AccessibilityManagerAdapter accessibilityManagerAdapter,
+ Executor asyncTaskExecutor) {
mContext = context.getApplicationContext();
LogUtils.initLogging(mContext);
AnomalyReporter.initialize(mContext);
@@ -332,6 +336,7 @@
CallAnomalyWatchdog callAnomalyWatchdog = new CallAnomalyWatchdog(
Executors.newSingleThreadScheduledExecutor(),
mLock, timeoutsAdapter, clockProxy);
+
mCallsManager = new CallsManager(
mContext,
mLock,
@@ -363,7 +368,9 @@
roleManagerAdapter,
toastFactory,
callEndpointControllerFactory,
- callAnomalyWatchdog);
+ callAnomalyWatchdog,
+ accessibilityManagerAdapter,
+ asyncTaskExecutor);
mIncomingCallNotifier = incomingCallNotifier;
incomingCallNotifier.setCallsManagerProxy(new IncomingCallNotifier.CallsManagerProxy() {
diff --git a/src/com/android/server/telecom/Timeouts.java b/src/com/android/server/telecom/Timeouts.java
index d4713e8..39c86ad 100644
--- a/src/com/android/server/telecom/Timeouts.java
+++ b/src/com/android/server/telecom/Timeouts.java
@@ -392,7 +392,7 @@
*/
public static long getNonVoipCallIntermediateStateTimeoutMillis() {
return DeviceConfig.getLong(DeviceConfig.NAMESPACE_TELEPHONY,
- INTERMEDIATE_STATE_NON_VOIP_NORMAL_TIMEOUT_MILLIS, 60000L);
+ INTERMEDIATE_STATE_NON_VOIP_NORMAL_TIMEOUT_MILLIS, 120000L);
}
/**
diff --git a/src/com/android/server/telecom/TransactionalServiceWrapper.java b/src/com/android/server/telecom/TransactionalServiceWrapper.java
index 6931996..d97d192 100644
--- a/src/com/android/server/telecom/TransactionalServiceWrapper.java
+++ b/src/com/android/server/telecom/TransactionalServiceWrapper.java
@@ -38,6 +38,7 @@
import com.android.internal.telecom.ICallControl;
import com.android.internal.telecom.ICallEventCallback;
+import com.android.server.telecom.voip.AnswerCallTransaction;
import com.android.server.telecom.voip.CallEventCallbackAckTransaction;
import com.android.server.telecom.voip.EndpointChangeTransaction;
import com.android.server.telecom.voip.HoldCallTransaction;
@@ -67,7 +68,7 @@
// CallControl : Client (ex. voip app) --> Telecom
public static final String SET_ACTIVE = "SetActive";
public static final String SET_INACTIVE = "SetInactive";
- public static final String REJECT = "Reject";
+ public static final String ANSWER = "Answer";
public static final String DISCONNECT = "Disconnect";
public static final String START_STREAMING = "StartStreaming";
@@ -75,7 +76,6 @@
public static final String ON_SET_ACTIVE = "onSetActive";
public static final String ON_SET_INACTIVE = "onSetInactive";
public static final String ON_ANSWER = "onAnswer";
- public static final String ON_REJECT = "onReject";
public static final String ON_DISCONNECT = "onDisconnect";
public static final String ON_STREAMING_STARTED = "onStreamingStarted";
@@ -196,6 +196,17 @@
}
@Override
+ public void answer(int videoState, String callId, android.os.ResultReceiver callback)
+ throws RemoteException {
+ try {
+ Log.startSession("TSW.a");
+ createTransactions(callId, callback, ANSWER, videoState);
+ } finally {
+ Log.endSession();
+ }
+ }
+
+ @Override
public void setInactive(String callId, android.os.ResultReceiver callback)
throws RemoteException {
try {
@@ -238,6 +249,10 @@
case SET_ACTIVE:
addTransactionsToManager(createSetActiveTransactions(call), callback);
break;
+ case ANSWER:
+ addTransactionsToManager(createSetAnswerTransactions(call,
+ (int) objects[0]), callback);
+ break;
case DISCONNECT:
addTransactionsToManager(new EndCallTransaction(mCallsManager,
(DisconnectCause) objects[0], call), callback);
@@ -273,6 +288,28 @@
Log.endSession();
}
}
+
+ /**
+ * Application would like to inform InCallServices of an event
+ */
+ @Override
+ public void sendEvent(String callId, String event, Bundle extras) {
+ try {
+ Log.startSession("TSW.sE");
+ Call call = mTrackedCalls.get(callId);
+ if (call != null) {
+ call.onConnectionEvent(event, extras);
+ }
+ else{
+ Log.i(TAG,
+ "sendEvent: was called but there is no call with id=[%s] cannot be "
+ + "found. Most likely the call has been disconnected");
+ }
+ }
+ finally {
+ Log.endSession();
+ }
+ }
};
private void addTransactionsToManager(VoipCallTransaction transaction,
@@ -366,7 +403,7 @@
Log.i(TAG, String.format(Locale.US, "onSetInactive: callId=[%s]", call.getId()));
mTransactionManager.addTransaction(
new CallEventCallbackAckTransaction(mICallEventCallback,
- ON_SET_INACTIVE, call.getId(), 0), new OutcomeReceiver<>() {
+ ON_SET_INACTIVE, call.getId()), new OutcomeReceiver<>() {
@Override
public void onResult(VoipCallTransactionResult result) {
mCallsManager.markCallAsOnHold(call);
@@ -389,7 +426,7 @@
mTransactionManager.addTransaction(
new CallEventCallbackAckTransaction(mICallEventCallback, ON_DISCONNECT,
- call.getId(), 0), new OutcomeReceiver<>() {
+ call.getId(), cause), new OutcomeReceiver<>() {
@Override
public void onResult(VoipCallTransactionResult result) {
removeCallFromCallsManager(call, cause);
@@ -406,32 +443,6 @@
}
}
- public void onReject(Call call, @android.telecom.Call.RejectReason int rejectReason) {
- try {
- Log.startSession("TSW.oR");
- Log.d(TAG, String.format(Locale.US, "onReject: callId=[%s]", call.getId()));
-
- mTransactionManager.addTransaction(
- new CallEventCallbackAckTransaction(mICallEventCallback, ON_REJECT,
- call.getId(), 0), new OutcomeReceiver<>() {
- @Override
- public void onResult(VoipCallTransactionResult result) {
- removeCallFromCallsManager(call,
- new DisconnectCause(DisconnectCause.REJECTED));
- }
-
- @Override
- public void onError(CallException exception) {
- removeCallFromCallsManager(call,
- new DisconnectCause(DisconnectCause.REJECTED));
- }
- }
- );
- } finally {
- Log.endSession();
- }
- }
-
public void onCallStreamingStarted(Call call) {
try {
Log.startSession("TSW.oCSS");
@@ -440,7 +451,7 @@
mTransactionManager.addTransaction(
new CallEventCallbackAckTransaction(mICallEventCallback, ON_STREAMING_STARTED,
- call.getId(), 0), new OutcomeReceiver<>() {
+ call.getId()), new OutcomeReceiver<>() {
@Override
public void onResult(VoipCallTransactionResult result) {
}
@@ -508,6 +519,15 @@
}
}
+ public void onEvent(Call call, String event, Bundle extras){
+ if (call != null) {
+ try {
+ mICallEventCallback.onEvent(call.getId(), event, extras);
+ } catch (RemoteException e) {
+ }
+ }
+ }
+
/***
*********************************************************************************************
** Helpers **
@@ -536,13 +556,27 @@
// add t1. hold potential active call
transactions.add(new HoldActiveCallForNewCallTransaction(mCallsManager, call));
- // add t2. answer current call
+ // add t2. send request to set the current call active
transactions.add(new RequestFocusTransaction(mCallsManager, call));
// send off to Transaction Manager to process
return new SerialTransaction(transactions);
}
+ private SerialTransaction createSetAnswerTransactions(Call call, int videoState) {
+ // create list for multiple transactions
+ List<VoipCallTransaction> transactions = new ArrayList<>();
+
+ // add t1. hold potential active call
+ transactions.add(new HoldActiveCallForNewCallTransaction(mCallsManager, call));
+
+ // add t2. answer current call
+ transactions.add(new AnswerCallTransaction(mCallsManager, call, videoState));
+
+ // send off to Transaction Manager to process
+ return new SerialTransaction(transactions);
+ }
+
/***
*********************************************************************************************
** FocusManager **
diff --git a/src/com/android/server/telecom/components/TelecomService.java b/src/com/android/server/telecom/components/TelecomService.java
index 9ad0da4..2ae5d8a 100644
--- a/src/com/android/server/telecom/components/TelecomService.java
+++ b/src/com/android/server/telecom/components/TelecomService.java
@@ -29,6 +29,7 @@
import android.telecom.Log;
import android.telecom.CallerInfoAsyncQuery;
+import android.view.accessibility.AccessibilityManager;
import com.android.internal.telecom.IInternalServiceRetriever;
import com.android.internal.telecom.ITelecomLoader;
@@ -53,6 +54,7 @@
import com.android.server.telecom.ProximitySensorManagerFactory;
import com.android.server.telecom.InCallWakeLockController;
import com.android.server.telecom.ProximitySensorManager;
+import com.android.server.telecom.Ringer;
import com.android.server.telecom.RoleManagerAdapterImpl;
import com.android.server.telecom.TelecomSystem;
import com.android.server.telecom.TelecomWakeLock;
@@ -61,6 +63,8 @@
import com.android.server.telecom.ui.MissedCallNotifierImpl;
import com.android.server.telecom.ui.NotificationChannelManager;
+import java.util.concurrent.Executors;
+
/**
* Implementation of the ITelecom interface.
*/
@@ -191,7 +195,23 @@
new RoleManagerAdapterImpl(context,
(RoleManager) context.getSystemService(Context.ROLE_SERVICE)),
new ContactsAsyncHelper.Factory(),
- internalServiceRetriever.getDeviceIdleController()));
+ internalServiceRetriever.getDeviceIdleController(),
+ new Ringer.AccessibilityManagerAdapter() {
+ @Override
+ public boolean startFlashNotificationSequence(
+ @androidx.annotation.NonNull Context context, int reason) {
+ return context.getSystemService(AccessibilityManager.class)
+ .startFlashNotificationSequence(context, reason);
+ }
+
+ @Override
+ public boolean stopFlashNotificationSequence(
+ @androidx.annotation.NonNull Context context) {
+ return context.getSystemService(AccessibilityManager.class)
+ .stopFlashNotificationSequence(context);
+ }
+ },
+ Executors.newSingleThreadExecutor()));
}
}
diff --git a/src/com/android/server/telecom/components/UserCallActivity.java b/src/com/android/server/telecom/components/UserCallActivity.java
index 1d85884..d7b2001 100644
--- a/src/com/android/server/telecom/components/UserCallActivity.java
+++ b/src/com/android/server/telecom/components/UserCallActivity.java
@@ -74,7 +74,8 @@
// ActivityThread.ActivityClientRecord#intent directly.
// Modifying directly may be a potential risk when relaunching this activity.
new UserCallIntentProcessor(this, userHandle).processIntent(new Intent(intent),
- getCallingPackage(), true /* hasCallAppOp*/, false /* isLocalInvocation */);
+ getCallingPackage(), false, true /* hasCallAppOp*/,
+ false /* isLocalInvocation */);
} finally {
Log.endSession();
wakelock.release();
diff --git a/src/com/android/server/telecom/components/UserCallIntentProcessor.java b/src/com/android/server/telecom/components/UserCallIntentProcessor.java
index cad7b4c..ca990bc 100755
--- a/src/com/android/server/telecom/components/UserCallIntentProcessor.java
+++ b/src/com/android/server/telecom/components/UserCallIntentProcessor.java
@@ -69,6 +69,7 @@
*
* @param intent The intent.
* @param callingPackageName The package name of the calling app.
+ * @param isSelfManaged {@code true} if SelfManaged profile enabled.
* @param canCallNonEmergency {@code true} if the caller is permitted to call non-emergency
* numbers.
* @param isLocalInvocation {@code true} if the caller is within the system service (i.e. the
@@ -79,19 +80,21 @@
* service resides.
*/
public void processIntent(Intent intent, String callingPackageName,
- boolean canCallNonEmergency, boolean isLocalInvocation) {
+ boolean isSelfManaged, boolean canCallNonEmergency,
+ boolean isLocalInvocation) {
String action = intent.getAction();
if (Intent.ACTION_CALL.equals(action) ||
Intent.ACTION_CALL_PRIVILEGED.equals(action) ||
Intent.ACTION_CALL_EMERGENCY.equals(action)) {
- processOutgoingCallIntent(intent, callingPackageName, canCallNonEmergency,
- isLocalInvocation);
+ processOutgoingCallIntent(intent, callingPackageName, isSelfManaged,
+ canCallNonEmergency, isLocalInvocation);
}
}
private void processOutgoingCallIntent(Intent intent, String callingPackageName,
- boolean canCallNonEmergency, boolean isLocalInvocation) {
+ boolean isSelfManaged, boolean canCallNonEmergency,
+ boolean isLocalInvocation) {
Uri handle = intent.getData();
if (handle == null) return;
String scheme = handle.getScheme();
@@ -102,35 +105,37 @@
handle = Uri.fromParts(PhoneAccount.SCHEME_SIP, uriString, null);
}
- // Check DISALLOW_OUTGOING_CALLS restriction. Note: We are skipping this check in a managed
- // profile user because this check can always be bypassed by copying and pasting the phone
- // number into the personal dialer.
- if (!UserUtil.isManagedProfile(mContext, mUserHandle)) {
- // Only emergency calls are allowed for users with the DISALLOW_OUTGOING_CALLS
- // restriction.
- if (!TelephonyUtil.shouldProcessAsEmergency(mContext, handle)) {
- final UserManager userManager = (UserManager) mContext.getSystemService(
- Context.USER_SERVICE);
- if (userManager.hasBaseUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS,
- mUserHandle)) {
- showErrorDialogForRestrictedOutgoingCall(mContext,
- R.string.outgoing_call_not_allowed_user_restriction);
- Log.w(this, "Rejecting non-emergency phone call due to DISALLOW_OUTGOING_CALLS "
- + "restriction");
- return;
- } else if (userManager.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS,
- mUserHandle)) {
- final DevicePolicyManager dpm =
- mContext.getSystemService(DevicePolicyManager.class);
- if (dpm == null) {
+ if(!isSelfManaged && !isLocalInvocation) {
+ // Check DISALLOW_OUTGOING_CALLS restriction. Note: We are skipping this
+ // check in a managed profile user because this check can always be bypassed
+ // by copying and pasting the phone number into the personal dialer.
+ if (!UserUtil.isManagedProfile(mContext, mUserHandle)) {
+ // Only emergency calls are allowed for users with the DISALLOW_OUTGOING_CALLS
+ // restriction.
+ if (!TelephonyUtil.shouldProcessAsEmergency(mContext, handle)) {
+ final UserManager userManager =
+ (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ if (userManager.hasBaseUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS,
+ mUserHandle)) {
+ showErrorDialogForRestrictedOutgoingCall(mContext,
+ R.string.outgoing_call_not_allowed_user_restriction);
+ Log.w(this, "Rejecting non-emergency phone call "
+ + "due to DISALLOW_OUTGOING_CALLS restriction");
+ return;
+ } else if (userManager.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS,
+ mUserHandle)) {
+ final DevicePolicyManager dpm =
+ mContext.getSystemService(DevicePolicyManager.class);
+ if (dpm == null) {
+ return;
+ }
+ final Intent adminSupportIntent = dpm.createAdminSupportIntent(
+ UserManager.DISALLOW_OUTGOING_CALLS);
+ if (adminSupportIntent != null) {
+ mContext.startActivity(adminSupportIntent);
+ }
return;
}
- final Intent adminSupportIntent = dpm.createAdminSupportIntent(
- UserManager.DISALLOW_OUTGOING_CALLS);
- if (adminSupportIntent != null) {
- mContext.startActivity(adminSupportIntent);
- }
- return;
}
}
}
diff --git a/src/com/android/server/telecom/voip/AnswerCallTransaction.java b/src/com/android/server/telecom/voip/AnswerCallTransaction.java
new file mode 100644
index 0000000..1eb34c4
--- /dev/null
+++ b/src/com/android/server/telecom/voip/AnswerCallTransaction.java
@@ -0,0 +1,75 @@
+/*
+ * 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.voip;
+
+import android.os.OutcomeReceiver;
+import android.telecom.CallException;
+import android.util.Log;
+
+import com.android.server.telecom.Call;
+import com.android.server.telecom.CallState;
+import com.android.server.telecom.CallsManager;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+
+/**
+ * This transaction should be created for new incoming calls that request to go from
+ * CallState.Ringing to CallState.Answered. Before changing the CallState, the focus manager must
+ * be updated. Once the focus manager updates, the call state will be set. If there is an issue
+ * answering the call, the transaction will fail.
+ */
+public class AnswerCallTransaction extends VoipCallTransaction {
+
+ private static final String TAG = AnswerCallTransaction.class.getSimpleName();
+ private final CallsManager mCallsManager;
+ private final Call mCall;
+ private final int mVideoState;
+
+ public AnswerCallTransaction(CallsManager callsManager, Call call, int videoState) {
+ mCallsManager = callsManager;
+ mCall = call;
+ mVideoState = videoState;
+ }
+
+ @Override
+ public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
+ Log.d(TAG, "processTransaction");
+ CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
+
+ mCall.setVideoState(mVideoState);
+
+ mCallsManager.transactionRequestNewFocusCall(mCall, CallState.ANSWERED,
+ new OutcomeReceiver<>() {
+ @Override
+ public void onResult(Boolean result) {
+ Log.d(TAG, "processTransaction: onResult");
+ future.complete(new VoipCallTransactionResult(
+ VoipCallTransactionResult.RESULT_SUCCEED, null));
+ }
+
+ @Override
+ public void onError(CallException exception) {
+ Log.d(TAG, "processTransaction: onError");
+ future.complete(new VoipCallTransactionResult(
+ exception.getCode(), exception.getMessage()));
+ }
+ });
+
+ return future;
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/server/telecom/voip/CallEventCallbackAckTransaction.java b/src/com/android/server/telecom/voip/CallEventCallbackAckTransaction.java
index 327694e..f47e4c5 100644
--- a/src/com/android/server/telecom/voip/CallEventCallbackAckTransaction.java
+++ b/src/com/android/server/telecom/voip/CallEventCallbackAckTransaction.java
@@ -22,6 +22,7 @@
import android.os.Bundle;
import android.os.RemoteException;
import android.os.ResultReceiver;
+import android.telecom.DisconnectCause;
import android.util.Log;
import com.android.internal.telecom.ICallEventCallback;
@@ -41,7 +42,10 @@
private final ICallEventCallback mICallEventCallback;
private final String mAction;
private final String mCallId;
- private final int mVideoState;
+ // optional values
+ private int mVideoState = 0;
+ private DisconnectCause mDisconnectCause = null;
+
private final VoipCallTransactionResult TRANSACTION_FAILED = new VoipCallTransactionResult(
CODE_OPERATION_TIMED_OUT, "failed to complete the operation before timeout");
@@ -61,6 +65,14 @@
}
}
+ public CallEventCallbackAckTransaction(ICallEventCallback service, String action,
+ String callId) {
+ mICallEventCallback = service;
+ mAction = action;
+ mCallId = callId;
+ }
+
+
public CallEventCallbackAckTransaction(ICallEventCallback service, String action, String callId,
int videoState) {
mICallEventCallback = service;
@@ -69,6 +81,14 @@
mVideoState = videoState;
}
+ public CallEventCallbackAckTransaction(ICallEventCallback service, String action, String callId,
+ DisconnectCause cause) {
+ mICallEventCallback = service;
+ mAction = action;
+ mCallId = callId;
+ mDisconnectCause = cause;
+ }
+
@Override
public CompletionStage<VoipCallTransactionResult> processTransaction(Void v) {
@@ -82,10 +102,7 @@
mICallEventCallback.onSetInactive(mCallId, receiver);
break;
case TransactionalServiceWrapper.ON_DISCONNECT:
- mICallEventCallback.onDisconnect(mCallId, receiver);
- break;
- case TransactionalServiceWrapper.ON_REJECT:
- mICallEventCallback.onReject(mCallId, receiver);
+ mICallEventCallback.onDisconnect(mCallId, mDisconnectCause, receiver);
break;
case TransactionalServiceWrapper.ON_SET_ACTIVE:
mICallEventCallback.onSetActive(mCallId, receiver);
diff --git a/src/com/android/server/telecom/voip/RequestFocusTransaction.java b/src/com/android/server/telecom/voip/RequestFocusTransaction.java
index f13525f..5dedbda 100644
--- a/src/com/android/server/telecom/voip/RequestFocusTransaction.java
+++ b/src/com/android/server/telecom/voip/RequestFocusTransaction.java
@@ -21,6 +21,7 @@
import android.util.Log;
import com.android.server.telecom.Call;
+import com.android.server.telecom.CallState;
import com.android.server.telecom.CallsManager;
import java.util.concurrent.CompletableFuture;
@@ -42,7 +43,8 @@
Log.d(TAG, "processTransaction");
CompletableFuture<VoipCallTransactionResult> future = new CompletableFuture<>();
- mCallsManager.transactionRequestNewFocusCall(mCall, new OutcomeReceiver<>() {
+ mCallsManager.transactionRequestNewFocusCall(mCall, CallState.ACTIVE,
+ new OutcomeReceiver<>() {
@Override
public void onResult(Boolean result) {
Log.d(TAG, "processTransaction: onResult");
diff --git a/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/MyVoipCall.java b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/MyVoipCall.java
index cdf2efa..690311e 100644
--- a/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/MyVoipCall.java
+++ b/testapps/transactionalVoipApp/src/com/android/server/telecom/transactionalVoipApp/MyVoipCall.java
@@ -16,11 +16,14 @@
package com.android.server.telecom.transactionalVoipApp;
+import android.os.Bundle;
import android.telecom.CallControlCallback;
import android.telecom.CallEndpoint;
import android.telecom.CallControl;
import android.telecom.CallEventCallback;
+import android.telecom.DisconnectCause;
import android.util.Log;
+
import java.util.List;
import androidx.annotation.NonNull;
@@ -60,13 +63,8 @@
}
@Override
- public void onReject(@NonNull Consumer<Boolean> wasCompleted) {
- Log.i(TAG, String.format("onReject: callId=[%s]", mCallId));
- wasCompleted.accept(Boolean.TRUE);
- }
-
- @Override
- public void onDisconnect(@NonNull Consumer<Boolean> wasCompleted) {
+ public void onDisconnect(@NonNull DisconnectCause cause,
+ @NonNull Consumer<Boolean> wasCompleted) {
Log.i(TAG, String.format("onDisconnect: callId=[%s]", mCallId));
wasCompleted.accept(Boolean.TRUE);
}
@@ -83,6 +81,12 @@
}
@Override
+ public void onEvent(String event, Bundle extras) {
+ Log.i(TAG, String.format("onEvent: id=[%s], event=[%s], extras=[%s]",
+ mCallId, event, extras));
+ }
+
+ @Override
public void onCallEndpointChanged(@NonNull CallEndpoint newCallEndpoint) {
Log.i(TAG, String.format("onCallEndpointChanged: endpoint=[%s]", newCallEndpoint));
}
diff --git a/tests/src/com/android/server/telecom/tests/CallAnomalyWatchdogTest.java b/tests/src/com/android/server/telecom/tests/CallAnomalyWatchdogTest.java
index 93d1314..7b1957a 100644
--- a/tests/src/com/android/server/telecom/tests/CallAnomalyWatchdogTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAnomalyWatchdogTest.java
@@ -21,11 +21,13 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.ComponentName;
import android.net.Uri;
+import android.telecom.DisconnectCause;
import android.telecom.PhoneAccount;
import android.telecom.PhoneAccountHandle;
@@ -755,6 +757,90 @@
}
/**
+ * Emulate the case where a new incoming call is created but the connection fails for a known
+ * reason before being added to CallsManager. In this case, the watchdog should stop tracking
+ * the call and not trigger an anomaly report.
+ */
+ @Test
+ public void testIncomingCallCreatedButNotAddedNoAnomalyReport() {
+ //The call is created:
+ Call call = getCall();
+ call.setState(CallState.NEW, "foo");
+ call.setIsCreateConnectionComplete(false);
+ mCallAnomalyWatchdog.onCallCreated(call);
+
+ //The connection fails before being added to CallsManager for a known reason:
+ call.handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.CANCELED));
+
+ // Move the clock forward:
+ when(mMockClockProxy.elapsedRealtime()).
+ thenReturn(TEST_NON_VOIP_INTERMEDIATE_MILLIS + 1);
+ mTestScheduledExecutorService.advanceTime(TEST_NON_VOIP_INTERMEDIATE_MILLIS + 1);
+
+ //Ensure an anomaly report is not generated:
+ verify(mAnomalyReporterAdapter, never()).reportAnomaly(
+ CallAnomalyWatchdog.WATCHDOG_DISCONNECTED_STUCK_CALL_UUID,
+ CallAnomalyWatchdog.WATCHDOG_DISCONNECTED_STUCK_CALL_MSG);
+ }
+
+ /**
+ * Emulate the case where a new outgoing call is created but the connection fails for a known
+ * reason before being added to CallsManager. In this case, the watchdog should stop tracking
+ * the call and not trigger an anomaly report.
+ */
+ @Test
+ public void testOutgoingCallCreatedButNotAddedNoAnomalyReport() {
+ //The call is created:
+ Call call = getCall();
+ call.setCallDirection(Call.CALL_DIRECTION_OUTGOING);
+ call.setState(CallState.NEW, "foo");
+ call.setIsCreateConnectionComplete(false);
+ mCallAnomalyWatchdog.onCallCreated(call);
+
+ //The connection fails before being added to CallsManager for a known reason.
+ call.handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.CANCELED));
+
+ // Move the clock forward:
+ when(mMockClockProxy.elapsedRealtime()).
+ thenReturn(TEST_NON_VOIP_INTERMEDIATE_MILLIS + 1);
+ mTestScheduledExecutorService.advanceTime(TEST_NON_VOIP_INTERMEDIATE_MILLIS + 1);
+
+ //Ensure an anomaly report is not generated:
+ verify(mAnomalyReporterAdapter, never()).reportAnomaly(
+ CallAnomalyWatchdog.WATCHDOG_DISCONNECTED_STUCK_CALL_UUID,
+ CallAnomalyWatchdog.WATCHDOG_DISCONNECTED_STUCK_CALL_MSG);
+ }
+
+ /**
+ * Emulate the case where a new incoming call is created but the connection fails for a known
+ * reason before being added to CallsManager and CallsManager notifies the watchdog by invoking
+ * onCallCreatedButNeverAdded(). In this case, the watchdog should stop tracking
+ * the call and not trigger an anomaly report.
+ */
+ @Test
+ public void testCallCreatedButNotAddedPreventsAnomalyReport() {
+ //The call is created:
+ Call call = getCall();
+ call.setState(CallState.NEW, "foo");
+ call.setIsCreateConnectionComplete(false);
+ mCallAnomalyWatchdog.onCallCreated(call);
+
+ //Telecom cancels the connection before adding it to CallsManager:
+ mCallAnomalyWatchdog.onCallCreatedButNeverAdded(call);
+
+ // Move the clock forward:
+ when(mMockClockProxy.elapsedRealtime()).
+ thenReturn(TEST_NON_VOIP_INTERMEDIATE_MILLIS + 1);
+ mTestScheduledExecutorService.advanceTime(TEST_NON_VOIP_INTERMEDIATE_MILLIS + 1);
+
+ //Ensure an anomaly report is not generated:
+ verify(mAnomalyReporterAdapter, never()).reportAnomaly(
+ CallAnomalyWatchdog.WATCHDOG_DISCONNECTED_STUCK_CALL_UUID,
+ CallAnomalyWatchdog.WATCHDOG_DISCONNECTED_STUCK_CALL_MSG);
+ }
+
+
+ /**
* @return an instance of {@link Call} for testing purposes.
*/
private Call getCall() {
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
index f53c953..569c487 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteStateMachineTest.java
@@ -153,7 +153,8 @@
mockStatusBarNotifier,
mAudioServiceFactory,
CallAudioRouteStateMachine.EARPIECE_AUTO_DETECT,
- mThreadHandler.getLooper());
+ mThreadHandler.getLooper(),
+ Runnable::run /** do async stuff sync for test purposes */);
// Since we don't know if we're on a platform with an earpiece or not, all we can do
// is ensure the stateMachine construction didn't fail. But at least we exercised the
@@ -172,7 +173,8 @@
mockStatusBarNotifier,
mAudioServiceFactory,
CallAudioRouteStateMachine.EARPIECE_AUTO_DETECT,
- mThreadHandler.getLooper());
+ mThreadHandler.getLooper(),
+ Runnable::run /** do async stuff sync for test purposes */);
stateMachine.setCallAudioManager(mockCallAudioManager);
Set<Call> trackedCalls = new HashSet<>(Arrays.asList(fakeCall, fakeSelfManagedCall));
@@ -217,7 +219,8 @@
mockStatusBarNotifier,
mAudioServiceFactory,
CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
- mThreadHandler.getLooper());
+ mThreadHandler.getLooper(),
+ Runnable::run /** do async stuff sync for test purposes */);
stateMachine.setCallAudioManager(mockCallAudioManager);
CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER,
CallAudioState.ROUTE_EARPIECE | CallAudioState.ROUTE_SPEAKER);
@@ -260,7 +263,8 @@
mockStatusBarNotifier,
mAudioServiceFactory,
CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
- mThreadHandler.getLooper());
+ mThreadHandler.getLooper(),
+ Runnable::run /** do async stuff sync for test purposes */);
when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
when(mockBluetoothRouteManager.isBluetoothAvailable()).thenReturn(true);
@@ -305,7 +309,8 @@
mockStatusBarNotifier,
mAudioServiceFactory,
CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
- mThreadHandler.getLooper());
+ mThreadHandler.getLooper(),
+ Runnable::run /** do async stuff sync for test purposes */);
stateMachine.setCallAudioManager(mockCallAudioManager);
when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
@@ -349,7 +354,8 @@
mockStatusBarNotifier,
mAudioServiceFactory,
CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
- mThreadHandler.getLooper());
+ mThreadHandler.getLooper(),
+ Runnable::run /** do async stuff sync for test purposes */);
stateMachine.setCallAudioManager(mockCallAudioManager);
Collection<BluetoothDevice> availableDevices = Collections.singleton(bluetoothDevice1);
@@ -427,7 +433,8 @@
mockStatusBarNotifier,
mAudioServiceFactory,
CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
- mThreadHandler.getLooper());
+ mThreadHandler.getLooper(),
+ Runnable::run /** do async stuff sync for test purposes */);
stateMachine.setCallAudioManager(mockCallAudioManager);
when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
@@ -463,7 +470,8 @@
mockStatusBarNotifier,
mAudioServiceFactory,
CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
- mThreadHandler.getLooper());
+ mThreadHandler.getLooper(),
+ Runnable::run /** do async stuff sync for test purposes */);
stateMachine.setCallAudioManager(mockCallAudioManager);
setInBandRing(false);
when(mockBluetoothRouteManager.isBluetoothAudioConnectedOrPending()).thenReturn(false);
@@ -518,7 +526,8 @@
mockStatusBarNotifier,
mAudioServiceFactory,
CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
- mThreadHandler.getLooper());
+ mThreadHandler.getLooper(),
+ Runnable::run /** do async stuff sync for test purposes */);
stateMachine.setCallAudioManager(mockCallAudioManager);
List<BluetoothDevice> availableDevices =
Arrays.asList(bluetoothDevice1, bluetoothDevice2, bluetoothDevice3);
@@ -568,7 +577,8 @@
mockStatusBarNotifier,
mAudioServiceFactory,
CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
- mThreadHandler.getLooper());
+ mThreadHandler.getLooper(),
+ Runnable::run /** do async stuff sync for test purposes */);
stateMachine.setCallAudioManager(mockCallAudioManager);
when(mockAudioManager.isSpeakerphoneOn()).thenReturn(false);
CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_SPEAKER,
@@ -599,7 +609,8 @@
mockStatusBarNotifier,
mAudioServiceFactory,
CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
- mThreadHandler.getLooper());
+ mThreadHandler.getLooper(),
+ Runnable::run /** do async stuff sync for test purposes */);
stateMachine.setCallAudioManager(mockCallAudioManager);
when(mockAudioManager.isSpeakerphoneOn()).thenReturn(false);
@@ -633,7 +644,8 @@
mockStatusBarNotifier,
mAudioServiceFactory,
CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
- mThreadHandler.getLooper());
+ mThreadHandler.getLooper(),
+ Runnable::run /** do async stuff sync for test purposes */);
stateMachine.setCallAudioManager(mockCallAudioManager);
List<BluetoothDevice> availableDevices =
Arrays.asList(bluetoothDevice1, bluetoothDevice2);
@@ -748,7 +760,8 @@
mockStatusBarNotifier,
mAudioServiceFactory,
CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
- mThreadHandler.getLooper());
+ mThreadHandler.getLooper(),
+ Runnable::run /** do async stuff sync for test purposes */);
stateMachine.initialize();
assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
}
@@ -764,7 +777,8 @@
mockStatusBarNotifier,
mAudioServiceFactory,
CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
- mThreadHandler.getLooper());
+ mThreadHandler.getLooper(),
+ Runnable::run /** do async stuff sync for test purposes */);
stateMachine.setCallAudioManager(mockCallAudioManager);
CallAudioState initState = new CallAudioState(false, CallAudioState.ROUTE_EARPIECE,
@@ -801,7 +815,8 @@
mockStatusBarNotifier,
mAudioServiceFactory,
earpieceControl,
- mThreadHandler.getLooper());
+ mThreadHandler.getLooper(),
+ Runnable::run /** do async stuff sync for test purposes */);
stateMachine.initialize();
assertEquals(expectedState, stateMachine.getCurrentCallAudioState());
}
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java b/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
index 5caa432..cf684de 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioRouteTransitionTests.java
@@ -269,7 +269,8 @@
mockStatusBarNotifier,
mAudioServiceFactory,
mParams.earpieceControl,
- mHandlerThread.getLooper());
+ mHandlerThread.getLooper(),
+ Runnable::run /** do async stuff sync for test purposes */);
stateMachine.setCallAudioManager(mockCallAudioManager);
setupMocksForParams(stateMachine, mParams);
@@ -365,7 +366,8 @@
mockStatusBarNotifier,
mAudioServiceFactory,
mParams.earpieceControl,
- mHandlerThread.getLooper());
+ mHandlerThread.getLooper(),
+ Runnable::run /** do async stuff sync for test purposes */);
stateMachine.setCallAudioManager(mockCallAudioManager);
// Set up bluetooth and speakerphone state
diff --git a/tests/src/com/android/server/telecom/tests/CallTest.java b/tests/src/com/android/server/telecom/tests/CallTest.java
index fc78810..6b817d8 100644
--- a/tests/src/com/android/server/telecom/tests/CallTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallTest.java
@@ -26,6 +26,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -426,11 +427,6 @@
call.answer(0);
verify(mMockTransactionalService, times(1)).onAnswer(call, 0);
- // assert CallEventCallback#onReject is called
- call.setState(CallState.RINGING, "test");
- call.reject(0);
- verify(mMockTransactionalService, times(1)).onReject(call, 0);
-
// assert CallEventCallback#onDisconnect is called
call.setState(CallState.ACTIVE, "test");
call.disconnect();
diff --git a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
index 75a9a96..7635ee5 100644
--- a/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallsManagerTest.java
@@ -48,6 +48,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.media.AudioManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
@@ -105,6 +106,7 @@
import com.android.server.telecom.PhoneNumberUtilsAdapter;
import com.android.server.telecom.ProximitySensorManager;
import com.android.server.telecom.ProximitySensorManagerFactory;
+import com.android.server.telecom.Ringer;
import com.android.server.telecom.RoleManagerAdapter;
import com.android.server.telecom.SystemStateHelper;
import com.android.server.telecom.TelecomSystem;
@@ -136,6 +138,7 @@
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
@@ -233,6 +236,7 @@
@Mock private Toast mToast;
@Mock private CallAnomalyWatchdog mCallAnomalyWatchdog;
@Mock private AnomalyReporterAdapter mAnomalyReporterAdapter;
+ @Mock private Ringer.AccessibilityManagerAdapter mAccessibilityManagerAdapter;
private CallsManager mCallsManager;
@@ -252,7 +256,7 @@
when(mCallEndpointControllerFactory.create(any(), any(), any())).thenReturn(
mCallEndpointController);
when(mCallAudioRouteStateMachineFactory.create(any(), any(), any(), any(), any(), any(),
- anyInt())).thenReturn(mCallAudioRouteStateMachine);
+ anyInt(), any())).thenReturn(mCallAudioRouteStateMachine);
when(mCallAudioModeStateMachineFactory.create(any(), any()))
.thenReturn(mCallAudioModeStateMachine);
when(mClockProxy.currentTimeMillis()).thenReturn(System.currentTimeMillis());
@@ -294,7 +298,10 @@
mRoleManagerAdapter,
mToastFactory,
mCallEndpointControllerFactory,
- mCallAnomalyWatchdog);
+ mCallAnomalyWatchdog,
+ mAccessibilityManagerAdapter,
+ // Just do async tasks synchronously to support testing.
+ command -> command.run());
when(mPhoneAccountRegistrar.getPhoneAccount(
eq(SELF_MANAGED_HANDLE), any())).thenReturn(SELF_MANAGED_ACCOUNT);
@@ -1993,6 +2000,79 @@
SELF_MANAGED_HANDLE.getUserHandle()));
}
+ /**
+ * Emulate the case where a new incoming call is created but the connection fails for a known
+ * reason before being added to CallsManager. In this case, the listeners should be notified
+ * properly.
+ */
+ @Test
+ public void testIncomingCallCreatedButNotAddedNotifyListener() {
+ //The call is created and a listener is added:
+ Call incomingCall = createCall(SIM_2_HANDLE, null, CallState.NEW);
+ CallsManager.CallsManagerListener listener = mock(CallsManager.CallsManagerListener.class);
+ mCallsManager.addListener(listener);
+
+ //The connection fails before being added to CallsManager for a known reason:
+ incomingCall.handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.CANCELED));
+
+ //Ensure the listener is notified properly:
+ verify(listener).onCallCreatedButNeverAdded(incomingCall);
+ }
+
+ /**
+ * Emulate the case where a new incoming call is created but the connection fails for a known
+ * reason after being added to CallsManager. Since the call was added to CallsManager, the
+ * listeners should not be notified via onCallCreatedButNeverAdded().
+ */
+ @Test
+ public void testIncomingCallCreatedAndAddedDoNotNotifyListener() {
+ //The call is created and a listener is added:
+ Call incomingCall = createCall(SIM_2_HANDLE, null, CallState.NEW);
+ CallsManager.CallsManagerListener listener = mock(CallsManager.CallsManagerListener.class);
+ mCallsManager.addListener(listener);
+
+ //The call is added to CallsManager:
+ mCallsManager.addCall(incomingCall);
+
+ //The connection fails after being added to CallsManager for a known reason:
+ incomingCall.handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.CANCELED));
+
+ //Since the call was added to CallsManager, onCallCreatedButNeverAdded shouldn't be invoked:
+ verify(listener, never()).onCallCreatedButNeverAdded(incomingCall);
+ }
+
+ /**
+ * Emulate the case where a new outgoing call is created but is aborted before being added to
+ * CallsManager since there are no available phone accounts. In this case, the listeners
+ * should be notified properly.
+ */
+ @Test
+ public void testAbortOutgoingCallNoPhoneAccountsNotifyListeners() throws Exception {
+ // Setup a new outgoing call and add a listener
+ Call newCall = addSpyCall(CallState.NEW);
+ CallsManager.CallsManagerListener listener = mock(CallsManager.CallsManagerListener.class);
+ mCallsManager.addListener(listener);
+
+ // Ensure contact info lookup succeeds but do not set the phone account info
+ doAnswer(invocation -> {
+ Uri handle = invocation.getArgument(0);
+ CallerInfo info = new CallerInfo();
+ CompletableFuture<Pair<Uri, CallerInfo>> callerInfoFuture = new CompletableFuture<>();
+ callerInfoFuture.complete(new Pair<>(handle, info));
+ return callerInfoFuture;
+ }).when(mCallerInfoLookupHelper).startLookup(any(Uri.class));
+
+ // Start the outgoing call
+ CompletableFuture<Call> callFuture = mCallsManager.startOutgoingCall(
+ newCall.getHandle(), newCall.getTargetPhoneAccount(), new Bundle(),
+ UserHandle.CURRENT, new Intent(), "com.test.stuff");
+ Call result = callFuture.get(TEST_TIMEOUT, TimeUnit.MILLISECONDS);
+
+ //Ensure the listener is notified properly:
+ verify(listener).onCallCreatedButNeverAdded(any());
+ assertNull(result);
+ }
+
@Test
public void testIsInSelfManagedCallOnlySelfManaged() {
Call selfManagedCall = createCall(SELF_MANAGED_HANDLE, CallState.ACTIVE);
@@ -2415,6 +2495,50 @@
@MediumTest
@Test
+ public void testSetCallDialingAndDontIncreaseVolume() {
+ // Start with a non zero volume.
+ mComponentContextFixture.getAudioManager().setStreamVolume(AudioManager.STREAM_VOICE_CALL,
+ 4, 0 /* flags */);
+
+ Call call = mock(Call.class);
+ mCallsManager.markCallAsDialing(call);
+
+ // We set the volume to non-zero above, so expect 1
+ verify(mComponentContextFixture.getAudioManager(), times(1)).setStreamVolume(
+ eq(AudioManager.STREAM_VOICE_CALL), anyInt(), anyInt());
+ }
+ @MediumTest
+ @Test
+ public void testSetCallDialingAndIncreaseVolume() {
+ // Start with a zero volume stream.
+ mComponentContextFixture.getAudioManager().setStreamVolume(AudioManager.STREAM_VOICE_CALL,
+ 0, 0 /* flags */);
+
+ Call call = mock(Call.class);
+ mCallsManager.markCallAsDialing(call);
+
+ // We set the volume to zero above, so expect 2
+ verify(mComponentContextFixture.getAudioManager(), times(2)).setStreamVolume(
+ eq(AudioManager.STREAM_VOICE_CALL), anyInt(), anyInt());
+ }
+
+ @MediumTest
+ @Test
+ public void testSetCallActiveAndDontIncreaseVolume() {
+ // Start with a non-zero volume.
+ mComponentContextFixture.getAudioManager().setStreamVolume(AudioManager.STREAM_VOICE_CALL,
+ 4, 0 /* flags */);
+
+ Call call = mock(Call.class);
+ mCallsManager.markCallAsActive(call);
+
+ // We set the volume to non-zero above, so expect 1 only.
+ verify(mComponentContextFixture.getAudioManager(), times(1)).setStreamVolume(
+ eq(AudioManager.STREAM_VOICE_CALL), anyInt(), anyInt());
+ }
+
+ @MediumTest
+ @Test
public void testHandoverToIsAccepted() {
Call sourceCall = addSpyCall(CONNECTION_MGR_1_HANDLE, CallState.NEW);
Call call = addSpyCall(CONNECTION_MGR_1_HANDLE, CallState.NEW);
diff --git a/tests/src/com/android/server/telecom/tests/CarModeTrackerTest.java b/tests/src/com/android/server/telecom/tests/CarModeTrackerTest.java
index 4ad46ae..6056747 100644
--- a/tests/src/com/android/server/telecom/tests/CarModeTrackerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CarModeTrackerTest.java
@@ -236,6 +236,28 @@
}
/**
+ * Verifies that setting automotive projection overrides entering car mode with the highest
+ * priority of 0. Also ensures exiting car mode doesn't interfere with the automotive
+ * projection being set.
+ */
+ @Test
+ public void testInterleaveCarModeAndProjectionMode() {
+ mCarModeTracker.handleEnterCarMode(0, CAR_MODE_APP1_PACKAGE_NAME);
+ assertEquals(CAR_MODE_APP1_PACKAGE_NAME, mCarModeTracker.getCurrentCarModePackage());
+ assertTrue(mCarModeTracker.isInCarMode());
+
+ mCarModeTracker.handleSetAutomotiveProjection(CAR_MODE_APP2_PACKAGE_NAME);
+ assertEquals(CAR_MODE_APP2_PACKAGE_NAME, mCarModeTracker.getCurrentCarModePackage());
+ assertTrue(mCarModeTracker.isInCarMode());
+
+ mCarModeTracker.handleExitCarMode(0, CAR_MODE_APP1_PACKAGE_NAME);
+ assertEquals(CAR_MODE_APP2_PACKAGE_NAME, mCarModeTracker.getCurrentCarModePackage());
+ assertTrue(mCarModeTracker.isInCarMode());
+
+ mCarModeTracker.handleReleaseAutomotiveProjection();
+ }
+
+ /**
* Verifies that if we set automotive projection more than once with the same package, nothing
* changes.
*/
diff --git a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
index 0743805..e3a1471 100644
--- a/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
+++ b/tests/src/com/android/server/telecom/tests/ComponentContextFixture.java
@@ -87,7 +87,7 @@
import java.util.Map;
import java.util.concurrent.Executor;
-import static android.companion.virtual.VirtualDeviceManager.DEVICE_ID_DEFAULT;
+import static android.content.Context.DEVICE_ID_DEFAULT;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.matches;
@@ -783,6 +783,10 @@
return mTelephonyManager;
}
+ public AudioManager getAudioManager() {
+ return mAudioManager;
+ }
+
public CarrierConfigManager getCarrierConfigManager() {
return mCarrierConfigManager;
}
diff --git a/tests/src/com/android/server/telecom/tests/HeadsetMediaButtonTest.java b/tests/src/com/android/server/telecom/tests/HeadsetMediaButtonTest.java
index 0bfa987..ce23724 100644
--- a/tests/src/com/android/server/telecom/tests/HeadsetMediaButtonTest.java
+++ b/tests/src/com/android/server/telecom/tests/HeadsetMediaButtonTest.java
@@ -18,6 +18,7 @@
import android.content.Intent;
import android.media.session.MediaSession;
+import android.telecom.CallEndpoint;
import android.test.suitebuilder.annotation.MediumTest;
import android.test.suitebuilder.annotation.SmallTest;
import android.view.KeyEvent;
@@ -80,7 +81,7 @@
}
/**
- * Nominal case; just add a call and remove it.
+ * Nominal case; just add a call and remove it; this happens when the audio state is earpiece.
*/
@SmallTest
@Test
@@ -90,14 +91,95 @@
when(mMockCallsManager.hasAnyCalls()).thenReturn(true);
mHeadsetMediaButton.onCallAdded(regularCall);
waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+ verify(mMediaSessionAdapter, never()).setActive(eq(true));
+
+ // Report that the endpoint is earpiece and other routes that don't matter
+ mHeadsetMediaButton.onCallEndpointChanged(
+ new CallEndpoint("Earpiece", CallEndpoint.TYPE_EARPIECE));
+ mHeadsetMediaButton.onCallEndpointChanged(
+ new CallEndpoint("Speaker", CallEndpoint.TYPE_SPEAKER));
+ mHeadsetMediaButton.onCallEndpointChanged(
+ new CallEndpoint("BT", CallEndpoint.TYPE_BLUETOOTH));
+ waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+ verify(mMediaSessionAdapter, never()).setActive(eq(true));
+
+ // ... and thus we see how the original code isn't amenable to tests.
+ when(mMediaSessionAdapter.isActive()).thenReturn(false);
+
+ // Still should not have done anything; we never hit wired headset
+ mHeadsetMediaButton.onCallRemoved(regularCall);
+ waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+ verify(mMediaSessionAdapter, never()).setActive(eq(false));
+ }
+
+ /**
+ * Call is added and then routed to headset after call start
+ */
+ @SmallTest
+ @Test
+ public void testAddCallThenRouteToHeadset() {
+ Call regularCall = getRegularCall();
+
+ mHeadsetMediaButton.onCallAdded(regularCall);
+ waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+ verify(mMediaSessionAdapter, never()).setActive(eq(true));
+
+ mHeadsetMediaButton.onCallEndpointChanged(
+ new CallEndpoint("Wired Headset", CallEndpoint.TYPE_WIRED_HEADSET));
+ waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
verify(mMediaSessionAdapter).setActive(eq(true));
+
// ... and thus we see how the original code isn't amenable to tests.
when(mMediaSessionAdapter.isActive()).thenReturn(true);
- when(mMockCallsManager.hasAnyCalls()).thenReturn(false);
+ // Remove the one call; we should release the session.
mHeadsetMediaButton.onCallRemoved(regularCall);
waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
verify(mMediaSessionAdapter).setActive(eq(false));
+ when(mMediaSessionAdapter.isActive()).thenReturn(false);
+
+ // Add a new call; make sure we go active once more.
+ mHeadsetMediaButton.onCallAdded(regularCall);
+ mHeadsetMediaButton.onCallEndpointChanged(
+ new CallEndpoint("Wired Headset", CallEndpoint.TYPE_WIRED_HEADSET));
+ waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+ verify(mMediaSessionAdapter, times(2)).setActive(eq(true));
+ }
+
+ /**
+ * Call is added and then routed to headset after call start
+ */
+ @SmallTest
+ @Test
+ public void testAddCallThenRouteToHeadsetAndBack() {
+ Call regularCall = getRegularCall();
+
+ when(mMockCallsManager.hasAnyCalls()).thenReturn(true);
+ mHeadsetMediaButton.onCallAdded(regularCall);
+ waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+ verify(mMediaSessionAdapter, never()).setActive(eq(true));
+
+ mHeadsetMediaButton.onCallEndpointChanged(
+ new CallEndpoint("Wired Headset", CallEndpoint.TYPE_WIRED_HEADSET));
+ waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+ verify(mMediaSessionAdapter).setActive(eq(true));
+
+ // ... and thus we see how the original code isn't amenable to tests.
+ when(mMediaSessionAdapter.isActive()).thenReturn(true);
+
+ mHeadsetMediaButton.onCallEndpointChanged(
+ new CallEndpoint("Earpiece", CallEndpoint.TYPE_EARPIECE));
+ waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+ verify(mMediaSessionAdapter).setActive(eq(false));
+ when(mMediaSessionAdapter.isActive()).thenReturn(false);
+
+ // Remove the one call; we should not release again.
+ mHeadsetMediaButton.onCallRemoved(regularCall);
+ waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
+ // Remember, mockito counts total invocations; we should have went active once and then
+ // inactive again when we hit earpiece.
+ verify(mMediaSessionAdapter, times(1)).setActive(eq(true));
+ verify(mMediaSessionAdapter, times(1)).setActive(eq(false));
}
/**
@@ -111,6 +193,8 @@
// Start with a regular old call.
when(mMockCallsManager.hasAnyCalls()).thenReturn(true);
mHeadsetMediaButton.onCallAdded(regularCall);
+ mHeadsetMediaButton.onCallEndpointChanged(
+ new CallEndpoint("Wired Headset", CallEndpoint.TYPE_WIRED_HEADSET));
waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
verify(mMediaSessionAdapter).setActive(eq(true));
when(mMediaSessionAdapter.isActive()).thenReturn(true);
@@ -122,6 +206,7 @@
// Expect to set session inactive.
waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
verify(mMediaSessionAdapter).setActive(eq(false));
+ when(mMediaSessionAdapter.isActive()).thenReturn(false);
// For good measure lets make it non-external again.
when(regularCall.isExternalCall()).thenReturn(false);
@@ -129,7 +214,7 @@
mHeadsetMediaButton.onExternalCallChanged(regularCall, false);
// Expect to set session active.
waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
- verify(mMediaSessionAdapter).setActive(eq(true));
+ verify(mMediaSessionAdapter, times(2)).setActive(eq(true));
}
@MediumTest
@@ -139,6 +224,8 @@
when(externalCall.isExternalCall()).thenReturn(true);
mHeadsetMediaButton.onCallAdded(externalCall);
+ mHeadsetMediaButton.onCallEndpointChanged(
+ new CallEndpoint("Wired Headset", CallEndpoint.TYPE_WIRED_HEADSET));
waitForHandlerAction(mHeadsetMediaButton.getHandler(), TEST_TIMEOUT_MILLIS);
verify(mMediaSessionAdapter, never()).setActive(eq(true));
diff --git a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
index f50753d..1fd5027 100644
--- a/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
+++ b/tests/src/com/android/server/telecom/tests/InCallControllerTests.java
@@ -74,6 +74,7 @@
import android.os.Looper;
import android.os.Process;
import android.os.UserHandle;
+import android.os.UserManager;
import android.permission.PermissionCheckerManager;
import android.telecom.CallAudioState;
import android.telecom.InCallService;
@@ -150,6 +151,7 @@
@Mock PermissionInfo mMockPermissionInfo;
@Mock InCallController.InCallServiceInfo mInCallServiceInfo;
@Mock private AnomalyReporterAdapter mAnomalyReporterAdapter;
+ @Mock UserManager mMockUserManager;
@Rule
public TestRule compatChangeRule = new PlatformCompatChangeRule();
@@ -180,6 +182,7 @@
private static final PhoneAccountHandle PA_HANDLE =
new PhoneAccountHandle(new ComponentName("pa_pkg", "pa_cls"),
"pa_id_0", UserHandle.of(CURRENT_USER_ID));
+ private static final UserHandle DUMMY_USER_HANDLE = UserHandle.of(10);
private UserHandle mUserHandle = UserHandle.of(CURRENT_USER_ID);
private InCallController mInCallController;
@@ -462,6 +465,9 @@
when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
when(mMockCallsManager.isInEmergencyCall()).thenReturn(false);
when(mMockCall.isEmergencyCall()).thenReturn(true);
+ when(mMockContext.getSystemService(eq(UserManager.class)))
+ .thenReturn(mMockUserManager);
+ when(mMockUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(false);
when(mMockCall.isIncoming()).thenReturn(false);
when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
when(mMockCall.getIntentExtras()).thenReturn(callExtras);
@@ -539,6 +545,9 @@
when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
when(mMockCallsManager.isInEmergencyCall()).thenReturn(true);
when(mMockCall.isEmergencyCall()).thenReturn(true);
+ when(mMockContext.getSystemService(eq(UserManager.class)))
+ .thenReturn(mMockUserManager);
+ when(mMockUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(false);
when(mMockCall.isIncoming()).thenReturn(false);
when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
when(mMockCall.getIntentExtras()).thenReturn(callExtras);
@@ -598,6 +607,60 @@
eq(Manifest.permission.ACCESS_FINE_LOCATION), eq(mUserHandle));
}
+ @MediumTest
+ @Test
+ public void
+ testBindToService_UserAssociatedWithCallIsInQuietMode_EmergCallInCallUi_BindsToPrimaryUser()
+ throws Exception {
+ when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockCall.isEmergencyCall()).thenReturn(true);
+ when(mMockCall.getUserHandleFromTargetPhoneAccount()).thenReturn(DUMMY_USER_HANDLE);
+ when(mMockContext.getSystemService(eq(UserManager.class)))
+ .thenReturn(mMockUserManager);
+ when(mMockUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(true);
+ setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
+ setupMockPackageManagerLocationPermission(SYS_PKG, false /* granted */);
+
+ mInCallController.bindToServices(mMockCall);
+
+ ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(mMockContext, times(1)).bindServiceAsUser(
+ bindIntentCaptor.capture(),
+ any(ServiceConnection.class),
+ eq(serviceBindingFlags),
+ eq(mUserHandle));
+ Intent bindIntent = bindIntentCaptor.getValue();
+ assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
+ }
+
+ @MediumTest
+ @Test
+ public void
+ testBindToService_UserAssociatedWithCallNotInQuietMode_EmergCallInCallUi_BindsToAssociatedUser()
+ throws Exception {
+ when(mMockCallsManager.getCurrentUserHandle()).thenReturn(mUserHandle);
+ when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
+ when(mMockCall.isEmergencyCall()).thenReturn(true);
+ when(mMockCall.getUserHandleFromTargetPhoneAccount()).thenReturn(DUMMY_USER_HANDLE);
+ when(mMockContext.getSystemService(eq(UserManager.class)))
+ .thenReturn(mMockUserManager);
+ when(mMockUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(false);
+ setupMockPackageManager(true /* default */, true /* system */, false /* external calls */);
+ setupMockPackageManagerLocationPermission(SYS_PKG, false /* granted */);
+
+ mInCallController.bindToServices(mMockCall);
+
+ ArgumentCaptor<Intent> bindIntentCaptor = ArgumentCaptor.forClass(Intent.class);
+ verify(mMockContext, times(1)).bindServiceAsUser(
+ bindIntentCaptor.capture(),
+ any(ServiceConnection.class),
+ eq(serviceBindingFlags),
+ eq(DUMMY_USER_HANDLE));
+ Intent bindIntent = bindIntentCaptor.getValue();
+ assertEquals(InCallService.SERVICE_INTERFACE, bindIntent.getAction());
+ }
+
/**
* This test verifies the behavior of Telecom when the system dialer crashes on binding and must
* be restarted. Specifically, it ensures when the system dialer crashes we revoke the runtime
@@ -614,6 +677,9 @@
when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
when(mMockCallsManager.isInEmergencyCall()).thenReturn(true);
when(mMockCall.isEmergencyCall()).thenReturn(true);
+ when(mMockContext.getSystemService(eq(UserManager.class)))
+ .thenReturn(mMockUserManager);
+ when(mMockUserManager.isQuietModeEnabled(any(UserHandle.class))).thenReturn(false);
when(mMockCall.isIncoming()).thenReturn(false);
when(mMockCall.getTargetPhoneAccount()).thenReturn(PA_HANDLE);
when(mMockCall.getIntentExtras()).thenReturn(callExtras);
diff --git a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
index c6cbbbb..cef032f 100644
--- a/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
+++ b/tests/src/com/android/server/telecom/tests/PhoneAccountRegistrarTest.java
@@ -1667,6 +1667,23 @@
}
}
+ /**
+ * Ensure an IllegalArgumentException is thrown when providing a SubscriptionAddress that
+ * exceeds the PhoneAccountRegistrar limit.
+ */
+ @Test
+ public void testLimitOnSubscriptionAddress() throws Exception {
+ String text = "a".repeat(100);
+ PhoneAccount.Builder builder = new PhoneAccount.Builder(makeQuickAccountHandle(TEST_ID),
+ TEST_LABEL).setSubscriptionAddress(Uri.fromParts(text, text, text));
+ try {
+ mRegistrar.enforceCharacterLimit(builder.build());
+ fail("failed to throw IllegalArgumentException");
+ } catch (IllegalArgumentException e) {
+ // pass test
+ }
+ }
+
private static PhoneAccount.Builder makeBuilderWithBindCapabilities(PhoneAccountHandle handle) {
return new PhoneAccount.Builder(handle, TEST_LABEL)
.setCapabilities(PhoneAccount.CAPABILITY_SUPPORTS_TRANSACTIONAL_OPERATIONS);
diff --git a/tests/src/com/android/server/telecom/tests/RingerTest.java b/tests/src/com/android/server/telecom/tests/RingerTest.java
index 35eda43..e4050b6 100644
--- a/tests/src/com/android/server/telecom/tests/RingerTest.java
+++ b/tests/src/com/android/server/telecom/tests/RingerTest.java
@@ -20,6 +20,7 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -44,6 +45,7 @@
import android.os.Bundle;
import android.os.Parcel;
import android.os.UserHandle;
+import android.os.UserManager;
import android.os.VibrationAttributes;
import android.os.VibrationEffect;
import android.os.Vibrator;
@@ -150,7 +152,7 @@
@Mock Vibrator mockVibrator;
@Mock InCallController mockInCallController;
@Mock NotificationManager mockNotificationManager;
- @Mock AccessibilityManager mockAccessibilityManager;
+ @Mock Ringer.AccessibilityManagerAdapter mockAccessibilityManagerAdapter;
@Spy Ringer.VibrationEffectProxy spyVibrationEffectProxy;
@@ -180,13 +182,12 @@
when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
when(mockSystemSettingsUtil.isHapticPlaybackSupported(any(Context.class))).thenReturn(true);
mockNotificationManager =mContext.getSystemService(NotificationManager.class);
- mockAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
when(mockTonePlayer.startTone()).thenReturn(true);
when(mockNotificationManager.matchesCallFilter(any(Bundle.class))).thenReturn(true);
when(mockRingtoneFactory.hasHapticChannels(any(Ringtone.class))).thenReturn(false);
mRingerUnderTest = new Ringer(mockPlayerFactory, mContext, mockSystemSettingsUtil,
mockRingtonePlayer, mockRingtoneFactory, mockVibrator, spyVibrationEffectProxy,
- mockInCallController, mockNotificationManager, mockAccessibilityManager);
+ mockInCallController, mockNotificationManager, mockAccessibilityManagerAdapter);
when(mockCall1.getState()).thenReturn(CallState.RINGING);
when(mockCall2.getState()).thenReturn(CallState.RINGING);
when(mockCall1.getUserHandleFromTargetPhoneAccount()).thenReturn(PA_HANDLE.getUserHandle());
@@ -558,7 +559,7 @@
assertFalse(mRingerUnderTest.shouldRingForContact(mockCall2));
assertFalse(mRingerUnderTest.startRinging(mockCall2, false));
- verify(mockAccessibilityManager, never())
+ verify(mockAccessibilityManagerAdapter, never())
.startFlashNotificationSequence(any(Context.class), anyInt());
}
@@ -573,7 +574,7 @@
assertTrue(mRingerUnderTest.shouldRingForContact(mockCall2));
assertTrue(mRingerUnderTest.startRinging(mockCall2, false));
- verify(mockAccessibilityManager, atLeastOnce())
+ verify(mockAccessibilityManagerAdapter, atLeastOnce())
.startFlashNotificationSequence(any(Context.class), anyInt());
}
@@ -589,11 +590,25 @@
assertTrue(mRingerUnderTest.shouldRingForContact(mockCall2));
assertTrue(mRingerUnderTest.startRinging(mockCall2, false));
mRingerUnderTest.stopRinging();
- verify(mockAccessibilityManager, atLeastOnce())
+ verify(mockAccessibilityManagerAdapter, atLeastOnce())
.stopFlashNotificationSequence(any(Context.class));
}
+ @SmallTest
+ @Test
+ public void testNoRingingForQuietProfile() {
+ UserManager um = mContext.getSystemService(UserManager.class);
+ when(um.isManagedProfile(PA_HANDLE.getUserHandle().getIdentifier())).thenReturn(true);
+ when(um.isQuietModeEnabled(PA_HANDLE.getUserHandle())).thenReturn(true);
+ // We don't want to acquire audio focus when self-managed
+ assertFalse(mRingerUnderTest.startRinging(mockCall2, true));
+ verify(mockTonePlayer, never()).stopTone();
+ verify(mockRingtonePlayer, never()).play(any(Ringtone.class));
+ verify(mockVibrator, never())
+ .vibrate(any(VibrationEffect.class), any(VibrationAttributes.class));
+ }
+
private void ensureRingerIsAudible() {
when(mockAudioManager.getRingerMode()).thenReturn(AudioManager.RINGER_MODE_NORMAL);
when(mockAudioManager.getStreamVolume(AudioManager.STREAM_RING)).thenReturn(100);
diff --git a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
index 569d6b4..91728c2 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomServiceImplTest.java
@@ -1125,7 +1125,7 @@
boolean shouldNonEmergencyBeAllowed) {
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
verify(mUserCallIntentProcessor).processIntent(intentCaptor.capture(), anyString(),
- eq(shouldNonEmergencyBeAllowed), eq(true));
+ eq(false), eq(shouldNonEmergencyBeAllowed), eq(true));
Intent capturedIntent = intentCaptor.getValue();
assertEquals(Intent.ACTION_CALL, capturedIntent.getAction());
assertEquals(expectedHandle, capturedIntent.getData());
@@ -1148,7 +1148,7 @@
}
verify(mUserCallIntentProcessor, never())
- .processIntent(any(Intent.class), anyString(), anyBoolean(), eq(true));
+ .processIntent(any(Intent.class), anyString(), eq(false), anyBoolean(), eq(true));
}
@SmallTest
diff --git a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
index 2ab3160..20a7fc8 100644
--- a/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
+++ b/tests/src/com/android/server/telecom/tests/TelecomSystemTest.java
@@ -89,6 +89,7 @@
import com.android.server.telecom.PhoneNumberUtilsAdapterImpl;
import com.android.server.telecom.ProximitySensorManager;
import com.android.server.telecom.ProximitySensorManagerFactory;
+import com.android.server.telecom.Ringer;
import com.android.server.telecom.RoleManagerAdapter;
import com.android.server.telecom.StatusBarNotifier;
import com.android.server.telecom.SystemStateHelper;
@@ -112,6 +113,7 @@
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
/**
@@ -209,6 +211,8 @@
@Mock ToneGenerator mToneGenerator;
@Mock DeviceIdleControllerAdapter mDeviceIdleControllerAdapter;
+ @Mock Ringer.AccessibilityManagerAdapter mAccessibilityManagerAdapter;
+
final ComponentName mInCallServiceComponentNameX =
new ComponentName(
"incall-service-package-X",
@@ -509,7 +513,8 @@
WiredHeadsetManager wiredHeadsetManager,
StatusBarNotifier statusBarNotifier,
CallAudioManager.AudioServiceFactory audioServiceFactory,
- int earpieceControl) {
+ int earpieceControl,
+ Executor asyncTaskExecutor) {
return new CallAudioRouteStateMachine(context,
callsManager,
bluetoothManager,
@@ -518,7 +523,8 @@
audioServiceFactory,
// Force enable an earpiece for the end-to-end tests
CallAudioRouteStateMachine.EARPIECE_FORCE_ENABLED,
- mHandlerThread.getLooper());
+ mHandlerThread.getLooper(),
+ Runnable::run /* async tasks as now sync for testing! */);
}
},
new CallAudioModeStateMachine.Factory() {
@@ -537,7 +543,8 @@
ContactsAsyncHelper.ContentResolverAdapter adapter) {
return new ContactsAsyncHelper(adapter, mHandlerThread.getLooper());
}
- }, mDeviceIdleControllerAdapter);
+ }, mDeviceIdleControllerAdapter, mAccessibilityManagerAdapter,
+ Runnable::run);
mComponentContextFixture.setTelecomManager(new TelecomManager(
mComponentContextFixture.getTestDouble(),
@@ -754,7 +761,7 @@
final UserHandle userHandle = initiatingUser;
Context localAppContext = mComponentContextFixture.getTestDouble().getApplicationContext();
new UserCallIntentProcessor(localAppContext, userHandle).processIntent(
- actionCallIntent, null, true /* hasCallAppOp*/, false /* isLocal */);
+ actionCallIntent, null, false, true /* hasCallAppOp*/, false /* isLocal */);
// Wait for handler to start CallerInfo lookup.
waitForHandlerAction(new Handler(Looper.getMainLooper()), TEST_TIMEOUT);
// Send the CallerInfo lookup reply.
diff --git a/tests/src/com/android/server/telecom/tests/TransactionTests.java b/tests/src/com/android/server/telecom/tests/TransactionTests.java
index a82fa78..1e6734b 100644
--- a/tests/src/com/android/server/telecom/tests/TransactionTests.java
+++ b/tests/src/com/android/server/telecom/tests/TransactionTests.java
@@ -47,6 +47,7 @@
import com.android.server.telecom.PhoneNumberUtilsAdapter;
import com.android.server.telecom.TelecomSystem;
import com.android.server.telecom.ui.ToastFactory;
+import com.android.server.telecom.voip.AnswerCallTransaction;
import com.android.server.telecom.voip.EndCallTransaction;
import com.android.server.telecom.voip.HoldCallTransaction;
import com.android.server.telecom.voip.IncomingCallTransaction;
@@ -152,7 +153,23 @@
// THEN
verify(mCallsManager, times(1))
- .transactionRequestNewFocusCall(eq(mMockCall1), isA(OutcomeReceiver.class));
+ .transactionRequestNewFocusCall(eq(mMockCall1), eq(CallState.ACTIVE),
+ isA(OutcomeReceiver.class));
+ }
+
+ @Test
+ public void testAnswerCallTransaction() throws Exception {
+ // GIVEN
+ AnswerCallTransaction transaction =
+ new AnswerCallTransaction(mCallsManager, mMockCall1, 0);
+
+ // WHEN
+ transaction.processTransaction(null);
+
+ // THEN
+ verify(mCallsManager, times(1))
+ .transactionRequestNewFocusCall(eq(mMockCall1), eq(CallState.ANSWERED),
+ isA(OutcomeReceiver.class));
}
@Test