org.physical_web.physicalweb.FatBeaconBroadcastService.java Source code

Java tutorial

Introduction

Here is the source code for org.physical_web.physicalweb.FatBeaconBroadcastService.java

Source

/*
 * Copyright 2016 Google Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.physical_web.physicalweb;

import org.physical_web.physicalweb.ble.AdvertiseDataUtils;

import android.annotation.TargetApi;
import android.app.Service;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattServer;
import android.bluetooth.BluetoothGattServerCallback;
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.le.AdvertiseCallback;
import android.bluetooth.le.AdvertiseData;
import android.bluetooth.le.AdvertiseSettings;
import android.bluetooth.le.BluetoothLeAdvertiser;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationManagerCompat;
import android.widget.Toast;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.UUID;

/**
 * Shares a Web page via bluetooth.
 * Lastly, it surfaces a persistent notification whenever a FatBeacon is currently being broadcast.
 **/
@TargetApi(21)
public class FatBeaconBroadcastService extends Service {

    private static final String TAG = FatBeaconBroadcastService.class.getSimpleName();
    private static final String SERVICE_UUID = "ae5946d4-e587-4ba8-b6a5-a97cca6affd3";
    private static final UUID CHARACTERISTIC_WEBPAGE_UUID = UUID.fromString("d1a517f0-2499-46ca-9ccc-809bc1c966fa");
    private static final String PREVIOUS_BROADCAST_INFO_KEY = "previousInfo";
    private static final int BROADCASTING_NOTIFICATION_ID = 8;
    private boolean mStartedByRestart;
    private BluetoothLeAdvertiser mBluetoothLeAdvertiser;
    private NotificationManagerCompat mNotificationManager;
    private String mDisplayInfo;
    private BluetoothManager mBluetoothManager;
    private BluetoothGattServer mGattServer;
    private byte[] data;
    public static final String TITLE_KEY = "title";
    public static final String URI_KEY = "uri";

