Compare commits

...

10 Commits

Author SHA1 Message Date
6b9cbdef84 Import to intelij, started updating to use maps v2 2014-08-30 11:29:50 -04:00
ab75dd3bb1 Added files needed to build with ant 2012-08-09 13:21:37 -04:00
4de767f753 Cleaned up code 2012-07-22 09:36:07 -04:00
5e6e6c4dd9 I removed the compat library, and imported the NotificationConpat
classes. This will prevent compilation errors.
2012-07-18 15:00:38 -04:00
507d7901bf Updated notifications to use the compat notification builder
closes #124
2012-07-16 13:44:16 -04:00
9ec67079c0 Added .NOTEMPTY to the drawables file 2012-07-14 12:15:07 -04:00
42cb0b9f0e Cleaned Up code
Signed-off-by: Ricky Barrette <rickbarrette@gmail.com>
2012-07-01 11:21:18 -04:00
c4980a8295 Fixed: Multiple reports filed
closes #102
Signed-off-by: Ricky Barrette <rickbarrette@gmail.com>
2012-06-04 12:10:09 -04:00
824f58879c I moved the report filing work, to it's own thread
closes #96

Signed-off-by: Ricky Barrette <rickbarrette@gmail.com>
2012-06-03 11:52:49 -04:00
9b7c2594c9 Fixed some lint errors
Signed-off-by: Ricky Barrette <rickbarrette@gmail.com>
2012-06-03 11:35:36 -04:00
18 changed files with 1034 additions and 397 deletions

View File

@@ -4,5 +4,6 @@
<classpathentry kind="src" path="gen"/> <classpathentry kind="src" path="gen"/>
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/> <classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
<classpathentry kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/> <classpathentry kind="con" path="com.android.ide.eclipse.adt.LIBRARIES"/>
<classpathentry exported="true" kind="con" path="com.android.ide.eclipse.adt.DEPENDENCIES"/>
<classpathentry kind="output" path="bin/classes"/> <classpathentry kind="output" path="bin/classes"/>
</classpath> </classpath>

View File

@@ -4,7 +4,9 @@
android:versionCode="1" android:versionCode="1"
android:versionName="1.0" > android:versionName="1.0" >
<uses-sdk android:minSdkVersion="1" /> <uses-sdk
android:minSdkVersion="7"
android:targetSdkVersion="16" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
@@ -14,7 +16,9 @@
android:configChanges="keyboard|keyboardHidden|orientation" > android:configChanges="keyboard|keyboardHidden|orientation" >
</activity> </activity>
<service android:name="ReportPostingService" > <service
android:name="ReportPostingService"
android:process=":reportpostingservice" >
</service> </service>
</application> </application>

View File

@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="EclipseModuleManager" forced_jdk="true">
<conelement value="com.android.ide.eclipse.adt.DEPENDENCIES" />
<src_description expected_position="1">
<src_folder value="file://$MODULE_DIR$/src" expected_position="0" />
<src_folder value="file://$MODULE_DIR$/gen" expected_position="1" />
<src_folder value="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK" expected_position="2" />
<src_folder value="com.android.ide.eclipse.adt.LIBRARIES" expected_position="3" />
</src_description>
</component>
<component name="FacetManager">
<facet type="android" name="Android">
<configuration>
<option name="LIBRARY_PROJECT" value="true" />
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="false">
<output url="file://$MODULE_DIR$/bin/classes" />
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/gen" isTestSource="false" generated="true" />
</content>
<orderEntry type="jdk" jdkName="Android 4.3 Google APIs" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

View File

@@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8"?>
<project name="ExceptionHandlerLib" default="help">
<!-- The local.properties file is created and updated by the 'android' tool.
It contains the path to the SDK. It should *NOT* be checked into
Version Control Systems. -->
<property file="local.properties" />
<!-- The ant.properties file can be created by you. It is only edited by the
'android' tool to add properties to it.
This is the place to change some Ant specific build properties.
Here are some properties you may want to change/update:
source.dir
The name of the source directory. Default is 'src'.
out.dir
The name of the output directory. Default is 'bin'.
For other overridable properties, look at the beginning of the rules
files in the SDK, at tools/ant/build.xml
Properties related to the SDK location or the project target should
be updated using the 'android' tool with the 'update' action.
This file is an integral part of the build system for your
application and should be checked into Version Control Systems.
-->
<property file="ant.properties" />
<!-- if sdk.dir was not set from one of the property file, then
get it from the ANDROID_HOME env var.
This must be done before we load project.properties since
the proguard config can use sdk.dir -->
<property environment="env" />
<condition property="sdk.dir" value="${env.ANDROID_HOME}">
<isset property="env.ANDROID_HOME" />
</condition>
<!-- The project.properties file is created and updated by the 'android'
tool, as well as ADT.
This contains project specific properties such as project target, and library
dependencies. Lower level build properties are stored in ant.properties
(or in .classpath for Eclipse projects).
This file is an integral part of the build system for your
application and should be checked into Version Control Systems. -->
<loadproperties srcFile="project.properties" />
<!-- quick check on sdk.dir -->
<fail
message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
unless="sdk.dir"
/>
<!--
Import per project custom build rules if present at the root of the project.
This is the place to put custom intermediary targets such as:
-pre-build
-pre-compile
-post-compile (This is typically used for code obfuscation.
Compiled code location: ${out.classes.absolute.dir}
If this is not done in place, override ${out.dex.input.absolute.dir})
-post-package
-post-build
-pre-clean
-->
<import file="custom_rules.xml" optional="true" />
<!-- Import the actual build file.
To customize existing targets, there are two options:
- Customize only one target:
- copy/paste the target into this file, *before* the
<import> task.
- customize it to your needs.
- Customize the whole content of build.xml
- copy/paste the content of the rules files (minus the top node)
into this file, replacing the <import> task.
- customize to your needs.
***********************
****** IMPORTANT ******
***********************
In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
in order to avoid having your file be overridden by tools such as "android update project"
-->
<!-- version-tag: 1 -->
<import file="${sdk.dir}/tools/ant/build.xml" />
</project>

View File

@@ -0,0 +1,3 @@
<?xml version="1.0" encoding="UTF-8"?>
<lint>
</lint>

View File

@@ -0,0 +1,10 @@
# This file is automatically generated by Android Tools.
# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
#
# This file must *NOT* be checked into Version Control Systems,
# as it contains information specific to your local configuration.
# location of the SDK. This is only used by Ant
# For customization when using a Version Control System, please read the
# header note.
sdk.dir=/app/android-sdk-linux_86

View File

@@ -0,0 +1,20 @@
# To enable ProGuard in your project, edit project.properties
# to define the proguard.config property as described in that file.
#
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in ${sdk.dir}/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the ProGuard
# include property in project.properties.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

View File

@@ -9,4 +9,4 @@
android.library=true android.library=true
# Project target. # Project target.
target=android-15 target=android-17

View File

