blob: 29c571d3ddb2327572100e7476fc87d95f6d1c28 [file] [log] [blame]
/*
* 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);
}