Home | History | Annotate | Download | only in telecom
      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.media.AudioManager;
     20 import android.media.ToneGenerator;
     21 import android.os.Handler;
     22 import android.os.Looper;
     23 
     24 /**
     25  * Play a call-related tone (ringback, busy signal, etc.) through ToneGenerator. To use, create an
     26  * instance using InCallTonePlayer.Factory (passing in the TONE_* constant for the tone you want)
     27  * and start() it. Implemented on top of {@link Thread} so that the tone plays in its own thread.
     28  */
     29 public final class InCallTonePlayer extends Thread {
     30 
     31     /**
     32      * Factory used to create InCallTonePlayers. Exists to aid with testing mocks.
     33      */
     34     public static class Factory {
     35         private final CallAudioManager mCallAudioManager;
     36 
     37         Factory(CallAudioManager callAudioManager) {
     38             mCallAudioManager = callAudioManager;
     39         }
     40 
     41         InCallTonePlayer createPlayer(int tone) {
     42             return new InCallTonePlayer(tone, mCallAudioManager);
     43         }
     44     }
     45 
     46     // The possible tones that we can play.
     47     public static final int TONE_INVALID = 0;
     48     public static final int TONE_BUSY = 1;
     49     public static final int TONE_CALL_ENDED = 2;
     50     public static final int TONE_OTA_CALL_ENDED = 3;
     51     public static final int TONE_CALL_WAITING = 4;
     52     public static final int TONE_CDMA_DROP = 5;
     53     public static final int TONE_CONGESTION = 6;
     54     public static final int TONE_INTERCEPT = 7;
     55     public static final int TONE_OUT_OF_SERVICE = 8;
     56     public static final int TONE_REDIAL = 9;
     57     public static final int TONE_REORDER = 10;
     58     public static final int TONE_RING_BACK = 11;
     59     public static final int TONE_UNOBTAINABLE_NUMBER = 12;
     60     public static final int TONE_VOICE_PRIVACY = 13;
     61 
     62     private static final int RELATIVE_VOLUME_EMERGENCY = 100;
     63     private static final int RELATIVE_VOLUME_HIPRI = 80;
     64     private static final int RELATIVE_VOLUME_LOPRI = 50;
     65 
     66     // Buffer time (in msec) to add on to the tone timeout value. Needed mainly when the timeout
     67     // value for a tone is exact duration of the tone itself.
     68     private static final int TIMEOUT_BUFFER_MILLIS = 20;
     69 
     70     // The tone state.
     71     private static final int STATE_OFF = 0;
     72     private static final int STATE_ON = 1;
     73     private static final int STATE_STOPPED = 2;
     74 
     75     /**
     76      * Keeps count of the number of actively playing tones so that we can notify CallAudioManager
     77      * when we need focus and when it can be release. This should only be manipulated from the main
     78      * thread.
     79      */
     80     private static int sTonesPlaying = 0;
     81 
     82     private final CallAudioManager mCallAudioManager;
     83 
     84     private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
     85 
     86     /** The ID of the tone to play. */
     87     private final int mToneId;
     88 
     89     /** Current state of the tone player. */
     90     private int mState;
     91 
     92     /**
     93      * Initializes the tone player. Private; use the {@link Factory} to create tone players.
     94      *
     95      * @param toneId ID of the tone to play, see TONE_* constants.
     96      */
     97     private InCallTonePlayer(int toneId, CallAudioManager callAudioManager) {
     98         mState = STATE_OFF;
     99         mToneId = toneId;
    100         mCallAudioManager = callAudioManager;
    101     }
    102 
    103     /** {@inheritDoc} */
    104     @Override
    105     public void run() {
    106         ToneGenerator toneGenerator = null;
    107         try {
    108             Log.d(this, "run(toneId = %s)", mToneId);
    109 
    110             final int toneType;  // Passed to ToneGenerator.startTone.
    111             final int toneVolume;  // Passed to the ToneGenerator constructor.
    112             final int toneLengthMillis;
    113 
    114             switch (mToneId) {
    115                 case TONE_BUSY:
    116                     // TODO: CDMA-specific tones
    117                     toneType = ToneGenerator.TONE_SUP_BUSY;
    118                     toneVolume = RELATIVE_VOLUME_HIPRI;
    119                     toneLengthMillis = 4000;
    120                     break;
    121                 case TONE_CALL_ENDED:
    122                     toneType = ToneGenerator.TONE_PROP_PROMPT;
    123                     toneVolume = RELATIVE_VOLUME_HIPRI;
    124                     toneLengthMillis = 200;
    125                     break;
    126                 case TONE_OTA_CALL_ENDED:
    127                     // TODO: fill in
    128                     throw new IllegalStateException("OTA Call ended NYI.");
    129                 case TONE_CALL_WAITING:
    130                     toneType = ToneGenerator.TONE_SUP_CALL_WAITING;
    131                     toneVolume = RELATIVE_VOLUME_HIPRI;
    132                     toneLengthMillis = Integer.MAX_VALUE - TIMEOUT_BUFFER_MILLIS;
    133                     break;
    134                 case TONE_CDMA_DROP:
    135                     toneType = ToneGenerator.TONE_CDMA_CALLDROP_LITE;
    136                     toneVolume = RELATIVE_VOLUME_LOPRI;
    137                     toneLengthMillis = 375;
    138                     break;
    139                 case TONE_CONGESTION:
    140                     toneType = ToneGenerator.TONE_SUP_CONGESTION;
    141                     toneVolume = RELATIVE_VOLUME_HIPRI;
    142                     toneLengthMillis = 4000;
    143                     break;
    144                 case TONE_INTERCEPT:
    145                     toneType = ToneGenerator.TONE_CDMA_ABBR_INTERCEPT;
    146                     toneVolume = RELATIVE_VOLUME_LOPRI;
    147                     toneLengthMillis = 500;
    148                     break;
    149                 case TONE_OUT_OF_SERVICE:
    150                     toneType = ToneGenerator.TONE_CDMA_CALLDROP_LITE;
    151                     toneVolume = RELATIVE_VOLUME_LOPRI;
    152                     toneLengthMillis = 375;
    153                     break;
    154                 case TONE_REDIAL:
    155                     toneType = ToneGenerator.TONE_CDMA_ALERT_AUTOREDIAL_LITE;
    156                     toneVolume = RELATIVE_VOLUME_LOPRI;
    157                     toneLengthMillis = 5000;
    158                     break;
    159                 case TONE_REORDER:
    160                     toneType = ToneGenerator.TONE_CDMA_REORDER;
    161                     toneVolume = RELATIVE_VOLUME_HIPRI;
    162                     toneLengthMillis = 4000;
    163                     break;
    164                 case TONE_RING_BACK:
    165                     toneType = ToneGenerator.TONE_SUP_RINGTONE;
    166                     toneVolume = RELATIVE_VOLUME_HIPRI;
    167                     toneLengthMillis = Integer.MAX_VALUE - TIMEOUT_BUFFER_MILLIS;
    168                     break;
    169                 case TONE_UNOBTAINABLE_NUMBER:
    170                     toneType = ToneGenerator.TONE_SUP_ERROR;
    171                     toneVolume = RELATIVE_VOLUME_HIPRI;
    172                     toneLengthMillis = 4000;
    173                     break;
    174                 case TONE_VOICE_PRIVACY:
    175                     // TODO: fill in.
    176                     throw new IllegalStateException("Voice privacy tone NYI.");
    177                 default:
    178                     throw new IllegalStateException("Bad toneId: " + mToneId);
    179             }
    180 
    181             int stream = AudioManager.STREAM_VOICE_CALL;
    182             if (mCallAudioManager.isBluetoothAudioOn()) {
    183                 stream = AudioManager.STREAM_BLUETOOTH_SCO;
    184             }
    185 
    186             // If the ToneGenerator creation fails, just continue without it. It is a local audio
    187             // signal, and is not as important.
    188             try {
    189                 Log.v(this, "Creating generator");
    190                 toneGenerator = new ToneGenerator(stream, toneVolume);
    191             } catch (RuntimeException e) {
    192                 Log.w(this, "Failed to create ToneGenerator.", e);
    193                 return;
    194             }
    195 
    196             // TODO: Certain CDMA tones need to check the ringer-volume state before
    197             // playing. See CallNotifier.InCallTonePlayer.
    198 
    199             // TODO: Some tones play through the end of a call so we need to inform
    200             // CallAudioManager that we want focus the same way that Ringer does.
    201 
    202             synchronized (this) {
    203                 if (mState != STATE_STOPPED) {
    204                     mState = STATE_ON;
    205                     toneGenerator.startTone(toneType);
    206                     try {
    207                         Log.v(this, "Starting tone %d...waiting for %d ms.", mToneId,
    208                                 toneLengthMillis + TIMEOUT_BUFFER_MILLIS);
    209                         wait(toneLengthMillis + TIMEOUT_BUFFER_MILLIS);
    210                     } catch (InterruptedException e) {
    211                         Log.w(this, "wait interrupted", e);
    212                     }
    213                 }
    214             }
    215             mState = STATE_OFF;
    216         } finally {
    217             if (toneGenerator != null) {
    218                 toneGenerator.release();
    219             }
    220             cleanUpTonePlayer();
    221         }
    222     }
    223 
    224     void startTone() {
    225         ThreadUtil.checkOnMainThread();
    226 
    227         sTonesPlaying++;
    228         if (sTonesPlaying == 1) {
    229             mCallAudioManager.setIsTonePlaying(true);
    230         }
    231 
    232         start();
    233     }
    234 
    235     /**
    236      * Stops the tone.
    237      */
    238     void stopTone() {
    239         synchronized (this) {
    240             if (mState == STATE_ON) {
    241                 Log.d(this, "Stopping the tone %d.", mToneId);
    242                 notify();
    243             }
    244             mState = STATE_STOPPED;
    245         }
    246     }
    247 
    248     private void cleanUpTonePlayer() {
    249         // Release focus on the main thread.
    250         mMainThreadHandler.post(new Runnable() {
    251             @Override public void run() {
    252                 if (sTonesPlaying == 0) {
    253                     Log.wtf(this, "Over-releasing focus for tone player.");
    254                 } else if (--sTonesPlaying == 0) {
    255                     mCallAudioManager.setIsTonePlaying(false);
    256                 }
    257             }
    258         });
    259     }
    260 }
    261