diff options
Diffstat (limited to 'src/com/android/server/telecom/AsyncRingtonePlayer.java')
-rw-r--r-- | src/com/android/server/telecom/AsyncRingtonePlayer.java | 109 |
1 files changed, 105 insertions, 4 deletions
diff --git a/src/com/android/server/telecom/AsyncRingtonePlayer.java b/src/com/android/server/telecom/AsyncRingtonePlayer.java index 3fbac1fdb..912305be4 100644 --- a/src/com/android/server/telecom/AsyncRingtonePlayer.java +++ b/src/com/android/server/telecom/AsyncRingtonePlayer.java @@ -30,6 +30,9 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.SomeArgs; import com.android.internal.util.Preconditions; +import java.util.ArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; import java.util.function.Supplier; @@ -39,6 +42,9 @@ import java.util.function.Supplier; */ @VisibleForTesting public class AsyncRingtonePlayer { + // Maximum amount of time we will delay playing a ringtone while waiting for audio routing to + // be ready. + private static final int PLAY_DELAY_TIMEOUT_MS = 1000; // Message codes used with the ringtone thread. private static final int EVENT_PLAY = 1; private static final int EVENT_STOP = 2; @@ -49,6 +55,23 @@ public class AsyncRingtonePlayer { /** The current ringtone. Only used by the ringtone thread. */ private Ringtone mRingtone; + /** + * Set to true if we are setting up to play or are currently playing. False if we are stopping + * or have stopped playing. + */ + private boolean mIsPlaying = false; + + /** + * Set to true if BT HFP is active and audio is connected. + */ + private boolean mIsBtActive = false; + + /** + * A list of pending ringing ready latches, which are used to delay the ringing command until + * audio paths are set and ringing is ready. + */ + private final ArrayList<CountDownLatch> mPendingRingingLatches = new ArrayList<>(); + public AsyncRingtonePlayer() { // Empty } @@ -60,21 +83,81 @@ public class AsyncRingtonePlayer { * * @param ringtoneSupplier The {@link Ringtone} factory. * @param ringtoneConsumer The {@link Ringtone} post-creation callback (to start the vibration). + * @param isHfpDeviceConnected True if there is a HFP BT device connected, false otherwise. */ public void play(@NonNull Supplier<Ringtone> ringtoneSupplier, - BiConsumer<Ringtone, Boolean> ringtoneConsumer) { + BiConsumer<Ringtone, Boolean> ringtoneConsumer, boolean isHfpDeviceConnected) { Log.d(this, "Posting play."); + mIsPlaying = true; SomeArgs args = SomeArgs.obtain(); args.arg1 = ringtoneSupplier; args.arg2 = ringtoneConsumer; args.arg3 = Log.createSubsession(); + args.arg4 = prepareRingingReadyLatch(isHfpDeviceConnected); postMessage(EVENT_PLAY, true /* shouldCreateHandler */, args); } /** Stops playing the ringtone. */ public void stop() { Log.d(this, "Posting stop."); + mIsPlaying = false; postMessage(EVENT_STOP, false /* shouldCreateHandler */, null); + // Clear any pending ringing latches so that we do not have to wait for its timeout to pass + // before calling stop. + clearPendingRingingLatches(); + } + + /** + * Called when the BT HFP profile active state changes. + * @param isBtActive A BT device is connected and audio is active. + */ + public void updateBtActiveState(boolean isBtActive) { + Log.i(this, "updateBtActiveState: " + isBtActive); + synchronized (mPendingRingingLatches) { + mIsBtActive = isBtActive; + if (isBtActive) mPendingRingingLatches.forEach(CountDownLatch::countDown); + } + } + + /** + * Prepares a new ringing ready latch and tracks it in a list. Once the ready latch has been + * used, {@link #removePendingRingingReadyLatch(CountDownLatch)} must be called on this latch. + * @param isHfpDeviceConnected true if there is a HFP device connected. + * @return the newly prepared CountDownLatch + */ + private CountDownLatch prepareRingingReadyLatch(boolean isHfpDeviceConnected) { + CountDownLatch latch = new CountDownLatch(1); + synchronized (mPendingRingingLatches) { + // We only want to delay ringing if BT is connected but not active yet. + boolean isDelayRequired = isHfpDeviceConnected && !mIsBtActive; + Log.i(this, "prepareRingingReadyLatch:" + + " connected=" + isHfpDeviceConnected + + ", BT active=" + mIsBtActive + + ", isDelayRequired=" + isDelayRequired); + if (!isDelayRequired) latch.countDown(); + mPendingRingingLatches.add(latch); + } + return latch; + } + + /** + * Remove a ringing ready latch that has been used and is no longer pending. + * @param l The latch to remove. + */ + private void removePendingRingingReadyLatch(CountDownLatch l) { + synchronized (mPendingRingingLatches) { + mPendingRingingLatches.remove(l); + } + } + + /** + * Count down all pending ringing ready latches and then clear the list. + */ + private void clearPendingRingingLatches() { + synchronized (mPendingRingingLatches) { + mPendingRingingLatches.forEach(CountDownLatch::countDown); + mPendingRingingLatches.clear(); + } } /** @@ -129,6 +212,7 @@ public class AsyncRingtonePlayer { Supplier<Ringtone> ringtoneSupplier = (Supplier<Ringtone>) args.arg1; BiConsumer<Ringtone, Boolean> ringtoneConsumer = (BiConsumer<Ringtone, Boolean>) args.arg2; Session session = (Session) args.arg3; + CountDownLatch ringingReadyLatch = (CountDownLatch) args.arg4; args.recycle(); Log.continueSession(session, "ARP.hP"); @@ -136,17 +220,29 @@ public class AsyncRingtonePlayer { // Don't bother with any of this if there is an EVENT_STOP waiting, but give the // consumer a chance to do anything no matter what. if (mHandler.hasMessages(EVENT_STOP)) { + Log.i(this, "handlePlay: skipping play early due to pending STOP"); + removePendingRingingReadyLatch(ringingReadyLatch); ringtoneConsumer.accept(null, /* stopped= */ true); return; } Ringtone ringtone = null; boolean hasStopped = false; try { + try { + Log.i(this, "handlePlay: delay ring for ready signal..."); + boolean reachedZero = ringingReadyLatch.await(PLAY_DELAY_TIMEOUT_MS, + TimeUnit.MILLISECONDS); + Log.i(this, "handlePlay: ringing ready, timeout=" + !reachedZero); + } catch (InterruptedException e) { + Log.w(this, "handlePlay: latch exception: " + e); + } ringtone = ringtoneSupplier.get(); - // Ringtone supply can be slow. Re-check for stop event. + // Ringtone supply can be slow or stop command could have been issued while waiting + // for BT to move to CONNECTED state. Re-check for stop event. if (mHandler.hasMessages(EVENT_STOP)) { + Log.i(this, "handlePlay: skipping play due to pending STOP"); hasStopped = true; - ringtone.stop(); // proactively release the ringtone. + if (ringtone != null) ringtone.stop(); // proactively release the ringtone. return; } // setRingtone even if null - it also stops any current ringtone to be consistent @@ -168,6 +264,7 @@ public class AsyncRingtonePlayer { mRingtone.play(); Log.i(this, "Play ringtone, looping."); } finally { + removePendingRingingReadyLatch(ringingReadyLatch); ringtoneConsumer.accept(ringtone, hasStopped); } } finally { @@ -196,11 +293,15 @@ public class AsyncRingtonePlayer { } } + /** + * @return true if we are currently preparing or playing a ringtone, false if we are not. + */ public boolean isPlaying() { - return mRingtone != null; + return mIsPlaying; } private void setRingtone(@Nullable Ringtone ringtone) { + Log.i(this, "setRingtone: ringtone null=" + (ringtone == null)); // Make sure that any previously created instance of Ringtone is stopped so the MediaPlayer // can be released, before replacing mRingtone with a new instance. This is always created // as a looping Ringtone, so if not stopped it will keep playing on the background. |