summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorHans Boehm <hboehm@google.com>2015-06-19 15:05:07 -0700
committerHans Boehm <hboehm@google.com>2015-06-23 18:15:09 -0700
commitc1ea091ae1a4d5145069bfc6248f83f5ca8f0b31 (patch)
tree24b028bfee9ed4a1f8b5008eae0a36df54eef2e5 /src
parent6e8087f483c13288bd589389414d9ed35ab73c31 (diff)
downloadandroid_packages_apps_ExactCalculator-c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31.tar.gz
android_packages_apps_ExactCalculator-c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31.tar.bz2
android_packages_apps_ExactCalculator-c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31.zip
Improve logic for evaluation/animation interruption
Bug: 21471857 Bug: 20819212 End rather than cancel() in-progress animation in the event of user interaction. Discard input that interrupted a computation only for delete, and clear, where it seems to make sense. Use similar interruption logic for physical keyboard input as for touch. Make integer exponentiation more interruptible. This remains imperfect; the latencies in a single BigInteger multiplication can be high. Filed b/21957088 to track that. Clear "instant" result before launching reevaluation. Otherwise the example from b/21957088 shows incorrect instant results for an uncomfortably long time as it's being entered. Correct some of the state maintenance operations in Calculator.java. The ANIMATE state was not being used correctly. Remove redundant cancelAll() and onCancelled() calls. Add an option to cancel without a message. Use it for clear. Change-Id: Ibab90dca0cb894e7985642f212ff41030f2fc52d
Diffstat (limited to 'src')
-rw-r--r--src/com/android/calculator2/BoundedRational.java3
-rw-r--r--src/com/android/calculator2/Calculator.java61
-rw-r--r--src/com/android/calculator2/Evaluator.java30
3 files changed, 66 insertions, 28 deletions
diff --git a/src/com/android/calculator2/BoundedRational.java b/src/com/android/calculator2/BoundedRational.java
index 2602f56..5b9f976 100644
--- a/src/com/android/calculator2/BoundedRational.java
+++ b/src/com/android/calculator2/BoundedRational.java
@@ -385,6 +385,9 @@ public class BoundedRational {
return ONE;
}
BoundedRational tmp = pow(exp.shiftRight(1));
+ if (Thread.interrupted()) {
+ throw new AbortedError();
+ }
return multiply(tmp, tmp);
}
diff --git a/src/com/android/calculator2/Calculator.java b/src/com/android/calculator2/Calculator.java
index e0c8a93..65c668b 100644
--- a/src/com/android/calculator2/Calculator.java
+++ b/src/com/android/calculator2/Calculator.java
@@ -83,6 +83,7 @@ public class Calculator extends Activity
INPUT, // Result and formula both visible, no evaluation requested,
// Though result may be visible on bottom line.
EVALUATE, // Both visible, evaluation requested, evaluation/animation incomplete.
+ // Not used for instant result evaluation.
INIT, // Very temporary state used as alternative to EVALUATE
// during reinitialization. Do not animate on completion.
ANIMATE, // Result computed, animation to enlarge result window in progress.
@@ -126,7 +127,6 @@ public class Calculator extends Activity
@Override
public boolean onKey(View view, int keyCode, KeyEvent keyEvent) {
stopActionMode();
-
// Never consume DPAD key events.
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_UP:
@@ -135,7 +135,13 @@ public class Calculator extends Activity
case KeyEvent.KEYCODE_DPAD_RIGHT:
return false;
}
-
+ // Always cancel unrequested in-progress evaluation, so that we don't have
+ // to worry about subsequent asynchronous completion.
+ // Requested in-progress evaluations are handled below.
+ if (mCurrentState != CalculatorState.EVALUATE) {
+ mEvaluator.cancelAll(true);
+ }
+ // In other cases we go ahead and process the input normally after cancelling:
if (keyEvent.getAction() != KeyEvent.ACTION_UP) {
return true;
}
@@ -151,6 +157,7 @@ public class Calculator extends Activity
onDelete();
return true;
default:
+ cancelIfEvaluating(false);
final int raw = keyEvent.getKeyCharacterMap()
.get(keyCode, keyEvent.getMetaState());
if ((raw & KeyCharacterMap.COMBINING_ACCENT) != 0) {
@@ -382,10 +389,10 @@ public class Calculator extends Activity
public void onUserInteraction() {
super.onUserInteraction();
- // If there's an animation in progress, cancel it so the user interaction can be handled
- // immediately.
+ // If there's an animation in progress, end it immediately, so the user interaction can
+ // be handled.
if (mCurrentAnimator != null) {
- mCurrentAnimator.cancel();
+ mCurrentAnimator.end();
}
}
@@ -478,19 +485,14 @@ public class Calculator extends Activity
}
public void onButtonClick(View view) {
+ // Any animation is ended before we get here.
mCurrentButton = view;
stopActionMode();
-
- // Always cancel in-progress evaluation.
- // If we were waiting for the result, do nothing else.
- mEvaluator.cancelAll();
-
- if (mCurrentState == CalculatorState.EVALUATE
- || mCurrentState == CalculatorState.ANIMATE) {
- onCancelled();
- return;
+ // See onKey above for the rationale behind some of the behavior below:
+ if (mCurrentState != CalculatorState.EVALUATE) {
+ // Cancel evaluations that were not specifically requested.
+ mEvaluator.cancelAll(true);
}
-
final int id = view.getId();
switch (id) {
case R.id.eq:
@@ -506,8 +508,12 @@ public class Calculator extends Activity
final boolean selected = !mInverseToggle.isSelected();
mInverseToggle.setSelected(selected);
onInverseToggled(selected);
+ if (mCurrentState == CalculatorState.RESULT) {
+ mResultText.redisplay(); // In case we cancelled reevaluation.
+ }
break;
case R.id.toggle_mode:
+ cancelIfEvaluating(false);
final boolean mode = !mEvaluator.getDegreeMode();
if (mCurrentState == CalculatorState.RESULT) {
mEvaluator.collapse(); // Capture result evaluated in old mode
@@ -516,7 +522,6 @@ public class Calculator extends Activity
// In input mode, we reinterpret already entered trig functions.
mEvaluator.setDegreeMode(mode);
onModeChanged(mode);
-
setState(CalculatorState.INPUT);
mResultText.clear();
if (mEvaluator.getExpr().hasInterestingOps()) {
@@ -524,6 +529,7 @@ public class Calculator extends Activity
}
break;
default:
+ cancelIfEvaluating(false);
addExplicitKeyToExpr(id);
redisplayAfterFormulaChange();
break;
@@ -568,9 +574,9 @@ public class Calculator extends Activity
}
}
+ // Reset state to reflect evaluator cancellation. Invoked by evaluator.
public void onCancelled() {
// We should be in EVALUATE state.
- // Display is still in input state.
setState(CalculatorState.INPUT);
mResultText.clear();
}
@@ -607,7 +613,23 @@ public class Calculator extends Activity
animatorSet.start();
}
+ /**
+ * Cancel any in-progress explicitly requested evaluations.
+ * @param quiet suppress pop-up message. Explicit evaluation can change the expression
+ value, and certainly changes the display, so it seems reasonable to warn.
+ * @return true if there was such an evaluation
+ */
+ private boolean cancelIfEvaluating(boolean quiet) {
+ if (mCurrentState == CalculatorState.EVALUATE) {
+ mEvaluator.cancelAll(quiet);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
private void onEquals() {
+ // In non-INPUT state assume this was redundant and ignore it.
if (mCurrentState == CalculatorState.INPUT && !mEvaluator.getExpr().isEmpty()) {
setState(CalculatorState.EVALUATE);
mEvaluator.requireResult();
@@ -619,8 +641,9 @@ public class Calculator extends Activity
// Note that we handle keyboard delete exactly like the delete button. For
// example the delete button can be used to delete a character from an incomplete
// function name typed on a physical keyboard.
- mEvaluator.cancelAll();
// This should be impossible in RESULT state.
+ // If there is an in-progress explicit evaluation, just cancel it and return.
+ if (cancelIfEvaluating(false)) return;
setState(CalculatorState.INPUT);
if (mUnprocessedChars != null) {
int len = mUnprocessedChars.length();
@@ -693,6 +716,7 @@ public class Calculator extends Activity
if (mEvaluator.getExpr().isEmpty()) {
return;
}
+ cancelIfEvaluating(true);
reveal(mCurrentButton, R.color.calculator_accent_color, new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -760,6 +784,7 @@ public class Calculator extends Activity
final int formulaTextColor = mFormulaText.getCurrentTextColor();
if (animate) {
+ setState(CalculatorState.ANIMATE);
final AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(
ObjectAnimator.ofPropertyValuesHolder(mResultText,
diff --git a/src/com/android/calculator2/Evaluator.java b/src/com/android/calculator2/Evaluator.java
index 666790a..d5676fd 100644
--- a/src/com/android/calculator2/Evaluator.java
+++ b/src/com/android/calculator2/Evaluator.java
@@ -345,11 +345,12 @@ class Evaluator {
class AsyncDisplayResult extends AsyncTask<Void, Void, InitialResult> {
private boolean mDm; // degrees
private boolean mRequired; // Result was requested by user.
- private boolean mTimedOut = false;
+ private boolean mQuiet; // Suppress cancellation message.
private Runnable mTimeoutRunnable = null;
AsyncDisplayResult(boolean dm, boolean required) {
mDm = dm;
mRequired = required;
+ mQuiet = !required;
}
private void handleTimeOut() {
boolean running = (getStatus() != AsyncTask.Status.FINISHED);
@@ -358,12 +359,15 @@ class Evaluator {
// Replace mExpr with clone to avoid races if task
// still runs for a while.
mExpr = (CalculatorExpr)mExpr.clone();
- mTimedOut = true;
if (mRequired) {
+ suppressCancelMessage();
displayTimeoutMessage();
}
}
}
+ private void suppressCancelMessage() {
+ mQuiet = true;
+ }
@Override
protected void onPreExecute() {
long timeout = mRequired ? mTimeout : mQuickTimeout;
@@ -375,7 +379,6 @@ class Evaluator {
}
};
mTimeoutHandler.postDelayed(mTimeoutRunnable, timeout);
- mTimedOut = false;
}
}
@Override
@@ -452,7 +455,7 @@ class Evaluator {
}
@Override
protected void onCancelled(InitialResult result) {
- if (mRequired && !mTimedOut) {
+ if (mRequired && !mQuiet) {
displayCancelledMessage();
} // Otherwise timeout processing displayed message.
mCalculator.onCancelled();
@@ -795,8 +798,10 @@ class Evaluator {
// Already done or in progress.
return;
}
- cancelAll();
clearCache();
+ // In very odd cases, there can be significant latency to evaluate.
+ // Don't show obsolete result.
+ mResult.clear();
mEvaluator = new AsyncDisplayResult(mDegreeMode, false);
mEvaluator.execute();
mChangedValue = false;
@@ -810,7 +815,7 @@ class Evaluator {
if (mCache == null || mExpr.hasTrailingOperators()) {
// Restart evaluator in requested mode, i.e. with
// longer timeout, not ignoring trailing operators.
- cancelAll();
+ cancelAll(true);
clearCache();
mEvaluator = new AsyncDisplayResult(mDegreeMode, true);
mEvaluator.execute();
@@ -823,10 +828,12 @@ class Evaluator {
}
}
- // Cancel all current background tasks.
- // Return true if we cancelled an initial evaluation,
- // leaving the expression displayed.
- boolean cancelAll() {
+ /**
+ * Cancel all current background tasks.
+ * @param quiet suppress cancellation message
+ * @return true if we cancelled an initial evaluation
+ */
+ boolean cancelAll(boolean quiet) {
if (mCurrentReevaluator != null) {
mCurrentReevaluator.cancel(true);
mCacheDigsReq = mCacheDigs;
@@ -835,6 +842,9 @@ class Evaluator {
mCurrentReevaluator = null;
}
if (mEvaluator != null) {
+ if (quiet) {
+ mEvaluator.suppressCancelMessage();
+ }
mEvaluator.cancel(true);
// There seems to be no good way to wait for cancellation
// to complete, and the evaluation continues to look at