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 Factory(CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter, TelecomSystem.SyncRoot lock, ToneGeneratorFactory toneGeneratorFactory, MediaPlayerFactory mediaPlayerFactory, AudioManagerAdapter audioManagerAdapter)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 setCallAudioManager(CallAudioManager callAudioManager)67 public void setCallAudioManager(CallAudioManager callAudioManager) { 68 mCallAudioManager = callAudioManager; 69 } 70 createPlayer(int tone)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 { get(int streamType, int volume)79 ToneGenerator get (int streamType, int volume); 80 } 81 82 public interface MediaPlayerAdapter { setLooping(boolean isLooping)83 void setLooping(boolean isLooping); setOnCompletionListener(MediaPlayer.OnCompletionListener listener)84 void setOnCompletionListener(MediaPlayer.OnCompletionListener listener); start()85 void start(); release()86 void release(); getDuration()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 */ MediaPlayerAdapterImpl(@ullable MediaPlayer mediaPlayer)100 public MediaPlayerAdapterImpl(@Nullable MediaPlayer mediaPlayer) { 101 mMediaPlayer = mediaPlayer; 102 } 103 104 @Override setLooping(boolean isLooping)105 public void setLooping(boolean isLooping) { 106 if (mMediaPlayer != null) { 107 mMediaPlayer.setLooping(isLooping); 108 } 109 } 110 111 @Override setOnCompletionListener(MediaPlayer.OnCompletionListener listener)112 public void setOnCompletionListener(MediaPlayer.OnCompletionListener listener) { 113 if (mMediaPlayer != null) { 114 mMediaPlayer.setOnCompletionListener(listener); 115 } 116 } 117 118 @Override start()119 public void start() { 120 if (mMediaPlayer != null) { 121 mMediaPlayer.start(); 122 } 123 } 124 125 @Override release()126 public void release() { 127 if (mMediaPlayer != null) { 128 mMediaPlayer.release(); 129 } 130 } 131 132 @Override getDuration()133 public int getDuration() { 134 if (mMediaPlayer != null) { 135 return mMediaPlayer.getDuration(); 136 } 137 return 0; 138 } 139 } 140 141 public interface MediaPlayerFactory { get(int resourceId, AudioAttributes attributes)142 MediaPlayerAdapter get (int resourceId, AudioAttributes attributes); 143 } 144 145 public interface AudioManagerAdapter { isVolumeOverZero()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 */ InCallTonePlayer( int toneId, CallAudioManager callAudioManager, CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter, TelecomSystem.SyncRoot lock, ToneGeneratorFactory toneGeneratorFactory, MediaPlayerFactory mediaPlayerFactor, AudioManagerAdapter audioManagerAdapter)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 run()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 */ playToneGeneratorTone(int stream, int toneVolume, int toneType, int toneLengthMillis)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 */ playMediaTone(int stream, int toneResourceId)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 startTone()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 start()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 stopTone()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 cleanup()506 public void cleanup() { 507 sTonesPlaying = 0; 508 } 509 cleanUpTonePlayer()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