Commits

Hal Blackburn committed 8ced340

Moved code into io.h4l.android.crashlogging namespace. Fixed license comments mangled by tool meant to automate the process...

Comments (0)

Files changed (35)

Design

-- Fail fast in setup
-- avoid failing after setup at runtime
-
-boolean CrashLogging.isActivated()
-
-
-LogHandler crashLogHandler = HttpPutLogHandler
-	.whichPostsLogsTo("http://cl.example.com/product");
-
-	.withTheLogElements(
-	.withLogElement(ExceptionLogElement)
-		.and(...)
-		.and(...)
-
-CrashLog.usingTheElements(foo, biz, baz)
-CrashLog.usingTheElement(foo)
-
-CrashLogs.setupACrashLogger()
-	.whichLogsDataProvidedByA(
-		CrashLog.usingTheElements(
-			foo,
-			biz,
-			baz))
-	.whichAponCrashingUsesA(
-		StartActivityAction.whichStartsActivity(MyCrashHandler.class)
-			.handlingLogsUsing(
-				HttpPutLogHandler
-					.whichPostsLogsTo("http://cl.example.com/product")))
-	//
-	.whichAponCrashingUsesA(
-		SupressForceCloseAction.which
-	.activateCrashLogging();
-
-
-
-CrashLogging.configure()
-	.addLogElement(...)
-	.addCrashHandler(...)
-	.apply();
-
-Crash Action:
- - Standard force close
- - Send Broadcast // default - should happen regardless
- - Start Activity
- - Provided user confirmation of log sending - special case of start activity
- UserNotifier.withHandler(HttpPut...)
- - None
-
-Elements:
- - Stack Trace
- - Package Information (version etc)
- - Time
- - 
- 
-Log Handler
- - HTTP Put
- - Email
- - File Handler

src/hal/android/crashlogging/BaseCrashAction.java

-/*******************************************************************************
- * Copyright 2011 Hal Blackburn <hal.blackburn@gmail.com>
- * 
- * 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.
- ******************************************************************************/
-/*
- * Copyright (c) 2010, Hal Blackburn
- * 
- * Permission to use, copy, modify, and/or distribute this software for any 
- * purpose with or without fee is hereby granted, provided that the above 
- * copyright notice and this permission notice appear in all copies.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 
- * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 
- * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 
- * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 
- * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 
- * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 
- * PERFORMANCE OF THIS SOFTWARE.
- */
-package hal.android.crashlogging;
-
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-
-import org.json.JSONObject;
-
-/**
- * The typical base class of {@link CrashAction}s. You will probably want to
- * subclass this rather than implementing {@link CrashAction} directly. 
- * <p>
- * It maintains an unmodifiable list of {@link LogHandler}s which can be used to 
- * handle the logs passed into the {@link CrashAction#onCrash}
- * method.
- * 
- * @author Hal
- */
-public abstract class BaseCrashAction implements CrashAction
-{
-	/** The handlers we can use to handle crash logs */
-	private final List<LogHandler> mLogHandlers;
-	
-	/**
-	 * Constructs the BaseLogAction with the provided handlers. These handlers 
-	 * are passed a log when {@link #handleLog(JSONObject)} is called.
-	 * 
-	 * @param logHandlers The log handlers to use to handle crash logs.
-	 * @throws NullPointerException 
-	 *         if logHandlers is null. Pass an empty collection if that is the 
-	 *         intent.
-	 */
-	public BaseCrashAction(Collection<LogHandler> logHandlers)
-	{
-		if(logHandlers == null)
-			throw new NullPointerException("logHandlers was null");
-		
-		mLogHandlers = Collections.unmodifiableList(
-				new ArrayList<LogHandler>(logHandlers));
-	}
-	
-	/**
-	 * @return an unmodifiable list containing our known log handlers.
-	 */
-	protected List<LogHandler> getLogHandlers()
-	{
-		return mLogHandlers;
-	}
-	
-	/**
-	 * Sends the specified crash log to each of the log handlers in our list of 
-	 * handlers. The list can be accessed manually via 
-	 * {@link #getLogHandlers()}.
-	 * 
-	 * @param crashLog 
-	 *        the crash log to send to each of our log handlers in turn.
-	 */
-	protected void handleLog(JSONObject crashLog)
-	{
-		final String compiledLog = crashLog.toString();
-		
-		for(LogHandler handler : mLogHandlers)
-			handler.handleLog(compiledLog, crashLog);
-	}
-}

src/hal/android/crashlogging/CrashAction.java

-/*******************************************************************************
- * Copyright 2011 Hal Blackburn <hal.blackburn@gmail.com>
- * 
- * 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.
- ******************************************************************************/
-/*
- * Copyright (c) 2010, Hal Blackburn
- * 
- * Permission to use, copy, modify, and/or distribute this software for any 
- * purpose with or without fee is hereby granted, provided that the above 
- * copyright notice and this permission notice appear in all copies.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 
- * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 
- * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 
- * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 
- * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 
- * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 
- * PERFORMANCE OF THIS SOFTWARE.
- */
-package hal.android.crashlogging;
-
-
-import java.lang.Thread.UncaughtExceptionHandler;
-
-import org.json.JSONObject;
-
-import android.content.Context;
-
-/**
- * Represents an action which is performed if a crash is detected. A typical 
- * {@code CrashAction} implementation will pass the crash log onto one or more
- * log handlers, then terminate the virtual machine.
- * <p>
- * {@link CrashActions} provides static methods to create common 
- * {@link CrashAction} implementations.
- * <p>
- * If your wanting to create your own crash action, it will probably be less 
- * work to subclass {@link BaseCrashAction} rather than implementing this 
- * interface directly (assuming you want to use {@link LogHandler}s to handle 
- * the crash log).
- * 
- * @see CrashActions
- * @author Hal
- */
-public interface CrashAction
-{
-	/**
-	 * Called when an exception is unhandled, causing the app to crash. Actions 
-	 * should implement this method to perform whatever action is required, for 
-	 * example sending the log to one of it's log handlers.
-	 * <p>
-	 * Implementations need not return normally from this call, for example it's
-	 * fine to call {@link System#exit(int)} inside this method.
-	 * 
-	 * @param crashedThread the thread in which the crash occurred
-	 * @param uncaughtException 
-	 *        the exception which the {@code crashedThread} failed to handle
-	 * @param parentHandler 
-	 *        The original {@link UncaughtExceptionHandler} which was set before 
-	 *        our handler was set. Typically this is the Android handler which
-	 *        displays the 'force close' dialog when its 
-	 *        {@link UncaughtExceptionHandler#uncaughtException(Thread, Throwable)}
-	 *        method is called. It is however possible that it will be someone 
-	 *        else's handler
-	 * @param crashlog 
-	 *        The result of aggregating the {@link LogSection}s crash logging 
-	 *        was configured to use.
-	 * @param applicationContext The context crash logging was configured in.
-	 */
-	void onCrash(Thread crashedThread,
-				 Throwable uncaughtException,
-				 UncaughtExceptionHandler parentHandler,
-				 JSONObject crashlog,
-				 Context applicationContext);
-}

src/hal/android/crashlogging/CrashActions.java

-/*******************************************************************************
- * Copyright 2011 Hal Blackburn <hal.blackburn@gmail.com>
- * 
- * 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.
- ******************************************************************************/
-/*
- * Copyright (c) 2010, Hal Blackburn
- * 
- * Permission to use, copy, modify, and/or distribute this software for any 
- * purpose with or without fee is hereby granted, provided that the above 
- * copyright notice and this permission notice appear in all copies.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 
- * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 
- * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 
- * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 
- * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 
- * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 
- * PERFORMANCE OF THIS SOFTWARE.
- */
-package hal.android.crashlogging;
-
-import java.lang.Thread.UncaughtExceptionHandler;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-
-import org.json.JSONObject;
-
-import android.content.Context;
-
-
-/**
- * Utility functions related to {@code CrashAction}s.
- * 
- * @see CrashAction
- * @author Hal
- */
-public final class CrashActions
-{
-	/**
-	 * Creates a {@link CrashAction} which delegates to the parent 
-	 * {@link UncaughtExceptionHandler} (passed to {@link CrashAction#onCrash}) 
-	 * after handling the logs with its list of {@link LogHandler}s.
-	 * <p>
-	 * Unless explicitly changed before setting up crash logging, the parent
-	 * exception handler we delegate to will be Android's uncaught exception 
-	 * handler which shows the 'force close' dialog when it's notified of an 
-	 * uncaught exception.
-	 * 
-	 * @param handlers 
-	 *        the log handlers to pass the log to before 'force close'ing
-	 * @return a {@link CrashAction} with the described behaviour.
-	 */
-	public static CrashAction newForceCloseAction(Collection<LogHandler> handlers)
-	{
-		return new ForceCloseAction(handlers);
-	}
-	
-	/**
-	 * Creates a {@link CrashAction} which after handling the crashlog with the 
-	 * provided {@link LogHandler}s, calls {@link System#exit(int)}, terminating the
-	 * process the app is running in.
-	 * 
-	 * @param logHandlers the handlers who will receive the log if we crash.
-	 * @param exitCode 
-	 *        The value to pass into System.exit(int). Because we're crashing if 
-	 *        it gets called you should probably use a non zero value (an exit 
-	 *        code of 0 indicates success for UNIX processes).
-	 */
-	public static CrashAction newSuppressForceCloseCrashAction(
-			Collection<LogHandler> logHandlers, 
-			int exitCode)
-	{
-		return new SuppressForceCloseCrashAction(logHandlers, exitCode);
-	}
-	
-	/**
-	 * Creates a {@link CrashAction} which after handling the crashlog with the 
-	 * provided {@link LogHandler}s, calls {@link System#exit(int)}, terminating 
-	 * the process the app is running in.
-	 * 
-	 * @param logHandlers the handlers who will receive the log if we crash.
-	 * @param exitCode 
-	 *        The value to pass into System.exit(int). Because we're crashing if 
-	 *        it gets called you should probably use a non zero value (an exit 
-	 *        code of 0 indicates success for UNIX processes).
-	 */
-	public static CrashAction newSuppressForceCloseCrashAction(
-			Collection<LogHandler> logHandlers)
-	{
-		return newSuppressForceCloseCrashAction(logHandlers, 1);
-	}
-	
-	// TODO: can probably remove these combine methods, don't need them
-	/**
-	 * Combines multiple {@link CrashAction}s into a single composite 
-	 * {@link CrashAction} object which forwards calls to it's {@link CrashAction#onCrash(Thread, Throwable, UncaughtExceptionHandler, JSONObject, Context)}
-	 * method to the provided actions in order that the items are specified in,
-	 * i.e the first item in the array is notified first, followed by the second 
-	 * etc.
-	 * <p>
-	 * Be careful of the order that actions are added in. Some actions may call
-	 * {@link System#exit(int)} which will of course stop any subsequent actions 
-	 * from getting a chance to receive an onCrash call. 
-	 * 
-	 * @param actions 
-	 *        A group of actions to combine into a single composite action.
-	 * @return An action composed of 0 or more sub actions.
-	 */
-	private static CrashAction combine(CrashAction...actions)
-	{
-		if(actions == null)
-			throw new NullPointerException("actions was null");
-		for(int i = 0; i < actions.length; ++i)
-			if(actions[i] == null)
-				throw new NullPointerException("null action at index: " + i);
-		
-		return new CombinatorialCrashAction(Arrays.asList(actions));
-	}
-	
-	/**
-	 * Combines multiple {@link CrashAction}s into a single composite 
-	 * {@link CrashAction} object which forwards calls to it's {@link CrashAction#onCrash(Thread, Throwable, UncaughtExceptionHandler, JSONObject, Context)}
-	 * method to the provided actions in order that the collection's iterator
-	 * returns them.
-	 * <p>
-	 * Be careful of the order that actions are added in. Some actions may call
-	 * {@link System#exit(int)} which will of course stop any subsequent actions 
-	 * from getting a chance to receive an onCrash call. 
-	 * 
-	 * @param actions 
-	 *        A group of actions to combine into a single composite action.
-	 * @return An action composed of 0 or more sub actions.
-	 */
-	private static CrashAction combine(Collection<CrashAction> actions)
-	{
-		if(actions == null)
-			throw new NullPointerException("actions was null");
-		for(CrashAction a : actions)
-			if(a == null)
-				throw new NullPointerException(
-						"null action encountered in actions collection");
-		
-		return new CombinatorialCrashAction(actions);
-	}
-	
-	/** A {@link CrashAction} which maintains a list of crash actions which are
-	 * invoked in ascending order when the onCrash method of this class is 
-	 * invoked. */
-	private static final class CombinatorialCrashAction implements CrashAction
-	{
-		private final List<CrashAction> mCrashActions;
-		
-		public CombinatorialCrashAction(Collection<CrashAction> crashActions)
-		{
-			assert crashActions != null;
-			
-			mCrashActions = Collections.unmodifiableList(
-					new ArrayList<CrashAction>(crashActions));
-		}
-
-		@Override
-		public void onCrash(Thread crashedThread, Throwable uncaughtException,
-				UncaughtExceptionHandler parentHandler, JSONObject crashlog,
-				Context applicationContext)
-		{
-			// iterate over the list of actions, calling each in turn
-			for(int i = 0, limit = mCrashActions.size(); i < limit; ++i)
-				mCrashActions.get(i).onCrash(crashedThread, uncaughtException, 
-						parentHandler, crashlog, applicationContext);
-		}
-	}
-	
-	// prevent construction
-	private CrashActions(){ throw new AssertionError(); }
-}

src/hal/android/crashlogging/CrashLogging.java

-/*******************************************************************************
- * Copyright 2011 Hal Blackburn <hal.blackburn@gmail.com>
- * 
- * 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.
- ******************************************************************************/
-/*
- * Copyright (c) 2010, Hal Blackburn
- * 
- * Permission to use, copy, modify, and/or distribute this software for any 
- * purpose with or without fee is hereby granted, provided that the above 
- * copyright notice and this permission notice appear in all copies.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 
- * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 
- * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 
- * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 
- * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 
- * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 
- * PERFORMANCE OF THIS SOFTWARE.
- */
-package hal.android.crashlogging;
-
-import hal.android.crashlogging.logsections.ExceptionLogSection;
-import hal.android.crashlogging.logsections.PackageInfoLogSection;
-
-import java.lang.Thread.UncaughtExceptionHandler;
-import java.util.Collection;
-
-import android.content.Context;
-
-/**
- * The starting point of the crash logging library. This class provides static
- * methods to activate crash logging, as well as to check if it's already 
- * activated.
- * 
- * <h3>Crash Logging Library</h3>
- * 
- * <p>
- * The purpose of this library is to intercept Android application crashes 
- * (specifically unhandled java exceptions) before the app is terminated with a 
- * force close message. There are two goals:
- * 
- * <ol>
- * <li>Allow information about the crash to be recorded for later analysis</li>
- * <li>Allow the user to be notified of the crash in a more useful way than 
- * simply displaying the dreaded Android "force close" dialog, for example to
- * decide whether to submit the log information.</li>
- * </ol>
- * 
- * <p>
- * The building blocks which allow this are implementations of the 
- * {@link LogSection} and {@link CrashAction} interfaces.
- * 
- * <h4>Overview</h4>
- * <ol>
- * <li>App crashes (fails to catch an exception) for some reason.</li>
- * <li>Each of the log sections have their 
- *     {@link LogSection#getLogSection(Thread, Throwable, Context)} method 
- *     called, aggregating all of the results into a single JSON object which is 
- *     the crash log.</li>
- * <li>The defined crash action's {@link CrashAction#onCrash(Thread, Throwable, UncaughtExceptionHandler, org.json.JSONObject, Context)} 
- *     method is called with the generated crash log. The crash action is free
- *     to react to the crash in any way it likes, but will typically use one or
- *     more {@link LogHandler}s to save the crash log for later review.</li>
- * </ol>
- * 
- * <h3>More Detail</h3> 
- * <h4>Crash Logs</h4>
- * <p>
- * A crash log is created in a modular way by combining one or more 
- * {@link LogSection}s into a single log. Each log section provides information
- * on one focused area, for example details of the unhandled exception, 
- * information on the application version, time/date of the crash etc.
- * 
- * <p>
- * There are several prebuilt log sections available which should provide 
- * sufficient information for most apps to diagnose the cause of the crash. The
- * most important being {@link ExceptionLogSection} and 
- * {@link PackageInfoLogSection} which provide exception information and the 
- * version of the installed app respectively.
- * <p>
- * Application specific information can be logged by writing a new 
- * {@link LogSection} implementation and using it when activating crash logging.
- * 
- * <h4>Crash Actions</h4>
- * <p>
- * {@link CrashAction}s define what happens after the crash log has been 
- * generated. Their {@link CrashAction#onCrash(Thread, Throwable, UncaughtExceptionHandler, org.json.JSONObject, Context)}
- * method is called immediately following the generation of the crash log.
- * 
- * <h3>Note on JSON Use</h3>
- * The crash logging system is fairly strongly tied to using JSON as a means of
- * representing log information. This coupling is not ideal, but was chosen for 
- * a few reasons. We need a standard, portable data format for the logged data. 
- * XML would also work, as would YAML or other similar formats but I find XML to 
- * be allot of hassle to work with in java. 
- * <p>
- * I find JSON to be more programmer friendly, plus Android provides JSON 
- * support in the SDK, so no external dependencies are needed. I like YAML, but 
- * using it would require a 3rd party library, plus it's more complex than JSON 
- * and does not have such wide support in other languages as JSON.
- * 
- * <h3>Note on Non-Java Crashes</h3>
- * As most Android developers who have poked around in the src will know, quite 
- * a bit of the SDK (especially the graphics package) is implemented in native 
- * code. If your app crashes because a bug in native code crashes the VM, the 
- * chances are crash logging applied here won't be able to intercept the 
- * crash. Just worth bearing in mind.
- * 
- * <p>
- * This resulting log is passed to the crashAction to deal with as it 
- * sees fit. Typically the crash action will extend {@link BaseCrashAction} and 
- * use one or more {@link LogHandler}s.
- * 
- * @author Hal Blackburn
- * 
- */
-public final class CrashLogging
-{
-	/**
-	 * Attempts to determine if crash logging has already been activated. If so, 
-	 * crash logging should not be activated again.
-	 * <p>
-	 * <strong>Implementation Note:</strong><br>
-	 * We rely on setting the default 
-	 * {@link UncaughtExceptionHandler} to our own by calling {@link Thread#setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler)}.
-	 * If the default handler is replaced by one which forwards to ours after 
-	 * we're setup we will have no way to really know if our handler is still
-	 * registered.
-	 * <p>
-	 * In other words, this method will fail to work correctly if {@link Thread#setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler)}
-	 * is called after enabling crash logging.
-	 *  
-	 * @return {@code true} if crash logging is known to be activated, 
-	 *         {@code false} otherwise.
-	 */
-	public static boolean isActivated()
-	{		
-		return handlerIsCrashLogger(Thread.getDefaultUncaughtExceptionHandler());
-	}
-	
-	/**
-	 * Determines if the specified {@link UncaughtExceptionHandler} is an 
-	 * instance of our crash logger.
-	 * 
-	 * @param handler the handler whose type to check
-	 * @return {@code true} if the handler is an instance of our package's 
-	 *         handler ({@link CrashLoggingUncaughtExceptionHandler}).
-	 */
-	private static boolean handlerIsCrashLogger(
-			UncaughtExceptionHandler handler)
-	{
-		return handler instanceof CrashLoggingUncaughtExceptionHandler;
-	}
-	
-	/**
-	 * Activates the crash logging system. Once this function returns, crash 
-	 * logging will be setup and any uncaught exceptions will trigger the 
-	 * {@link CrashAction} provided to this function.
-	 * 
-	 * <p>
-	 * It is an error to attempt to activate crash logging more than once. 
-	 * {@link #isActivated()} can be used to determine if crash logging has 
-	 * already been setup.
-	 * 
-	 * @param logSections 
-	 *        The sections which when combined make up a complete crash log. 
-	 *         
-	 * @param crashAction 
-	 *        The action to trigger if the app crashes at a later point.
-	 * 
-	 * @param context 
-	 *        An Android context. Note that the actual context passed here is 
-	 *        not itself stored, rather {@link Context#getApplicationContext()} 
-	 *        is called upon it and the resulting context is stored, so it's 
-	 *        safe to pass in a full Activity or whatever you like.
-	 */
-	public static void activateWith(
-			Collection<LogSection> logSections, 
-			CrashAction crashAction, 
-			Context context)
-	{
-		// ensure our arguments are OK
-		if(context == null)
-			throw new NullPointerException("context was null");
-		if(logSections == null)
-			throw new NullPointerException("logSections was null");
-		if(crashAction == null)
-			throw new NullPointerException("crashAction was null");
-		
-		UncaughtExceptionHandler oldHandler = 
-			Thread.getDefaultUncaughtExceptionHandler();
-		
-		CrashLoggingUncaughtExceptionHandler crashHandler = new 
-			CrashLoggingUncaughtExceptionHandler(oldHandler, logSections, 
-					crashAction, context);
-		
-		Thread.setDefaultUncaughtExceptionHandler(crashHandler);
-	}
-	
-	// prevents construction of this class
-	private CrashLogging()
-	{ throw new AssertionError("instantiation not permitted"); }
-}

src/hal/android/crashlogging/CrashLoggingUncaughtExceptionHandler.java

-/*******************************************************************************
- * Copyright 2011 Hal Blackburn <hal.blackburn@gmail.com>
- * 
- * 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.
- ******************************************************************************/
-/*
- * Copyright (c) 2010, Hal Blackburn
- * 
- * Permission to use, copy, modify, and/or distribute this software for any 
- * purpose with or without fee is hereby granted, provided that the above 
- * copyright notice and this permission notice appear in all copies.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 
- * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 
- * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 
- * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 
- * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 
- * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 
- * PERFORMANCE OF THIS SOFTWARE.
- */
-package hal.android.crashlogging;
-
-import java.lang.Thread.UncaughtExceptionHandler;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-
-import org.json.JSONObject;
-
-import android.content.Context;
-import android.util.Log;
-
-/**
- * This is the {@link UncaughtExceptionHandler} implementation which is used to
- * intercept uncaught exceptions before the virtual machine is terminated.
- * <p>
- * To enable crash logging, an instance of this class can be set as the default 
- * uncaught exception handler using 
- * {@link Thread#setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler)}.
- * Users of this crash logging API do not need to see this class, I consider it
- * to be just an implementation detail (hence it having package level access).
- * Users instantiate and configure an instance of this class using the static 
- * methods of {@link CrashLogging}.
- * 
- * @author Hal
- */
-/* package */ class CrashLoggingUncaughtExceptionHandler implements
-		UncaughtExceptionHandler
-{
-	/** Key used to record any {@link LogSection}s which failed to properly 
-	 * generate log data. */
-	public static final String SECTION_FAILURES_KEY = 
-		"__log_generation_failures__";
-	
-	/** System log tag name used when logging error messages. */
-	private static final String TAG = "CrashLogging";
-	
-	/** The handler which was set as the default handler before we were created. 
-	 * May be null.*/
-	private final UncaughtExceptionHandler mParentHandler;
-	
-	/** The log sections which make up a completed log. If we crash these 
-	 * sections are aggregated into a single log and passed to the crash action.
-	 */
-	private final List<LogSection> mLogSections;
-	
-	/** The action to perform if we crash. */
-	private final CrashAction mCrashAction;
-	
-	private final Context mContext;
-	
-	/**
-	 * Creates an {@link UncaughtExceptionHandler} which 
-	 * 
-	 * @param parent
-	 * @param logSections
-	 * @param crashAction 
-	 * 
-	 * @param context 
-	 *        An android context. This should usually be [from]the app's 
-	 *        Application class.
-	 */
-	/* package */ CrashLoggingUncaughtExceptionHandler(
-			UncaughtExceptionHandler parent, 
-			Collection<LogSection> logSections, CrashAction crashAction, 
-			Context context)
-	{
-		if(crashAction == null)
-			throw new NullPointerException("crashAction was null");
-		if(context == null)
-			throw new NullPointerException("context was null");
-		if(logSections == null)
-			throw new NullPointerException("logSections was null");
-		
-		mParentHandler = parent;
-		mCrashAction = crashAction;
-		
-		// save the log sections in an unmodifiable list.
-		mLogSections = Collections.unmodifiableList(
-				new ArrayList<LogSection>(logSections));
-		
-		// Set our context to the application context. This should ensure we
-		// don't hold onto a whole activity or something silly like that
-		mContext = context.getApplicationContext();
-	}
-	
-	/* Called when the application code failed to catch an exception. This is 
-	 * pretty much the last thing called before the VM shuts us down. This
-	 * is where we do our logging and trigger the CrashAction. */
-	@Override
-	public void uncaughtException(Thread thread, Throwable error)
-	{
-		// We've crashed, oops. We need to assemble the log then send it on to
-		// out CrashAction who knows what to do with it.
-		
-		// We've got no idea how we've got here, the VM could be close to out of
-		// memory so we've got to be OTT careful here, we want to keep on 
-		// trucking as far as possible in the face of problems... 
-		
-		try
-		{
-			// Create a JSON object to hold the log sections. This makes up the
-			// crash log.
-			JSONObject crashLog = new JSONObject();
-			
-			// Loop through our list of log sections, adding each section's data
-			// to our crash log.
-			for(LogSection logSection : mLogSections)
-			{
-				// we can't drop all the sections after the current section if
-				// something goes wrong, so we need to make sure we try to
-				// generate each section, even in the face of unforeseen errors
-				// we wouldn't usually try to catch...
-				String key = null;
-				try
-				{
-					// obtain the key to save the data against
-					key = logSection.getLogKey();
-					// ensure we can't replace an existing section because of a
-					// duplicate key...
-					{
-						// keep generating key.NUMBER until we find a unique key
-						String uniqueKey = key;
-						for(int i = 1; crashLog.has(uniqueKey); ++i)
-							uniqueKey = key + '.' + i;
-						key = uniqueKey;
-					}
-					
-					// extract the log data from the section
-					JsonElement data = logSection.getLogSection(
-							thread, error, mContext);
-					// Because we're catching everything we can merrily throw
-					// runtime exceptions on programming errors as usual without
-					// actually killing our crash logging :)
-					if(data == null) throw new NullPointerException(
-								"data returned by logSection was null.");
-					
-					// Add the log section's data to the log. getValue() is 
-					// certain to return a proper JSON compatible object because
-					// of the restrictions on JsonElement's instantiation. 
-					crashLog.put(key, data.getValue());
-				}
-				catch(Throwable t)
-				{
-					// The log section threw an error either while getting the
-					// key or while generating the log data. This is almost
-					// certainly happening as the result of a programming error.
-					// Regardless, we need to carry on (we're in the middle of a
-					// critical task, can't just bail out and die as would 
-					// normally be reasonable for a coding error. Because we're 
-					// not crashing with the error we make an effort not to hide 
-					// the problem by logging this failure against the 
-					// SECTION_FAILURES_KEY in the crash log.
-					Log.e(TAG, "Generation of log section failed on: " 
-							+ logSection, t);
-					if(key != null) 
-						addLogSectionFailureRecord(
-							crashLog, logSection, key, t);
-				}
-			}
-			
-			// TODO: could wrap crashLog with JSONUtils.unmodifiableJSONObject(...) at this point
-			
-			// we're done generating the log now; we just need to invoke the 
-			// crash action to let it do it's thing. Note that this invocation
-			// may well never return, so the finally clause will quite possibly
-			// not actually get executed!
-			mCrashAction.onCrash(
-					thread, error, mParentHandler, crashLog, mContext);
-		}
-		finally
-		{
-			// If control leaves the above try block (either because of an 
-			// exception or because mCrashAction returned from onCrash(...)
-			// we want to call the default handler to ensure stuff gets sorted
-			// out. This will usually be the android handler which displays the
-			// 'force close' dialog.
-			//
-			// Note that we never get to this point if the CrashAction's 
-			// onCrash(...) method terminates the VM by calling System.exit(int)
-			if(mParentHandler != null)
-				mParentHandler.uncaughtException(thread, error);
-		}
-	}
-	
-	/**
-	 * Adds an error record to the log to indicate that the generation of a log 
-	 * section failed.
-	 * 
-	 * @param log The root of the crash log we're building
-	 * @param section The section which failed to return data
-	 * @param key The key of the section (to avoid us having to call getKey())
-	 * @param error The error which was caught when obtaining data
-	 * @return {@code false} if an error occurred while attempting to log the
-	 *         error being reported to this function. Action is not expected
-	 *         to be taken if an error happens in this function which is why it
-	 *         returns a boolean instead of re throwing the error.
-	 */
-	private boolean addLogSectionFailureRecord(JSONObject log, LogSection section, 
-			String key, Throwable error)
-	{
-		if(log == null || section == null)
-			return false;
-		try
-		{
-			assert log != null;
-			
-			JSONObject failure = new JSONObject();
-			failure.put("failedKey", key);
-			failure.put("failedSection", section.getClass().getName());
-			failure.put("error", 
-					error == null ? JSONObject.NULL : error.toString());
-			failure.put("stackTrace", Log.getStackTraceString(error));
-			
-			// add the failure record to the log
-			log.accumulate(SECTION_FAILURES_KEY, failure);
-			
-			return true;
-		}
-		catch(Throwable t)
-		{ 
-			/* We're already logging an error which happened while logging an
-		     * error so there's not much we can reasonably do if another error
-		     * happens! */
-			return false;
-		}
-	}
-}

src/hal/android/crashlogging/ForceCloseAction.java

-/*******************************************************************************
- * Copyright 2011 Hal Blackburn <hal.blackburn@gmail.com>
- * 
- * 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.
- ******************************************************************************/
-/*
- * Copyright (c) 2010, Hal Blackburn
- * 
- * Permission to use, copy, modify, and/or distribute this software for any 
- * purpose with or without fee is hereby granted, provided that the above 
- * copyright notice and this permission notice appear in all copies.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 
- * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 
- * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 
- * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 
- * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 
- * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 
- * PERFORMANCE OF THIS SOFTWARE.
- */
-package hal.android.crashlogging;
-
-import java.lang.Thread.UncaughtExceptionHandler;
-import java.util.Collection;
-
-import org.json.JSONObject;
-
-import android.content.Context;
-
-/**
- * A {@link CrashAction} which delegates to the parent 
- * {@link UncaughtExceptionHandler} after handling the logs with its list of log 
- * handlers.
- * <p>
- * <strong>Warning:</strong> This {@link CrashAction}'s {@link #onCrash}
- * will usually never return as it calls 
- * {@link UncaughtExceptionHandler#uncaughtException} on the
- * parent handler which is usually the Android handler which generates a 'force 
- * close' dialog, terminating the app.
- * 
- * @author Hal
- */
-public class ForceCloseAction extends BaseCrashAction
-{
-	public ForceCloseAction(Collection<LogHandler> logHandlers)
-	{
-		super(logHandlers);
-	}
-
-	@Override
-	public void onCrash(Thread crashedThread, Throwable uncaughtException,
-			UncaughtExceptionHandler parentHandler, JSONObject crashlog,
-			Context applicationContext)
-	{
-		// pass the crash log to our handlers so that the logs can be dealt with
-		handleLog(crashlog);
-		
-		// call our parent uncaught ex handler which *should*  be the Android 
-		// handler which will result in the dreaded force close dialog being 
-		// shown
-		if(parentHandler != null)
-			parentHandler.uncaughtException(crashedThread, uncaughtException);
-	}
-}

src/hal/android/crashlogging/JSONUtils.java

-/*******************************************************************************
- * Copyright 2011 Hal Blackburn <hal.blackburn@gmail.com>
- * 
- * 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.
- ******************************************************************************/
-/*
- * Copyright (c) 2010, Hal Blackburn
- * 
- * Permission to use, copy, modify, and/or distribute this software for any 
- * purpose with or without fee is hereby granted, provided that the above 
- * copyright notice and this permission notice appear in all copies.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 
- * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 
- * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 
- * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 
- * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 
- * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 
- * PERFORMANCE OF THIS SOFTWARE.
- */
-package hal.android.crashlogging;
-
-import java.util.Iterator;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-/**
- * Utility functions for working with JSON, specifically the JSON implementation
- * provided in the Android SDK, e.g. {@link JSONObject} and {@link JSONArray}.
- * 
- * @author Hal
- */
-public final class JSONUtils
-{
-	// No instantiation
-	private JSONUtils(){ throw new AssertionError(); }
-	
-	/**
-	 * Wraps a JSON array in an unmodifiable wrapper which refuses to forward 
-	 * modification requests to the target.
-	 * 
-	 * @param jsonArray the array to wrap
-	 * @return jsonArray wrapped in a wrapper which throws an exception on a 
-	 *         modification request.
-	 */
-	public static JSONArray unmodifiableJSONArray(JSONArray jsonArray)
-	{
-		if(jsonArray == null)
-			throw new NullPointerException("jsonArray was null");
-		
-		// wrap the json array in an unmodifiable wrapper if it's not already 
-		// wrapped
-		if(!(jsonArray instanceof UnmodifiableJSONArray))
-			jsonArray = new UnmodifiableJSONArray(jsonArray);
-		return null;
-	}
-	
-	/**
-	 * Wraps the specified JSONObject with a wrapper which refuses to make 
-	 * changes to the underlying object. Note that the returned object is only 
-	 * immutable if all of its JSONObject and JSONArray children are also 
-	 * immutable.
-	 * <p>
-	 * This method is conceptually the same as the Collections.unmodifiable... 
-	 * methods.
-	 * 
-	 * @param json a JSONObject to place in an unmodifiable wrapper.
-	 * @return An unmodifiable wrapper around the json object.
-	 * 
-	 * @throws NullPointerException if json is null
-	 */
-	public static JSONObject unmodifiableJSONObject(JSONObject json)
-	{
-		if(json == null)
-			throw new NullPointerException("json was null");
-		
-		if(!(json instanceof UnmodifiableJSONObject))
-			json = new UnmodifiableJSONObject(json);
-		
-		return json;
-	}
-	
-	private static final class UnmodifiableJSONObject extends JSONObject
-	{
-		private final JSONObject mTarget;
-		
-		UnmodifiableJSONObject(JSONObject object)
-		{
-			if(object == null)
-				throw new NullPointerException("object was null");
-			
-			mTarget = object;
-		}
-
-		public JSONObject accumulate(String key, Object value) throws JSONException
-		{
-			throw new UnsupportedOperationException("This JSONObject is unmodifiable");
-		}
-
-		public boolean equals(Object o)
-		{
-			return mTarget.equals(o);
-		}
-
-		public Object get(String key) throws JSONException
-		{
-			return mTarget.get(key);
-		}
-
-		public boolean getBoolean(String key) throws JSONException
-		{
-			return mTarget.getBoolean(key);
-		}
-
-		public double getDouble(String key) throws JSONException
-		{
-			return mTarget.getDouble(key);
-		}
-
-		public int getInt(String key) throws JSONException
-		{
-			return mTarget.getInt(key);
-		}
-
-		public JSONArray getJSONArray(String key) throws JSONException
-		{
-			return mTarget.getJSONArray(key);
-		}
-
-		public JSONObject getJSONObject(String key) throws JSONException
-		{
-			return mTarget.getJSONObject(key);
-		}
-
-		public long getLong(String key) throws JSONException
-		{
-			return mTarget.getLong(key);
-		}
-
-		public String getString(String key) throws JSONException
-		{
-			return mTarget.getString(key);
-		}
-
-		public boolean has(String key)
-		{
-			return mTarget.has(key);
-		}
-
-		public int hashCode()
-		{
-			return mTarget.hashCode();
-		}
-
-		public boolean isNull(String key)
-		{
-			return mTarget.isNull(key);
-		}
-
-		public Iterator<?> keys()
-		{
-			return mTarget.keys();
-		}
-
-		public int length()
-		{
-			return mTarget.length();
-		}
-
-		public JSONArray names()
-		{
-			return mTarget.names();
-		}
-
-		public Object opt(String key)
-		{
-			return mTarget.opt(key);
-		}
-
-		public boolean optBoolean(String key, boolean defaultValue)
-		{
-			return mTarget.optBoolean(key, defaultValue);
-		}
-
-		public boolean optBoolean(String key)
-		{
-			return mTarget.optBoolean(key);
-		}
-
-		public double optDouble(String key, double defaultValue)
-		{
-			return mTarget.optDouble(key, defaultValue);
-		}
-
-		public double optDouble(String key)
-		{
-			return mTarget.optDouble(key);
-		}
-
-		public int optInt(String key, int defaultValue)
-		{
-			return mTarget.optInt(key, defaultValue);
-		}
-
-		public int optInt(String key)
-		{
-			return mTarget.optInt(key);
-		}
-
-		public JSONArray optJSONArray(String key)
-		{
-			return mTarget.optJSONArray(key);
-		}
-
-		public JSONObject optJSONObject(String key)
-		{
-			return mTarget.optJSONObject(key);
-		}
-
-		public long optLong(String key, long defaultValue)
-		{
-			return mTarget.optLong(key, defaultValue);
-		}
-
-		public long optLong(String key)
-		{
-			return mTarget.optLong(key);
-		}
-
-		public String optString(String key, String defaultValue)
-		{
-			return mTarget.optString(key, defaultValue);
-		}
-
-		public String optString(String key)
-		{
-			return mTarget.optString(key);
-		}
-
-		public JSONObject put(String key, boolean value) throws JSONException
-		{
-			throw new UnsupportedOperationException("This JSONObject is unmodifiable");
-		}
-
-		public JSONObject put(String key, double value) throws JSONException
-		{
-			throw new UnsupportedOperationException("This JSONObject is unmodifiable");
-		}
-
-		public JSONObject put(String key, int value) throws JSONException
-		{
-			throw new UnsupportedOperationException("This JSONObject is unmodifiable");
-		}
-
-		public JSONObject put(String key, long value) throws JSONException
-		{
-			throw new UnsupportedOperationException("This JSONObject is unmodifiable");
-		}
-
-		public JSONObject put(String key, Object value) throws JSONException
-		{
-			throw new UnsupportedOperationException("This JSONObject is unmodifiable");
-		}
-
-		public JSONObject putOpt(String key, Object value) throws JSONException
-		{
-			throw new UnsupportedOperationException("This JSONObject is unmodifiable");
-		}
-
-		public Object remove(String key)
-		{
-			throw new UnsupportedOperationException("This JSONObject is unmodifiable");
-		}
-
-		public JSONArray toJSONArray(JSONArray names) throws JSONException
-		{
-			return mTarget.toJSONArray(names);
-		}
-
-		public String toString()
-		{
-			return mTarget.toString();
-		}
-
-		public String toString(int indentFactor) throws JSONException
-		{
-			return mTarget.toString(indentFactor);
-		}
-	}
-	
-	private static final class UnmodifiableJSONArray extends JSONArray
-	{
-		private final JSONArray mTarget;
-		
-		public UnmodifiableJSONArray(JSONArray target)
-		{
-			if(target == null)
-				throw new NullPointerException("target was null");
-			mTarget = target;
-		}
-
-		public boolean equals(Object object)
-		{
-			return mTarget.equals(object);
-		}
-
-		public Object get(int index) throws JSONException
-		{
-			return mTarget.get(index);
-		}
-
-		public boolean getBoolean(int index) throws JSONException
-		{
-			return mTarget.getBoolean(index);
-		}
-
-		public double getDouble(int index) throws JSONException
-		{
-			return mTarget.getDouble(index);
-		}
-
-		public int getInt(int index) throws JSONException
-		{
-			return mTarget.getInt(index);
-		}
-
-		public JSONArray getJSONArray(int index) throws JSONException
-		{
-			return mTarget.getJSONArray(index);
-		}
-
-		public JSONObject getJSONObject(int index) throws JSONException
-		{
-			return mTarget.getJSONObject(index);
-		}
-
-		public long getLong(int index) throws JSONException
-		{
-			return mTarget.getLong(index);
-		}
-
-		public String getString(int index) throws JSONException
-		{
-			return mTarget.getString(index);
-		}
-
-		public int hashCode()
-		{
-			return mTarget.hashCode();
-		}
-
-		public boolean isNull(int index)
-		{
-			return mTarget.isNull(index);
-		}
-
-		public String join(String separator) throws JSONException
-		{
-			return mTarget.join(separator);
-		}
-
-		public int length()
-		{
-			return mTarget.length();
-		}
-
-		public Object opt(int index)
-		{
-			return mTarget.opt(index);
-		}
-
-		public boolean optBoolean(int index, boolean defaultValue)
-		{
-			return mTarget.optBoolean(index, defaultValue);
-		}
-
-		public boolean optBoolean(int index)
-		{
-			return mTarget.optBoolean(index);
-		}
-
-		public double optDouble(int index, double defaultValue)
-		{
-			return mTarget.optDouble(index, defaultValue);
-		}
-
-		public double optDouble(int index)
-		{
-			return mTarget.optDouble(index);
-		}
-
-		public int optInt(int index, int defaultValue)
-		{
-			return mTarget.optInt(index, defaultValue);
-		}
-
-		public int optInt(int index)
-		{
-			return mTarget.optInt(index);
-		}
-
-		public JSONArray optJSONArray(int index)
-		{
-			return mTarget.optJSONArray(index);
-		}
-
-		public JSONObject optJSONObject(int index)
-		{
-			return mTarget.optJSONObject(index);
-		}
-
-		public long optLong(int index, long defaultValue)
-		{
-			return mTarget.optLong(index, defaultValue);
-		}
-
-		public long optLong(int index)
-		{
-			return mTarget.optLong(index);
-		}
-
-		public String optString(int index, String defaultValue)
-		{
-			return mTarget.optString(index, defaultValue);
-		}
-
-		public String optString(int index)
-		{
-			return mTarget.optString(index);
-		}
-
-		public JSONArray put(boolean value)
-		{
-			throw new UnsupportedOperationException(
-					"This JSONArray is unmodifiable");
-		}
-
-		public JSONArray put(double value) throws JSONException
-		{
-			throw new UnsupportedOperationException(
-					"This JSONArray is unmodifiable");
-		}
-
-		public JSONArray put(int index, boolean value) throws JSONException
-		{
-			throw new UnsupportedOperationException(
-					"This JSONArray is unmodifiable");
-		}
-
-		public JSONArray put(int index, double value) throws JSONException
-		{
-			throw new UnsupportedOperationException(
-					"This JSONArray is unmodifiable");
-		}
-
-		public JSONArray put(int index, int value) throws JSONException
-		{
-			throw new UnsupportedOperationException(
-					"This JSONArray is unmodifiable");
-		}
-
-		public JSONArray put(int index, long value) throws JSONException
-		{
-			throw new UnsupportedOperationException(
-					"This JSONArray is unmodifiable");
-		}
-
-		public JSONArray put(int index, Object value) throws JSONException
-		{
-			throw new UnsupportedOperationException(
-					"This JSONArray is unmodifiable");
-		}
-
-		public JSONArray put(int value)
-		{
-			throw new UnsupportedOperationException(
-					"This JSONArray is unmodifiable");
-		}
-
-		public JSONArray put(long value)
-		{
-			throw new UnsupportedOperationException(
-					"This JSONArray is unmodifiable");
-		}
-
-		public JSONArray put(Object value)
-		{
-			throw new UnsupportedOperationException(
-					"This JSONArray is unmodifiable");
-		}
-
-		public JSONObject toJSONObject(JSONArray names) throws JSONException
-		{
-			return mTarget.toJSONObject(names);
-		}
-
-		public String toString()
-		{
-			return mTarget.toString();
-		}
-
-		public String toString(int indentFactor) throws JSONException
-		{
-			return mTarget.toString(indentFactor);
-		}
-	}
-}
-

src/hal/android/crashlogging/JsonElement.java

-/*******************************************************************************
- * Copyright 2011 Hal Blackburn <hal.blackburn@gmail.com>
- * 
- * 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.
- ******************************************************************************/
-/*
- * Copyright (c) 2010, Hal Blackburn
- * 
- * Permission to use, copy, modify, and/or distribute this software for any 
- * purpose with or without fee is hereby granted, provided that the above 
- * copyright notice and this permission notice appear in all copies.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 
- * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 
- * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 
- * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 
- * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 
- * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 
- * PERFORMANCE OF THIS SOFTWARE.
- */
-package hal.android.crashlogging;
-
-import org.json.JSONArray;
-import org.json.JSONObject;
-
-/**
- * A type safe wrapper for objects intended to be added to a JSON object/array. 
- * {@link #getValue()} is guaranteed to return an object which can be passed to 
- * one of the JSONObject/JSONArray put* /accumulate* methods.
- * 
- * @author Hal
- */
-public final class JsonElement
-{
-	/** Our value which will be of one of the JSON compatable types specified in 
-	 * {@link JSONObject} */
-	private final Object mElement;
-	
-	/**
-	 * Creates a {@link JsonElement} with the specified value. If value is null,
-	 * the {@link #NULL} constant is returned.
-	 * @param elementValue the value.
-	 * @return a new {@link JsonElement} wrapping the value.
-	 */
-	public static JsonElement newJsonElement(JSONObject elementValue)
-	{ return newJsonElement((Object)elementValue); }
-	
-	/**
-	 * Creates a {@link JsonElement} with the specified value. If value is null,
-	 * the {@link #NULL} constant is returned.
-	 * @param elementValue the value.
-	 * @return a new {@link JsonElement} wrapping the value.
-	 */
-	public static JsonElement newJsonElement(JSONArray elementValue)
-	{ return newJsonElement((Object)elementValue); }
-	
-	/**
-	 * Creates a {@link JsonElement} with the specified value. If value is null,
-	 * the {@link #NULL} constant is returned.
-	 * @param elementValue the value.
-	 * @return a new {@link JsonElement} wrapping the value.
-	 */
-	public static JsonElement newJsonElement(Number elementValue)
-	{ return newJsonElement((Object)elementValue); }
-	
-	/**
-	 * Creates a {@link JsonElement} with the specified value. If value is null,
-	 * the {@link #NULL} constant is returned.
-	 * @param elementValue the value.
-	 * @return a new {@link JsonElement} wrapping the value.
-	 */
-	public static JsonElement newJsonElement(String elementValue)
-	{ return newJsonElement((Object)elementValue); }
-	
-	/**
-	 * Creates a {@link JsonElement} with the specified value. If value is null,
-	 * the {@link #NULL} constant is returned.
-	 * @param elementValue the value.
-	 * @return a new {@link JsonElement} wrapping the value.
-	 */
-	public static JsonElement newJsonElement(Boolean elementValue)
-	{ return newJsonElement((Object)elementValue); }
-	
-	private static JsonElement newJsonElement(Object elementValue)
-	{
-		// return the NULL constant if the value is null
-		if(elementValue == null)
-			return NULL;
-		
-		return new JsonElement(elementValue);
-	}
-	
-	/** @return a JsonElement containing the {@link JSONObject#NULL} value. */
-	public static JsonElement getNullElement()
-	{
-		return NULL;
-	}
-	
-	/** The singleton JsonElement containing the JSONObject.NULL object as it's 
-	 * element value */
-	public static final JsonElement NULL = new JsonElement(JSONObject.NULL);
-	
-	private JsonElement(Object element)
-	{
-		// null is represented by the JSON null object
-		if(element == null)
-			element = JSONObject.NULL;
-		
-		// Ensure our element is one of the types which can be added to a JSON
-		// Object/Array.
-		if(!(element instanceof Boolean
-		  || element instanceof Number
-		  || element instanceof String
-		  || element instanceof JSONObject
-		  || element instanceof JSONArray
-		  || element == JSONObject.NULL))
-		{
-			throw new IllegalArgumentException(String.format(
-					"Invalid element '%s' of type '%s', expected one of " +
-					"Boolean, Double, Integer, JSONArray, JSONObject, Long, " +
-					"String, or the JSONObject.NULL object.", 
-					element, element.getClass()));
-		}
-		
-		mElement = element;
-	}
-	
-	/**
-	 * Gets the value of this json element. This value is guaranteed to be one 
-	 * which can be added directly to a JSONObject or JSONArray as specified by 
-	 * the class documentation for JSONObject:
-	 * <blockquote>
-	 * Boolean, JSONArray, JSONObject, Number, String, or the JSONObject.NULL
-	 * </blockquote>
-	 * 
-	 * @return
-	 */
-	public Object getValue()
-	{
-		return mElement;
-	}
-
-	@Override
-	public int hashCode()
-	{
-		final int prime = 31;
-		int result = 1;
-		result = prime * result
-				+ ((mElement == null) ? 0 : mElement.hashCode());
-		return result;
-	}
-
-	@Override
-	public boolean equals(Object obj)
-	{
-		if (this == obj)
-			return true;
-		if (obj == null)
-			return false;
-		if (getClass() != obj.getClass())
-			return false;
-		JsonElement other = (JsonElement) obj;
-		if (mElement == null)
-		{
-			if (other.mElement != null)
-				return false;
-		} else if (!mElement.equals(other.mElement))
-			return false;
-		return true;
-	}
-	
-	@Override
-	public String toString()
-	{
-		return "" + mElement;
-	}
-}

src/hal/android/crashlogging/LogHandler.java

-/*******************************************************************************
- * Copyright 2011 Hal Blackburn <hal.blackburn@gmail.com>
- * 
- * 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.
- ******************************************************************************/
-/*
- * Copyright (c) 2010, Hal Blackburn
- * 
- * Permission to use, copy, modify, and/or distribute this software for any 
- * purpose with or without fee is hereby granted, provided that the above 
- * copyright notice and this permission notice appear in all copies.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 
- * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 
- * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 
- * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 
- * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 
- * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 
- * PERFORMANCE OF THIS SOFTWARE.
- */
-package hal.android.crashlogging;
-
-import org.json.JSONObject;
-
-/**
- * Defines an object capable of handling a crash log in some way, e.g. saving to 
- * a file, sending to a server etc.
- * 
- * @author Hal
- */
-public interface LogHandler
-{
-	/**
-	 * Handles a crash log in some way, for example saving it to a file or 
-	 * sending it to a remote server etc.
-	 * 
-	 * @param crashLog 
-	 *        a pre compiled string representation of the JSON crash log.
-	 * @param crashLogOrigional 
-	 *        the original JSON representation of the crash log. This object 
-	 *        must not be modified.
-	 */
-	void handleLog(String crashLog, JSONObject crashLogOrigional);
-}

src/hal/android/crashlogging/LogSection.java

-/*******************************************************************************
- * Copyright 2011 Hal Blackburn <hal.blackburn@gmail.com>
- * 
- * 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.
- ******************************************************************************/
-/*
- * Copyright (c) 2010, Hal Blackburn
- * 
- * Permission to use, copy, modify, and/or distribute this software for any 
- * purpose with or without fee is hereby granted, provided that the above 
- * copyright notice and this permission notice appear in all copies.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 
- * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 
- * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 
- * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 
- * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 
- * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 
- * PERFORMANCE OF THIS SOFTWARE.
- */
-package hal.android.crashlogging;
-
-import org.json.JSONException;
-
-import android.content.Context;
-
-/**
- * Represents a single focused part of a crash log, for example data on the 
- * uncaught exception, the current time or the version number of the app etc.
- * 
- * @author Hal
- */
-public interface LogSection
-{
-	/**
-	 * Gets some data which to put into the crash log. The data returned by this
-	 * method will be stored in the final JSON log against the key returned by
-	 * this object's {@link #getLogKey()} method.
-	 * 
-	 * @param crashedThread The thread which had an uncaught exception.
-	 * @param error The exception which was uncaught.
-	 * @param context The context crash logging was configured in.
-	 * @return A JsonElement wrapper containing the data to add to the log.
-	 * 
-	 * @throws Exception This method is permitted to throw any exception if 
-	 * desired. Handlers must account for error conditions and handle them 
-	 * correctly. As the app has crashed it's likely that the app will be in an
-	 * unforseen state, making crashes more likely when attempting to report the
-	 * state of the app.
-	 */
-	JsonElement getLogSection(Thread crashedThread, Throwable error, 
-			Context context) throws Exception;
-	
-	/**
-	 * Gets the key to store the log section against in the JSON log.
-	 * 
-	 * @return a string concisely representing the name of this log section.
-	 */
-	String getLogKey();
-}

src/hal/android/crashlogging/SuppressForceCloseCrashAction.java

-/*******************************************************************************
- * Copyright 2011 Hal Blackburn <hal.blackburn@gmail.com>
- * 
- * 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 hal.android.crashlogging;
-
-
-import java.lang.Thread.UncaughtExceptionHandler;
-import java.util.Collection;
-
-import org.json.JSONObject;
-
-import android.content.Context;
-
-/* protected */ class SuppressForceCloseCrashAction extends BaseCrashAction
-{
-	private final int mExitCode;
-	
-	/**
-	 * Creates a {@link CrashAction} which after handling the crashlog with the 
-	 * provided log handlers, calls {@link System#exit(int)} with an exit code 
-	 * of 1, terminating the process the app is running in.
-	 * 
-	 * @param logHandlers the handlers who will receive the log if we crash.
-	 */
-	protected SuppressForceCloseCrashAction(Collection<LogHandler> logHandlers)
-	{
-		this(logHandlers, 1);
-	}
-	
-	/**
-	 * Creates a {@link CrashAction} which after handling the crashlog with the 
-	 * provided log handlers, calls {@link System#exit(int)}, terminating the
-	 * process the app is running in.
-	 * 
-	 * @param logHandlers the handlers who will receive the log if we crash.
-	 * @param exitCode 
-	 *        The value to pass into System.exit(int). Because we're crashing if 
-	 *        it gets called you should probably use a non zero value (an exit 
-	 *        code of 0 indicates success for UNIX processes).
-	 */
-	protected SuppressForceCloseCrashAction(Collection<LogHandler> logHandlers, 
-			int exitCode)
-	{
-		super(logHandlers);
-		mExitCode = exitCode;
-	}
-	
-	@Override
-	public void onCrash(Thread crashedThread, Throwable uncaughtException,
-			UncaughtExceptionHandler parentHandler, JSONObject crashlog,
-			Context applicationContext)
-	{
-		// send the crash log to each of our handlers
-		handleLog(crashlog);
-		
-		// terminate the virtual machine which stops this android process. Other 
-		// Activities/Services in the crashed app running in separate processes 
-		// are not effected by this call. This shuts down the app without 
-		// showing the 'force close' dialog. The app will silently die like an
-		// apps do on the iPhone when they crash.
-		//
-		// Because it is bad form to just disappear from view upon a crash it is
-		// assumed that one of the log handlers just called has done something
-		// to notify the user.
-		System.exit(mExitCode);
-	}
-}

src/hal/android/crashlogging/logsections/DateLogSection.java

-/*******************************************************************************
- * Copyright 2011 Hal Blackburn <hal.blackburn@gmail.com>
- * 
- * 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.
- ******************************************************************************/
-/*
- * Copyright (c) 2010, Hal Blackburn
- * 
- * Permission to use, copy, modify, and/or distribute this software for any 
- * purpose with or without fee is hereby granted, provided that the above 
- * copyright notice and this permission notice appear in all copies.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 
- * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY