How to retrieve crash reports from a wearable app? - android

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?

Don't use MessageApi for this purpose but DataApi. Then you don't have to worry about lost bluetooth connection.
The way it works:
when a crash occurs, set a DataItem with the crash on the wearable;
eventually it will be delivered to the Mobile device.
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

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:
// ...
}
}
}
}

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.

I solved this problem in following way:
Integrate this lib into your project. This lib will transmit all exceptions from wear app to mobile app.
If you don't use proguard - you can simple use ExceptionWear lib and log exceptions on mobile-app side into crashlytics.
otherwise
When you receive throwable on mobile-app side - you can log it into crashlytics, but there is a problem:
If we build mobile+wear application using android plugin feature we will have something like this:
dependencies {
compile 'com.google.android.gms:play-services:5.0.+#aar'
...lots of cool libs...
wearApp project(':wear')
}
and apply crashlytics plugin on both applications (mobile and wear) then during building wear application you can see that after proguard task and dex task (gradle tasks) crashlytics plugin doesn't store and upload Deobs and as a result - stacktraces are not remapped(retraced) on the crashlytics dashboard:
:wear:crashlyticsCleanupResourcesRelease//EXPECTED
:wear:crashlyticsUploadStoredDeobsRelease//EXPECTED
:wear:crashlyticsGenerateResourcesRelease//EXPECTED
:wear:generateReleaseResValues UP-TO-DATE
:wear:generateReleaseResources
:wear:mergeReleaseResources
:wear:processReleaseResources
:wear:generateReleaseSources
:wear:compileReleaseJava
:wear:proguardRelease
:wear:dexRelease//NO crashlytics store and upload Deobs tasks
:wear:processReleaseJavaRes UP-TO-DATE
:wear:shrinkReleaseResources
but when wear app is builded (wear ap is like dependency to mobile app) then mobile app build starts and during mobile build process crashlytics plugin works well:
:mobile:crashlyticsCleanupResourcesRelease//EXPECTED
:mobile:crashlyticsUploadStoredDeobsRelease//EXPECTED
:mobile:crashlyticsGenerateResourcesRelease//EXPECTED
:mobile:generateReleaseResValues UP-TO-DATE
:mobile:generateReleaseResources
:mobile:mergeReleaseResourcesknown
:mobile:processReleaseResources
:mobile:generateReleaseSources
:mobile:compileReleaseJava
:mobile:proguardRelease
:mobile:dexRelease
:mobile:crashlyticsStoreDeobsRelease//EXPECTED
:mobile:crashlyticsUploadDeobsRelease//EXPECTED
:mobile:crashlyticsCleanupResourcesAfterUploadRelease//EXPECTED
:mobile:lintVitalRelease
:mobile:compileReleaseNdk UP-TO-DATE
:mobile:processReleaseJavaRes UP-TO-DATE
:mobile:shrinkReleaseResources
So, during standard build process wear module deobs are not uploaded, but there is workaround:
if start build wear app separately and then manualy package wear apk in mobile module resources,
then wear deobs uploaded successfully and you can observe retraced crashes on dashboard.
But i personally don't like manual way of building apk, so i tried to do following : at first just build only wear app. deobs will uploaded to crashlytics. then run full build using 'wearApp project(':wear')' feature and looks like it works.
Anyway i am waiting for android-wear support by crashlytics out of the box.

It's possible to upload the deobs of mobile and wear within the build process.
Concept:
1. Ensure mobile and wear have unique mappings
2. Merge wear mappings into mobile mappings before upload
1. Configure proquard (usually proguard-rules.pro)
for wear add:
-useuniqueclassmembernames
for mobile add:
-useuniqueclassmembernames
-applymapping ../wear/build/outputs/mapping/release/mapping.txt
This change ensures you have unique names over mobile and wear by applying the mappings of the wear build to the mobile build.
2. Configure the build to merge mapping.txt
Add to build.gradle of mobile:
// allows to use Crashlytics also for wear by merging the mappings of wear into the
// mappings of mobile
//noinspection GroovyAssignabilityCheck
task mergeMappings(dependsOn: "transformClassesAndResourcesWithProguardForRelease") << {
File wearMappingFile = new File("wear/build/outputs/mapping/release/mapping.txt");
File mobileMappingFile = new File("mobile/build/outputs/mapping/release/mapping.txt");
if (wearMappingFile.exists() && mobileMappingFile.exists()) {
println("merge mapping.txt")
java.nio.file.Files.copy(wearMappingFile.toPath(),
new FileOutputStream(mobileMappingFile, true))
} // else we are on the wear build and the mobile build was not yet executed
}
afterEvaluate {
project.("crashlyticsStoreDeobsRelease").dependsOn(mergeMappings);
}
Appends the wear mappings to the mobile mappings before crashlyticsStoreDeobsRelease.

