android.bluetooth.IBluetoothManager

Here are the examples of the java api android.bluetooth.IBluetoothManager taken from open source projects. By voting up you can indicate which examples are most useful and appropriate.

3 Examples 7

19 Source : PeriodicAdvertisingManager.java
with Apache License 2.0
from lulululbj

/**
 * This clreplaced provides methods to perform periodic advertising related
 * operations. An application can register for periodic advertisements using
 * {@link PeriodicAdvertisingManager#registerSync}.
 * <p>
 * Use {@link BluetoothAdapter#getPeriodicAdvertisingManager()} to get an
 * instance of {@link PeriodicAdvertisingManager}.
 * <p>
 * <b>Note:</b> Most of the methods here require
 * {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
 *
 * @hide
 */
public final clreplaced PeriodicAdvertisingManager {

    private static final String TAG = "PeriodicAdvertisingManager";

    private static final int SKIP_MIN = 0;

    private static final int SKIP_MAX = 499;

    private static final int TIMEOUT_MIN = 10;

    private static final int TIMEOUT_MAX = 16384;

    private static final int SYNC_STARTING = -1;

    private final IBluetoothManager mBluetoothManager;

    private BluetoothAdapter mBluetoothAdapter;

    /* maps callback, to callback wrapper and sync handle */
    Map<PeriodicAdvertisingCallback, IPeriodicAdvertisingCallback> /* callbackWrapper */
    mCallbackWrappers;

    /**
     * Use {@link BluetoothAdapter#getBluetoothLeScanner()} instead.
     *
     * @param bluetoothManager BluetoothManager that conducts overall Bluetooth Management.
     * @hide
     */
    public PeriodicAdvertisingManager(IBluetoothManager bluetoothManager) {
        mBluetoothManager = bluetoothManager;
        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        mCallbackWrappers = new IdenreplacedyHashMap<>();
    }

    /**
     * Synchronize with periodic advertising pointed to by the {@code scanResult}.
     * The {@code scanResult} used must contain a valid advertisingSid. First
     * call to registerSync will use the {@code skip} and {@code timeout} provided.
     * Subsequent calls from other apps, trying to sync with same set will reuse
     * existing sync, thus {@code skip} and {@code timeout} values will not take
     * effect. The values in effect will be returned in
     * {@link PeriodicAdvertisingCallback#onSyncEstablished}.
     *
     * @param scanResult Scan result containing advertisingSid.
     * @param skip The number of periodic advertising packets that can be skipped after a successful
     * receive. Must be between 0 and 499.
     * @param timeout Synchronization timeout for the periodic advertising. One unit is 10ms. Must
     * be between 10 (100ms) and 16384 (163.84s).
     * @param callback Callback used to deliver all operations status.
     * @throws IllegalArgumentException if {@code scanResult} is null or {@code skip} is invalid or
     * {@code timeout} is invalid or {@code callback} is null.
     */
    public void registerSync(ScanResult scanResult, int skip, int timeout, PeriodicAdvertisingCallback callback) {
        registerSync(scanResult, skip, timeout, callback, null);
    }

    /**
     * Synchronize with periodic advertising pointed to by the {@code scanResult}.
     * The {@code scanResult} used must contain a valid advertisingSid. First
     * call to registerSync will use the {@code skip} and {@code timeout} provided.
     * Subsequent calls from other apps, trying to sync with same set will reuse
     * existing sync, thus {@code skip} and {@code timeout} values will not take
     * effect. The values in effect will be returned in
     * {@link PeriodicAdvertisingCallback#onSyncEstablished}.
     *
     * @param scanResult Scan result containing advertisingSid.
     * @param skip The number of periodic advertising packets that can be skipped after a successful
     * receive. Must be between 0 and 499.
     * @param timeout Synchronization timeout for the periodic advertising. One unit is 10ms. Must
     * be between 10 (100ms) and 16384 (163.84s).
     * @param callback Callback used to deliver all operations status.
     * @param handler thread upon which the callbacks will be invoked.
     * @throws IllegalArgumentException if {@code scanResult} is null or {@code skip} is invalid or
     * {@code timeout} is invalid or {@code callback} is null.
     */
    public void registerSync(ScanResult scanResult, int skip, int timeout, PeriodicAdvertisingCallback callback, Handler handler) {
        if (callback == null) {
            throw new IllegalArgumentException("callback can't be null");
        }
        if (scanResult == null) {
            throw new IllegalArgumentException("scanResult can't be null");
        }
        if (scanResult.getAdvertisingSid() == ScanResult.SID_NOT_PRESENT) {
            throw new IllegalArgumentException("scanResult must contain a valid sid");
        }
        if (skip < SKIP_MIN || skip > SKIP_MAX) {
            throw new IllegalArgumentException("timeout must be between " + TIMEOUT_MIN + " and " + TIMEOUT_MAX);
        }
        if (timeout < TIMEOUT_MIN || timeout > TIMEOUT_MAX) {
            throw new IllegalArgumentException("timeout must be between " + TIMEOUT_MIN + " and " + TIMEOUT_MAX);
        }
        IBluetoothGatt gatt;
        try {
            gatt = mBluetoothManager.getBluetoothGatt();
        } catch (RemoteException e) {
            Log.e(TAG, "Failed to get Bluetooth gatt - ", e);
            callback.onSyncEstablished(0, scanResult.getDevice(), scanResult.getAdvertisingSid(), skip, timeout, PeriodicAdvertisingCallback.SYNC_NO_RESOURCES);
            return;
        }
        if (handler == null) {
            handler = new Handler(Looper.getMainLooper());
        }
        IPeriodicAdvertisingCallback wrapped = wrap(callback, handler);
        mCallbackWrappers.put(callback, wrapped);
        try {
            gatt.registerSync(scanResult, skip, timeout, wrapped);
        } catch (RemoteException e) {
            Log.e(TAG, "Failed to register sync - ", e);
            return;
        }
    }

    /**
     * Cancel pending attempt to create sync, or terminate existing sync.
     *
     * @param callback Callback used to deliver all operations status.
     * @throws IllegalArgumentException if {@code callback} is null, or not a properly registered
     * callback.
     */
    public void unregisterSync(PeriodicAdvertisingCallback callback) {
        if (callback == null) {
            throw new IllegalArgumentException("callback can't be null");
        }
        IBluetoothGatt gatt;
        try {
            gatt = mBluetoothManager.getBluetoothGatt();
        } catch (RemoteException e) {
            Log.e(TAG, "Failed to get Bluetooth gatt - ", e);
            return;
        }
        IPeriodicAdvertisingCallback wrapper = mCallbackWrappers.remove(callback);
        if (wrapper == null) {
            throw new IllegalArgumentException("callback was not properly registered");
        }
        try {
            gatt.unregisterSync(wrapper);
        } catch (RemoteException e) {
            Log.e(TAG, "Failed to cancel sync creation - ", e);
            return;
        }
    }

    private IPeriodicAdvertisingCallback wrap(PeriodicAdvertisingCallback callback, Handler handler) {
        return new IPeriodicAdvertisingCallback.Stub() {

            public void onSyncEstablished(int syncHandle, BluetoothDevice device, int advertisingSid, int skip, int timeout, int status) {
                handler.post(new Runnable() {

                    @Override
                    public void run() {
                        callback.onSyncEstablished(syncHandle, device, advertisingSid, skip, timeout, status);
                        if (status != PeriodicAdvertisingCallback.SYNC_SUCCESS) {
                            // App can still unregister the sync until notified it failed. Remove
                            // callback
                            // after app was notifed.
                            mCallbackWrappers.remove(callback);
                        }
                    }
                });
            }

            public void onPeriodicAdvertisingReport(PeriodicAdvertisingReport report) {
                handler.post(new Runnable() {

                    @Override
                    public void run() {
                        callback.onPeriodicAdvertisingReport(report);
                    }
                });
            }

            public void onSyncLost(int syncHandle) {
                handler.post(new Runnable() {

                    @Override
                    public void run() {
                        callback.onSyncLost(syncHandle);
                        // App can still unregister the sync until notified it's lost.
                        // Remove callback after app was notifed.
                        mCallbackWrappers.remove(callback);
                    }
                });
            }
        };
    }
}

