diff --git a/Published/ExaltedDice/.classpath b/Published/ExaltedDice/.classpath
new file mode 100755
index 0000000..207072c
--- /dev/null
+++ b/Published/ExaltedDice/.classpath
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
diff --git a/Published/ExaltedDice/.project b/Published/ExaltedDice/.project
new file mode 100755
index 0000000..9075306
--- /dev/null
+++ b/Published/ExaltedDice/.project
@@ -0,0 +1,33 @@
+
+
+ ExaltedDice
+
+
+
+
+
+ com.android.ide.eclipse.adt.ResourceManagerBuilder
+
+
+
+
+ com.android.ide.eclipse.adt.PreCompilerBuilder
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ com.android.ide.eclipse.adt.ApkBuilder
+
+
+
+
+
+ com.android.ide.eclipse.adt.AndroidNature
+ org.eclipse.jdt.core.javanature
+
+
diff --git a/Published/ExaltedDice/AndroidManifest.xml b/Published/ExaltedDice/AndroidManifest.xml
new file mode 100755
index 0000000..5ad97cb
--- /dev/null
+++ b/Published/ExaltedDice/AndroidManifest.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Published/ExaltedDice/Change Log.txt b/Published/ExaltedDice/Change Log.txt
new file mode 100644
index 0000000..e69de29
diff --git a/Published/ExaltedDice/bin/ExaltedDice.apk b/Published/ExaltedDice/bin/ExaltedDice.apk
new file mode 100644
index 0000000..ffc918f
Binary files /dev/null and b/Published/ExaltedDice/bin/ExaltedDice.apk differ
diff --git a/Published/ExaltedDice/bin/classes.dex b/Published/ExaltedDice/bin/classes.dex
new file mode 100644
index 0000000..e77d1aa
Binary files /dev/null and b/Published/ExaltedDice/bin/classes.dex differ
diff --git a/Published/ExaltedDice/bin/resources.ap_ b/Published/ExaltedDice/bin/resources.ap_
new file mode 100644
index 0000000..9deafbf
Binary files /dev/null and b/Published/ExaltedDice/bin/resources.ap_ differ
diff --git a/Published/ExaltedDice/default.properties b/Published/ExaltedDice/default.properties
new file mode 100755
index 0000000..51e933a
--- /dev/null
+++ b/Published/ExaltedDice/default.properties
@@ -0,0 +1,13 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system use,
+# "build.properties", and override values to adapt the script to your
+# project structure.
+
+# Indicates whether an apk should be generated for each density.
+split.density=false
+# Project target.
+target=android-8
diff --git a/Published/ExaltedDice/libs/admob-sdk-android.jar b/Published/ExaltedDice/libs/admob-sdk-android.jar
new file mode 100644
index 0000000..e186671
Binary files /dev/null and b/Published/ExaltedDice/libs/admob-sdk-android.jar differ
diff --git a/Published/ExaltedDice/res/drawable/icon.png b/Published/ExaltedDice/res/drawable/icon.png
new file mode 100644
index 0000000..b101000
Binary files /dev/null and b/Published/ExaltedDice/res/drawable/icon.png differ
diff --git a/Published/ExaltedDice/res/layout/list_row.xml b/Published/ExaltedDice/res/layout/list_row.xml
new file mode 100755
index 0000000..b39b66e
--- /dev/null
+++ b/Published/ExaltedDice/res/layout/list_row.xml
@@ -0,0 +1,6 @@
+
+
\ No newline at end of file
diff --git a/Published/ExaltedDice/res/layout/main.xml b/Published/ExaltedDice/res/layout/main.xml
new file mode 100755
index 0000000..f21aa3b
--- /dev/null
+++ b/Published/ExaltedDice/res/layout/main.xml
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Published/ExaltedDice/res/values/array.xml b/Published/ExaltedDice/res/values/array.xml
new file mode 100644
index 0000000..19382c3
--- /dev/null
+++ b/Published/ExaltedDice/res/values/array.xml
@@ -0,0 +1,8 @@
+
+
+- Welcome to ExaltedDice!
+- Press the - button to lower your dice count by 1, or long press to continuously lower the dice count.
- Press the + button to raise your dice count by 1, or long press to continuously raise the dice count.
- When you press Roll, Your Results Will Be Displayed Here
+
+
+
+
diff --git a/Published/ExaltedDice/res/values/attrs.xml b/Published/ExaltedDice/res/values/attrs.xml
new file mode 100644
index 0000000..68a68fc
--- /dev/null
+++ b/Published/ExaltedDice/res/values/attrs.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Published/ExaltedDice/res/values/strings.xml b/Published/ExaltedDice/res/values/strings.xml
new file mode 100755
index 0000000..eec08d3
--- /dev/null
+++ b/Published/ExaltedDice/res/values/strings.xml
@@ -0,0 +1,12 @@
+
+
+
+ Exalted Dice
+
+Rolled:
+
+
+
+
+twentycodes@gmail.com
+
diff --git a/Published/ExaltedDice/src/com/TwentyCode/android/ExaltedDice/ExaltedDice.java b/Published/ExaltedDice/src/com/TwentyCode/android/ExaltedDice/ExaltedDice.java
new file mode 100755
index 0000000..7b68fda
--- /dev/null
+++ b/Published/ExaltedDice/src/com/TwentyCode/android/ExaltedDice/ExaltedDice.java
@@ -0,0 +1,542 @@
+package com.TwentyCode.android.ExaltedDice;
+
+import java.util.ArrayList;
+import java.util.Random;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Vibrator;
+import android.text.InputFilter;
+import android.text.InputType;
+import android.text.Spanned;
+import android.text.method.NumberKeyListener;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLongClickListener;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.ListView;
+import android.widget.Toast;
+
+public class ExaltedDice extends Activity implements OnClickListener, OnLongClickListener, OnItemClickListener {
+
+ private EditText dice;
+ private ListView listview;
+ private ArrayList rollHistory = new ArrayList();
+ private ArrayList rolled = new ArrayList();
+ private int intSuccesses;
+ private int mCurrent;
+ private static boolean mIncrement;
+ private static boolean mDecrement;
+ private InputFilter mNumberInputFilter;
+ private String[] mDisplayedValues;
+ //the speed in milliseconds for dice increment or decrement
+ private long mSpeed = 300;
+ //the least and most dice allowed
+ private int mStart = 1;
+ private int mEnd = 999;
+ private static final int MENU_QUIT = Menu.FIRST;
+ private static final int MENU_CLEAR = Menu.FIRST + 1;
+ protected PostMortemReportExceptionHandler mDamageReport = new PostMortemReportExceptionHandler(this);
+ private static final String TAG = "ExaltedDice";
+ private Handler mHandler;
+ //this runnable will be used to increment or decrement the amount of dice being rolled
+ private final Runnable mRunnable = new Runnable() {
+ public void run() {
+ if (mIncrement) {
+ changeCurrent(mCurrent + 1);
+ mHandler.postDelayed(this, mSpeed);
+ } else if (mDecrement) {
+ changeCurrent(mCurrent - 1);
+ mHandler.postDelayed(this, mSpeed);
+ }
+ }
+ };
+
+ private static final char[] DIGIT_CHARACTERS = new char[] {
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'
+ };
+
+ private class NumberPickerInputFilter implements InputFilter {
+ public CharSequence filter(CharSequence source, int start, int end,
+ Spanned dest, int dstart, int dend) {
+ if (mDisplayedValues == null) {
+ return mNumberInputFilter.filter(source, start, end, dest, dstart, dend);
+ }
+ CharSequence filtered = String.valueOf(source.subSequence(start, end));
+ String result = String.valueOf(dest.subSequence(0, dstart)) + filtered
+ + dest.subSequence(dend, dest.length());
+ String str = String.valueOf(result).toLowerCase();
+ for (String val : mDisplayedValues) {
+ val = val.toLowerCase();
+ if (val.startsWith(str)) {
+ return filtered;
+ }
+ }
+ return "";
+ }
+ }
+
+ private class NumberRangeKeyListener extends NumberKeyListener {
+
+ @Override
+ public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) {
+
+ CharSequence filtered = super.filter(source, start, end, dest, dstart, dend);
+ if (filtered == null) {
+ filtered = source.subSequence(start, end);
+ }
+
+ String result = String.valueOf(dest.subSequence(0, dstart)) + filtered + dest.subSequence(dend, dest.length());
+
+ if ("".equals(result)) {
+ return result;
+ }
+ int val = getSelectedPos(result);
+
+ /* Ensure the user can't type in a value greater
+ * than the max allowed. We have to allow less than min
+ * as the user might want to delete some numbers
+ * and then type a new number.
+ */
+ if (val > mEnd) {
+ return "";
+ } else {
+ return filtered;
+ }
+ }
+
+ @Override
+ protected char[] getAcceptedChars() {
+ return DIGIT_CHARACTERS;
+ }
+
+ // XXX This doesn't allow for range limits when controlled by a
+ // soft input method!
+ public int getInputType() {
+ return InputType.TYPE_CLASS_NUMBER;
+ }
+ }
+
+ /**
+ * stops decrementing of dice after longpress
+ * @author ricky barrette
+ */
+ public static void cancelDecrement() {
+ mDecrement = false;
+ }
+
+ /**
+ * stop incrementing of dice after longpress
+ * @author ricky barrette
+ */
+ public static void cancelIncrement() {
+ mIncrement = false;
+ }
+
+ protected void changeCurrent(int current) {
+ // Wrap around the values if we go past the start or end
+ if (current > mEnd) {
+ current = mStart;
+ } else if (current < mStart) {
+ current = mEnd;
+ }
+ mCurrent = current;
+ updateView();
+ }
+
+ /**
+ * clears the rollHistory List array and refreshes the listview
+ *
+ * @author ricky barrette
+ */
+ private void clearHistory() {
+ rollHistory.clear();
+ rolled.clear();
+ listview.setAdapter(new ArrayAdapter(this, R.layout.list_row, rollHistory));
+ }
+
+ private int getSelectedPos(String str) {
+ if (mDisplayedValues == null) {
+ return Integer.parseInt(str);
+ } else {
+ for (int i = 0; i < mDisplayedValues.length; i++) {
+
+ /* Don't force the user to type in jan when ja will do */
+ str = str.toLowerCase();
+ if (mDisplayedValues[i].toLowerCase().startsWith(str)) {
+ return mStart + i;
+ }
+ }
+
+ /* The user might have typed in a number into the month field i.e.
+ * 10 instead of OCT so support that too.
+ */
+ try {
+ return Integer.parseInt(str);
+ } catch (NumberFormatException e) {
+
+ /* Ignore as if it's not a number we don't care */
+ }
+ }
+ return mStart;
+ }
+
+ /**
+ * also implemented OnClickListener
+ *
+ * @author ricky barrette 3-27-2010
+ * @author - WWPowers 3-26-2010
+ */
+ @Override
+ public void onClick(View v){
+
+ //get the number from the edit text
+ try {
+ mCurrent = Integer.parseInt(dice.getText().toString());
+ } catch (NumberFormatException e) {
+ e.printStackTrace();
+ }
+
+ switch (v.getId()){
+ case R.id.up:
+ changeCurrent(mCurrent + 1);
+ break;
+ case R.id.down:
+ changeCurrent(mCurrent - 1);
+ break;
+ case R.id.roll:
+ rollDice();
+ break;
+ }
+ }
+
+ /**
+ * Called when the activity is first created. starts gui and sets up buttons
+ *
+ * @author ricky barrette 3-27-2010
+ */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ mDamageReport.run();
+ Thread.setDefaultUncaughtExceptionHandler(mDamageReport);
+
+ super.onCreate(savedInstanceState);
+ Log.i(TAG, "onCreate()");
+ setContentView(R.layout.main);
+
+ mHandler = new Handler();
+
+ /*
+ * views and listeners
+ */
+ dice = (EditText) findViewById(R.id.dice);
+ InputFilter inputFilter = new NumberPickerInputFilter();
+ mNumberInputFilter = new NumberRangeKeyListener();
+ dice.setFilters(new InputFilter[] {inputFilter});
+
+ listview = (ListView) findViewById(R.id.list);
+ NumberPickerButton btAddDice = (NumberPickerButton) findViewById(R.id.up);
+ NumberPickerButton btSubtractDice = (NumberPickerButton) findViewById(R.id.down);
+ Button btRollDice = (Button) findViewById(R.id.roll);
+ btAddDice.setOnClickListener(this);
+ btSubtractDice.setOnClickListener(this);
+ btRollDice.setOnClickListener(this);
+ listview.setOnItemClickListener(this);
+ btAddDice.setOnLongClickListener(this);
+ btSubtractDice.setOnLongClickListener(this);
+
+ /*
+ * shake Listener
+ */
+// ShakeListener mShaker = new ShakeListener(this);
+// mShaker.setOnShakeListener(new ShakeListener.OnShakeListener() {
+// public void onShake() {
+// rollDice();
+// }
+// });
+
+ /*
+ * hide keyboard
+ */
+ ((InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(dice.getWindowToken(), 0);
+
+ /*
+ * display hello message
+ */
+ listview.setAdapter(new ArrayAdapter(this, R.layout.list_row, getResources().getStringArray(R.array.hello_msg)));
+
+ System.gc();
+
+ }
+
+ /**
+ * creates a menu with a quit option
+ *
+ * @author WWPowers 3-27-2010
+ */
+ public boolean onCreateOptionsMenu(Menu menu) {
+ menu.add(1, MENU_CLEAR, 0, "Clear Roll History");
+ menu.add(1, MENU_QUIT, 0, "Quit");
+ return true;
+ }
+
+ /**
+ * rolls same amount of dice as previous roll
+ * @author ricky barrette
+ */
+ @Override
+ public void onItemClick(AdapterView> arg0, View v, int position, long arg3) {
+ if(rolled.size() != 0){
+ dice.setText("" + rolled.get(position));
+ rollDice();
+ }
+ }
+
+ /**
+ * starts a runnable that will increment or decrement the dice
+ *
+ * @author ricky barrette 3-27-2010
+ * @param v
+ */
+ public boolean onLongClick(View v) {
+
+ //get the number from the edit text
+ try {
+ mCurrent = Integer.parseInt(dice.getText().toString());
+ } catch (NumberFormatException e) {
+ e.printStackTrace();
+ }
+
+ switch (v.getId()){
+ case R.id.up:
+ mIncrement = true;
+ mHandler.post(mRunnable);
+ return true;
+
+ case R.id.down:
+ mDecrement = true;
+ mHandler.post(mRunnable);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * handles menu selection
+ *
+ * @author WWPowers 3-27-2010
+ * @author ricky barrette 3-27-2010
+ */
+ public boolean onOptionsItemSelected(MenuItem item) {
+
+ switch (item.getItemId()) {
+ case MENU_QUIT:
+ quitDialog();
+ return true;
+ case MENU_CLEAR:
+ clearHistory();
+ return true;
+ }
+ return false;
+
+ }
+
+ /**
+ * resorts application state after rotation
+ * @author ricky barrette
+ */
+ @Override
+ public void onRestoreInstanceState(Bundle savedInstanceState) {
+ super.onRestoreInstanceState(savedInstanceState);
+ // Restore UI state from the savedInstanceState.
+ // This bundle has also been passed to onCreate.
+ rollHistory = savedInstanceState.getStringArrayList("roll_history");
+ dice.setText(savedInstanceState.getString("dice"));
+ rolled = savedInstanceState.getIntegerArrayList("rolled");
+ listview.setAdapter(new ArrayAdapter(this, R.layout.list_row, rollHistory));
+ }
+
+ /**
+ * saves application state before rotatoin
+ * @author ricky barrette
+ */
+ @Override
+ public void onSaveInstanceState(Bundle savedInstanceState) {
+ // Save UI state changes to the savedInstanceState.
+ // This bundle will be passed to onCreate if the process is
+ // killed and restarted.
+ savedInstanceState.putStringArrayList("roll_history", rollHistory);
+ savedInstanceState.putString("dice", dice.getText().toString());
+ savedInstanceState.putIntegerArrayList("rolled", rolled);
+ super.onSaveInstanceState(savedInstanceState);
+ }
+
+
+ /**
+ * displays a quit dialog
+ *
+ * @author ricky barrette 3-28-2010
+ * @author WWPowers 3-27-2010
+ */
+ public void quitDialog() {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setMessage("Are you sure you want to quit?").setCancelable(
+ false).setPositiveButton("Yes",
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ ExaltedDice.this.finish();
+ }
+ }).setNegativeButton("No",
+ new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.cancel();
+ }
+ });
+ builder.show();
+ }
+
+ /**
+ * returns a custom string containing dice rolls and number of successes
+ *
+ * @param int times
+ * @return String resultsString
+ * @author ricky barrette
+ */
+ public String results(int times) {
+ Log.i(TAG, "results()");
+ StringBuffer resultsString = new StringBuffer();
+ resultsString.append("Rolled "+ times +" dice\n");
+
+ /**
+ * roll the dice
+ */
+ int[] roll = rollGen(times);
+
+ /**
+ * add number of successes to resultsString
+ */
+ resultsString.append("Successes: "+ successes(roll) +"\n");
+
+ resultsString.append("Rolled: ");
+ /**
+ * add rolled dice results to resultsString
+ */
+ for (int i = 0; i < roll.length; i++) {
+ resultsString.append(roll[i] + ", ");
+ }
+
+ return resultsString.toString();
+ }
+
+ /**
+ * Performs a dice roll
+ *
+ * @author ricky barrette
+ */
+ public void rollDice() {
+ // vibrate for 50 milliseconds
+ vibrate(50);
+
+ //get the number from the edit text
+ try {
+ mCurrent = Integer.parseInt(dice.getText().toString());
+ } catch (NumberFormatException e) {
+ e.printStackTrace();
+ }
+ rolled.add(0, mCurrent);
+ rollHistory.add(0, results(mCurrent));
+
+ listview.setAdapter(new ArrayAdapter(this, R.layout.list_row, rollHistory));
+ }
+
+ /**
+ * generates an array containing 10 sided dice rolls
+ *
+ * @param int times
+ * @return int[] roll
+ * @author ricky barrette
+ */
+ public int[] rollGen(int times) {
+ Log.i(TAG, "rollGen()" + times);
+ int[] roll = new int[times];
+ Random random = new Random();
+ for (int i = 0; i < times; i++) {
+ roll[i] = random.nextInt(10) + 1;
+ }
+ return roll;
+ }
+
+ /**
+ * counts each dice roll that is greater than or equal to 7 as a success. 10
+ * gets another success (for a total of 2)
+ *
+ * @param int[] roll
+ * @return int successes
+ * @author ricky barrette
+ */
+ public int successes(int[] roll) {
+ Log.i(TAG, "successes()");
+ intSuccesses = 0;
+ for (int i = 0; i < roll.length; i++) {
+ if (roll[i] >= 7)
+ intSuccesses++;
+ if (roll[i] == 10)
+ intSuccesses++;
+ }
+ return intSuccesses;
+ }
+
+ /**
+ * displays toast message with a long duration
+ *
+ * @param msg
+ * @author ricky barrette 3-26-2010
+ * @author WWPowers 3-26-2010
+ */
+ public void toastLong(CharSequence msg) {
+ Log.i(TAG, "toastLong()");
+ Toast.makeText(this, msg, Toast.LENGTH_LONG).show();
+ }
+
+ /**
+ * a convince method that will update the edit text dice to what the current amount of dice is
+ *
+ * @author ricky barrette
+ */
+ protected void updateView() {
+ dice.setText(mCurrent+"");
+ }
+
+ /**
+ * starts Vibrator service and then vibrates for x milliseconds
+ *
+ * @param Long
+ * milliseconds
+ * @author ricky barrette
+ */
+ public void vibrate(long milliseconds) {
+ Log.i(TAG, "vibrate() for " + milliseconds);
+ /**
+ * start vibrator service
+ */
+ Vibrator vib = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
+
+ /**
+ * Vibrate for x milliseconds
+ */
+ vib.vibrate(milliseconds);
+ }
+
+}
\ No newline at end of file
diff --git a/Published/ExaltedDice/src/com/TwentyCode/android/ExaltedDice/NumberPickerButton.java b/Published/ExaltedDice/src/com/TwentyCode/android/ExaltedDice/NumberPickerButton.java
new file mode 100644
index 0000000..ca2e1a6
--- /dev/null
+++ b/Published/ExaltedDice/src/com/TwentyCode/android/ExaltedDice/NumberPickerButton.java
@@ -0,0 +1,81 @@
+/**
+ * @author Twenty Codes, LLC
+ * @author ricky barrette
+ * @date Sep 20, 2010
+ *
+ * Source from com.android.internal.widget.NumberPickerButton;
+ * Copyright (C) 2008 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 com.TwentyCode.android.ExaltedDice;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.widget.Button;
+
+/**
+ * This class exists purely to cancel long click events.
+ */
+public class NumberPickerButton extends Button {
+
+ public NumberPickerButton(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public NumberPickerButton(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public NumberPickerButton(Context context) {
+ super(context);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ cancelLongpressIfRequired(event);
+ return super.onTouchEvent(event);
+ }
+
+ @Override
+ public boolean onTrackballEvent(MotionEvent event) {
+ cancelLongpressIfRequired(event);
+ return super.onTrackballEvent(event);
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ if ((keyCode == KeyEvent.KEYCODE_DPAD_CENTER)
+ || (keyCode == KeyEvent.KEYCODE_ENTER)) {
+ cancelLongpress();
+ }
+ return super.onKeyUp(keyCode, event);
+ }
+
+ private void cancelLongpressIfRequired(MotionEvent event) {
+ if ((event.getAction() == MotionEvent.ACTION_CANCEL)
+ || (event.getAction() == MotionEvent.ACTION_UP)) {
+ cancelLongpress();
+ }
+ }
+
+ private void cancelLongpress() {
+ if (R.id.up == getId()) {
+ ExaltedDice.cancelIncrement();
+ } else if (R.id.down == getId()) {
+ ExaltedDice.cancelDecrement();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Published/ExaltedDice/src/com/TwentyCode/android/ExaltedDice/PostMortemReportExceptionHandler.java b/Published/ExaltedDice/src/com/TwentyCode/android/ExaltedDice/PostMortemReportExceptionHandler.java
new file mode 100644
index 0000000..29fee4e
--- /dev/null
+++ b/Published/ExaltedDice/src/com/TwentyCode/android/ExaltedDice/PostMortemReportExceptionHandler.java
@@ -0,0 +1,211 @@
+package com.TwentyCode.android.ExaltedDice;
+
+import java.io.BufferedReader;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.lang.Thread.UncaughtExceptionHandler;
+import java.lang.reflect.Field;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Build;
+
+/**
+ * dont forget the manifest tag
+ *
+ * @author ricky
+ */
+public class PostMortemReportExceptionHandler implements UncaughtExceptionHandler, Runnable {
+ public static final String ExceptionReportFilename = "postmortem.trace";
+
+ private static final String MSG_SUBJECT_TAG = "Exception Report"; //"app title + this tag" = email subject
+ private static final String MSG_SENDTO = "twentycodes@gmail.com"; //email will be sent to this account
+ //the following may be something you wish to consider localizing
+ 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).";
+
+ private Thread.UncaughtExceptionHandler mDefaultUEH;
+ private Activity mApp = null;
+
+ public PostMortemReportExceptionHandler(Activity aApp) {
+ mDefaultUEH = Thread.getDefaultUncaughtExceptionHandler();
+ mApp = aApp;
+ }
+
+ public String getDebugReport(Throwable aException) {
+
+// NumberFormat theFormatter = new DecimalFormat("#0.");
+ //stack trace
+ StackTraceElement[] theStackTrace = aException.getStackTrace();
+
+ StringBuffer report = new StringBuffer();
+
+ report.append("--------- Application ---------\n\n");
+
+ report.append(mApp.getPackageName()+" generated the following exception:\n\n");
+
+ report.append(aException.toString() + "\n\n");
+
+ report.append("-------------------------------\n\n");
+
+ report.append("--------- Stack trace ---------\n\n");
+ for (int i = 0; i < theStackTrace.length; i++) {
+ report.append(" " + theStackTrace[i].toString() + "\n");
+ }
+ report.append("-------------------------------\n\n");
+
+ //app environment
+ PackageManager pm = mApp.getPackageManager();
+ PackageInfo pi;
+ try {
+ pi = pm.getPackageInfo(mApp.getPackageName(), 0);
+ } catch (NameNotFoundException eNnf) {
+ //doubt this will ever run since we want info about our own package
+ pi = new PackageInfo();
+ pi.versionName = "unknown";
+ pi.versionCode = 69;
+ }
+
+ Date theDate = new Date();
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy.MM.dd_HH.mm.ss_zzz");
+ report.append("-------- Environment --------\n");
+ report.append("Time\t="+sdf.format(theDate)+"\n");
+ report.append("Device\t="+Build.FINGERPRINT+"\n");
+ try {
+ Field theMfrField = Build.class.getField("MANUFACTURER");
+ report.append("Make\t="+theMfrField.get(null)+"\n");
+ } catch (SecurityException e) {
+ } catch (NoSuchFieldException e) {
+ } catch (IllegalArgumentException e) {
+ } catch (IllegalAccessException e) {
+ }
+ report.append("Device: " + Build.DEVICE + "\n");
+ report.append("Brand: " + Build.BRAND + "\n");
+ report.append("Model: "+Build.MODEL+"\n");
+ report.append("Product: "+Build.PRODUCT+"\n");
+ report.append("App:\t "+mApp.getPackageName()+", version "+pi.versionName+" (build "+pi.versionCode+")\n");
+ report.append("Locale: "+mApp.getResources().getConfiguration().locale.getDisplayName()+"\n");
+ report.append("-----------------------------\n\n");
+
+ report.append("--------- Firmware ---------\n\n");
+ report.append("SDK: " + Build.VERSION.SDK + "\n");
+ report.append("Release: " + Build.VERSION.RELEASE + "\n");
+ report.append("Incremental: " + Build.VERSION.INCREMENTAL + "\n");
+ report.append("Build Id: " + Build.ID + "\n");
+ report.append("-------------------------------\n\n");
+
+ // If the exception was thrown in a background thread inside
+ // AsyncTask, then the actual exception can be found with getCause
+ report.append("--------- Cause ---------\n\n");
+ Throwable cause = aException.getCause();
+ if (cause != null) {
+ report.append(cause.toString() + "\n\n");
+ theStackTrace = cause.getStackTrace();
+ for (int i = 0; i < theStackTrace.length; i++) {
+ report.append(" " + theStackTrace[i].toString() + "\n");
+ }
+ }
+ report.append("-------------------------------\n\n");
+
+ report.append("--------- Complete Logcat ---------\n\n");
+ report.append(getLog().toString());
+ report.append("-------------------------------\n\n");
+
+ report.append("END REPORT");
+
+ return report.toString();
+ }
+
+ protected StringBuilder getLog(){
+ final StringBuilder log = new StringBuilder();
+ try{
+ Process process = Runtime.getRuntime().exec("logcat -d");
+ BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
+
+ String line;
+ while ((line = bufferedReader.readLine()) != null){
+ log.append(line);
+ log.append("\n");
+ }
+ }
+ catch (IOException e){
+ }
+ return log;
+ }
+
+ public void run() {
+ sendDebugReportToAuthor();
+ }
+
+ protected void saveDebugReport(String aReport) {
+ //save report to file
+ try {
+ FileOutputStream theFile = mApp.openFileOutput(ExceptionReportFilename, Context.MODE_PRIVATE);
+ theFile.write(aReport.getBytes());
+ theFile.close();
+ } catch(IOException ioe) {
+ //error during error report needs to be ignored, do not wish to start infinite loop
+ }
+ }
+
+ public void sendDebugReportToAuthor() {
+ String theLine = "";
+ StringBuffer theTrace = new StringBuffer();
+ try {
+ BufferedReader theReader = new BufferedReader(
+ new InputStreamReader(mApp.openFileInput(ExceptionReportFilename)));
+ while ((theLine = theReader.readLine())!=null) {
+ theTrace.append(theLine+"\n");
+ }
+ if (sendDebugReportToAuthor(theTrace.toString())) {
+ mApp.deleteFile(ExceptionReportFilename);
+ }
+ } catch (FileNotFoundException eFnf) {
+ // nothing to do
+ } catch(IOException eIo) {
+ // not going to report
+ }
+ }
+
+ public Boolean sendDebugReportToAuthor(String aReport) {
+ if (aReport!=null) {
+ Intent theIntent = new Intent(Intent.ACTION_SEND);
+ String theSubject = mApp.getTitle()+" "+MSG_SUBJECT_TAG;
+ String theBody = "\n"+MSG_BODY+"\n\n"+aReport+"\n\n";
+ theIntent.putExtra(Intent.EXTRA_EMAIL,new String[] {MSG_SENDTO});
+ theIntent.putExtra(Intent.EXTRA_TEXT, theBody);
+ theIntent.putExtra(Intent.EXTRA_SUBJECT, theSubject);
+ theIntent.setType("message/rfc822");
+ Boolean hasSendRecipients = (mApp.getPackageManager().queryIntentActivities(theIntent,0).size()>0);
+ if (hasSendRecipients) {
+ mApp.startActivity(theIntent);
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return true;
+ }
+ }
+
+ public void submit(Throwable e) {
+ String theErrReport = getDebugReport(e);
+ saveDebugReport(theErrReport);
+ //try to send file contents via email (need to do so via the UI thread)
+ mApp.runOnUiThread(this);
+ }
+
+ public void uncaughtException(Thread t, Throwable e) {
+ submit(e);
+ //do not forget to pass this exception through up the chain
+ mDefaultUEH.uncaughtException(t,e);
+ }
+}
\ No newline at end of file
diff --git a/Published/ExaltedDice/src/com/TwentyCode/android/ExaltedDice/ShakeListener.java b/Published/ExaltedDice/src/com/TwentyCode/android/ExaltedDice/ShakeListener.java
new file mode 100644
index 0000000..f2a2615
--- /dev/null
+++ b/Published/ExaltedDice/src/com/TwentyCode/android/ExaltedDice/ShakeListener.java
@@ -0,0 +1,98 @@
+package com.TwentyCode.android.ExaltedDice;
+
+/* The following code was written by Matthew Wiggins
+ * and is released under the APACHE 2.0 license
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+import android.hardware.SensorListener;
+import android.hardware.SensorManager;
+import android.content.Context;
+import java.lang.UnsupportedOperationException;
+
+@SuppressWarnings("deprecation")
+public class ShakeListener implements SensorListener
+{
+ private static final int FORCE_THRESHOLD = 350;
+ private static final int TIME_THRESHOLD = 100;
+ private static final int SHAKE_TIMEOUT = 500;
+ private static final int SHAKE_DURATION = 1000;
+ private static final int SHAKE_COUNT = 3;
+
+ private SensorManager mSensorMgr;
+ private float mLastX=-1.0f, mLastY=-1.0f, mLastZ=-1.0f;
+ private long mLastTime;
+ private OnShakeListener mShakeListener;
+ private Context mContext;
+ private int mShakeCount = 0;
+ private long mLastShake;
+ private long mLastForce;
+
+ public interface OnShakeListener
+ {
+ public void onShake();
+ }
+
+ public ShakeListener(Context context)
+ {
+ mContext = context;
+ resume();
+ }
+
+ public void setOnShakeListener(OnShakeListener listener)
+ {
+ mShakeListener = listener;
+ }
+
+ public void resume() {
+ mSensorMgr = (SensorManager)mContext.getSystemService(Context.SENSOR_SERVICE);
+ if (mSensorMgr == null) {
+ throw new UnsupportedOperationException("Sensors not supported");
+ }
+ boolean supported = mSensorMgr.registerListener(this, SensorManager.SENSOR_ACCELEROMETER, SensorManager.SENSOR_DELAY_GAME);
+ if (!supported) {
+ mSensorMgr.unregisterListener(this, SensorManager.SENSOR_ACCELEROMETER);
+ throw new UnsupportedOperationException("Accelerometer not supported");
+ }
+ }
+
+ public void pause() {
+ if (mSensorMgr != null) {
+ mSensorMgr.unregisterListener(this, SensorManager.SENSOR_ACCELEROMETER);
+ mSensorMgr = null;
+ }
+ }
+
+ public void onAccuracyChanged(int sensor, int accuracy) { }
+
+ public void onSensorChanged(int sensor, float[] values)
+ {
+ if (sensor != SensorManager.SENSOR_ACCELEROMETER) return;
+ long now = System.currentTimeMillis();
+
+ if ((now - mLastForce) > SHAKE_TIMEOUT) {
+ mShakeCount = 0;
+ }
+
+ if ((now - mLastTime) > TIME_THRESHOLD) {
+ long diff = now - mLastTime;
+ float speed = Math.abs(values[SensorManager.DATA_X] + values[SensorManager.DATA_Y] + values[SensorManager.DATA_Z] - mLastX - mLastY - mLastZ) / diff * 10000;
+ if (speed > FORCE_THRESHOLD) {
+ if ((++mShakeCount >= SHAKE_COUNT) && (now - mLastShake > SHAKE_DURATION)) {
+ mLastShake = now;
+ mShakeCount = 0;
+ if (mShakeListener != null) {
+ mShakeListener.onShake();
+ }
+ }
+ mLastForce = now;
+ }
+ mLastTime = now;
+ mLastX = values[SensorManager.DATA_X];
+ mLastY = values[SensorManager.DATA_Y];
+ mLastZ = values[SensorManager.DATA_Z];
+ }
+ }
+
+}