Home | History | Annotate | Download | only in hfp
      1 /*
      2  * Copyright 2017 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.bluetooth.hfp;
     18 
     19 import android.bluetooth.BluetoothDevice;
     20 import android.bluetooth.BluetoothHeadset;
     21 import android.bluetooth.IBluetoothHeadsetPhone;
     22 import android.content.ActivityNotFoundException;
     23 import android.content.ComponentName;
     24 import android.content.Context;
     25 import android.content.Intent;
     26 import android.content.ServiceConnection;
     27 import android.media.AudioManager;
     28 import android.os.IBinder;
     29 import android.os.PowerManager;
     30 import android.os.RemoteException;
     31 import android.util.Log;
     32 
     33 import com.android.internal.annotations.VisibleForTesting;
     34 
     35 /**
     36  * Defines system calls that is used by state machine/service to either send or receive
     37  * messages from the Android System.
     38  */
     39 @VisibleForTesting
     40 public class HeadsetSystemInterface {
     41     private static final String TAG = HeadsetSystemInterface.class.getSimpleName();
     42     private static final boolean DBG = false;
     43 
     44     private final HeadsetService mHeadsetService;
     45     private final AudioManager mAudioManager;
     46     private final HeadsetPhoneState mHeadsetPhoneState;
     47     private PowerManager.WakeLock mVoiceRecognitionWakeLock;
     48     private volatile IBluetoothHeadsetPhone mPhoneProxy;
     49     private final ServiceConnection mPhoneProxyConnection = new ServiceConnection() {
     50         @Override
     51         public void onServiceConnected(ComponentName className, IBinder service) {
     52             if (DBG) {
     53                 Log.d(TAG, "Proxy object connected");
     54             }
     55             synchronized (HeadsetSystemInterface.this) {
     56                 mPhoneProxy = IBluetoothHeadsetPhone.Stub.asInterface(service);
     57             }
     58         }
     59 
     60         @Override
     61         public void onServiceDisconnected(ComponentName className) {
     62             if (DBG) {
     63                 Log.d(TAG, "Proxy object disconnected");
     64             }
     65             synchronized (HeadsetSystemInterface.this) {
     66                 mPhoneProxy = null;
     67             }
     68         }
     69     };
     70 
     71     HeadsetSystemInterface(HeadsetService headsetService) {
     72         if (headsetService == null) {
     73             Log.wtfStack(TAG, "HeadsetService parameter is null");
     74         }
     75         mHeadsetService = headsetService;
     76         mAudioManager = (AudioManager) mHeadsetService.getSystemService(Context.AUDIO_SERVICE);
     77         PowerManager powerManager =
     78                 (PowerManager) mHeadsetService.getSystemService(Context.POWER_SERVICE);
     79         mVoiceRecognitionWakeLock =
     80                 powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG + ":VoiceRecognition");
     81         mVoiceRecognitionWakeLock.setReferenceCounted(false);
     82         mHeadsetPhoneState = new HeadsetPhoneState(mHeadsetService);
     83     }
     84 
     85     /**
     86      * Initialize this system interface
     87      */
     88     public synchronized void init() {
     89         // Bind to Telecom phone proxy service
     90         Intent intent = new Intent(IBluetoothHeadsetPhone.class.getName());
     91         intent.setComponent(intent.resolveSystemService(mHeadsetService.getPackageManager(), 0));
     92         if (intent.getComponent() == null || !mHeadsetService.bindService(intent,
     93                 mPhoneProxyConnection, 0)) {
     94             // Crash the stack if cannot bind to Telecom
     95             Log.wtfStack(TAG, "Could not bind to IBluetoothHeadsetPhone Service, intent=" + intent);
     96         }
     97     }
     98 
     99     /**
    100      * Stop this system interface
    101      */
    102     public synchronized void stop() {
    103         if (mPhoneProxy != null) {
    104             if (DBG) {
    105                 Log.d(TAG, "Unbinding phone proxy");
    106             }
    107             mPhoneProxy = null;
    108             // Synchronization should make sure unbind can be successful
    109             mHeadsetService.unbindService(mPhoneProxyConnection);
    110         }
    111         mHeadsetPhoneState.cleanup();
    112     }
    113 
    114     /**
    115      * Get audio manager. Most audio manager oprations are pass through and therefore are not
    116      * individually managed by this class
    117      *
    118      * @return audio manager for setting audio parameters
    119      */
    120     @VisibleForTesting
    121     public AudioManager getAudioManager() {
    122         return mAudioManager;
    123     }
    124 
    125     /**
    126      * Get wake lock for voice recognition
    127      *
    128      * @return wake lock for voice recognition
    129      */
    130     @VisibleForTesting
    131     public PowerManager.WakeLock getVoiceRecognitionWakeLock() {
    132         return mVoiceRecognitionWakeLock;
    133     }
    134 
    135     /**
    136      * Get HeadsetPhoneState instance to interact with Telephony service
    137      *
    138      * @return HeadsetPhoneState interface to interact with Telephony service
    139      */
    140     @VisibleForTesting
    141     public HeadsetPhoneState getHeadsetPhoneState() {
    142         return mHeadsetPhoneState;
    143     }
    144 
    145     /**
    146      * Answer the current incoming call in Telecom service
    147      *
    148      * @param device the Bluetooth device used for answering this call
    149      */
    150     @VisibleForTesting
    151     public void answerCall(BluetoothDevice device) {
    152         if (device == null) {
    153             Log.w(TAG, "answerCall device is null");
    154             return;
    155         }
    156 
    157         if (mPhoneProxy != null) {
    158             try {
    159                 mHeadsetService.setActiveDevice(device);
    160                 mPhoneProxy.answerCall();
    161             } catch (RemoteException e) {
    162                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
    163             }
    164         } else {
    165             Log.e(TAG, "Handsfree phone proxy null for answering call");
    166         }
    167     }
    168 
    169     /**
    170      * Hangup the current call, could either be Telecom call or virtual call
    171      *
    172      * @param device the Bluetooth device used for hanging up this call
    173      */
    174     @VisibleForTesting
    175     public void hangupCall(BluetoothDevice device) {
    176         if (device == null) {
    177             Log.w(TAG, "hangupCall device is null");
    178             return;
    179         }
    180         // Close the virtual call if active. Virtual call should be
    181         // terminated for CHUP callback event
    182         if (mHeadsetService.isVirtualCallStarted()) {
    183             mHeadsetService.stopScoUsingVirtualVoiceCall();
    184         } else {
    185             if (mPhoneProxy != null) {
    186                 try {
    187                     mPhoneProxy.hangupCall();
    188                 } catch (RemoteException e) {
    189                     Log.e(TAG, Log.getStackTraceString(new Throwable()));
    190                 }
    191             } else {
    192                 Log.e(TAG, "Handsfree phone proxy null for hanging up call");
    193             }
    194         }
    195     }
    196 
    197     /**
    198      * Instructs Telecom to play the specified DTMF tone for the current foreground call
    199      *
    200      * @param dtmf dtmf code
    201      * @param device the Bluetooth device that sent this code
    202      */
    203     @VisibleForTesting
    204     public boolean sendDtmf(int dtmf, BluetoothDevice device) {
    205         if (device == null) {
    206             Log.w(TAG, "sendDtmf device is null");
    207             return false;
    208         }
    209         if (mPhoneProxy != null) {
    210             try {
    211                 return mPhoneProxy.sendDtmf(dtmf);
    212             } catch (RemoteException e) {
    213                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
    214             }
    215         } else {
    216             Log.e(TAG, "Handsfree phone proxy null for sending DTMF");
    217         }
    218         return false;
    219     }
    220 
    221     /**
    222      * Instructs Telecom hold an incoming call
    223      *
    224      * @param chld index of the call to hold
    225      */
    226     @VisibleForTesting
    227     public boolean processChld(int chld) {
    228         if (mPhoneProxy != null) {
    229             try {
    230                 return mPhoneProxy.processChld(chld);
    231             } catch (RemoteException e) {
    232                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
    233             }
    234         } else {
    235             Log.e(TAG, "Handsfree phone proxy null for sending DTMF");
    236         }
    237         return false;
    238     }
    239 
    240     /**
    241      * Get the the alphabetic name of current registered operator.
    242      *
    243      * @return null on error, empty string if not available
    244      */
    245     @VisibleForTesting
    246     public String getNetworkOperator() {
    247         final IBluetoothHeadsetPhone phoneProxy = mPhoneProxy;
    248         if (phoneProxy == null) {
    249             Log.e(TAG, "getNetworkOperator() failed: mPhoneProxy is null");
    250             return null;
    251         }
    252         try {
    253             // Should never return null
    254             return mPhoneProxy.getNetworkOperator();
    255         } catch (RemoteException exception) {
    256             Log.e(TAG, "getNetworkOperator() failed: " + exception.getMessage());
    257             exception.printStackTrace();
    258             return null;
    259         }
    260     }
    261 
    262     /**
    263      * Get the phone number of this device
    264      *
    265      * @return null if unavailable
    266      */
    267     @VisibleForTesting
    268     public String getSubscriberNumber() {
    269         final IBluetoothHeadsetPhone phoneProxy = mPhoneProxy;
    270         if (phoneProxy == null) {
    271             Log.e(TAG, "getSubscriberNumber() failed: mPhoneProxy is null");
    272             return null;
    273         }
    274         try {
    275             return mPhoneProxy.getSubscriberNumber();
    276         } catch (RemoteException exception) {
    277             Log.e(TAG, "getSubscriberNumber() failed: " + exception.getMessage());
    278             exception.printStackTrace();
    279             return null;
    280         }
    281     }
    282 
    283 
    284     /**
    285      * Ask the Telecomm service to list current list of calls through CLCC response
    286      * {@link BluetoothHeadset#clccResponse(int, int, int, int, boolean, String, int)}
    287      *
    288      * @return
    289      */
    290     @VisibleForTesting
    291     public boolean listCurrentCalls() {
    292         final IBluetoothHeadsetPhone phoneProxy = mPhoneProxy;
    293         if (phoneProxy == null) {
    294             Log.e(TAG, "listCurrentCalls() failed: mPhoneProxy is null");
    295             return false;
    296         }
    297         try {
    298             return mPhoneProxy.listCurrentCalls();
    299         } catch (RemoteException exception) {
    300             Log.e(TAG, "listCurrentCalls() failed: " + exception.getMessage());
    301             exception.printStackTrace();
    302             return false;
    303         }
    304     }
    305 
    306     /**
    307      * Request Telecom service to send an update of the current call state to the headset service
    308      * through {@link BluetoothHeadset#phoneStateChanged(int, int, int, String, int)}
    309      */
    310     @VisibleForTesting
    311     public void queryPhoneState() {
    312         final IBluetoothHeadsetPhone phoneProxy = mPhoneProxy;
    313         if (phoneProxy != null) {
    314             try {
    315                 mPhoneProxy.queryPhoneState();
    316             } catch (RemoteException e) {
    317                 Log.e(TAG, Log.getStackTraceString(new Throwable()));
    318             }
    319         } else {
    320             Log.e(TAG, "Handsfree phone proxy null for query phone state");
    321         }
    322     }
    323 
    324     /**
    325      * Check if we are currently in a phone call
    326      *
    327      * @return True iff we are in a phone call
    328      */
    329     @VisibleForTesting
    330     public boolean isInCall() {
    331         return ((mHeadsetPhoneState.getNumActiveCall() > 0) || (mHeadsetPhoneState.getNumHeldCall()
    332                 > 0) || ((mHeadsetPhoneState.getCallState() != HeadsetHalConstants.CALL_STATE_IDLE)
    333                 && (mHeadsetPhoneState.getCallState() != HeadsetHalConstants.CALL_STATE_INCOMING)));
    334     }
    335 
    336     /**
    337      * Check if there is currently an incoming call
    338      *
    339      * @return True iff there is an incoming call
    340      */
    341     @VisibleForTesting
    342     public boolean isRinging() {
    343         return mHeadsetPhoneState.getCallState() == HeadsetHalConstants.CALL_STATE_INCOMING;
    344     }
    345 
    346     /**
    347      * Check if call status is idle
    348      *
    349      * @return true if call state is neither ringing nor in call
    350      */
    351     @VisibleForTesting
    352     public boolean isCallIdle() {
    353         return !isInCall() && !isRinging();
    354     }
    355 
    356     /**
    357      * Activate voice recognition on Android system
    358      *
    359      * @return true if activation succeeds, caller should wait for
    360      * {@link BluetoothHeadset#startVoiceRecognition(BluetoothDevice)} callback that will then
    361      * trigger {@link HeadsetService#startVoiceRecognition(BluetoothDevice)}, false if failed to
    362      * activate
    363      */
    364     @VisibleForTesting
    365     public boolean activateVoiceRecognition() {
    366         Intent intent = new Intent(Intent.ACTION_VOICE_COMMAND);
    367         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    368         try {
    369             mHeadsetService.startActivity(intent);
    370         } catch (ActivityNotFoundException e) {
    371             Log.e(TAG, "activateVoiceRecognition, failed due to activity not found for " + intent);
    372             return false;
    373         }
    374         return true;
    375     }
    376 
    377     /**
    378      * Deactivate voice recognition on Android system
    379      *
    380      * @return true if activation succeeds, caller should wait for
    381      * {@link BluetoothHeadset#stopVoiceRecognition(BluetoothDevice)} callback that will then
    382      * trigger {@link HeadsetService#stopVoiceRecognition(BluetoothDevice)}, false if failed to
    383      * activate
    384      */
    385     @VisibleForTesting
    386     public boolean deactivateVoiceRecognition() {
    387         // TODO: need a method to deactivate voice recognition on Android
    388         return true;
    389     }
    390 
    391 }
    392