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