Home | History | Annotate | Download | only in telecom
      1 /*
      2  * Copyright (C) 2015 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 
     20 import android.app.ActivityManagerNative;
     21 import android.app.NotificationManager;
     22 import android.content.BroadcastReceiver;
     23 import android.content.Context;
     24 import android.content.Intent;
     25 import android.content.IntentFilter;
     26 import android.content.pm.UserInfo;
     27 import android.media.AudioManager;
     28 import android.media.IAudioService;
     29 import android.os.Binder;
     30 import android.os.Message;
     31 import android.os.RemoteException;
     32 import android.os.SystemProperties;
     33 import android.os.UserHandle;
     34 import android.telecom.CallAudioState;
     35 import android.util.SparseArray;
     36 
     37 import com.android.internal.util.IState;
     38 import com.android.internal.util.State;
     39 import com.android.internal.util.StateMachine;
     40 
     41 import java.util.HashMap;
     42 
     43 /**
     44  * This class describes the available routes of a call as a state machine.
     45  * Transitions are caused solely by the commands sent as messages. Possible values for msg.what
     46  * are defined as event constants in this file.
     47  *
     48  * The eight states are all instances of the abstract base class, {@link AudioState}. Each state
     49  * is a combination of one of the four audio routes (earpiece, wired headset, bluetooth, and
     50  * speakerphone) and audio focus status (active or quiescent).
     51  *
     52  * Messages are processed first by the processMessage method in the base class, AudioState.
     53  * Any messages not completely handled by AudioState are further processed by the same method in
     54  * the route-specific abstract classes: {@link EarpieceRoute}, {@link HeadsetRoute},
     55  * {@link BluetoothRoute}, and {@link SpeakerRoute}. Finally, messages that are not handled at
     56  * this level are then processed by the classes corresponding to the state instances themselves.
     57  *
     58  * There are several variables carrying additional state. These include:
     59  * mAvailableRoutes: A bitmask describing which audio routes are available
     60  * mWasOnSpeaker: A boolean indicating whether we should switch to speakerphone after disconnecting
     61  *     from a wired headset
     62  * mIsMuted: a boolean indicating whether the audio is muted
     63  */
     64 public class CallAudioRouteStateMachine extends StateMachine {
     65     /** Direct the audio stream through the device's earpiece. */
     66     public static final int ROUTE_EARPIECE      = CallAudioState.ROUTE_EARPIECE;
     67 
     68     /** Direct the audio stream through Bluetooth. */
     69     public static final int ROUTE_BLUETOOTH     = CallAudioState.ROUTE_BLUETOOTH;
     70 
     71     /** Direct the audio stream through a wired headset. */
     72     public static final int ROUTE_WIRED_HEADSET = CallAudioState.ROUTE_WIRED_HEADSET;
     73 
     74     /** Direct the audio stream through the device's speakerphone. */
     75     public static final int ROUTE_SPEAKER       = CallAudioState.ROUTE_SPEAKER;
     76 
     77     /** Valid values for msg.what */
     78     public static final int CONNECT_WIRED_HEADSET = 1;
     79     public static final int DISCONNECT_WIRED_HEADSET = 2;
     80     public static final int CONNECT_BLUETOOTH = 3;
     81     public static final int DISCONNECT_BLUETOOTH = 4;
     82     public static final int CONNECT_DOCK = 5;
     83     public static final int DISCONNECT_DOCK = 6;
     84 
     85     public static final int SWITCH_EARPIECE = 1001;
     86     public static final int SWITCH_BLUETOOTH = 1002;
     87     public static final int SWITCH_HEADSET = 1003;
     88     public static final int SWITCH_SPEAKER = 1004;
     89     // Wired headset, earpiece, or speakerphone, in that order of precedence.
     90     public static final int SWITCH_BASELINE_ROUTE = 1005;
     91     public static final int BT_AUDIO_DISCONNECT = 1006;
     92 
     93     public static final int USER_SWITCH_EARPIECE = 1101;
     94     public static final int USER_SWITCH_BLUETOOTH = 1102;
     95     public static final int USER_SWITCH_HEADSET = 1103;
     96     public static final int USER_SWITCH_SPEAKER = 1104;
     97     public static final int USER_SWITCH_BASELINE_ROUTE = 1105;
     98 
     99     public static final int UPDATE_SYSTEM_AUDIO_ROUTE = 1201;
    100 
    101     public static final int MUTE_ON = 3001;
    102     public static final int MUTE_OFF = 3002;
    103     public static final int TOGGLE_MUTE = 3003;
    104 
    105     public static final int SWITCH_FOCUS = 4001;
    106 
    107     // Used in testing to execute verifications. Not compatible with subsessions.
    108     public static final int RUN_RUNNABLE = 9001;
    109 
    110     /** Valid values for mAudioFocusType */
    111     public static final int NO_FOCUS = 1;
    112     public static final int ACTIVE_FOCUS = 2;
    113     public static final int RINGING_FOCUS = 3;
    114 
    115     private static final SparseArray<String> AUDIO_ROUTE_TO_LOG_EVENT = new SparseArray<String>() {{
    116         put(CallAudioState.ROUTE_BLUETOOTH, Log.Events.AUDIO_ROUTE_BT);
    117         put(CallAudioState.ROUTE_EARPIECE, Log.Events.AUDIO_ROUTE_EARPIECE);
    118         put(CallAudioState.ROUTE_SPEAKER, Log.Events.AUDIO_ROUTE_SPEAKER);
    119         put(CallAudioState.ROUTE_WIRED_HEADSET, Log.Events.AUDIO_ROUTE_HEADSET);
    120     }};
    121 
    122     private static final SparseArray<String> MESSAGE_CODE_TO_NAME = new SparseArray<String>() {{
    123         put(CONNECT_WIRED_HEADSET, "CONNECT_WIRED_HEADSET");
    124         put(DISCONNECT_WIRED_HEADSET, "DISCONNECT_WIRED_HEADSET");
    125         put(CONNECT_BLUETOOTH, "CONNECT_BLUETOOTH");
    126         put(DISCONNECT_BLUETOOTH, "DISCONNECT_BLUETOOTH");
    127         put(CONNECT_DOCK, "CONNECT_DOCK");
    128         put(DISCONNECT_DOCK, "DISCONNECT_DOCK");
    129 
    130         put(SWITCH_EARPIECE, "SWITCH_EARPIECE");
    131         put(SWITCH_BLUETOOTH, "SWITCH_BLUETOOTH");
    132         put(SWITCH_HEADSET, "SWITCH_HEADSET");
    133         put(SWITCH_SPEAKER, "SWITCH_SPEAKER");
    134         put(SWITCH_BASELINE_ROUTE, "SWITCH_BASELINE_ROUTE");
    135         put(BT_AUDIO_DISCONNECT, "BT_AUDIO_DISCONNECT");
    136 
    137         put(USER_SWITCH_EARPIECE, "USER_SWITCH_EARPIECE");
    138         put(USER_SWITCH_BLUETOOTH, "USER_SWITCH_BLUETOOTH");
    139         put(USER_SWITCH_HEADSET, "USER_SWITCH_HEADSET");
    140         put(USER_SWITCH_SPEAKER, "USER_SWITCH_SPEAKER");
    141         put(USER_SWITCH_BASELINE_ROUTE, "USER_SWITCH_BASELINE_ROUTE");
    142 
    143         put(UPDATE_SYSTEM_AUDIO_ROUTE, "UPDATE_SYSTEM_AUDIO_ROUTE");
    144 
    145         put(MUTE_ON, "MUTE_ON");
    146         put(MUTE_OFF, "MUTE_OFF");
    147         put(TOGGLE_MUTE, "TOGGLE_MUTE");
    148 
    149         put(SWITCH_FOCUS, "SWITCH_FOCUS");
    150 
    151         put(RUN_RUNNABLE, "RUN_RUNNABLE");
    152     }};
    153 
    154     /**
    155      * BroadcastReceiver used to track changes in the notification interruption filter.  This
    156      * ensures changes to the notification interruption filter made by the user during a call are
    157      * respected when restoring the notification interruption filter state.
    158      */
    159     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
    160         @Override
    161         public void onReceive(Context context, Intent intent) {
    162             Log.startSession("CARSM.oR");
    163             try {
    164                 String action = intent.getAction();
    165 
    166                 if (action.equals(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED)) {
    167                     if (mAreNotificationSuppressed) {
    168                         // If we've already set the interruption filter, and the user changes it to
    169                         // something other than INTERRUPTION_FILTER_ALARMS, assume we will no longer
    170                         // try to change it back if the audio route changes.
    171                         mAreNotificationSuppressed =
    172                                 mInterruptionFilterProxy.getCurrentInterruptionFilter()
    173                                         == NotificationManager.INTERRUPTION_FILTER_ALARMS;
    174                     }
    175                 }
    176             } finally {
    177                 Log.endSession();
    178             }
    179         }
    180     };
    181 
    182     private static final String ACTIVE_EARPIECE_ROUTE_NAME = "ActiveEarpieceRoute";
    183     private static final String ACTIVE_BLUETOOTH_ROUTE_NAME = "ActiveBluetoothRoute";
    184     private static final String ACTIVE_SPEAKER_ROUTE_NAME = "ActiveSpeakerRoute";
    185     private static final String ACTIVE_HEADSET_ROUTE_NAME = "ActiveHeadsetRoute";
    186     private static final String RINGING_BLUETOOTH_ROUTE_NAME = "RingingBluetoothRoute";
    187     private static final String QUIESCENT_EARPIECE_ROUTE_NAME = "QuiescentEarpieceRoute";
    188     private static final String QUIESCENT_BLUETOOTH_ROUTE_NAME = "QuiescentBluetoothRoute";
    189     private static final String QUIESCENT_SPEAKER_ROUTE_NAME = "QuiescentSpeakerRoute";
    190     private static final String QUIESCENT_HEADSET_ROUTE_NAME = "QuiescentHeadsetRoute";
    191 
    192     public static final String NAME = CallAudioRouteStateMachine.class.getName();
    193 
    194     @Override
    195     protected void onPreHandleMessage(Message msg) {
    196         if (msg.obj != null && msg.obj instanceof Session) {
    197             String messageCodeName = MESSAGE_CODE_TO_NAME.get(msg.what, "unknown");
    198             Log.continueSession((Session) msg.obj, "CARSM.pM_" + messageCodeName);
    199             Log.i(this, "Message received: %s=%d, arg1=%d", messageCodeName, msg.what, msg.arg1);
    200         }
    201     }
    202 
    203     @Override
    204     protected void onPostHandleMessage(Message msg) {
    205         Log.endSession();
    206     }
    207 
    208     abstract class AudioState extends State {
    209         @Override
    210         public void enter() {
    211             super.enter();
    212             Log.event(mCallsManager.getForegroundCall(), Log.Events.AUDIO_ROUTE,
    213                     "Entering state " + getName());
    214         }
    215 
    216         @Override
    217         public void exit() {
    218             Log.event(mCallsManager.getForegroundCall(), Log.Events.AUDIO_ROUTE,
    219                     "Leaving state " + getName());
    220             super.exit();
    221         }
    222 
    223         @Override
    224         public boolean processMessage(Message msg) {
    225             switch (msg.what) {
    226                 case CONNECT_WIRED_HEADSET:
    227                     Log.event(mCallsManager.getForegroundCall(), Log.Events.AUDIO_ROUTE,
    228                             "Wired headset connected");
    229                     mAvailableRoutes &= ~ROUTE_EARPIECE;
    230                     mAvailableRoutes |= ROUTE_WIRED_HEADSET;
    231                     return NOT_HANDLED;
    232                 case CONNECT_BLUETOOTH:
    233                     Log.event(mCallsManager.getForegroundCall(), Log.Events.AUDIO_ROUTE,
    234                             "Bluetooth connected");
    235                     mAvailableRoutes |= ROUTE_BLUETOOTH;
    236                     return NOT_HANDLED;
    237                 case DISCONNECT_WIRED_HEADSET:
    238                     Log.event(mCallsManager.getForegroundCall(), Log.Events.AUDIO_ROUTE,
    239                             "Wired headset disconnected");
    240                     mAvailableRoutes &= ~ROUTE_WIRED_HEADSET;
    241                     if (mDoesDeviceSupportEarpieceRoute) {
    242                         mAvailableRoutes |= ROUTE_EARPIECE;
    243                     }
    244                     return NOT_HANDLED;
    245                 case DISCONNECT_BLUETOOTH:
    246                     Log.event(mCallsManager.getForegroundCall(), Log.Events.AUDIO_ROUTE,
    247                             "Bluetooth disconnected");
    248                     mAvailableRoutes &= ~ROUTE_BLUETOOTH;
    249                     return NOT_HANDLED;
    250                 case SWITCH_BASELINE_ROUTE:
    251                     sendInternalMessage(calculateBaselineRouteMessage(false));
    252                     return HANDLED;
    253                 case USER_SWITCH_BASELINE_ROUTE:
    254                     sendInternalMessage(calculateBaselineRouteMessage(true));
    255                     return HANDLED;
    256                 case SWITCH_FOCUS:
    257                     mAudioFocusType = msg.arg1;
    258                     return NOT_HANDLED;
    259                 default:
    260                     return NOT_HANDLED;
    261             }
    262         }
    263 
    264         // Behavior will depend on whether the state is an active one or a quiescent one.
    265         abstract public void updateSystemAudioState();
    266         abstract public boolean isActive();
    267     }
    268 
    269     class ActiveEarpieceRoute extends EarpieceRoute {
    270         @Override
    271         public String getName() {
    272             return ACTIVE_EARPIECE_ROUTE_NAME;
    273         }
    274 
    275         @Override
    276         public boolean isActive() {
    277             return true;
    278         }
    279 
    280         @Override
    281         public void enter() {
    282             super.enter();
    283             setSpeakerphoneOn(false);
    284             setBluetoothOn(false);
    285             if (mAudioFocusType == ACTIVE_FOCUS) {
    286                 setNotificationsSuppressed(true);
    287             }
    288             CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_EARPIECE,
    289                     mAvailableRoutes);
    290             setSystemAudioState(newState);
    291             updateInternalCallAudioState();
    292         }
    293 
    294         @Override
    295         public void exit() {
    296             super.exit();
    297             setNotificationsSuppressed(false);
    298         }
    299 
    300         @Override
    301         public void updateSystemAudioState() {
    302             updateInternalCallAudioState();
    303             setSystemAudioState(mCurrentCallAudioState);
    304         }
    305 
    306         @Override
    307         public boolean processMessage(Message msg) {
    308             if (super.processMessage(msg) == HANDLED) {
    309                 return HANDLED;
    310             }
    311             switch (msg.what) {
    312                 case SWITCH_EARPIECE:
    313                 case USER_SWITCH_EARPIECE:
    314                     // Nothing to do here
    315                     return HANDLED;
    316                 case SWITCH_BLUETOOTH:
    317                 case USER_SWITCH_BLUETOOTH:
    318                     if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
    319                         transitionTo(mAudioFocusType == ACTIVE_FOCUS ?
    320                                 mActiveBluetoothRoute : mRingingBluetoothRoute);
    321                     } else {
    322                         Log.w(this, "Ignoring switch to bluetooth command. Not available.");
    323                     }
    324                     return HANDLED;
    325                 case SWITCH_HEADSET:
    326                 case USER_SWITCH_HEADSET:
    327                     if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
    328                         transitionTo(mActiveHeadsetRoute);
    329                     } else {
    330                         Log.w(this, "Ignoring switch to headset command. Not available.");
    331                     }
    332                     return HANDLED;
    333                 case SWITCH_SPEAKER:
    334                 case USER_SWITCH_SPEAKER:
    335                     transitionTo(mActiveSpeakerRoute);
    336                     return HANDLED;
    337                 case SWITCH_FOCUS:
    338                     if (msg.arg1 == ACTIVE_FOCUS) {
    339                         setNotificationsSuppressed(true);
    340                     }
    341 
    342                     if (msg.arg1 == NO_FOCUS) {
    343                         reinitialize();
    344                     }
    345                     return HANDLED;
    346                 default:
    347                     return NOT_HANDLED;
    348             }
    349         }
    350     }
    351 
    352     class QuiescentEarpieceRoute extends EarpieceRoute {
    353         @Override
    354         public String getName() {
    355             return QUIESCENT_EARPIECE_ROUTE_NAME;
    356         }
    357 
    358         @Override
    359         public boolean isActive() {
    360             return false;
    361         }
    362 
    363         @Override
    364         public void enter() {
    365             super.enter();
    366             mHasUserExplicitlyLeftBluetooth = false;
    367             updateInternalCallAudioState();
    368         }
    369 
    370         @Override
    371         public void updateSystemAudioState() {
    372             updateInternalCallAudioState();
    373         }
    374 
    375         @Override
    376         public boolean processMessage(Message msg) {
    377             if (super.processMessage(msg) == HANDLED) {
    378                 return HANDLED;
    379             }
    380             switch (msg.what) {
    381                 case SWITCH_EARPIECE:
    382                 case USER_SWITCH_EARPIECE:
    383                     // Nothing to do here
    384                     return HANDLED;
    385                 case SWITCH_BLUETOOTH:
    386                 case USER_SWITCH_BLUETOOTH:
    387                     if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
    388                         transitionTo(mQuiescentBluetoothRoute);
    389                     } else {
    390                         Log.w(this, "Ignoring switch to bluetooth command. Not available.");
    391                     }
    392                     return HANDLED;
    393                 case SWITCH_HEADSET:
    394                 case USER_SWITCH_HEADSET:
    395                     if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
    396                         transitionTo(mQuiescentHeadsetRoute);
    397                     } else {
    398                         Log.w(this, "Ignoring switch to headset command. Not available.");
    399                     }
    400                     return HANDLED;
    401                 case SWITCH_SPEAKER:
    402                 case USER_SWITCH_SPEAKER:
    403                     transitionTo(mQuiescentSpeakerRoute);
    404                     return HANDLED;
    405                 case SWITCH_FOCUS:
    406                     if (msg.arg1 == ACTIVE_FOCUS || msg.arg1 == RINGING_FOCUS) {
    407                         transitionTo(mActiveEarpieceRoute);
    408                     }
    409                     return HANDLED;
    410                 default:
    411                     return NOT_HANDLED;
    412             }
    413         }
    414     }
    415 
    416     abstract class EarpieceRoute extends AudioState {
    417         @Override
    418         public boolean processMessage(Message msg) {
    419             if (super.processMessage(msg) == HANDLED) {
    420                 return HANDLED;
    421             }
    422             switch (msg.what) {
    423                 case CONNECT_WIRED_HEADSET:
    424                     sendInternalMessage(SWITCH_HEADSET);
    425                     return HANDLED;
    426                 case CONNECT_BLUETOOTH:
    427                     if (!mHasUserExplicitlyLeftBluetooth) {
    428                         sendInternalMessage(SWITCH_BLUETOOTH);
    429                     } else {
    430                         Log.i(this, "Not switching to BT route from earpiece because user has " +
    431                                 "explicitly disconnected.");
    432                         updateSystemAudioState();
    433                     }
    434                     return HANDLED;
    435                 case DISCONNECT_BLUETOOTH:
    436                     updateSystemAudioState();
    437                     // No change in audio route required
    438                     return HANDLED;
    439                 case DISCONNECT_WIRED_HEADSET:
    440                     Log.e(this, new IllegalStateException(),
    441                             "Wired headset should not go from connected to not when on " +
    442                             "earpiece");
    443                     updateSystemAudioState();
    444                     return HANDLED;
    445                 case BT_AUDIO_DISCONNECT:
    446                     // This may be sent as a confirmation by the BT stack after switch off BT.
    447                     return HANDLED;
    448                 case CONNECT_DOCK:
    449                     sendInternalMessage(SWITCH_SPEAKER);
    450                     return HANDLED;
    451                 case DISCONNECT_DOCK:
    452                     // Nothing to do here
    453                     return HANDLED;
    454                 default:
    455                     return NOT_HANDLED;
    456             }
    457         }
    458     }
    459 
    460     class ActiveHeadsetRoute extends HeadsetRoute {
    461         @Override
    462         public String getName() {
    463             return ACTIVE_HEADSET_ROUTE_NAME;
    464         }
    465 
    466         @Override
    467         public boolean isActive() {
    468             return true;
    469         }
    470 
    471         @Override
    472         public void enter() {
    473             super.enter();
    474             setSpeakerphoneOn(false);
    475             setBluetoothOn(false);
    476             CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_WIRED_HEADSET,
    477                     mAvailableRoutes);
    478             setSystemAudioState(newState);
    479             updateInternalCallAudioState();
    480         }
    481 
    482         @Override
    483         public void updateSystemAudioState() {
    484             updateInternalCallAudioState();
    485             setSystemAudioState(mCurrentCallAudioState);
    486         }
    487 
    488         @Override
    489         public boolean processMessage(Message msg) {
    490             if (super.processMessage(msg) == HANDLED) {
    491                 return HANDLED;
    492             }
    493             switch (msg.what) {
    494                 case SWITCH_EARPIECE:
    495                 case USER_SWITCH_EARPIECE:
    496                     if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
    497                         transitionTo(mActiveEarpieceRoute);
    498                     } else {
    499                         Log.w(this, "Ignoring switch to earpiece command. Not available.");
    500                     }
    501                     return HANDLED;
    502                 case SWITCH_BLUETOOTH:
    503                 case USER_SWITCH_BLUETOOTH:
    504                     if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
    505                         transitionTo(mAudioFocusType == ACTIVE_FOCUS ?
    506                                 mActiveBluetoothRoute : mRingingBluetoothRoute);
    507                     } else {
    508                         Log.w(this, "Ignoring switch to bluetooth command. Not available.");
    509                     }
    510                     return HANDLED;
    511                 case SWITCH_HEADSET:
    512                 case USER_SWITCH_HEADSET:
    513                     // Nothing to do
    514                     return HANDLED;
    515                 case SWITCH_SPEAKER:
    516                 case USER_SWITCH_SPEAKER:
    517                     transitionTo(mActiveSpeakerRoute);
    518                     return HANDLED;
    519                 case SWITCH_FOCUS:
    520                     if (msg.arg1 == NO_FOCUS) {
    521                         reinitialize();
    522                     }
    523                     return HANDLED;
    524                 default:
    525                     return NOT_HANDLED;
    526             }
    527         }
    528     }
    529 
    530     class QuiescentHeadsetRoute extends HeadsetRoute {
    531         @Override
    532         public String getName() {
    533             return QUIESCENT_HEADSET_ROUTE_NAME;
    534         }
    535 
    536         @Override
    537         public boolean isActive() {
    538             return false;
    539         }
    540 
    541         @Override
    542         public void enter() {
    543             super.enter();
    544             mHasUserExplicitlyLeftBluetooth = false;
    545             updateInternalCallAudioState();
    546         }
    547 
    548         @Override
    549         public void updateSystemAudioState() {
    550             updateInternalCallAudioState();
    551         }
    552 
    553         @Override
    554         public boolean processMessage(Message msg) {
    555             if (super.processMessage(msg) == HANDLED) {
    556                 return HANDLED;
    557             }
    558             switch (msg.what) {
    559                 case SWITCH_EARPIECE:
    560                 case USER_SWITCH_EARPIECE:
    561                     if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
    562                         transitionTo(mQuiescentEarpieceRoute);
    563                     } else {
    564                         Log.w(this, "Ignoring switch to earpiece command. Not available.");
    565                     }
    566                     return HANDLED;
    567                 case SWITCH_BLUETOOTH:
    568                 case USER_SWITCH_BLUETOOTH:
    569                     if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
    570                         transitionTo(mQuiescentBluetoothRoute);
    571                     } else {
    572                         Log.w(this, "Ignoring switch to bluetooth command. Not available.");
    573                     }
    574                     return HANDLED;
    575                 case SWITCH_HEADSET:
    576                 case USER_SWITCH_HEADSET:
    577                     // Nothing to do
    578                     return HANDLED;
    579                 case SWITCH_SPEAKER:
    580                 case USER_SWITCH_SPEAKER:
    581                     transitionTo(mQuiescentSpeakerRoute);
    582                     return HANDLED;
    583                 case SWITCH_FOCUS:
    584                     if (msg.arg1 == ACTIVE_FOCUS || msg.arg1 == RINGING_FOCUS) {
    585                         transitionTo(mActiveHeadsetRoute);
    586                     }
    587                     return HANDLED;
    588                 default:
    589                     return NOT_HANDLED;
    590             }
    591         }
    592     }
    593 
    594     abstract class HeadsetRoute extends AudioState {
    595         @Override
    596         public boolean processMessage(Message msg) {
    597             if (super.processMessage(msg) == HANDLED) {
    598                 return HANDLED;
    599             }
    600             switch (msg.what) {
    601                 case CONNECT_WIRED_HEADSET:
    602                     Log.e(this, new IllegalStateException(),
    603                             "Wired headset should already be connected.");
    604                     mAvailableRoutes |= ROUTE_WIRED_HEADSET;
    605                     updateSystemAudioState();
    606                     return HANDLED;
    607                 case CONNECT_BLUETOOTH:
    608                     if (!mHasUserExplicitlyLeftBluetooth) {
    609                         sendInternalMessage(SWITCH_BLUETOOTH);
    610                     } else {
    611                         Log.i(this, "Not switching to BT route from headset because user has " +
    612                                 "explicitly disconnected.");
    613                         updateSystemAudioState();
    614                     }
    615                     return HANDLED;
    616                 case DISCONNECT_BLUETOOTH:
    617                     updateSystemAudioState();
    618                     // No change in audio route required
    619                     return HANDLED;
    620                 case DISCONNECT_WIRED_HEADSET:
    621                     if (mWasOnSpeaker) {
    622                         sendInternalMessage(SWITCH_SPEAKER);
    623                     } else {
    624                         sendInternalMessage(SWITCH_BASELINE_ROUTE);
    625                     }
    626                     return HANDLED;
    627                 case BT_AUDIO_DISCONNECT:
    628                     // This may be sent as a confirmation by the BT stack after switch off BT.
    629                     return HANDLED;
    630                 case CONNECT_DOCK:
    631                     // Nothing to do here
    632                     return HANDLED;
    633                 case DISCONNECT_DOCK:
    634                     // Nothing to do here
    635                     return HANDLED;
    636                 default:
    637                     return NOT_HANDLED;
    638             }
    639         }
    640     }
    641 
    642     class ActiveBluetoothRoute extends BluetoothRoute {
    643         @Override
    644         public String getName() {
    645             return ACTIVE_BLUETOOTH_ROUTE_NAME;
    646         }
    647 
    648         @Override
    649         public boolean isActive() {
    650             return true;
    651         }
    652 
    653         @Override
    654         public void enter() {
    655             super.enter();
    656             setSpeakerphoneOn(false);
    657             setBluetoothOn(true);
    658             CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_BLUETOOTH,
    659                     mAvailableRoutes);
    660             setSystemAudioState(newState);
    661             updateInternalCallAudioState();
    662         }
    663 
    664         @Override
    665         public void updateSystemAudioState() {
    666             updateInternalCallAudioState();
    667             setSystemAudioState(mCurrentCallAudioState);
    668         }
    669 
    670         @Override
    671         public boolean processMessage(Message msg) {
    672             if (super.processMessage(msg) == HANDLED) {
    673                 return HANDLED;
    674             }
    675             switch (msg.what) {
    676                 case USER_SWITCH_EARPIECE:
    677                     mHasUserExplicitlyLeftBluetooth = true;
    678                     // fall through
    679                 case SWITCH_EARPIECE:
    680                     if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
    681                         transitionTo(mActiveEarpieceRoute);
    682                     } else {
    683                         Log.w(this, "Ignoring switch to earpiece command. Not available.");
    684                     }
    685                     return HANDLED;
    686                 case SWITCH_BLUETOOTH:
    687                 case USER_SWITCH_BLUETOOTH:
    688                     // Nothing to do
    689                     return HANDLED;
    690                 case USER_SWITCH_HEADSET:
    691                     mHasUserExplicitlyLeftBluetooth = true;
    692                     // fall through
    693                 case SWITCH_HEADSET:
    694                     if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
    695                         transitionTo(mActiveHeadsetRoute);
    696                     } else {
    697                         Log.w(this, "Ignoring switch to headset command. Not available.");
    698                     }
    699                     return HANDLED;
    700                 case USER_SWITCH_SPEAKER:
    701                     mHasUserExplicitlyLeftBluetooth = true;
    702                     // fall through
    703                 case SWITCH_SPEAKER:
    704                     transitionTo(mActiveSpeakerRoute);
    705                     return HANDLED;
    706                 case SWITCH_FOCUS:
    707                     if (msg.arg1 == NO_FOCUS) {
    708                         reinitialize();
    709                     } else if (msg.arg1 == RINGING_FOCUS) {
    710                         transitionTo(mRingingBluetoothRoute);
    711                     }
    712                     return HANDLED;
    713                 case BT_AUDIO_DISCONNECT:
    714                     sendInternalMessage(SWITCH_BASELINE_ROUTE);
    715                     return HANDLED;
    716                 default:
    717                     return NOT_HANDLED;
    718             }
    719         }
    720     }
    721 
    722     class RingingBluetoothRoute extends BluetoothRoute {
    723         @Override
    724         public String getName() {
    725             return RINGING_BLUETOOTH_ROUTE_NAME;
    726         }
    727 
    728         @Override
    729         public boolean isActive() {
    730             return false;
    731         }
    732 
    733         @Override
    734         public void enter() {
    735             super.enter();
    736             setSpeakerphoneOn(false);
    737             // Do not enable SCO audio here, since RING is being sent to the headset.
    738             CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_BLUETOOTH,
    739                     mAvailableRoutes);
    740             setSystemAudioState(newState);
    741             updateInternalCallAudioState();
    742         }
    743 
    744         @Override
    745         public void updateSystemAudioState() {
    746             updateInternalCallAudioState();
    747             setSystemAudioState(mCurrentCallAudioState);
    748         }
    749 
    750         @Override
    751         public boolean processMessage(Message msg) {
    752             if (super.processMessage(msg) == HANDLED) {
    753                 return HANDLED;
    754             }
    755             switch (msg.what) {
    756                 case USER_SWITCH_EARPIECE:
    757                     mHasUserExplicitlyLeftBluetooth = true;
    758                     // fall through
    759                 case SWITCH_EARPIECE:
    760                     if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
    761                         transitionTo(mActiveEarpieceRoute);
    762                     } else {
    763                         Log.w(this, "Ignoring switch to earpiece command. Not available.");
    764                     }
    765                     return HANDLED;
    766                 case SWITCH_BLUETOOTH:
    767                 case USER_SWITCH_BLUETOOTH:
    768                     // Nothing to do
    769                     return HANDLED;
    770                 case USER_SWITCH_HEADSET:
    771                     mHasUserExplicitlyLeftBluetooth = true;
    772                     // fall through
    773                 case SWITCH_HEADSET:
    774                     if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
    775                         transitionTo(mActiveHeadsetRoute);
    776                     } else {
    777                         Log.w(this, "Ignoring switch to headset command. Not available.");
    778                     }
    779                     return HANDLED;
    780                 case USER_SWITCH_SPEAKER:
    781                     mHasUserExplicitlyLeftBluetooth = true;
    782                     // fall through
    783                 case SWITCH_SPEAKER:
    784                     transitionTo(mActiveSpeakerRoute);
    785                     return HANDLED;
    786                 case SWITCH_FOCUS:
    787                     if (msg.arg1 == NO_FOCUS) {
    788                         reinitialize();
    789                     } else if (msg.arg1 == ACTIVE_FOCUS) {
    790                         transitionTo(mActiveBluetoothRoute);
    791                     }
    792                     return HANDLED;
    793                 case BT_AUDIO_DISCONNECT:
    794                     // Ignore BT_AUDIO_DISCONNECT when ringing, since SCO audio should not be
    795                     // connected.
    796                     return HANDLED;
    797                 default:
    798                     return NOT_HANDLED;
    799             }
    800         }
    801     }
    802 
    803     class QuiescentBluetoothRoute extends BluetoothRoute {
    804         @Override
    805         public String getName() {
    806             return QUIESCENT_BLUETOOTH_ROUTE_NAME;
    807         }
    808 
    809         @Override
    810         public boolean isActive() {
    811             return false;
    812         }
    813 
    814         @Override
    815         public void enter() {
    816             super.enter();
    817             mHasUserExplicitlyLeftBluetooth = false;
    818             updateInternalCallAudioState();
    819         }
    820 
    821         @Override
    822         public void updateSystemAudioState() {
    823             updateInternalCallAudioState();
    824         }
    825 
    826         @Override
    827         public boolean processMessage(Message msg) {
    828             if (super.processMessage(msg) == HANDLED) {
    829                 return HANDLED;
    830             }
    831             switch (msg.what) {
    832                 case SWITCH_EARPIECE:
    833                 case USER_SWITCH_EARPIECE:
    834                     if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
    835                         transitionTo(mQuiescentEarpieceRoute);
    836                     } else {
    837                         Log.w(this, "Ignoring switch to earpiece command. Not available.");
    838                     }
    839                     return HANDLED;
    840                 case SWITCH_BLUETOOTH:
    841                 case USER_SWITCH_BLUETOOTH:
    842                     // Nothing to do
    843                     return HANDLED;
    844                 case SWITCH_HEADSET:
    845                 case USER_SWITCH_HEADSET:
    846                     if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
    847                         transitionTo(mQuiescentHeadsetRoute);
    848                     } else {
    849                         Log.w(this, "Ignoring switch to headset command. Not available.");
    850                     }
    851                     return HANDLED;
    852                 case SWITCH_SPEAKER:
    853                 case USER_SWITCH_SPEAKER:
    854                     transitionTo(mQuiescentSpeakerRoute);
    855                     return HANDLED;
    856                 case SWITCH_FOCUS:
    857                     if (msg.arg1 == ACTIVE_FOCUS) {
    858                         transitionTo(mActiveBluetoothRoute);
    859                     } else if (msg.arg1 == RINGING_FOCUS) {
    860                         transitionTo(mRingingBluetoothRoute);
    861                     }
    862                     return HANDLED;
    863                 case BT_AUDIO_DISCONNECT:
    864                     // Ignore this -- audio disconnecting while quiescent should not cause a
    865                     // route switch, since the device is still connected.
    866                     return HANDLED;
    867                 default:
    868                     return NOT_HANDLED;
    869             }
    870         }
    871     }
    872 
    873     abstract class BluetoothRoute extends AudioState {
    874         @Override
    875         public boolean processMessage(Message msg) {
    876             if (super.processMessage(msg) == HANDLED) {
    877                 return HANDLED;
    878             }
    879             switch (msg.what) {
    880                 case CONNECT_WIRED_HEADSET:
    881                     sendInternalMessage(SWITCH_HEADSET);
    882                     return HANDLED;
    883                 case CONNECT_BLUETOOTH:
    884                     // We can't tell when a change in bluetooth state corresponds to an
    885                     // actual connection or disconnection, so we'll just ignore it if we're already
    886                     // in the bluetooth route.
    887                     return HANDLED;
    888                 case DISCONNECT_BLUETOOTH:
    889                     sendInternalMessage(SWITCH_BASELINE_ROUTE);
    890                     mWasOnSpeaker = false;
    891                     return HANDLED;
    892                 case DISCONNECT_WIRED_HEADSET:
    893                     updateSystemAudioState();
    894                     // No change in audio route required
    895                     return HANDLED;
    896                 case CONNECT_DOCK:
    897                     // Nothing to do here
    898                     return HANDLED;
    899                 case DISCONNECT_DOCK:
    900                     // Nothing to do here
    901                     return HANDLED;
    902                 default:
    903                     return NOT_HANDLED;
    904             }
    905         }
    906     }
    907 
    908     class ActiveSpeakerRoute extends SpeakerRoute {
    909         @Override
    910         public String getName() {
    911             return ACTIVE_SPEAKER_ROUTE_NAME;
    912         }
    913 
    914         @Override
    915         public boolean isActive() {
    916             return true;
    917         }
    918 
    919         @Override
    920         public void enter() {
    921             super.enter();
    922             mWasOnSpeaker = true;
    923             setSpeakerphoneOn(true);
    924             setBluetoothOn(false);
    925             CallAudioState newState = new CallAudioState(mIsMuted, ROUTE_SPEAKER,
    926                     mAvailableRoutes);
    927             setSystemAudioState(newState);
    928             updateInternalCallAudioState();
    929         }
    930 
    931         @Override
    932         public void updateSystemAudioState() {
    933             updateInternalCallAudioState();
    934             setSystemAudioState(mCurrentCallAudioState);
    935         }
    936 
    937         @Override
    938         public boolean processMessage(Message msg) {
    939             if (super.processMessage(msg) == HANDLED) {
    940                 return HANDLED;
    941             }
    942             switch(msg.what) {
    943                 case USER_SWITCH_EARPIECE:
    944                     mWasOnSpeaker = false;
    945                     // fall through
    946                 case SWITCH_EARPIECE:
    947                     if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
    948                         transitionTo(mActiveEarpieceRoute);
    949                     } else {
    950                         Log.w(this, "Ignoring switch to earpiece command. Not available.");
    951                     }
    952                     return HANDLED;
    953                 case USER_SWITCH_BLUETOOTH:
    954                     mWasOnSpeaker = false;
    955                     // fall through
    956                 case SWITCH_BLUETOOTH:
    957                     if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
    958                         transitionTo(mAudioFocusType == ACTIVE_FOCUS ?
    959                                 mActiveBluetoothRoute : mRingingBluetoothRoute);
    960                     } else {
    961                         Log.w(this, "Ignoring switch to bluetooth command. Not available.");
    962                     }
    963                     return HANDLED;
    964                 case USER_SWITCH_HEADSET:
    965                     mWasOnSpeaker = false;
    966                     // fall through
    967                 case SWITCH_HEADSET:
    968                     if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
    969                         transitionTo(mActiveHeadsetRoute);
    970                     } else {
    971                         Log.w(this, "Ignoring switch to headset command. Not available.");
    972                     }
    973                     return HANDLED;
    974                 case SWITCH_SPEAKER:
    975                 case USER_SWITCH_SPEAKER:
    976                     // Nothing to do
    977                     return HANDLED;
    978                 case SWITCH_FOCUS:
    979                     if (msg.arg1 == NO_FOCUS) {
    980                         reinitialize();
    981                     }
    982                     return HANDLED;
    983                 default:
    984                     return NOT_HANDLED;
    985             }
    986         }
    987     }
    988 
    989     class QuiescentSpeakerRoute extends SpeakerRoute {
    990         @Override
    991         public String getName() {
    992             return QUIESCENT_SPEAKER_ROUTE_NAME;
    993         }
    994 
    995         @Override
    996         public boolean isActive() {
    997             return false;
    998         }
    999 
   1000         @Override
   1001         public void enter() {
   1002             super.enter();
   1003             mHasUserExplicitlyLeftBluetooth = false;
   1004             // Omit setting mWasOnSpeaker to true here, since this does not reflect a call
   1005             // actually being on speakerphone.
   1006             updateInternalCallAudioState();
   1007         }
   1008 
   1009         @Override
   1010         public void updateSystemAudioState() {
   1011             updateInternalCallAudioState();
   1012         }
   1013 
   1014         @Override
   1015         public boolean processMessage(Message msg) {
   1016             if (super.processMessage(msg) == HANDLED) {
   1017                 return HANDLED;
   1018             }
   1019             switch(msg.what) {
   1020                 case SWITCH_EARPIECE:
   1021                 case USER_SWITCH_EARPIECE:
   1022                     if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
   1023                         transitionTo(mQuiescentEarpieceRoute);
   1024                     } else {
   1025                         Log.w(this, "Ignoring switch to earpiece command. Not available.");
   1026                     }
   1027                     return HANDLED;
   1028                 case SWITCH_BLUETOOTH:
   1029                 case USER_SWITCH_BLUETOOTH:
   1030                     if ((mAvailableRoutes & ROUTE_BLUETOOTH) != 0) {
   1031                         transitionTo(mQuiescentBluetoothRoute);
   1032                     } else {
   1033                         Log.w(this, "Ignoring switch to bluetooth command. Not available.");
   1034                     }
   1035                     return HANDLED;
   1036                 case SWITCH_HEADSET:
   1037                 case USER_SWITCH_HEADSET:
   1038                     if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
   1039                         transitionTo(mQuiescentHeadsetRoute);
   1040                     } else {
   1041                         Log.w(this, "Ignoring switch to headset command. Not available.");
   1042                     }
   1043                     return HANDLED;
   1044                 case SWITCH_SPEAKER:
   1045                 case USER_SWITCH_SPEAKER:
   1046                     // Nothing to do
   1047                     return HANDLED;
   1048                 case SWITCH_FOCUS:
   1049                     if (msg.arg1 == ACTIVE_FOCUS || msg.arg1 == RINGING_FOCUS) {
   1050                         transitionTo(mActiveSpeakerRoute);
   1051                     }
   1052                     return HANDLED;
   1053                 default:
   1054                     return NOT_HANDLED;
   1055             }
   1056         }
   1057     }
   1058 
   1059     abstract class SpeakerRoute extends AudioState {
   1060         @Override
   1061         public boolean processMessage(Message msg) {
   1062             if (super.processMessage(msg) == HANDLED) {
   1063                 return HANDLED;
   1064             }
   1065             switch (msg.what) {
   1066                 case CONNECT_WIRED_HEADSET:
   1067                     sendInternalMessage(SWITCH_HEADSET);
   1068                     return HANDLED;
   1069                 case CONNECT_BLUETOOTH:
   1070                     if (!mHasUserExplicitlyLeftBluetooth) {
   1071                         sendInternalMessage(SWITCH_BLUETOOTH);
   1072                     } else {
   1073                         Log.i(this, "Not switching to BT route from speaker because user has " +
   1074                                 "explicitly disconnected.");
   1075                         updateSystemAudioState();
   1076                     }
   1077                     return HANDLED;
   1078                 case DISCONNECT_BLUETOOTH:
   1079                     updateSystemAudioState();
   1080                     // No change in audio route required
   1081                     return HANDLED;
   1082                 case DISCONNECT_WIRED_HEADSET:
   1083                     updateSystemAudioState();
   1084                     // No change in audio route required
   1085                     return HANDLED;
   1086                 case BT_AUDIO_DISCONNECT:
   1087                     // This may be sent as a confirmation by the BT stack after switch off BT.
   1088                     return HANDLED;
   1089                 case CONNECT_DOCK:
   1090                     // Nothing to do here
   1091                     return HANDLED;
   1092                 case DISCONNECT_DOCK:
   1093                     sendInternalMessage(SWITCH_BASELINE_ROUTE);
   1094                     return HANDLED;
   1095                default:
   1096                     return NOT_HANDLED;
   1097             }
   1098         }
   1099     }
   1100 
   1101     private final ActiveEarpieceRoute mActiveEarpieceRoute = new ActiveEarpieceRoute();
   1102     private final ActiveHeadsetRoute mActiveHeadsetRoute = new ActiveHeadsetRoute();
   1103     private final ActiveBluetoothRoute mActiveBluetoothRoute = new ActiveBluetoothRoute();
   1104     private final ActiveSpeakerRoute mActiveSpeakerRoute = new ActiveSpeakerRoute();
   1105     private final RingingBluetoothRoute mRingingBluetoothRoute = new RingingBluetoothRoute();
   1106     private final QuiescentEarpieceRoute mQuiescentEarpieceRoute = new QuiescentEarpieceRoute();
   1107     private final QuiescentHeadsetRoute mQuiescentHeadsetRoute = new QuiescentHeadsetRoute();
   1108     private final QuiescentBluetoothRoute mQuiescentBluetoothRoute = new QuiescentBluetoothRoute();
   1109     private final QuiescentSpeakerRoute mQuiescentSpeakerRoute = new QuiescentSpeakerRoute();
   1110 
   1111     /**
   1112      * A few pieces of hidden state. Used to avoid exponential explosion of number of explicit
   1113      * states
   1114      */
   1115     private int mAvailableRoutes;
   1116     private int mAudioFocusType;
   1117     private boolean mWasOnSpeaker;
   1118     private boolean mIsMuted;
   1119     private boolean mAreNotificationSuppressed = false;
   1120 
   1121     private final Context mContext;
   1122     private final CallsManager mCallsManager;
   1123     private final AudioManager mAudioManager;
   1124     private final BluetoothManager mBluetoothManager;
   1125     private final WiredHeadsetManager mWiredHeadsetManager;
   1126     private final StatusBarNotifier mStatusBarNotifier;
   1127     private final CallAudioManager.AudioServiceFactory mAudioServiceFactory;
   1128     private final InterruptionFilterProxy mInterruptionFilterProxy;
   1129     private final boolean mDoesDeviceSupportEarpieceRoute;
   1130     private final TelecomSystem.SyncRoot mLock;
   1131     private boolean mHasUserExplicitlyLeftBluetooth = false;
   1132 
   1133     private HashMap<String, Integer> mStateNameToRouteCode;
   1134     private HashMap<Integer, AudioState> mRouteCodeToQuiescentState;
   1135 
   1136     // CallAudioState is used as an interface to communicate with many other system components.
   1137     // No internal state transitions should depend on this variable.
   1138     private CallAudioState mCurrentCallAudioState;
   1139     private CallAudioState mLastKnownCallAudioState;
   1140 
   1141     public CallAudioRouteStateMachine(
   1142             Context context,
   1143             CallsManager callsManager,
   1144             BluetoothManager bluetoothManager,
   1145             WiredHeadsetManager wiredHeadsetManager,
   1146             StatusBarNotifier statusBarNotifier,
   1147             CallAudioManager.AudioServiceFactory audioServiceFactory,
   1148             InterruptionFilterProxy interruptionFilterProxy,
   1149             boolean doesDeviceSupportEarpieceRoute) {
   1150         super(NAME);
   1151         addState(mActiveEarpieceRoute);
   1152         addState(mActiveHeadsetRoute);
   1153         addState(mActiveBluetoothRoute);
   1154         addState(mActiveSpeakerRoute);
   1155         addState(mRingingBluetoothRoute);
   1156         addState(mQuiescentEarpieceRoute);
   1157         addState(mQuiescentHeadsetRoute);
   1158         addState(mQuiescentBluetoothRoute);
   1159         addState(mQuiescentSpeakerRoute);
   1160 
   1161         mContext = context;
   1162         mCallsManager = callsManager;
   1163         mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
   1164         mBluetoothManager = bluetoothManager;
   1165         mWiredHeadsetManager = wiredHeadsetManager;
   1166         mStatusBarNotifier = statusBarNotifier;
   1167         mAudioServiceFactory = audioServiceFactory;
   1168         mInterruptionFilterProxy = interruptionFilterProxy;
   1169         // Register for misc other intent broadcasts.
   1170         IntentFilter intentFilter =
   1171                 new IntentFilter(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED);
   1172         context.registerReceiver(mReceiver, intentFilter);
   1173         mDoesDeviceSupportEarpieceRoute = doesDeviceSupportEarpieceRoute;
   1174         mLock = callsManager.getLock();
   1175 
   1176         mStateNameToRouteCode = new HashMap<>(8);
   1177         mStateNameToRouteCode.put(mQuiescentEarpieceRoute.getName(), ROUTE_EARPIECE);
   1178         mStateNameToRouteCode.put(mQuiescentBluetoothRoute.getName(), ROUTE_BLUETOOTH);
   1179         mStateNameToRouteCode.put(mQuiescentHeadsetRoute.getName(), ROUTE_WIRED_HEADSET);
   1180         mStateNameToRouteCode.put(mQuiescentSpeakerRoute.getName(), ROUTE_SPEAKER);
   1181         mStateNameToRouteCode.put(mRingingBluetoothRoute.getName(), ROUTE_BLUETOOTH);
   1182         mStateNameToRouteCode.put(mActiveEarpieceRoute.getName(), ROUTE_EARPIECE);
   1183         mStateNameToRouteCode.put(mActiveBluetoothRoute.getName(), ROUTE_BLUETOOTH);
   1184         mStateNameToRouteCode.put(mActiveHeadsetRoute.getName(), ROUTE_WIRED_HEADSET);
   1185         mStateNameToRouteCode.put(mActiveSpeakerRoute.getName(), ROUTE_SPEAKER);
   1186 
   1187         mRouteCodeToQuiescentState = new HashMap<>(4);
   1188         mRouteCodeToQuiescentState.put(ROUTE_EARPIECE, mQuiescentEarpieceRoute);
   1189         mRouteCodeToQuiescentState.put(ROUTE_BLUETOOTH, mQuiescentBluetoothRoute);
   1190         mRouteCodeToQuiescentState.put(ROUTE_SPEAKER, mQuiescentSpeakerRoute);
   1191         mRouteCodeToQuiescentState.put(ROUTE_WIRED_HEADSET, mQuiescentHeadsetRoute);
   1192     }
   1193 
   1194     /**
   1195      * Initializes the state machine with info on initial audio route, supported audio routes,
   1196      * and mute status.
   1197      */
   1198     public void initialize() {
   1199         CallAudioState initState = getInitialAudioState();
   1200         initialize(initState);
   1201     }
   1202 
   1203     public void initialize(CallAudioState initState) {
   1204         mCurrentCallAudioState = initState;
   1205         mLastKnownCallAudioState = initState;
   1206         mAvailableRoutes = initState.getSupportedRouteMask();
   1207         mIsMuted = initState.isMuted();
   1208         mWasOnSpeaker = false;
   1209 
   1210         mStatusBarNotifier.notifyMute(initState.isMuted());
   1211         mStatusBarNotifier.notifySpeakerphone(initState.getRoute() == CallAudioState.ROUTE_SPEAKER);
   1212         setInitialState(mRouteCodeToQuiescentState.get(initState.getRoute()));
   1213         start();
   1214     }
   1215 
   1216     /**
   1217      * Getter for the current CallAudioState object that the state machine is keeping track of.
   1218      * Used for compatibility purposes.
   1219      */
   1220     public CallAudioState getCurrentCallAudioState() {
   1221         return mCurrentCallAudioState;
   1222     }
   1223 
   1224     public void sendMessageWithSessionInfo(int message, int arg) {
   1225         sendMessage(message, arg, 0, Log.createSubsession());
   1226     }
   1227 
   1228     public void sendMessageWithSessionInfo(int message) {
   1229         sendMessage(message, 0, 0, Log.createSubsession());
   1230     }
   1231 
   1232     /**
   1233      * This is for state-independent changes in audio route (i.e. muting or runnables)
   1234      * @param msg that couldn't be handled.
   1235      */
   1236     @Override
   1237     protected void unhandledMessage(Message msg) {
   1238         CallAudioState newCallAudioState;
   1239         switch (msg.what) {
   1240             case MUTE_ON:
   1241                 setMuteOn(true);
   1242                 newCallAudioState = new CallAudioState(mIsMuted,
   1243                         mCurrentCallAudioState.getRoute(),
   1244                         mAvailableRoutes);
   1245                 setSystemAudioState(newCallAudioState);
   1246                 updateInternalCallAudioState();
   1247                 return;
   1248             case MUTE_OFF:
   1249                 setMuteOn(false);
   1250                 newCallAudioState = new CallAudioState(mIsMuted,
   1251                         mCurrentCallAudioState.getRoute(),
   1252                         mAvailableRoutes);
   1253                 setSystemAudioState(newCallAudioState);
   1254                 updateInternalCallAudioState();
   1255                 return;
   1256             case TOGGLE_MUTE:
   1257                 if (mIsMuted) {
   1258                     sendInternalMessage(MUTE_OFF);
   1259                 } else {
   1260                     sendInternalMessage(MUTE_ON);
   1261                 }
   1262                 return;
   1263             case UPDATE_SYSTEM_AUDIO_ROUTE:
   1264                 resendSystemAudioState();
   1265                 return;
   1266             case RUN_RUNNABLE:
   1267                 java.lang.Runnable r = (java.lang.Runnable) msg.obj;
   1268                 r.run();
   1269                 return;
   1270             default:
   1271                 Log.e(this, new IllegalStateException(),
   1272                         "Unexpected message code");
   1273         }
   1274     }
   1275 
   1276     public void quitStateMachine() {
   1277         quitNow();
   1278     }
   1279 
   1280     /**
   1281      * Sets whether notifications should be suppressed or not.  Used when in a call to ensure the
   1282      * device will not vibrate due to notifications.
   1283      * Alarm-only filtering is activated when
   1284      *
   1285      * @param on {@code true} when notification suppression should be activated, {@code false} when
   1286      *                       it should be deactivated.
   1287      */
   1288     private void setNotificationsSuppressed(boolean on) {
   1289         if (mInterruptionFilterProxy == null) {
   1290             return;
   1291         }
   1292 
   1293         Log.i(this, "setNotificationsSuppressed: on=%s; suppressed=%s", (on ? "yes" : "no"),
   1294                 (mAreNotificationSuppressed ? "yes" : "no"));
   1295         if (on) {
   1296             if (!mAreNotificationSuppressed) {
   1297                 // Enabling suppression of notifications.
   1298                 int interruptionFilter = mInterruptionFilterProxy.getCurrentInterruptionFilter();
   1299                 if (interruptionFilter == NotificationManager.INTERRUPTION_FILTER_ALL) {
   1300                     // No interruption filter is specified, so suppress notifications by setting the
   1301                     // current filter to alarms-only.
   1302                     mAreNotificationSuppressed = true;
   1303                     mInterruptionFilterProxy.setInterruptionFilter(
   1304                             NotificationManager.INTERRUPTION_FILTER_ALARMS);
   1305                 } else {
   1306                     // Interruption filter is already chosen by the user, so do not attempt to change
   1307                     // it.
   1308                     mAreNotificationSuppressed = false;
   1309                 }
   1310             }
   1311         } else {
   1312             // Disabling suppression of notifications.
   1313             if (mAreNotificationSuppressed) {
   1314                 // We have implemented the alarms-only policy and the user has not changed it since
   1315                 // we originally set it, so reset the notification filter.
   1316                 mInterruptionFilterProxy.setInterruptionFilter(
   1317                         NotificationManager.INTERRUPTION_FILTER_ALL);
   1318             }
   1319             mAreNotificationSuppressed = false;
   1320         }
   1321     }
   1322 
   1323     private void setSpeakerphoneOn(boolean on) {
   1324         if (mAudioManager.isSpeakerphoneOn() != on) {
   1325             Log.i(this, "turning speaker phone %s", on);
   1326             mAudioManager.setSpeakerphoneOn(on);
   1327             mStatusBarNotifier.notifySpeakerphone(on);
   1328         }
   1329     }
   1330 
   1331     private void setBluetoothOn(boolean on) {
   1332         if (mBluetoothManager.isBluetoothAvailable()) {
   1333             boolean isAlreadyOn = mBluetoothManager.isBluetoothAudioConnectedOrPending();
   1334             if (on != isAlreadyOn) {
   1335                 Log.i(this, "connecting bluetooth %s", on);
   1336                 if (on) {
   1337                     mBluetoothManager.connectBluetoothAudio();
   1338                 } else {
   1339                     mBluetoothManager.disconnectBluetoothAudio();
   1340                 }
   1341             }
   1342         }
   1343     }
   1344 
   1345     private void setMuteOn(boolean mute) {
   1346         mIsMuted = mute;
   1347         Log.event(mCallsManager.getForegroundCall(), mute ? Log.Events.MUTE : Log.Events.UNMUTE);
   1348 
   1349         if (mute != mAudioManager.isMicrophoneMute() && isInActiveState()) {
   1350             IAudioService audio = mAudioServiceFactory.getAudioService();
   1351             Log.i(this, "changing microphone mute state to: %b [serviceIsNull=%b]",
   1352                     mute, audio == null);
   1353             if (audio != null) {
   1354                 try {
   1355                     // We use the audio service directly here so that we can specify
   1356                     // the current user. Telecom runs in the system_server process which
   1357                     // may run as a separate user from the foreground user. If we
   1358                     // used AudioManager directly, we would change mute for the system's
   1359                     // user and not the current foreground, which we want to avoid.
   1360                     audio.setMicrophoneMute(
   1361                             mute, mContext.getOpPackageName(), getCurrentUserId());
   1362                     mStatusBarNotifier.notifyMute(mute);
   1363 
   1364                 } catch (RemoteException e) {
   1365                     Log.e(this, e, "Remote exception while toggling mute.");
   1366                 }
   1367                 // TODO: Check microphone state after attempting to set to ensure that
   1368                 // our state corroborates AudioManager's state.
   1369             }
   1370         }
   1371     }
   1372 
   1373     /**
   1374      * Updates the CallAudioState object from current internal state. The result is used for
   1375      * external communication only.
   1376      */
   1377     private void updateInternalCallAudioState() {
   1378         IState currentState = getCurrentState();
   1379         if (currentState == null) {
   1380             Log.e(this, new IllegalStateException(), "Current state should never be null" +
   1381                     " when updateInternalCallAudioState is called.");
   1382             mCurrentCallAudioState = new CallAudioState(
   1383                     mIsMuted, mCurrentCallAudioState.getRoute(), mAvailableRoutes);
   1384             return;
   1385         }
   1386         int currentRoute = mStateNameToRouteCode.get(currentState.getName());
   1387         mCurrentCallAudioState = new CallAudioState(mIsMuted, currentRoute, mAvailableRoutes);
   1388     }
   1389 
   1390     private void setSystemAudioState(CallAudioState newCallAudioState) {
   1391         setSystemAudioState(newCallAudioState, false);
   1392     }
   1393 
   1394     private void resendSystemAudioState() {
   1395         setSystemAudioState(mLastKnownCallAudioState, true);
   1396     }
   1397 
   1398     private void setSystemAudioState(CallAudioState newCallAudioState, boolean force) {
   1399         synchronized (mLock) {
   1400             Log.i(this, "setSystemAudioState: changing from %s to %s", mLastKnownCallAudioState,
   1401                     newCallAudioState);
   1402             if (force || !newCallAudioState.equals(mLastKnownCallAudioState)) {
   1403                 if (newCallAudioState.getRoute() != mLastKnownCallAudioState.getRoute()) {
   1404                     Log.event(mCallsManager.getForegroundCall(),
   1405                             AUDIO_ROUTE_TO_LOG_EVENT.get(newCallAudioState.getRoute(),
   1406                                     Log.Events.AUDIO_ROUTE));
   1407                 }
   1408 
   1409                 mCallsManager.onCallAudioStateChanged(mLastKnownCallAudioState, newCallAudioState);
   1410                 updateAudioForForegroundCall(newCallAudioState);
   1411                 mLastKnownCallAudioState = newCallAudioState;
   1412             }
   1413         }
   1414     }
   1415 
   1416     private void updateAudioForForegroundCall(CallAudioState newCallAudioState) {
   1417         Call call = mCallsManager.getForegroundCall();
   1418         if (call != null && call.getConnectionService() != null) {
   1419             call.getConnectionService().onCallAudioStateChanged(call, newCallAudioState);
   1420         }
   1421     }
   1422 
   1423     private int calculateSupportedRoutes() {
   1424         int routeMask = CallAudioState.ROUTE_SPEAKER;
   1425 
   1426         if (mWiredHeadsetManager.isPluggedIn()) {
   1427             routeMask |= CallAudioState.ROUTE_WIRED_HEADSET;
   1428         } else if (mDoesDeviceSupportEarpieceRoute){
   1429             routeMask |= CallAudioState.ROUTE_EARPIECE;
   1430         }
   1431 
   1432         if (mBluetoothManager.isBluetoothAvailable()) {
   1433             routeMask |=  CallAudioState.ROUTE_BLUETOOTH;
   1434         }
   1435 
   1436         return routeMask;
   1437     }
   1438 
   1439     private void sendInternalMessage(int messageCode) {
   1440         // Internal messages are messages which the state machine sends to itself in the
   1441         // course of processing externally-sourced messages. We want to send these messages at
   1442         // the front of the queue in order to make actions appear atomic to the user and to
   1443         // prevent scenarios such as these:
   1444         // 1. State machine handler thread is suspended for some reason.
   1445         // 2. Headset gets connected (sends CONNECT_HEADSET).
   1446         // 3. User switches to speakerphone in the UI (sends SWITCH_SPEAKER).
   1447         // 4. State machine handler is un-suspended.
   1448         // 5. State machine handler processes the CONNECT_HEADSET message and sends
   1449         //    SWITCH_HEADSET at end of queue.
   1450         // 6. State machine handler processes SWITCH_SPEAKER.
   1451         // 7. State machine handler processes SWITCH_HEADSET.
   1452         Session subsession = Log.createSubsession();
   1453         if(subsession != null) {
   1454             sendMessageAtFrontOfQueue(messageCode, subsession);
   1455         } else {
   1456             sendMessageAtFrontOfQueue(messageCode);
   1457         }
   1458     }
   1459 
   1460     private CallAudioState getInitialAudioState() {
   1461         int supportedRouteMask = calculateSupportedRoutes();
   1462         final int route;
   1463 
   1464         if ((supportedRouteMask & ROUTE_BLUETOOTH) != 0) {
   1465             route = ROUTE_BLUETOOTH;
   1466         } else if ((supportedRouteMask & ROUTE_WIRED_HEADSET) != 0) {
   1467             route = ROUTE_WIRED_HEADSET;
   1468         } else if ((supportedRouteMask & ROUTE_EARPIECE) != 0) {
   1469             route = ROUTE_EARPIECE;
   1470         } else {
   1471             route = ROUTE_SPEAKER;
   1472         }
   1473 
   1474         return new CallAudioState(false, route, supportedRouteMask);
   1475     }
   1476 
   1477     private int getCurrentUserId() {
   1478         final long ident = Binder.clearCallingIdentity();
   1479         try {
   1480             UserInfo currentUser = ActivityManagerNative.getDefault().getCurrentUser();
   1481             return currentUser.id;
   1482         } catch (RemoteException e) {
   1483             // Activity manager not running, nothing we can do assume user 0.
   1484         } finally {
   1485             Binder.restoreCallingIdentity(ident);
   1486         }
   1487         return UserHandle.USER_OWNER;
   1488     }
   1489 
   1490     private boolean isInActiveState() {
   1491         AudioState currentState = (AudioState) getCurrentState();
   1492         if (currentState == null) {
   1493             Log.w(this, "Current state is null, assuming inactive state");
   1494             return false;
   1495         }
   1496         return currentState.isActive();
   1497     }
   1498 
   1499     public static boolean doesDeviceSupportEarpieceRoute() {
   1500         String[] characteristics = SystemProperties.get("ro.build.characteristics").split(",");
   1501         for (String characteristic : characteristics) {
   1502             if ("watch".equals(characteristic)) {
   1503                 return false;
   1504             }
   1505         }
   1506         return true;
   1507     }
   1508 
   1509     private int calculateBaselineRouteMessage(boolean isExplicitUserRequest) {
   1510         if ((mAvailableRoutes & ROUTE_EARPIECE) != 0) {
   1511             return isExplicitUserRequest ? USER_SWITCH_EARPIECE : SWITCH_EARPIECE;
   1512         } else if ((mAvailableRoutes & ROUTE_WIRED_HEADSET) != 0) {
   1513             return isExplicitUserRequest ? USER_SWITCH_HEADSET : SWITCH_HEADSET;
   1514         } else if (!mDoesDeviceSupportEarpieceRoute) {
   1515             return isExplicitUserRequest ? USER_SWITCH_SPEAKER : SWITCH_SPEAKER;
   1516         } else {
   1517             Log.e(this, new IllegalStateException(),
   1518                     "Neither headset nor earpiece are available, but there is an " +
   1519                             "earpiece on the device. Defaulting to earpiece.");
   1520             return isExplicitUserRequest ? USER_SWITCH_EARPIECE : SWITCH_EARPIECE;
   1521         }
   1522     }
   1523 
   1524     private void reinitialize() {
   1525         CallAudioState initState = getInitialAudioState();
   1526         mAvailableRoutes = initState.getSupportedRouteMask();
   1527         mIsMuted = initState.isMuted();
   1528         setMuteOn(mIsMuted);
   1529         mWasOnSpeaker = false;
   1530         mHasUserExplicitlyLeftBluetooth = false;
   1531         mLastKnownCallAudioState = initState;
   1532         transitionTo(mRouteCodeToQuiescentState.get(initState.getRoute()));
   1533     }
   1534 }
   1535