19 Source : BluetoothLeScanner.java
with Apache License 2.0
from lulululbj

/**
 * This clreplaced provides methods to perform scan related operations for Bluetooth LE devices. An
 * application can scan for a particular type of Bluetooth LE devices using {@link ScanFilter}. It
 * can also request different types of callbacks for delivering the result.
 * <p>
 * Use {@link BluetoothAdapter#getBluetoothLeScanner()} to get an instance of
 * {@link BluetoothLeScanner}.
 * <p>
 * <b>Note:</b> Most of the scan methods here require
 * {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
 *
 * @see ScanFilter
 */
public final clreplaced BluetoothLeScanner {

    private static final String TAG = "BluetoothLeScanner";

    private static final boolean DBG = true;

    private static final boolean VDBG = false;

    /**
     * Extra containing a list of ScanResults. It can have one or more results if there was no
     * error. In case of error, {@link #EXTRA_ERROR_CODE} will contain the error code and this
     * extra will not be available.
     */
    public static final String EXTRA_LIST_SCAN_RESULT = "android.bluetooth.le.extra.LIST_SCAN_RESULT";

    /**
     * Optional extra indicating the error code, if any. The error code will be one of the
     * SCAN_FAILED_* codes in {@link ScanCallback}.
     */
    public static final String EXTRA_ERROR_CODE = "android.bluetooth.le.extra.ERROR_CODE";

    /**
     * Optional extra indicating the callback type, which will be one of
     * CALLBACK_TYPE_* constants in {@link ScanSettings}.
     *
     * @see ScanCallback#onScanResult(int, ScanResult)
     */
    public static final String EXTRA_CALLBACK_TYPE = "android.bluetooth.le.extra.CALLBACK_TYPE";

    private final IBluetoothManager mBluetoothManager;

    private final Handler mHandler;

    private BluetoothAdapter mBluetoothAdapter;

    private final Map<ScanCallback, BleScanCallbackWrapper> mLeScanClients;

    /**
     * Use {@link BluetoothAdapter#getBluetoothLeScanner()} instead.
     *
     * @param bluetoothManager BluetoothManager that conducts overall Bluetooth Management.
     * @hide
     */
    public BluetoothLeScanner(IBluetoothManager bluetoothManager) {
        mBluetoothManager = bluetoothManager;
        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        mHandler = new Handler(Looper.getMainLooper());
        mLeScanClients = new HashMap<ScanCallback, BleScanCallbackWrapper>();
    }

    /**
     * Start Bluetooth LE scan with default parameters and no filters. The scan results will be
     * delivered through {@code callback}. For unfiltered scans, scanning is stopped on screen
     * off to save power. Scanning is resumed when screen is turned on again. To avoid this, use
     * {@link #startScan(List, ScanSettings, ScanCallback)} with desired {@link ScanFilter}.
     * <p>
     * An app must hold
     * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} or
     * {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} permission
     * in order to get results.
     *
     * @param callback Callback used to deliver scan results.
     * @throws IllegalArgumentException If {@code callback} is null.
     */
    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
    public void startScan(final ScanCallback callback) {
        startScan(null, new ScanSettings.Builder().build(), callback);
    }

    /**
     * Start Bluetooth LE scan. The scan results will be delivered through {@code callback}.
     * For unfiltered scans, scanning is stopped on screen off to save power. Scanning is
     * resumed when screen is turned on again. To avoid this, do filetered scanning by
     * using proper {@link ScanFilter}.
     * <p>
     * An app must hold
     * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} or
     * {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} permission
     * in order to get results.
     *
     * @param filters {@link ScanFilter}s for finding exact BLE devices.
     * @param settings Settings for the scan.
     * @param callback Callback used to deliver scan results.
     * @throws IllegalArgumentException If {@code settings} or {@code callback} is null.
     */
    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
    public void startScan(List<ScanFilter> filters, ScanSettings settings, final ScanCallback callback) {
        startScan(filters, settings, null, callback, /*callbackIntent=*/
        null, null);
    }

    /**
     * Start Bluetooth LE scan using a {@link PendingIntent}. The scan results will be delivered via
     * the PendingIntent. Use this method of scanning if your process is not always running and it
     * should be started when scan results are available.
     * <p>
     * An app must hold
     * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} or
     * {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} permission
     * in order to get results.
     * <p>
     * When the PendingIntent is delivered, the Intent preplaceded to the receiver or activity
     * will contain one or more of the extras {@link #EXTRA_CALLBACK_TYPE},
     * {@link #EXTRA_ERROR_CODE} and {@link #EXTRA_LIST_SCAN_RESULT} to indicate the result of
     * the scan.
     *
     * @param filters Optional list of ScanFilters for finding exact BLE devices.
     * @param settings Optional settings for the scan.
     * @param callbackIntent The PendingIntent to deliver the result to.
     * @return Returns 0 for success or an error code from {@link ScanCallback} if the scan request
     * could not be sent.
     * @see #stopScan(PendingIntent)
     */
    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
    public int startScan(@Nullable List<ScanFilter> filters, @Nullable ScanSettings settings, @NonNull PendingIntent callbackIntent) {
        return startScan(filters, settings != null ? settings : new ScanSettings.Builder().build(), null, null, callbackIntent, null);
    }

    /**
     * Start Bluetooth LE scan. Same as {@link #startScan(ScanCallback)} but allows the caller to
     * specify on behalf of which application(s) the work is being done.
     *
     * @param workSource {@link WorkSource} identifying the application(s) for which to blame for
     * the scan.
     * @param callback Callback used to deliver scan results.
     * @hide
     */
    @SystemApi
    @RequiresPermission(allOf = { Manifest.permission.BLUETOOTH_ADMIN, Manifest.permission.UPDATE_DEVICE_STATS })
    public void startScanFromSource(final WorkSource workSource, final ScanCallback callback) {
        startScanFromSource(null, new ScanSettings.Builder().build(), workSource, callback);
    }

    /**
     * Start Bluetooth LE scan. Same as {@link #startScan(List, ScanSettings, ScanCallback)} but
     * allows the caller to specify on behalf of which application(s) the work is being done.
     *
     * @param filters {@link ScanFilter}s for finding exact BLE devices.
     * @param settings Settings for the scan.
     * @param workSource {@link WorkSource} identifying the application(s) for which to blame for
     * the scan.
     * @param callback Callback used to deliver scan results.
     * @hide
     */
    @SystemApi
    @RequiresPermission(allOf = { Manifest.permission.BLUETOOTH_ADMIN, Manifest.permission.UPDATE_DEVICE_STATS })
    public void startScanFromSource(List<ScanFilter> filters, ScanSettings settings, final WorkSource workSource, final ScanCallback callback) {
        startScan(filters, settings, workSource, callback, null, null);
    }

    private int startScan(List<ScanFilter> filters, ScanSettings settings, final WorkSource workSource, final ScanCallback callback, final PendingIntent callbackIntent, List<List<ResultStorageDescriptor>> resultStorages) {
        BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter);
        if (callback == null && callbackIntent == null) {
            throw new IllegalArgumentException("callback is null");
        }
        if (settings == null) {
            throw new IllegalArgumentException("settings is null");
        }
        synchronized (mLeScanClients) {
            if (callback != null && mLeScanClients.containsKey(callback)) {
                return postCallbackErrorOrReturn(callback, ScanCallback.SCAN_FAILED_ALREADY_STARTED);
            }
            IBluetoothGatt gatt;
            try {
                gatt = mBluetoothManager.getBluetoothGatt();
            } catch (RemoteException e) {
                gatt = null;
            }
            if (gatt == null) {
                return postCallbackErrorOrReturn(callback, ScanCallback.SCAN_FAILED_INTERNAL_ERROR);
            }
            if (!isSettingsConfigAllowedForScan(settings)) {
                return postCallbackErrorOrReturn(callback, ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED);
            }
            if (!isHardwareResourcesAvailableForScan(settings)) {
                return postCallbackErrorOrReturn(callback, ScanCallback.SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES);
            }
            if (!isSettingsAndFilterComboAllowed(settings, filters)) {
                return postCallbackErrorOrReturn(callback, ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED);
            }
            if (callback != null) {
                BleScanCallbackWrapper wrapper = new BleScanCallbackWrapper(gatt, filters, settings, workSource, callback, resultStorages);
                wrapper.startRegistration();
            } else {
                try {
                    gatt.startScanForIntent(callbackIntent, settings, filters, ActivityThread.currentOpPackageName());
                } catch (RemoteException e) {
                    return ScanCallback.SCAN_FAILED_INTERNAL_ERROR;
                }
            }
        }
        return ScanCallback.NO_ERROR;
    }

    /**
     * Stops an ongoing Bluetooth LE scan.
     *
     * @param callback
     */
    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
    public void stopScan(ScanCallback callback) {
        BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter);
        synchronized (mLeScanClients) {
            BleScanCallbackWrapper wrapper = mLeScanClients.remove(callback);
            if (wrapper == null) {
                if (DBG)
                    Log.d(TAG, "could not find callback wrapper");
                return;
            }
            wrapper.stopLeScan();
        }
    }

    /**
     * Stops an ongoing Bluetooth LE scan started using a PendingIntent. When creating the
     * PendingIntent parameter, please do not use the FLAG_CANCEL_CURRENT flag. Otherwise, the stop
     * scan may have no effect.
     *
     * @param callbackIntent The PendingIntent that was used to start the scan.
     * @see #startScan(List, ScanSettings, PendingIntent)
     */
    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
    public void stopScan(PendingIntent callbackIntent) {
        BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter);
        IBluetoothGatt gatt;
        try {
            gatt = mBluetoothManager.getBluetoothGatt();
            gatt.stopScanForIntent(callbackIntent, ActivityThread.currentOpPackageName());
        } catch (RemoteException e) {
        }
    }

    /**
     * Flush pending batch scan results stored in Bluetooth controller. This will return Bluetooth
     * LE scan results batched on bluetooth controller. Returns immediately, batch scan results data
     * will be delivered through the {@code callback}.
     *
     * @param callback Callback of the Bluetooth LE Scan, it has to be the same instance as the one
     * used to start scan.
     */
    public void flushPendingScanResults(ScanCallback callback) {
        BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter);
        if (callback == null) {
            throw new IllegalArgumentException("callback cannot be null!");
        }
        synchronized (mLeScanClients) {
            BleScanCallbackWrapper wrapper = mLeScanClients.get(callback);
            if (wrapper == null) {
                return;
            }
            wrapper.flushPendingBatchResults();
        }
    }

    /**
     * Start truncated scan.
     *
     * @hide
     */
    @SystemApi
    public void startTruncatedScan(List<TruncatedFilter> truncatedFilters, ScanSettings settings, final ScanCallback callback) {
        int filterSize = truncatedFilters.size();
        List<ScanFilter> scanFilters = new ArrayList<ScanFilter>(filterSize);
        List<List<ResultStorageDescriptor>> scanStorages = new ArrayList<List<ResultStorageDescriptor>>(filterSize);
        for (TruncatedFilter filter : truncatedFilters) {
            scanFilters.add(filter.getFilter());
            scanStorages.add(filter.getStorageDescriptors());
        }
        startScan(scanFilters, settings, null, callback, null, scanStorages);
    }

    /**
     * Cleans up scan clients. Should be called when bluetooth is down.
     *
     * @hide
     */
    public void cleanup() {
        mLeScanClients.clear();
    }

    /**
     * Bluetooth GATT interface callbacks
     */
    private clreplaced BleScanCallbackWrapper extends IScannerCallback.Stub {

        private static final int REGISTRATION_CALLBACK_TIMEOUT_MILLIS = 2000;

        private final ScanCallback mScanCallback;

        private final List<ScanFilter> mFilters;

        private final WorkSource mWorkSource;

        private ScanSettings mSettings;

        private IBluetoothGatt mBluetoothGatt;

        private List<List<ResultStorageDescriptor>> mResultStorages;

        // mLeHandle 0: not registered
        // -2: registration failed because app is scanning to frequently
        // -1: scan stopped or registration failed
        // > 0: registered and scan started
        private int mScannerId;

        public BleScanCallbackWrapper(IBluetoothGatt bluetoothGatt, List<ScanFilter> filters, ScanSettings settings, WorkSource workSource, ScanCallback scanCallback, List<List<ResultStorageDescriptor>> resultStorages) {
            mBluetoothGatt = bluetoothGatt;
            mFilters = filters;
            mSettings = settings;
            mWorkSource = workSource;
            mScanCallback = scanCallback;
            mScannerId = 0;
            mResultStorages = resultStorages;
        }

        public void startRegistration() {
            synchronized (this) {
                // Scan stopped.
                if (mScannerId == -1 || mScannerId == -2)
                    return;
                try {
                    mBluetoothGatt.registerScanner(this, mWorkSource);
                    wait(REGISTRATION_CALLBACK_TIMEOUT_MILLIS);
                } catch (InterruptedException | RemoteException e) {
                    Log.e(TAG, "application registeration exception", e);
                    postCallbackError(mScanCallback, ScanCallback.SCAN_FAILED_INTERNAL_ERROR);
                }
                if (mScannerId > 0) {
                    mLeScanClients.put(mScanCallback, this);
                } else {
                    // Registration timed out or got exception, reset RscannerId to -1 so no
                    // subsequent operations can proceed.
                    if (mScannerId == 0)
                        mScannerId = -1;
                    // If scanning too frequently, don't report anything to the app.
                    if (mScannerId == -2)
                        return;
                    postCallbackError(mScanCallback, ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED);
                }
            }
        }

        public void stopLeScan() {
            synchronized (this) {
                if (mScannerId <= 0) {
                    Log.e(TAG, "Error state, mLeHandle: " + mScannerId);
                    return;
                }
                try {
                    mBluetoothGatt.stopScan(mScannerId);
                    mBluetoothGatt.unregisterScanner(mScannerId);
                } catch (RemoteException e) {
                    Log.e(TAG, "Failed to stop scan and unregister", e);
                }
                mScannerId = -1;
            }
        }

        void flushPendingBatchResults() {
            synchronized (this) {
                if (mScannerId <= 0) {
                    Log.e(TAG, "Error state, mLeHandle: " + mScannerId);
                    return;
                }
                try {
                    mBluetoothGatt.flushPendingBatchResults(mScannerId);
                } catch (RemoteException e) {
                    Log.e(TAG, "Failed to get pending scan results", e);
                }
            }
        }

        /**
         * Application interface registered - app is ready to go
         */
        @Override
        public void onScannerRegistered(int status, int scannerId) {
            Log.d(TAG, "onScannerRegistered() - status=" + status + " scannerId=" + scannerId + " mScannerId=" + mScannerId);
            synchronized (this) {
                if (status == BluetoothGatt.GATT_SUCCESS) {
                    try {
                        if (mScannerId == -1) {
                            // Registration succeeds after timeout, unregister client.
                            mBluetoothGatt.unregisterClient(scannerId);
                        } else {
                            mScannerId = scannerId;
                            mBluetoothGatt.startScan(mScannerId, mSettings, mFilters, mResultStorages, ActivityThread.currentOpPackageName());
                        }
                    } catch (RemoteException e) {
                        Log.e(TAG, "fail to start le scan: " + e);
                        mScannerId = -1;
                    }
                } else if (status == ScanCallback.SCAN_FAILED_SCANNING_TOO_FREQUENTLY) {
                    // applicaiton was scanning too frequently
                    mScannerId = -2;
                } else {
                    // registration failed
                    mScannerId = -1;
                }
                notifyAll();
            }
        }

        /**
         * Callback reporting an LE scan result.
         *
         * @hide
         */
        @Override
        public void onScanResult(final ScanResult scanResult) {
            if (VDBG)
                Log.d(TAG, "onScanResult() - " + scanResult.toString());
            // Check null in case the scan has been stopped
            synchronized (this) {
                if (mScannerId <= 0)
                    return;
            }
            Handler handler = new Handler(Looper.getMainLooper());
            handler.post(new Runnable() {

                @Override
                public void run() {
                    mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_ALL_MATCHES, scanResult);
                }
            });
        }

        @Override
        public void onBatchScanResults(final List<ScanResult> results) {
            Handler handler = new Handler(Looper.getMainLooper());
            handler.post(new Runnable() {

                @Override
                public void run() {
                    mScanCallback.onBatchScanResults(results);
                }
            });
        }

        @Override
        public void onFoundOrLost(final boolean onFound, final ScanResult scanResult) {
            if (VDBG) {
                Log.d(TAG, "onFoundOrLost() - onFound = " + onFound + " " + scanResult.toString());
            }
            // Check null in case the scan has been stopped
            synchronized (this) {
                if (mScannerId <= 0) {
                    return;
                }
            }
            Handler handler = new Handler(Looper.getMainLooper());
            handler.post(new Runnable() {

                @Override
                public void run() {
                    if (onFound) {
                        mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_FIRST_MATCH, scanResult);
                    } else {
                        mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_MATCH_LOST, scanResult);
                    }
                }
            });
        }

        @Override
        public void onScanManagerErrorCallback(final int errorCode) {
            if (VDBG) {
                Log.d(TAG, "onScanManagerErrorCallback() - errorCode = " + errorCode);
            }
            synchronized (this) {
                if (mScannerId <= 0) {
                    return;
                }
            }
            postCallbackError(mScanCallback, errorCode);
        }
    }

    private int postCallbackErrorOrReturn(final ScanCallback callback, final int errorCode) {
        if (callback == null) {
            return errorCode;
        } else {
            postCallbackError(callback, errorCode);
            return ScanCallback.NO_ERROR;
        }
    }

    private void postCallbackError(final ScanCallback callback, final int errorCode) {
        mHandler.post(new Runnable() {

            @Override
            public void run() {
                callback.onScanFailed(errorCode);
            }
        });
    }

    private boolean isSettingsConfigAllowedForScan(ScanSettings settings) {
        if (mBluetoothAdapter.isOffloadedFilteringSupported()) {
            return true;
        }
        final int callbackType = settings.getCallbackType();
        // Only support regular scan if no offloaded filter support.
        if (callbackType == ScanSettings.CALLBACK_TYPE_ALL_MATCHES && settings.getReportDelayMillis() == 0) {
            return true;
        }
        return false;
    }

    private boolean isSettingsAndFilterComboAllowed(ScanSettings settings, List<ScanFilter> filterList) {
        final int callbackType = settings.getCallbackType();
        // If onlost/onfound is requested, a non-empty filter is expected
        if ((callbackType & (ScanSettings.CALLBACK_TYPE_FIRST_MATCH | ScanSettings.CALLBACK_TYPE_MATCH_LOST)) != 0) {
            if (filterList == null) {
                return false;
            }
            for (ScanFilter filter : filterList) {
                if (filter.isAllFieldsEmpty()) {
                    return false;
                }
            }
        }
        return true;
    }

    private boolean isHardwareResourcesAvailableForScan(ScanSettings settings) {
        final int callbackType = settings.getCallbackType();
        if ((callbackType & ScanSettings.CALLBACK_TYPE_FIRST_MATCH) != 0 || (callbackType & ScanSettings.CALLBACK_TYPE_MATCH_LOST) != 0) {
            // For onlost/onfound, we required hw support be available
            return (mBluetoothAdapter.isOffloadedFilteringSupported() && mBluetoothAdapter.isHardwareTrackingFiltersAvailable());
        }
        return true;
    }
}

