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