Related

How do I save app logs locally on Android?

I want to save the logs generated by my application locally on the android device and view them in an instance of a crash.
Using the "Take Bug Report" under the developer options gives the entire system logs which are irrelevant to me. I am looking only for those logs created by my application when it runs.
Is there any application that does this? Or are there any libraries I could include in my application code to satisfy my requirement?
You may just add firebase to your project, and everything will be done automatically.
Or if need it to be "locally", can use the Thread.UncaughtExceptionHandler to save crash log. Register it when your application onCreate.
private static UncaughtExceptionHandler mDefaultUncaughtExceptionHandler;
public static void registerUncaughtExceptionHandler() {
mDefaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() {
public void uncaughtException(Thread thread, Throwable ex) {
// Save Log
saveLog(ex);
// Throw system
mDefaultUncaughtExceptionHandler.uncaughtException(thread, ex);
}
});
}
private static void saveLog(Throwable exception) {
try {
String stackTrace = Log.getStackTraceString(exception);
// Save it to SharedPreferences or DB as you like
} catch (Exception e) {
}
}
Then can extract the last crash log, submit to your server or display in logcat when app starts.
It is much better to use Third Party libraries such as Firebase Crashlytics or Sentry Crash Report or AppMetrica for crash reports.
just add these libraries and make an account on one of these sites, then you can have a full report of crashes if happen.
but if you want to save the logs on the device, you can refer to this question :
Saving Logcat to a text file in Android Device
You can try this
fun writeLog(context: Context) {
try {
val path = File(context.filesDir, "log_files")
if (!path.exists()) {
path.mkdir()
}
val fileName = "your_filename.txt"
Runtime.getRuntime().exec("logcat -v time -f $fileName")
} catch (e: IOException) {
}
}
Or you can change logcat command based on your requirements: refer to this https://developer.android.com/studio/command-line/logcat
You can check it at data/data/{applicationId}/files/log_files/

GoogleApiClient builder failing to build

I'm implementing Google Smart Lock into an app, and I was having no trouble with the Api Client building before. In fact, I was finalizing some syntax changes and cleaning up the code (didn't even touch the code that initializes the Api Client), and my app now dies when build() is called on the Api Client builder, due to abstract method zza. Here is the error being displayed:
java.lang.AbstractMethodError: abstract method "com.google.android.gms.common.api.Api$zze com.google.android.gms.common.api.Api$zza.zza(android.content.Context, android.os.Looper, com.google.android.gms.common.internal.zzq, java.lang.Object, com.google.android.gms.common.api.GoogleApiClient$ConnectionCallbacks, com.google.android.gms.common.api.GoogleApiClient$OnConnectionFailedListener)"
at com.google.android.gms.common.api.GoogleApiClient$Builder.build(Unknown Source)
I have no clue why it suddenly started failing, and I couldn't find any changes I made that would have caused this error. Why isn't that abstract method being overridden? It's nested deep inside the library so I don't understand how I could have affected it.
I wrapped the Google Api Client calls in a manager I named CredentialManager. Here is the code I used to initialize the client:
public CredentialManager(ContextProvider contextProvider) {
mContextProvider = contextProvider;
mCredentialsApiClient = new GoogleApiClient.Builder(mContextProvider.getContext())
.addConnectionCallbacks(new GoogleApiClient.ConnectionCallbacks() {
#Override
public void onConnected(#Nullable Bundle bundle) {
Log.i(CredentialManager.TAG, "Api connected");
}
#Override
public void onConnectionSuspended(int i) {
Log.i(CredentialManager.TAG, "Connection suspended with status " + i);
}
})
.enableAutoManage(mContextProvider.getContext(), connectionFailedResult -> {
if (connectionFailedResult.hasResolution()) {
try {
connectionFailedResult.startResolutionForResult(
mContextProvider.getContext(),
CredentialManager.Codes.RESOLVE_CONNECTION_REQUEST_CODE);
} catch (IntentSender.SendIntentException e) {
// Unable to resolve, log error
Log.e(CredentialManager.TAG, "Resolution failed: " + e.getMessage());
}
} else {
//instead of displaying a dialog, just let the user continue and login manually.
Log.e(CredentialManager.TAG, "Connection failed: " + connectionFailedResult.getErrorMessage());
}
})
.addApi(Auth.CREDENTIALS_API)
.build();
}
If you have any insight as to what is causing this error, please let me know. I've scoured the internet for anyone that has seen something like this before, but couldn't find anything.
The issue was that some google play services dependencies had their versions updated and not the play-services-auth dependency used for google smart lock. The apk would compile fine, but crash when the Google Api Client was trying to initialize. The fix was to make all the versions the same, and invalidate cache + restart android studio, recompile, and run.

