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

Commit d5a6dabc authored by William Escande's avatar William Escande
Browse files

Adapter: Reduce MainLooper load when connect profiles

Historically, getProfileProxy has been running it's callback on the main
thread. To respect API contract, we cannot change this anymore, plus, As
per API guideline, this is expected:
go/android-api-guidelines#provide-executor
> if the Executor is not provided, the callback should be invoked on the
  main thread using Looper.getMainLooper()

If we want to change the callback to another thread/executor, we need to
provide a new API that take an executor in parameter.

But before doing that, we can analyze that the current implementation is
doing other operation on the main looper, and Bluetooth could reduce the
burden on the main looper without the need for a new API.
For example, `proxy.onServiceConnected` doesn't involve a callback call
and has no reason to be run on the mainLooper (except for simplicity of
lock). We can extract all of this and only call the user callback on the
mainLooper.

Bug: 377808044
Bug: 367914132
Bug: 370815283
Test: m .
Test: pair some HA devices and reboot the phone. The settings should
      have it's cached properly updated
Flag: com.android.bluetooth.flags.get_profile_use_lock
Change-Id: Ibaf52b538a35e037f0307106ea1fbe05dd0461c8
parent 6fa6d9cb
Loading
Loading
Loading
Loading
+70 −50
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import static android.Manifest.permission.BLUETOOTH_PRIVILEGED;
import static android.Manifest.permission.BLUETOOTH_SCAN;
import static android.Manifest.permission.LOCAL_MAC_ADDRESS;
import static android.Manifest.permission.MODIFY_PHONE_STATE;
import static android.bluetooth.BluetoothProfile.getProfileName;
import static android.bluetooth.BluetoothStatusCodes.FEATURE_NOT_SUPPORTED;
import static android.bluetooth.BluetoothUtils.executeFromBinder;

