Arrange for evaluateAndNotify calls at the right time.

Bug: 33011774
Bug: 33087975

We were sometimes calling it before layout after restart. Defer to
onLayout, as with requireResult.

Arrange for an explicit call when returning from HistoryFragment
to compensate for unintentional cancellation. This part is a hack
that needs to be revisited.

Adjust some constants to make it less likely that we will not see
am instant result, but equals will produce a valid result. This is
not a fundamental change, but 10000! now works as expected.
As does e^(1+10^-1000)-e, since we now force more evaluation to
try to distinguish a result from zero .  This may slow things down
on underpowered devices, but it shouldn't be a serious issue.

Change-Id: I5fcddef84dd907bc83d9bf575d0d378ca99d6359
diff --git a/src/com/android/calculator2/Calculator.java b/src/com/android/calculator2/Calculator.java
index f13a9ed..ce11d7a 100644
--- a/src/com/android/calculator2/Calculator.java
+++ b/src/com/android/calculator2/Calculator.java
@@ -402,16 +402,15 @@
             showAndMaybeHideToolbar();
         }
 
+        redisplayFormula();
         if (mCurrentState != CalculatorState.INPUT) {
             // Just reevaluate.
-            redisplayFormula();
             setState(CalculatorState.INIT);
             // Request evaluation when we know display width.
-            mResultText.setShouldRequireResult(true, this);
+            mResultText.setShouldEvaluateResult(CalculatorResult.SHOULD_REQUIRE, this);
         } else {
             // This resultText will explicitly call evaluateAndNotify when ready.
-            mResultText.setShouldRequireResult(false, null);
-            redisplayAfterFormulaChange();
+            mResultText.setShouldEvaluateResult(CalculatorResult.SHOULD_EVALUATE, this);
         }
         // TODO: We're currently not saving and restoring scroll position.
         //       We probably should.  Details may require care to deal with:
@@ -457,7 +456,7 @@
         if (mCurrentState != state) {
             if (state == CalculatorState.INPUT) {
                 // We'll explicitly request evaluation from now on.
-                mResultText.setShouldRequireResult(false, null);
+                mResultText.setShouldEvaluateResult(CalculatorResult.SHOULD_NOT_EVALUATE, null);
                 restoreDisplayPositions();
             }
             mCurrentState = state;
@@ -722,6 +721,13 @@
         addKeyToExpr(id);
     }
 
+    public void evaluateInstantIfNecessary() {
+        if (mCurrentState == CalculatorState.INPUT
+                && mEvaluator.getExpr(Evaluator.MAIN_INDEX).hasInterestingOps()) {
+            mEvaluator.evaluateAndNotify(Evaluator.MAIN_INDEX, this, mResultText);
+        }
+    }
+
     private void redisplayAfterFormulaChange() {
         // TODO: Could do this more incrementally.
         redisplayFormula();
@@ -731,9 +737,7 @@
             // Force reevaluation when text is deleted, even if expression is unchanged.
             mEvaluator.touch();
         } else {
-            if (mEvaluator.getExpr(Evaluator.MAIN_INDEX).hasInterestingOps()) {
-                mEvaluator.evaluateAndNotify(Evaluator.MAIN_INDEX, this, mResultText);
-            }
+            evaluateInstantIfNecessary();
         }
     }
 
@@ -806,9 +810,8 @@
                 showAndMaybeHideToolbar();
                 setState(CalculatorState.INPUT);
                 mResultText.clear();
