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.Nullable;
20 import android.content.Context;
21 import android.media.AudioAttributes;
22 import android.media.AudioManager;
23 import android.media.MediaPlayer;
24 import android.media.ToneGenerator;
25 import android.net.Uri;
26 import android.os.Handler;
27 import android.os.Looper;
28 import android.telecom.Log;
29 import android.telecom.Logging.Runnable;
30 import android.telecom.Logging.Session;
31 
32 import com.android.internal.annotations.VisibleForTesting;
33 
34 import java.util.concurrent.CountDownLatch;
35 import java.util.concurrent.TimeUnit;
36 import java.util.concurrent.atomic.AtomicInteger;
37 
38 /**
39  * Play a call-related tone (ringback, busy signal, etc.) either through ToneGenerator, or using a
40  * media resource file.
41  * To use, create an instance using InCallTonePlayer.Factory (passing in the TONE_* constant for
42  * the tone you want) and start() it. Implemented on top of {@link Thread} so that the tone plays in
43  * its own thread.
44  */
45 public class InCallTonePlayer extends Thread {
46 
47     /**
48      * Factory used to create InCallTonePlayers. Exists to aid with testing mocks.
49      */
50     public static class Factory {
51         private CallAudioManager mCallAudioManager;
52         private final CallAudioRoutePeripheralAdapter mCallAudioRoutePeripheralAdapter;
53         private final TelecomSystem.SyncRoot mLock;
54         private final ToneGeneratorFactory mToneGeneratorFactory;
55         private final MediaPlayerFactory mMediaPlayerFactory;
56         private final AudioManagerAdapter mAudioManagerAdapter;
57 
Factory(CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter, TelecomSystem.SyncRoot lock, ToneGeneratorFactory toneGeneratorFactory, MediaPlayerFactory mediaPlayerFactory, AudioManagerAdapter audioManagerAdapter)58         public Factory(CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter,
59                 TelecomSystem.SyncRoot lock, ToneGeneratorFactory toneGeneratorFactory,
60                 MediaPlayerFactory mediaPlayerFactory, AudioManagerAdapter audioManagerAdapter) {
61             mCallAudioRoutePeripheralAdapter = callAudioRoutePeripheralAdapter;
62             mLock = lock;
63             mToneGeneratorFactory = toneGeneratorFactory;
64             mMediaPlayerFactory = mediaPlayerFactory;
65             mAudioManagerAdapter = audioManagerAdapter;
66         }
67 
setCallAudioManager(CallAudioManager callAudioManager)68         public void setCallAudioManager(CallAudioManager callAudioManager) {
69             mCallAudioManager = callAudioManager;
70         }
71 
createPlayer(Call call, int tone)72         public InCallTonePlayer createPlayer(Call call, int tone) {
73             return new InCallTonePlayer(call, tone, mCallAudioManager,
74                     mCallAudioRoutePeripheralAdapter, mLock, mToneGeneratorFactory,
75                     mMediaPlayerFactory, mAudioManagerAdapter);
76         }
77     }
78 
79     public interface ToneGeneratorFactory {
get(int streamType, int volume)80         ToneGenerator get (int streamType, int volume);
81     }
82 
83     public interface MediaPlayerAdapter {
setLooping(boolean isLooping)84         void setLooping(boolean isLooping);
setOnCompletionListener(MediaPlayer.OnCompletionListener listener)85         void setOnCompletionListener(MediaPlayer.OnCompletionListener listener);
start()86         void start();
release()87         void release();
getDuration()88         int getDuration();
89     }
90 
91     public static class MediaPlayerAdapterImpl implements MediaPlayerAdapter {
92         private MediaPlayer mMediaPlayer;
93 
94         /**
95          * Create new media player adapter backed by a real mediaplayer.
96          * Note: Its possible for the mediaplayer to be null if
97          * {@link MediaPlayer#create(Context, Uri)} fails for some reason; in this case we can
98          * continue but not bother playing the audio.
99          * @param mediaPlayer The media player.
100          */
MediaPlayerAdapterImpl(@ullable MediaPlayer mediaPlayer)101         public MediaPlayerAdapterImpl(@Nullable MediaPlayer mediaPlayer) {
102             mMediaPlayer = mediaPlayer;
103         }
104 
105         @Override
setLooping(boolean isLooping)106         public void setLooping(boolean isLooping) {
107             if (mMediaPlayer != null) {
108                 mMediaPlayer.setLooping(isLooping);
109             }
110         }
111 
112         @Override
setOnCompletionListener(MediaPlayer.OnCompletionListener listener)113         public void setOnCompletionListener(MediaPlayer.OnCompletionListener listener) {
114             if (mMediaPlayer != null) {
115                 mMediaPlayer.setOnCompletionListener(listener);
116             }
117         }
118 
119         @Override
start()120         public void start() {
121             if (mMediaPlayer != null) {
122                 mMediaPlayer.start();
123             }
124         }
125 
126         @Override
release()127         public void release() {
128             if (mMediaPlayer != null) {
129                 mMediaPlayer.release();
130             }
131         }
132 
133         @Override
getDuration()134         public int getDuration() {
135             if (mMediaPlayer != null) {
136                 return mMediaPlayer.getDuration();
137             }
138             return 0;
139         }
140     }
141 
142     public interface MediaPlayerFactory {
get(int resourceId, AudioAttributes attributes)143         MediaPlayerAdapter get (int resourceId, AudioAttributes attributes);
144     }
145 
146     public interface AudioManagerAdapter {
isVolumeOverZero()147         boolean isVolumeOverZero();
148     }
149 
150     // The possible tones that we can play.
151     public static final int TONE_INVALID = 0;
152     public static final int TONE_BUSY = 1;
153     public static final int TONE_CALL_ENDED = 2;
154     public static final int TONE_OTA_CALL_ENDED = 3;
155     public static final int TONE_CALL_WAITING = 4;
156     public static final int TONE_CDMA_DROP = 5;
157     public static final int TONE_CONGESTION = 6;
158     public static final int TONE_INTERCEPT = 7;
159     public static final int TONE_OUT_OF_SERVICE = 8;
160     public static final int TONE_REDIAL = 9;
161     public static final int TONE_REORDER = 10;
162     public static final int TONE_RING_BACK = 11;
163     public static final int TONE_UNOBTAINABLE_NUMBER = 12;
164     public static final int TONE_VOICE_PRIVACY = 13;
165     public static final int TONE_VIDEO_UPGRADE = 14;
166     public static final int TONE_RTT_REQUEST = 15;
167     public static final int TONE_IN_CALL_QUALITY_NOTIFICATION = 16;
168 
169     private static final int TONE_RESOURCE_ID_UNDEFINED = -1;
170 
171     private static final int RELATIVE_VOLUME_EMERGENCY = 100;
172     private static final int RELATIVE_VOLUME_HIPRI = 80;
173     private static final int RELATIVE_VOLUME_LOPRI = 30;
174     private static final int RELATIVE_VOLUME_UNDEFINED = -1;
175 
176     // Buffer time (in msec) to add on to the tone timeout value. Needed mainly when the timeout
177     // value for a tone is exact duration of the tone itself.
178     private static final int TIMEOUT_BUFFER_MILLIS = 20;
179 
180     // The tone state.
181     private static final int STATE_OFF = 0;
182     private static final int STATE_ON = 1;
183     private static final int STATE_STOPPED = 2;
184 
185     // Invalid audio stream
186     private static final int STREAM_INVALID = -1;
187 
188     /**
189      * Keeps count of the number of actively playing tones so that we can notify CallAudioManager
190      * when we need focus and when it can be release. This should only be manipulated from the main
191      * thread.
192      */
193     private static AtomicInteger sTonesPlaying = new AtomicInteger(0);
194 
195     private final CallAudioManager mCallAudioManager;
196     private final CallAudioRoutePeripheralAdapter mCallAudioRoutePeripheralAdapter;
197 
198     private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
199 
200     /** The ID of the tone to play. */
201     private final int mToneId;
202 
203     /** Current state of the tone player. */
204     private int mState;
205 
206     /** For tones which are not generated using ToneGenerator. */
207     private MediaPlayerAdapter mToneMediaPlayer = null;
208 
209     /** Telecom lock object. */
210     private final TelecomSystem.SyncRoot mLock;
211 
212     private Session mSession;
213     private final Object mSessionLock = new Object();
214 
215     private final Call mCall;
216     private final ToneGeneratorFactory mToneGenerator;
217     private final MediaPlayerFactory mMediaPlayerFactory;
218     private final AudioManagerAdapter mAudioManagerAdapter;
219 
220     /**
221      * Latch used for awaiting on playback, which may be interrupted if the tone is stopped from
222      * outside the playback.
223      */
224     private final CountDownLatch mPlaybackLatch = new CountDownLatch(1);
225 
226     /**
227      * Initializes the tone player. Private; use the {@link Factory} to create tone players.
228      *
229      * @param toneId ID of the tone to play, see TONE_* constants.
230      */
InCallTonePlayer( Call call, int toneId, CallAudioManager callAudioManager, CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter, TelecomSystem.SyncRoot lock, ToneGeneratorFactory toneGeneratorFactory, MediaPlayerFactory mediaPlayerFactor, AudioManagerAdapter audioManagerAdapter)231     private InCallTonePlayer(
232             Call call,
233             int toneId,
234             CallAudioManager callAudioManager,
235             CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter,
236             TelecomSystem.SyncRoot lock,
237             ToneGeneratorFactory toneGeneratorFactory,
238             MediaPlayerFactory mediaPlayerFactor,
239             AudioManagerAdapter audioManagerAdapter) {
240         mCall = call;
241         mState = STATE_OFF;
242         mToneId = toneId;
243         mCallAudioManager = callAudioManager;
244         mCallAudioRoutePeripheralAdapter = callAudioRoutePeripheralAdapter;
245         mLock = lock;
246         mToneGenerator = toneGeneratorFactory;
247         mMediaPlayerFactory = mediaPlayerFactor;
248         mAudioManagerAdapter = audioManagerAdapter;
249     }
250 
251     /** {@inheritDoc} */
252     @Override
run()253     public void run() {
254         try {
255             synchronized (mSessionLock) {
256                 if (mSession != null) {
257                     Log.continueSession(mSession, "ICTP.r");
258                     mSession = null;
259                 }
260             }
261             Log.d(this, "run(toneId = %s)", mToneId);
262 
263             final int toneType;  // Passed to ToneGenerator.startTone.
264             final int toneVolume;  // Passed to the ToneGenerator constructor.
265             final int toneLengthMillis;
266             final int mediaResourceId; // The resourceId of the tone to play.  Used for media-based
267                                       // tones.
268 
269             switch (mToneId) {
270                 case TONE_BUSY:
271                     // TODO: CDMA-specific tones
272                     toneType = ToneGenerator.TONE_SUP_BUSY;
273                     toneVolume = RELATIVE_VOLUME_HIPRI;
274                     toneLengthMillis = 4000;
275                     mediaResourceId = TONE_RESOURCE_ID_UNDEFINED;
276                     break;
277                 case TONE_CALL_ENDED:
278                     // Don't use tone generator
279                     toneType = ToneGenerator.TONE_UNKNOWN;
280                     toneVolume = RELATIVE_VOLUME_UNDEFINED;
281                     toneLengthMillis = 0;
282 
283                     // Use a tone resource file for a more rich, full-bodied tone experience.
284                     mediaResourceId = R.raw.endcall;
285                     break;
286                 case TONE_OTA_CALL_ENDED:
287                     // TODO: fill in
288                     throw new IllegalStateException("OTA Call ended NYI.");
289                 case TONE_CALL_WAITING:
290                     toneType = ToneGenerator.TONE_SUP_CALL_WAITING;
291                     toneVolume = RELATIVE_VOLUME_HIPRI;
292                     toneLengthMillis = Integer.MAX_VALUE - TIMEOUT_BUFFER_MILLIS;
293                     mediaResourceId = TONE_RESOURCE_ID_UNDEFINED;
294                     break;
295                 case TONE_CDMA_DROP:
296                     toneType = ToneGenerator.TONE_CDMA_CALLDROP_LITE;
297                     toneVolume = RELATIVE_VOLUME_LOPRI;
298                     toneLengthMillis = 375;
299                     mediaResourceId = TONE_RESOURCE_ID_UNDEFINED;
300                     break;
301                 case TONE_CONGESTION:
302                     toneType = ToneGenerator.TONE_SUP_CONGESTION;
303                     toneVolume = RELATIVE_VOLUME_HIPRI;
304                     toneLengthMillis = 4000;
305                     mediaResourceId = TONE_RESOURCE_ID_UNDEFINED;
306                     break;
307                 case TONE_INTERCEPT:
308                     toneType = ToneGenerator.TONE_CDMA_ABBR_INTERCEPT;
309                     toneVolume = RELATIVE_VOLUME_LOPRI;
310                     toneLengthMillis = 500;
311                     mediaResourceId = TONE_RESOURCE_ID_UNDEFINED;
312                     break;
313                 case TONE_OUT_OF_SERVICE:
314                     toneType = ToneGenerator.TONE_CDMA_CALLDROP_LITE;
315                     toneVolume = RELATIVE_VOLUME_LOPRI;
316                     toneLengthMillis = 375;
317                     mediaResourceId = TONE_RESOURCE_ID_UNDEFINED;
318                     break;
319                 case TONE_REDIAL:
320                     toneType = ToneGenerator.TONE_CDMA_ALERT_AUTOREDIAL_LITE;
321                     toneVolume = RELATIVE_VOLUME_LOPRI;
322                     toneLengthMillis = 5000;
323                     mediaResourceId = TONE_RESOURCE_ID_UNDEFINED;
324                     break;
325                 case TONE_REORDER:
326                     toneType = ToneGenerator.TONE_CDMA_REORDER;
327                     toneVolume = RELATIVE_VOLUME_HIPRI;
328                     toneLengthMillis = 4000;
329                     mediaResourceId = TONE_RESOURCE_ID_UNDEFINED;
330                     break;
331                 case TONE_RING_BACK:
332                     toneType = ToneGenerator.TONE_SUP_RINGTONE;
333                     toneVolume = RELATIVE_VOLUME_HIPRI;
334                     toneLengthMillis = Integer.MAX_VALUE - TIMEOUT_BUFFER_MILLIS;
335                     mediaResourceId = TONE_RESOURCE_ID_UNDEFINED;
336                     break;
337                 case TONE_UNOBTAINABLE_NUMBER:
338                     toneType = ToneGenerator.TONE_SUP_ERROR;
339                     toneVolume = RELATIVE_VOLUME_HIPRI;
340                     toneLengthMillis = 4000;
341                     mediaResourceId = TONE_RESOURCE_ID_UNDEFINED;
342                     break;
343                 case TONE_VOICE_PRIVACY:
344                     // TODO: fill in.
345                     throw new IllegalStateException("Voice privacy tone NYI.");
346                 case TONE_VIDEO_UPGRADE:
347                 case TONE_RTT_REQUEST:
348                     // Similar to the call waiting tone, but does not repeat.
349                     toneType = ToneGenerator.TONE_SUP_CALL_WAITING;
350                     toneVolume = RELATIVE_VOLUME_HIPRI;
351                     toneLengthMillis = 4000;
352                     mediaResourceId = TONE_RESOURCE_ID_UNDEFINED;
353                     break;
354                 case TONE_IN_CALL_QUALITY_NOTIFICATION:
355                     // Don't use tone generator
356                     toneType = ToneGenerator.TONE_UNKNOWN;
357                     toneVolume = RELATIVE_VOLUME_UNDEFINED;
358                     toneLengthMillis = 0;
359 
360                     // Use a tone resource file for a more rich, full-bodied tone experience.
361                     mediaResourceId = R.raw.InCallQualityNotification;
362                     break;
363                 default:
364                     throw new IllegalStateException("Bad toneId: " + mToneId);
365             }
366 
367             int stream = AudioManager.STREAM_VOICE_CALL;
368             if (mCallAudioRoutePeripheralAdapter.isBluetoothAudioOn()) {
369                 stream = AudioManager.STREAM_BLUETOOTH_SCO;
370             }
371             if (toneType != ToneGenerator.TONE_UNKNOWN) {
372                 if (stream == AudioManager.STREAM_BLUETOOTH_SCO) {
373                     // Override audio stream for BT le device and hearing aid device
374                     if (mCallAudioRoutePeripheralAdapter.isLeAudioDeviceOn()
375                             || mCallAudioRoutePeripheralAdapter.isHearingAidDeviceOn()) {
376                         stream = AudioManager.STREAM_VOICE_CALL;
377                     }
378                 }
379                 playToneGeneratorTone(stream, toneVolume, toneType, toneLengthMillis);
380             } else if (mediaResourceId != TONE_RESOURCE_ID_UNDEFINED) {
381                 playMediaTone(stream, mediaResourceId);
382             }
383         } finally {
384             cleanUpTonePlayer();
385             Log.endSession();
386         }
387     }
388 
389     /**
390      * Play a tone generated by the {@link ToneGenerator}.
391      * @param stream The stream on which the tone will be played.
392      * @param toneVolume The volume of the tone.
393      * @param toneType The type of tone to play.
394      * @param toneLengthMillis How long to play the tone.
395      */
playToneGeneratorTone(int stream, int toneVolume, int toneType, int toneLengthMillis)396     private void playToneGeneratorTone(int stream, int toneVolume, int toneType,
397             int toneLengthMillis) {
398         ToneGenerator toneGenerator = null;
399         try {
400             // If the ToneGenerator creation fails, just continue without it. It is a local audio
401             // signal, and is not as important.
402             try {
403                 toneGenerator = mToneGenerator.get(stream, toneVolume);
404             } catch (RuntimeException e) {
405                 Log.w(this, "Failed to create ToneGenerator.", e);
406                 return;
407             }
408 
409             Log.i(this, "playToneGeneratorTone: toneType=%d", toneType);
410 
411             mState = STATE_ON;
412             toneGenerator.startTone(toneType);
413             try {
414                 Log.v(this, "Starting tone %d...waiting for %d ms.", mToneId,
415                         toneLengthMillis + TIMEOUT_BUFFER_MILLIS);
416                 if (mPlaybackLatch.await(toneLengthMillis + TIMEOUT_BUFFER_MILLIS,
417                         TimeUnit.MILLISECONDS)) {
418                     Log.i(this, "playToneGeneratorTone: tone playback stopped.");
419                 }
420             } catch (InterruptedException e) {
421                 Log.w(this, "playToneGeneratorTone: wait interrupted", e);
422             }
423             // Redundant; don't want anyone re-using at this point.
424             mState = STATE_STOPPED;
425         } finally {
426             if (toneGenerator != null) {
427                 toneGenerator.release();
428             }
429         }
430     }
431 
432     /**
433      * Plays an audio-file based media tone.
434      * @param stream The audio stream on which to play the tone.
435      * @param toneResourceId The resource ID of the tone to play.
436      */
playMediaTone(int stream, int toneResourceId)437     private void playMediaTone(int stream, int toneResourceId) {
438         mState = STATE_ON;
439         Log.i(this, "playMediaTone: toneResourceId=%d", toneResourceId);
440         AudioAttributes attributes = new AudioAttributes.Builder()
441                 .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
442                 .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH)
443                 .setLegacyStreamType(stream)
444                 .build();
445         mToneMediaPlayer = mMediaPlayerFactory.get(toneResourceId, attributes);
446         mToneMediaPlayer.setLooping(false);
447         int durationMillis = mToneMediaPlayer.getDuration();
448         mToneMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
449             @Override
450             public void onCompletion(MediaPlayer mp) {
451                 Log.i(InCallTonePlayer.this, "playMediaTone: toneResourceId=%d completed.",
452                         toneResourceId);
453                 mPlaybackLatch.countDown();
454             }
455         });
456 
457         try {
458             mToneMediaPlayer.start();
459             // Wait for the tone to stop playing; timeout at 2x the length of the file just to
460             // be on the safe side.  Playback can also be stopped via stopTone().
461             if (mPlaybackLatch.await(durationMillis * 2, TimeUnit.MILLISECONDS)) {
462                 Log.i(this, "playMediaTone: tone playback stopped.");
463             }
464         } catch (InterruptedException ie) {
465             Log.e(this, ie, "playMediaTone: tone playback interrupted.");
466         } finally {
467             // Redundant; don't want anyone re-using at this point.
468             mState = STATE_STOPPED;
469             mToneMediaPlayer.release();
470             mToneMediaPlayer = null;
471         }
472     }
473 
474     @VisibleForTesting
startTone()475     public boolean startTone() {
476         // Tone already done; don't allow re-used
477         if (mState == STATE_STOPPED) {
478             return false;
479         }
480 
481         if (sTonesPlaying.incrementAndGet() == 1) {
482             mCallAudioManager.setIsTonePlaying(mCall, true);
483         }
484 
485         synchronized (mSessionLock) {
486             if (mSession != null) {
487                 Log.cancelSubsession(mSession);
488             }
489             mSession = Log.createSubsession();
490         }
491 
492         super.start();
493         return true;
494     }
495 
496     @Override
start()497     public void start() {
498         Log.w(this, "Do not call the start method directly; use startTone instead.");
499     }
500 
501     /**
502      * Stops the tone.
503      */
504     @VisibleForTesting
stopTone()505     public void stopTone() {
506         Log.i(this, "stopTone: Stopping the tone %d.", mToneId);
507         // Notify the playback to end early.
508         mPlaybackLatch.countDown();
509 
510         mState = STATE_STOPPED;
511     }
512 
513     @VisibleForTesting
cleanup()514     public void cleanup() {
515         sTonesPlaying.set(0);
516     }
517 
cleanUpTonePlayer()518     private void cleanUpTonePlayer() {
519         Log.d(this, "cleanUpTonePlayer(): posting cleanup");
520         // Release focus on the main thread.
521         mMainThreadHandler.post(new Runnable("ICTP.cUTP", mLock) {
522             @Override
523             public void loggedRun() {
524                 int newToneCount = sTonesPlaying.updateAndGet( t -> Math.max(0, --t));
525 
526                 if (newToneCount == 0) {
527                     Log.i(InCallTonePlayer.this,
528                             "cleanUpTonePlayer(): tonesPlaying=%d, tone completed", newToneCount);
529                     if (mCallAudioManager != null) {
530                         mCallAudioManager.setIsTonePlaying(mCall, false);
531                     } else {
532                         Log.w(InCallTonePlayer.this,
533                                 "cleanUpTonePlayer(): mCallAudioManager is null!");
534                     }
535                 } else {
536                     Log.i(InCallTonePlayer.this,
537                             "cleanUpTonePlayer(): tonesPlaying=%d; still playing", newToneCount);
538                 }
539             }
540         }.prepare());
541     }
542 }
543