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