Loading android/app/jni/com_android_bluetooth_hfpclient.cpp +33 −0 Original line number Diff line number Diff line Loading @@ -879,6 +879,37 @@ static jboolean sendATCmdNative(JNIEnv* env, jobject object, jbyteArray address, return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; } static jboolean sendAndroidAtNative(JNIEnv* env, jobject object, jbyteArray address, jstring arg_str) { std::shared_lock<std::shared_mutex> lock(interface_mutex); if (!sBluetoothHfpClientInterface) return JNI_FALSE; jbyte* addr = env->GetByteArrayElements(address, NULL); if (!addr) { jniThrowIOException(env, EINVAL); return JNI_FALSE; } const char* arg = NULL; if (arg_str != NULL) { arg = env->GetStringUTFChars(arg_str, NULL); } bt_status_t status = sBluetoothHfpClientInterface->send_android_at( (const RawAddress*)addr, arg); if (status != BT_STATUS_SUCCESS) { ALOGE("FAILED to control volume, status: %d", status); } if (arg != NULL) { env->ReleaseStringUTFChars(arg_str, arg); } env->ReleaseByteArrayElements(address, addr, 0); return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; } static JNINativeMethod sMethods[] = { {"classInitNative", "()V", (void*)classInitNative}, {"initializeNative", "()V", (void*)initializeNative}, Loading @@ -903,6 +934,8 @@ static JNINativeMethod sMethods[] = { {"requestLastVoiceTagNumberNative", "([B)Z", (void*)requestLastVoiceTagNumberNative}, {"sendATCmdNative", "([BIIILjava/lang/String;)Z", (void*)sendATCmdNative}, {"sendAndroidAtNative", "([BLjava/lang/String;)Z", (void*)sendAndroidAtNative}, }; int register_com_android_bluetooth_hfpclient(JNIEnv* env) { Loading android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java +8 −3 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAudioPolicy; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHapClient; import android.bluetooth.BluetoothHeadset; Loading Loading @@ -689,11 +690,15 @@ class ActiveDeviceManager { if (headsetService == null) { return; } BluetoothAudioPolicy audioPolicy = headsetService.getHfpCallAudioPolicy(device); if (audioPolicy == null || audioPolicy.getConnectingTimePolicy() != BluetoothAudioPolicy.POLICY_NOT_ALLOWED) { if (!headsetService.setActiveDevice(device)) { return; } mHfpActiveDevice = device; } } private void setHearingAidActiveDevice(BluetoothDevice device) { if (DBG) { Loading android/app/src/com/android/bluetooth/btservice/AdapterService.java +130 −0 Original line number Diff line number Diff line Loading @@ -42,6 +42,7 @@ import android.bluetooth.BluetoothActivityEnergyInfo; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAdapter.ActiveDeviceProfile; import android.bluetooth.BluetoothAdapter.ActiveDeviceUse; import android.bluetooth.BluetoothAudioPolicy; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothFrameworkInitializer; Loading Loading @@ -3669,6 +3670,73 @@ public class AdapterService extends Service { return service.mDatabaseManager.getCustomMeta(device, key); } @Override public void getAudioPolicyRemoteSupported(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver) { try { receiver.send(getAudioPolicyRemoteSupported(device, source)); } catch (RuntimeException e) { receiver.propagateException(e); } } private int getAudioPolicyRemoteSupported(BluetoothDevice device, AttributionSource source) { AdapterService service = getService(); if (service == null || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getAudioPolicyRemoteSupported") || !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) { return BluetoothAudioPolicy.FEATURE_UNCONFIGURED_BY_REMOTE; } enforceBluetoothPrivilegedPermission(service); return service.getAudioPolicyRemoteSupported(device); } @Override public void setAudioPolicy(BluetoothDevice device, BluetoothAudioPolicy policies, AttributionSource source, SynchronousResultReceiver receiver) { try { receiver.send(setAudioPolicy(device, policies, source)); } catch (RuntimeException e) { receiver.propagateException(e); } } private int setAudioPolicy(BluetoothDevice device, BluetoothAudioPolicy policies, AttributionSource source) { AdapterService service = getService(); if (service == null) { return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED; } else if (!callerIsSystemOrActiveOrManagedUser(service, TAG, "setAudioPolicy")) { return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ALLOWED; } else if (!Utils.checkConnectPermissionForDataDelivery( service, source, TAG)) { return BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION; } enforceBluetoothPrivilegedPermission(service); return service.setAudioPolicy(device, policies); } @Override public void getAudioPolicy(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver) { try { receiver.send(getAudioPolicy(device, source)); } catch (RuntimeException e) { receiver.propagateException(e); } } private BluetoothAudioPolicy getAudioPolicy(BluetoothDevice device, AttributionSource source) { AdapterService service = getService(); if (service == null || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getAudioPolicy") || !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) { return null; } enforceBluetoothPrivilegedPermission(service); return service.getAudioPolicy(device); } @Override public void requestActivityInfo(IBluetoothActivityEnergyInfoListener listener, AttributionSource source) { Loading Loading @@ -5508,6 +5576,68 @@ public class AdapterService extends Service { } } /** * Get audio policy feature support status * * @param device Bluetooth device to be checked for audio policy support * @return int status of the remote support for audio policy feature */ public int getAudioPolicyRemoteSupported(BluetoothDevice device) { if (mHeadsetClientService != null) { return mHeadsetClientService.getAudioPolicyRemoteSupported(device); } else { Log.e(TAG, "No audio transport connected"); return BluetoothAudioPolicy.FEATURE_UNCONFIGURED_BY_REMOTE; } } /** * Set audio policy for remote device * * @param device Bluetooth device to be set policy for * @return int result status for setAudioPolicy API */ public int setAudioPolicy(BluetoothDevice device, BluetoothAudioPolicy policies) { DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device); if (deviceProp == null) { return BluetoothStatusCodes.ERROR_DEVICE_NOT_BONDED; } if (mHeadsetClientService != null) { if (getAudioPolicyRemoteSupported(device) != BluetoothAudioPolicy.FEATURE_SUPPORTED_BY_REMOTE) { Log.w(TAG, "Audio Policy feature not supported by AG"); return BluetoothStatusCodes.FEATURE_NOT_SUPPORTED; } deviceProp.setHfAudioPolicyForRemoteAg(policies); mHeadsetClientService.setAudioPolicy(device, policies); return BluetoothStatusCodes.SUCCESS; } else { Log.e(TAG, "HeadsetClient not connected"); return BluetoothStatusCodes.ERROR_PROFILE_NOT_CONNECTED; } } /** * Get audio policy for remote device * * @param device Bluetooth device to be set policy for * @return {@link BluetoothAudioPolicy} policy stored for the device */ public BluetoothAudioPolicy getAudioPolicy(BluetoothDevice device) { DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device); if (deviceProp == null) { return null; } if (mHeadsetClientService != null) { return deviceProp.getHfAudioPolicyForRemoteAg(); } else { Log.e(TAG, "HeadsetClient not connected"); return null; } } /** * Allow audio low latency * Loading android/app/src/com/android/bluetooth/btservice/RemoteDevices.java +10 −0 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import static android.Manifest.permission.BLUETOOTH_SCAN; import android.app.admin.SecurityLog; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAssignedNumbers; import android.bluetooth.BluetoothAudioPolicy; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; Loading Loading @@ -312,6 +313,7 @@ final class RemoteDevices { @VisibleForTesting int mBondState; @VisibleForTesting int mDeviceType; @VisibleForTesting ParcelUuid[] mUuids; private BluetoothAudioPolicy mAudioPolicy; DeviceProperties() { mBondState = BluetoothDevice.BOND_NONE; Loading Loading @@ -499,6 +501,14 @@ final class RemoteDevices { return mIsCoordinatedSetMember; } } public void setHfAudioPolicyForRemoteAg(BluetoothAudioPolicy policies) { mAudioPolicy = policies; } public BluetoothAudioPolicy getHfAudioPolicyForRemoteAg() { return mAudioPolicy; } } private void sendUuidIntent(BluetoothDevice device, DeviceProperties prop) { Loading android/app/src/com/android/bluetooth/btservice/storage/AudioPolicyEntity.java 0 → 100644 +61 −0 Original line number Diff line number Diff line /* * Copyright 2022 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.btservice.storage; import android.bluetooth.BluetoothAudioPolicy; import androidx.room.ColumnInfo; import androidx.room.Entity; @Entity class AudioPolicyEntity { @ColumnInfo(name = "call_establish_audio_policy") public int callEstablishAudioPolicy; @ColumnInfo(name = "connecting_time_audio_policy") public int connectingTimeAudioPolicy; @ColumnInfo(name = "in_band_ringtone_audio_policy") public int inBandRingtoneAudioPolicy; AudioPolicyEntity() { callEstablishAudioPolicy = BluetoothAudioPolicy.POLICY_UNCONFIGURED; connectingTimeAudioPolicy = BluetoothAudioPolicy.POLICY_UNCONFIGURED; inBandRingtoneAudioPolicy = BluetoothAudioPolicy.POLICY_UNCONFIGURED; } AudioPolicyEntity(int callEstablishAudioPolicy, int connectingTimeAudioPolicy, int inBandRingtoneAudioPolicy) { this.callEstablishAudioPolicy = callEstablishAudioPolicy; this.connectingTimeAudioPolicy = connectingTimeAudioPolicy; this.inBandRingtoneAudioPolicy = inBandRingtoneAudioPolicy; } public String toString() { StringBuilder builder = new StringBuilder(); builder.append("callEstablishAudioPolicy=") .append(metadataToString(callEstablishAudioPolicy)) .append("|connectingTimeAudioPolicy=") .append(metadataToString(connectingTimeAudioPolicy)) .append("|inBandRingtoneAudioPolicy=") .append(metadataToString(inBandRingtoneAudioPolicy)); return builder.toString(); } private String metadataToString(int metadata) { return String.valueOf(metadata); } } Loading
android/app/jni/com_android_bluetooth_hfpclient.cpp +33 −0 Original line number Diff line number Diff line Loading @@ -879,6 +879,37 @@ static jboolean sendATCmdNative(JNIEnv* env, jobject object, jbyteArray address, return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; } static jboolean sendAndroidAtNative(JNIEnv* env, jobject object, jbyteArray address, jstring arg_str) { std::shared_lock<std::shared_mutex> lock(interface_mutex); if (!sBluetoothHfpClientInterface) return JNI_FALSE; jbyte* addr = env->GetByteArrayElements(address, NULL); if (!addr) { jniThrowIOException(env, EINVAL); return JNI_FALSE; } const char* arg = NULL; if (arg_str != NULL) { arg = env->GetStringUTFChars(arg_str, NULL); } bt_status_t status = sBluetoothHfpClientInterface->send_android_at( (const RawAddress*)addr, arg); if (status != BT_STATUS_SUCCESS) { ALOGE("FAILED to control volume, status: %d", status); } if (arg != NULL) { env->ReleaseStringUTFChars(arg_str, arg); } env->ReleaseByteArrayElements(address, addr, 0); return (status == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; } static JNINativeMethod sMethods[] = { {"classInitNative", "()V", (void*)classInitNative}, {"initializeNative", "()V", (void*)initializeNative}, Loading @@ -903,6 +934,8 @@ static JNINativeMethod sMethods[] = { {"requestLastVoiceTagNumberNative", "([B)Z", (void*)requestLastVoiceTagNumberNative}, {"sendATCmdNative", "([BIIILjava/lang/String;)Z", (void*)sendATCmdNative}, {"sendAndroidAtNative", "([BLjava/lang/String;)Z", (void*)sendAndroidAtNative}, }; int register_com_android_bluetooth_hfpclient(JNIEnv* env) { Loading
android/app/src/com/android/bluetooth/btservice/ActiveDeviceManager.java +8 −3 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ import android.annotation.RequiresPermission; import android.annotation.SuppressLint; import android.bluetooth.BluetoothA2dp; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAudioPolicy; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHapClient; import android.bluetooth.BluetoothHeadset; Loading Loading @@ -689,11 +690,15 @@ class ActiveDeviceManager { if (headsetService == null) { return; } BluetoothAudioPolicy audioPolicy = headsetService.getHfpCallAudioPolicy(device); if (audioPolicy == null || audioPolicy.getConnectingTimePolicy() != BluetoothAudioPolicy.POLICY_NOT_ALLOWED) { if (!headsetService.setActiveDevice(device)) { return; } mHfpActiveDevice = device; } } private void setHearingAidActiveDevice(BluetoothDevice device) { if (DBG) { Loading
android/app/src/com/android/bluetooth/btservice/AdapterService.java +130 −0 Original line number Diff line number Diff line Loading @@ -42,6 +42,7 @@ import android.bluetooth.BluetoothActivityEnergyInfo; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAdapter.ActiveDeviceProfile; import android.bluetooth.BluetoothAdapter.ActiveDeviceUse; import android.bluetooth.BluetoothAudioPolicy; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothFrameworkInitializer; Loading Loading @@ -3669,6 +3670,73 @@ public class AdapterService extends Service { return service.mDatabaseManager.getCustomMeta(device, key); } @Override public void getAudioPolicyRemoteSupported(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver) { try { receiver.send(getAudioPolicyRemoteSupported(device, source)); } catch (RuntimeException e) { receiver.propagateException(e); } } private int getAudioPolicyRemoteSupported(BluetoothDevice device, AttributionSource source) { AdapterService service = getService(); if (service == null || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getAudioPolicyRemoteSupported") || !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) { return BluetoothAudioPolicy.FEATURE_UNCONFIGURED_BY_REMOTE; } enforceBluetoothPrivilegedPermission(service); return service.getAudioPolicyRemoteSupported(device); } @Override public void setAudioPolicy(BluetoothDevice device, BluetoothAudioPolicy policies, AttributionSource source, SynchronousResultReceiver receiver) { try { receiver.send(setAudioPolicy(device, policies, source)); } catch (RuntimeException e) { receiver.propagateException(e); } } private int setAudioPolicy(BluetoothDevice device, BluetoothAudioPolicy policies, AttributionSource source) { AdapterService service = getService(); if (service == null) { return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED; } else if (!callerIsSystemOrActiveOrManagedUser(service, TAG, "setAudioPolicy")) { return BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ALLOWED; } else if (!Utils.checkConnectPermissionForDataDelivery( service, source, TAG)) { return BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION; } enforceBluetoothPrivilegedPermission(service); return service.setAudioPolicy(device, policies); } @Override public void getAudioPolicy(BluetoothDevice device, AttributionSource source, SynchronousResultReceiver receiver) { try { receiver.send(getAudioPolicy(device, source)); } catch (RuntimeException e) { receiver.propagateException(e); } } private BluetoothAudioPolicy getAudioPolicy(BluetoothDevice device, AttributionSource source) { AdapterService service = getService(); if (service == null || !callerIsSystemOrActiveOrManagedUser(service, TAG, "getAudioPolicy") || !Utils.checkConnectPermissionForDataDelivery(service, source, TAG)) { return null; } enforceBluetoothPrivilegedPermission(service); return service.getAudioPolicy(device); } @Override public void requestActivityInfo(IBluetoothActivityEnergyInfoListener listener, AttributionSource source) { Loading Loading @@ -5508,6 +5576,68 @@ public class AdapterService extends Service { } } /** * Get audio policy feature support status * * @param device Bluetooth device to be checked for audio policy support * @return int status of the remote support for audio policy feature */ public int getAudioPolicyRemoteSupported(BluetoothDevice device) { if (mHeadsetClientService != null) { return mHeadsetClientService.getAudioPolicyRemoteSupported(device); } else { Log.e(TAG, "No audio transport connected"); return BluetoothAudioPolicy.FEATURE_UNCONFIGURED_BY_REMOTE; } } /** * Set audio policy for remote device * * @param device Bluetooth device to be set policy for * @return int result status for setAudioPolicy API */ public int setAudioPolicy(BluetoothDevice device, BluetoothAudioPolicy policies) { DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device); if (deviceProp == null) { return BluetoothStatusCodes.ERROR_DEVICE_NOT_BONDED; } if (mHeadsetClientService != null) { if (getAudioPolicyRemoteSupported(device) != BluetoothAudioPolicy.FEATURE_SUPPORTED_BY_REMOTE) { Log.w(TAG, "Audio Policy feature not supported by AG"); return BluetoothStatusCodes.FEATURE_NOT_SUPPORTED; } deviceProp.setHfAudioPolicyForRemoteAg(policies); mHeadsetClientService.setAudioPolicy(device, policies); return BluetoothStatusCodes.SUCCESS; } else { Log.e(TAG, "HeadsetClient not connected"); return BluetoothStatusCodes.ERROR_PROFILE_NOT_CONNECTED; } } /** * Get audio policy for remote device * * @param device Bluetooth device to be set policy for * @return {@link BluetoothAudioPolicy} policy stored for the device */ public BluetoothAudioPolicy getAudioPolicy(BluetoothDevice device) { DeviceProperties deviceProp = mRemoteDevices.getDeviceProperties(device); if (deviceProp == null) { return null; } if (mHeadsetClientService != null) { return deviceProp.getHfAudioPolicyForRemoteAg(); } else { Log.e(TAG, "HeadsetClient not connected"); return null; } } /** * Allow audio low latency * Loading
android/app/src/com/android/bluetooth/btservice/RemoteDevices.java +10 −0 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import static android.Manifest.permission.BLUETOOTH_SCAN; import android.app.admin.SecurityLog; import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAssignedNumbers; import android.bluetooth.BluetoothAudioPolicy; import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothHeadset; Loading Loading @@ -312,6 +313,7 @@ final class RemoteDevices { @VisibleForTesting int mBondState; @VisibleForTesting int mDeviceType; @VisibleForTesting ParcelUuid[] mUuids; private BluetoothAudioPolicy mAudioPolicy; DeviceProperties() { mBondState = BluetoothDevice.BOND_NONE; Loading Loading @@ -499,6 +501,14 @@ final class RemoteDevices { return mIsCoordinatedSetMember; } } public void setHfAudioPolicyForRemoteAg(BluetoothAudioPolicy policies) { mAudioPolicy = policies; } public BluetoothAudioPolicy getHfAudioPolicyForRemoteAg() { return mAudioPolicy; } } private void sendUuidIntent(BluetoothDevice device, DeviceProperties prop) { Loading
android/app/src/com/android/bluetooth/btservice/storage/AudioPolicyEntity.java 0 → 100644 +61 −0 Original line number Diff line number Diff line /* * Copyright 2022 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.btservice.storage; import android.bluetooth.BluetoothAudioPolicy; import androidx.room.ColumnInfo; import androidx.room.Entity; @Entity class AudioPolicyEntity { @ColumnInfo(name = "call_establish_audio_policy") public int callEstablishAudioPolicy; @ColumnInfo(name = "connecting_time_audio_policy") public int connectingTimeAudioPolicy; @ColumnInfo(name = "in_band_ringtone_audio_policy") public int inBandRingtoneAudioPolicy; AudioPolicyEntity() { callEstablishAudioPolicy = BluetoothAudioPolicy.POLICY_UNCONFIGURED; connectingTimeAudioPolicy = BluetoothAudioPolicy.POLICY_UNCONFIGURED; inBandRingtoneAudioPolicy = BluetoothAudioPolicy.POLICY_UNCONFIGURED; } AudioPolicyEntity(int callEstablishAudioPolicy, int connectingTimeAudioPolicy, int inBandRingtoneAudioPolicy) { this.callEstablishAudioPolicy = callEstablishAudioPolicy; this.connectingTimeAudioPolicy = connectingTimeAudioPolicy; this.inBandRingtoneAudioPolicy = inBandRingtoneAudioPolicy; } public String toString() { StringBuilder builder = new StringBuilder(); builder.append("callEstablishAudioPolicy=") .append(metadataToString(callEstablishAudioPolicy)) .append("|connectingTimeAudioPolicy=") .append(metadataToString(connectingTimeAudioPolicy)) .append("|inBandRingtoneAudioPolicy=") .append(metadataToString(inBandRingtoneAudioPolicy)); return builder.toString(); } private String metadataToString(int metadata) { return String.valueOf(metadata); } }