Merge "Fix concurrency issues with ringtone playback." into qt-r1-dev
diff --git a/src/com/android/server/telecom/AsyncRingtonePlayer.java b/src/com/android/server/telecom/AsyncRingtonePlayer.java
index 3745bc5..8f11882 100644
--- a/src/com/android/server/telecom/AsyncRingtonePlayer.java
+++ b/src/com/android/server/telecom/AsyncRingtonePlayer.java
@@ -26,6 +26,7 @@
import android.os.HandlerThread;
import android.os.Message;
import android.telecom.Log;
+import android.telecom.Logging.Session;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.os.SomeArgs;
@@ -107,6 +108,7 @@
args.arg2 = incomingCall;
args.arg3 = volumeShaperConfig;
args.arg4 = isVibrationEnabled;
+ args.arg5 = Log.createSubsession();
postMessage(EVENT_PLAY, true /* shouldCreateHandler */, args);
return mHapticsFuture;
}
@@ -173,73 +175,87 @@
Call incomingCall = (Call) args.arg2;
VolumeShaper.Configuration volumeShaperConfig = (VolumeShaper.Configuration) args.arg3;
boolean isVibrationEnabled = (boolean) args.arg4;
+ Session session = (Session) args.arg5;
args.recycle();
- // don't bother with any of this if there is an EVENT_STOP waiting.
- if (mHandler.hasMessages(EVENT_STOP)) {
- mHapticsFuture.complete(false /* ringtoneHasHaptics */);
- mHapticsFuture = null;
- return;
- }
- // If the Ringtone Uri is EMPTY, then the "None" Ringtone has been selected. Do not play
- // anything.
- if(Uri.EMPTY.equals(incomingCall.getRingtone())) {
- mRingtone = null;
- mHapticsFuture.complete(false /* ringtoneHasHaptics */);
- mHapticsFuture = null;
- return;
- }
-
- ThreadUtil.checkNotOnMainThread();
- Log.i(this, "handlePlay: Play ringtone.");
-
- if (mRingtone == null) {
- mRingtone = factory.getRingtone(incomingCall, volumeShaperConfig);
- if (mRingtone == null) {
- Uri ringtoneUri = incomingCall.getRingtone();
- String ringtoneUriString = (ringtoneUri == null) ? "null" :
- ringtoneUri.toSafeString();
- Log.addEvent(null, LogUtils.Events.ERROR_LOG, "Failed to get ringtone from " +
- "factory. Skipping ringing. Uri was: " + ringtoneUriString);
- mHapticsFuture.complete(false /* ringtoneHasHaptics */);
- mHapticsFuture = null;
- return;
- }
-
- // With the ringtone to play now known, we can determine if it has haptic channels or
- // not; we will complete the haptics future so the default vibration code in Ringer
- // can know whether to trigger the vibrator.
- if (mHapticsFuture != null && !mHapticsFuture.isDone()) {
- boolean hasHaptics = factory.hasHapticChannels(mRingtone);
-
- Log.i(this, "handlePlay: hasHaptics=%b, isVibrationEnabled=%b", hasHaptics,
- isVibrationEnabled);
- if (hasHaptics) {
- AudioAttributes attributes = mRingtone.getAudioAttributes();
- Log.d(this, "handlePlay: %s haptic channel",
- (isVibrationEnabled ? "unmuting" : "muting"));
- mRingtone.setAudioAttributes(
- new AudioAttributes.Builder(attributes)
- .setHapticChannelsMuted(!isVibrationEnabled)
- .build());
+ Log.continueSession(session, "ARP.hP");
+ try {
+ // don't bother with any of this if there is an EVENT_STOP waiting.
+ if (mHandler.hasMessages(EVENT_STOP)) {
+ if (mHapticsFuture != null) {
+ mHapticsFuture.complete(false /* ringtoneHasHaptics */);
+ mHapticsFuture = null;
}
- mHapticsFuture.complete(hasHaptics);
- mHapticsFuture = null;
- }
- }
-
- if (mShouldPauseBetweenRepeat) {
- // We're trying to pause between repeats, so the ringtone will not intentionally loop.
- // Instead, we'll use a handler message to perform repeats.
- handleRepeat();
- } else {
- mRingtone.setLooping(true);
- if (mRingtone.isPlaying()) {
- Log.d(this, "Ringtone already playing.");
return;
}
- mRingtone.play();
- Log.i(this, "Play ringtone, looping.");
+
+ // If the Ringtone Uri is EMPTY, then the "None" Ringtone has been selected. Do not play
+ // anything.
+ if (Uri.EMPTY.equals(incomingCall.getRingtone())) {
+ mRingtone = null;
+ if (mHapticsFuture != null) {
+ mHapticsFuture.complete(false /* ringtoneHasHaptics */);
+ mHapticsFuture = null;
+ }
+
+ return;
+ }
+
+ ThreadUtil.checkNotOnMainThread();
+ Log.i(this, "handlePlay: Play ringtone.");
+
+ if (mRingtone == null) {
+ mRingtone = factory.getRingtone(incomingCall, volumeShaperConfig);
+ if (mRingtone == null) {
+ Uri ringtoneUri = incomingCall.getRingtone();
+ String ringtoneUriString = (ringtoneUri == null) ? "null" :
+ ringtoneUri.toSafeString();
+ Log.addEvent(null, LogUtils.Events.ERROR_LOG, "Failed to get ringtone from " +
+ "factory. Skipping ringing. Uri was: " + ringtoneUriString);
+ if (mHapticsFuture != null) {
+ mHapticsFuture.complete(false /* ringtoneHasHaptics */);
+ mHapticsFuture = null;
+ }
+ return;
+ }
+
+ // With the ringtone to play now known, we can determine if it has haptic channels or
+ // not; we will complete the haptics future so the default vibration code in Ringer
+ // can know whether to trigger the vibrator.
+ if (mHapticsFuture != null && !mHapticsFuture.isDone()) {
+ boolean hasHaptics = factory.hasHapticChannels(mRingtone);
+
+ Log.i(this, "handlePlay: hasHaptics=%b, isVibrationEnabled=%b", hasHaptics,
+ isVibrationEnabled);
+ if (hasHaptics) {
+ AudioAttributes attributes = mRingtone.getAudioAttributes();
+ Log.d(this, "handlePlay: %s haptic channel",
+ (isVibrationEnabled ? "unmuting" : "muting"));
+ mRingtone.setAudioAttributes(
+ new AudioAttributes.Builder(attributes)
+ .setHapticChannelsMuted(!isVibrationEnabled)
+ .build());
+ }
+ mHapticsFuture.complete(hasHaptics);
+ mHapticsFuture = null;
+ }
+ }
+
+ if (mShouldPauseBetweenRepeat) {
+ // We're trying to pause between repeats, so the ringtone will not intentionally loop.
+ // Instead, we'll use a handler message to perform repeats.
+ handleRepeat();
+ } else {
+ mRingtone.setLooping(true);
+ if (mRingtone.isPlaying()) {
+ Log.d(this, "Ringtone already playing.");
+ return;
+ }
+ mRingtone.play();
+ Log.i(this, "Play ringtone, looping.");
+ }
+ } finally {
+ Log.cancelSubsession(session);
}
}
diff --git a/src/com/android/server/telecom/CallAudioManager.java b/src/com/android/server/telecom/CallAudioManager.java
index 7e4c3ba..3345fa4 100644
--- a/src/com/android/server/telecom/CallAudioManager.java
+++ b/src/com/android/server/telecom/CallAudioManager.java
@@ -431,35 +431,45 @@
}
void silenceRingers() {
- for (Call call : mRingingCalls) {
- call.silence();
- }
+ synchronized (mCallsManager.getLock()) {
+ for (Call call : mRingingCalls) {
+ call.silence();
+ }
- mRinger.stopRinging();
- mRinger.stopCallWaiting();
+ mRinger.stopRinging();
+ mRinger.stopCallWaiting();
+ }
}
@VisibleForTesting
public boolean startRinging() {
- return mRinger.startRinging(mForegroundCall,
- mCallAudioRouteStateMachine.isHfpDeviceAvailable());
+ synchronized (mCallsManager.getLock()) {
+ return mRinger.startRinging(mForegroundCall,
+ mCallAudioRouteStateMachine.isHfpDeviceAvailable());
+ }
}
@VisibleForTesting
public void startCallWaiting(String reason) {
- if (mRingingCalls.size() == 1) {
- mRinger.startCallWaiting(mRingingCalls.iterator().next(), reason);
+ synchronized (mCallsManager.getLock()) {
+ if (mRingingCalls.size() == 1) {
+ mRinger.startCallWaiting(mRingingCalls.iterator().next(), reason);
+ }
}
}
@VisibleForTesting
public void stopRinging() {
- mRinger.stopRinging();
+ synchronized (mCallsManager.getLock()) {
+ mRinger.stopRinging();
+ }
}
@VisibleForTesting
public void stopCallWaiting() {
- mRinger.stopCallWaiting();
+ synchronized (mCallsManager.getLock()) {
+ mRinger.stopCallWaiting();
+ }
}
@VisibleForTesting
@@ -801,10 +811,12 @@
private void maybeStopRingingAndCallWaitingForAnsweredOrRejectedCall(Call call) {
// Check to see if the call being answered/rejected is the only ringing call, since this
// will be called before the connection service acknowledges the state change.
- if (mRingingCalls.size() == 0 ||
- (mRingingCalls.size() == 1 && call == mRingingCalls.iterator().next())) {
- mRinger.stopRinging();
- mRinger.stopCallWaiting();
+ synchronized (mCallsManager.getLock()) {
+ if (mRingingCalls.size() == 0 ||
+ (mRingingCalls.size() == 1 && call == mRingingCalls.iterator().next())) {
+ mRinger.stopRinging();
+ mRinger.stopCallWaiting();
+ }
}
}
diff --git a/src/com/android/server/telecom/Ringer.java b/src/com/android/server/telecom/Ringer.java
index 2909b72..1dceb01 100644
--- a/src/com/android/server/telecom/Ringer.java
+++ b/src/com/android/server/telecom/Ringer.java
@@ -326,7 +326,7 @@
}
if (hapticsFuture != null) {
- mVibrateFuture = hapticsFuture.thenAccept(isUsingAudioCoupledHaptics -> {
+ mVibrateFuture = hapticsFuture.thenAccept(isUsingAudioCoupledHaptics -> {
if (!isUsingAudioCoupledHaptics || !mIsHapticPlaybackSupportedByDevice) {
Log.i(this, "startRinging: fileHasHaptics=%b, hapticsSupported=%b",
isUsingAudioCoupledHaptics, mIsHapticPlaybackSupportedByDevice);
diff --git a/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java b/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java
index 01add22..3f70453 100644
--- a/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java
+++ b/tests/src/com/android/server/telecom/tests/CallAudioManagerTest.java
@@ -31,6 +31,7 @@
import com.android.server.telecom.InCallTonePlayer;
import com.android.server.telecom.RingbackPlayer;
import com.android.server.telecom.Ringer;
+import com.android.server.telecom.TelecomSystem;
import com.android.server.telecom.bluetooth.BluetoothStateReceiver;
import org.junit.Before;
@@ -66,6 +67,7 @@
@Mock private RingbackPlayer mRingbackPlayer;
@Mock private DtmfLocalTonePlayer mDtmfLocalTonePlayer;
@Mock private BluetoothStateReceiver mBluetoothStateReceiver;
+ @Mock private TelecomSystem.SyncRoot mLock;
private CallAudioManager mCallAudioManager;
@@ -81,6 +83,7 @@
}).when(mockInCallTonePlayer).startTone();
return mockInCallTonePlayer;
}).when(mPlayerFactory).createPlayer(anyInt());
+ when(mCallsManager.getLock()).thenReturn(mLock);
mCallAudioManager = new CallAudioManager(
mCallAudioRouteStateMachine,
mCallsManager,