diff --git a/lib/src/main/java/com/ibm/mobilefirstplatform/clientsdk/android/logger/api/LogPersister.java b/lib/src/main/java/com/ibm/mobilefirstplatform/clientsdk/android/logger/api/LogPersister.java index e7dfefe..08856a9 100644 --- a/lib/src/main/java/com/ibm/mobilefirstplatform/clientsdk/android/logger/api/LogPersister.java +++ b/lib/src/main/java/com/ibm/mobilefirstplatform/clientsdk/android/logger/api/LogPersister.java @@ -17,6 +17,7 @@ import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.pm.Signature; +import android.support.annotation.Nullable; import android.util.Log; import com.ibm.mobilefirstplatform.clientsdk.android.analytics.internal.BMSAnalytics; @@ -39,8 +40,6 @@ import java.io.UnsupportedEncodingException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; -import java.text.SimpleDateFormat; -import java.util.TimeZone; import java.util.WeakHashMap; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.Callable; @@ -194,8 +193,6 @@ public final class LogPersister { private static Logger.LEVEL level = null; // we keep a global java.util.logging.Handler to capture third-party stuff: private static JULHandler julHandler = new JULHandler(); - // don't set up the static UncaughtExceptionHandler until after we have a context - private static UncaughtExceptionHandler uncaughtExceptionHandler = null; // Track instances so we give back the same one for the same logger name passed to getInstance method. // We use a WeakHashMap because some instances in this map may go out of scope // very soon after instantiation, thus no reason to keep a strong reference, and let @@ -240,25 +237,36 @@ public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { static private FileLoggerInterface fileLoggerInstance; // log application crash stack traces. This handler is registered after we have the context object in Logger.setContext(Context) - private static class UncaughtExceptionHandler implements Thread.UncaughtExceptionHandler - { - private final Thread.UncaughtExceptionHandler defaultUEH = Thread.getDefaultUncaughtExceptionHandler(); + private static class UncaughtExceptionHandler implements Thread.UncaughtExceptionHandler { + + @Nullable + private Thread.UncaughtExceptionHandler original; + + public UncaughtExceptionHandler(@Nullable Thread.UncaughtExceptionHandler original) { + this.original = original; + } + + @Nullable + public Thread.UncaughtExceptionHandler getOriginal() { + return original; + } @Override - public final void uncaughtException(final Thread t, final Throwable e) - { + public final void uncaughtException(final Thread t, final Throwable e) { // place a marker that indicates for next run that a crash was caught: if (null != context) { - context.getSharedPreferences (SHARED_PREF_KEY, Context.MODE_PRIVATE).edit ().putBoolean (SHARED_PREF_KEY_CRASH_DETECTED, true).commit(); + context.getSharedPreferences(SHARED_PREF_KEY, Context.MODE_PRIVATE).edit().putBoolean(SHARED_PREF_KEY_CRASH_DETECTED, true).commit(); } // log it to file: Logger logger = Logger.getLogger(this.getClass().getName()); - logger.fatal ("Uncaught Exception", e); + logger.fatal("Uncaught Exception", e); MFPAnalyticsActivityLifecycleListener.getInstance().logAppCrash(); // allow it to pass through: - defaultUEH.uncaughtException(t, e); + if (original != null) { + original.uncaughtException(t, e); + } } } @@ -273,8 +281,8 @@ protected static void unsetContext() { analyticsCapture = null; logFileMaxSize = null; level = null; - uncaughtExceptionHandler = null; fileLoggerInstance = null; + installUncaughtExceptionHandler(false); LogManager.getLogManager().getLogger("").removeHandler(julHandler); } @@ -323,8 +331,7 @@ static public void setContext(final Context context) { setCaptureSync(prefs.getBoolean (SHARED_PREF_KEY_logPersistence, DEFAULT_capture)); } - uncaughtExceptionHandler = new UncaughtExceptionHandler (); - Thread.setDefaultUncaughtExceptionHandler (uncaughtExceptionHandler); + installUncaughtExceptionHandler(true); } } @@ -412,6 +419,29 @@ static public void storeLogs(final boolean shouldStoreLogs) { }); } + /** + * Disables the internal uncaught exception handler, so uncaught exceptions are not reported + * to the Analytics backend. + */ + static public void disableUncaughtExceptionHandler() { + installUncaughtExceptionHandler(false); + } + + static synchronized private void installUncaughtExceptionHandler(final boolean install) { + // The uncaught exception handler needs the context to operate properly, so + // we postpone its configuration until this is set + if (null == context) return; + + Thread.UncaughtExceptionHandler current = Thread.getDefaultUncaughtExceptionHandler(); + if (install && !(current instanceof UncaughtExceptionHandler)) { + UncaughtExceptionHandler wrapped = new UncaughtExceptionHandler(current); + Thread.setDefaultUncaughtExceptionHandler(wrapped); + } else if (!install && (current instanceof UncaughtExceptionHandler)) { + Thread.UncaughtExceptionHandler original = ((UncaughtExceptionHandler) current).getOriginal(); + Thread.setDefaultUncaughtExceptionHandler(original); + } + } + static synchronized private void setCaptureSync(final boolean capture) { // to avoid thread deadlocks, we have this method that can be called within a thread that is already on the work queue LogPersister.capture = capture;