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 166 private static final int TONE_RESOURCE_ID_UNDEFINED = -1; 167 168 private static final int RELATIVE_VOLUME_EMERGENCY = 100; 169 private static final int RELATIVE_VOLUME_HIPRI = 80; 170 private static final int RELATIVE_VOLUME_LOPRI = 50; 171 private static final int RELATIVE_VOLUME_UNDEFINED = -1; 172 173 // Buffer time (in msec) to add on to the tone timeout value. Needed mainly when the timeout 174 // value for a tone is exact duration of the tone itself. 175 private static final int TIMEOUT_BUFFER_MILLIS = 20; 176 177 // The tone state. 178 private static final int STATE_OFF = 0; 179 private static final int STATE_ON = 1; 180 private static final int STATE_STOPPED = 2; 181 182 /** 183 * Keeps count of the number of actively playing tones so that we can notify CallAudioManager 184 * when we need focus and when it can be release. This should only be manipulated from the main 185 * thread. 186 */ 187 private static int sTonesPlaying = 0; 188 189 private final CallAudioManager mCallAudioManager; 190 private final CallAudioRoutePeripheralAdapter mCallAudioRoutePeripheralAdapter; 191 192 private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper()); 193 194 /** The ID of the tone to play. */ 195 private final int mToneId; 196 197 /** Current state of the tone player. */ 198 private int mState; 199 200 /** For tones which are not generated using ToneGenerator. */ 201 private MediaPlayerAdapter mToneMediaPlayer = null; 202 203 /** Telecom lock object. */ 204 private final TelecomSystem.SyncRoot mLock; 205 206 private Session mSession; 207 private final Object mSessionLock = new Object(); 208 209 private final ToneGeneratorFactory mToneGenerator; 210 private final MediaPlayerFactory mMediaPlayerFactory; 211 private final AudioManagerAdapter mAudioManagerAdapter; 212 213 /** 214 * Initializes the tone player. Private; use the {@link Factory} to create tone players. 215 * 216 * @param toneId ID of the tone to play, see TONE_* constants. 217 */ InCallTonePlayer( int toneId, CallAudioManager callAudioManager, CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter, TelecomSystem.SyncRoot lock, ToneGeneratorFactory toneGeneratorFactory, MediaPlayerFactory mediaPlayerFactor, AudioManagerAdapter audioManagerAdapter)218 private InCallTonePlayer( 219 int toneId, 220 CallAudioManager callAudioManager, 221 CallAudioRoutePeripheralAdapter callAudioRoutePeripheralAdapter, 222 TelecomSystem.SyncRoot lock, 223 ToneGeneratorFactory toneGeneratorFactory, 224 MediaPlayerFactory mediaPlayerFactor, 225 AudioManagerAdapter audioManagerAdapter) { 226 mState = STATE_OFF; 227 mToneId = toneId; 228 mCallAudioManager = callAudioManager; 229 mCallAudioRoutePeripheralAdapter = callAudioRoutePeripheralAdapter; 230 mLock = lock; 231 mToneGenerator = toneGeneratorFactory; 232 mMediaPlayerFactory = mediaPlayerFactor; 233 mAudioManagerAdapter = audioManagerAdapter; 234 } 235 236 /** {@inheritDoc} */ 237 @Override run()238 public void run() { 239 try { 240 synchronized (mSessionLock) { 241 if (mSession != null) { 242 Log.continueSession(mSession, "ICTP.r"); 243 mSession = null; 244 } 245 } 246 Log.d(this, "run(toneId = %s)", mToneId); 247 248 final int toneType; // Passed to ToneGenerator.startTone. 249 final int toneVolume; // Passed to the ToneGenerator constructor. 250 final int toneLengthMillis; 251 final int mediaResourceId; // The resourceId of the tone to play. Used for media-based 252 // tones. 253 254 switch (mToneId) { 255 case TONE_BUSY: 256 // TODO: CDMA-specific tones 257 toneType = ToneGenerator.TONE_SUP_BUSY; 258 toneVolume = RELATIVE_VOLUME_HIPRI; 259 toneLengthMillis = 4000; 260 mediaResourceId = TONE_RESOURCE_ID_UNDEFINED; 261 break; 262 case TONE_CALL_ENDED: 263 // Don't use tone generator 264 toneType = ToneGenerator.TONE_UNKNOWN; 265 toneVolume = RELATIVE_VOLUME_UNDEFINED; 266 toneLengthMillis = 0; 267 268 // Use a tone resource file for a more rich, full-bodied tone experience. 269 mediaResourceId = R.raw.endcall; 270 break; 271 case TONE_OTA_CALL_ENDED: 272 // TODO: fill in 273 throw new IllegalStateException("OTA Call ended NYI."); 274 case TONE_CALL_WAITING: 275 toneType = ToneGenerator.TONE_SUP_CALL_WAITING; 276 toneVolume = RELATIVE_VOLUME_HIPRI; 277 toneLengthMillis = Integer.MAX_VALUE - TIMEOUT_BUFFER_MILLIS; 278 mediaResourceId = TONE_RESOURCE_ID_UNDEFINED; 279 break; 280 case TONE_CDMA_DROP: 281 toneType = ToneGenerator.TONE_CDMA_CALLDROP_LITE; 282 toneVolume = RELATIVE_VOLUME_LOPRI; 283 toneLengthMillis = 375; 284 mediaResourceId = TONE_RESOURCE_ID_UNDEFINED; 285 break; 286 case TONE_CONGESTION: 287 toneType = ToneGenerator.TONE_SUP_CONGESTION; 288 toneVolume = RELATIVE_VOLUME_HIPRI; 289 toneLengthMillis = 4000; 290 mediaResourceId = TONE_RESOURCE_ID_UNDEFINED; 291 break; 292 case TONE_INTERCEPT: 293 toneType = ToneGenerator.TONE_CDMA_ABBR_INTERCEPT; 294 toneVolume = RELATIVE_VOLUME_LOPRI; 295 toneLengthMillis = 500; 296 mediaResourceId = TONE_RESOURCE_ID_UNDEFINED; 297 break; 298 case TONE_OUT_OF_SERVICE: 299 toneType = ToneGenerator.TONE_CDMA_CALLDROP_LITE; 300 toneVolume = RELATIVE_VOLUME_LOPRI; 301 toneLengthMillis = 375; 302 mediaResourceId = TONE_RESOURCE_ID_UNDEFINED; 303 break; 304 case TONE_REDIAL: 305 toneType = ToneGenerator.TONE_CDMA_ALERT_AUTOREDIAL_LITE; 306 toneVolume = RELATIVE_VOLUME_LOPRI; 307 toneLengthMillis = 5000; 308 mediaResourceId = TONE_RESOURCE_ID_UNDEFINED; 309 break; 310 case TONE_REORDER: 311 toneType = ToneGenerator.TONE_CDMA_REORDER; 312 toneVolume = RELATIVE_VOLUME_HIPRI; 313 toneLengthMillis = 4000; 314 mediaResourceId = TONE_RESOURCE_ID_UNDEFINED; 315 break; 316 case TONE_RING_BACK: 317 toneType = ToneGenerator.TONE_SUP_RINGTONE; 318 toneVolume = RELATIVE_VOLUME_HIPRI; 319 toneLengthMillis = Integer.MAX_VALUE - TIMEOUT_BUFFER_MILLIS; 320 mediaResourceId = TONE_RESOURCE_ID_UNDEFINED; 321 break; 322 case TONE_UNOBTAINABLE_NUMBER: 323 toneType = ToneGenerator.TONE_SUP_ERROR; 324 toneVolume = RELATIVE_VOLUME_HIPRI; 325 toneLengthMillis = 4000; 326 mediaResourceId = TONE_RESOURCE_ID_UNDEFINED; 327 break; 328 case TONE_VOICE_PRIVACY: 329 // TODO: fill in. 330 throw new IllegalStateException("Voice privacy tone NYI."); 331 case TONE_VIDEO_UPGRADE: 332 // Similar to the call waiting tone, but does not repeat. 333 toneType = ToneGenerator.TONE_SUP_CALL_WAITING; 334 toneVolume = RELATIVE_VOLUME_HIPRI; 335 toneLengthMillis = 4000; 336 mediaResourceId = TONE_RESOURCE_ID_UNDEFINED; 337 break; 338 default: 339 throw new IllegalStateException("Bad toneId: " + mToneId); 340 } 341 342 int stream = AudioManager.STREAM_VOICE_CALL; 343 if (mCallAudioRoutePeripheralAdapter.isBluetoothAudioOn()) { 344 stream = AudioManager.STREAM_BLUETOOTH_SCO; 345 } 346 347 if (toneType != ToneGenerator.TONE_UNKNOWN) { 348 playToneGeneratorTone(stream, toneVolume, toneType, toneLengthMillis); 349 } else if (mediaResourceId != TONE_RESOURCE_ID_UNDEFINED) { 350 playMediaTone(stream, mediaResourceId); 351 } 352 } finally { 353 cleanUpTonePlayer(); 354 Log.endSession(); 355 } 356 } 357 358 /** 359 * Play a tone generated by the {@link ToneGenerator}. 360 * @param stream The stream on which the tone will be played. 361 * @param toneVolume The volume of the tone. 362 * @param toneType The type of tone to play. 363 * @param toneLengthMillis How long to play the tone. 364 */ playToneGeneratorTone(int stream, int toneVolume, int toneType, int toneLengthMillis)365 private void playToneGeneratorTone(int stream, int toneVolume, int toneType, 366 int toneLengthMillis) { 367 ToneGenerator toneGenerator = null; 368 try { 369 // If the ToneGenerator creation fails, just continue without it. It is a local audio 370 // signal, and is not as important. 371 try { 372 toneGenerator = mToneGenerator.get(stream, toneVolume); 373 } catch (RuntimeException e) { 374 Log.w(this, "Failed to create ToneGenerator.", e); 375 return; 376 } 377 378 Log.i(this, "playToneGeneratorTone: toneType=%d", toneType); 379 // TODO: Certain CDMA tones need to check the ringer-volume state before 380 // playing. See CallNotifier.InCallTonePlayer. 381 382 // TODO: Some tones play through the end of a call so we need to inform 383 // CallAudioManager that we want focus the same way that Ringer does. 384 385 synchronized (this) { 386 if (mState != STATE_STOPPED) { 387 mState = STATE_ON; 388 toneGenerator.startTone(toneType); 389 try { 390 Log.v(this, "Starting tone %d...waiting for %d ms.", mToneId, 391 toneLengthMillis + TIMEOUT_BUFFER_MILLIS); 392 wait(toneLengthMillis + TIMEOUT_BUFFER_MILLIS); 393 } catch (InterruptedException e) { 394 Log.w(this, "wait interrupted", e); 395 } 396 } 397 } 398 mState = STATE_OFF; 399 } finally { 400 if (toneGenerator != null) { 401 toneGenerator.release(); 402 } 403 } 404 } 405 406 /** 407 * Plays an audio-file based media tone. 408 * @param stream The audio stream on which to play the tone. 409 * @param toneResourceId The resource ID of the tone to play. 410 */ playMediaTone(int stream, int toneResourceId)411 private void playMediaTone(int stream, int toneResourceId) { 412 synchronized (this) { 413 if (mState != STATE_STOPPED) { 414 mState = STATE_ON; 415 } 416 Log.i(this, "playMediaTone: toneResourceId=%d", toneResourceId); 417 AudioAttributes attributes = new AudioAttributes.Builder() 418 .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION) 419 .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) 420 .setLegacyStreamType(stream) 421 .build(); 422 mToneMediaPlayer = mMediaPlayerFactory.get(toneResourceId, attributes); 423 mToneMediaPlayer.setLooping(false); 424 int durationMillis = mToneMediaPlayer.getDuration(); 425 final CountDownLatch toneLatch = new CountDownLatch(1); 426 mToneMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { 427 @Override 428 public void onCompletion(MediaPlayer mp) { 429 Log.i(this, "playMediaTone: toneResourceId=%d completed.", toneResourceId); 430 synchronized (this) { 431 mState = STATE_OFF; 432 } 433 mToneMediaPlayer.release(); 434 mToneMediaPlayer = null; 435 toneLatch.countDown(); 436 } 437 }); 438 mToneMediaPlayer.start(); 439 try { 440 // Wait for the tone to stop playing; timeout at 2x the length of the file just to 441 // be on the safe side. 442 toneLatch.await(durationMillis * 2, TimeUnit.MILLISECONDS); 443 } catch (InterruptedException ie) { 444 Log.e(this, ie, "playMediaTone: tone playback interrupted."); 445 } 446 } 447 448 } 449 450 @VisibleForTesting startTone()451 public boolean startTone() { 452 // Skip playing the end call tone if the volume is silenced. 453 if (mToneId == TONE_CALL_ENDED && !mAudioManagerAdapter.isVolumeOverZero()) { 454 Log.i(this, "startTone: skip end-call tone as device is silenced."); 455 return false; 456 } 457 458 sTonesPlaying++; 459 if (sTonesPlaying == 1) { 460 mCallAudioManager.setIsTonePlaying(true); 461 } 462 463 synchronized (mSessionLock) { 464 if (mSession != null) { 465 Log.cancelSubsession(mSession); 466 } 467 mSession = Log.createSubsession(); 468 } 469 470 super.start(); 471 return true; 472 } 473 474 @Override start()475 public void start() { 476 Log.w(this, "Do not call the start method directly; use startTone instead."); 477 } 478 479 /** 480 * Stops the tone. 481 */ 482 @VisibleForTesting stopTone()483 public void stopTone() { 484 synchronized (this) { 485 if (mState == STATE_ON) { 486 Log.d(this, "Stopping the tone %d.", mToneId); 487 notify(); 488 } 489 mState = STATE_STOPPED; 490 } 491 } 492 493 @VisibleForTesting cleanup()494 public void cleanup() { 495 sTonesPlaying = 0; 496 } 497 cleanUpTonePlayer()498 private void cleanUpTonePlayer() { 499 // Release focus on the main thread. 500 mMainThreadHandler.post(new Runnable("ICTP.cUTP", mLock) { 501 @Override 502 public void loggedRun() { 503 if (sTonesPlaying == 0) { 504 Log.wtf(this, "Over-releasing focus for tone player."); 505 } else if (--sTonesPlaying == 0 && mCallAudioManager != null) { 506 mCallAudioManager.setIsTonePlaying(false); 507 } 508 } 509 }.prepare()); 510 } 511 } 512