-                if (!haveUnprocessed()
-                        && mEvaluator.getExpr(Evaluator.MAIN_INDEX).hasInterestingOps()) {
-                    mEvaluator.evaluateAndNotify(mEvaluator.MAIN_INDEX, this, mResultText);
+                if (!haveUnprocessed()) {
+                    evaluateInstantIfNecessary();
                 }
                 return;
             default:
diff --git a/src/com/android/calculator2/CalculatorResult.java b/src/com/android/calculator2/CalculatorResult.java
index 0b399c4..5e61dc4 100644
--- a/src/com/android/calculator2/CalculatorResult.java
+++ b/src/com/android/calculator2/CalculatorResult.java
@@ -23,6 +23,7 @@
 import android.content.Context;
 import android.graphics.Rect;
 import android.os.Build;
+import android.support.annotation.IntDef;
 import android.support.v4.content.ContextCompat;
 import android.support.v4.os.BuildCompat;
 import android.text.Layout;
@@ -45,6 +46,9 @@
 import android.widget.OverScroller;
 import android.widget.Toast;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 // A text widget that is "infinitely" scrollable to the right,
 // and obtains the text to display via a callback to Logic.
 public class CalculatorResult extends AlignedTextView implements MenuItem.OnMenuItemClickListener,
@@ -114,7 +118,14 @@
     private float mNoEllipsisCredit;
                             // Fraction of digit width saved by both replacing ellipsis with digit
                             // and avoiding scientific notation.
-    private boolean mShouldRequireResult = true;
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({SHOULD_REQUIRE, SHOULD_EVALUATE, SHOULD_NOT_EVALUATE})
+    public @interface EvaluationRequest {}
+    public static final int SHOULD_REQUIRE = 2;
+    public static final int SHOULD_EVALUATE = 1;
+    public static final int SHOULD_NOT_EVALUATE = 0;
+    @EvaluationRequest private int mEvaluationRequest = SHOULD_REQUIRE;
+                            // Should we evaluate when layout completes, and how?
     private Evaluator.EvaluationListener mEvaluationListener = this;
                             // Listener to use if/when evaluation is requested.
     public static final int MAX_LEADING_ZEROES = 6;
@@ -313,17 +324,26 @@
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
 
-        if (mEvaluator != null && mShouldRequireResult) {
+        if (mEvaluator != null && mEvaluationRequest != SHOULD_NOT_EVALUATE) {
             final CalculatorExpr expr = mEvaluator.getExpr(mIndex);
             if (expr != null && expr.hasInterestingOps()) {
-                mEvaluator.requireResult(mIndex, mEvaluationListener, this);
+                if (mEvaluationRequest == SHOULD_REQUIRE) {
+                    mEvaluator.requireResult(mIndex, mEvaluationListener, this);
+                } else {
+                    mEvaluator.evaluateAndNotify(mIndex, mEvaluationListener, this);
+                }
             }
         }
     }
 
-    public void setShouldRequireResult(boolean should, Evaluator.EvaluationListener listener) {
+    /**
+     * Specify whether we should evaluate result on layout.
+     * @param should one of SHOULD_REQUIRE, SHOULD_EVALUATE, SHOULD_NOT_EVALUATE
+     */
+    public void setShouldEvaluateResult(@EvaluationRequest int request,
+            Evaluator.EvaluationListener listener) {
         mEvaluationListener = listener;
-        mShouldRequireResult = should;
+        mEvaluationRequest = request;
     }
 
     // From Evaluator.CharMetricsInfo.
@@ -1113,4 +1133,4 @@
                 return false;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/com/android/calculator2/Evaluator.java b/src/com/android/calculator2/Evaluator.java
index 77c762d..df36d49 100644
--- a/src/com/android/calculator2/Evaluator.java
+++ b/src/com/android/calculator2/Evaluator.java
@@ -240,10 +240,10 @@
 
     // The largest number of digits to the right of the decimal point to which we will evaluate to
     // compute proper scientific notation for values close to zero.  Chosen to ensure that we
-    // always to better than IEEE double precision at identifying nonzeros.
-    // This used only when we cannot a prior determine the most significant digit position, as
+    // always to better than IEEE double precision at identifying nonzeros. And then some.
+    // This is used only when we cannot a priori determine the most significant digit position, as
     // we always can if we have a rational representation.
-    private static final int MAX_MSD_PREC_OFFSET = 320;
+    private static final int MAX_MSD_PREC_OFFSET = 1100;
 
     // If we can replace an exponent by this many leading zeroes, we do so.  Also used in
     // estimating exponent size for truncating short representation.
@@ -430,7 +430,7 @@
      * Maximum result bit length for unrequested, speculative evaluations.
      * Also used to bound evaluation precision for small non-zero fractions.
      */
-    private static final int QUICK_MAX_RESULT_BITS = 50000;
+    private static final int QUICK_MAX_RESULT_BITS = 150000;
 
     private void displayTimeoutMessage(boolean longTimeout) {
         AlertDialogFragment.showMessageDialog(mActivity, R.string.dialog_timeout,
diff --git a/src/com/android/calculator2/HistoryFragment.java b/src/com/android/calculator2/HistoryFragment.java
index 5be3452..1d13a98 100644
--- a/src/com/android/calculator2/HistoryFragment.java
+++ b/src/com/android/calculator2/HistoryFragment.java
@@ -198,6 +198,9 @@
 
         mEvaluator.cancelAll(true);
         super.onDestroy();
+        // FIXME: There are probably better ways to do this. But we can end up cancelling
+        // an in-progress evaluation for the main expression that we have to restart.
+        ((Calculator)(getActivity())).evaluateInstantIfNecessary();
     }
 
     private void initializeController() {