Home | History | Annotate | Download | only in connserv
      1 /*
      2  * Copyright (C) 2016 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 package com.android.bluetooth.hfpclient.connserv;
     17 
     18 import android.bluetooth.BluetoothDevice;
     19 import android.bluetooth.BluetoothHeadsetClient;
     20 import android.bluetooth.BluetoothHeadsetClientCall;
     21 import android.bluetooth.BluetoothProfile;
     22 import android.content.Context;
     23 import android.net.Uri;
     24 import android.telecom.Connection;
     25 import android.telecom.DisconnectCause;
     26 import android.telecom.TelecomManager;
     27 import android.util.Log;
     28 
     29 public class HfpClientConnection extends Connection {
     30     private static final String TAG = "HfpClientConnection";
     31 
     32     private final Context mContext;
     33     private final BluetoothDevice mDevice;
     34 
     35     private BluetoothHeadsetClient mHeadsetProfile;
     36     private BluetoothHeadsetClientCall mCurrentCall;
     37     private boolean mClosed;
     38     private boolean mLocalDisconnect;
     39     private boolean mClientHasEcc;
     40     private boolean mAdded;
     41 
     42     public HfpClientConnection(Context context, BluetoothDevice device, BluetoothHeadsetClient client,
     43             BluetoothHeadsetClientCall call, Uri number) {
     44         mDevice = device;
     45         mContext = context;
     46         mHeadsetProfile = client;
     47         mCurrentCall = call;
     48         if (mHeadsetProfile != null) {
     49             mClientHasEcc = HfpClientConnectionService.hasHfpClientEcc(mHeadsetProfile, mDevice);
     50         }
     51         setAudioModeIsVoip(false);
     52         setAddress(number, TelecomManager.PRESENTATION_ALLOWED);
     53         setInitialized();
     54 
     55         if (mHeadsetProfile != null) {
     56             finishInitializing();
     57         }
     58     }
     59 
     60     public void onHfpConnected(BluetoothHeadsetClient client) {
     61         mHeadsetProfile = client;
     62         mClientHasEcc = HfpClientConnectionService.hasHfpClientEcc(mHeadsetProfile, mDevice);
     63         finishInitializing();
     64     }
     65 
     66     public void onHfpDisconnected() {
     67         mHeadsetProfile = null;
     68         close(DisconnectCause.ERROR);
     69     }
     70 
     71     public void onAdded() {
     72         mAdded = true;
     73     }
     74 
     75     public BluetoothHeadsetClientCall getCall() {
     76         return mCurrentCall;
     77     }
     78 
     79     public boolean inConference() {
     80         return mAdded && mCurrentCall != null && mCurrentCall.isMultiParty() &&
     81                 getState() != Connection.STATE_DISCONNECTED;
     82     }
     83 
     84     public void enterPrivateMode() {
     85         mHeadsetProfile.enterPrivateMode(mDevice, mCurrentCall.getId());
     86         setActive();
     87     }
     88 
     89     public void handleCallChanged(BluetoothHeadsetClientCall call) {
     90         HfpClientConference conference = (HfpClientConference) getConference();
     91         mCurrentCall = call;
     92 
     93         int state = call.getState();
     94         Log.d(TAG, "Got call state change to " + state);
     95         switch (state) {
     96             case BluetoothHeadsetClientCall.CALL_STATE_ACTIVE:
     97                 setActive();
     98                 if (conference != null) {
     99                     conference.setActive();
    100                 }
    101                 break;
    102             case BluetoothHeadsetClientCall.CALL_STATE_HELD_BY_RESPONSE_AND_HOLD:
    103             case BluetoothHeadsetClientCall.CALL_STATE_HELD:
    104                 setOnHold();
    105                 if (conference != null) {
    106                     conference.setOnHold();
    107                 }
    108                 break;
    109             case BluetoothHeadsetClientCall.CALL_STATE_DIALING:
    110             case BluetoothHeadsetClientCall.CALL_STATE_ALERTING:
    111                 setDialing();
    112                 break;
    113             case BluetoothHeadsetClientCall.CALL_STATE_INCOMING:
    114             case BluetoothHeadsetClientCall.CALL_STATE_WAITING:
    115                 setRinging();
    116                 break;
    117             case BluetoothHeadsetClientCall.CALL_STATE_TERMINATED:
    118                 // TODO Use more specific causes
    119                 close(mLocalDisconnect ? DisconnectCause.LOCAL : DisconnectCause.REMOTE);
    120                 break;
    121             default:
    122                 Log.wtf(TAG, "Unexpected phone state " + state);
    123         }
    124         setConnectionCapabilities(CAPABILITY_SUPPORT_HOLD | CAPABILITY_MUTE |
    125                 CAPABILITY_SEPARATE_FROM_CONFERENCE | CAPABILITY_DISCONNECT_FROM_CONFERENCE |
    126                 (getState() == STATE_ACTIVE || getState() == STATE_HOLDING ? CAPABILITY_HOLD : 0));
    127     }
    128 
    129     private void finishInitializing() {
    130         if (mCurrentCall == null) {
    131             String number = getAddress().getSchemeSpecificPart();
    132             Log.d(TAG, "Dialing " + number);
    133             mHeadsetProfile.dial(mDevice, number);
    134             setDialing();
    135             // We will change state dependent on broadcasts from BluetoothHeadsetClientCall.
    136         } else {
    137             handleCallChanged(mCurrentCall);
    138         }
    139     }
    140 
    141     private void close(int cause) {
    142         Log.d(TAG, "Closing " + mClosed);
    143         if (mClosed) {
    144             return;
    145         }
    146         setDisconnected(new DisconnectCause(cause));
    147 
    148         mClosed = true;
    149         mCurrentCall = null;
    150 
    151         destroy();
    152     }
    153 
    154     @Override
    155     public void onPlayDtmfTone(char c) {
    156         Log.d(TAG, "onPlayDtmfTone " + c + " " + mCurrentCall);
    157         if (!mClosed && mHeadsetProfile != null) {
    158             mHeadsetProfile.sendDTMF(mDevice, (byte) c);
    159         }
    160     }
    161 
    162     @Override
    163     public void onDisconnect() {
    164         Log.d(TAG, "onDisconnect " + mCurrentCall);
    165         if (!mClosed) {
    166             if (mHeadsetProfile != null && mCurrentCall != null) {
    167                 mHeadsetProfile.terminateCall(mDevice, mClientHasEcc ? mCurrentCall.getId() : 0);
    168                 mLocalDisconnect = true;
    169             } else {
    170                 close(DisconnectCause.LOCAL);
    171             }
    172         }
    173     }
    174 
    175     @Override
    176     public void onAbort() {
    177         Log.d(TAG, "onAbort " + mCurrentCall);
    178         onDisconnect();
    179     }
    180 
    181     @Override
    182     public void onHold() {
    183         Log.d(TAG, "onHold " + mCurrentCall);
    184         if (!mClosed && mHeadsetProfile != null) {
    185             mHeadsetProfile.holdCall(mDevice);
    186         }
    187     }
    188 
    189     @Override
    190     public void onUnhold() {
    191         if (getConnectionService().getAllConnections().size() > 1) {
    192             Log.w(TAG, "Ignoring unhold; call hold on the foreground call");
    193             return;
    194         }
    195         Log.d(TAG, "onUnhold " + mCurrentCall);
    196         if (!mClosed && mHeadsetProfile != null) {
    197             mHeadsetProfile.acceptCall(mDevice, BluetoothHeadsetClient.CALL_ACCEPT_HOLD);
    198         }
    199     }
    200 
    201     @Override
    202     public void onAnswer() {
    203         Log.d(TAG, "onAnswer " + mCurrentCall);
    204         if (!mClosed) {
    205             mHeadsetProfile.acceptCall(mDevice, BluetoothHeadsetClient.CALL_ACCEPT_NONE);
    206         }
    207     }
    208 
    209     @Override
    210     public void onReject() {
    211         Log.d(TAG, "onReject " + mCurrentCall);
    212         if (!mClosed) {
    213             mHeadsetProfile.rejectCall(mDevice);
    214         }
    215     }
    216 
    217     @Override
    218     public boolean equals(Object o) {
    219         if (!(o instanceof HfpClientConnection)) {
    220             return false;
    221         }
    222         Uri otherAddr = ((HfpClientConnection) o).getAddress();
    223         return getAddress() == otherAddr || otherAddr != null && otherAddr.equals(getAddress());
    224     }
    225 
    226     @Override
    227     public int hashCode() {
    228         return getAddress() == null ? 0 : getAddress().hashCode();
    229     }
    230 
    231     @Override
    232     public String toString() {
    233         return "HfpClientConnection{" + getAddress() + "," + stateToString(getState()) + "," +
    234                 mCurrentCall + "}";
    235     }
    236 }
    237