1 /* 2 * Copyright 2014, The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.telecom; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.media.Ringtone; 22 import android.media.VolumeShaper; 23 import android.net.Uri; 24 import android.os.Handler; 25 import android.os.HandlerThread; 26 import android.os.Message; 27 import android.telecom.Log; 28 import android.telecom.Logging.Session; 29 import com.android.internal.annotations.VisibleForTesting; 30 import com.android.internal.os.SomeArgs; 31 import com.android.internal.util.Preconditions; 32 33 import java.util.ArrayList; 34 import java.util.concurrent.CountDownLatch; 35 import java.util.concurrent.TimeUnit; 36 import java.util.function.BiConsumer; 37 import java.util.function.Supplier; 38 39 /** 40 * Plays the default ringtone. Uses {@link Ringtone} in a separate thread so that this class can be 41 * used from the main thread. 42 */ 43 @VisibleForTesting 44 public class AsyncRingtonePlayer { 45 // Maximum amount of time we will delay playing a ringtone while waiting for audio routing to 46 // be ready. 47 private static final int PLAY_DELAY_TIMEOUT_MS = 1000; 48 // Message codes used with the ringtone thread. 49 private static final int EVENT_PLAY = 1; 50 private static final int EVENT_STOP = 2; 51 52 /** Handler running on the ringtone thread. */ 53 private Handler mHandler; 54 55 /** The current ringtone. Only used by the ringtone thread. */ 56 private Ringtone mRingtone; 57 58 /** 59 * Set to true if we are setting up to play or are currently playing. False if we are stopping 60 * or have stopped playing. 61 */ 62 private boolean mIsPlaying = false; 63 64 /** 65 * Set to true if BT HFP is active and audio is connected. 66 */ 67 private boolean mIsBtActive = false; 68 69 /** 70 * A list of pending ringing ready latches, which are used to delay the ringing command until 71 * audio paths are set and ringing is ready. 72 */ 73 private final ArrayList<CountDownLatch> mPendingRingingLatches = new ArrayList<>(); 74 AsyncRingtonePlayer()75 public AsyncRingtonePlayer() { 76 // Empty 77 } 78 79 /** 80 * Plays the appropriate ringtone for the specified call. 81 * If {@link VolumeShaper.Configuration} is specified, it is applied to the ringtone to change 82 * the volume of the ringtone as it plays. 83 * 84 * @param ringtoneSupplier The {@link Ringtone} factory. 85 * @param ringtoneConsumer The {@link Ringtone} post-creation callback (to start the vibration). 86 * @param isHfpDeviceConnected True if there is a HFP BT device connected, false otherwise. 87 */ play(@onNull Supplier<Ringtone> ringtoneSupplier, BiConsumer<Ringtone, Boolean> ringtoneConsumer, boolean isHfpDeviceConnected)88 public void play(@NonNull Supplier<Ringtone> ringtoneSupplier, 89 BiConsumer<Ringtone, Boolean> ringtoneConsumer, boolean isHfpDeviceConnected) { 90 Log.d(this, "Posting play."); 91 mIsPlaying = true; 92 SomeArgs args = SomeArgs.obtain(); 93 args.arg1 = ringtoneSupplier; 94 args.arg2 = ringtoneConsumer; 95 args.arg3 = Log.createSubsession(); 96 args.arg4 = prepareRingingReadyLatch(isHfpDeviceConnected); 97 postMessage(EVENT_PLAY, true /* shouldCreateHandler */, args); 98 } 99 100 /** Stops playing the ringtone. */ stop()101 public void stop() { 102 Log.d(this, "Posting stop."); 103 mIsPlaying = false; 104 postMessage(EVENT_STOP, false /* shouldCreateHandler */, null); 105 // Clear any pending ringing latches so that we do not have to wait for its timeout to pass 106 // before calling stop. 107 clearPendingRingingLatches(); 108 } 109 110 /** 111 * Called when the BT HFP profile active state changes. 112 * @param isBtActive A BT device is connected and audio is active. 113 */ updateBtActiveState(boolean isBtActive)114 public void updateBtActiveState(boolean isBtActive) { 115 Log.i(this, "updateBtActiveState: " + isBtActive); 116 synchronized (mPendingRingingLatches) { 117 mIsBtActive = isBtActive; 118 if (isBtActive) mPendingRingingLatches.forEach(CountDownLatch::countDown); 119 } 120 } 121 122 /** 123 * Prepares a new ringing ready latch and tracks it in a list. Once the ready latch has been 124 * used, {@link #removePendingRingingReadyLatch(CountDownLatch)} must be called on this latch. 125 * @param isHfpDeviceConnected true if there is a HFP device connected. 126 * @return the newly prepared CountDownLatch 127 */ prepareRingingReadyLatch(boolean isHfpDeviceConnected)128 private CountDownLatch prepareRingingReadyLatch(boolean isHfpDeviceConnected) { 129 CountDownLatch latch = new CountDownLatch(1); 130 synchronized (mPendingRingingLatches) { 131 // We only want to delay ringing if BT is connected but not active yet. 132 boolean isDelayRequired = isHfpDeviceConnected && !mIsBtActive; 133 Log.i(this, "prepareRingingReadyLatch:" 134 + " connected=" + isHfpDeviceConnected 135 + ", BT active=" + mIsBtActive 136 + ", isDelayRequired=" + isDelayRequired); 137 if (!isDelayRequired) latch.countDown(); 138 mPendingRingingLatches.add(latch); 139 } 140 return latch; 141 } 142 143 /** 144 * Remove a ringing ready latch that has been used and is no longer pending. 145 * @param l The latch to remove. 146 */ removePendingRingingReadyLatch(CountDownLatch l)147 private void removePendingRingingReadyLatch(CountDownLatch l) { 148 synchronized (mPendingRingingLatches) { 149 mPendingRingingLatches.remove(l); 150 } 151 } 152 153 /** 154 * Count down all pending ringing ready latches and then clear the list. 155 */ clearPendingRingingLatches()156 private void clearPendingRingingLatches() { 157 synchronized (mPendingRingingLatches) { 158 mPendingRingingLatches.forEach(CountDownLatch::countDown); 159 mPendingRingingLatches.clear(); 160 } 161 } 162 163 /** 164 * Posts a message to the ringtone-thread handler. Creates the handler if specified by the 165 * parameter shouldCreateHandler. 166 * 167 * @param messageCode The message to post. 168 * @param shouldCreateHandler True when a handler should be created to handle this message. 169 */ postMessage(int messageCode, boolean shouldCreateHandler, SomeArgs args)170 private void postMessage(int messageCode, boolean shouldCreateHandler, SomeArgs args) { 171 synchronized(this) { 172 if (mHandler == null && shouldCreateHandler) { 173 mHandler = getNewHandler(); 174 } 175 176 if (mHandler == null) { 177 Log.d(this, "Message %d skipped because there is no handler.", messageCode); 178 } else { 179 mHandler.obtainMessage(messageCode, args).sendToTarget(); 180 } 181 } 182 } 183 184 /** 185 * Creates a new ringtone Handler running in its own thread. 186 */ getNewHandler()187 private Handler getNewHandler() { 188 Preconditions.checkState(mHandler == null); 189 190 HandlerThread thread = new HandlerThread("ringtone-player"); 191 thread.start(); 192 193 return new Handler(thread.getLooper(), null /*callback*/, true /*async*/) { 194 @Override 195 public void handleMessage(Message msg) { 196 switch(msg.what) { 197 case EVENT_PLAY: 198 handlePlay((SomeArgs) msg.obj); 199 break; 200 case EVENT_STOP: 201 handleStop(); 202 break; 203 } 204 } 205 }; 206 } 207 208 /** 209 * Starts the actual playback of the ringtone. Executes on ringtone-thread. 210 */ 211 private void handlePlay(SomeArgs args) { 212 Supplier<Ringtone> ringtoneSupplier = (Supplier<Ringtone>) args.arg1; 213 BiConsumer<Ringtone, Boolean> ringtoneConsumer = (BiConsumer<Ringtone, Boolean>) args.arg2; 214 Session session = (Session) args.arg3; 215 CountDownLatch ringingReadyLatch = (CountDownLatch) args.arg4; 216 args.recycle(); 217 218 Log.continueSession(session, "ARP.hP"); 219 try { 220 // Don't bother with any of this if there is an EVENT_STOP waiting, but give the 221 // consumer a chance to do anything no matter what. 222 if (mHandler.hasMessages(EVENT_STOP)) { 223 Log.i(this, "handlePlay: skipping play early due to pending STOP"); 224 removePendingRingingReadyLatch(ringingReadyLatch); 225 ringtoneConsumer.accept(null, /* stopped= */ true); 226 return; 227 } 228 Ringtone ringtone = null; 229 boolean hasStopped = false; 230 try { 231 try { 232 Log.i(this, "handlePlay: delay ring for ready signal..."); 233 boolean reachedZero = ringingReadyLatch.await(PLAY_DELAY_TIMEOUT_MS, 234 TimeUnit.MILLISECONDS); 235 Log.i(this, "handlePlay: ringing ready, timeout=" + !reachedZero); 236 } catch (InterruptedException e) { 237 Log.w(this, "handlePlay: latch exception: " + e); 238 } 239 ringtone = ringtoneSupplier.get(); 240 // Ringtone supply can be slow or stop command could have been issued while waiting 241 // for BT to move to CONNECTED state. Re-check for stop event. 242 if (mHandler.hasMessages(EVENT_STOP)) { 243 Log.i(this, "handlePlay: skipping play due to pending STOP"); 244 hasStopped = true; 245 if (ringtone != null) ringtone.stop(); // proactively release the ringtone. 246 return; 247 } 248 // setRingtone even if null - it also stops any current ringtone to be consistent 249 // with the overall state. 250 setRingtone(ringtone); 251 if (mRingtone == null) { 252 // The ringtoneConsumer can still vibrate at this stage. 253 Log.w(this, "No ringtone was found bail out from playing."); 254 return; 255 } 256 Uri uri = mRingtone.getUri(); 257 String uriString = (uri != null ? uri.toSafeString() : ""); 258 Log.i(this, "handlePlay: Play ringtone. Uri: " + uriString); 259 mRingtone.setLooping(true); 260 if (mRingtone.isPlaying()) { 261 Log.d(this, "Ringtone already playing."); 262 return; 263 } 264 mRingtone.play(); 265 Log.i(this, "Play ringtone, looping."); 266 } finally { 267 removePendingRingingReadyLatch(ringingReadyLatch); 268 ringtoneConsumer.accept(ringtone, hasStopped); 269 } 270 } finally { 271 Log.cancelSubsession(session); 272 } 273 } 274 275 /** 276 * Stops the playback of the ringtone. Executes on the ringtone-thread. 277 */ 278 private void handleStop() { 279 ThreadUtil.checkNotOnMainThread(); 280 Log.i(this, "Stop ringtone."); 281 282 setRingtone(null); 283 284 synchronized(this) { 285 if (mHandler.hasMessages(EVENT_PLAY)) { 286 Log.v(this, "Keeping alive ringtone thread for subsequent play request."); 287 } else { 288 mHandler.removeMessages(EVENT_STOP); 289 mHandler.getLooper().quitSafely(); 290 mHandler = null; 291 Log.v(this, "Handler cleared."); 292 } 293 } 294 } 295 296 /** 297 * @return true if we are currently preparing or playing a ringtone, false if we are not. 298 */ 299 public boolean isPlaying() { 300 return mIsPlaying; 301 } 302 303 private void setRingtone(@Nullable Ringtone ringtone) { 304 Log.i(this, "setRingtone: ringtone null=" + (ringtone == null)); 305 // Make sure that any previously created instance of Ringtone is stopped so the MediaPlayer 306 // can be released, before replacing mRingtone with a new instance. This is always created 307 // as a looping Ringtone, so if not stopped it will keep playing on the background. 308 if (mRingtone != null) { 309 Log.d(this, "Ringtone.stop() invoked."); 310 mRingtone.stop(); 311 } 312 mRingtone = ringtone; 313 } 314 } 315