Polly – getting started with Nearby API

In July 2015, Google released the Nearby API as a part of Google Play services. It gives developers a new way to exchange data between devices based on their proximity. The API is divided into two subcomponents: Nearby Messages and Nearby Connections.

Nearby Connections relies solely on Wi-Fi and allows for real-time communication between devices connected to the same local network. First use case that comes to mind is multiplayer gaming, but Google also suggests it could turn a phone into an Android TV’s game controller.

Nearby Messages, on the other hand, uses a more sophisticated and interesting mechanism to determine proximity: it combines Wi-Fi, Bluetooth, speaker and microphone to establish a contextual pairing between all nearby devices. That alone made me want to play with it, since I’ve been recently drafting my own solution for local, P2P-like communication, but totally independent from any server (as opposed to last year’s famous FireChat, which requires an account) and Google Play services.

While Nearby Messages doesn’t require a Google Account, the actual communication is handled by Google’s servers, so each device has to be connected to the Internet, which also implies that there’s a data payload limit of 100 KB, and the recommended optimal size is only 3 KB for a single message (see: Message class). However, after the neighbouring devices have been discovered and the pairing was established, it’s up to developers to decide if they want to stick with Nearby API for further messages exchange, or transparently redirect the app to use their own implementation (whether it’s a server or local BT/Wi-Fi Direct communication).

https://developers.google.com/nearby/

There’s a lot more of interesting stuff about the API, but here are the 2 facts that you won’t find in the official documentation:

  • There’s currently no limit to number of devices communicating over Nearby API.
  • While it is claimed that broadcasting/scanning for token with speaker/microphone is based on inaudible ultrasonic sounds, you can actually hear your speaker working quite clearly (at least on a Samsung Galaxy S2 and Nexus 5 that I’ve been testing with) when scanning is active.

Giving it a try

To familiarize myself with the API, I’ve started to work on a simple app called Polly, which – you guessed it – allows to conduct polls and surveys with people around you (at a conference, for example). I’m not sure if it will end up as a full release to Google Play (it would be nice to have an iOS companion app) or just one of many working prototypes, as I call my unfinished projects.

What’s in the box? Mainly ButterKnife, Dagger, Timber and Otto. I’ll probably also refactor PollRepository and its clients to make use of RxAndroid.

GitHub

1 4

Raz dwa!

“Raz dwa!” speeds up SMS-based two-factor authentication process with selected banks and services.

When you receive an authorization SMS from your bank or other service, the app will automatically display a notification with the one-time password, copy it to the clipboard and read it out loud. You will no longer lose your time trying to find it in long, boilerplate messages. Confirm money transfers or log in to your account in the blink of an eye.

Currently most major Polish banks and a few other services are supported, which means that my brain’s got a quite intensive regex training during past few days.

The app is live on Google Play (available in Poland only).

Update: the app received a positive review on AntyApps.pl.

Update 2: Raz dwa! was also featured on AntyWeb.pl.

screenshot
screenshot

Introducing HeartWatch

It took Piotr and me quite some time, but the first release is now ready.

HeartWatch gives you a unique way to monitor the heart rate of elder people or anyone suffering from heart diseases. It allows you to request an instant, remote pulse measurement with just a single tap, at any time and from anywhere.

You can grab the apk on Google Play or watch a short preview:

When I’m writing this, our app is the only one on Google Play that allows to remotely check someone’s heart rate. I was responsible for the mobile app and Piotr did the API part (ASP.NET WebAPI on Azure platform).

We’ve started the development before the Android Design Support library was released at I/O ’15, so initially I had to recreate many parts of “material” UI manually (like NavigationView), following Google’s cheat sheet, or use some really good 3rd party libraries, like FloatingActionButton or Snackbar.

Retrofit and OkHttp were used for the API communication, with Gson and its custom adapters (mainly for time-related fields from Joda-Time) for automatic JSON deserialization.

For pulse measurement requests, alerts and other notifications, we’ve used Azure’s Notification Hub, which is basically Microsoft’s interface for Google Cloud Messaging.

Database runs on a cloud version of MS SQL Server (v12) located somewhere over the rainbow in East US.

Samsung Gear Live that we bought few months ago was our Android Wear test device.

Main features

  • Remotely check the heart rate of your close friends and family members (they’re required to have an Android Wear smartwatch equipped with a heart rate monitor).
  • Remotely check the heart rate of your close friends and family members (they’re required to have an Android Wear smartwatch equipped with a heart rate monitor).
  • Set automatic periodic heart rate measurements for them.
  • Choose a suitable measurement duration.
  • Receive notifications when your friend’s heart rate goes out of a specified range.
  • Get quick access to contact data and GPS location of your friend in case of emergency.
  • Browse the history of measurements.
  • Inspect the data in charts.
  • Automatic activity recognition for measurements (walking, running, cycling, driving).
  • Android Wear integration, including a wearable app.
  • Start a heart rate check via voice command by speaking to your watch or phone (e.g. “Ok Google, check my pulse”, “Ok Google, what’s my bpm?” etc.).
  • Seamlessly sync measurements to Google Fit and integrate the data with other sources.
  • Material Design touch

Requirements

  • An Android Wear smartwatch equipped with a heart rate monitor for you or your friend
  • Android Wear app (available in Play Store)
  • Google+ account for quick and secure sign-in
  • Internet connection

Shared “checkable” behavior across all groups in NavigationView

I’m not sure why Google not only did not provide a straightforward API to customize such basic property, but also chose to set the default behavior to be rather undesirable in most cases.

