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