From dc648096a09f3cbe763cf60ab39f1b64903991dc Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Fri, 2 Feb 2018 11:05:09 +0100 Subject: [PATCH 1/2] Enable the configuration of the uncaught exception handler. --- .../android/logger/api/LogPersister.java | 69 +++++++++++++++---- 1 file changed, 54 insertions(+), 15 deletions(-) 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..d92193b 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; @@ -153,6 +152,10 @@ public final class LogPersister { * @exclude */ public static final String SHARED_PREF_KEY_logPersistence = "logPersistence"; + /** + * @exclude + */ + public static final String SHARED_PREF_KEY_installUncaughtExceptionHandler = "installUncaughtExceptionHandler"; /** * @exclude */ @@ -173,6 +176,10 @@ public final class LogPersister { * @exclude */ public static final boolean DEFAULT_capture = true; // capture is on by default + /** + * @exclude + */ + public static final boolean DEFAULT_installUncaughtExceptionHandler = true; /** * @exclude */ @@ -192,10 +199,9 @@ public final class LogPersister { // size when we stop accumulating data in the file: private static Integer logFileMaxSize = null; // bytes private static Logger.LEVEL level = null; + private static Boolean installUncaughtExceptionHandler = 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 +246,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,7 +290,7 @@ protected static void unsetContext() { analyticsCapture = null; logFileMaxSize = null; level = null; - uncaughtExceptionHandler = null; + installUncaughtExceptionHandler = null; fileLoggerInstance = null; LogManager.getLogManager().getLogger("").removeHandler(julHandler); } @@ -323,8 +340,11 @@ static public void setContext(final Context context) { setCaptureSync(prefs.getBoolean (SHARED_PREF_KEY_logPersistence, DEFAULT_capture)); } - uncaughtExceptionHandler = new UncaughtExceptionHandler (); - Thread.setDefaultUncaughtExceptionHandler (uncaughtExceptionHandler); + if (null != installUncaughtExceptionHandler) { + installUncaughtExceptionHandler(installUncaughtExceptionHandler); + } else { + installUncaughtExceptionHandler(prefs.getBoolean(SHARED_PREF_KEY_installUncaughtExceptionHandler, DEFAULT_installUncaughtExceptionHandler)); + } } } @@ -412,6 +432,25 @@ static public void storeLogs(final boolean shouldStoreLogs) { }); } + static synchronized public void installUncaughtExceptionHandler(final boolean install) { + LogPersister.installUncaughtExceptionHandler = install; + // The uncaught exception handler needs the context to operate properly, so + // we postpone its configuration until this is set + if (null == context) return; + + SharedPreferences prefs = context.getSharedPreferences(SHARED_PREF_KEY, Context.MODE_PRIVATE); + prefs.edit().putBoolean(SHARED_PREF_KEY_installUncaughtExceptionHandler, LogPersister.capture).apply(); + + 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; From 68951b6b995b4febbe9f5fb96a3e3af01d98be4e Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Mon, 19 Mar 2018 09:16:48 +0100 Subject: [PATCH 2/2] Review comments. --- .../android/logger/api/LogPersister.java | 31 +++++++------------ 1 file changed, 11 insertions(+), 20 deletions(-) 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 d92193b..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 @@ -152,10 +152,6 @@ public final class LogPersister { * @exclude */ public static final String SHARED_PREF_KEY_logPersistence = "logPersistence"; - /** - * @exclude - */ - public static final String SHARED_PREF_KEY_installUncaughtExceptionHandler = "installUncaughtExceptionHandler"; /** * @exclude */ @@ -176,10 +172,6 @@ public final class LogPersister { * @exclude */ public static final boolean DEFAULT_capture = true; // capture is on by default - /** - * @exclude - */ - public static final boolean DEFAULT_installUncaughtExceptionHandler = true; /** * @exclude */ @@ -199,7 +191,6 @@ public final class LogPersister { // size when we stop accumulating data in the file: private static Integer logFileMaxSize = null; // bytes private static Logger.LEVEL level = null; - private static Boolean installUncaughtExceptionHandler = null; // we keep a global java.util.logging.Handler to capture third-party stuff: private static JULHandler julHandler = new JULHandler(); // Track instances so we give back the same one for the same logger name passed to getInstance method. @@ -290,8 +281,8 @@ protected static void unsetContext() { analyticsCapture = null; logFileMaxSize = null; level = null; - installUncaughtExceptionHandler = null; fileLoggerInstance = null; + installUncaughtExceptionHandler(false); LogManager.getLogManager().getLogger("").removeHandler(julHandler); } @@ -340,11 +331,7 @@ static public void setContext(final Context context) { setCaptureSync(prefs.getBoolean (SHARED_PREF_KEY_logPersistence, DEFAULT_capture)); } - if (null != installUncaughtExceptionHandler) { - installUncaughtExceptionHandler(installUncaughtExceptionHandler); - } else { - installUncaughtExceptionHandler(prefs.getBoolean(SHARED_PREF_KEY_installUncaughtExceptionHandler, DEFAULT_installUncaughtExceptionHandler)); - } + installUncaughtExceptionHandler(true); } } @@ -432,15 +419,19 @@ static public void storeLogs(final boolean shouldStoreLogs) { }); } - static synchronized public void installUncaughtExceptionHandler(final boolean install) { - LogPersister.installUncaughtExceptionHandler = install; + /** + * 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; - SharedPreferences prefs = context.getSharedPreferences(SHARED_PREF_KEY, Context.MODE_PRIVATE); - prefs.edit().putBoolean(SHARED_PREF_KEY_installUncaughtExceptionHandler, LogPersister.capture).apply(); - Thread.UncaughtExceptionHandler current = Thread.getDefaultUncaughtExceptionHandler(); if (install && !(current instanceof UncaughtExceptionHandler)) { UncaughtExceptionHandler wrapped = new UncaughtExceptionHandler(current);