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