org.chromium.ChromeBluetooth.java Source code

Java tutorial

Introduction

Here is the source code for org.chromium.ChromeBluetooth.java

Source

// Copyright (c) 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaArgs;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.PluginResult;
import org.apache.cordova.PluginResult.Status;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.uribeacon.scan.compat.BluetoothLeScannerCompat;
import org.uribeacon.scan.compat.BluetoothLeScannerCompatProvider;
import org.uribeacon.scan.compat.ScanCallback;
import org.uribeacon.scan.compat.ScanRecord;
import org.uribeacon.scan.compat.ScanResult;
import org.uribeacon.scan.compat.ScanSettings;

import android.annotation.TargetApi;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Build;
import android.os.ParcelUuid;
import android.util.Base64;
import android.util.Log;

@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public class ChromeBluetooth extends CordovaPlugin {

    private static final String LOG_TAG = "ChromeBluetooth";
    private static final int[] SUPPORTED_PROFILE = { BluetoothProfile.GATT, BluetoothProfile.GATT_SERVER };

    private Map<String, ScanResult> knownLeScanResults = new HashMap<String, ScanResult>();
    private Map<String, BluetoothDevice> knownBluetoothDevices = new ConcurrentHashMap<String, BluetoothDevice>();
    private Set<String> activeDevices = new HashSet<String>();

    private BluetoothManager bluetoothManager;
    private BluetoothAdapter bluetoothAdapter;
    private BluetoothLeScannerCompat leScanner;
    private boolean isLeScanning;
    private boolean isDiscovering = false;

    private CallbackContext bluetoothEventsCallback;

    @Override
    public boolean execute(String action, CordovaArgs args, final CallbackContext callbackContext)
            throws JSONException {
        if ("getAdapterState".equals(action)) {
            getAdapterState(callbackContext);
        } else if ("getDevice".equals(action)) {
            getDevice(args, callbackContext);
        } else if ("getDevices".equals(action)) {
            getDevices(callbackContext);
        } else if ("startDiscovery".equals(action)) {
            startDiscovery(callbackContext);
        } else if ("stopDiscovery".equals(action)) {
            stopDiscovery(callbackContext);
        } else if ("registerBluetoothEvents".equals(action)) {
            registerBluetoothEvents(callbackContext);
        } else {
            return false;
        }
        return true;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        unregisterAdapterStateReceiver();
    }

    @Override
    protected void pluginInitialize() {
        registerAdapterStateReceiver();
        bluetoothManager = (BluetoothManager) webView.getContext().getSystemService(Context.BLUETOOTH_SERVICE);
        bluetoothAdapter = bluetoothManager.getAdapter();
        leScanner = BluetoothLeScannerCompatProvider.getBluetoothLeScannerCompat(webView.getContext());
        isLeScanning = false;
        enableBluetooth();
    }

    private void enableBluetooth() {
        if (bluetoothAdapter == null) {
            Log.e(LOG_TAG, "Bluetooth is not supported");
        } else if (!bluetoothAdapter.isEnabled()) {
            bluetoothAdapter.enable();
        }
    }

    // @Nullable
    ScanResult getKnownLeScanResults(String deviceAddress) {
        return knownLeScanResults.get(deviceAddress);
    }

    // @Nullable
    BluetoothDevice getKnownBluetoothDevice(String deviceAddress) {
        return knownBluetoothDevices.get(deviceAddress);
    }

    private JSONObject getAdapterStateInfo() throws JSONException {
        JSONObject stateInfo = new JSONObject();
        stateInfo.put("address", bluetoothAdapter.getAddress());
        stateInfo.put("name", bluetoothAdapter.getName());
        stateInfo.put("powered", bluetoothAdapter.getState() != BluetoothAdapter.STATE_OFF);
        stateInfo.put("available", bluetoothAdapter.isEnabled());
        stateInfo.put("discovering", isLeScanning);
        return stateInfo;
    }

    private void getAdapterState(CallbackContext callbackContext) throws JSONException {
        callbackContext.success(getAdapterStateInfo());
    }

    public boolean isConnected(BluetoothDevice device) {
        for (int profile : SUPPORTED_PROFILE) {
            if (bluetoothManager.getConnectedDevices(profile).contains(device))
                return true;
        }
        return false;
    }

    private JSONObject getBasicDeviceInfo(BluetoothDevice device) throws JSONException {
        JSONObject deviceInfo = new JSONObject();
        deviceInfo.put("address", device.getAddress());
        deviceInfo.put("name", device.getName());
        deviceInfo.put("deviceClass", device.getBluetoothClass().getDeviceClass());
        deviceInfo.put("paired", device.getBondState() == BluetoothDevice.BOND_BONDED);
        deviceInfo.put("connected", isConnected(device));
        deviceInfo.put("uuids", new JSONArray(getUuidStringsFromDevice(device)));
        return deviceInfo;
    }

    private Collection<String> getUuidStringsFromDevice(BluetoothDevice device) {
        Set<String> uuidStrings = new HashSet<String>();
        ParcelUuid[] uuids = device.getUuids();
        if (uuids != null) {
            for (ParcelUuid uuid : uuids) {
                uuidStrings.add(uuid.toString());
            }
        }
        return uuidStrings;
    }

    private Collection<String> getUuidStringsFromLeScanRecord(ScanRecord scanRecord) {
        Set<String> uuidStrings = new HashSet<String>();
        List<ParcelUuid> uuids = scanRecord.getServiceUuids();
        if (uuids != null) {
            for (ParcelUuid uuid : uuids) {
                uuidStrings.add(uuid.toString());
            }
        }
        return uuidStrings;
    }

    private JSONObject getLeDeviceInfo(ScanResult leScanResult) throws JSONException {
        JSONObject deviceInfo = getBasicDeviceInfo(leScanResult.getDevice());
        Set<String> uuidStrings = new HashSet<String>();
        uuidStrings.addAll(getUuidStringsFromDevice(leScanResult.getDevice()));
        uuidStrings.addAll(getUuidStringsFromLeScanRecord(leScanResult.getScanRecord()));

        JSONArray uuids = new JSONArray(uuidStrings);

        if (!uuidStrings.isEmpty()) {
            deviceInfo.put("uuids", uuids);
        }

        deviceInfo.put("rssi", leScanResult.getRssi());
        deviceInfo.put("tx_power", leScanResult.getScanRecord().getTxPowerLevel());

        Iterator serviceDataIt = leScanResult.getScanRecord().getServiceData().entrySet().iterator();
        if (serviceDataIt.hasNext()) {
            Map.Entry<ParcelUuid, byte[]> serviceDataPair = (Map.Entry<ParcelUuid, byte[]>) serviceDataIt.next();
            deviceInfo.put("serviceDataUuid", serviceDataPair.getKey().toString());
            deviceInfo.put("serviceData", Base64.encodeToString(serviceDataPair.getValue(), Base64.NO_WRAP));
        }

        return deviceInfo;
    }

    // Note: If the device both discovered by LeScanner and regular scanner, LeDevice
    // info will be returned since it contains more information.
    private void getDevice(CordovaArgs args, CallbackContext callbackContext) throws JSONException {
        String deviceAddress = args.getString(0);
        ScanResult leResult = knownLeScanResults.get(deviceAddress);
        BluetoothDevice device = knownBluetoothDevices.get(deviceAddress);

        if (leResult == null && device == null) {
            callbackContext.error("Invalid Argument");
        } else if (leResult != null) {
            callbackContext.success(getLeDeviceInfo(leResult));
        } else {
            callbackContext.success(getBasicDeviceInfo(device));
        }
    }

    // Note: If the device both discovered by LeScanner and regular scanner, LeDevice
    // info will be returned since it contains more information.
    private void getDevices(CallbackContext callbackContext) throws JSONException {
        JSONArray results = new JSONArray();

        for (BluetoothDevice device : knownBluetoothDevices.values()) {
            results.put(getBasicDeviceInfo(device));
        }

        for (ScanResult result : knownLeScanResults.values()) {
            results.put(getLeDeviceInfo(result));
        }

        callbackContext.success(results);
    }

    private void startDiscovery(CallbackContext callbackContext) {

        ScanSettings settings = new ScanSettings.Builder()
                .setCallbackType(ScanSettings.CALLBACK_TYPE_FIRST_MATCH | ScanSettings.CALLBACK_TYPE_MATCH_LOST)
                .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();

        if (isLeScanning && bluetoothAdapter.isDiscovering()) {
            // both regular and low energy are already in scanning mode. return the
            // same error message as desktop.
            callbackContext.error("Starting discovery failed");
            return;
        }

        // Reset low energy scanner or regular scanner if they are in scanning mode.
        if (isLeScanning) {
            leScanner.stopScan(leScanCallback);
        }

        if (bluetoothAdapter.isDiscovering()) {
            bluetoothAdapter.cancelDiscovery();
        }

        isLeScanning = true;
        leScanner.startScan(Collections.EMPTY_LIST, settings, leScanCallback);

        isDiscovering = true;
        bluetoothAdapter.startDiscovery();
        callbackContext.success();
        sendAdapterStateChangedEvent();
    }

    private void stopDiscovery(CallbackContext callbackContext) {

        if (!isLeScanning && !bluetoothAdapter.isDiscovering()) {
            callbackContext.error("Failed to stop discovery");
            return;
        }

        if (isLeScanning) {
            isLeScanning = false;
            leScanner.stopScan(leScanCallback);
        }

        if (bluetoothAdapter.isDiscovering()) {
            isDiscovering = false;
            bluetoothAdapter.cancelDiscovery();
        }

        callbackContext.success();
        sendAdapterStateChangedEvent();
    }

    private void registerBluetoothEvents(CallbackContext callbackContext) {
        bluetoothEventsCallback = callbackContext;
    }

    private static PluginResult getMultipartEventsResult(String eventType, JSONObject info) {
        List<PluginResult> multipartMessage = new ArrayList<PluginResult>();
        multipartMessage.add(new PluginResult(Status.OK, eventType));
        multipartMessage.add(new PluginResult(Status.OK, info));
        PluginResult result = new PluginResult(Status.OK, multipartMessage);
        result.setKeepCallback(true);
        return result;
    }

    private void sendAdapterStateChangedEvent() {
        try {
            bluetoothEventsCallback
                    .sendPluginResult(getMultipartEventsResult("onAdapterStateChanged", getAdapterStateInfo()));
        } catch (JSONException e) {
        }
    }

    private void sendDeviceAddedEvent(ScanResult scanResult) {
        try {
            bluetoothEventsCallback
                    .sendPluginResult(getMultipartEventsResult("onDeviceAdded", getLeDeviceInfo(scanResult)));
        } catch (JSONException e) {
        }
    }

    private void sendDeviceAddedEvent(BluetoothDevice device) {
        try {
            bluetoothEventsCallback
                    .sendPluginResult(getMultipartEventsResult("onDeviceAdded", getBasicDeviceInfo(device)));
        } catch (JSONException e) {
        }
    }

    void sendDeviceChangedEvent(ScanResult scanResult) {
        try {
            bluetoothEventsCallback
                    .sendPluginResult(getMultipartEventsResult("onDeviceChanged", getLeDeviceInfo(scanResult)));
        } catch (JSONException e) {
        }
    }

    private void sendDeviceRemovedEvent(ScanResult scanResult) {
        try {
            bluetoothEventsCallback
                    .sendPluginResult(getMultipartEventsResult("onDeviceRemoved", getLeDeviceInfo(scanResult)));
        } catch (JSONException e) {
        }
    }

    private void sendDeviceRemovedEvent(BluetoothDevice device) {
        try {
            bluetoothEventsCallback
                    .sendPluginResult(getMultipartEventsResult("onDeviceRemoved", getBasicDeviceInfo(device)));
        } catch (JSONException e) {
        }
    }

    private void unregisterAdapterStateReceiver() {
        webView.getContext().unregisterReceiver(adapterStateReceiver);
    }

    private void registerAdapterStateReceiver() {
        IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
        filter.addAction(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
        filter.addAction(BluetoothDevice.ACTION_FOUND);
        webView.getContext().registerReceiver(adapterStateReceiver, filter);
    }

    private final BroadcastReceiver adapterStateReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {

            if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(intent.getAction())) {
                sendAdapterStateChangedEvent();
            } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(intent.getAction())) {

                if (!isDiscovering) {
                    return;
                }

                Set<String> knownAddresses = new HashSet<String>(knownBluetoothDevices.keySet());
                knownAddresses.removeAll(activeDevices);
                for (String address : knownAddresses) {
                    sendDeviceRemovedEvent(knownBluetoothDevices.get(address));
                    knownBluetoothDevices.remove(address);
                }
                activeDevices.clear();
                bluetoothAdapter.startDiscovery();

            } else if (BluetoothDevice.ACTION_FOUND.equals(intent.getAction())) {
                BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                activeDevices.add(device.getAddress());
                if (!knownBluetoothDevices.containsKey(device.getAddress())) {
                    knownBluetoothDevices.put(device.getAddress(), device);
                    sendDeviceAddedEvent(device);
                }
            }
        }
    };

    private final ScanCallback leScanCallback = new ScanCallback() {
        @Override
        public void onScanResult(int callbackType, ScanResult result) {
            switch (callbackType) {
            case ScanSettings.CALLBACK_TYPE_FIRST_MATCH:
                assert (!knownLeScanResults.containsKey(result.getDevice().getAddress()));
                knownLeScanResults.put(result.getDevice().getAddress(), result);
                sendDeviceAddedEvent(result);
                break;
            case ScanSettings.CALLBACK_TYPE_MATCH_LOST:
                sendDeviceRemovedEvent(result);
                knownLeScanResults.remove(result.getDevice().getAddress());
                break;
            }
        }

        @Override
        public void onScanFailed(int errorCode) {
            Log.e(LOG_TAG, "onScanFailed():");
            isLeScanning = false;
        }
    };
}