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