I’ve asked a question on Stack Overflow and on the next day I provided a quick solution. Here’s the snippet:

Step 1: Remove

android:checkableBehavior="single"

from all groups.

Step 2: Add

android:checkable="true"

to all items in all groups.

Step 3: Add the following logic to your OnNavigationItemSelectedListener:

mNavigationView.setNavigationItemSelectedListener(
            new NavigationView.OnNavigationItemSelectedListener() {
                @Override
                public boolean onNavigationItemSelected(MenuItem menuItem) {
                    menuItem.setChecked(true);
                    if (mPreviousMenuItem != null) {
                        mPreviousMenuItem.setChecked(false);
                    }
                    mPreviousMenuItem = menuItem;
                    //...
                    changeCurrentFragment(...);
                    return true;
                }
            });

Forwarding Android Wear app’s crash report to phone

About a week ago, I posted a question on how to retrieve crash reports from wearable apps. Apparently, neither Crashlytics, nor Android Wear itself supports this feature out of the box, so we’re required to do it manually. Luckily, I received a useful answer from a Google employee hinting to try DataApi instead of MessageApi, which I originally intended to use.

To put it simply, MessageApi is a “fire and forget” communication protocol, while DataApi takes care of possible connection breakdowns, e.g. losing Bluetooth link between watch and handset. It also handles device reboots – the data will still be transferred eventually. Well, one problem solved. I’m posting a draft of my solution below.

First, we have to implement Thread.UncaughtExceptionHandler interface. The Application subclass is a perfect place for this:

public class WApplication extends Application
        implements Thread.UncaughtExceptionHandler {

    private static final String LOG_TAG = WApplication.class.getSimpleName();

    private Thread.UncaughtExceptionHandler mDefaultUncaughtExceptionHandler;

    ...

    @Override
    public void onCreate() {
        super.onCreate();
        mDefaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
        Thread.setDefaultUncaughtExceptionHandler(this);
    }

    @Override
    public void uncaughtException(Thread thread, final Throwable throwable) {
        Log.e(LOG_TAG, "Uncaught exception thrown.");

        WearableService.launchService(throwable, WApplication.this);

        mDefaultUncaughtExceptionHandler.uncaughtException(thread, throwable);
    }

}

Inside the uncaughtException() method, we’re launching a service and passing the crash to it as an intent’s extra. Then, we need to create a PutDataRequest instance with the stack trace, and sent it to the handset:

public class WearableService extends Service {
    ...
    public static void launchService(Throwable throwable, Context context) {
        Intent startServiceIntent = new Intent(context, WearableService.class);
        startServiceIntent.putExtra(EXTRA_KEY_EXCEPTION, throwable);
        context.startService(startServiceIntent);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Throwable throwable = (Throwable) intent.getSerializableExtra(KEY_EXCEPTION);

        sendExceptionToMobile(throwable);

        return super.onStartCommand(intent, Service.START_REDELIVER_INTENT, startId);
    }

    private void sendExceptionToMobile(final Throwable throwable) {
        if (throwable == null) {
            return;
        }
        Log.d(LOG_TAG, "Sending exception to mobile...");
    
        PutDataMapRequest putDataMapReq = PutDataMapRequest
                .create(WearCommunicationConstants.PATH_EXCEPTION);
        DataMap dataMap = putDataMapReq.getDataMap();
    
        StringWriter sw = new StringWriter();
        throwable.printStackTrace(new PrintWriter(sw));
        String stackTrace = sw.toString();
    
        dataMap.putString(WearCommunicationConstants.KEY_STACK_TRACE, stackTrace);
        PutDataRequest putDataReq = putDataMapReq.asPutDataRequest();
        PendingResult<DataApi.DataItemResult> pendingResult =
                Wearable.DataApi.putDataItem(mGoogleApiClient, putDataReq);
        
        pendingResult.setResultCallback(new ResultCallback<DataApi.DataItemResult>() {
            @Override
            public void onResult(final DataApi.DataItemResult result) {
                if (result.getStatus().isSuccess()) {
                    Log.d(LOG_TAG,
                            "DataItem synced: " + result.getDataItem().getUri());
                } else {
                    Log.e(LOG_TAG,
                            "Failed to sync DataItem: " + result.getStatus().getStatusCode() + ", "
                                    + result.getStatus().getStatusMessage());
                }
            }
        });
    }
}

Now, all that’s left is retrieve the message in a subclass of WearableListenerService and log it (or forward it to Crashlytics):

public class MobileService extends WearableListenerService {
    ...
    @Override
    public void onDataChanged(DataEventBuffer dataEvents) {
        Log.d(LOG_TAG, "Data changed, data event(s) received.");

        for (DataEvent event : dataEvents) {
            Log.d(LOG_TAG, "Data event type: " + event.getType());
            switch (event.getType()) {
                case DataEvent.TYPE_CHANGED:
                    DataItem item = event.getDataItem();
                    DataMap dataMap = DataMapItem.fromDataItem(item).getDataMap();
                    switch (item.getUri().getPath()) {
                        case WearCommunicationConstants.PATH_EXCEPTION:
                            Log.e(LOG_TAG, "Received exception from a wearable device.");
                            String stackTrace = dataMap
                                    .getString(WearCommunicationConstants.KEY_STACK_TRACE);
                            Utils.logWithCrashlytics(stackTrace);

                            break;

                        // ...
                    }
                    break;
                case DataEvent.TYPE_DELETED:
                    // ...
            }
        }
    }
}

That’s it!

Of course, there’s always an easier way to go: write an app which never crashes.