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

Commit 2d2786a4 authored by Ying Hsu's avatar Ying Hsu
Browse files

Initial implementation for AdapterSuspend

AdapterSuspend listens to Display state changes
and configures the Bluetooth controller to only allow
wake events from bonded Bluetooth HID devices.
This ensures that the system can be woken up by Bluetooth
while minimizing power consumption.

Bug: 327644045
Bug: 366432079
Test: m -j
Test: suspend system and wake it up by classic mouse
Flag: adapter_suspend_mgmt

Change-Id: I22f7fa49397d365ec59d4db61da14372bffe5b86
parent 4c97123e
Loading
Loading
Loading
Loading
+13 −0
Original line number Diff line number Diff line
@@ -87,6 +87,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;
@@ -280,6 +281,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;
@@ -754,6 +756,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);
        }
@@ -1481,6 +1489,11 @@ public class AdapterService extends Service {
            mBluetoothSocketManagerBinder = null;
        }

        if (mAdapterSuspend != null) {
            mAdapterSuspend.cleanup();
            mAdapterSuspend = null;
        }

        mPreferredAudioProfilesCallbacks.kill();

        mBluetoothQualityReportReadyCallbacks.kill();
+120 −0
Original line number Diff line number Diff line
/*
 * 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");
    }
}