diff options
5 files changed, 253 insertions, 0 deletions
diff --git a/android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp b/android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp index 72f1e7ce8a..0d69e44939 100644 --- a/android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp +++ b/android/app/jni/com_android_bluetooth_btservice_AdapterService.cpp @@ -2143,6 +2143,73 @@ static jint getSocketL2capRemoteChannelIdNative(JNIEnv* /* env */, jobject /* ob return (jint)cid; } +static jboolean setDefaultEventMaskExceptNative(JNIEnv* /* env */, jobject /* obj */, jlong mask, + jlong le_mask) { + log::verbose(""); + + if (!sBluetoothInterface) { + return JNI_FALSE; + } + + int ret = sBluetoothInterface->set_default_event_mask_except(mask, le_mask); + return (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + +static jboolean clearEventFilterNative(JNIEnv* /* env */, jobject /* obj */) { + log::verbose(""); + + if (!sBluetoothInterface) { + return JNI_FALSE; + } + + int ret = sBluetoothInterface->clear_event_filter(); + return (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + +static jboolean clearFilterAcceptListNative(JNIEnv* /* env */, jobject /* obj */) { + log::verbose(""); + + if (!sBluetoothInterface) { + return JNI_FALSE; + } + + int ret = sBluetoothInterface->clear_filter_accept_list(); + return (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + +static jboolean disconnectAllAclsNative(JNIEnv* /* env */, jobject /* obj */) { + log::verbose(""); + + if (!sBluetoothInterface) { + return JNI_FALSE; + } + + int ret = sBluetoothInterface->disconnect_all_acls(); + return (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + +static jboolean allowWakeByHidNative(JNIEnv* /* env */, jobject /* obj */) { + log::verbose(""); + + if (!sBluetoothInterface) { + return JNI_FALSE; + } + + int ret = sBluetoothInterface->allow_wake_by_hid(); + return (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + +static jboolean restoreFilterAcceptListNative(JNIEnv* /* env */, jobject /* obj */) { + log::verbose(""); + + if (!sBluetoothInterface) { + return JNI_FALSE; + } + + int ret = sBluetoothInterface->restore_filter_accept_list(); + return (ret == BT_STATUS_SUCCESS) ? JNI_TRUE : JNI_FALSE; +} + int register_com_android_bluetooth_btservice_AdapterService(JNIEnv* env) { const JNINativeMethod methods[] = { {"initNative", "(ZZI[Ljava/lang/String;ZLjava/lang/String;)Z", @@ -2207,6 +2274,16 @@ int register_com_android_bluetooth_btservice_AdapterService(JNIEnv* env) { reinterpret_cast<void*>(getSocketL2capLocalChannelIdNative)}, {"getSocketL2capRemoteChannelIdNative", "(JJ)I", reinterpret_cast<void*>(getSocketL2capRemoteChannelIdNative)}, + {"setDefaultEventMaskExceptNative", "(JJ)Z", + reinterpret_cast<void*>(setDefaultEventMaskExceptNative)}, + {"clearEventFilterNative", "()Z", reinterpret_cast<void*>(clearEventFilterNative)}, + {"clearFilterAcceptListNative", "()Z", + reinterpret_cast<void*>(clearFilterAcceptListNative)}, + {"disconnectAllAclsNative", "()Z", reinterpret_cast<void*>(disconnectAllAclsNative)}, + {"allowWakeByHidNative", "()Z", reinterpret_cast<void*>(allowWakeByHidNative)}, + {"restoreFilterAcceptListNative", "()Z", + reinterpret_cast<void*>(restoreFilterAcceptListNative)}, + }; const int result = REGISTER_NATIVE_METHODS( env, "com/android/bluetooth/btservice/AdapterNativeInterface", methods); diff --git a/android/app/src/com/android/bluetooth/btservice/AdapterNativeInterface.java b/android/app/src/com/android/bluetooth/btservice/AdapterNativeInterface.java index 1ab1a7ea0c..28bff10bb7 100644 --- a/android/app/src/com/android/bluetooth/btservice/AdapterNativeInterface.java +++ b/android/app/src/com/android/bluetooth/btservice/AdapterNativeInterface.java @@ -259,6 +259,30 @@ public class AdapterNativeInterface { connectionUuid.getUuid().getMostSignificantBits()); } + boolean setDefaultEventMaskExcept(long mask, long leMask) { + return setDefaultEventMaskExceptNative(mask, leMask); + } + + boolean clearEventFilter() { + return clearEventFilterNative(); + } + + boolean clearFilterAcceptList() { + return clearFilterAcceptListNative(); + } + + boolean disconnectAllAcls() { + return disconnectAllAclsNative(); + } + + boolean allowWakeByHid() { + return allowWakeByHidNative(); + } + + boolean restoreFilterAcceptList() { + return restoreFilterAcceptListNative(); + } + /**********************************************************************************************/ /*********************************** callbacks from native ************************************/ /**********************************************************************************************/ @@ -371,4 +395,16 @@ public class AdapterNativeInterface { private native int getSocketL2capRemoteChannelIdNative( long connectionUuidLsb, long connectionUuidMsb); + + private native boolean setDefaultEventMaskExceptNative(long mask, long leMask); + + private native boolean clearEventFilterNative(); + + private native boolean clearFilterAcceptListNative(); + + private native boolean disconnectAllAclsNative(); + + private native boolean allowWakeByHidNative(); + + private native boolean restoreFilterAcceptListNative(); } diff --git a/android/app/src/com/android/bluetooth/btservice/AdapterService.java b/android/app/src/com/android/bluetooth/btservice/AdapterService.java index 62a532dccd..5acdcd0197 100644 --- a/android/app/src/com/android/bluetooth/btservice/AdapterService.java +++ b/android/app/src/com/android/bluetooth/btservice/AdapterService.java @@ -88,6 +88,7 @@ import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; +import android.hardware.display.DisplayManager; import android.os.AsyncTask; import android.os.BatteryStatsManager; import android.os.Binder; @@ -281,6 +282,7 @@ public class AdapterService extends Service { private AdapterState mAdapterStateMachine; private BondStateMachine mBondStateMachine; private RemoteDevices mRemoteDevices; + private AdapterSuspend mAdapterSuspend; /* TODO: Consider to remove the search API from this class, if changed to use call-back */ private SdpManager mSdpManager = null; @@ -755,6 +757,12 @@ public class AdapterService extends Service { mBluetoothSocketManagerBinder = new BluetoothSocketManagerBinder(this); + if (Flags.adapterSuspendMgmt()) { + mAdapterSuspend = + new AdapterSuspend( + mNativeInterface, mLooper, getSystemService(DisplayManager.class)); + } + if (!Flags.fastBindToApp()) { setAdapterService(this); } @@ -1508,6 +1516,11 @@ public class AdapterService extends Service { mBluetoothSocketManagerBinder = null; } + if (mAdapterSuspend != null) { + mAdapterSuspend.cleanup(); + mAdapterSuspend = null; + } + mPreferredAudioProfilesCallbacks.kill(); mBluetoothQualityReportReadyCallbacks.kill(); diff --git a/android/app/src/com/android/bluetooth/btservice/AdapterSuspend.java b/android/app/src/com/android/bluetooth/btservice/AdapterSuspend.java new file mode 100644 index 0000000000..ab5187b14b --- /dev/null +++ b/android/app/src/com/android/bluetooth/btservice/AdapterSuspend.java @@ -0,0 +1,120 @@ +/* + * Copyright 2024 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; + +import static android.bluetooth.BluetoothAdapter.SCAN_MODE_CONNECTABLE; +import static android.bluetooth.BluetoothAdapter.SCAN_MODE_NONE; + +import static java.util.Objects.requireNonNull; + +import android.hardware.display.DisplayManager; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; +import android.view.Display; + +import java.util.Arrays; + +public class AdapterSuspend { + private static final String TAG = "BtAdapterSuspend"; + + // Event mask bits corresponding to specific HCI events + // as defined in Bluetooth core v5.4, Vol 4, Part E, 7.3.1. + private static final long MASK_DISCONNECT_CMPLT = 1 << 4; + private static final long MASK_MODE_CHANGE = 1 << 19; + + private boolean mSuspended = false; + + private final AdapterNativeInterface mAdapterNativeInterface; + private final Looper mLooper; + private final DisplayManager mDisplayManager; + private final DisplayManager.DisplayListener mDisplayListener = + new DisplayManager.DisplayListener() { + @Override + public void onDisplayAdded(int displayId) {} + + @Override + public void onDisplayRemoved(int displayId) {} + + @Override + public void onDisplayChanged(int displayId) { + if (isScreenOn()) { + handleResume(); + } else { + handleSuspend(); + } + } + }; + + public AdapterSuspend( + AdapterNativeInterface adapterNativeInterface, + Looper looper, + DisplayManager displayManager) { + mAdapterNativeInterface = requireNonNull(adapterNativeInterface); + mLooper = requireNonNull(looper); + mDisplayManager = requireNonNull(displayManager); + + mDisplayManager.registerDisplayListener(mDisplayListener, new Handler(mLooper)); + } + + void cleanup() { + mDisplayManager.unregisterDisplayListener(mDisplayListener); + } + + private boolean isScreenOn() { + return Arrays.stream(mDisplayManager.getDisplays()) + .anyMatch(display -> display.getState() == Display.STATE_ON); + } + + private void handleSuspend() { + if (mSuspended) { + return; + } + mSuspended = true; + + long mask = MASK_DISCONNECT_CMPLT | MASK_MODE_CHANGE; + long leMask = 0; + + // Avoid unexpected interrupt during suspend. + mAdapterNativeInterface.setDefaultEventMaskExcept(mask, leMask); + + // Disable inquiry scan and page scan. + mAdapterNativeInterface.setScanMode(AdapterService.convertScanModeToHal(SCAN_MODE_NONE)); + + mAdapterNativeInterface.clearEventFilter(); + mAdapterNativeInterface.clearFilterAcceptList(); + mAdapterNativeInterface.disconnectAllAcls(); + mAdapterNativeInterface.allowWakeByHid(); + Log.i(TAG, "ready to suspend"); + } + + private void handleResume() { + if (!mSuspended) { + return; + } + mSuspended = false; + + long mask = 0; + long leMask = 0; + mAdapterNativeInterface.setDefaultEventMaskExcept(mask, leMask); + mAdapterNativeInterface.clearEventFilter(); + mAdapterNativeInterface.restoreFilterAcceptList(); + mAdapterNativeInterface.setScanMode( + AdapterService.convertScanModeToHal(SCAN_MODE_CONNECTABLE)); + Log.i(TAG, "resumed"); + } +} diff --git a/flags/system_service.aconfig b/flags/system_service.aconfig index 1c40504d97..df29c6f3af 100644 --- a/flags/system_service.aconfig +++ b/flags/system_service.aconfig @@ -84,3 +84,10 @@ flag { description: "Replace binder call to the system server with a Messenger to enforce thread safety" bug: "321804999" } + +flag { + name: "adapter_suspend_mgmt" + namespace: "bluetooth" + description: "Configure the BT adapter in a suspend state to avoid unexpected wake-up" + bug: "366432079" +} |