Android service fails to connect to server when the activities are in the background

this is my first post on stackoverflow so I hope I am doing everything correctly.
I am developing my first android App and I am experiencing weird behavior on some devices. I have a foreground service that periodically obtains the location of the device and I use a handler with a delayed runner to send it to a server. In my phone (Marshmallow, API 23) everything is working fine, but a friend who uses a Xiaomi phone running Lollipop (API 21) fails to connect to the server when the App's activities are in the background.
This is the function that sends the location to the server:
private void sendLocation(final String request_id, final String location ) {
Retrofit retrofit = new Retrofit.Builder()
.baseUrl(RestServiceConstants.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
String email = prefManager.pref.getString("UserEmail","");
String password = prefManager.pref.getString("UserPassword","");
Log.d(TAG,"sendLocation called");
RestService service = retrofit.create(RestService.class);
Call<StatusResponse> call = service.location("Basic "+ Base64.encodeToString((email + ":" + password).getBytes(),Base64.NO_WRAP),
request_id,location);
call.enqueue(new Callback<StatusResponse>() {
#Override
public void onResponse(Call<StatusResponse> call, Response<StatusResponse> response) {
Log.d(TAG, "onResponse: raw: " + response.body());
if (response.isSuccess() && response.body() != null){
Log.d(TAG, "The location has been sent successfully");
if(resendLocationHandler != null)
resendLocationHandler.removeCallbacksAndMessages(null);
} else if (response.code() == 401){
Log.i(TAG, "onCreate: User not logged in");
prefManager.setIsLoggedIn(false);
} else {
Log.i(TAG, "sendLocation Unknown error occurred");
}
}
#Override
public void onFailure(Call<StatusResponse> call, Throwable t) {
Log.i(TAG, "sendLocation Failed to get the server");
if(resendLocationHandler == null)
resendLocationHandler = new Handler();
resendLocationHandler.postDelayed(new Runnable() {
#Override
public void run() {
sendLocation(request_id, location);
}
}, resendFailedRequestDelay);
}
});
}
I don't know what else should I provide to help you diagnose the issue so feel free to request whatever may seem relevant. Thanks in advance
Edit: what I mean by the request failing is that it triggers the onFailure callback. The exception caught in the callback is:
java.net.ConnectException: Failed to connect to my_server_address:80
at okhttp3.internal.io.RealConnection.connectSocket(RealConnection.java:139)
at okhttp3.internal.io.RealConnection.connect(RealConnection.java:108)
at okhttp3.internal.http.StreamAllocation.findConnection(StreamAllocation.java:188)
at okhttp3.internal.http.StreamAllocation.findHealthyConnection(StreamAllocation.java:127)
at okhttp3.internal.http.StreamAllocation.newStream(StreamAllocation.java:97)
at okhttp3.internal.http.HttpEngine.connect(HttpEngine.java:289)
at okhttp3.internal.http.HttpEngine.sendRequest(HttpEngine.java:241)
at okhttp3.RealCall.getResponse(RealCall.java:240)
at okhttp3.RealCall$ApplicationInterceptorChain.proceed(RealCall.java:198)
at okhttp3.RealCall.getResponseWithInterceptorChain(RealCall.java:160)
at okhttp3.RealCall.access$100(RealCall.java:30)
at okhttp3.RealCall$AsyncCall.execute(RealCall.java:127)
at okhttp3.internal.NamedRunnable.run(NamedRunnable.java:33)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
at java.lang.Thread.run(Thread.java:818)
It may be related some kind of "Battery optimization" which could prevent background apps from accessing the network -- see, for example, https://www.reddit.com/r/Xiaomi/comments/4r6eld/does_battery_optimization_ever_work_for/
after i had some problems with background apps on MIUI, i figured it
hat something to do with the battery saver feature in MIUI. When i
just set "Manage apps' battery usage" to standard, the background apps
rarely work, even if they have autorun permission. "Rarely work"
means: Google Inbox does not sync, Google Maps does not track, some
location based apps (like Rain Alarm) do not show rain notifications,
and so on.
I had to set EVERY of these apps to the exception list, so
that the battery optimization does not affect them. [...]
So, it may be that Xiaomi users (and users whose devices have similar functionality) need to manually add your app to a whitelist.
I don't know if "optimization" settings like these are on by default -- I would certainly hope they aren't.
Check following permission added in manifest file
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

Pebble Communication Issues [AppSync/AppMessage]

I'm currently attempting to use AppSync to sync a piece of data between an android app and a pebble app. However, I seem to not be able to get the pebble to realize that any data is being transferred - that is, no logs are being produced where they should be. What is really bothering me is that this is essentially the code found in the pebble weather example. I've pasted the relevant bits of code below - could someone possibly look it over and suggest where any issues may be? I've made sure that the UUIDs in both programs (pebble app and android app) are the same, and that they are on the same network, and that the pebble is actually connected to the phone, and that the android function is actually being called and all.
Snippet of pebble app code:
static void sync_error_callback(DictionaryResult dict_error, AppMessageResult app_message_error, void *context) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "App Message Sync Error: %d", app_message_error);
}
static void sync_tuple_changed_callback(const uint32_t key, const Tuple* new_tuple, const Tuple* old_tuple, void* context) {
APP_LOG(APP_LOG_LEVEL_DEBUG, new_tuple->value->cstring);
}
void home_screen_load() {
// set up each one of the SimpleMenuItems
Tuplet initial_values[] = {
TupletCString(0x0, "Initial 1")
};
app_sync_init(&sync, sync_buffer, sizeof(sync_buffer), initial_values, ARRAY_LENGTH(initial_values), sync_tuple_changed_callback, sync_error_callback, NULL);
}
Snippet of android app:
final UUID PEBBLE_APP_UUID = UUID.fromString("10549fd4-1fe4-4d30-8a18-6f2f8149f8fd");
public void sendDataToWatch(String toSend) {
// Build up a Pebble dictionary containing the weather icon and the current temperature in degrees celsius
PebbleDictionary data = new PebbleDictionary();
data.addString(0x0, toSend);
PebbleKit.sendDataToPebble(getApplicationContext(), PEBBLE_APP_UUID, data);
}
To debug this type of problem, you should set a inbox_dropped handler and see if you get anything there.
After initializing AppMessage and AppSync, call:
app_message_register_inbox_dropped(appmsg_in_dropped);
And add this function:
static void appmsg_in_dropped(AppMessageResult reason, void *context) {
APP_LOG(APP_LOG_LEVEL_DEBUG, "In dropped: %s", translate_error(reason));
}
Take a look at this question for the source of the translate_error function.

