am 2be4fdbf: Preserve/restore calculator instance state
* commit '2be4fdbfcd5f16bc12066d1ccac181bca3dfaa7a':
Preserve/restore calculator instance state
diff --git a/src/com/android/calculator2/Calculator.java b/src/com/android/calculator2/Calculator.java
index 256cf4e..ac8c86a 100644
--- a/src/com/android/calculator2/Calculator.java
+++ b/src/com/android/calculator2/Calculator.java
@@ -31,7 +31,6 @@
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
-import android.view.KeyEvent;
import android.view.View;
import android.view.View.OnLongClickListener;
import android.view.ViewAnimationUtils;
@@ -46,8 +45,16 @@
public class Calculator extends Activity
implements OnTextSizeChangeListener, EvaluateCallback, OnLongClickListener {
- public static final String CALCULATOR_ACTIVITY_CURRENT_STATE =
- Calculator.class.getSimpleName() + "_currentState";
+ private static final String NAME = Calculator.class.getName();
+
+ // instance state keys
+ private static final String KEY_CURRENT_STATE = NAME + "_currentState";
+ private static final String KEY_CURRENT_EXPRESSION = NAME + "_currentExpression";
+
+ /**
+ * Constant for an invalid resource id.
+ */
+ public static final int INVALID_RES_ID = -1;
private enum CalculatorState {
INPUT, EVALUATE, RESULT, ERROR
@@ -72,19 +79,19 @@
private final Editable.Factory mFormulaEditableFactory = new Editable.Factory() {
@Override
public Editable newEditable(CharSequence source) {
- return new CalculatorExpressionBuilder(Calculator.this, source,
- mCurrentState == CalculatorState.INPUT);
+ final boolean isEdited = mCurrentState == CalculatorState.INPUT
+ || mCurrentState == CalculatorState.ERROR;
+ return new CalculatorExpressionBuilder(source, mTokenizer, isEdited);
}
};
private CalculatorState mCurrentState;
+ private CalculatorExpressionTokenizer mTokenizer;
private CalculatorExpressionEvaluator mEvaluator;
private CalculatorEditText mFormulaEditText;
private CalculatorEditText mResultEditText;
-
private ViewPager mPadViewPager;
-
private View mDeleteButton;
private View mClearButton;
@@ -98,28 +105,32 @@
mFormulaEditText = (CalculatorEditText) findViewById(R.id.formula);
mResultEditText = (CalculatorEditText) findViewById(R.id.result);
-
mPadViewPager = (ViewPager) findViewById(R.id.pad_pager);
-
mDeleteButton = findViewById(R.id.del);
mClearButton = findViewById(R.id.clr);
+ mTokenizer = new CalculatorExpressionTokenizer(this);
+ mEvaluator = new CalculatorExpressionEvaluator(mTokenizer);
+
savedInstanceState = savedInstanceState == null ? Bundle.EMPTY : savedInstanceState;
- setState(CalculatorState.values()[savedInstanceState.getInt(
- CALCULATOR_ACTIVITY_CURRENT_STATE, CalculatorState.INPUT.ordinal())]);
- mEvaluator = new CalculatorExpressionEvaluator(this);
+ setState(CalculatorState.values()[
+ savedInstanceState.getInt(KEY_CURRENT_STATE, CalculatorState.INPUT.ordinal())]);
+ mFormulaEditText.setText(mTokenizer.getLocalizedExpression(
+ savedInstanceState.getString(KEY_CURRENT_EXPRESSION, "")));
+ mEvaluator.evaluate(mFormulaEditText.getText(), this);
mFormulaEditText.setEditableFactory(mFormulaEditableFactory);
mFormulaEditText.addTextChangedListener(mFormulaTextWatcher);
mFormulaEditText.setOnTextSizeChangeListener(this);
-
mDeleteButton.setOnLongClickListener(this);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
- outState.putInt(CALCULATOR_ACTIVITY_CURRENT_STATE, mCurrentState.ordinal());
+ outState.putInt(KEY_CURRENT_STATE, mCurrentState.ordinal());
+ outState.putString(KEY_CURRENT_EXPRESSION,
+ mTokenizer.getNormalizedExpression(mFormulaEditText.getText().toString()));
}
private void setState(CalculatorState state) {
@@ -184,8 +195,12 @@
}
break;
case R.id.del:
- mFormulaEditText.dispatchKeyEvent(
- new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL));
+ // Delete works like backspace; remove the last character from the expression.
+ final Editable formulaText = mFormulaEditText.getEditableText();
+ final int formulaLength = formulaText.length();
+ if (formulaLength > 0) {
+ formulaText.delete(formulaLength - 1, formulaLength);
+ }
break;
case R.id.clr:
onClear();
@@ -195,7 +210,7 @@
case R.id.fun_log:
case R.id.fun_sin:
case R.id.fun_tan:
- // add left paren after functions
+ // Add left parenthesis after functions.
mFormulaEditText.append(((Button) view).getText() + "(");
break;
default:
@@ -216,11 +231,11 @@
}
@Override
- public void onEvaluate(String expr, String result, String error) {
+ public void onEvaluate(String expr, String result, int errorResourceId) {
if (mCurrentState == CalculatorState.INPUT) {
mResultEditText.setText(result);
- } else if (!TextUtils.isEmpty(error)) {
- onError(error);
+ } else if (errorResourceId != INVALID_RES_ID) {
+ onError(errorResourceId);
} else if (!TextUtils.isEmpty(result)) {
onResult(result);
} else if (mCurrentState == CalculatorState.EVALUATE) {
@@ -327,12 +342,18 @@
});
}
- private void onError(final String error) {
+ private void onError(final int errorResourceId) {
+ if (mCurrentState != CalculatorState.EVALUATE) {
+ // Only animate error on evaluate.
+ mResultEditText.setText(errorResourceId);
+ return;
+ }
+
reveal(mCurrentButton, R.color.calculator_error_color, new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
setState(CalculatorState.ERROR);
- mResultEditText.setText(error);
+ mResultEditText.setText(errorResourceId);
}
});
}
diff --git a/src/com/android/calculator2/CalculatorEditText.java b/src/com/android/calculator2/CalculatorEditText.java
index 63a5ae5..b5ab026 100644
--- a/src/com/android/calculator2/CalculatorEditText.java
+++ b/src/com/android/calculator2/CalculatorEditText.java
@@ -60,6 +60,10 @@
private final float mMinimumTextSize;
private final float mStepTextSize;
+ // Temporary objects for use in layout methods.
+ private final Paint mTempPaint = new TextPaint();
+ private final Rect mTempRect = new Rect();
+
private int mWidthConstraint = -1;
private OnTextSizeChangeListener mOnTextSizeChangeListener;
@@ -92,17 +96,6 @@
}
@Override
- protected void onSelectionChanged(int selStart, int selEnd) {
- final int textLength = getText() == null ? 0 : getText().length();
- if (selStart != textLength || selEnd != textLength) {
- // Pin the selection to the end of the current text.
- setSelection(textLength);
- }
-
- super.onSelectionChanged(selStart, selEnd);
- }
-
- @Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getActionMasked() == MotionEvent.ACTION_UP) {
// Hack to prevent keyboard and insertion handle from showing.
@@ -123,8 +116,6 @@
@Override
protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
super.onTextChanged(text, start, lengthBefore, lengthAfter);
-
- setSelection(text.length());
setTextSize(TypedValue.COMPLEX_UNIT_PX, getVariableTextSize(text.toString()));
}
@@ -148,12 +139,15 @@
return getTextSize();
}
- final Paint paint = new TextPaint(getPaint());
+ // Capture current paint state.
+ mTempPaint.set(getPaint());
+
+ // Step through increasing text sizes until the text would no longer fit.
float lastFitTextSize = mMinimumTextSize;
while (lastFitTextSize < mMaximumTextSize) {
final float nextSize = Math.min(lastFitTextSize + mStepTextSize, mMaximumTextSize);
- paint.setTextSize(nextSize);
- if (paint.measureText(text) > mWidthConstraint) {
+ mTempPaint.setTextSize(nextSize);
+ if (mTempPaint.measureText(text) > mWidthConstraint) {
break;
} else {
lastFitTextSize = nextSize;
@@ -167,12 +161,10 @@
public int getCompoundPaddingTop() {
// Measure the top padding from the capital letter height of the text instead of the top,
// but don't remove more than the available top padding otherwise clipping may occur.
- final Rect capBounds = new Rect();
- getPaint().getTextBounds("H", 0, 1, capBounds);
+ getPaint().getTextBounds("H", 0, 1, mTempRect);
final FontMetricsInt fontMetrics = getPaint().getFontMetricsInt();
- final int paddingOffset = -(fontMetrics.ascent + capBounds.height());
-
+ final int paddingOffset = -(fontMetrics.ascent + mTempRect.height());
return super.getCompoundPaddingTop() - Math.min(getPaddingTop(), paddingOffset);
}
diff --git a/src/com/android/calculator2/CalculatorExpressionBuilder.java b/src/com/android/calculator2/CalculatorExpressionBuilder.java
index 028a504..7fb47be 100644
--- a/src/com/android/calculator2/CalculatorExpressionBuilder.java
+++ b/src/com/android/calculator2/CalculatorExpressionBuilder.java
@@ -25,10 +25,11 @@
private final CalculatorExpressionTokenizer mTokenizer;
private boolean mIsEdited;
- public CalculatorExpressionBuilder(Context context, CharSequence text, boolean isEdited) {
+ public CalculatorExpressionBuilder(
+ CharSequence text, CalculatorExpressionTokenizer tokenizer, boolean isEdited) {
super(text);
- mTokenizer = CalculatorExpressionTokenizer.getInstance(context);
+ mTokenizer = tokenizer;
mIsEdited = isEdited;
}
diff --git a/src/com/android/calculator2/CalculatorExpressionEvaluator.java b/src/com/android/calculator2/CalculatorExpressionEvaluator.java
index 5a032c3..26cd404 100644
--- a/src/com/android/calculator2/CalculatorExpressionEvaluator.java
+++ b/src/com/android/calculator2/CalculatorExpressionEvaluator.java
@@ -16,8 +16,6 @@
package com.android.calculator2;
-import android.content.Context;
-
import org.javia.arity.Symbols;
import org.javia.arity.SyntaxException;
import org.javia.arity.Util;
@@ -30,15 +28,9 @@
private final Symbols mSymbols;
private final CalculatorExpressionTokenizer mTokenizer;
- private final String mErrorNaN;
- private final String mErrorSyntax;
-
- public CalculatorExpressionEvaluator(Context context) {
+ public CalculatorExpressionEvaluator(CalculatorExpressionTokenizer tokenizer) {
mSymbols = new Symbols();
- mTokenizer = CalculatorExpressionTokenizer.getInstance(context);
-
- mErrorNaN = context.getString(R.string.error_nan);
- mErrorSyntax = context.getString(R.string.error_syntax);
+ mTokenizer = tokenizer;
}
public void evaluate(CharSequence expr, EvaluateCallback callback) {
@@ -54,8 +46,8 @@
}
try {
- if (expr == null || expr.length() == 0 || Double.valueOf(expr) != null) {
- callback.onEvaluate(expr, null, null);
+ if (expr.length() == 0 || Double.valueOf(expr) != null) {
+ callback.onEvaluate(expr, null, Calculator.INVALID_RES_ID);
return;
}
} catch (NumberFormatException e) {
@@ -65,20 +57,21 @@
try {
double result = mSymbols.eval(expr);
if (Double.isNaN(result)) {
- callback.onEvaluate(expr, null, mErrorNaN);
+ callback.onEvaluate(expr, null, R.string.error_nan);
} else {
// The arity library uses floating point arithmetic when evaluating the expression
// leading to precision errors in the result. The method doubleToString hides these
// errors; rounding the result by dropping N digits of precision.
- callback.onEvaluate(expr, mTokenizer.getLocalizedExpression(
- Util.doubleToString(result, MAX_DIGITS, ROUNDING_DIGITS)), null);
+ final String resultString = mTokenizer.getLocalizedExpression(
+ Util.doubleToString(result, MAX_DIGITS, ROUNDING_DIGITS));
+ callback.onEvaluate(expr, resultString, Calculator.INVALID_RES_ID);
}
} catch (SyntaxException e) {
- callback.onEvaluate(expr, null, mErrorSyntax);
+ callback.onEvaluate(expr, null, R.string.error_syntax);
}
}
public interface EvaluateCallback {
- public void onEvaluate(String expr, String result, String error);
+ public void onEvaluate(String expr, String result, int errorResourceId);
}
}
diff --git a/src/com/android/calculator2/CalculatorExpressionTokenizer.java b/src/com/android/calculator2/CalculatorExpressionTokenizer.java
index c4e2ab4..b9c91e2 100644
--- a/src/com/android/calculator2/CalculatorExpressionTokenizer.java
+++ b/src/com/android/calculator2/CalculatorExpressionTokenizer.java
@@ -24,19 +24,9 @@
public class CalculatorExpressionTokenizer {
- private static CalculatorExpressionTokenizer sSharedInstance;
-
- public static CalculatorExpressionTokenizer getInstance(Context context) {
- if (sSharedInstance == null) {
- sSharedInstance = new CalculatorExpressionTokenizer(context);
- }
-
- return sSharedInstance;
- }
-
private final Map<String, String> mReplacementMap;
- private CalculatorExpressionTokenizer(Context context) {
+ public CalculatorExpressionTokenizer(Context context) {
mReplacementMap = new HashMap<>();
mReplacementMap.put(".", context.getString(R.string.dec_point));