@@ -888,31 +889,44 @@ public final class BluetoothAdapter {
            mMetadataListeners = new HashMap<>();

    private static final class ProfileConnection {
        int mProfile;
        BluetoothProfile.ServiceListener mListener;
        private final int mProfile;
        private final BluetoothProfile.ServiceListener mListener;
        private final Executor mExecutor;

        @GuardedBy("BluetoothAdapter.sProfileLock")
        boolean mConnected = false;

        ProfileConnection(int profile, BluetoothProfile.ServiceListener listener) {
        ProfileConnection(
                int profile, BluetoothProfile.ServiceListener listener, Executor executor) {
            mProfile = profile;
            mListener = listener;
            mExecutor = executor;
        }

        @GuardedBy("BluetoothAdapter.sProfileLock")
        void connect(BluetoothProfile proxy, IBinder binder) {
            Log.d(TAG, BluetoothProfile.getProfileName(mProfile) + " connected");
            Log.d(TAG, getProfileName(mProfile) + " connected");
            mConnected = true;
            proxy.onServiceConnected(binder);
            if (Flags.getProfileUseLock()) {
                executeFromBinder(mExecutor, () -> mListener.onServiceConnected(mProfile, proxy));
            } else {
                mListener.onServiceConnected(mProfile, proxy);
            }
        }

        @GuardedBy("BluetoothAdapter.sProfileLock")
        void disconnect(BluetoothProfile proxy) {
            Log.d(TAG, BluetoothProfile.getProfileName(mProfile) + " disconnected");
            Log.d(TAG, getProfileName(mProfile) + " disconnected");
            mConnected = false;
            proxy.onServiceDisconnected();
            if (Flags.getProfileUseLock()) {
                executeFromBinder(mExecutor, () -> mListener.onServiceDisconnected(mProfile));
            } else {
                mListener.onServiceDisconnected(mProfile);
            }
        }
    }

    private static final Object sProfileLock = new Object();

@@ -2334,11 +2348,7 @@ public final class BluetoothAdapter {
        try {
            if (mService != null) {
                if (DBG) {
                    Log.d(
                            TAG,
                            "getActiveDevices(profile= "
                                    + BluetoothProfile.getProfileName(profile)
                                    + ")");
                    Log.d(TAG, "getActiveDevices(" + getProfileName(profile) + ")");
                }
                return mService.getActiveDevices(profile, mAttributionSource);
            }
@@ -3561,17 +3571,11 @@ public final class BluetoothAdapter {
    /**
     * Get the profile proxy object associated with the profile.
     *
     * <p>Profile can be one of {@link BluetoothProfile#HEADSET}, {@link BluetoothProfile#A2DP},
     * {@link BluetoothProfile#GATT}, {@link BluetoothProfile#HEARING_AID}, or {@link
     * BluetoothProfile#GATT_SERVER}. Clients must implement {@link
     * BluetoothProfile.ServiceListener} to get notified of the connection status and to get the
     * proxy object.
     * <p> The ServiceListener's methods will be invoked on the application's main looper
     *
     * @param context Context of the application
     * @param listener The service Listener for connection callbacks.
     * @param profile The Bluetooth profile; either {@link BluetoothProfile#HEADSET}, {@link
     *     BluetoothProfile#A2DP}, {@link BluetoothProfile#GATT}, {@link
     *     BluetoothProfile#HEARING_AID} or {@link BluetoothProfile#GATT_SERVER}.
     * @param listener The service listener for connection callbacks.
     * @param profile The Bluetooth profile to listen for status change
     * @return true on success, false on error
     */
    @SuppressLint({
@@ -3585,6 +3589,26 @@ public final class BluetoothAdapter {
            return false;
        }

        // Preserve legacy compatibility where apps were depending on
        // registerStateChangeCallback() performing a permissions check which
        // has been relaxed in modern platform versions
        if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.S
                && context.checkSelfPermission(BLUETOOTH) != PackageManager.PERMISSION_GRANTED) {
            throw new SecurityException("Need BLUETOOTH permission");
        }

        return getProfileProxy(context, listener, profile, mMainHandler::post);
    }

    private boolean getProfileProxy(
            @NonNull Context context,
            @NonNull BluetoothProfile.ServiceListener listener,
            int profile,
            @NonNull @CallbackExecutor Executor executor) {
        requireNonNull(context);
        requireNonNull(listener);
        requireNonNull(executor);

        if (profile == BluetoothProfile.HEALTH) {
            Log.e(TAG, "getProfileProxy(): BluetoothHealth is deprecated");
            return false;
@@ -3598,28 +3622,19 @@ public final class BluetoothAdapter {
        BiFunction<Context, BluetoothAdapter, BluetoothProfile> constructor =
                PROFILE_CONSTRUCTORS.get(profile);

        BluetoothProfile profileProxy = constructor.apply(context, this);
        ProfileConnection connection = new ProfileConnection(profile, listener, executor);

        if (constructor == null) {
            Log.e(TAG, "getProfileProxy(): Unknown profile " + profile);
            return false;
        }

        // Preserve legacy compatibility where apps were depending on
        // registerStateChangeCallback() performing a permissions check which
        // has been relaxed in modern platform versions
        if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.S
                && context.checkSelfPermission(BLUETOOTH) != PackageManager.PERMISSION_GRANTED) {
            throw new SecurityException("Need BLUETOOTH permission");
        }

        BluetoothProfile profileProxy = constructor.apply(context, this);
        ProfileConnection connection = new ProfileConnection(profile, listener);

        Runnable connectAction =
                () -> {
                    synchronized (sProfileLock) {
                        // Synchronize with the binder callback to prevent performing the
                        // connection.connect
                        // concurrently
                        // ProfileConnection.connect concurrently
                        mProfileConnections.put(profileProxy, connection);

                        IBinder binder = getProfile(profile);
@@ -3632,7 +3647,7 @@ public final class BluetoothAdapter {
            connectAction.run();
            return true;
        }
        mMainHandler.post(connectAction);
        executor.execute(connectAction);
        return true;
    }

@@ -3935,25 +3950,31 @@ public final class BluetoothAdapter {
                    }
                }

                @RequiresNoPermission
                public void onBluetoothOn() {
                    Runnable btOnAction =
                            () -> {
                                synchronized (sProfileLock) {
                @GuardedBy("sProfileLock")
                private boolean connectAllProfileProxyLocked() {
                    mProfileConnections.forEach(
                            (proxy, connection) -> {
                                if (connection.mConnected) return;

                                IBinder binder = getProfile(connection.mProfile);
                                                if (binder != null) {
                                                    connection.connect(proxy, binder);
                                                } else {
                                if (binder == null) {
                                    Log.e(
                                            TAG,
                                                            "onBluetoothOn: Binder null for "
                                                                    + BluetoothProfile.getProfileName(connection.mProfile));
                                            "Failed to retrieve a binder for "
                                                    + getProfileName(connection.mProfile));
                                    return;
                                }
                                connection.connect(proxy, binder);
                            });
                    return true;
                }

                @RequiresNoPermission
                public void onBluetoothOn() {
                    Runnable btOnAction =
                            () -> {
                                synchronized (sProfileLock) {
                                    connectAllProfileProxyLocked();
                                }
                            };
                    if (Flags.getProfileUseLock()) {
@@ -4283,7 +4304,6 @@ public final class BluetoothAdapter {
    }

    /** Return a binder to a Profile service */
    @GuardedBy("sProfileLock")
    private @Nullable IBinder getProfile(int profile) {
        mServiceLock.readLock().lock();
        try {