diff options
author | Hans Boehm <hboehm@google.com> | 2015-06-19 15:05:07 -0700 |
---|---|---|
committer | Hans Boehm <hboehm@google.com> | 2015-06-23 18:15:09 -0700 |
commit | c1ea091ae1a4d5145069bfc6248f83f5ca8f0b31 (patch) | |
tree | 24b028bfee9ed4a1f8b5008eae0a36df54eef2e5 /src | |
parent | 6e8087f483c13288bd589389414d9ed35ab73c31 (diff) | |
download | android_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.java | 3 | ||||
-rw-r--r-- | src/com/android/calculator2/Calculator.java | 61 | ||||
-rw-r--r-- | src/com/android/calculator2/Evaluator.java | 30 |
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 |