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(int tone)72 public InCallTonePlayer createPlayer(int tone) { 73 return new InCallTonePlayer(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 = 50; 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 ToneGeneratorFactory mToneGenerator; 216 private final MediaPlayerFactory mMediaPlayerFactory; 217 private final AudioManagerAdapter mAudioManagerAdapter; 218 219 /** 220 * Latch used for awaiting on playback, which may be interrupted if the tone is stopped from 221 * outside the playback. 222 */ 223 private final CountDownLatch mPlaybackLatch = new CountDownLatch(1); 224 225 /** 226 * Initializes the tone player. Private; use the {@link Factory} to create tone players. 227 * 228 * @param toneId ID of the tone to play, see TONE_* constants. 229 */ InCallTonePlayer( int toneId, CallAudioManager callAudioManager, CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter, TelecomSystem.SyncRoot lock, ToneGeneratorFactory toneGeneratorFactory, MediaPlayerFactory mediaPlayerFactor, AudioManagerAdapter audioManagerAdapter)230 private InCallTonePlayer( 231 int toneId, 232 CallAudioManager callAudioManager, 233 CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter, 234 TelecomSystem.SyncRoot lock, 235 ToneGeneratorFactory toneGeneratorFactory, 236 MediaPlayerFactory mediaPlayerFactor, 237 AudioManagerAdapter audioManagerAdapter) { 238 mState = STATE_OFF; 239 mToneId = toneId; 240 mCallAudioManager = callAudioManager; 241 mCallAudioRoutePeripheralAdapter = callAudioRoutePeripheralAdapter; 242 mLock = lock; 243 mToneGenerator = toneGeneratorFactory; 244 mMediaPlayerFactory = mediaPlayerFactor; 245 mAudioManagerAdapter = audioManagerAdapter; 246 } 247 248 /** {@inheritDoc} */ 249 @Override run()250 public void run() { 251 try { 252 synchronized (mSessionLock) { 253 if (mSession != null) { 254 Log.continueSession(mSession, "ICTP.r"); 255 mSession = null; 256 } 257 } 258 Log.d(this, "run(toneId = %s)", mToneId); 259 260 final int toneType; // Passed to ToneGenerator.startTone. 261 final int toneVolume; // Passed to the ToneGenerator constructor. 262 final int toneLengthMillis; 263 final int mediaResourceId; // The resourceId of the tone to play. Used for media-based 264 // tones. 265 266 switch (mToneId) { 267 case TONE_BUSY: 268 // TODO: CDMA-specific tones 269 toneType = ToneGenerator.TONE_SUP_BUSY; 270 toneVolume = RELATIVE_VOLUME_HIPRI; 271 toneLengthMillis = 4000; 272 mediaResourceId = TONE_RESOURCE_ID_UNDEFINED; 273 break; 274 case TONE_CALL_ENDED: 275 // Don't use tone generator 276 toneType = ToneGenerator.TONE_UNKNOWN; 277 toneVolume = RELATIVE_VOLUME_UNDEFINED; 278 toneLengthMillis = 0; 279 280 // Use a tone resource file for a more rich, full-bodied tone experience. 281 mediaResourceId = R.raw.endcall; 282 break; 283 case TONE_OTA_CALL_ENDED: 284 // TODO: fill in 285 throw new IllegalStateException("OTA Call ended NYI."); 286 case TONE_CALL_WAITING: 287 toneType = ToneGenerator.TONE_SUP_CALL_WAITING; 288 toneVolume = RELATIVE_VOLUME_HIPRI; 289 toneLengthMillis = Integer.MAX_VALUE - TIMEOUT_BUFFER_MILLIS; 290 mediaResourceId = TONE_RESOURCE_ID_UNDEFINED; 291 break; 292 case TONE_CDMA_DROP: 293 toneType = ToneGenerator.TONE_CDMA_CALLDROP_LITE; 294 toneVolume = RELATIVE_VOLUME_LOPRI; 295 toneLengthMillis = 375; 296 mediaResourceId = TONE_RESOURCE_ID_UNDEFINED; 297 break; 298 case TONE_CONGESTION: 299 toneType = ToneGenerator.TONE_SUP_CONGESTION; 300 toneVolume = RELATIVE_VOLUME_HIPRI; 301 toneLengthMillis = 4000; 302 mediaResourceId = TONE_RESOURCE_ID_UNDEFINED; 303 break; 304 case TONE_INTERCEPT: 305 toneType = ToneGenerator.TONE_CDMA_ABBR_INTERCEPT; 306 toneVolume = RELATIVE_VOLUME_LOPRI; 307 toneLengthMillis = 500; 308 mediaResourceId = TONE_RESOURCE_ID_UNDEFINED; 309 break; 310 case TONE_OUT_OF_SERVICE: 311 toneType = ToneGenerator.TONE_CDMA_CALLDROP_LITE; 312 toneVolume = RELATIVE_VOLUME_LOPRI; 313 toneLengthMillis = 375; 314 mediaResourceId = TONE_RESOURCE_ID_UNDEFINED; 315 break; 316 case TONE_REDIAL: 317 toneType = ToneGenerator.TONE_CDMA_ALERT_AUTOREDIAL_LITE; 318 toneVolume = RELATIVE_VOLUME_LOPRI; 319 toneLengthMillis = 5000; 320 mediaResourceId = TONE_RESOURCE_ID_UNDEFINED; 321 break; 322 case TONE_REORDER: 323 toneType = ToneGenerator.TONE_CDMA_REORDER; 324 toneVolume = RELATIVE_VOLUME_HIPRI; 325 toneLengthMillis = 4000; 326 mediaResourceId = TONE_RESOURCE_ID_UNDEFINED; 327 break; 328 case TONE_RING_BACK: 329 toneType = ToneGenerator.TONE_SUP_RINGTONE; 330 toneVolume = RELATIVE_VOLUME_HIPRI; 331 toneLengthMillis = Integer.MAX_VALUE - TIMEOUT_BUFFER_MILLIS; 332 mediaResourceId = TONE_RESOURCE_ID_UNDEFINED; 333 break; 334 case TONE_UNOBTAINABLE_NUMBER: 335 toneType = ToneGenerator.TONE_SUP_ERROR; 336 toneVolume = RELATIVE_VOLUME_HIPRI; 337 toneLengthMillis = 4000; 338 mediaResourceId = TONE_RESOURCE_ID_UNDEFINED; 339 break; 340 case TONE_VOICE_PRIVACY: 341 // TODO: fill in. 342 throw new IllegalStateException("Voice privacy tone NYI."); 343 case TONE_VIDEO_UPGRADE: 344 case TONE_RTT_REQUEST: 345 // Similar to the call waiting tone, but does not repeat. 346 toneType = ToneGenerator.TONE_SUP_CALL_WAITING; 347 toneVolume = RELATIVE_VOLUME_HIPRI; 348 toneLengthMillis = 4000; 349 mediaResourceId = TONE_RESOURCE_ID_UNDEFINED; 350 break; 351 case TONE_IN_CALL_QUALITY_NOTIFICATION: 352 // Don't use tone generator 353 toneType = ToneGenerator.TONE_UNKNOWN; 354 toneVolume = RELATIVE_VOLUME_UNDEFINED; 355 toneLengthMillis = 0; 356 357 // Use a tone resource file for a more rich, full-bodied tone experience. 358 mediaResourceId = R.raw.InCallQualityNotification; 359 break; 360 default: 361 throw new IllegalStateException("Bad toneId: " + mToneId); 362 } 363 364 int stream = AudioManager.STREAM_VOICE_CALL; 365 if (mCallAudioRoutePeripheralAdapter.isBluetoothAudioOn()) { 366 stream = AudioManager.STREAM_BLUETOOTH_SCO; 367 } 368 if (toneType != ToneGenerator.TONE_UNKNOWN) { 369 if (stream == AudioManager.STREAM_BLUETOOTH_SCO) { 370 // Override audio stream for BT le device and hearing aid device 371 if (mCallAudioRoutePeripheralAdapter.isLeAudioDeviceOn() 372 || mCallAudioRoutePeripheralAdapter.isHearingAidDeviceOn()) { 373 stream = AudioManager.STREAM_VOICE_CALL; 374 } 375 } 376 playToneGeneratorTone(stream, toneVolume, toneType, toneLengthMillis); 377 } else if (mediaResourceId != TONE_RESOURCE_ID_UNDEFINED) { 378 playMediaTone(stream, mediaResourceId); 379 } 380 } finally { 381 cleanUpTonePlayer(); 382 Log.endSession(); 383 } 384 } 385 386 /** 387 * Play a tone generated by the {@link ToneGenerator}. 388 * @param stream The stream on which the tone will be played. 389 * @param toneVolume The volume of the tone. 390 * @param toneType The type of tone to play. 391 * @param toneLengthMillis How long to play the tone. 392 */ playToneGeneratorTone(int stream, int toneVolume, int toneType, int toneLengthMillis)393 private void playToneGeneratorTone(int stream, int toneVolume, int toneType, 394 int toneLengthMillis) { 395 ToneGenerator toneGenerator = null; 396 try { 397 // If the ToneGenerator creation fails, just continue without it. It is a local audio 398 // signal, and is not as important. 399 try { 400 toneGenerator = mToneGenerator.get(stream, toneVolume); 401 } catch (RuntimeException e) { 402 Log.w(this, "Failed to create ToneGenerator.", e); 403 return; 404 } 405 406 Log.i(this, "playToneGeneratorTone: toneType=%d", toneType); 407 408 mState = STATE_ON; 409 toneGenerator.startTone(toneType); 410 try { 411 Log.v(this, "Starting tone %d...waiting for %d ms.", mToneId, 412 toneLengthMillis + TIMEOUT_BUFFER_MILLIS); 413 if (mPlaybackLatch.await(toneLengthMillis + TIMEOUT_BUFFER_MILLIS, 414 TimeUnit.MILLISECONDS)) { 415 Log.i(this, "playToneGeneratorTone: tone playback stopped."); 416 } 417 } catch (InterruptedException e) { 418 Log.w(this, "playToneGeneratorTone: wait interrupted", e); 419 } 420 // Redundant; don't want anyone re-using at this point. 421 mState = STATE_STOPPED; 422 } finally { 423 if (toneGenerator != null) { 424 toneGenerator.release(); 425 } 426 } 427 } 428 429 /** 430 * Plays an audio-file based media tone. 431 * @param stream The audio stream on which to play the tone. 432 * @param toneResourceId The resource ID of the tone to play. 433 */ playMediaTone(int stream, int toneResourceId)434 private void playMediaTone(int stream, int toneResourceId) { 435 mState = STATE_ON; 436 Log.i(this, "playMediaTone: toneResourceId=%d", toneResourceId); 437 AudioAttributes attributes = new AudioAttributes.Builder() 438 .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION) 439 .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) 440 .setLegacyStreamType(stream) 441 .build(); 442 mToneMediaPlayer = mMediaPlayerFactory.get(toneResourceId, attributes); 443 mToneMediaPlayer.setLooping(false); 444 int durationMillis = mToneMediaPlayer.getDuration(); 445 mToneMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { 446 @Override 447 public void onCompletion(MediaPlayer mp) { 448 Log.i(InCallTonePlayer.this, "playMediaTone: toneResourceId=%d completed.", 449 toneResourceId); 450 mPlaybackLatch.countDown(); 451 } 452 }); 453 454 try { 455 mToneMediaPlayer.start(); 456 // Wait for the tone to stop playing; timeout at 2x the length of the file just to 457 // be on the safe side. Playback can also be stopped via stopTone(). 458 if (mPlaybackLatch.await(durationMillis * 2, TimeUnit.MILLISECONDS)) { 459 Log.i(this, "playMediaTone: tone playback stopped."); 460 } 461 } catch (InterruptedException ie) { 462 Log.e(this, ie, "playMediaTone: tone playback interrupted."); 463 } finally { 464 // Redundant; don't want anyone re-using at this point. 465 mState = STATE_STOPPED; 466 mToneMediaPlayer.release(); 467 mToneMediaPlayer = null; 468 } 469 } 470 471 @VisibleForTesting startTone()472 public boolean startTone() { 473 // Skip playing the end call tone if the volume is silenced. 474 if (mToneId == TONE_CALL_ENDED && !mAudioManagerAdapter.isVolumeOverZero()) { 475 Log.i(this, "startTone: skip end-call tone as device is silenced."); 476 return false; 477 } 478 479 // Tone already done; don't allow re-used 480 if (mState == STATE_STOPPED) { 481 return false; 482 } 483 484 if (sTonesPlaying.incrementAndGet() == 1) { 485 mCallAudioManager.setIsTonePlaying(true); 486 } 487 488 synchronized (mSessionLock) { 489 if (mSession != null) { 490 Log.cancelSubsession(mSession); 491 } 492 mSession = Log.createSubsession(); 493 } 494 495 super.start(); 496 return true; 497 } 498 499 @Override start()500 public void start() { 501 Log.w(this, "Do not call the start method directly; use startTone instead."); 502 } 503 504 /** 505 * Stops the tone. 506 */ 507 @VisibleForTesting stopTone()508 public void stopTone() { 509 Log.i(this, "stopTone: Stopping the tone %d.", mToneId); 510 // Notify the playback to end early. 511 mPlaybackLatch.countDown(); 512 513 mState = STATE_STOPPED; 514 } 515 516 @VisibleForTesting cleanup()517 public void cleanup() { 518 sTonesPlaying.set(0); 519 } 520 cleanUpTonePlayer()521 private void cleanUpTonePlayer() { 522 Log.d(this, "cleanUpTonePlayer(): posting cleanup"); 523 // Release focus on the main thread. 524 mMainThreadHandler.post(new Runnable("ICTP.cUTP", mLock) { 525 @Override 526 public void loggedRun() { 527 int newToneCount = sTonesPlaying.updateAndGet( t -> Math.min(0, t--)); 528 529 if (newToneCount == 0) { 530 Log.i(InCallTonePlayer.this, 531 "cleanUpTonePlayer(): tonesPlaying=%d, tone completed", newToneCount); 532 if (mCallAudioManager != null) { 533 mCallAudioManager.setIsTonePlaying(false); 534 } else { 535 Log.w(InCallTonePlayer.this, 536 "cleanUpTonePlayer(): mCallAudioManager is null!"); 537 } 538 } else { 539 Log.i(InCallTonePlayer.this, 540 "cleanUpTonePlayer(): tonesPlaying=%d; still playing", newToneCount); 541 } 542 } 543 }.prepare()); 544 } 545 } 546