com.tfg.sawan.bsecure.beacon.UriBeaconDiscoveryService.java Source code

Java tutorial

Introduction

Here is the source code for com.tfg.sawan.bsecure.beacon.UriBeaconDiscoveryService.java

Source

/*
 * Copyright 2014 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 com.tfg.sawan.bsecure.beacon;

import android.app.Notification;
import android.app.PendingIntent;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.PowerManager;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationManagerCompat;
import android.util.Log;
import android.view.View;
import android.webkit.URLUtil;
import android.widget.RemoteViews;

import com.tfg.sawan.bsecure.MainActivity;
import com.tfg.sawan.bsecure.R;

import org.uribeacon.beacon.UriBeacon;
import org.uribeacon.scan.compat.BluetoothLeScannerCompat;
import org.uribeacon.scan.compat.BluetoothLeScannerCompatProvider;
import org.uribeacon.scan.compat.ScanCallback;
import org.uribeacon.scan.compat.ScanFilter;
import org.uribeacon.scan.compat.ScanResult;
import org.uribeacon.scan.compat.ScanSettings;
import org.uribeacon.scan.util.RegionResolver;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * This is a service that scans for nearby uriBeacons.
 * It is created by MainActivity.
 * It finds nearby ble beacons,
 * and stores a count of them.
 * It also listens for screen on/off events
 * and start/stops the scanning accordingly.
 * It also silently issues a notification
 * informing the user of nearby beacons.
 * As beacons are found and lost,
 * the notification is updated to reflect
 * the current number of nearby beacons.
 */

