Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 76ef509d authored by Zhengping Jiang's avatar Zhengping Jiang
Browse files

bluetooth: configure wake by BT according to device state

Monitor device state changes and configure wake by BT.

Bug: 371030412
Bug: 366432079
Test: m -j, manual test suspend
Test: atest AdapterSuspendTest
Flag: com.android.bluetooth.flags.adapter_suspend_mgmt
Change-Id: I091196103cb09bfae7b146da4e999ed0baf02f26
parent ee8e422f
Loading
Loading
Loading
Loading
+7 −4
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@ import static com.android.bluetooth.Utils.callerIsSystemOrActiveOrManagedUser;
import static com.android.bluetooth.Utils.getBytesFromAddress;
import static com.android.bluetooth.Utils.isDualModeAudioEnabled;
import static com.android.bluetooth.Utils.isPackageNameAccurate;
import static com.android.modules.utils.build.SdkLevel.isAtLeastV;

import static java.util.Objects.requireNonNull;

@@ -89,7 +90,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.hardware.devicestate.DeviceStateManager;
import android.os.AsyncTask;
import android.os.BatteryStatsManager;
import android.os.Binder;
@@ -733,10 +734,10 @@ public class AdapterService extends Service {

        mBluetoothSocketManagerBinder = new BluetoothSocketManagerBinder(this);

        if (Flags.adapterSuspendMgmt()) {
        if (Flags.adapterSuspendMgmt() && isAtLeastV()) {
            mAdapterSuspend =
                    new AdapterSuspend(
                            mNativeInterface, mLooper, getSystemService(DisplayManager.class));
                            mNativeInterface, mLooper, getSystemService(DeviceStateManager.class));
        }

        invalidateBluetoothCaches();
@@ -1482,7 +1483,9 @@ public class AdapterService extends Service {
        }

        if (mAdapterSuspend != null) {
            if (Flags.adapterSuspendMgmt() && isAtLeastV()) {
                mAdapterSuspend.cleanup();
            }
            mAdapterSuspend = null;
        }

+69 −31
Original line number Diff line number Diff line
@@ -21,16 +21,19 @@ import static android.bluetooth.BluetoothAdapter.SCAN_MODE_NONE;

import static java.util.Objects.requireNonNull;

import android.hardware.display.DisplayManager;
import android.annotation.NonNull;
import android.hardware.devicestate.DeviceState;
import android.hardware.devicestate.DeviceStateManager;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.Display;

import com.android.internal.annotations.VisibleForTesting;
import androidx.annotation.RequiresApi;

import java.util.Arrays;
import com.android.internal.annotations.VisibleForTesting;

@RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM)
public class AdapterSuspend {
    private static final String TAG = "BtAdapterSuspend";

@@ -39,42 +42,78 @@ public class AdapterSuspend {
    private static final long MASK_DISCONNECT_CMPLT = 1 << 4;
    private static final long MASK_MODE_CHANGE = 1 << 19;

    private boolean mSuspended = false;
    private static final String DEVICE_STATE_LAPTOP = "LAPTOP";
    private static final String DEVICE_STATE_TABLET = "TABLET";
    private static final String DEVICE_STATE_DOCKED = "DOCKED";
    private static final String DEVICE_STATE_CLOSED = "CLOSED";
    private static final String DEVICE_STATE_DISPLAY_OFF = "DISPLAY_OFF";

    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) {}
    private final DeviceStateManager mDeviceStateManager;

