Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to retrieve crash reports from a wearable app?

Since Crashlytics doesn't work on wearable apps out of the box, I'm looking for an optimal way to intercept and report any potential exception thrown in the runtime. I wonder why they're not being automatically reported to Google Play Developer Console?

Google already announced that the future Android Wear update will have Wi-Fi support built-in, but even then, not every device is going to be equipped with the adequate hardware.

In that case, my initial idea was to create a subclass of Application and implement Thread.UncaughtExceptionHandler. Then, every exception would have to be marshalled and sent to a handset, using MessageApi. An extension of WearableListenerService on the handset would receive a message, unmarshal the exception and pass it to, for instance, Crashlytics.

However, that raises a few more questions. There's a risk that the Bluetooth connection between wearable and handset is disrupted, so all errors should be queued and stored on the wearable device's file system.

This seems like an overkill for a simple crash report. Is there an easier way to do this?

like image 419
EyesClear Avatar asked Mar 18 '15 10:03

EyesClear


3 Answers

Don't use MessageApi for this purpose but DataApi. Then you don't have to worry about lost bluetooth connection.

The way it works:

  1. when a crash occurs, set a DataItem with the crash on the wearable;

  2. eventually it will be delivered to the Mobile device.

  3. send the information about the crash from the Mobile and delete the DataItem.

More information here: http://developer.android.com/training/wearables/data-layer/index.html

like image 105
gruszczy Avatar answered Nov 14 '22 22:11

gruszczy


Here's a draft of my solution. As @gruszczy suggested, I'm using DataApi.

Wearable Application:

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);
    }

}

Wearable Service:

public class WearableService extends Service {
    ...
    public static void launchService(Throwable throwable, Context context) {
        Intent startServiceIntent = new Intent(context, WearableService.class);
        startService.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());
                }
            }
        });
    }
}

Mobile Service:

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:
                    // ...
            }
        }
    }
}
like image 28
EyesClear Avatar answered Nov 15 '22 00:11

EyesClear


Existing solutions require that the phone is currently in range. With Wear 2.0 providing for watch autonomy, we need to be able to store the crashes and send them over once we are connected. WearCrashReporter does exactly this.

We install a crash handler on the watch Virtual Machine. When a crash is caught, its trace and type are serialized to json, saved to the FileSystem, then sent with a service as a MessageApi Message when the phone is available. Upon reception by a WearableListenerService in the phone app it is deserialized and passed to the installed Phone Virtual Machine's crash reporter.

like image 43
Philippe Laporte Avatar answered Nov 15 '22 00:11

Philippe Laporte