public class UriBeaconDiscoveryService extends Service implements PwsClient.ResolveScanCallback,
        MdnsUrlDiscoverer.MdnsUrlDiscovererCallback, SsdpUrlDiscoverer.SsdpUrlDiscovererCallback {

    private static final String TAG = "UriBeaconDiscoveryService";
    private final ScanCallback mScanCallback = new ScanCallback() {
        @Override
        public void onScanResult(int callbackType, ScanResult scanResult) {
            switch (callbackType) {
            case ScanSettings.CALLBACK_TYPE_FIRST_MATCH:
                handleFoundDevice(scanResult);
                break;
            case ScanSettings.CALLBACK_TYPE_ALL_MATCHES:
                handleFoundDevice(scanResult);
                break;
            default:
                Log.e(TAG, "Unrecognized callback type constant received: " + callbackType);
            }
        }

        @Override
        public void onScanFailed(int errorCode) {
            Log.d(TAG, "onScanFailed  " + "errorCode: " + errorCode);
        }
    };
    private static final String NOTIFICATION_GROUP_KEY = "URI_BEACON_NOTIFICATIONS";
    private static final int NEAREST_BEACON_NOTIFICATION_ID = 23;
    private static final int SECOND_NEAREST_BEACON_NOTIFICATION_ID = 24;
    private static final int SUMMARY_NOTIFICATION_ID = 25;
    private static final int TIMEOUT_FOR_OLD_BEACONS = 2;
    private static final int NON_LOLLIPOP_NOTIFICATION_TITLE_COLOR = Color.parseColor("#ffffff");
    private static final int NON_LOLLIPOP_NOTIFICATION_URL_COLOR = Color.parseColor("#999999");
    private static final int NON_LOLLIPOP_NOTIFICATION_SNIPPET_COLOR = Color.parseColor("#999999");
    private static final int NOTIFICATION_PRIORITY = NotificationCompat.PRIORITY_MIN;
    private static final int NOTIFICATION_VISIBILITY = NotificationCompat.VISIBILITY_PUBLIC;
    private static final long NOTIFICATION_UPDATE_GATE_DURATION = 1000;
    private boolean mCanUpdateNotifications = false;
    private Handler mHandler;
    private ScreenBroadcastReceiver mScreenStateBroadcastReceiver;
    private RegionResolver mRegionResolver;
    private NotificationManagerCompat mNotificationManager;
    private HashMap<String, PwsClient.UrlMetadata> mUrlToUrlMetadata;
    private HashSet<String> mPublicUrls;
    private List<String> mSortedDevices;
    private HashMap<String, String> mDeviceAddressToUrl;
    private MdnsUrlDiscoverer mMdnsUrlDiscoverer;
    private SsdpUrlDiscoverer mSsdpUrlDiscoverer;

    // TODO: consider a more elegant solution for preventing notification conflicts
    private Runnable mNotificationUpdateGateTimeout = new Runnable() {
        @Override
        public void run() {
            mCanUpdateNotifications = true;
            updateNotifications();
        }
    };

    public UriBeaconDiscoveryService() {
    }

    private static String generateMockBluetoothAddress(int hashCode) {
        String mockAddress = "00:11";
        for (int i = 0; i < 4; i++) {
            mockAddress += String.format(":%02X", hashCode & 0xFF);
            hashCode = hashCode >> 8;
        }
        return mockAddress;
    }

    private void initialize() {
        mNotificationManager = NotificationManagerCompat.from(this);
        mMdnsUrlDiscoverer = new MdnsUrlDiscoverer(this, UriBeaconDiscoveryService.this);
        mSsdpUrlDiscoverer = new SsdpUrlDiscoverer(this, UriBeaconDiscoveryService.this);
        mHandler = new Handler();
        initializeScreenStateBroadcastReceiver();
    }

    private void initializeLists() {
        mRegionResolver = new RegionResolver();
        mUrlToUrlMetadata = new HashMap<>();
        mPublicUrls = new HashSet<>();
        mSortedDevices = null;
        mDeviceAddressToUrl = new HashMap<>();
    }

    /**
     * Create the broadcast receiver that will listen
     * for screen on/off events
     */
    private void initializeScreenStateBroadcastReceiver() {
        mScreenStateBroadcastReceiver = new ScreenBroadcastReceiver();
        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(Intent.ACTION_SCREEN_ON);
        intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
        registerReceiver(mScreenStateBroadcastReceiver, intentFilter);
    }

    private BluetoothLeScannerCompat getLeScanner() {
        return BluetoothLeScannerCompatProvider.getBluetoothLeScannerCompat(getApplicationContext());
    }

    @Override
    public void onCreate() {
        super.onCreate();
        initialize();
    }

    @Override
    @SuppressWarnings("deprecation")
    public int onStartCommand(Intent intent, int flags, int startId) {
        // Since sometimes the lists have values when onStartCommand gets called
        initializeLists();
        // Start scanning only if the screen is on
        PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
        // NOTE: use powerManager.isInteractive() when minsdk >= 20
        if (powerManager.isScreenOn()) {
            mCanUpdateNotifications = false;
            mHandler.postDelayed(mNotificationUpdateGateTimeout, NOTIFICATION_UPDATE_GATE_DURATION);
            startSearchingForUriBeacons();
            mMdnsUrlDiscoverer.startScanning();
            mSsdpUrlDiscoverer.startScanning();
        }

        //make sure the service keeps running
        return START_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public void onDestroy() {
        Log.d(TAG, "onDestroy:  service exiting");
        mHandler.removeCallbacks(mNotificationUpdateGateTimeout);
        stopSearchingForUriBeacons();
        mMdnsUrlDiscoverer.stopScanning();
        mSsdpUrlDiscoverer.stopScanning();
        unregisterReceiver(mScreenStateBroadcastReceiver);
        mNotificationManager.cancelAll();
    }

    @Override
    public void onUrlMetadataReceived(String url, PwsClient.UrlMetadata urlMetadata, long tripMillis) {
        mUrlToUrlMetadata.put(url, urlMetadata);
        updateNotifications();
    }

    @Override
    public void onUrlMetadataIconReceived() {
        updateNotifications();
    }

    private class DemoResolveScanCallback implements PwsClient.ResolveScanCallback {
        @Override
        public void onUrlMetadataReceived(String url, PwsClient.UrlMetadata urlMetadata, long tripMillis) {
        }

        @Override
        public void onUrlMetadataIconReceived() {
            updateNotifications();
        }
    }

    @Override
    public void onMdnsUrlFound(String url) {
        onLanUrlFound(url);
    }

    @Override
    public void onSsdpUrlFound(String url) {
        onLanUrlFound(url);
    }

    private void onLanUrlFound(String url) {
        if (!mUrlToUrlMetadata.containsKey(url)) {
            // Fabricate the device values so that we can show these ersatz beacons
            String mockAddress = generateMockBluetoothAddress(url.hashCode());
            int mockRssi = 0;
            int mockTxPower = 0;
            mUrlToUrlMetadata.put(url, null);
            mDeviceAddressToUrl.put(mockAddress, url);
            mRegionResolver.onUpdate(mockAddress, mockRssi, mockTxPower);
            PwsClient.getInstance(this).findUrlMetadata(url, mockTxPower, mockRssi, UriBeaconDiscoveryService.this,
                    TAG);
        }
    }

    private void startSearchingForUriBeacons() {
        ScanSettings settings = new ScanSettings.Builder().setCallbackType(ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
                .setScanMode(ScanSettings.SCAN_MODE_LOW_POWER).build();

        List<ScanFilter> filters = new ArrayList<>();
        filters.add(new ScanFilter.Builder()
                .setServiceData(UriBeacon.URI_SERVICE_UUID, new byte[] {}, new byte[] {}).build());
        filters.add(new ScanFilter.Builder()
                .setServiceData(UriBeacon.TEST_SERVICE_UUID, new byte[] {}, new byte[] {}).build());

        boolean started = getLeScanner().startScan(filters, settings, mScanCallback);
        Log.v(TAG, started ? "... scan started" : "... scan NOT started");
    }

    private void stopSearchingForUriBeacons() {
        getLeScanner().stopScan(mScanCallback);
    }

    private void handleFoundDevice(ScanResult scanResult) {
        long timeStamp = scanResult.getTimestampNanos();
        long now = TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis());
        if (now - timeStamp < TimeUnit.SECONDS.toNanos(TIMEOUT_FOR_OLD_BEACONS)) {
            UriBeacon uriBeacon = UriBeacon.parseFromBytes(scanResult.getScanRecord().getBytes());
            if (uriBeacon != null) {
                String url = uriBeacon.getUriString();
                if (url != null && !url.isEmpty()) {
                    String address = scanResult.getDevice().getAddress();
                    int rssi = scanResult.getRssi();
                    int txPower = uriBeacon.getTxPowerLevel();
                    // If we haven't yet seen this url
                    if (!mUrlToUrlMetadata.containsKey(url)) {
                        mUrlToUrlMetadata.put(url, null);
                        mPublicUrls.add(url);
                        mDeviceAddressToUrl.put(address, url);
                        // Fetch the metadata for this url
                        PwsClient.getInstance(this).findUrlMetadata(url, txPower, rssi,
                                UriBeaconDiscoveryService.this, TAG);
                    }
                    // Update the ranging data
                    mRegionResolver.onUpdate(address, rssi, txPower);
                }
            }
        }
    }

    /**
     * Create a new set of notifications or update those existing
     */
    private void updateNotifications() {
        if (!mCanUpdateNotifications) {
            return;
        }

        mSortedDevices = getSortedBeaconsWithMetadata();

    }

    private ArrayList<String> getSortedBeaconsWithMetadata() {
        ArrayList<String> unSorted = new ArrayList<>(mDeviceAddressToUrl.size());
        for (String key : mDeviceAddressToUrl.keySet()) {
            if (mUrlToUrlMetadata.get(mDeviceAddressToUrl.get(key)) != null) {
                unSorted.add(key);
            }
        }
        Collections.sort(unSorted, new MetadataComparator(mUrlToUrlMetadata));
        return unSorted;
    }

    private PendingIntent createReturnToAppPendingIntent() {
        Intent intent = new Intent(this, MainActivity.class);
        int requestID = (int) System.currentTimeMillis();
        PendingIntent pendingIntent = PendingIntent.getActivity(this, requestID, intent, 0);
        return pendingIntent;
    }

    private PendingIntent createNavigateToUrlPendingIntent(String url) {
        String urlToNavigateTo = url;
        // If this url has metadata
        if (mUrlToUrlMetadata.get(url) != null) {
            String siteUrl = mUrlToUrlMetadata.get(url).siteUrl;
            // If this metadata has a siteUrl
            if (siteUrl != null) {
                urlToNavigateTo = siteUrl;
            }
        }
        if (!URLUtil.isNetworkUrl(urlToNavigateTo)) {
            urlToNavigateTo = "http://" + urlToNavigateTo;
        }
        urlToNavigateTo = PwsClient.getInstance(this).createUrlProxyGoLink(urlToNavigateTo);
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setData(Uri.parse(urlToNavigateTo));
        int requestID = (int) System.currentTimeMillis();
        PendingIntent pendingIntent = PendingIntent.getActivity(this, requestID, intent, 0);
        return pendingIntent;
    }

    /**
     * This is the class that listens for screen on/off events
     */
    private class ScreenBroadcastReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            boolean isScreenOn = Intent.ACTION_SCREEN_ON.equals(intent.getAction());
            initializeLists();
            mNotificationManager.cancelAll();
            if (isScreenOn) {
                mCanUpdateNotifications = false;
                mHandler.postDelayed(mNotificationUpdateGateTimeout, NOTIFICATION_UPDATE_GATE_DURATION);
                startSearchingForUriBeacons();
                mMdnsUrlDiscoverer.startScanning();
                mSsdpUrlDiscoverer.startScanning();
            } else {
                mHandler.removeCallbacks(mNotificationUpdateGateTimeout);
                stopSearchingForUriBeacons();
                mMdnsUrlDiscoverer.stopScanning();
                mSsdpUrlDiscoverer.stopScanning();
            }
        }
    }
}