    public final DeviceStateManager.DeviceStateCallback mDeviceStateCallback =
            new DeviceStateManager.DeviceStateCallback() {
                @Override
                public void onDisplayRemoved(int displayId) {}
                public void onDeviceStateChanged(@NonNull DeviceState state) {
                    String nextState = state.getName();
                    Log.d(TAG, "Handle state transition: " + mCurrentState + " => " + nextState);
                    if (mCurrentState.equals("None")) {
                        mCurrentState = nextState;
                        Log.i(TAG, "Initialize device state to " + nextState);
                        return;
                    }

                @Override
                public void onDisplayChanged(int displayId) {
                    if (isScreenOn()) {
                    switch (nextState) {
                        case DEVICE_STATE_CLOSED -> {
                            switch (mCurrentState) {
                                case DEVICE_STATE_DISPLAY_OFF ->
                                        Log.d(TAG, "No action for state " + nextState);
                                default -> handleSuspend(false);
                            }
                        }
                        case DEVICE_STATE_DISPLAY_OFF -> {
                            switch (mCurrentState) {
                                case DEVICE_STATE_TABLET -> handleSuspend(false);
                                case DEVICE_STATE_DOCKED, DEVICE_STATE_LAPTOP ->
                                        handleSuspend(true);
                                default -> Log.d(TAG, "No action for state " + nextState);
                            }
                        }
                        case DEVICE_STATE_LAPTOP, DEVICE_STATE_DOCKED, DEVICE_STATE_TABLET -> {
                            switch (mCurrentState) {
                                case DEVICE_STATE_CLOSED, DEVICE_STATE_DISPLAY_OFF ->
                                        handleResume();
                    } else {
                        handleSuspend();
                                default -> Log.d(TAG, "No action for state " + nextState);
                            }
                        }
                        default -> {
                            Log.wtf(TAG, "Unknown state transition to " + nextState);
                            return;
                        }
                    }
                    mCurrentState = nextState;
                }
            };

    private boolean mSuspended = false;

    // Value should be initialized when registering the mDeviceStateCallback.
    private String mCurrentState = "None";

    private final AdapterNativeInterface mAdapterNativeInterface;

    public AdapterSuspend(
            AdapterNativeInterface adapterNativeInterface,
            Looper looper,
            DisplayManager displayManager) {
            DeviceStateManager deviceStateManager) {
        mAdapterNativeInterface = requireNonNull(adapterNativeInterface);
        mLooper = requireNonNull(looper);
        mDisplayManager = requireNonNull(displayManager);
        Handler handler = new Handler(requireNonNull(looper));

        mDisplayManager.registerDisplayListener(mDisplayListener, new Handler(mLooper));
        mDeviceStateManager = requireNonNull(deviceStateManager);
        mDeviceStateManager.registerCallback(handler::post, mDeviceStateCallback);
    }

    void cleanup() {
        mDisplayManager.unregisterDisplayListener(mDisplayListener);
        mDeviceStateManager.unregisterCallback(mDeviceStateCallback);
    }

    @VisibleForTesting
@@ -82,13 +121,8 @@ public class AdapterSuspend {
        return mSuspended;
    }

    private boolean isScreenOn() {
        return Arrays.stream(mDisplayManager.getDisplays())
                .anyMatch(display -> display.getState() == Display.STATE_ON);
    }

    @VisibleForTesting
    void handleSuspend() {
    void handleSuspend(boolean allowWakeByHid) {
        if (mSuspended) {
            return;
        }
@@ -106,7 +140,11 @@ public class AdapterSuspend {
        mAdapterNativeInterface.clearEventFilter();
        mAdapterNativeInterface.clearFilterAcceptList();
        mAdapterNativeInterface.disconnectAllAcls();

        if (allowWakeByHid) {
            mAdapterNativeInterface.allowWakeByHid();
            Log.i(TAG, "configure wake by hid");
        }
        Log.i(TAG, "ready to suspend");
    }

+5 −5
Original line number Diff line number Diff line
@@ -25,7 +25,7 @@ import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.verify;

import android.content.Context;
import android.hardware.display.DisplayManager;
import android.hardware.devicestate.DeviceStateManager;
import android.os.test.TestLooper;

import androidx.test.InstrumentationRegistry;
@@ -44,7 +44,7 @@ import org.mockito.junit.MockitoRule;
@RunWith(AndroidJUnit4.class)
public class AdapterSuspendTest {
    private TestLooper mTestLooper;
    private DisplayManager mDisplayManager;
    private DeviceStateManager mDeviceStateManager;
    private AdapterSuspend mAdapterSuspend;

    @Rule public MockitoRule mockitoRule = MockitoJUnit.rule();
@@ -54,15 +54,15 @@ public class AdapterSuspendTest {
    public void setUp() throws Exception {
        Context context = InstrumentationRegistry.getTargetContext();
        mTestLooper = new TestLooper();
        mDisplayManager = context.getSystemService(DisplayManager.class);
        mDeviceStateManager = context.getSystemService(DeviceStateManager.class);

        mAdapterSuspend =
                new AdapterSuspend(
                        mAdapterNativeInterface, mTestLooper.getLooper(), mDisplayManager);
                        mAdapterNativeInterface, mTestLooper.getLooper(), mDeviceStateManager);
    }

    private void triggerSuspend() throws Exception {
        mAdapterSuspend.handleSuspend();
        mAdapterSuspend.handleSuspend(true);
    }

    private void triggerResume() throws Exception {