| /* |
| * Copyright 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.bluetooth.hfp; |
| |
| import android.bluetooth.BluetoothAdapter; |
| import android.bluetooth.BluetoothDevice; |
| import android.util.Log; |
| |
| import com.android.bluetooth.Utils; |
| import com.android.internal.annotations.VisibleForTesting; |
| |
| /** |
| * Defines native calls that are used by state machine/service to either send or receive |
| * messages to/from the native stack. This file is registered for the native methods in |
| * corresponding CPP file. |
| */ |
| public class HeadsetNativeInterface { |
| private static final String TAG = "HeadsetNativeInterface"; |
| |
| private final BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter(); |
| |
| static { |
| classInitNative(); |
| } |
| |
| private static HeadsetNativeInterface sInterface; |
| private static final Object INSTANCE_LOCK = new Object(); |
| |
| private HeadsetNativeInterface() {} |
| |
| /** |
| * This class is a singleton because native library should only be loaded once |
| * |
| * @return default instance |
| */ |
| public static HeadsetNativeInterface getInstance() { |
| synchronized (INSTANCE_LOCK) { |
| if (sInterface == null) { |
| sInterface = new HeadsetNativeInterface(); |
| } |
| } |
| return sInterface; |
| } |
| |
| private void sendMessageToService(HeadsetStackEvent event) { |
| HeadsetService service = HeadsetService.getHeadsetService(); |
| if (service != null) { |
| service.messageFromNative(event); |
| } else { |
| // Service must call cleanup() when quiting and native stack shouldn't send any event |
| // after cleanup() -> cleanupNative() is called. |
| Log.wtf(TAG, "FATAL: Stack sent event while service is not available: " + event); |
| } |
| } |
| |
| private BluetoothDevice getDevice(byte[] address) { |
| return mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address)); |
| } |
| |
| void onConnectionStateChanged(int state, byte[] address) { |
| HeadsetStackEvent event = |
| new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED, state, |
| getDevice(address)); |
| sendMessageToService(event); |
| } |
| |
| // Callbacks for native code |
| |
| private void onAudioStateChanged(int state, byte[] address) { |
| HeadsetStackEvent event = |
| new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_AUDIO_STATE_CHANGED, state, |
| getDevice(address)); |
| sendMessageToService(event); |
| } |
| |
| private void onVrStateChanged(int state, byte[] address) { |
| HeadsetStackEvent event = |
| new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_VR_STATE_CHANGED, state, |
| getDevice(address)); |
| sendMessageToService(event); |
| } |
| |
| private void onAnswerCall(byte[] address) { |
| HeadsetStackEvent event = |
| new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_ANSWER_CALL, getDevice(address)); |
| sendMessageToService(event); |
| } |
| |
| private void onHangupCall(byte[] address) { |
| HeadsetStackEvent event = |
| new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_HANGUP_CALL, getDevice(address)); |
| sendMessageToService(event); |
| } |
| |
| private void onVolumeChanged(int type, int volume, byte[] address) { |
| HeadsetStackEvent event = |
| new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_VOLUME_CHANGED, type, volume, |
| getDevice(address)); |
| sendMessageToService(event); |
| } |
| |
| private void onDialCall(String number, byte[] address) { |
| HeadsetStackEvent event = |
| new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_DIAL_CALL, number, |
| getDevice(address)); |
| sendMessageToService(event); |
| } |
| |
| private void onSendDtmf(int dtmf, byte[] address) { |
| HeadsetStackEvent event = |
| new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_SEND_DTMF, dtmf, |
| getDevice(address)); |
| sendMessageToService(event); |
| } |
| |
| private void onNoiseReductionEnable(boolean enable, byte[] address) { |
| HeadsetStackEvent event = |
| new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_NOISE_REDUCTION, enable ? 1 : 0, |
| getDevice(address)); |
| sendMessageToService(event); |
| } |
| |
| private void onWBS(int codec, byte[] address) { |
| HeadsetStackEvent event = |
| new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_WBS, codec, getDevice(address)); |
| sendMessageToService(event); |
| } |
| |
| private void onAtChld(int chld, byte[] address) { |
| HeadsetStackEvent event = new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_AT_CHLD, chld, |
| getDevice(address)); |
| sendMessageToService(event); |
| } |
| |
| private void onAtCnum(byte[] address) { |
| HeadsetStackEvent event = |
| new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_SUBSCRIBER_NUMBER_REQUEST, |
| getDevice(address)); |
| sendMessageToService(event); |
| } |
| |
| private void onAtCind(byte[] address) { |
| HeadsetStackEvent event = |
| new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_AT_CIND, getDevice(address)); |
| sendMessageToService(event); |
| } |
| |
| private void onAtCops(byte[] address) { |
| HeadsetStackEvent event = |
| new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_AT_COPS, getDevice(address)); |
| sendMessageToService(event); |
| } |
| |
| private void onAtClcc(byte[] address) { |
| HeadsetStackEvent event = |
| new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_AT_CLCC, getDevice(address)); |
| sendMessageToService(event); |
| } |
| |
| private void onUnknownAt(String atString, byte[] address) { |
| HeadsetStackEvent event = |
| new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_UNKNOWN_AT, atString, |
| getDevice(address)); |
| sendMessageToService(event); |
| } |
| |
| private void onKeyPressed(byte[] address) { |
| HeadsetStackEvent event = |
| new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_KEY_PRESSED, getDevice(address)); |
| sendMessageToService(event); |
| } |
| |
| private void onATBind(String atString, byte[] address) { |
| HeadsetStackEvent event = new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_BIND, atString, |
| getDevice(address)); |
| sendMessageToService(event); |
| } |
| |
| private void onATBiev(int indId, int indValue, byte[] address) { |
| HeadsetStackEvent event = |
| new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_BIEV, indId, indValue, |
| getDevice(address)); |
| sendMessageToService(event); |
| } |
| |
| private void onAtBia(boolean service, boolean roam, boolean signal, boolean battery, |
| byte[] address) { |
| HeadsetAgIndicatorEnableState agIndicatorEnableState = |
| new HeadsetAgIndicatorEnableState(service, roam, signal, battery); |
| HeadsetStackEvent event = |
| new HeadsetStackEvent(HeadsetStackEvent.EVENT_TYPE_BIA, agIndicatorEnableState, |
| getDevice(address)); |
| sendMessageToService(event); |
| } |
| |
| // Native wrappers to help unit testing |
| |
| /** |
| * Initialize native stack |
| * |
| * @param maxHfClients maximum number of headset clients that can be connected simultaneously |
| * @param inbandRingingEnabled whether in-band ringing is enabled on this AG |
| */ |
| @VisibleForTesting |
| public void init(int maxHfClients, boolean inbandRingingEnabled) { |
| initializeNative(maxHfClients, inbandRingingEnabled); |
| } |
| |
| /** |
| * Closes the interface |
| */ |
| @VisibleForTesting |
| public void cleanup() { |
| cleanupNative(); |
| } |
| |
| /** |
| * ok/error response |
| * |
| * @param device target device |
| * @param responseCode 0 - ERROR, 1 - OK |
| * @param errorCode error code in case of ERROR |
| * @return True on success, False on failure |
| */ |
| @VisibleForTesting |
| public boolean atResponseCode(BluetoothDevice device, int responseCode, int errorCode) { |
| return atResponseCodeNative(responseCode, errorCode, Utils.getByteAddress(device)); |
| } |
| |
| /** |
| * Pre-formatted AT response, typically in response to unknown AT cmd |
| * |
| * @param device target device |
| * @param responseString formatted AT response string |
| * @return True on success, False on failure |
| */ |
| @VisibleForTesting |
| public boolean atResponseString(BluetoothDevice device, String responseString) { |
| return atResponseStringNative(responseString, Utils.getByteAddress(device)); |
| } |
| |
| /** |
| * Connect to headset |
| * |
| * @param device target headset |
| * @return True on success, False on failure |
| */ |
| @VisibleForTesting |
| public boolean connectHfp(BluetoothDevice device) { |
| return connectHfpNative(Utils.getByteAddress(device)); |
| } |
| |
| /** |
| * Disconnect from headset |
| * |
| * @param device target headset |
| * @return True on success, False on failure |
| */ |
| @VisibleForTesting |
| public boolean disconnectHfp(BluetoothDevice device) { |
| return disconnectHfpNative(Utils.getByteAddress(device)); |
| } |
| |
| /** |
| * Connect HFP audio (SCO) to headset |
| * |
| * @param device target headset |
| * @return True on success, False on failure |
| */ |
| @VisibleForTesting |
| public boolean connectAudio(BluetoothDevice device) { |
| return connectAudioNative(Utils.getByteAddress(device)); |
| } |
| |
| /** |
| * Disconnect HFP audio (SCO) from to headset |
| * |
| * @param device target headset |
| * @return True on success, False on failure |
| */ |
| @VisibleForTesting |
| public boolean disconnectAudio(BluetoothDevice device) { |
| return disconnectAudioNative(Utils.getByteAddress(device)); |
| } |
| |
| /** |
| * Start voice recognition |
| * |
| * @param device target headset |
| * @return True on success, False on failure |
| */ |
| @VisibleForTesting |
| public boolean startVoiceRecognition(BluetoothDevice device) { |
| return startVoiceRecognitionNative(Utils.getByteAddress(device)); |
| } |
| |
| |
| /** |
| * Stop voice recognition |
| * |
| * @param device target headset |
| * @return True on success, False on failure |
| */ |
| @VisibleForTesting |
| public boolean stopVoiceRecognition(BluetoothDevice device) { |
| return stopVoiceRecognitionNative(Utils.getByteAddress(device)); |
| } |
| |
| /** |
| * Set HFP audio (SCO) volume |
| * |
| * @param device target headset |
| * @param volumeType type of volume |
| * @param volume value value |
| * @return True on success, False on failure |
| */ |
| @VisibleForTesting |
| public boolean setVolume(BluetoothDevice device, int volumeType, int volume) { |
| return setVolumeNative(volumeType, volume, Utils.getByteAddress(device)); |
| } |
| |
| /** |
| * Response for CIND command |
| * |
| * @param device target device |
| * @param service service availability, 0 - no service, 1 - presence of service |
| * @param numActive number of active calls |
| * @param numHeld number of held calls |
| * @param callState overall call state [0-6] |
| * @param signal signal quality [0-5] |
| * @param roam roaming indicator, 0 - not roaming, 1 - roaming |
| * @param batteryCharge battery charge level [0-5] |
| * @return True on success, False on failure |
| */ |
| @VisibleForTesting |
| public boolean cindResponse(BluetoothDevice device, int service, int numActive, int numHeld, |
| int callState, int signal, int roam, int batteryCharge) { |
| return cindResponseNative(service, numActive, numHeld, callState, signal, roam, |
| batteryCharge, Utils.getByteAddress(device)); |
| } |
| |
| /** |
| * Combined device status change notification |
| * |
| * @param device target device |
| * @param deviceState device status object |
| * @return True on success, False on failure |
| */ |
| @VisibleForTesting |
| public boolean notifyDeviceStatus(BluetoothDevice device, HeadsetDeviceState deviceState) { |
| return notifyDeviceStatusNative(deviceState.mService, deviceState.mRoam, |
| deviceState.mSignal, deviceState.mBatteryCharge, Utils.getByteAddress(device)); |
| } |
| |
| /** |
| * Response for CLCC command. Can be iteratively called for each call index. Call index of 0 |
| * will be treated as NULL termination (Completes response) |
| * |
| * @param device target device |
| * @param index index of the call given by the sequence of setting up or receiving the calls |
| * as seen by the served subscriber. Calls hold their number until they are released. New |
| * calls take the lowest available number. |
| * @param dir direction of the call, 0 (outgoing), 1 (incoming) |
| * @param status 0 = Active, 1 = Held, 2 = Dialing (outgoing calls only), 3 = Alerting |
| * (outgoing calls only), 4 = Incoming (incoming calls only), 5 = Waiting (incoming calls |
| * only), 6 = Call held by Response and Hold |
| * @param mode 0 (Voice), 1 (Data), 2 (FAX) |
| * @param mpty 0 - this call is NOT a member of a multi-party (conference) call, 1 - this |
| * call IS a member of a multi-party (conference) call |
| * @param number optional |
| * @param type optional |
| * @return True on success, False on failure |
| */ |
| @VisibleForTesting |
| public boolean clccResponse(BluetoothDevice device, int index, int dir, int status, int mode, |
| boolean mpty, String number, int type) { |
| return clccResponseNative(index, dir, status, mode, mpty, number, type, |
| Utils.getByteAddress(device)); |
| } |
| |
| /** |
| * Response for COPS command |
| * |
| * @param device target device |
| * @param operatorName operator name |
| * @return True on success, False on failure |
| */ |
| @VisibleForTesting |
| public boolean copsResponse(BluetoothDevice device, String operatorName) { |
| return copsResponseNative(operatorName, Utils.getByteAddress(device)); |
| } |
| |
| /** |
| * Notify of a call state change |
| * Each update notifies |
| * 1. Number of active/held/ringing calls |
| * 2. call_state: This denotes the state change that triggered this msg |
| * This will take one of the values from BtHfCallState |
| * 3. number & type: valid only for incoming & waiting call |
| * |
| * @param device target device for this update |
| * @param callState callstate structure |
| * @return True on success, False on failure |
| */ |
| @VisibleForTesting |
| public boolean phoneStateChange(BluetoothDevice device, HeadsetCallState callState) { |
| return phoneStateChangeNative(callState.mNumActive, callState.mNumHeld, |
| callState.mCallState, callState.mNumber, callState.mType, callState.mName, |
| Utils.getByteAddress(device)); |
| } |
| |
| /** |
| * Set whether we will initiate SCO or not |
| * |
| * @param value True to enable, False to disable |
| * @return True on success, False on failure |
| */ |
| @VisibleForTesting |
| public boolean setScoAllowed(boolean value) { |
| return setScoAllowedNative(value); |
| } |
| |
| /** |
| * Enable or disable in-band ringing for the current service level connection through sending |
| * +BSIR AT command |
| * |
| * @param value True to enable, False to disable |
| * @return True on success, False on failure |
| */ |
| @VisibleForTesting |
| public boolean sendBsir(BluetoothDevice device, boolean value) { |
| return sendBsirNative(value, Utils.getByteAddress(device)); |
| } |
| |
| /** |
| * Set the current active headset device for SCO audio |
| * @param device current active SCO device |
| * @return true on success |
| */ |
| @VisibleForTesting |
| public boolean setActiveDevice(BluetoothDevice device) { |
| return setActiveDeviceNative(Utils.getByteAddress(device)); |
| } |
| |
| /* Native methods */ |
| private static native void classInitNative(); |
| |
| private native boolean atResponseCodeNative(int responseCode, int errorCode, byte[] address); |
| |
| private native boolean atResponseStringNative(String responseString, byte[] address); |
| |
| private native void initializeNative(int maxHfClients, boolean inbandRingingEnabled); |
| |
| private native void cleanupNative(); |
| |
| private native boolean connectHfpNative(byte[] address); |
| |
| private native boolean disconnectHfpNative(byte[] address); |
| |
| private native boolean connectAudioNative(byte[] address); |
| |
| private native boolean disconnectAudioNative(byte[] address); |
| |
| private native boolean startVoiceRecognitionNative(byte[] address); |
| |
| private native boolean stopVoiceRecognitionNative(byte[] address); |
| |
| private native boolean setVolumeNative(int volumeType, int volume, byte[] address); |
| |
| private native boolean cindResponseNative(int service, int numActive, int numHeld, |
| int callState, int signal, int roam, int batteryCharge, byte[] address); |
| |
| private native boolean notifyDeviceStatusNative(int networkState, int serviceType, int signal, |
| int batteryCharge, byte[] address); |
| |
| private native boolean clccResponseNative(int index, int dir, int status, int mode, |
| boolean mpty, String number, int type, byte[] address); |
| |
| private native boolean copsResponseNative(String operatorName, byte[] address); |
| |
| private native boolean phoneStateChangeNative(int numActive, int numHeld, int callState, |
| String number, int type, String name, byte[] address); |
| |
| private native boolean setScoAllowedNative(boolean value); |
| |
| private native boolean sendBsirNative(boolean value, byte[] address); |
| |
| private native boolean setActiveDeviceNative(byte[] address); |
| } |