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

Commit 8a6588e7 authored by Wei Wang's avatar Wei Wang
Browse files

Fix threading issue for advertising.

Post callback to main thread to execute. In general we should avoid
running app's callback method from binder thread as the callback method
may block.
Also removed callback from advertisers on stop advertising callback.
This fixes the issue of not being able to restart adv for limited
advertising.

Bug: 17045567

Change-Id: I56cd2bdf4b1ad120a0358fa4065ad77be4bff144
parent 8350757d
Loading
Loading
Loading
Loading
+83 −80
Original line number Diff line number Diff line
@@ -21,7 +21,6 @@ import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallbackWrapper;
import android.bluetooth.BluetoothUuid;
import android.bluetooth.IBluetoothGatt;
import android.bluetooth.IBluetoothGattCallback;
import android.bluetooth.IBluetoothManager;
import android.os.Handler;
import android.os.Looper;
@@ -30,7 +29,6 @@ import android.os.RemoteException;
import android.util.Log;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

@@ -108,41 +106,37 @@ public final class BluetoothLeAdvertiser {
    public void startAdvertising(AdvertiseSettings settings,
            AdvertiseData advertiseData, AdvertiseData scanResponse,
            final AdvertiseCallback callback) {
        synchronized (mLeAdvertisers) {
            checkAdapterState();
            if (callback == null) {
                throw new IllegalArgumentException("callback cannot be null");
            }
            if (!mBluetoothAdapter.isMultipleAdvertisementSupported()) {
                postStartFailure(callback,
                        AdvertiseCallback.ADVERTISE_FAILED_FEATURE_UNSUPPORTED);
                return;
            }
            if (totalBytes(advertiseData) > MAX_ADVERTISING_DATA_BYTES ||
                    totalBytes(scanResponse) > MAX_ADVERTISING_DATA_BYTES) {
            postCallbackFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE);
                postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE);
                return;
            }
            if (mLeAdvertisers.containsKey(callback)) {
            postCallbackFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_ALREADY_STARTED);
                postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_ALREADY_STARTED);
                return;
            }

            IBluetoothGatt gatt;
            try {
                gatt = mBluetoothManager.getBluetoothGatt();
            } catch (RemoteException e) {
                Log.e(TAG, "Failed to get Bluetooth gatt - ", e);
            postCallbackFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR);
            return;
        }
        if (!mBluetoothAdapter.isMultipleAdvertisementSupported()) {
            postCallbackFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_FEATURE_UNSUPPORTED);
                postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR);
                return;
            }
            AdvertiseCallbackWrapper wrapper = new AdvertiseCallbackWrapper(callback, advertiseData,
                    scanResponse, settings, gatt);
        UUID uuid = UUID.randomUUID();
        try {
            gatt.registerClient(new ParcelUuid(uuid), wrapper);
            if (wrapper.advertiseStarted()) {
                mLeAdvertisers.put(callback, wrapper);
            }
        } catch (RemoteException e) {
            Log.e(TAG, "Failed to stop advertising", e);
            wrapper.startRegisteration();
        }
    }

