Loading android/app/jni/com_android_bluetooth_hfp.cpp +21 −0 Original line number Diff line number Diff line Loading @@ -799,6 +799,26 @@ static jboolean sendBsirNative(JNIEnv* env, jobject object, jboolean value, return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; } static jboolean setActiveDeviceNative(JNIEnv* env, jobject object, jbyteArray address) { std::shared_lock<std::shared_timed_mutex> lock(interface_mutex); if (!sBluetoothHfpInterface) return JNI_FALSE; jbyte* addr = env->GetByteArrayElements(address, NULL); if (!addr) { jniThrowIOException(env, EINVAL); return JNI_FALSE; } bt_status_t status = sBluetoothHfpInterface->SetActiveDevice((RawAddress*)addr); if (status != BT_STATUS_SUCCESS) { ALOGE("Failed to set active device, status: %d", status); } env->ReleaseByteArrayElements(address, addr, 0); return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; } static JNINativeMethod sMethods[] = { {"classInitNative", "()V", (void*)classInitNative}, {"initializeNative", "(IZ)V", (void*)initializeNative}, Loading @@ -824,6 +844,7 @@ static JNINativeMethod sMethods[] = { (void*)phoneStateChangeNative}, {"setScoAllowedNative", "(Z)Z", (void*)setScoAllowedNative}, {"sendBsirNative", "(Z[B)Z", (void*)sendBsirNative}, {"setActiveDeviceNative", "([B)Z", (void*)setActiveDeviceNative}, }; int register_com_android_bluetooth_hfp(JNIEnv* env) { Loading android/app/src/com/android/bluetooth/hfp/HeadsetNativeInterface.java +12 −0 Original line number Diff line number Diff line Loading @@ -433,6 +433,16 @@ public class HeadsetNativeInterface { 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(); Loading Loading @@ -475,4 +485,6 @@ public class HeadsetNativeInterface { private native boolean setScoAllowedNative(boolean value); private native boolean sendBsirNative(boolean value, byte[] address); private native boolean setActiveDeviceNative(byte[] address); } android/app/src/com/android/bluetooth/hfp/HeadsetService.java +84 −0 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ import android.os.BatteryManager; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.os.UserHandle; import android.provider.Settings; import android.util.Log; Loading @@ -47,6 +48,7 @@ public class HeadsetService extends ProfileService { private static final String MODIFY_PHONE_STATE = android.Manifest.permission.MODIFY_PHONE_STATE; private static final int MAX_HEADSET_CONNECTIONS = 1; private BluetoothDevice mActiveDevice; private HandlerThread mStateMachinesThread; private HeadsetStateMachine mStateMachine; private HeadsetNativeInterface mNativeInterface; Loading Loading @@ -443,6 +445,24 @@ public class HeadsetService extends ProfileService { } return service.sendVendorSpecificResultCode(device, command, arg); } @Override public boolean setActiveDevice(BluetoothDevice device) { HeadsetService service = getService(); if (service == null) { return false; } return service.setActiveDevice(device); } @Override public BluetoothDevice getActiveDevice() { HeadsetService service = getService(); if (service == null) { return null; } return service.getActiveDevice(); } } // API methods Loading Loading @@ -586,6 +606,47 @@ public class HeadsetService extends ProfileService { mStateMachine.setForceScoAudio(forced); } /** * Set the active device. * * @param device the active device * @return true on success, otherwise false */ public synchronized boolean setActiveDevice(BluetoothDevice device) { enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); if (DBG) { Log.d(TAG, "setActiveDevice: " + device); } if (device == null) { // Clear the active device mActiveDevice = null; broadcastActiveDevice(null); return true; } if (getConnectionState(device) != BluetoothProfile.STATE_CONNECTED) { Log.e(TAG, "setActiveDevice: Cannot set " + device + " as active, device is not connected"); return false; } if (!mNativeInterface.setActiveDevice(device)) { Log.e(TAG, "setActiveDevice: Cannot set " + device + " as active in native layer"); return false; } mActiveDevice = device; broadcastActiveDevice(mActiveDevice); return true; } /** * Get the active device. * * @return the active device or null if no device is active */ public synchronized BluetoothDevice getActiveDevice() { enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); return mActiveDevice; } boolean connectAudio() { // TODO(BT) BLUETOOTH or BLUETOOTH_ADMIN permission enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); Loading Loading @@ -666,6 +727,29 @@ public class HeadsetService extends ProfileService { return true; } void connectionStateChanged(BluetoothDevice device, int fromState, int toState) { if (fromState != BluetoothProfile.STATE_CONNECTED && toState == BluetoothProfile.STATE_CONNECTED) { // Assume only one connected device setActiveDevice(device); } if (fromState == BluetoothProfile.STATE_CONNECTED && toState != BluetoothProfile.STATE_CONNECTED) { setActiveDevice(null); } } private void broadcastActiveDevice(BluetoothDevice device) { if (DBG) { Log.d(TAG, "broadcastActiveDevice: " + device); } Intent intent = new Intent(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); sendBroadcastAsUser(intent, UserHandle.ALL, HeadsetService.BLUETOOTH_PERM); } @Override public void dump(StringBuilder sb) { super.dump(sb); Loading android/app/src/com/android/bluetooth/hfp/HeadsetStateMachine.java +11 −2 Original line number Diff line number Diff line Loading @@ -308,6 +308,7 @@ final class HeadsetStateMachine extends StateMachine { // Headset is disconnecting, stop Virtual call if active. terminateScoUsingVirtualVoiceCall(); } mService.connectionStateChanged(device, fromState, toState); Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, fromState); intent.putExtra(BluetoothProfile.EXTRA_STATE, toState); Loading Loading @@ -2443,8 +2444,16 @@ final class HeadsetStateMachine extends StateMachine { if (mForceScoAudio) { return true; } return mAudioRouteAllowed && (mVoiceRecognitionStarted || isInCall() || ( BluetoothHeadset.isInbandRingingSupported(mService) && isRinging())); if (!mService.getAudioRouteAllowed()) { return false; } if (isInCall() || mVoiceRecognitionStarted) { return true; } if (isRinging() && BluetoothHeadset.isInbandRingingSupported(mService)) { return true; } return false; } private boolean okToAcceptConnection(BluetoothDevice device) { Loading Loading
android/app/jni/com_android_bluetooth_hfp.cpp +21 −0 Original line number Diff line number Diff line Loading @@ -799,6 +799,26 @@ static jboolean sendBsirNative(JNIEnv* env, jobject object, jboolean value, return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; } static jboolean setActiveDeviceNative(JNIEnv* env, jobject object, jbyteArray address) { std::shared_lock<std::shared_timed_mutex> lock(interface_mutex); if (!sBluetoothHfpInterface) return JNI_FALSE; jbyte* addr = env->GetByteArrayElements(address, NULL); if (!addr) { jniThrowIOException(env, EINVAL); return JNI_FALSE; } bt_status_t status = sBluetoothHfpInterface->SetActiveDevice((RawAddress*)addr); if (status != BT_STATUS_SUCCESS) { ALOGE("Failed to set active device, status: %d", status); } env->ReleaseByteArrayElements(address, addr, 0); return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; } static JNINativeMethod sMethods[] = { {"classInitNative", "()V", (void*)classInitNative}, {"initializeNative", "(IZ)V", (void*)initializeNative}, Loading @@ -824,6 +844,7 @@ static JNINativeMethod sMethods[] = { (void*)phoneStateChangeNative}, {"setScoAllowedNative", "(Z)Z", (void*)setScoAllowedNative}, {"sendBsirNative", "(Z[B)Z", (void*)sendBsirNative}, {"setActiveDeviceNative", "([B)Z", (void*)setActiveDeviceNative}, }; int register_com_android_bluetooth_hfp(JNIEnv* env) { Loading
android/app/src/com/android/bluetooth/hfp/HeadsetNativeInterface.java +12 −0 Original line number Diff line number Diff line Loading @@ -433,6 +433,16 @@ public class HeadsetNativeInterface { 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(); Loading Loading @@ -475,4 +485,6 @@ public class HeadsetNativeInterface { private native boolean setScoAllowedNative(boolean value); private native boolean sendBsirNative(boolean value, byte[] address); private native boolean setActiveDeviceNative(byte[] address); }
android/app/src/com/android/bluetooth/hfp/HeadsetService.java +84 −0 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ import android.os.BatteryManager; import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.os.UserHandle; import android.provider.Settings; import android.util.Log; Loading @@ -47,6 +48,7 @@ public class HeadsetService extends ProfileService { private static final String MODIFY_PHONE_STATE = android.Manifest.permission.MODIFY_PHONE_STATE; private static final int MAX_HEADSET_CONNECTIONS = 1; private BluetoothDevice mActiveDevice; private HandlerThread mStateMachinesThread; private HeadsetStateMachine mStateMachine; private HeadsetNativeInterface mNativeInterface; Loading Loading @@ -443,6 +445,24 @@ public class HeadsetService extends ProfileService { } return service.sendVendorSpecificResultCode(device, command, arg); } @Override public boolean setActiveDevice(BluetoothDevice device) { HeadsetService service = getService(); if (service == null) { return false; } return service.setActiveDevice(device); } @Override public BluetoothDevice getActiveDevice() { HeadsetService service = getService(); if (service == null) { return null; } return service.getActiveDevice(); } } // API methods Loading Loading @@ -586,6 +606,47 @@ public class HeadsetService extends ProfileService { mStateMachine.setForceScoAudio(forced); } /** * Set the active device. * * @param device the active device * @return true on success, otherwise false */ public synchronized boolean setActiveDevice(BluetoothDevice device) { enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); if (DBG) { Log.d(TAG, "setActiveDevice: " + device); } if (device == null) { // Clear the active device mActiveDevice = null; broadcastActiveDevice(null); return true; } if (getConnectionState(device) != BluetoothProfile.STATE_CONNECTED) { Log.e(TAG, "setActiveDevice: Cannot set " + device + " as active, device is not connected"); return false; } if (!mNativeInterface.setActiveDevice(device)) { Log.e(TAG, "setActiveDevice: Cannot set " + device + " as active in native layer"); return false; } mActiveDevice = device; broadcastActiveDevice(mActiveDevice); return true; } /** * Get the active device. * * @return the active device or null if no device is active */ public synchronized BluetoothDevice getActiveDevice() { enforceCallingOrSelfPermission(BLUETOOTH_ADMIN_PERM, "Need BLUETOOTH_ADMIN permission"); return mActiveDevice; } boolean connectAudio() { // TODO(BT) BLUETOOTH or BLUETOOTH_ADMIN permission enforceCallingOrSelfPermission(BLUETOOTH_PERM, "Need BLUETOOTH permission"); Loading Loading @@ -666,6 +727,29 @@ public class HeadsetService extends ProfileService { return true; } void connectionStateChanged(BluetoothDevice device, int fromState, int toState) { if (fromState != BluetoothProfile.STATE_CONNECTED && toState == BluetoothProfile.STATE_CONNECTED) { // Assume only one connected device setActiveDevice(device); } if (fromState == BluetoothProfile.STATE_CONNECTED && toState != BluetoothProfile.STATE_CONNECTED) { setActiveDevice(null); } } private void broadcastActiveDevice(BluetoothDevice device) { if (DBG) { Log.d(TAG, "broadcastActiveDevice: " + device); } Intent intent = new Intent(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED); intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); sendBroadcastAsUser(intent, UserHandle.ALL, HeadsetService.BLUETOOTH_PERM); } @Override public void dump(StringBuilder sb) { super.dump(sb); Loading
android/app/src/com/android/bluetooth/hfp/HeadsetStateMachine.java +11 −2 Original line number Diff line number Diff line Loading @@ -308,6 +308,7 @@ final class HeadsetStateMachine extends StateMachine { // Headset is disconnecting, stop Virtual call if active. terminateScoUsingVirtualVoiceCall(); } mService.connectionStateChanged(device, fromState, toState); Intent intent = new Intent(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED); intent.putExtra(BluetoothProfile.EXTRA_PREVIOUS_STATE, fromState); intent.putExtra(BluetoothProfile.EXTRA_STATE, toState); Loading Loading @@ -2443,8 +2444,16 @@ final class HeadsetStateMachine extends StateMachine { if (mForceScoAudio) { return true; } return mAudioRouteAllowed && (mVoiceRecognitionStarted || isInCall() || ( BluetoothHeadset.isInbandRingingSupported(mService) && isRinging())); if (!mService.getAudioRouteAllowed()) { return false; } if (isInCall() || mVoiceRecognitionStarted) { return true; } if (isRinging() && BluetoothHeadset.isInbandRingingSupported(mService)) { return true; } return false; } private boolean okToAcceptConnection(BluetoothDevice device) { Loading