@@ -0,0 +1,388 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* 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 anroid.v4.compat;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.graphics.Bitmap;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Build;
import android.widget.RemoteViews;
public class NotificationCompat {
/**
* Bit to be bitwise-ored into the {@link Notification#flags} field that should be set if
* this notification represents a high-priority event that may be shown to the user even
* if notifications are otherwise unavailable (that is, when the status bar is hidden).
* This flag is ideally used in conjunction with fullScreenIntent.
*
* <p>This will only be respected on API level 9 and above.</p>
*/
public static final int FLAG_HIGH_PRIORITY = 0x00000080;
private static final NotificationCompatImpl IMPL;
interface NotificationCompatImpl {
public Notification getNotification(Builder b);
}
static class NotificationCompatImplBase implements NotificationCompatImpl {
public Notification getNotification(Builder b) {
Notification result = (Notification) b.mNotification;
result.setLatestEventInfo(b.mContext, b.mContentTitle,
b.mContentText, b.mContentIntent);
return result;
}
}
static class NotificationCompatImplHoneycomb implements NotificationCompatImpl {
public Notification getNotification(Builder b) {
return NotificationCompatHoneycomb.add(b.mContext, b.mNotification,
b.mContentTitle, b.mContentText, b.mContentInfo, b.mTickerView,
b.mNumber, b.mContentIntent, b.mFullScreenIntent, b.mLargeIcon);
}
}
static {
if (Build.VERSION.SDK_INT >= 11) {
IMPL = new NotificationCompatImplHoneycomb();
} else {
IMPL = new NotificationCompatImplBase();
}
}
/**
* Builder class for {@link Notification} objects. Allows easier control over
* all the flags, as well as help constructing the typical notification layouts.
*/
public static class Builder {
Context mContext;
CharSequence mContentTitle;
CharSequence mContentText;
PendingIntent mContentIntent;
PendingIntent mFullScreenIntent;
RemoteViews mTickerView;
Bitmap mLargeIcon;
CharSequence mContentInfo;
int mNumber;
Notification mNotification = new Notification();
/**
* Constructor.
*
* Automatically sets the when field to {@link System#currentTimeMillis()
* System.currentTimeMillis()} and the audio stream to the
* {@link Notification#STREAM_DEFAULT}.
*
* @param context A {@link Context} that will be used to construct the
* RemoteViews. The Context will not be held past the lifetime of this
* Builder object.
*/
public Builder(Context context) {
mContext = context;
// Set defaults to match the defaults of a Notification
mNotification.when = System.currentTimeMillis();
mNotification.audioStreamType = Notification.STREAM_DEFAULT;
}
/**
* Set the time that the event occurred. Notifications in the panel are
* sorted by this time.
*/
public Builder setWhen(long when) {
mNotification.when = when;
return this;
}
/**
* Set the small icon to use in the notification layouts. Different classes of devices
* may return different sizes. See the UX guidelines for more information on how to
* design these icons.
*
* @param icon A resource ID in the application's package of the drawble to use.
*/
public Builder setSmallIcon(int icon) {
mNotification.icon = icon;
return this;
}
/**
* A variant of {@link #setSmallIcon(int) setSmallIcon(int)} that takes an additional
* level parameter for when the icon is a {@link android.graphics.drawable.LevelListDrawable
* LevelListDrawable}.
*
* @param icon A resource ID in the application's package of the drawble to use.
* @param level The level to use for the icon.
*
* @see android.graphics.drawable.LevelListDrawable
*/
public Builder setSmallIcon(int icon, int level) {
mNotification.icon = icon;
mNotification.iconLevel = level;
return this;
}
/**
* Set the title (first row) of the notification, in a standard notification.
*/
public Builder setContentTitle(CharSequence title) {
mContentTitle = title;
return this;
}
/**
* Set the text (second row) of the notification, in a standard notification.
*/
public Builder setContentText(CharSequence text) {
mContentText = text;
return this;
}
/**
* Set the large number at the right-hand side of the notification. This is
* equivalent to setContentInfo, although it might show the number in a different
* font size for readability.
*/
public Builder setNumber(int number) {
mNumber = number;
return this;
}
/**
* Set the large text at the right-hand side of the notification.
*/
public Builder setContentInfo(CharSequence info) {
mContentInfo = info;
return this;
}
/**
* Set the progress this notification represents, which may be
* represented as a {@link ProgressBar}.
*/
/* TODO
public Builder setProgress(int max, int progress, boolean indeterminate) {
mProgressMax = max;
mProgress = progress;
mProgressIndeterminate = indeterminate;
return this;
}*/
/**
* Supply a custom RemoteViews to use instead of the standard one.
*/
public Builder setContent(RemoteViews views) {
mNotification.contentView = views;
return this;
}
/**
* Supply a {@link PendingIntent} to send when the notification is clicked.
* If you do not supply an intent, you can now add PendingIntents to individual
* views to be launched when clicked by calling {@link RemoteViews#setOnClickPendingIntent
* RemoteViews.setOnClickPendingIntent(int,PendingIntent)}. Be sure to
* read {@link Notification#contentIntent Notification.contentIntent} for
* how to correctly use this.
*/
public Builder setContentIntent(PendingIntent intent) {
mContentIntent = intent;
return this;
}
/**
* Supply a {@link PendingIntent} to send when the notification is cleared by the user
* directly from the notification panel. For example, this intent is sent when the user
* clicks the "Clear all" button, or the individual "X" buttons on notifications. This
* intent is not sent when the application calls {@link NotificationManager#cancel
* NotificationManager.cancel(int)}.
*/
public Builder setDeleteIntent(PendingIntent intent) {
mNotification.deleteIntent = intent;
return this;
}
/**
* An intent to launch instead of posting the notification to the status bar.
* Only for use with extremely high-priority notifications demanding the user's
* <strong>immediate</strong> attention, such as an incoming phone call or
* alarm clock that the user has explicitly set to a particular time.
* If this facility is used for something else, please give the user an option
* to turn it off and use a normal notification, as this can be extremely
* disruptive.
*
* @param intent The pending intent to launch.
* @param highPriority Passing true will cause this notification to be sent
* even if other notifications are suppressed.
*/
public Builder setFullScreenIntent(PendingIntent intent, boolean highPriority) {
mFullScreenIntent = intent;
setFlag(FLAG_HIGH_PRIORITY, highPriority);
return this;
}
/**
* Set the text that is displayed in the status bar when the notification first
* arrives.
*/
public Builder setTicker(CharSequence tickerText) {
mNotification.tickerText = tickerText;
return this;
}
/**
* Set the text that is displayed in the status bar when the notification first
* arrives, and also a RemoteViews object that may be displayed instead on some
* devices.
*/
public Builder setTicker(CharSequence tickerText, RemoteViews views) {
mNotification.tickerText = tickerText;
mTickerView = views;
return this;
}
/**
* Set the large icon that is shown in the ticker and notification.
*/
public Builder setLargeIcon(Bitmap icon) {
mLargeIcon = icon;
return this;
}
/**
* Set the sound to play. It will play on the default stream.
*/
public Builder setSound(Uri sound) {
mNotification.sound = sound;
mNotification.audioStreamType = Notification.STREAM_DEFAULT;
return this;
}
/**
* Set the sound to play. It will play on the stream you supply.
*
* @see #STREAM_DEFAULT
* @see AudioManager for the <code>STREAM_</code> constants.
*/
public Builder setSound(Uri sound, int streamType) {
mNotification.sound = sound;
mNotification.audioStreamType = streamType;
return this;
}
/**
* Set the vibration pattern to use.
*
* @see android.os.Vibrator for a discussion of the <code>pattern</code>
* parameter.
*/
public Builder setVibrate(long[] pattern) {
mNotification.vibrate = pattern;
return this;
}
/**
* Set the argb value that you would like the LED on the device to blnk, as well as the
* rate. The rate is specified in terms of the number of milliseconds to be on
* and then the number of milliseconds to be off.
*/
public Builder setLights(int argb, int onMs, int offMs) {
mNotification.ledARGB = argb;
mNotification.ledOnMS = onMs;
mNotification.ledOffMS = offMs;
boolean showLights = mNotification.ledOnMS != 0 && mNotification.ledOffMS != 0;
mNotification.flags = (mNotification.flags & ~Notification.FLAG_SHOW_LIGHTS) |
(showLights ? Notification.FLAG_SHOW_LIGHTS : 0);
return this;
}
/**
* Set whether this is an ongoing notification.
*
* <p>Ongoing notifications differ from regular notifications in the following ways:
* <ul>
* <li>Ongoing notifications are sorted above the regular notifications in the
* notification panel.</li>
* <li>Ongoing notifications do not have an 'X' close button, and are not affected
* by the "Clear all" button.
* </ul>
*/
public Builder setOngoing(boolean ongoing) {
setFlag(Notification.FLAG_ONGOING_EVENT, ongoing);
return this;
}
/**
* Set this flag if you would only like the sound, vibrate
* and ticker to be played if the notification is not already showing.
*/
public Builder setOnlyAlertOnce(boolean onlyAlertOnce) {
setFlag(Notification.FLAG_ONLY_ALERT_ONCE, onlyAlertOnce);
return this;
}
/**
* Setting this flag will make it so the notification is automatically
* canceled when the user clicks it in the panel. The PendingIntent
* set with {@link #setDeleteIntent} will be broadcast when the notification
* is canceled.
*/
public Builder setAutoCancel(boolean autoCancel) {
setFlag(Notification.FLAG_AUTO_CANCEL, autoCancel);
return this;
}
/**
* Set the default notification options that will be used.
* <p>
* The value should be one or more of the following fields combined with
* bitwise-or:
* {@link Notification#DEFAULT_SOUND}, {@link Notification#DEFAULT_VIBRATE},
* {@link Notification#DEFAULT_LIGHTS}.
* <p>
* For all default values, use {@link Notification#DEFAULT_ALL}.
*/
public Builder setDefaults(int defaults) {
mNotification.defaults = defaults;
if ((defaults & Notification.DEFAULT_LIGHTS) != 0) {
mNotification.flags |= Notification.FLAG_SHOW_LIGHTS;
}
return this;
}
private void setFlag(int mask, boolean value) {
if (value) {
mNotification.flags |= mask;
} else {
mNotification.flags &= ~mask;
}
}
/**
* Combine all of the options that have been set and return a new {@link Notification}
* object.
*/
public Notification getNotification() {
return (Notification) IMPL.getNotification(this);
}
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright (C) 2012 The Android Open Source Project
*
* 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 anroid.v4.compat;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
import android.graphics.Bitmap;
import android.widget.RemoteViews;
class NotificationCompatHoneycomb {
static Notification add(Context context, Notification n,
CharSequence contentTitle, CharSequence contentText, CharSequence contentInfo,
RemoteViews tickerView, int number,
PendingIntent contentIntent, PendingIntent fullScreenIntent, Bitmap largeIcon) {
Notification.Builder b = new Notification.Builder(context)
.setWhen(n.when)
.setSmallIcon(n.icon, n.iconLevel)
.setContent(n.contentView)
.setTicker(n.tickerText, tickerView)
.setSound(n.sound, n.audioStreamType)
.setVibrate(n.vibrate)
.setLights(n.ledARGB, n.ledOnMS, n.ledOffMS)
.setOngoing((n.flags & Notification.FLAG_ONGOING_EVENT) != 0)
.setOnlyAlertOnce((n.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0)
.setAutoCancel((n.flags & Notification.FLAG_AUTO_CANCEL) != 0)
.setDefaults(n.defaults)
.setContentTitle(contentTitle)
.setContentText(contentText)
.setContentInfo(contentInfo)
.setContentIntent(contentIntent)
.setDeleteIntent(n.deleteIntent)
.setFullScreenIntent(fullScreenIntent,
(n.flags & Notification.FLAG_HIGH_PRIORITY) != 0)
.setLargeIcon(largeIcon)
.setNumber(number);
return b.getNotification();
}
}

View File

@@ -14,7 +14,6 @@ import java.util.Date;
import java.util.Properties; import java.util.Properties;
import android.app.Activity; import android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.app.Service; import android.app.Service;
@@ -28,203 +27,212 @@ import android.content.res.AssetManager;
import android.content.res.Resources; import android.content.res.Resources;
import android.os.Build; import android.os.Build;
import android.util.Log; import android.util.Log;
import anroid.v4.compat.NotificationCompat;
/** /**
* This is Twenty Codes, LLC Exception Handler of Awesomeness! * This is Twenty Codes, LLC Exception Handler of Awesomeness! This class will
* This class will be used to generate reports that will be emailed to us via the users email client after the users approval * be used to generate reports that will be emailed to us via the users email
* client after the users approval
*
* @author ricky barrette * @author ricky barrette
*/ */
public class ExceptionHandler implements UncaughtExceptionHandler, Runnable { public class ExceptionHandler implements UncaughtExceptionHandler, Runnable {
private static final String MSG_SUBJECT_TAG = "Exception Report"; private static final String MSG_SUBJECT_TAG = "Exception Report";
private static final String MSG_BODY = "Just click send to help make this application better. "+ private static final String MSG_BODY = "Just click send to help make this application better. "
"No personal information is being sent (you can check by reading the rest of the email)."; + "No personal information is being sent (you can check by reading the rest of the email).";
protected static final int SIMPLE_NOTFICATION_ID = 45684645; protected static final int SIMPLE_NOTFICATION_ID = 45684645;
private Thread.UncaughtExceptionHandler mDefaultUEH; private final Thread.UncaughtExceptionHandler mDefaultUEH;
private Activity mApp= null; private Activity mApp = null;
private Service mService = null; private Service mService = null;
private BroadcastReceiver mBroadcastReceiver = null; private BroadcastReceiver mBroadcastReceiver = null;
private Context mContext; private final Context mContext;
private Report mReport; private Report mReport;
private static final String TAG = "ExceptionHandler"; private static final String TAG = "ExceptionHandler";
private String mURL = null; private String mURL = null;
private String mEmail; private String mEmail;
private String mAppName; private String mAppName;
private String mTracker; private String mTracker;
/**
* Creates a new ExceptionHandler
* @param app
* @author ricky barrette
*/
public ExceptionHandler(Activity app) {
this.mDefaultUEH = Thread.getDefaultUncaughtExceptionHandler();
this.mApp = app;
this.mContext = app;
parseProperties();
}
/** /**
* Creates a new ExceptionHandler * Creates a new ExceptionHandler
*
* @param app
* @author ricky barrette
*/
public ExceptionHandler(final Activity app) {
mDefaultUEH = Thread.getDefaultUncaughtExceptionHandler();
mApp = app;
mContext = app;
parseProperties();
}
/**
* Creates a new ExceptionHandler
*
* @param broadcastReceiver * @param broadcastReceiver
* @author ricky barrette * @author ricky barrette
*/ */
public ExceptionHandler(BroadcastReceiver broadcastReceiver, Context context){ public ExceptionHandler(final BroadcastReceiver broadcastReceiver, final Context context) {
this.mDefaultUEH = Thread.getDefaultUncaughtExceptionHandler(); mDefaultUEH = Thread.getDefaultUncaughtExceptionHandler();
this.mBroadcastReceiver = broadcastReceiver; mBroadcastReceiver = broadcastReceiver;
this.mContext = context; mContext = context;
parseProperties(); parseProperties();
} }
/** /**
* Creates a new ExceptionHandler * Creates a new ExceptionHandler
*
* @param service * @param service
* @author ricky barrette * @author ricky barrette
*/ */
public ExceptionHandler(Service service){ public ExceptionHandler(final Service service) {
this.mDefaultUEH = Thread.getDefaultUncaughtExceptionHandler(); mDefaultUEH = Thread.getDefaultUncaughtExceptionHandler();
this.mService = service; mService = service;
this.mContext = service; mContext = service;
parseProperties(); parseProperties();
} }
/** /**
* Generates an email from the report * Generates an email from the report
*
* @author ricky barrette * @author ricky barrette
*/ */
private void displayEmailNotification(){ private void displayEmailNotification() {
Log.i(TAG, "displayEmailNotification"); Log.i(TAG, "displayEmailNotification");
CharSequence title = null; CharSequence title = null;
if(mApp != null) if (mApp != null)
title = mApp.getTitle(); title = mApp.getTitle();
if(mService != null) if (mService != null)
title = mService.getClass().getName(); title = mService.getClass().getName();
if(mBroadcastReceiver != null) if (mBroadcastReceiver != null)
title = mBroadcastReceiver.getClass().getName(); title = mBroadcastReceiver.getClass().getName();
Intent intent = new Intent(Intent.ACTION_SEND); final Intent intent = new Intent(Intent.ACTION_SEND);
String theSubject = title + " " + MSG_SUBJECT_TAG; final String theSubject = title + " " + MSG_SUBJECT_TAG;
String theBody = "\n\n"+MSG_BODY+this.mReport.toString(); final String theBody = "\n\n" + MSG_BODY + mReport.toString();
intent.putExtra(Intent.EXTRA_EMAIL,new String[] {this.mEmail}); intent.putExtra(Intent.EXTRA_EMAIL, new String[] { mEmail });
intent.putExtra(Intent.EXTRA_TEXT, theBody); intent.putExtra(Intent.EXTRA_TEXT, theBody);
intent.putExtra(Intent.EXTRA_SUBJECT, theSubject); intent.putExtra(Intent.EXTRA_SUBJECT, theSubject);
intent.setType("message/rfc822"); intent.setType("message/rfc822");
displayNotification(intent); displayNotification(intent);
} }
/** /**
* displays an notification in the status bar, letting the user know that there was an issue * displays an notification in the status bar, letting the user know that
* there was an issue
*
* @param generatedReportIntent * @param generatedReportIntent
*/ */
@SuppressWarnings("deprecation") private void displayNotification(final Intent generatedReportIntent) {
private void displayNotification(Intent generatedReportIntent) {
Log.i(TAG, "displayNotification"); Log.i(TAG, "displayNotification");
Context context = mContext.getApplicationContext(); final Context context = mContext.getApplicationContext();
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); final NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
Notification notifyDetails = new Notification(android.R.drawable.stat_notify_error, context.getString(R.string.sorry), System.currentTimeMillis()); final PendingIntent intent = PendingIntent.getActivity(context, 0, generatedReportIntent, android.content.Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent intent = PendingIntent.getActivity(context, 0, generatedReportIntent, android.content.Intent.FLAG_ACTIVITY_NEW_TASK); final NotificationCompat.Builder builder = new NotificationCompat.Builder(context).setContentTitle(context.getString(R.string.crash))
notifyDetails.setLatestEventInfo(context, context.getString(R.string.crash), context.getString(R.string.sorry), intent); .setContentText(context.getString(R.string.sorry)).setTicker(context.getString(R.string.crash)).setSmallIcon(android.R.drawable.stat_notify_error)
notifyDetails.flags |= Notification.FLAG_AUTO_CANCEL; .setWhen(System.currentTimeMillis()).setAutoCancel(true).setContentIntent(intent);
notificationManager.notify(SIMPLE_NOTFICATION_ID, notifyDetails); notificationManager.notify(SIMPLE_NOTFICATION_ID, builder.getNotification());
} }
/** /**
* parses in the exception handler options from the client application's assets folder. /assets/exceptionhandler.properties * parses in the exception handler options from the client application's
* assets folder. /assets/exceptionhandler.properties
*
* @author ricky barrette * @author ricky barrette
*/ */
private void parseProperties() { private void parseProperties() {
Resources resources = this.mContext.getResources(); final Resources resources = mContext.getResources();
AssetManager assetManager = resources.getAssets(); final AssetManager assetManager = resources.getAssets();
// Read from the /assets directory // Read from the /assets directory
try { try {
InputStream inputStream = assetManager.open("exceptionhandler.properties"); final InputStream inputStream = assetManager.open("exceptionhandler.properties");
Properties properties = new Properties(); final Properties properties = new Properties();
properties.load(inputStream); properties.load(inputStream);
this.mURL = properties.getProperty("server"); mURL = properties.getProperty("server");
this.mEmail = properties.getProperty("email"); mEmail = properties.getProperty("email");
this.mAppName = properties.getProperty("app"); mAppName = properties.getProperty("app");
this.mTracker = properties.getProperty("tracker"); mTracker = properties.getProperty("tracker");
} catch (IOException e) { } catch (final IOException e) {
Log.e(TAG, "Failed to open exceptionhandler.properties"); Log.e(TAG, "Failed to open exceptionhandler.properties");
e.printStackTrace(); e.printStackTrace();
} }
} }
@Override
public void run() { public void run() {
if(this.mEmail == null) if (mEmail == null)
displayNotification(new Intent(this.mContext, ExceptionReportActivity.class).putExtra("report", this.mReport)); displayNotification(new Intent(mContext, ExceptionReportActivity.class).putExtra("report", mReport));
else else
displayEmailNotification(); displayEmailNotification();
} }
/** /**
* Called when there is an uncaught exception. * Called when there is an uncaught exception. (non-Javadoc)
* (non-Javadoc) *
* @see java.lang.Thread.UncaughtExceptionHandler#uncaughtException(java.lang.Thread, java.lang.Throwable) * @see java.lang.Thread.UncaughtExceptionHandler#uncaughtException(java.lang.Thread,
* java.lang.Throwable)
* @author ricky barrette * @author ricky barrette
*/ */
@Override @Override
public void uncaughtException(Thread t, Throwable e) { public void uncaughtException(final Thread t, final Throwable e) {
Log.d(TAG, "uncaughtException()"); Log.d(TAG, "uncaughtException()");
// Log.d(TAG,"mURL = "+ this.mURL);
// Log.d(TAG,"mEmail = "+ this.mEmail);
Date theDate = new Date(); // Log.d(TAG,"mURL = "+ this.mURL);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd_HH.mm.ss_zzz"); // Log.d(TAG,"mEmail = "+ this.mEmail);
PackageManager pm = mContext.getPackageManager();
final Date theDate = new Date();
//app environment; final SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd_HH.mm.ss_zzz");
final PackageManager pm = mContext.getPackageManager();
// app environment;
PackageInfo pi; PackageInfo pi;
try { try {
pi = pm.getPackageInfo(mContext.getPackageName(), 0); pi = pm.getPackageInfo(mContext.getPackageName(), 0);
} catch (NameNotFoundException eNnf) { } catch (final NameNotFoundException eNnf) {
//doubt this will ever run since we want info about our own package // doubt this will ever run since we want info about our own package
pi = new PackageInfo(); pi = new PackageInfo();
pi.versionName = "unknown"; pi.versionName = "unknown";
pi.versionCode = 69; pi.versionCode = 69;
} }
StringBuffer report = new StringBuffer(); final StringBuffer report = new StringBuffer();
for (StackTraceElement item : e.getStackTrace()) for (final StackTraceElement item : e.getStackTrace())
report.append("at "+item.toString() + "\n"); report.append("at " + item.toString() + "\n");
StringBuffer causereport = new StringBuffer(); final StringBuffer causereport = new StringBuffer();
Throwable cause = e.getCause(); final Throwable cause = e.getCause();
if (cause != null) { if (cause != null) {
causereport.append(cause.toString() + "\n \n"); causereport.append(cause.toString() + "\n \n");
for (StackTraceElement item : cause.getStackTrace()) for (final StackTraceElement item : cause.getStackTrace())
causereport.append("at "+item.toString() + "\n"); causereport.append("at " + item.toString() + "\n");
} }
//generate the report // generate the report
this.mReport = new Report(mURL).generateReport(e.toString(), report.toString(), causereport.toString(), sdf.format(theDate), Build.FINGERPRINT, pi.versionName+"b"+pi.versionCode, mAppName != null ? mAppName : mContext.getPackageName(), this.mTracker, mContext.getPackageName()); mReport = new Report(mURL).generateReport(e.toString(), report.toString(), causereport.toString(), sdf.format(theDate), Build.FINGERPRINT, pi.versionName + "b"
+ pi.versionCode, mAppName != null ? mAppName : mContext.getPackageName(), mTracker, mContext.getPackageName());
//try to send file contents via email (need to do so via the UI thread)
if(this.mApp != null){ // try to send file contents via email (need to do so via the UI thread)
this.mApp.runOnUiThread(this); if (mApp != null)
} mApp.runOnUiThread(this);
if (mService != null)
if(this.mService != null){ if (mEmail == null)
if(this.mEmail == null) displayNotification(new Intent(mContext, ExceptionReportActivity.class).putExtra("report", mReport));
displayNotification(new Intent(this.mContext, ExceptionReportActivity.class).putExtra("report", this.mReport));
else else
displayEmailNotification(); displayEmailNotification();
}
if (mBroadcastReceiver != null)
if(this.mBroadcastReceiver != null){ if (mEmail == null)
if(this.mEmail == null) displayNotification(new Intent(mContext, ExceptionReportActivity.class).putExtra("report", mReport));
displayNotification(new Intent(this.mContext, ExceptionReportActivity.class).putExtra("report", this.mReport));
else else
displayEmailNotification(); displayEmailNotification();
}
// do not forget to pass this exception through up the chain
//do not forget to pass this exception through up the chain mDefaultUEH.uncaughtException(t, e);
mDefaultUEH.uncaughtException(t,e);
} }
} }

View File

@@ -16,7 +16,9 @@ import android.widget.EditText;
import android.widget.ListView; import android.widget.ListView;
/** /**
* This activity will be used to present the user with the exception report, and allows them to send it, or not * This activity will be used to present the user with the exception report, and
* allows them to send it, or not
*
* @author ricky barrette * @author ricky barrette
*/ */
public class ExceptionReportActivity extends Activity implements OnClickListener { public class ExceptionReportActivity extends Activity implements OnClickListener {
@@ -26,37 +28,39 @@ public class ExceptionReportActivity extends Activity implements OnClickListener
/** /**
* (non-Javadoc) * (non-Javadoc)
* @see android.app.Activity#onCreate(android.os.Bundle) *
* @author ricky barrette
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "onCreate()");
super.onCreate(savedInstanceState);
this.mReport = (Report) this.getIntent().getParcelableExtra("report");
if(this.getIntent().hasExtra("display"))
this.setContentView(R.layout.list);
else {
this.setContentView(R.layout.exception_activity);
this.findViewById(R.id.send).setOnClickListener(this);
}
ListView list = (ListView) this.findViewById(android.R.id.list);
list.setAdapter(new ReportAdapter(this, this.mReport.getReport()));
list.setClickable(false);
}
/**
* (non-Javadoc)
* @see android.view.View.OnClickListener#onClick(android.view.View) * @see android.view.View.OnClickListener#onClick(android.view.View)
* @author ricky barrette * @author ricky barrette
*/ */
@Override @Override
public void onClick(View v) { public void onClick(final View v) {
EditText description = (EditText) findViewById(R.id.description); final EditText description = (EditText) findViewById(R.id.description);
this.mReport.setDescription(description.getText().toString()); mReport.setDescription(description.getText().toString());
v.setEnabled(false); v.setEnabled(false);
this.startService(new Intent(this, ReportPostingService.class).putExtra("report", this.mReport)); startService(new Intent(this, ReportPostingService.class).putExtra("report", mReport));
this.finish(); finish();
}
/**
* (non-Javadoc)
*
* @see android.app.Activity#onCreate(android.os.Bundle)
* @author ricky barrette
*/
@Override
protected void onCreate(final Bundle savedInstanceState) {
Log.d(TAG, "onCreate()");
super.onCreate(savedInstanceState);
mReport = (Report) getIntent().getParcelableExtra("report");
if (getIntent().hasExtra("display"))
this.setContentView(R.layout.list);
else {
this.setContentView(R.layout.exception_activity);
findViewById(R.id.send).setOnClickListener(this);
}
final ListView list = (ListView) findViewById(android.R.id.list);
list.setAdapter(new ReportAdapter(this, mReport.getReport()));
list.setClickable(false);
} }
} }

View File

@@ -29,96 +29,105 @@ import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
/** /**
* This class will be used to generate a report, and insert it into our exception report database * This class will be used to generate a report, and insert it into our
* exception report database
*
* @author ricky barrette * @author ricky barrette
*/ */
public class Report implements Parcelable{ public class Report implements Parcelable {
private final String mUrl; private final String mUrl;
private ArrayList<ReportItem> mReport; private ArrayList<ReportItem> mReport;
public static final Parcelable.Creator<Report> CREATOR = new Parcelable.Creator<Report>() { public static final Parcelable.Creator<Report> CREATOR = new Parcelable.Creator<Report>() {
public Report createFromParcel(Parcel in) { @Override
public Report createFromParcel(final Parcel in) {
return new Report(in); return new Report(in);
} }
public Report[] newArray(int size) { @Override
public Report[] newArray(final int size) {
return new Report[size]; return new Report[size];
} }
}; };
/** /**
* Creates a new Report * Creates a new Report
*
* @param in * @param in
* @author ricky barrette * @author ricky barrette
*/ */
public Report(Parcel in){ public Report(final Parcel in) {
this.mUrl = in.readString(); mUrl = in.readString();
this.mReport = new ArrayList<ReportItem>(); mReport = new ArrayList<ReportItem>();
in.readTypedList(this.mReport, ReportItem.CREATOR); in.readTypedList(mReport, ReportItem.CREATOR);
} }
/** /**
* Creates a new Report * Creates a new Report
*
* @author ricky barrette * @author ricky barrette
*/ */
public Report(String url) { public Report(final String url) {
// Log.d(TAG, url); // Log.d(TAG, url);
this.mUrl = url; mUrl = url;
} }
@Override @Override
public int describeContents() { public int describeContents() {
return 0; return 0;
} }
/** /**
* Files the report with the remote database * Files the report with the remote database
*
* @author ricky barrette * @author ricky barrette
* @throws IOException * @throws IOException
* @throws ClientProtocolException * @throws ClientProtocolException
* @return String result * @return String result
*/ */
public String file() throws ClientProtocolException, IOException{ public String file() throws ClientProtocolException, IOException {
HttpClient httpclient = new DefaultHttpClient(); final HttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost(mUrl); final HttpPost httppost = new HttpPost(mUrl);
httppost.setEntity(new UrlEncodedFormEntity(getNameValuePairs())); httppost.setEntity(new UrlEncodedFormEntity(getNameValuePairs()));
//return the results // return the results
HttpResponse response = httpclient.execute(httppost); final HttpResponse response = httpclient.execute(httppost);
HttpEntity entity = response.getEntity(); final HttpEntity entity = response.getEntity();
InputStream is = entity.getContent(); final InputStream is = entity.getContent();
BufferedReader reader = new BufferedReader(new InputStreamReader(is,"iso-8859-1"),8); final BufferedReader reader = new BufferedReader(new InputStreamReader(is, "iso-8859-1"), 8);
StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
sb.append(reader.readLine() + "\n"); sb.append(reader.readLine() + "\n");
String line="0"; String line = "0";
while ((line = reader.readLine()) != null) while ((line = reader.readLine()) != null)
sb.append(line + "\n"); sb.append(line + "\n");
is.close(); is.close();
reader.close(); reader.close();
return sb.toString(); return sb.toString();
} }
/** /**
* Generates a report to be displayed form a downloaded JSON object * Generates a report to be displayed form a downloaded JSON object
*
* @param report * @param report
* @return * @return
* @author ricky barrette * @author ricky barrette
* @throws JSONException * @throws JSONException
*/ */
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
public Report generateReport(JSONObject report) throws JSONException{ public Report generateReport(final JSONObject report) throws JSONException {
this.mReport = new ArrayList<ReportItem>(); mReport = new ArrayList<ReportItem>();
Iterator iter = report.keys(); final Iterator iter = report.keys();
while(iter.hasNext()){ while (iter.hasNext()) {
String key = (String)iter.next(); final String key = (String) iter.next();
this.mReport.add(new ReportItem(key , report.getString(key))); mReport.add(new ReportItem(key, report.getString(key)));
} }
return this; return this;
} }
/** /**
* Generates a report to be sent. * Generates a report to be sent.
*
* @param msg * @param msg
* @param stackTrace * @param stackTrace
* @param cause * @param cause
@@ -128,29 +137,31 @@ public class Report implements Parcelable{
* @return this * @return this
* @author ricky barrette * @author ricky barrette
*/ */
public Report generateReport(String msg, String stackTrace, String cause, String date, String device, String appVersion, String app, String tracker, String packageName){ public Report generateReport(final String msg, final String stackTrace, final String cause, final String date, final String device, final String appVersion,
this.mReport = new ArrayList<ReportItem>(); final String app, final String tracker, final String packageName) {
this.mReport.add(new ReportItem("app",app)); mReport = new ArrayList<ReportItem>();
this.mReport.add(new ReportItem("version",appVersion)); mReport.add(new ReportItem("app", app));
this.mReport.add(new ReportItem("date",date)); mReport.add(new ReportItem("version", appVersion));
this.mReport.add(new ReportItem("msg",msg)); mReport.add(new ReportItem("date", date));
this.mReport.add(new ReportItem("stackTrace",stackTrace)); mReport.add(new ReportItem("msg", msg));
this.mReport.add(new ReportItem("cause",cause)); mReport.add(new ReportItem("stackTrace", stackTrace));
this.mReport.add(new ReportItem("device",device)); mReport.add(new ReportItem("cause", cause));
this.mReport.add(new ReportItem("tracker",tracker)); mReport.add(new ReportItem("device", device));
this.mReport.add(new ReportItem("package",packageName)); mReport.add(new ReportItem("tracker", tracker));
mReport.add(new ReportItem("package", packageName));
return this; return this;
} }
/** /**
* Extracts the name value pairs from the report bundle * Extracts the name value pairs from the report bundle
*
* @return * @return
* @author ricky barrette * @author ricky barrette
*/ */
private ArrayList<NameValuePair> getNameValuePairs() { private ArrayList<NameValuePair> getNameValuePairs() {
ArrayList<NameValuePair> list = new ArrayList<NameValuePair>(); final ArrayList<NameValuePair> list = new ArrayList<NameValuePair>();
for(ReportItem entry : this.mReport) for (final ReportItem entry : mReport)
list.add(new BasicNameValuePair(entry.getKey(), (String) entry.getValue())); list.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
return list; return list;
} }
@@ -158,17 +169,18 @@ public class Report implements Parcelable{
* @return the generated exception report * @return the generated exception report
* @author ricky barrette * @author ricky barrette
*/ */
public ArrayList<NameValuePair> getReport(){ public ArrayList<NameValuePair> getReport() {
return getNameValuePairs(); return getNameValuePairs();
} }
/** /**
* Sets the optional users description of what happened * Sets the optional users description of what happened
*
* @param string * @param string
* @author ricky barrette * @author ricky barrette
*/ */
public Report setDescription(String description) { public Report setDescription(final String description) {
this.mReport.add(new ReportItem("description", description)); mReport.add(new ReportItem("description", description));
return this; return this;
} }
@@ -177,18 +189,18 @@ public class Report implements Parcelable{
* @author ricky barrette * @author ricky barrette
*/ */
@Override @Override
public String toString(){ public String toString() {
StringBuilder s = new StringBuilder(); final StringBuilder s = new StringBuilder();
for(ReportItem item : this.mReport){ for (final ReportItem item : mReport) {
s.append("\n\n-----"+ item.getKey()+"-----"); s.append("\n\n-----" + item.getKey() + "-----");
s.append("\n"+item.getValue()); s.append("\n" + item.getValue());
} }
return s.toString(); return s.toString();
} }
@Override @Override
public void writeToParcel(Parcel out, int flags) { public void writeToParcel(final Parcel out, final int flags) {
out.writeString(this.mUrl); out.writeString(mUrl);
out.writeTypedList(this.mReport); out.writeTypedList(mReport);
} }
} }

View File

@@ -17,55 +17,65 @@ import android.view.ViewGroup;
import android.widget.BaseAdapter; import android.widget.BaseAdapter;
import android.widget.TextView; import android.widget.TextView;
import com.TwentyCodes.android.exception.R;
/** /**
* This class will be used to populate a custom Listview used to display the Generated exception report * This class will be used to populate a custom Listview used to display the
* Generated exception report
*
* @author ricky barrette * @author ricky barrette
*/ */
public class ReportAdapter extends BaseAdapter { public class ReportAdapter extends BaseAdapter {
private ArrayList<NameValuePair> mReport; class ViewHolder {
private LayoutInflater mInflater; TextView title;
TextView body;
}
private final ArrayList<NameValuePair> mReport;
private final LayoutInflater mInflater;
/** /**
* Creates a new ReportAdapter * Creates a new ReportAdapter
*
* @author ricky barrette * @author ricky barrette
*/ */
public ReportAdapter(Context context, ArrayList<NameValuePair> report) { public ReportAdapter(final Context context, final ArrayList<NameValuePair> report) {
super(); super();
// Cache the LayoutInflate to avoid asking for a new one each time. // Cache the LayoutInflate to avoid asking for a new one each time.
this.mInflater = LayoutInflater.from(context); mInflater = LayoutInflater.from(context);
this.mReport = report; mReport = report;
} }
/** /**
* (non-Javadoc) * (non-Javadoc)
*
* @see android.widget.Adapter#getCount() * @see android.widget.Adapter#getCount()
* @author ricky barrette * @author ricky barrette
*/ */
@Override @Override
public int getCount() { public int getCount() {
return this.mReport.size(); return mReport.size();
} }
/** /**
* (non-Javadoc) * (non-Javadoc)
*
* @see android.widget.Adapter#getItem(int) * @see android.widget.Adapter#getItem(int)
* @author ricky barrette * @author ricky barrette
*/ */
@Override @Override
public NameValuePair getItem(int index) { public NameValuePair getItem(final int index) {
return this.mReport.get(index); return mReport.get(index);
} }
/** /**
* (non-Javadoc) * (non-Javadoc)
*
* @see android.widget.Adapter#getItemId(int) * @see android.widget.Adapter#getItemId(int)
* @author ricky barrette * @author ricky barrette
*/ */
@Override @Override
public long getItemId(int index) { public long getItemId(final int index) {
return index; return index;
} }
@@ -73,40 +83,38 @@ public class ReportAdapter extends BaseAdapter {
* *
*/ */
@Override @Override
public View getView(final int position, View convertView, ViewGroup parent) { public View getView(final int position, View convertView, final ViewGroup parent) {
// A ViewHolder keeps references to children views to avoid unnecessary calls to findViewById() on each row.
ViewHolder holder;
// When convertView is not null, we can reuse it directly, there is no need // A ViewHolder keeps references to children views to avoid unnecessary
// to reinflate it. We only inflate a new View when the convertView supplied // calls to findViewById() on each row.
// by ListView is null. ViewHolder holder;
if (convertView == null) {
convertView = mInflater.inflate(R.layout.exception_list_item, null);
// Creates a ViewHolder and store references to the two children views // When convertView is not null, we can reuse it directly, there is no
// we want to bind data to. // need
holder = new ViewHolder(); // to reinflate it. We only inflate a new View when the convertView
holder.title = (TextView) convertView.findViewById(R.id.exception_title); // supplied
holder.body = (TextView) convertView.findViewById(R.id.exception_text); // by ListView is null.
if (convertView == null) {
convertView = mInflater.inflate(R.layout.exception_list_item, null);
convertView.setTag(holder); // Creates a ViewHolder and store references to the two children
} else { // views
holder = (ViewHolder) convertView.getTag(); // we want to bind data to.
} holder = new ViewHolder();
holder.title = (TextView) convertView.findViewById(R.id.exception_title);
/* holder.body = (TextView) convertView.findViewById(R.id.exception_text);
* Bind the data efficiently with the holder.
*/ convertView.setTag(holder);
holder.title.setText(getItem(position).getName()); } else
holder.body.setText(getItem(position).getValue()); holder = (ViewHolder) convertView.getTag();
return convertView; /*
* Bind the data efficiently with the holder.
*/
holder.title.setText(getItem(position).getName());
holder.body.setText(getItem(position).getValue());
return convertView;
} }
class ViewHolder {
TextView title;
TextView body;
}
} }

View File

@@ -10,44 +10,52 @@ import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
/** /**
* This class will represent an individual report item. The ReportItems will be used in an array list that will be passed via intent. * This class will represent an individual report item. The ReportItems will be
* This will allow for our report items to stay in the proper order. * used in an array list that will be passed via intent. This will allow for our
* report items to stay in the proper order.
*
* @author ricky * @author ricky
*/ */
public final class ReportItem implements Parcelable { public final class ReportItem implements Parcelable {
public static final Parcelable.Creator<ReportItem> CREATOR = new Parcelable.Creator<ReportItem>() { public static final Parcelable.Creator<ReportItem> CREATOR = new Parcelable.Creator<ReportItem>() {
public ReportItem createFromParcel(Parcel in) { @Override
public ReportItem createFromParcel(final Parcel in) {
return new ReportItem(in); return new ReportItem(in);
} }
public ReportItem[] newArray(int size) { @Override
public ReportItem[] newArray(final int size) {
return new ReportItem[size]; return new ReportItem[size];
} }
}; };
private final String mKey; private final String mKey;
private final String mValue; private final String mValue;
/**
* Creates a new ReportItem
* @author ricky barrette
*/
public ReportItem(String key, String value) {
this.mKey = key;
this.mValue = value;
}
/** /**
* Creates a new ReportItem from a parcel * Creates a new ReportItem from a parcel
*
* @param in * @param in
*/ */
public ReportItem(Parcel in){ public ReportItem(final Parcel in) {
this.mKey = in.readString(); mKey = in.readString();
this.mValue = in.readString(); mValue = in.readString();
} }
/* (non-Javadoc) /**
* Creates a new ReportItem
*
* @author ricky barrette
*/
public ReportItem(final String key, final String value) {
mKey = key;
mValue = value;
}
/*
* (non-Javadoc)
*
* @see android.os.Parcelable#describeContents() * @see android.os.Parcelable#describeContents()
*/ */
@Override @Override
@@ -56,27 +64,29 @@ public final class ReportItem implements Parcelable {
return 0; return 0;
} }
/* (non-Javadoc)
* @see android.os.Parcelable#writeToParcel(android.os.Parcel, int)
*/
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(this.mKey);
dest.writeString(this.mValue);
}
/** /**
* @return the key * @return the key
*/ */
public String getKey() { public String getKey() {
return this.mKey; return mKey;
} }
/** /**
* @return the value * @return the value
*/ */
public String getValue() { public String getValue() {
return this.mValue; return mValue;
}
/*
* (non-Javadoc)
*
* @see android.os.Parcelable#writeToParcel(android.os.Parcel, int)
*/
@Override
public void writeToParcel(final Parcel dest, final int flags) {
dest.writeString(mKey);
dest.writeString(mValue);
} }
} }

View File

@@ -10,9 +10,7 @@ import java.io.IOException;
import org.apache.http.client.ClientProtocolException; import org.apache.http.client.ClientProtocolException;
import android.annotation.SuppressLint; import android.annotation.TargetApi;
import android.app.Notification;
import android.app.Notification.Builder;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.app.Service; import android.app.Service;
@@ -20,14 +18,16 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.IBinder; import android.os.IBinder;
import android.util.Log; import android.util.Log;
import anroid.v4.compat.NotificationCompat;
/** /**
* This service will allow the exception handler to post reports in the backgound, * This service will allow the exception handler to post reports in the
* allowing the user to do what ever they want * backgound, allowing the user to do what ever they want
*
* @author ricky barrette * @author ricky barrette
*/ */
public class ReportPostingService extends Service { public class ReportPostingService extends Service {
public static final int NOTIFICATION_ID = 1973646478; public static final int NOTIFICATION_ID = 1973646478;
private NotificationManager mNotificationManager; private NotificationManager mNotificationManager;
private static final String TAG = "ReportPostingService"; private static final String TAG = "ReportPostingService";
@@ -39,6 +39,7 @@ public class ReportPostingService extends Service {
/** /**
* Fires of a notification based upon api level * Fires of a notification based upon api level
*
* @param title * @param title
* @param contentText * @param contentText
* @param ticker * @param ticker
@@ -47,105 +48,83 @@ public class ReportPostingService extends Service {
* @param isOngoing * @param isOngoing
* @author ricky barrette * @author ricky barrette
*/ */
@SuppressLint("NewApi") private void fireNotification(final String title, final String contentText, final String ticker, final int icon, final Intent intent, final boolean isOngoing) {
@SuppressWarnings("deprecation")
private void fireNotification(String title, String contentText, String ticker, int icon, Intent intent, boolean isOngoing) {
PendingIntent pendingIntent = null; PendingIntent pendingIntent = null;
if(intent != null) if (intent != null)
pendingIntent = PendingIntent.getService(this.getApplicationContext(), 0, intent, 0); pendingIntent = PendingIntent.getService(getApplicationContext(), 0, intent, 0);
/*
* Use the appropriate notificafation methods final NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext()).setContentTitle(title).setContentText(contentText)
*/ .setTicker(ticker).setSmallIcon(icon).setWhen(System.currentTimeMillis());
if(Integer.valueOf(android.os.Build.VERSION.SDK_INT) > 11){
Builder builder = new Notification.Builder(this.getApplicationContext()) if (isOngoing)
.setContentTitle(title) builder.setOngoing(true);
.setContentText(contentText) else
.setTicker(ticker) builder.setAutoCancel(true);
.setSmallIcon(icon)
.setWhen(System.currentTimeMillis()); if (intent != null)
if(isOngoing) builder.setContentIntent(pendingIntent);
builder.setOngoing(true); mNotificationManager.notify(NOTIFICATION_ID, builder.getNotification());
else
builder.setAutoCancel(true);
if (intent != null)
builder.setContentIntent(pendingIntent);
mNotificationManager.notify(NOTIFICATION_ID, builder.getNotification());
} else {
Notification notification = new Notification(icon, title , System.currentTimeMillis());
if(isOngoing)
notification.flags |= Notification.FLAG_ONGOING_EVENT;
else
notification.flags |= Notification.FLAG_AUTO_CANCEL;
notification.setLatestEventInfo(this.getApplicationContext(), title, contentText, pendingIntent);
mNotificationManager.notify(NOTIFICATION_ID, notification);
}
} }
/** /**
* Extracts the report object from the intent * Extracts the report object from the intent
*
* @param intent * @param intent
* @author ricky barrette * @author ricky barrette
*/ */
private void getReport(Intent intent) { private void getReport(final Intent intent) {
mReport = (Report) intent.getParcelableExtra("report"); mReport = (Report) intent.getParcelableExtra("report");
} }
/** /**
* notifiys the user that we are sending a report * notifiys the user that we are sending a report
*
* @author ricky barrette * @author ricky barrette
*/ */
private void notifyError() { private void notifyError() {
fireNotification(getString(R.string.reporting_error), fireNotification(getString(R.string.reporting_error), getString(R.string.reporting_error_msg), getString(R.string.reporting_error_msg),
getString(R.string.reporting_error_msg), android.R.drawable.stat_notify_error, new Intent(getApplicationContext(), ReportPostingService.class).putExtras(mIntent), false);
getString(R.string.reporting_error_msg),
android.R.drawable.stat_notify_error,
new Intent(this.getApplicationContext(), ReportPostingService.class).putExtras(mIntent),
false);
} }
/** /**
* notifiys the user that we are sending a report * notifiys the user that we are sending a report
*
* @author ricky barrette * @author ricky barrette
*/ */
private void notifySending() { private void notifySending() {
fireNotification(getString(R.string.sending), fireNotification(getString(R.string.sending), getString(R.string.sending_report), getString(R.string.sending), android.R.drawable.stat_sys_upload, null, true);
getString(R.string.sending_report),
getString(R.string.sending),
android.R.drawable.stat_sys_upload,
null,
true);
} }
/** /**
* (non-Javadoc) * (non-Javadoc)
*
* @see android.app.Service#onBind(android.content.Intent) * @see android.app.Service#onBind(android.content.Intent)
*/ */
@Override @Override
public IBinder onBind(Intent intent) { public IBinder onBind(final Intent intent) {
// Unused // Unused
return null; return null;
} }
/** /**
* Called when the service is being created * Called when the service is being created Here we want to display a
* Here we want to display a notifcation, * notifcation, to inform the user what we are doing. (non-Javadoc)
* to inform the user what we are doing. *
* (non-Javadoc)
* @see android.app.Service#onCreate() * @see android.app.Service#onCreate()
*/ */
@Override @Override
public void onCreate() { public void onCreate() {
Context context = this.getApplicationContext(); final Context context = getApplicationContext();
mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
notifySending(); notifySending();
super.onCreate(); super.onCreate();
} }
/** /**
* Called when the service is being destroyed * Called when the service is being destroyed Here we want to dismiss the
* Here we want to dismiss the notifications * notifications (non-Javadoc)
* (non-Javadoc) *
* @see android.app.Service#onDestroy() * @see android.app.Service#onDestroy()
*/ */
@Override @Override
@@ -158,11 +137,12 @@ public class ReportPostingService extends Service {
/** /**
* (non-Javadoc) * (non-Javadoc)
*
* @see android.app.Service#onStart(android.content.Intent, int) * @see android.app.Service#onStart(android.content.Intent, int)
*/ */
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@Override @Override
public void onStart(Intent intent, int startId) { public void onStart(final Intent intent, final int startId) {
mStartId = startId; mStartId = startId;
getReport(intent); getReport(intent);
postReport(); postReport();
@@ -172,10 +152,12 @@ public class ReportPostingService extends Service {
/** /**
* (non-Javadoc) * (non-Javadoc)
*
* @see android.app.Service#onStartCommand(android.content.Intent, int, int) * @see android.app.Service#onStartCommand(android.content.Intent, int, int)
*/ */
@TargetApi(5)
@Override @Override
public int onStartCommand(Intent intent, int flags, int startId) { public int onStartCommand(final Intent intent, final int flags, final int startId) {
mStartId = startId; mStartId = startId;
getReport(intent); getReport(intent);
postReport(); postReport();
@@ -185,22 +167,28 @@ public class ReportPostingService extends Service {
/** /**
* Posts a copy of the report to the report handing server * Posts a copy of the report to the report handing server
*
* @author ricky barrette * @author ricky barrette
*/ */
private void postReport(){ private void postReport() {
if(!isStarted){ if (!isStarted) {
isStarted = true; isStarted = true;
try { new Thread(new Runnable() {
Log.d(TAG, mReport.file()); @Override
} catch (ClientProtocolException e) { public void run() {
e.printStackTrace(); try {
hasErrored = true; Log.d(TAG, mReport.file());
} catch (IOException e) { } catch (final ClientProtocolException e) {
e.printStackTrace(); e.printStackTrace();
hasErrored = true; hasErrored = true;
} finally { } catch (final IOException e) {
ReportPostingService.this.stopSelf(mStartId); e.printStackTrace();
} hasErrored = true;
} finally {
ReportPostingService.this.stopSelf(mStartId);
}
}
}).start();
} }
} }
} }

View File

@@ -15,91 +15,96 @@ import android.util.AttributeSet;
import android.view.Gravity; import android.view.Gravity;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
/** /**
* this class will be a simple preference that contains only a text view that will display the application build information * this class will be a simple preference that contains only a text view that
* will display the application build information
*
* @author ricky barrette * @author ricky barrette
*/ */
public class VersionInformationPreference extends Preference { public class VersionInformationPreference extends Preference {
private Context mContext; private final Context mContext;
/** /**
* creates a preference that is nothing but a text view * creates a preference that is nothing but a text view
*
* @param context * @param context
*/ */
public VersionInformationPreference(Context context) { public VersionInformationPreference(final Context context) {
super(context); super(context);
mContext = context; mContext = context;
} }
/** /**
* creates a preference that is nothing but a text view * creates a preference that is nothing but a text view
*
* @param context * @param context
* @param attrs * @param attrs
*/ */
public VersionInformationPreference(Context context, AttributeSet attrs) { public VersionInformationPreference(final Context context, final AttributeSet attrs) {
super(context, attrs); super(context, attrs);
mContext = context; mContext = context;
} }
/** /**
* creates a preference that is nothing but a text view * creates a preference that is nothing but a text view
*
* @param context * @param context
* @param attrs * @param attrs
* @param defStyle * @param defStyle
*/ */
public VersionInformationPreference(Context context, AttributeSet attrs, int defStyle) { public VersionInformationPreference(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle); super(context, attrs, defStyle);
mContext = context; mContext = context;
} }
/** /**
* creates a linear layout the contains only a textview. * creates a linear layout the contains only a textview. (non-Javadoc)
* (non-Javadoc) *
* @see android.preference.Preference#onCreateView(android.view.ViewGroup) * @see android.preference.Preference#onCreateView(android.view.ViewGroup)
* @param parent * @param parent
* @return * @return
* @author ricky barrette * @author ricky barrette
*/ */
@Override @Override
protected View onCreateView(ViewGroup parent){ protected View onCreateView(final ViewGroup parent) {
/* /*
* get the build information, and build the string * get the build information, and build the string
*/ */
PackageManager pm = mContext.getPackageManager(); final PackageManager pm = mContext.getPackageManager();
PackageInfo pi; PackageInfo pi;
try { try {
pi = pm.getPackageInfo(mContext.getPackageName(), 0); pi = pm.getPackageInfo(mContext.getPackageName(), 0);
} catch (NameNotFoundException eNnf) { } catch (final NameNotFoundException eNnf) {
//doubt this will ever run since we want info about our own package // doubt this will ever run since we want info about our own package
pi = new PackageInfo(); pi = new PackageInfo();
pi.versionName = "unknown"; pi.versionName = "unknown";
pi.versionCode = 1; pi.versionCode = 1;
} }
/* /*
* create a vertical linear layout that width and height that wraps content * create a vertical linear layout that width and height that wraps
* content
*/ */
LinearLayout layout = new LinearLayout(getContext()); final LinearLayout layout = new LinearLayout(getContext());
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT); final LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
// params.gravity = Gravity.CENTER; // params.gravity = Gravity.CENTER;
layout.setPadding(15, 5, 10, 5); layout.setPadding(15, 5, 10, 5);
layout.setOrientation(LinearLayout.VERTICAL); layout.setOrientation(LinearLayout.VERTICAL);
layout.removeAllViews(); layout.removeAllViews();
/* /*
* create a textview that will be used to display the application's name and build information * create a textview that will be used to display the application's name
* and add it to the layout * and build information and add it to the layout
*/ */
TextView title = new TextView(getContext()); final TextView title = new TextView(getContext());
title.setText(mContext.getString(R.string.version)+" "+pi.versionName+" bulid "+pi.versionCode); title.setText(mContext.getString(R.string.version) + " " + pi.versionName + " bulid " + pi.versionCode);
title.setTextSize(16); title.setTextSize(16);
title.setTypeface(Typeface.SANS_SERIF); title.setTypeface(Typeface.SANS_SERIF);
title.setGravity(Gravity.LEFT); title.setGravity(Gravity.LEFT);