19 Source : BluetoothLeAdvertiser.java
with Apache License 2.0
from lulululbj

/**
 * This clreplaced provides a way to perform Bluetooth LE advertise operations, such as starting and
 * stopping advertising. An advertiser can broadcast up to 31 bytes of advertisement data
 * represented by {@link AdvertiseData}.
 * <p>
 * To get an instance of {@link BluetoothLeAdvertiser}, call the
 * {@link BluetoothAdapter#getBluetoothLeAdvertiser()} method.
 * <p>
 * <b>Note:</b> Most of the methods here require {@link android.Manifest.permission#BLUETOOTH_ADMIN}
 * permission.
 *
 * @see AdvertiseData
 */
public final clreplaced BluetoothLeAdvertiser {

    private static final String TAG = "BluetoothLeAdvertiser";

    private static final int MAX_ADVERTISING_DATA_BYTES = 1650;

    private static final int MAX_LEGACY_ADVERTISING_DATA_BYTES = 31;

    // Each fields need one byte for field length and another byte for field type.
    private static final int OVERHEAD_BYTES_PER_FIELD = 2;

    // Flags field will be set by system.
    private static final int FLAGS_FIELD_BYTES = 3;

    private static final int MANUFACTURER_SPECIFIC_DATA_LENGTH = 2;

    private final IBluetoothManager mBluetoothManager;

    private final Handler mHandler;

    private BluetoothAdapter mBluetoothAdapter;

    private final Map<AdvertiseCallback, AdvertisingSetCallback> mLegacyAdvertisers = new HashMap<>();

    private final Map<AdvertisingSetCallback, IAdvertisingSetCallback> mCallbackWrappers = Collections.synchronizedMap(new HashMap<>());

    private final Map<Integer, AdvertisingSet> mAdvertisingSets = Collections.synchronizedMap(new HashMap<>());

    /**
     * Use BluetoothAdapter.getLeAdvertiser() instead.
     *
     * @param bluetoothManager BluetoothManager that conducts overall Bluetooth Management
     * @hide
     */
    public BluetoothLeAdvertiser(IBluetoothManager bluetoothManager) {
        mBluetoothManager = bluetoothManager;
        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        mHandler = new Handler(Looper.getMainLooper());
    }

    /**
     * Start Bluetooth LE Advertising. On success, the {@code advertiseData} will be broadcasted.
     * Returns immediately, the operation status is delivered through {@code callback}.
     * <p>
     * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
     *
     * @param settings Settings for Bluetooth LE advertising.
     * @param advertiseData Advertisement data to be broadcasted.
     * @param callback Callback for advertising status.
     */
    public void startAdvertising(AdvertiseSettings settings, AdvertiseData advertiseData, final AdvertiseCallback callback) {
        startAdvertising(settings, advertiseData, null, callback);
    }

    /**
     * Start Bluetooth LE Advertising. The {@code advertiseData} will be broadcasted if the
     * operation succeeds. The {@code scanResponse} is returned when a scanning device sends an
     * active scan request. This method returns immediately, the operation status is delivered
     * through {@code callback}.
     * <p>
     * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
     *
     * @param settings Settings for Bluetooth LE advertising.
     * @param advertiseData Advertisement data to be advertised in advertisement packet.
     * @param scanResponse Scan response replacedociated with the advertisement data.
     * @param callback Callback for advertising status.
     */
    public void startAdvertising(AdvertiseSettings settings, AdvertiseData advertiseData, AdvertiseData scanResponse, final AdvertiseCallback callback) {
        synchronized (mLegacyAdvertisers) {
            BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter);
            if (callback == null) {
                throw new IllegalArgumentException("callback cannot be null");
            }
            boolean isConnectable = settings.isConnectable();
            if (totalBytes(advertiseData, isConnectable) > MAX_LEGACY_ADVERTISING_DATA_BYTES || totalBytes(scanResponse, false) > MAX_LEGACY_ADVERTISING_DATA_BYTES) {
                postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE);
                return;
            }
            if (mLegacyAdvertisers.containsKey(callback)) {
                postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_ALREADY_STARTED);
                return;
            }
            AdvertisingSetParameters.Builder parameters = new AdvertisingSetParameters.Builder();
            parameters.setLegacyMode(true);
            parameters.setConnectable(isConnectable);
            // legacy advertisements we support are always scannable
            parameters.setScannable(true);
            if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_LOW_POWER) {
                // 1s
                parameters.setInterval(1600);
            } else if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_BALANCED) {
                // 250ms
                parameters.setInterval(400);
            } else if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY) {
                // 100ms
                parameters.setInterval(160);
            }
            if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_ULTRA_LOW) {
                parameters.setTxPowerLevel(-21);
            } else if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_LOW) {
                parameters.setTxPowerLevel(-15);
            } else if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM) {
                parameters.setTxPowerLevel(-7);
            } else if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_HIGH) {
                parameters.setTxPowerLevel(1);
            }
            int duration = 0;
            int timeoutMillis = settings.getTimeout();
            if (timeoutMillis > 0) {
                duration = (timeoutMillis < 10) ? 1 : timeoutMillis / 10;
            }
            AdvertisingSetCallback wrapped = wrapOldCallback(callback, settings);
            mLegacyAdvertisers.put(callback, wrapped);
            startAdvertisingSet(parameters.build(), advertiseData, scanResponse, null, null, duration, 0, wrapped);
        }
    }

    AdvertisingSetCallback wrapOldCallback(AdvertiseCallback callback, AdvertiseSettings settings) {
        return new AdvertisingSetCallback() {

            @Override
            public void onAdvertisingSetStarted(AdvertisingSet advertisingSet, int txPower, int status) {
                if (status != AdvertisingSetCallback.ADVERTISE_SUCCESS) {
                    postStartFailure(callback, status);
                    return;
                }
                postStartSuccess(callback, settings);
            }

            /* Legacy advertiser is disabled on timeout */
            @Override
            public void onAdvertisingEnabled(AdvertisingSet advertisingSet, boolean enabled, int status) {
                if (enabled) {
                    Log.e(TAG, "Legacy advertiser should be only disabled on timeout," + " but was enabled!");
                    return;
                }
                stopAdvertising(callback);
            }
        };
    }

    /**
     * Stop Bluetooth LE advertising. The {@code callback} must be the same one use in
     * {@link BluetoothLeAdvertiser#startAdvertising}.
     * <p>
     * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
     *
     * @param callback {@link AdvertiseCallback} identifies the advertising instance to stop.
     */
    public void stopAdvertising(final AdvertiseCallback callback) {
        synchronized (mLegacyAdvertisers) {
            if (callback == null) {
                throw new IllegalArgumentException("callback cannot be null");
            }
            AdvertisingSetCallback wrapper = mLegacyAdvertisers.get(callback);
            if (wrapper == null)
                return;
            stopAdvertisingSet(wrapper);
            mLegacyAdvertisers.remove(callback);
        }
    }

    /**
     * Creates a new advertising set. If operation succeed, device will start advertising. This
     * method returns immediately, the operation status is delivered through
     * {@code callback.onAdvertisingSetStarted()}.
     * <p>
     *
     * @param parameters advertising set parameters.
     * @param advertiseData Advertisement data to be broadcasted. Size must not exceed {@link
     * BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the advertisement is connectable,
     * three bytes will be added for flags.
     * @param scanResponse Scan response replacedociated with the advertisement data. Size must not
     * exceed {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}.
     * @param periodicParameters periodic advertisng parameters. If null, periodic advertising will
     * not be started.
     * @param periodicData Periodic advertising data. Size must not exceed {@link
     * BluetoothAdapter#getLeMaximumAdvertisingDataLength}.
     * @param callback Callback for advertising set.
     * @throws IllegalArgumentException when any of the data parameter exceed the maximum allowable
     * size, or unsupported advertising PHY is selected, or when attempt to use Periodic Advertising
     * feature is made when it's not supported by the controller.
     */
    public void startAdvertisingSet(AdvertisingSetParameters parameters, AdvertiseData advertiseData, AdvertiseData scanResponse, PeriodicAdvertisingParameters periodicParameters, AdvertiseData periodicData, AdvertisingSetCallback callback) {
        startAdvertisingSet(parameters, advertiseData, scanResponse, periodicParameters, periodicData, 0, 0, callback, new Handler(Looper.getMainLooper()));
    }

    /**
     * Creates a new advertising set. If operation succeed, device will start advertising. This
     * method returns immediately, the operation status is delivered through
     * {@code callback.onAdvertisingSetStarted()}.
     * <p>
     *
     * @param parameters advertising set parameters.
     * @param advertiseData Advertisement data to be broadcasted. Size must not exceed {@link
     * BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the advertisement is connectable,
     * three bytes will be added for flags.
     * @param scanResponse Scan response replacedociated with the advertisement data. Size must not
     * exceed {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}.
     * @param periodicParameters periodic advertisng parameters. If null, periodic advertising will
     * not be started.
     * @param periodicData Periodic advertising data. Size must not exceed {@link
     * BluetoothAdapter#getLeMaximumAdvertisingDataLength}.
     * @param callback Callback for advertising set.
     * @param handler thread upon which the callbacks will be invoked.
     * @throws IllegalArgumentException when any of the data parameter exceed the maximum allowable
     * size, or unsupported advertising PHY is selected, or when attempt to use Periodic Advertising
     * feature is made when it's not supported by the controller.
     */
    public void startAdvertisingSet(AdvertisingSetParameters parameters, AdvertiseData advertiseData, AdvertiseData scanResponse, PeriodicAdvertisingParameters periodicParameters, AdvertiseData periodicData, AdvertisingSetCallback callback, Handler handler) {
        startAdvertisingSet(parameters, advertiseData, scanResponse, periodicParameters, periodicData, 0, 0, callback, handler);
    }

    /**
     * Creates a new advertising set. If operation succeed, device will start advertising. This
     * method returns immediately, the operation status is delivered through
     * {@code callback.onAdvertisingSetStarted()}.
     * <p>
     *
     * @param parameters advertising set parameters.
     * @param advertiseData Advertisement data to be broadcasted. Size must not exceed {@link
     * BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the advertisement is connectable,
     * three bytes will be added for flags.
     * @param scanResponse Scan response replacedociated with the advertisement data. Size must not
     * exceed {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}.
     * @param periodicParameters periodic advertisng parameters. If null, periodic advertising will
     * not be started.
     * @param periodicData Periodic advertising data. Size must not exceed {@link
     * BluetoothAdapter#getLeMaximumAdvertisingDataLength}.
     * @param duration advertising duration, in 10ms unit. Valid range is from 1 (10ms) to 65535
     * (655,350 ms). 0 means advertising should continue until stopped.
     * @param maxExtendedAdvertisingEvents maximum number of extended advertising events the
     * controller shall attempt to send prior to terminating the extended advertising, even if the
     * duration has not expired. Valid range is from 1 to 255. 0 means no maximum.
     * @param callback Callback for advertising set.
     * @throws IllegalArgumentException when any of the data parameter exceed the maximum allowable
     * size, or unsupported advertising PHY is selected, or when attempt to use Periodic Advertising
     * feature is made when it's not supported by the controller.
     */
    public void startAdvertisingSet(AdvertisingSetParameters parameters, AdvertiseData advertiseData, AdvertiseData scanResponse, PeriodicAdvertisingParameters periodicParameters, AdvertiseData periodicData, int duration, int maxExtendedAdvertisingEvents, AdvertisingSetCallback callback) {
        startAdvertisingSet(parameters, advertiseData, scanResponse, periodicParameters, periodicData, duration, maxExtendedAdvertisingEvents, callback, new Handler(Looper.getMainLooper()));
    }

    /**
     * Creates a new advertising set. If operation succeed, device will start advertising. This
     * method returns immediately, the operation status is delivered through
     * {@code callback.onAdvertisingSetStarted()}.
     * <p>
     *
     * @param parameters Advertising set parameters.
     * @param advertiseData Advertisement data to be broadcasted. Size must not exceed {@link
     * BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the advertisement is connectable,
     * three bytes will be added for flags.
     * @param scanResponse Scan response replacedociated with the advertisement data. Size must not
     * exceed {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}
     * @param periodicParameters Periodic advertisng parameters. If null, periodic advertising will
     * not be started.
     * @param periodicData Periodic advertising data. Size must not exceed {@link
     * BluetoothAdapter#getLeMaximumAdvertisingDataLength}
     * @param duration advertising duration, in 10ms unit. Valid range is from 1 (10ms) to 65535
     * (655,350 ms). 0 means advertising should continue until stopped.
     * @param maxExtendedAdvertisingEvents maximum number of extended advertising events the
     * controller shall attempt to send prior to terminating the extended advertising, even if the
     * duration has not expired. Valid range is from 1 to 255. 0 means no maximum.
     * @param callback Callback for advertising set.
     * @param handler Thread upon which the callbacks will be invoked.
     * @throws IllegalArgumentException When any of the data parameter exceed the maximum allowable
     * size, or unsupported advertising PHY is selected, or when attempt to use Periodic Advertising
     * feature is made when it's not supported by the controller, or when
     * maxExtendedAdvertisingEvents is used on a controller that doesn't support the LE Extended
     * Advertising
     */
    public void startAdvertisingSet(AdvertisingSetParameters parameters, AdvertiseData advertiseData, AdvertiseData scanResponse, PeriodicAdvertisingParameters periodicParameters, AdvertiseData periodicData, int duration, int maxExtendedAdvertisingEvents, AdvertisingSetCallback callback, Handler handler) {
        BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter);
        if (callback == null) {
            throw new IllegalArgumentException("callback cannot be null");
        }
        boolean isConnectable = parameters.isConnectable();
        if (parameters.isLegacy()) {
            if (totalBytes(advertiseData, isConnectable) > MAX_LEGACY_ADVERTISING_DATA_BYTES) {
                throw new IllegalArgumentException("Legacy advertising data too big");
            }
            if (totalBytes(scanResponse, false) > MAX_LEGACY_ADVERTISING_DATA_BYTES) {
                throw new IllegalArgumentException("Legacy scan response data too big");
            }
        } else {
            boolean supportCodedPhy = mBluetoothAdapter.isLeCodedPhySupported();
            boolean support2MPhy = mBluetoothAdapter.isLe2MPhySupported();
            int pphy = parameters.getPrimaryPhy();
            int sphy = parameters.getSecondaryPhy();
            if (pphy == BluetoothDevice.PHY_LE_CODED && !supportCodedPhy) {
                throw new IllegalArgumentException("Unsupported primary PHY selected");
            }
            if ((sphy == BluetoothDevice.PHY_LE_CODED && !supportCodedPhy) || (sphy == BluetoothDevice.PHY_LE_2M && !support2MPhy)) {
                throw new IllegalArgumentException("Unsupported secondary PHY selected");
            }
            int maxData = mBluetoothAdapter.getLeMaximumAdvertisingDataLength();
            if (totalBytes(advertiseData, isConnectable) > maxData) {
                throw new IllegalArgumentException("Advertising data too big");
            }
            if (totalBytes(scanResponse, false) > maxData) {
                throw new IllegalArgumentException("Scan response data too big");
            }
            if (totalBytes(periodicData, false) > maxData) {
                throw new IllegalArgumentException("Periodic advertising data too big");
            }
            boolean supportPeriodic = mBluetoothAdapter.isLePeriodicAdvertisingSupported();
            if (periodicParameters != null && !supportPeriodic) {
                throw new IllegalArgumentException("Controller does not support LE Periodic Advertising");
            }
        }
        if (maxExtendedAdvertisingEvents < 0 || maxExtendedAdvertisingEvents > 255) {
            throw new IllegalArgumentException("maxExtendedAdvertisingEvents out of range: " + maxExtendedAdvertisingEvents);
        }
        if (maxExtendedAdvertisingEvents != 0 && !mBluetoothAdapter.isLePeriodicAdvertisingSupported()) {
            throw new IllegalArgumentException("Can't use maxExtendedAdvertisingEvents with controller that don't support " + "LE Extended Advertising");
        }
        if (duration < 0 || duration > 65535) {
            throw new IllegalArgumentException("duration out of range: " + duration);
        }
        IBluetoothGatt gatt;
        try {
            gatt = mBluetoothManager.getBluetoothGatt();
        } catch (RemoteException e) {
            Log.e(TAG, "Failed to get Bluetooth gatt - ", e);
            postStartSetFailure(handler, callback, AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR);
            return;
        }
        IAdvertisingSetCallback wrapped = wrap(callback, handler);
        if (mCallbackWrappers.putIfAbsent(callback, wrapped) != null) {
            throw new IllegalArgumentException("callback instance already replacedociated with advertising");
        }
        try {
            gatt.startAdvertisingSet(parameters, advertiseData, scanResponse, periodicParameters, periodicData, duration, maxExtendedAdvertisingEvents, wrapped);
        } catch (RemoteException e) {
            Log.e(TAG, "Failed to start advertising set - ", e);
            postStartSetFailure(handler, callback, AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR);
            return;
        }
    }

    /**
     * Used to dispose of a {@link AdvertisingSet} object, obtained with {@link
     * BluetoothLeAdvertiser#startAdvertisingSet}.
     */
    public void stopAdvertisingSet(AdvertisingSetCallback callback) {
        if (callback == null) {
            throw new IllegalArgumentException("callback cannot be null");
        }
        IAdvertisingSetCallback wrapped = mCallbackWrappers.remove(callback);
        if (wrapped == null) {
            return;
        }
        IBluetoothGatt gatt;
        try {
            gatt = mBluetoothManager.getBluetoothGatt();
            gatt.stopAdvertisingSet(wrapped);
        } catch (RemoteException e) {
            Log.e(TAG, "Failed to stop advertising - ", e);
        }
    }

    /**
     * Cleans up advertisers. Should be called when bluetooth is down.
     *
     * @hide
     */
    public void cleanup() {
        mLegacyAdvertisers.clear();
        mCallbackWrappers.clear();
        mAdvertisingSets.clear();
    }

    // Compute the size of advertisement data or scan resp
    private int totalBytes(AdvertiseData data, boolean isFlagsIncluded) {
        if (data == null)
            return 0;
        // Flags field is omitted if the advertising is not connectable.
        int size = (isFlagsIncluded) ? FLAGS_FIELD_BYTES : 0;
        if (data.getServiceUuids() != null) {
            int num16BitUuids = 0;
            int num32BitUuids = 0;
            int num128BitUuids = 0;
            for (ParcelUuid uuid : data.getServiceUuids()) {
                if (BluetoothUuid.is16BitUuid(uuid)) {
                    ++num16BitUuids;
                } else if (BluetoothUuid.is32BitUuid(uuid)) {
                    ++num32BitUuids;
                } else {
                    ++num128BitUuids;
                }
            }
            // 16 bit service uuids are grouped into one field when doing advertising.
            if (num16BitUuids != 0) {
                size += OVERHEAD_BYTES_PER_FIELD + num16BitUuids * BluetoothUuid.UUID_BYTES_16_BIT;
            }
            // 32 bit service uuids are grouped into one field when doing advertising.
            if (num32BitUuids != 0) {
                size += OVERHEAD_BYTES_PER_FIELD + num32BitUuids * BluetoothUuid.UUID_BYTES_32_BIT;
            }
            // 128 bit service uuids are grouped into one field when doing advertising.
            if (num128BitUuids != 0) {
                size += OVERHEAD_BYTES_PER_FIELD + num128BitUuids * BluetoothUuid.UUID_BYTES_128_BIT;
            }
        }
        for (ParcelUuid uuid : data.getServiceData().keySet()) {
            int uuidLen = BluetoothUuid.uuidToBytes(uuid).length;
            size += OVERHEAD_BYTES_PER_FIELD + uuidLen + byteLength(data.getServiceData().get(uuid));
        }
        for (int i = 0; i < data.getManufacturerSpecificData().size(); ++i) {
            size += OVERHEAD_BYTES_PER_FIELD + MANUFACTURER_SPECIFIC_DATA_LENGTH + byteLength(data.getManufacturerSpecificData().valueAt(i));
        }
        if (data.getIncludeTxPowerLevel()) {
            // tx power level value is one byte.
            size += OVERHEAD_BYTES_PER_FIELD + 1;
        }
        if (data.getIncludeDeviceName() && mBluetoothAdapter.getName() != null) {
            size += OVERHEAD_BYTES_PER_FIELD + mBluetoothAdapter.getName().length();
        }
        return size;
    }

    private int byteLength(byte[] array) {
        return array == null ? 0 : array.length;
    }

    IAdvertisingSetCallback wrap(AdvertisingSetCallback callback, Handler handler) {
        return new IAdvertisingSetCallback.Stub() {

            @Override
            public void onAdvertisingSetStarted(int advertiserId, int txPower, int status) {
                handler.post(new Runnable() {

                    @Override
                    public void run() {
                        if (status != AdvertisingSetCallback.ADVERTISE_SUCCESS) {
                            callback.onAdvertisingSetStarted(null, 0, status);
                            mCallbackWrappers.remove(callback);
                            return;
                        }
                        AdvertisingSet advertisingSet = new AdvertisingSet(advertiserId, mBluetoothManager);
                        mAdvertisingSets.put(advertiserId, advertisingSet);
                        callback.onAdvertisingSetStarted(advertisingSet, txPower, status);
                    }
                });
            }

            @Override
            public void onOwnAddressRead(int advertiserId, int addressType, String address) {
                handler.post(new Runnable() {

                    @Override
                    public void run() {
                        AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
                        callback.onOwnAddressRead(advertisingSet, addressType, address);
                    }
                });
            }

            @Override
            public void onAdvertisingSetStopped(int advertiserId) {
                handler.post(new Runnable() {

                    @Override
                    public void run() {
                        AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
                        callback.onAdvertisingSetStopped(advertisingSet);
                        mAdvertisingSets.remove(advertiserId);
                        mCallbackWrappers.remove(callback);
                    }
                });
            }

            @Override
            public void onAdvertisingEnabled(int advertiserId, boolean enabled, int status) {
                handler.post(new Runnable() {

                    @Override
                    public void run() {
                        AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
                        callback.onAdvertisingEnabled(advertisingSet, enabled, status);
                    }
                });
            }

            @Override
            public void onAdvertisingDataSet(int advertiserId, int status) {
                handler.post(new Runnable() {

                    @Override
                    public void run() {
                        AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
                        callback.onAdvertisingDataSet(advertisingSet, status);
                    }
                });
            }

            @Override
            public void onScanResponseDataSet(int advertiserId, int status) {
                handler.post(new Runnable() {

                    @Override
                    public void run() {
                        AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
                        callback.onScanResponseDataSet(advertisingSet, status);
                    }
                });
            }

            @Override
            public void onAdvertisingParametersUpdated(int advertiserId, int txPower, int status) {
                handler.post(new Runnable() {

                    @Override
                    public void run() {
                        AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
                        callback.onAdvertisingParametersUpdated(advertisingSet, txPower, status);
                    }
                });
            }

            @Override
            public void onPeriodicAdvertisingParametersUpdated(int advertiserId, int status) {
                handler.post(new Runnable() {

                    @Override
                    public void run() {
                        AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
                        callback.onPeriodicAdvertisingParametersUpdated(advertisingSet, status);
                    }
                });
            }

            @Override
            public void onPeriodicAdvertisingDataSet(int advertiserId, int status) {
                handler.post(new Runnable() {

                    @Override
                    public void run() {
                        AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
                        callback.onPeriodicAdvertisingDataSet(advertisingSet, status);
                    }
                });
            }

            @Override
            public void onPeriodicAdvertisingEnabled(int advertiserId, boolean enable, int status) {
                handler.post(new Runnable() {

                    @Override
                    public void run() {
                        AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
                        callback.onPeriodicAdvertisingEnabled(advertisingSet, enable, status);
                    }
                });
            }
        };
    }

    private void postStartSetFailure(Handler handler, final AdvertisingSetCallback callback, final int error) {
        handler.post(new Runnable() {

            @Override
            public void run() {
                callback.onAdvertisingSetStarted(null, 0, error);
            }
        });
    }

    private void postStartFailure(final AdvertiseCallback callback, final int error) {
        mHandler.post(new Runnable() {

            @Override
            public void run() {
                callback.onStartFailure(error);
            }
        });
    }

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

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