Error integrating Aurasma: Resource Integrity check failed

I am trying to Integrating Aurasma in my application. All application work well but on Aurasma part when I launch it on Button Click IT throws a message on splash screen as "An error is occurred" and on Log Cat It shows "Resource integrity check failed" I am wondering why this is happening, I integrate aurasma on a separate application without any click event, it launches directly then it works but in side of my application its not working, why. I am sure about these points:
Make sure the SDK tools are version 14 or above.
Check the Eclipse project to make sure that AurasmaKernel is set as required on the build path
Check that the AurasmaKernel package has built properly in Eclipse (also try building it manually)
Make sure that the kernel is correctly extracted, and that your resources don't clash with any of the packaged library
But yet it not working same error message.
Code for launching Aurasma is below:
aurasmaIntent = AurasmaIntentFactory.getAurasmaLaunchIntent(HomeActivity.this,
getString(R.string.app_name), getString(R.string.app_version));
} catch (AurasmaLaunchException e) {
Log.e("AKTest", "Error getting intent", e);
showDialog(DIALOG_ERROR);
return;
}
if (DELAY_START) {
AurasmaSetupCallback callback = new AurasmaSetupCallback() {
#Override
public void onLoaded() {
dismissDialog(DIALOG_PROGRESS);
startActivity(aurasmaIntent);
}
#Override
public void onLoadWarning(final int code) {
Log.w("AKTest", "Preload warning: " + code);
}
#Override
public void onLoadFail(final int code) {
Log.e("AKTest", "Preload error: " + code);
dismissDialog(DIALOG_PROGRESS);
showDialog(DIALOG_ERROR);
}
};
showDialog(DIALOG_PROGRESS);
AurasmaIntentFactory.startAurasmaPreload(getApplicationContext(), aurasmaIntent,
callback);
} else {
startActivity(aurasmaIntent);
}
}
If you change some resource from Aurasma library(layout or string) you will get this error - "An error is occurred". Library checks resources on Aurasma start. Don't change or delete any files.
Another thing that can cause error is:
aurasmaIntent = AurasmaIntentFactory.getAurasmaLaunchIntent(HomeActivity.this,
getString(R.string.app_name), getString(R.string.app_version));
Here second parameter is userAgentName. This is the name of your app that you have from studio.aurasma.com. In "Make your own app" you can see the application name - this name is connected with your application but can be different.
check your minSdkVersion in the manifest
android:minSdkVersion="8"

Categories

Resources