org.physical_web.physicalweb.PhysicalWebBroadcastService.java Source code

Java tutorial

Introduction

Here is the source code for org.physical_web.physicalweb.PhysicalWebBroadcastService.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.collection.PwsClient;
import org.physical_web.collection.PwsResult;
import org.physical_web.collection.PwsResultCallback;
import org.physical_web.physicalweb.ble.AdvertiseDataUtils;

import android.annotation.TargetApi;
import android.app.Service;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothManager;
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.content.SharedPreferences;
import android.os.Handler;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationManagerCompat;
import android.widget.Toast;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.Collection;

/**
 * Shares URLs via bluetooth.
 * Also interfaces with PWS to shorten URLs that are too long for Eddystone URLs.
 * Lastly, it surfaces a persistent notification whenever a URL is currently being broadcast.
 **/
@TargetApi(21)
public class PhysicalWebBroadcastService extends Service {

    private static final String TAG = PhysicalWebBroadcastService.class.getSimpleName();
    private BluetoothLeAdvertiser mBluetoothLeAdvertiser;
    private static final int BROADCASTING_NOTIFICATION_ID = 6;
    public static final String DISPLAY_URL_KEY = "displayUrl";
    public static final String PREVIOUS_BROADCAST_URL_KEY = "previousUrl";
    public static final int MAX_URI_LENGTH = 18;
    private NotificationManagerCompat mNotificationManager;
    private Handler mHandler = new Handler();
    private String mDisplayUrl;
    private byte[] mShareUrl;
    private boolean mStartedByRestart;

    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            Log.d(TAG, "in receiver");
            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:
                    Log.d(TAG, "stop because BT off");
                    stopSelf();
                    break;
                default:

                }
            }
        }
    };

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

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d(TAG, "SERVICE onCreate");
        BluetoothManager bluetoothManager = (BluetoothManager) getApplicationContext()
                .getSystemService(Context.BLUETOOTH_SERVICE);
        mBluetoothLeAdvertiser = bluetoothManager.getAdapter().getBluetoothLeAdvertiser();
        mNotificationManager = NotificationManagerCompat.from(this);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        fetchBroadcastData(intent);
        if (mDisplayUrl == null) {
            stopSelf();
            return START_STICKY;
        }
        Log.d(TAG, "SERVICE onStartCommand");
        IntentFilter filter = new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED);
        registerReceiver(mReceiver, filter);

        Log.d(TAG, mDisplayUrl);
        byte[] encodedUrl = AdvertiseDataUtils.encodeUri(mDisplayUrl);
        if (hasValidUrlLength(encodedUrl.length) && checkAndHandleAsciiUrl(mDisplayUrl)) {
            // Set the url if we can
            Log.d(TAG, "valid length");
            mShareUrl = encodedUrl;
            broadcastUrl();
        } else {
            Log.d(TAG, "needs shortening");
            UrlShortenerClient.ShortenUrlCallback urlSetter = new UrlShortenerClient.ShortenUrlCallback() {
                @Override
                public void onUrlShortened(String newUrl) {
                    Log.d(TAG, "shortening success");
                    mShareUrl = AdvertiseDataUtils.encodeUri(newUrl);
                    broadcastUrl();
                }

                @Override
                public void onError(String oldUrl) {
                    Toast.makeText(getApplicationContext(), getString(R.string.shorten_error), Toast.LENGTH_LONG)
                            .show();
                    stopSelf();
                }
            };
            UrlShortenerClient shortenerClient = UrlShortenerClient.getInstance(this);

            if (mDisplayUrl.contains("goo.gl")) {
                // find the site URL and reshorten it since
                // goo.gl will not reshorten other goo.gl links
                fetchAndShorten(shortenerClient, urlSetter);
            } else {
                shortenerClient.shortenUrl(mDisplayUrl, urlSetter, TAG);
            }
        }
        return START_STICKY;
    }

    private void fetchAndShorten(UrlShortenerClient shortenerClient,
            UrlShortenerClient.ShortenUrlCallback urlSetter) {
        PwsClient pwsClient = new PwsClient();
        pwsClient.resolve(Arrays.asList(mDisplayUrl), new PwsResultCallback() {
            @Override
            public void onPwsResult(PwsResult pwsResult) {
                shortenerClient.shortenUrl(pwsResult.getSiteUrl(), urlSetter, TAG);
            }

            @Override
            public void onPwsResultAbsent(String url) {
                Toast.makeText(getApplicationContext(), getString(R.string.shorten_error), Toast.LENGTH_LONG)
                        .show();
                stopSelf();
            }

            @Override
            public void onPwsResultError(Collection<String> urls, int httpResponseCode, Exception e) {
                Toast.makeText(getApplicationContext(), getString(R.string.shorten_error), Toast.LENGTH_LONG)
                        .show();
                stopSelf();
            }

            @Override
            public void onResponseReceived(long durationMillis) {
            }
        });
    }

    private void fetchBroadcastData(Intent intent) {
        mStartedByRestart = intent == null;
        if (intent == null) {
            SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
            mDisplayUrl = sharedPrefs.getString(PREVIOUS_BROADCAST_URL_KEY, null);
            return;
        }
        mDisplayUrl = intent.getStringExtra(DISPLAY_URL_KEY);
        PreferenceManager.getDefaultSharedPreferences(this).edit()
                .putString(PREVIOUS_BROADCAST_URL_KEY, mDisplayUrl).commit();
    }

    private static boolean hasValidUrlLength(int uriLength) {
        return 0 < uriLength && uriLength <= MAX_URI_LENGTH;
    }

    private boolean checkAndHandleAsciiUrl(String url) {
        boolean isCompliant = false;
        try {
            URI uri = new URI(url);
            String urlString = uri.toASCIIString();
            isCompliant = url.equals(urlString);
        } catch (URISyntaxException e) {
            Toast.makeText(this, getString(R.string.no_url_error), Toast.LENGTH_LONG).show();
        }
        return isCompliant;
    }

    @Override
    public void onDestroy() {
        Log.d(TAG, "SERVICE onDestroy");
        unregisterReceiver(stopServiceReceiver);
        unregisterReceiver(mReceiver);
        disableUrlBroadcasting();
        super.onDestroy();
    }

    @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) {
            Log.d(TAG, "URL is broadcasting");
            Utils.createBroadcastNotification(PhysicalWebBroadcastService.this, stopServiceReceiver,
                    BROADCASTING_NOTIFICATION_ID, getString(R.string.broadcast_notif), mDisplayUrl, "myFilter");
            if (!mStartedByRestart) {
                Toast.makeText(getApplicationContext(), getString(R.string.url_broadcast), 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() {
        final AdvertiseData advertisementData = AdvertiseDataUtils.getAdvertisementData(mShareUrl);
        final AdvertiseSettings advertiseSettings = AdvertiseDataUtils.getAdvertiseSettings(false);
        mBluetoothLeAdvertiser.stopAdvertising(mAdvertiseCallback);
        mBluetoothLeAdvertiser.startAdvertising(advertiseSettings, advertisementData, mAdvertiseCallback);
    }

    // Turn off URL broadcasting
    public void disableUrlBroadcasting() {
        Log.d(TAG, "disableUrlBroadcasting");
        mBluetoothLeAdvertiser.stopAdvertising(mAdvertiseCallback);
        mNotificationManager.cancel(BROADCASTING_NOTIFICATION_ID);
    }

    protected BroadcastReceiver stopServiceReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            Log.d(TAG, context.toString());
            stopSelf();
        }
    };

}