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.PhoneAccount;
     27 import android.telecom.TelecomManager;
     28 import android.util.Log;
     29 
     30 import java.util.UUID;
     31 
     32 public class HfpClientConnection extends Connection {
     33     private static final String TAG = "HfpClientConnection";
     34     private static final boolean DBG = false;
     35 
     36     private final Context mContext;
     37     private final BluetoothDevice mDevice;
     38     private BluetoothHeadsetClient mHeadsetProfile;
     39 
     40     private BluetoothHeadsetClientCall mCurrentCall;
     41     private int mPreviousCallState = -1;
     42     private boolean mClosed;
     43     private boolean mClosing = false;
     44     private boolean mLocalDisconnect;
     45     private boolean mClientHasEcc;
     46     private boolean mAdded;
     47 
     48     // Constructor to be used when there's an existing call (such as that created on the AG or
     49     // when connection happens and we see calls for the first time).
     50     public HfpClientConnection(Context context, BluetoothDevice device,
     51             BluetoothHeadsetClient client, BluetoothHeadsetClientCall call) {
     52         mDevice = device;
     53         mContext = context;
     54         mHeadsetProfile = client;
     55 
     56         if (call == null) {
     57             throw new IllegalStateException("Call is null");
     58         }
     59 
     60         mCurrentCall = call;
     61         handleCallChanged();
     62         finishInitializing();
     63     }
     64 
     65     // Constructor to be used when a call is intiated on the HF. The call handle is obtained by
     66     // using the dial() command.
     67     public HfpClientConnection(Context context, BluetoothDevice device,
     68             BluetoothHeadsetClient client, Uri number) {
     69         mDevice = device;
     70         mContext = context;
     71         mHeadsetProfile = client;
     72 
     73         if (mHeadsetProfile == null) {
     74             throw new IllegalStateException("HeadsetProfile is null, returning");
     75         }
     76 
     77         mCurrentCall = mHeadsetProfile.dial(
     78             mDevice, number.getSchemeSpecificPart());
     79         if (mCurrentCall == null) {
     80             close(DisconnectCause.ERROR);
     81             Log.e(TAG, "Failed to create the call, dial failed.");
     82             return;
     83         }
     84 
     85         mHeadsetProfile.connectAudio(device);
     86         setInitializing();
     87         setDialing();
     88         finishInitializing();
     89     }
     90 
     91     void finishInitializing() {
     92         mClientHasEcc = HfpClientConnectionService.hasHfpClientEcc(mHeadsetProfile, mDevice);
     93         setAudioModeIsVoip(false);
     94         Uri number = Uri.fromParts(PhoneAccount.SCHEME_TEL, mCurrentCall.getNumber(), null);
     95         setAddress(number, TelecomManager.PRESENTATION_ALLOWED);
     96         setConnectionCapabilities(CAPABILITY_SUPPORT_HOLD | CAPABILITY_MUTE |
     97                 CAPABILITY_SEPARATE_FROM_CONFERENCE | CAPABILITY_DISCONNECT_FROM_CONFERENCE |
     98                 (getState() == STATE_ACTIVE || getState() == STATE_HOLDING ? CAPABILITY_HOLD : 0));
     99     }
    100 
    101     public UUID getUUID() {
    102         return mCurrentCall.getUUID();
    103     }
    104 
    105     public void onHfpDisconnected() {
    106         mHeadsetProfile = null;
    107         close(DisconnectCause.ERROR);
    108     }
    109 
    110     public void onAdded() {
    111         mAdded = true;
    112     }
    113 
    114     public BluetoothHeadsetClientCall getCall() {
    115         return mCurrentCall;
    116     }
    117 
    118     public boolean inConference() {
    119         return mAdded && mCurrentCall != null && mCurrentCall.isMultiParty() &&
    120                 getState() != Connection.STATE_DISCONNECTED;
    121     }
    122 
    123     public void enterPrivateMode() {
    124         mHeadsetProfile.enterPrivateMode(mDevice, mCurrentCall.getId());
    125         setActive();
    126     }
    127 
    128     public void updateCall(BluetoothHeadsetClientCall call) {
    129         if (call == null) {
    130             Log.e(TAG, "Updating call to a null value.");
    131             return;
    132         }
    133         mCurrentCall = call;
    134     }
    135 
    136     public void handleCallChanged() {
    137         HfpClientConference conference = (HfpClientConference) getConference();
    138         int state = mCurrentCall.getState();
    139 
    140         if (DBG) {
    141             Log.d(TAG, "Got call state change to " + state);
    142         }
    143         switch (state) {
    144             case BluetoothHeadsetClientCall.CALL_STATE_ACTIVE:
    145                 setActive();
    146                 if (conference != null) {
    147                     conference.setActive();
    148                 }
    149                 break;
    150             case BluetoothHeadsetClientCall.CALL_STATE_HELD_BY_RESPONSE_AND_HOLD:
    151             case BluetoothHeadsetClientCall.CALL_STATE_HELD:
    152                 setOnHold();
    153                 if (conference != null) {
    154                     conference.setOnHold();
    155                 }
    156                 break;
    157             case BluetoothHeadsetClientCall.CALL_STATE_DIALING:
    158             case BluetoothHeadsetClientCall.CALL_STATE_ALERTING:
    159                 setDialing();
    160                 break;
    161             case BluetoothHeadsetClientCall.CALL_STATE_INCOMING:
    162             case BluetoothHeadsetClientCall.CALL_STATE_WAITING:
    163                 setRinging();
    164                 break;
    165             case BluetoothHeadsetClientCall.CALL_STATE_TERMINATED:
    166                 if (mPreviousCallState == BluetoothHeadsetClientCall.CALL_STATE_INCOMING
    167                         || mPreviousCallState == BluetoothHeadsetClientCall.CALL_STATE_WAITING) {
    168                     close(DisconnectCause.MISSED);
    169                 } else if (mLocalDisconnect) {
    170                     close(DisconnectCause.LOCAL);
    171                 } else {
    172                     close(DisconnectCause.REMOTE);
    173                 }
    174                 break;
    175             default:
    176                 Log.wtf(TAG, "Unexpected phone state " + state);
    177         }
    178         mPreviousCallState = state;
    179     }
    180 
    181     public synchronized void close(int cause) {
    182         if (DBG) {
    183             Log.d(TAG, "Closing call " + mCurrentCall + "state: " + mClosed);
    184         }
    185         if (mClosed) {
    186             return;
    187         }
    188         Log.d(TAG, "Setting " + mCurrentCall + " to disconnected " + getTelecomCallId());
    189         setDisconnected(new DisconnectCause(cause));
    190 
    191         mClosed = true;
    192         mCurrentCall = null;
    193 
    194         destroy();
    195     }
    196 
    197     public synchronized boolean isClosing() {
    198         return mClosing;
    199     }
    200 
    201     public synchronized BluetoothDevice getDevice() {
    202         return mDevice;
    203     }
    204 
    205     @Override
    206     public synchronized void onPlayDtmfTone(char c) {
    207         if (DBG) {
    208             Log.d(TAG, "onPlayDtmfTone " + c + " " + mCurrentCall);
    209         }
    210         if (!mClosed) {
    211             mHeadsetProfile.sendDTMF(mDevice, (byte) c);
    212         }
    213     }
    214 
    215     @Override
    216     public synchronized void onDisconnect() {
    217         if (DBG) {
    218             Log.d(TAG, "onDisconnect call: " + mCurrentCall + " state: " + mClosed);
    219         }
    220         // The call is not closed so we should send a terminate here.
    221         if (!mClosed) {
    222             mHeadsetProfile.terminateCall(mDevice, mCurrentCall);
    223             mLocalDisconnect = true;
    224             mClosing = true;
    225         }
    226     }
    227 
    228     @Override
    229     public void onAbort() {
    230         if (DBG) {
    231             Log.d(TAG, "onAbort " + mCurrentCall);
    232         }
    233         onDisconnect();
    234     }
    235 
    236     @Override
    237     public synchronized void onHold() {
    238         if (DBG) {
    239             Log.d(TAG, "onHold " + mCurrentCall);
    240         }
    241         if (!mClosed) {
    242             mHeadsetProfile.holdCall(mDevice);
    243         }
    244     }
    245 
    246     @Override
    247     public synchronized void onUnhold() {
    248         if (getConnectionService().getAllConnections().size() > 1) {
    249             Log.w(TAG, "Ignoring unhold; call hold on the foreground call");
    250             return;
    251         }
    252         if (DBG) {
    253             Log.d(TAG, "onUnhold " + mCurrentCall);
    254         }
    255         if (!mClosed) {
    256             mHeadsetProfile.acceptCall(mDevice, BluetoothHeadsetClient.CALL_ACCEPT_HOLD);
    257         }
    258     }
    259 
    260     @Override
    261     public synchronized void onAnswer() {
    262         if (DBG) {
    263             Log.d(TAG, "onAnswer " + mCurrentCall);
    264         }
    265         if (!mClosed) {
    266             mHeadsetProfile.acceptCall(mDevice, BluetoothHeadsetClient.CALL_ACCEPT_NONE);
    267         }
    268         mHeadsetProfile.connectAudio(mDevice);
    269     }
    270 
    271     @Override
    272     public synchronized void onReject() {
    273         if (DBG) {
    274             Log.d(TAG, "onReject " + mCurrentCall);
    275         }
    276         if (!mClosed) {
    277             mHeadsetProfile.rejectCall(mDevice);
    278         }
    279     }
    280 
    281     @Override
    282     public boolean equals(Object o) {
    283         if (!(o instanceof HfpClientConnection)) {
    284             return false;
    285         }
    286         Uri otherAddr = ((HfpClientConnection) o).getAddress();
    287         return getAddress() == otherAddr || otherAddr != null && otherAddr.equals(getAddress());
    288     }
    289 
    290     @Override
    291     public String toString() {
    292         return "HfpClientConnection{" + getAddress() + "," + stateToString(getState()) + "," +
    293                 mCurrentCall + "}";
    294     }
    295 }
    296