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