diff --git a/app/build.gradle b/app/build.gradle index f7ccd64..c4bede4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -41,6 +41,7 @@ dependencies { implementation 'com.google.android.gms:play-services:10.0.1' implementation 'com.github.wendykierp:JTransforms:3.0' implementation 'com.google.android.gms:play-services-location:10.0.0' + //implementation 'com.github.RohitSurwase.UCE-Handler:uce_handler:1.3' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0672e07..bc37045 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -44,6 +44,9 @@ + + *

This class is used to

+ * Created by Rohit. + */ +public final class UCEDefaultActivity extends Activity { + private File txtFile; + private String strCurrentErrorLog; + private String TAG = "UCEDefaultActivity"; + + @SuppressLint("PrivateResource") + @Override + protected void onCreate(Bundle savedInstanceState) { + setTheme(android.R.style.Theme_Holo_Light_DarkActionBar); + super.onCreate(savedInstanceState); + Log.i(TAG,"onCreate()"); + setContentView(R.layout.default_error_activity); + findViewById(R.id.button_close_app).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + UCEHandler.closeApplication(UCEDefaultActivity.this); + } + }); + findViewById(R.id.button_copy_error_log).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + copyErrorToClipboard(); + } + }); + findViewById(R.id.button_share_error_log).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + shareErrorLog(); + } + }); + findViewById(R.id.button_save_error_log).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + saveErrorLogToFile(true); + } + }); + findViewById(R.id.button_email_error_log).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + emailErrorLog(); + } + }); + findViewById(R.id.button_view_error_log).setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + AlertDialog dialog = new AlertDialog.Builder(UCEDefaultActivity.this) + .setTitle("Error Log") + .setMessage(getAllErrorDetailsFromIntent(UCEDefaultActivity.this, getIntent())) + .setPositiveButton("Copy Log & Close", + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + copyErrorToClipboard(); + dialog.dismiss(); + } + }) + .setNeutralButton("Close", + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.dismiss(); + } + }) + .show(); + TextView textView = dialog.findViewById(android.R.id.message); + if (textView != null) { + textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 18); + } + } + }); + } + + public String getApplicationName(Context context) { + ApplicationInfo applicationInfo = context.getApplicationInfo(); + int stringId = applicationInfo.labelRes; + return stringId == 0 ? applicationInfo.nonLocalizedLabel.toString() : context.getString(stringId); + } + + private String getVersionName(Context context) { + try { + PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); + return packageInfo.versionName; + } catch (Exception e) { + return "Unknown"; + } + } + + private String getActivityLogFromIntent(Intent intent) { + return intent.getStringExtra(UCEHandler.EXTRA_ACTIVITY_LOG); + } + + private String getStackTraceFromIntent(Intent intent) { + return intent.getStringExtra(UCEHandler.EXTRA_STACK_TRACE); + } + + private String getEmailAddressesFromIntent(Intent intent) { + Log.d(TAG,"getEmailFromIntent - "+intent.getStringExtra(UCEHandler.EXTRA_EMAIL_ADDRESSES)+"."); + return intent.getStringExtra(UCEHandler.EXTRA_EMAIL_ADDRESSES); + } + + private void emailErrorLog() { + saveErrorLogToFile(false); + String errorLog = getAllErrorDetailsFromIntent(UCEDefaultActivity.this, getIntent()); + Log.d(TAG,"emailErrorLog() - addresses = "+UCEHandler.COMMA_SEPARATED_EMAIL_ADDRESSES+"."); + //String[] emailAddressArray = UCEHandler.COMMA_SEPARATED_EMAIL_ADDRESSES.trim().split("\\s*,\\s*"); + String emailStr = getEmailAddressesFromIntent(getIntent()); + Log.d(TAG,"emailErrorLog() - EmailStr = "+emailStr); + String[] emailAddressArray = emailStr.split("\\s*,\\s*"); + //String[] emailAddressArray = {"crashreports@openseizuredetector.org.uk"}; + Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND); + emailIntent.setType("plain/text"); + emailIntent.putExtra(Intent.EXTRA_EMAIL, emailAddressArray); + emailIntent.putExtra(Intent.EXTRA_SUBJECT, getApplicationName(UCEDefaultActivity.this) + " Application Crash Error Log"); + emailIntent.putExtra(Intent.EXTRA_TEXT, getString(R.string.email_welcome_note) + errorLog); + //if (txtFile.exists()) { + // Uri filePath = Uri.fromFile(txtFile); + // emailIntent.putExtra(Intent.EXTRA_STREAM, filePath); + //} + startActivity(Intent.createChooser(emailIntent, "Email Error Log")); + } + + private void saveErrorLogToFile(boolean isShowToast) { + Boolean isSDPresent = Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED); + if (isSDPresent && isExternalStorageWritable()) { + Date currentDate = new Date(); + DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US); + String strCurrentDate = dateFormat.format(currentDate); + strCurrentDate = strCurrentDate.replace(" ", "_"); + String errorLogFileName = getApplicationName(UCEDefaultActivity.this) + "_Error-Log_" + strCurrentDate; + String errorLog = getAllErrorDetailsFromIntent(UCEDefaultActivity.this, getIntent()); + String fullPath = Environment.getExternalStorageDirectory() + "/AppErrorLogs_UCEH/"; + FileOutputStream outputStream; + try { + File file = new File(fullPath); + file.mkdir(); + txtFile = new File(fullPath + errorLogFileName + ".txt"); + txtFile.createNewFile(); + outputStream = new FileOutputStream(txtFile); + outputStream.write(errorLog.getBytes()); + outputStream.close(); + if (txtFile.exists() && isShowToast) { + Toast.makeText(this, "File Saved Successfully", Toast.LENGTH_SHORT).show(); + } + } catch (IOException e) { + Log.e("REQUIRED", "This app does not have write storage permission to save log file."); + if (isShowToast) { + Toast.makeText(this, "Storage Permission Not Found", Toast.LENGTH_SHORT).show(); + } + e.printStackTrace(); + } + } + } + + private void shareErrorLog() { + String errorLog = getAllErrorDetailsFromIntent(UCEDefaultActivity.this, getIntent()); + Intent share = new Intent(Intent.ACTION_SEND); + share.setType("text/plain"); + share.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); + share.putExtra(Intent.EXTRA_SUBJECT, "Application Crash Error Log"); + share.putExtra(Intent.EXTRA_TEXT, errorLog); + startActivity(Intent.createChooser(share, "Share Error Log")); + } + + private void copyErrorToClipboard() { + String errorInformation = getAllErrorDetailsFromIntent(UCEDefaultActivity.this, getIntent()); + ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); + if (clipboard != null) { + ClipData clip = ClipData.newPlainText("View Error Log", errorInformation); + clipboard.setPrimaryClip(clip); + Toast.makeText(UCEDefaultActivity.this, "Error Log Copied", Toast.LENGTH_SHORT).show(); + } + } + + private String getAllErrorDetailsFromIntent(Context context, Intent intent) { + if (TextUtils.isEmpty(strCurrentErrorLog)) { + String LINE_SEPARATOR = "\n"; + StringBuilder errorReport = new StringBuilder(); + errorReport.append("***** UCE HANDLER Library "); + errorReport.append("\n***** by Rohit Surwase \n"); + errorReport.append("\n***** DEVICE INFO \n"); + errorReport.append("Brand: "); + errorReport.append(Build.BRAND); + errorReport.append(LINE_SEPARATOR); + errorReport.append("Device: "); + errorReport.append(Build.DEVICE); + errorReport.append(LINE_SEPARATOR); + errorReport.append("Model: "); + errorReport.append(Build.MODEL); + errorReport.append(LINE_SEPARATOR); + errorReport.append("Manufacturer: "); + errorReport.append(Build.MANUFACTURER); + errorReport.append(LINE_SEPARATOR); + errorReport.append("Product: "); + errorReport.append(Build.PRODUCT); + errorReport.append(LINE_SEPARATOR); + errorReport.append("SDK: "); + errorReport.append(Build.VERSION.SDK); + errorReport.append(LINE_SEPARATOR); + errorReport.append("Release: "); + errorReport.append(Build.VERSION.RELEASE); + errorReport.append(LINE_SEPARATOR); + errorReport.append("\n***** APP INFO \n"); + String versionName = getVersionName(context); + errorReport.append("Version: "); + errorReport.append(versionName); + errorReport.append(LINE_SEPARATOR); + Date currentDate = new Date(); + DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US); + String firstInstallTime = getFirstInstallTimeAsString(context, dateFormat); + if (!TextUtils.isEmpty(firstInstallTime)) { + errorReport.append("Installed On: "); + errorReport.append(firstInstallTime); + errorReport.append(LINE_SEPARATOR); + } + String lastUpdateTime = getLastUpdateTimeAsString(context, dateFormat); + if (!TextUtils.isEmpty(lastUpdateTime)) { + errorReport.append("Updated On: "); + errorReport.append(lastUpdateTime); + errorReport.append(LINE_SEPARATOR); + } + errorReport.append("Current Date: "); + errorReport.append(dateFormat.format(currentDate)); + errorReport.append(LINE_SEPARATOR); + errorReport.append("\n***** ERROR LOG \n"); + errorReport.append(getStackTraceFromIntent(intent)); + errorReport.append(LINE_SEPARATOR); + String activityLog = getActivityLogFromIntent(intent); + errorReport.append(LINE_SEPARATOR); + if (activityLog != null) { + errorReport.append("\n***** USER ACTIVITIES \n"); + errorReport.append("User Activities: "); + errorReport.append(activityLog); + errorReport.append(LINE_SEPARATOR); + } + errorReport.append("\n***** END OF LOG *****\n"); + strCurrentErrorLog = errorReport.toString(); + return strCurrentErrorLog; + } else { + return strCurrentErrorLog; + } + } + + private String getFirstInstallTimeAsString(Context context, DateFormat dateFormat) { + long firstInstallTime; + try { + firstInstallTime = context + .getPackageManager() + .getPackageInfo(context.getPackageName(), 0) + .firstInstallTime; + return dateFormat.format(new Date(firstInstallTime)); + } catch (PackageManager.NameNotFoundException e) { + return ""; + } + } + + private String getLastUpdateTimeAsString(Context context, DateFormat dateFormat) { + long lastUpdateTime; + try { + lastUpdateTime = context + .getPackageManager() + .getPackageInfo(context.getPackageName(), 0) + .lastUpdateTime; + return dateFormat.format(new Date(lastUpdateTime)); + } catch (PackageManager.NameNotFoundException e) { + return ""; + } + } + + public boolean isExternalStorageWritable() { + String state = Environment.getExternalStorageState(); + if (Environment.MEDIA_MOUNTED.equals(state)) { + return true; + } + return false; + } +} diff --git a/app/src/main/java/com/rohitss/uceh/UCEHandler.java b/app/src/main/java/com/rohitss/uceh/UCEHandler.java new file mode 100644 index 0000000..9970e55 --- /dev/null +++ b/app/src/main/java/com/rohitss/uceh/UCEHandler.java @@ -0,0 +1,271 @@ +/* + * + * * Copyright © 2018 Rohit Sahebrao Surwase. + * * + * * 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.rohitss.uceh; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.Application; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.ref.WeakReference; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayDeque; +import java.util.Date; +import java.util.Deque; +import java.util.Locale; + +/** + * + *

This class is used to

+ * Created by Rohit. + */ +public final class UCEHandler { + static final String EXTRA_STACK_TRACE = "EXTRA_STACK_TRACE"; + static final String EXTRA_ACTIVITY_LOG = "EXTRA_ACTIVITY_LOG"; + static final String EXTRA_EMAIL_ADDRESSES = "EXTRA_EMAIL_ADDRESSES"; + private final static String TAG = "UCEHandler"; + private static final String UCE_HANDLER_PACKAGE_NAME = "com.rohitss.uceh"; + private static final String DEFAULT_HANDLER_PACKAGE_NAME = "com.android.internal.os"; + private static final int MAX_STACK_TRACE_SIZE = 131071; //128 KB - 1 + private static final int MAX_ACTIVITIES_IN_LOG = 50; + private static final String SHARED_PREFERENCES_FILE = "uceh_preferences"; + private static final String SHARED_PREFERENCES_FIELD_TIMESTAMP = "last_crash_timestamp"; + private static final Deque activityLog = new ArrayDeque<>(MAX_ACTIVITIES_IN_LOG); + static String COMMA_SEPARATED_EMAIL_ADDRESSES; + @SuppressLint("StaticFieldLeak") + private static Application application; + private static boolean isInBackground = true; + private static boolean isBackgroundMode; + private static boolean isUCEHEnabled; + private static boolean isTrackActivitiesEnabled; + private static WeakReference lastActivityCreated = new WeakReference<>(null); + + UCEHandler(Builder builder) { + isUCEHEnabled = builder.isUCEHEnabled; + isTrackActivitiesEnabled = builder.isTrackActivitiesEnabled; + isBackgroundMode = builder.isBackgroundModeEnabled; + COMMA_SEPARATED_EMAIL_ADDRESSES = builder.commaSeparatedEmailAddresses; + Log.d(TAG,"UCEHandler() - Email Addresses = "+COMMA_SEPARATED_EMAIL_ADDRESSES+"."); + Log.d(TAG,"UCEHandler() - UCEHandler.Email Addresses = "+UCEHandler.COMMA_SEPARATED_EMAIL_ADDRESSES+"."); + setUCEHandler(builder.context); + } + + private static void setUCEHandler(final Context context) { + Log.i(TAG,"setUCEHandler() - email addresses = "+UCEHandler.COMMA_SEPARATED_EMAIL_ADDRESSES+"."); + try { + if (context != null) { + final Thread.UncaughtExceptionHandler oldHandler = Thread.getDefaultUncaughtExceptionHandler(); + if (oldHandler != null && oldHandler.getClass().getName().startsWith(UCE_HANDLER_PACKAGE_NAME)) { + Log.e(TAG, "UCEHandler was already installed, doing nothing!"); + } else { + if (oldHandler != null && !oldHandler.getClass().getName().startsWith(DEFAULT_HANDLER_PACKAGE_NAME)) { + Log.e(TAG, "You already have an UncaughtExceptionHandler. If you use a custom UncaughtExceptionHandler, it should be initialized after UCEHandler! Installing anyway, but your original handler will not be called."); + } + application = (Application) context.getApplicationContext(); + //Setup UCE Handler. + Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { + @Override + public void uncaughtException(Thread thread, final Throwable throwable) { + if (isUCEHEnabled) { + Log.e(TAG, "App crashed, executing UCEHandler's UncaughtExceptionHandler", throwable); + if (hasCrashedInTheLastSeconds(application)) { + Log.e(TAG, "App already crashed recently, not starting custom error activity because we could enter a restart loop. Are you sure that your app does not crash directly on init?", throwable); + if (oldHandler != null) { + oldHandler.uncaughtException(thread, throwable); + return; + } + } else { + setLastCrashTimestamp(application, new Date().getTime()); + if (!isInBackground || isBackgroundMode) { + Log.d(TAG,"Preparing Intent"); + final Intent intent = new Intent(application, UCEDefaultActivity.class); + intent.putExtra(EXTRA_EMAIL_ADDRESSES, COMMA_SEPARATED_EMAIL_ADDRESSES); + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + throwable.printStackTrace(pw); + String stackTraceString = sw.toString(); + if (stackTraceString.length() > MAX_STACK_TRACE_SIZE) { + String disclaimer = " [stack trace too large]"; + stackTraceString = stackTraceString.substring(0, MAX_STACK_TRACE_SIZE - disclaimer.length()) + disclaimer; + } + intent.putExtra(EXTRA_STACK_TRACE, stackTraceString); + if (isTrackActivitiesEnabled) { + StringBuilder activityLogStringBuilder = new StringBuilder(); + while (!activityLog.isEmpty()) { + activityLogStringBuilder.append(activityLog.poll()); + } + intent.putExtra(EXTRA_ACTIVITY_LOG, activityLogStringBuilder.toString()); + } + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + Log.d(TAG,"calling startActivity()"); + application.startActivity(intent); + } else { + Log.d(TAG,"using oldHandler"); + if (oldHandler != null) { + oldHandler.uncaughtException(thread, throwable); + return; + } + //If it is null (should not be), we let it continue and kill the process or it will be stuck + } + } + final Activity lastActivity = lastActivityCreated.get(); + if (lastActivity != null) { + lastActivity.finish(); + lastActivityCreated.clear(); + } + killCurrentProcess(); + } else if (oldHandler != null) { + //Pass control to old uncaught exception handler + oldHandler.uncaughtException(thread, throwable); + } + } + }); + application.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() { + final DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US); + int currentlyStartedActivities = 0; + + @Override + public void onActivityCreated(Activity activity, Bundle savedInstanceState) { + if (activity.getClass() != UCEDefaultActivity.class) { + lastActivityCreated = new WeakReference<>(activity); + } + if (isTrackActivitiesEnabled) { + activityLog.add(dateFormat.format(new Date()) + ": " + activity.getClass().getSimpleName() + " created\n"); + } + } + + @Override + public void onActivityStarted(Activity activity) { + currentlyStartedActivities++; + isInBackground = (currentlyStartedActivities == 0); + } + + @Override + public void onActivityResumed(Activity activity) { + if (isTrackActivitiesEnabled) { + activityLog.add(dateFormat.format(new Date()) + ": " + activity.getClass().getSimpleName() + " resumed\n"); + } + } + + @Override + public void onActivityPaused(Activity activity) { + if (isTrackActivitiesEnabled) { + activityLog.add(dateFormat.format(new Date()) + ": " + activity.getClass().getSimpleName() + " paused\n"); + } + } + + @Override + public void onActivityStopped(Activity activity) { + currentlyStartedActivities--; + isInBackground = (currentlyStartedActivities == 0); + } + + @Override + public void onActivitySaveInstanceState(Activity activity, Bundle outState) { + } + + @Override + public void onActivityDestroyed(Activity activity) { + if (isTrackActivitiesEnabled) { + activityLog.add(dateFormat.format(new Date()) + ": " + activity.getClass().getSimpleName() + " destroyed\n"); + } + } + }); + } + Log.i(TAG, "UCEHandler has been installed."); + } else { + Log.e(TAG, "Context can not be null"); + } + } catch (Throwable throwable) { + Log.e(TAG, "UCEHandler can not be initialized. Help making it better by reporting this as a bug.", throwable); + } + } + + /** + * INTERNAL method that tells if the app has crashed in the last seconds. + * This is used to avoid restart loops. + * + * @return true if the app has crashed in the last seconds, false otherwise. + */ + private static boolean hasCrashedInTheLastSeconds(Context context) { + long lastTimestamp = getLastCrashTimestamp(context); + long currentTimestamp = new Date().getTime(); + return (lastTimestamp <= currentTimestamp && currentTimestamp - lastTimestamp < 3000); + } + + @SuppressLint("ApplySharedPref") + private static void setLastCrashTimestamp(Context context, long timestamp) { + context.getSharedPreferences(SHARED_PREFERENCES_FILE, Context.MODE_PRIVATE).edit().putLong(SHARED_PREFERENCES_FIELD_TIMESTAMP, timestamp).commit(); + } + + private static void killCurrentProcess() { + android.os.Process.killProcess(android.os.Process.myPid()); + System.exit(10); + } + + private static long getLastCrashTimestamp(Context context) { + return context.getSharedPreferences(SHARED_PREFERENCES_FILE, Context.MODE_PRIVATE).getLong(SHARED_PREFERENCES_FIELD_TIMESTAMP, -1); + } + + static void closeApplication(Activity activity) { + activity.finish(); + killCurrentProcess(); + } + + public static class Builder { + private Context context; + private boolean isUCEHEnabled = true; + private String commaSeparatedEmailAddresses; + private boolean isTrackActivitiesEnabled = false; + private boolean isBackgroundModeEnabled = true; + + public Builder(Context context) { + this.context = context; + } + + public Builder setUCEHEnabled(boolean isUCEHEnabled) { + this.isUCEHEnabled = isUCEHEnabled; + return this; + } + + public Builder setTrackActivitiesEnabled(boolean isTrackActivitiesEnabled) { + this.isTrackActivitiesEnabled = isTrackActivitiesEnabled; + return this; + } + + public Builder setBackgroundModeEnabled(boolean isBackgroundModeEnabled) { + this.isBackgroundModeEnabled = isBackgroundModeEnabled; + return this; + } + + public Builder addCommaSeparatedEmailAddresses(String commaSeparatedEmailAddresses) { + this.commaSeparatedEmailAddresses = (commaSeparatedEmailAddresses != null) ? commaSeparatedEmailAddresses : ""; + return this; + } + + public UCEHandler build() { + return new UCEHandler(this); + } + } +} diff --git a/app/src/main/java/uk/org/openseizuredetector/MainActivity.java b/app/src/main/java/uk/org/openseizuredetector/MainActivity.java index 7f07f08..21a00ac 100644 --- a/app/src/main/java/uk/org/openseizuredetector/MainActivity.java +++ b/app/src/main/java/uk/org/openseizuredetector/MainActivity.java @@ -61,6 +61,7 @@ import com.github.mikephil.charting.data.BarData; import com.github.mikephil.charting.data.BarDataSet; import com.github.mikephil.charting.data.BarEntry; import com.github.mikephil.charting.utils.ValueFormatter; +import com.rohitss.uceh.UCEHandler; public class MainActivity extends AppCompatActivity { static final String TAG = "MainActivity"; @@ -89,7 +90,11 @@ public class MainActivity extends AppCompatActivity { Log.i(TAG,"onCreate()"); // Set our custom uncaught exception handler to report issues. - Thread.setDefaultUncaughtExceptionHandler(new OsdUncaughtExceptionHandler(MainActivity.this)); + //Thread.setDefaultUncaughtExceptionHandler(new OsdUncaughtExceptionHandler(MainActivity.this)); + new UCEHandler.Builder(this) + .addCommaSeparatedEmailAddresses("crashreports@openseizuredetector.org.uk,") + .build(); + //int i = 5/0; // Force exception to test handler. mUtil = new OsdUtil(this,serverStatusHandler); mConnection = new SdServiceConnection(this); diff --git a/app/src/main/java/uk/org/openseizuredetector/OsdUncaughtExceptionHandler.java b/app/src/main/java/uk/org/openseizuredetector/OsdUncaughtExceptionHandler.java index 3834a92..1a5f866 100644 --- a/app/src/main/java/uk/org/openseizuredetector/OsdUncaughtExceptionHandler.java +++ b/app/src/main/java/uk/org/openseizuredetector/OsdUncaughtExceptionHandler.java @@ -163,8 +163,8 @@ public class OsdUncaughtExceptionHandler implements Thread.UncaughtExceptionHand "You can review the information being sent in the next screen:"+ "\n"+errorContent.toString()); Dialog dialog = builder.create(); - dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); - //dialog.show(); + //dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT); + dialog.show(); Looper.loop(); } }.start(); diff --git a/app/src/main/java/uk/org/openseizuredetector/OsdUtil.java b/app/src/main/java/uk/org/openseizuredetector/OsdUtil.java index ccfcf27..289ff6b 100644 --- a/app/src/main/java/uk/org/openseizuredetector/OsdUtil.java +++ b/app/src/main/java/uk/org/openseizuredetector/OsdUtil.java @@ -91,6 +91,9 @@ public class OsdUtil implements ActivityCompat.OnRequestPermissionsResultCallbac private final String[] REQUIRED_PERMISSIONS = { Manifest.permission.SEND_SMS, Manifest.permission.WRITE_EXTERNAL_STORAGE, + //Manifest.permission.SYSTEM_ALERT_WINDOW, + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.WAKE_LOCK, }; /** @@ -342,24 +345,30 @@ public class OsdUtil implements ActivityCompat.OnRequestPermissionsResultCallbac fname = fname + "_" + dateStr + ".txt"; // Open output directory on SD Card. - if (isExternalStorageWritable()) { - try { - FileWriter of = new FileWriter(getDataStorageDir().toString() - + "/" + fname, true); - if (msgStr != null) { - String dateTimeStr = tnow.format("%Y-%m-%d %H:%M:%S"); - Log.v(TAG, "writing msgStr"); - of.append(dateTimeStr+", " - +tnow.toMillis(true)+", " - +msgStr+"
\n"); - } - of.close(); - } catch (Exception ex) { - Log.e(TAG, "writeToLogFile - error " + ex.toString()); - showToast("ERROR Writing to Log File"); - } + if (ContextCompat.checkSelfPermission(mContext, + Manifest.permission.WRITE_EXTERNAL_STORAGE) + != PackageManager.PERMISSION_GRANTED) { + Log.e(TAG,"ERROR: We do not have permission to write to external storage"); } else { - Log.e(TAG, "ERROR - Can not Write to External Folder"); + if (isExternalStorageWritable()) { + try { + FileWriter of = new FileWriter(getDataStorageDir().toString() + + "/" + fname, true); + if (msgStr != null) { + String dateTimeStr = tnow.format("%Y-%m-%d %H:%M:%S"); + Log.v(TAG, "writing msgStr"); + of.append(dateTimeStr + ", " + + tnow.toMillis(true) + ", " + + msgStr + "
\n"); + } + of.close(); + } catch (Exception ex) { + Log.e(TAG, "writeToLogFile - error " + ex.toString()); + showToast("ERROR Writing to Log File"); + } + } else { + Log.e(TAG, "ERROR - Can not Write to External Folder"); + } } } diff --git a/app/src/main/java/uk/org/openseizuredetector/SdDataSourceGarmin.java b/app/src/main/java/uk/org/openseizuredetector/SdDataSourceGarmin.java index 90e3eb4..f3fe8f3 100644 --- a/app/src/main/java/uk/org/openseizuredetector/SdDataSourceGarmin.java +++ b/app/src/main/java/uk/org/openseizuredetector/SdDataSourceGarmin.java @@ -40,6 +40,7 @@ import com.getpebble.android.kit.PebbleKit; import com.getpebble.android.kit.util.PebbleDictionary; import org.json.JSONArray; +import org.json.JSONException; import org.json.JSONObject; import org.jtransforms.fft.DoubleFFT_1D; @@ -373,7 +374,12 @@ public class SdDataSourceGarmin extends SdDataSource { Log.v(TAG,"updateFromJSON - dataType="+dataTypeStr); if (dataTypeStr.equals("raw")) { Log.v(TAG,"updateFromJSON - processing raw data"); - mSdData.mHR = dataObject.getDouble("HR"); + try { + mSdData.mHR = dataObject.getDouble("HR"); + } catch (JSONException e) { + // if we get 'null' HR (For example if the heart rate is not working) + mSdData.mHR = -1; + } JSONArray accelVals = dataObject.getJSONArray("data"); Log.v(TAG, "Received " + accelVals.length() + " acceleration values"); int i; diff --git a/app/src/main/java/uk/org/openseizuredetector/SdServer.java b/app/src/main/java/uk/org/openseizuredetector/SdServer.java index 0255b8d..82321b0 100644 --- a/app/src/main/java/uk/org/openseizuredetector/SdServer.java +++ b/app/src/main/java/uk/org/openseizuredetector/SdServer.java @@ -68,6 +68,8 @@ import java.util.*; import android.text.format.Time; +import com.rohitss.uceh.UCEHandler; + /** * Based on example at: @@ -169,15 +171,18 @@ public class SdServer extends Service implements SdDataReceiver, SdLocationRecei mUtil.writeToSysLogFile("SdServer.onCreate()"); // Set our custom uncaught exception handler to report issues. - Thread.setDefaultUncaughtExceptionHandler( - new OsdUncaughtExceptionHandler(SdServer.this)); + //Thread.setDefaultUncaughtExceptionHandler( + // new OsdUncaughtExceptionHandler(SdServer.this)); + new UCEHandler.Builder(this) + .addCommaSeparatedEmailAddresses("crashreports@openseizuredetector.org.uk,") + .build(); //int i = 5/0; // Force exception to test handler. // Create a wake lock, but don't use it until the service is started. PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE); mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, - "MyWakelockTag"); + "OSD:WakeLock"); } /** diff --git a/app/src/main/java/uk/org/openseizuredetector/StartupActivity.java b/app/src/main/java/uk/org/openseizuredetector/StartupActivity.java index 5654667..44e6cbd 100644 --- a/app/src/main/java/uk/org/openseizuredetector/StartupActivity.java +++ b/app/src/main/java/uk/org/openseizuredetector/StartupActivity.java @@ -49,6 +49,8 @@ import android.widget.Button; import android.widget.ProgressBar; import android.widget.TextView; +import com.rohitss.uceh.UCEHandler; + import java.util.Timer; import java.util.TimerTask; @@ -82,7 +84,11 @@ public class StartupActivity extends Activity { Log.i(TAG,"onCreate()"); // Set our custom uncaught exception handler to report issues. - Thread.setDefaultUncaughtExceptionHandler(new OsdUncaughtExceptionHandler(StartupActivity.this)); + //Thread.setDefaultUncaughtExceptionHandler(new OsdUncaughtExceptionHandler(StartupActivity.this)); + new UCEHandler.Builder(this) + .addCommaSeparatedEmailAddresses("crashreports@openseizuredetector.org.uk,") + .build(); + mHandler = new Handler(); mUtil = new OsdUtil(this, mHandler); @@ -103,6 +109,10 @@ public class StartupActivity extends Activity { PreferenceManager.setDefaultValues(this, R.xml.camera_prefs, true); PreferenceManager.setDefaultValues(this, R.xml.general_prefs, true); PreferenceManager.setDefaultValues(this, R.xml.network_datasource_prefs, true); + PreferenceManager.setDefaultValues(this, R.xml.pebble_datasource_prefs, true); + PreferenceManager.setDefaultValues(this, R.xml.garmin_datasource_prefs, true); + PreferenceManager.setDefaultValues(this, R.xml.seizure_detector_prefs, true); + PreferenceManager.setDefaultValues(this, R.xml.network_passive_datasource_prefs, true); Button b; @@ -174,6 +184,14 @@ public class StartupActivity extends Activity { mUtil.writeToSysLogFile("StartupActivity.onStart()"); TextView tv; + if (mUtil.arePermissionsOK()) { + Log.i(TAG,"onStart() - Permissions OK"); + } else { + Log.i(TAG,"onStart() - Permissions Not OK - requesting them"); + mUtil.requestPermissions(this); + } + + String versionName = mUtil.getAppVersionName(); tv = (TextView) findViewById(R.id.appNameTv); tv.setText("OpenSeizureDetector V" + versionName); @@ -198,6 +216,8 @@ public class StartupActivity extends Activity { mUsingPebbleDataSource = true; } + + if (mUtil.isServerRunning()) { Log.i(TAG, "onStart() - server running - stopping it"); mUtil.writeToSysLogFile("StartupActivity.onStart() - server already running - stopping it."); diff --git a/app/src/main/res/layout/default_error_activity.xml b/app/src/main/res/layout/default_error_activity.xml new file mode 100644 index 0000000..be73d5d --- /dev/null +++ b/app/src/main/res/layout/default_error_activity.xml @@ -0,0 +1,89 @@ + + + + + + + + + +