@@ -155,23 +149,14 @@ public final class BluetoothLeAdvertiser {
     * @param callback {@link AdvertiseCallback} identifies the advertising instance to stop.
     */
    public void stopAdvertising(final AdvertiseCallback callback) {
        synchronized (mLeAdvertisers) {
            checkAdapterState();
            if (callback == null) {
                throw new IllegalArgumentException("callback cannot be null");
            }
            AdvertiseCallbackWrapper wrapper = mLeAdvertisers.get(callback);
        if (wrapper == null)
            return;
        try {
            IBluetoothGatt gatt = mBluetoothManager.getBluetoothGatt();
            if (gatt != null)
                gatt.stopMultiAdvertising(wrapper.mClientIf);

            if (wrapper.advertiseStopped()) {
                mLeAdvertisers.remove(callback);
            }
        } catch (RemoteException e) {
            Log.e(TAG, "Failed to stop advertising", e);
            if (wrapper == null) return;
            wrapper.stopAdvertising();
        }
    }

@@ -186,9 +171,7 @@ public final class BluetoothLeAdvertiser {

    // Compute the size of the advertise data.
    private int totalBytes(AdvertiseData data) {
        if (data == null) {
            return 0;
        }
        if (data == null) return 0;
        int size = FLAGS_FIELD_BYTES; // flags field is always set.
        if (data.getServiceUuids() != null) {
            int num16BitUuids = 0;
@@ -243,7 +226,7 @@ public final class BluetoothLeAdvertiser {
    /**
     * Bluetooth GATT interface callbacks for advertising.
     */
    private static class AdvertiseCallbackWrapper extends BluetoothGattCallbackWrapper {
    private class AdvertiseCallbackWrapper extends BluetoothGattCallbackWrapper {
        private static final int LE_CALLBACK_TIMEOUT_MILLIS = 2000;
        private final AdvertiseCallback mAdvertiseCallback;
        private final AdvertiseData mAdvertisement;
@@ -269,30 +252,40 @@ public final class BluetoothLeAdvertiser {
            mClientIf = 0;
        }

        public boolean advertiseStarted() {
            boolean started = false;
        public void startRegisteration() {
            synchronized (this) {
                if (mClientIf == -1) {
                    return false;
                }
                if (mClientIf == -1) return;

                try {
                    UUID uuid = UUID.randomUUID();
                    mBluetoothGatt.registerClient(new ParcelUuid(uuid), this);
                    wait(LE_CALLBACK_TIMEOUT_MILLIS);
                } catch (InterruptedException e) {
                    Log.e(TAG, "Callback reg wait interrupted: ", e);
                } catch (InterruptedException | RemoteException e) {
                    Log.e(TAG, "Failed to start registeration", e);
                }
                if (mClientIf > 0 && mIsAdvertising) {
                    mLeAdvertisers.put(mAdvertiseCallback, this);
                } else {
                    postStartFailure(mAdvertiseCallback,
                            AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR);
                }
                started = (mClientIf > 0 && mIsAdvertising);
            }
            return started;
        }

        public boolean advertiseStopped() {
        public void stopAdvertising() {
            synchronized (this) {
                try {
                    mBluetoothGatt.stopMultiAdvertising(mClientIf);
                    wait(LE_CALLBACK_TIMEOUT_MILLIS);
                } catch (InterruptedException e) {
                    Log.e(TAG, "Callback reg wait interrupted: " + e);
                } catch (InterruptedException | RemoteException e) {
                    Log.e(TAG, "Failed to stop advertising", e);
                }
                // Advertise callback should have been removed from LeAdvertisers when
                // onMultiAdvertiseCallback was called. In case onMultiAdvertiseCallback is never
                // invoked and wait timeout expires, remove callback here.
                if (mLeAdvertisers.containsKey(mAdvertiseCallback)) {
                    mLeAdvertisers.remove(mAdvertiseCallback);
                }
                return !mIsAdvertising;
            }
        }

@@ -308,18 +301,16 @@ public final class BluetoothLeAdvertiser {
                    try {
                        mBluetoothGatt.startMultiAdvertising(mClientIf, mAdvertisement,
                                mScanResponse, mSettings);
                        return;
                    } catch (RemoteException e) {
                        Log.e(TAG, "fail to start le advertise: " + e);
                        mClientIf = -1;
                        notifyAll();
                        Log.e(TAG, "failed to start advertising", e);
                    }
                } else {
                    // registration failed
                }
                // Registration failed.
                mClientIf = -1;
                notifyAll();
            }
        }
        }

        @Override
        public void onMultiAdvertiseCallback(int status, boolean isStart,
@@ -328,11 +319,11 @@ public final class BluetoothLeAdvertiser {
                if (isStart) {
                    if (status == AdvertiseCallback.ADVERTISE_SUCCESS) {
                        // Start success
                        mAdvertiseCallback.onStartSuccess(settings);
                        mIsAdvertising = true;
                        postStartSuccess(mAdvertiseCallback, settings);
                    } else {
                        // Start failure.
                        mAdvertiseCallback.onStartFailure(status);
                        postStartFailure(mAdvertiseCallback, status);
                    }
                } else {
                    // unregister client for stop.
@@ -340,6 +331,7 @@ public final class BluetoothLeAdvertiser {
                        mBluetoothGatt.unregisterClient(mClientIf);
                        mClientIf = -1;
                        mIsAdvertising = false;
                        mLeAdvertisers.remove(mAdvertiseCallback);
                    } catch (RemoteException e) {
                        Log.e(TAG, "remote exception when unregistering", e);
                    }
@@ -357,7 +349,7 @@ public final class BluetoothLeAdvertiser {
        }
    }

    private void postCallbackFailure(final AdvertiseCallback callback, final int error) {
    private void postStartFailure(final AdvertiseCallback callback, final int error) {
        mHandler.post(new Runnable() {
            @Override
            public void run() {
@@ -365,4 +357,15 @@ public final class BluetoothLeAdvertiser {
            }
        });
    }

    private void postStartSuccess(final AdvertiseCallback callback,
            final AdvertiseSettings settings) {
        mHandler.post(new Runnable() {

            @Override
            public void run() {
                callback.onStartSuccess(settings);
            }
        });
    }
}