    /*
      * Callback handles all incoming requests from GATT clients.
      * From connections to read/write requests.
      */
    private BluetoothGattServerCallback mGattServerCallback = new BluetoothGattServerCallback() {
        private int transferSpeed = 20;
        private int queueOffset;

        @Override
        public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
            super.onConnectionStateChange(device, status, newState);

            if (newState == BluetoothProfile.STATE_CONNECTED) {
                Log.i(TAG, "Connected to device " + device.getAddress());
                queueOffset = 0;
            } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
                Log.i(TAG, "Disconnected from device " + device.getAddress());
            }
        }

        @Override
        public void onCharacteristicReadRequest(BluetoothDevice device, int requestId, int offset,
                BluetoothGattCharacteristic characteristic) {
            super.onCharacteristicReadRequest(device, requestId, offset, characteristic);
            Log.i(TAG, "onCharacteristicReadRequest " + characteristic.getUuid().toString());

            if (CHARACTERISTIC_WEBPAGE_UUID.equals(characteristic.getUuid())) {
                Log.d(TAG, "Data length:" + data.length + ", offset:" + queueOffset);
                if (queueOffset < data.length) {
                    int end = queueOffset + transferSpeed >= data.length ? data.length
                            : queueOffset + transferSpeed;
                    Log.d(TAG, "Data length:" + data.length + ", offset:" + queueOffset + ", end:" + end);
                    mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0,
                            Arrays.copyOfRange(data, queueOffset, end));
                    queueOffset = end;
                } else if (queueOffset == data.length) {
                    mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, 0, new byte[] {});
                    queueOffset++;
                }
            }

            /*
             * Unless the characteristic supports WRITE_NO_RESPONSE,
             * always send a response back for any request.
             */
            mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_FAILURE, 0, null);
        }

        @Override
        public void onMtuChanged(BluetoothDevice device, int mtu) {
            super.onMtuChanged(device, mtu);
            transferSpeed = mtu - 5;
        }
    };

    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();

            if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
                int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
                switch (state) {
                case BluetoothAdapter.STATE_OFF:
                    stopSelf();
                    break;
                default:

                }
            }
        }
    };

    /////////////////////////////////
    // callbacks
    /////////////////////////////////

    @Override
    public void onCreate() {
        super.onCreate();
        mBluetoothLeAdvertiser = BluetoothAdapter.getDefaultAdapter().getBluetoothLeAdvertiser();
        mNotificationManager = NotificationManagerCompat.from(this);
        mBluetoothManager = (BluetoothManager) this.getSystemService(Context.BLUETOOTH_SERVICE);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        fetchBroadcastData(intent);
        if (mDisplayInfo == null || data == null) {
            stopSelf();
            return START_STICKY;
        }
        IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
        registerReceiver(mReceiver, filter);
        broadcastUrl();
        initGattServer();
        return START_STICKY;
    }

    private void fetchBroadcastData(Intent intent) {
        if ((mStartedByRestart = intent == null)) {
            mDisplayInfo = PreferenceManager.getDefaultSharedPreferences(this)
                    .getString(PREVIOUS_BROADCAST_INFO_KEY, null);
            return;
        }
        mDisplayInfo = intent.getStringExtra(TITLE_KEY);
        String intentUri = intent.getStringExtra(URI_KEY);
        if (intentUri == null) {
            return;
        }
        try {
            data = Utils.getBytes(getContentResolver().openInputStream(Uri.parse(intentUri)));
        } catch (IOException e) {
            data = null;
            Log.e(TAG, "Error reading file");
        }
        PreferenceManager.getDefaultSharedPreferences(this).edit()
                .putString(PREVIOUS_BROADCAST_INFO_KEY, mDisplayInfo).apply();
    }

    @Override
    public void onDestroy() {
        unregisterReceiver(stopServiceReceiver);
        unregisterReceiver(mReceiver);
        disableUrlBroadcasting();
        super.onDestroy();
    }

    // Fires when user swipes away app from the recent apps list
    @Override
    public void onTaskRemoved(Intent rootIntent) {
        stopSelf();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    // The callbacks for the ble advertisement events
    private final AdvertiseCallback mAdvertiseCallback = new AdvertiseCallback() {

        // Fires when the URL is successfully being advertised
        @Override
        public void onStartSuccess(AdvertiseSettings advertiseSettings) {
            Utils.createBroadcastNotification(FatBeaconBroadcastService.this, stopServiceReceiver,
                    BROADCASTING_NOTIFICATION_ID, getString(R.string.fatbeacon_notification_title), mDisplayInfo,
                    "fatBeaconFilter");
            if (!mStartedByRestart) {
                Toast.makeText(getApplicationContext(), R.string.fatbeacon_broadcasting_confirmation,
                        Toast.LENGTH_LONG).show();
            }
        }

        // Fires when the URL could not be advertised
        @Override
        public void onStartFailure(int result) {
            Log.d(TAG, "onStartFailure" + result);
        }
    };

    /////////////////////////////////
    // utilities
    /////////////////////////////////

    // Broadcast via bluetooth the stored URL
    private void broadcastUrl() {
        byte[] bytes = null;
        try {
            bytes = mDisplayInfo.getBytes("UTF-8");
        } catch (UnsupportedEncodingException e) {
            Log.e(TAG, "Could not encode URL", e);
            return;
        }
        AdvertiseData advertiseData = AdvertiseDataUtils.getFatBeaconAdvertisementData(bytes);
        AdvertiseSettings advertiseSettings = AdvertiseDataUtils.getAdvertiseSettings(true);
        mBluetoothLeAdvertiser.stopAdvertising(mAdvertiseCallback);
        mBluetoothLeAdvertiser.startAdvertising(advertiseSettings, advertiseData, mAdvertiseCallback);
    }

    // Turn off URL broadcasting
    private void disableUrlBroadcasting() {
        mBluetoothLeAdvertiser.stopAdvertising(mAdvertiseCallback);
        mNotificationManager.cancel(BROADCASTING_NOTIFICATION_ID);
    }

    private BroadcastReceiver stopServiceReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            stopSelf();
        }
    };

    private void initGattServer() {
        mGattServer = mBluetoothManager.openGattServer(this, mGattServerCallback);
        BluetoothGattService service = new BluetoothGattService(UUID.fromString(SERVICE_UUID),
                BluetoothGattService.SERVICE_TYPE_PRIMARY);
        BluetoothGattCharacteristic webpage = new BluetoothGattCharacteristic(CHARACTERISTIC_WEBPAGE_UUID,
                BluetoothGattCharacteristic.PROPERTY_READ, BluetoothGattCharacteristic.PERMISSION_READ);
        service.addCharacteristic(webpage);
        mGattServer.addService(service);
    }
}