Always calculate textSize in CalculatorText#onMeasure
am: 0ace4eb854

* commit '0ace4eb8542b090864944b9f9d8e5e7af7898436':
  Always calculate textSize in CalculatorText#onMeasure
diff --git a/src/com/android/calculator2/BoundedRational.java b/src/com/android/calculator2/BoundedRational.java
index dc132f6..383e59d 100644
--- a/src/com/android/calculator2/BoundedRational.java
+++ b/src/com/android/calculator2/BoundedRational.java
@@ -35,7 +35,7 @@
     // much faster.
     // TODO: Maybe eventually make this extend Number?
 
-    private static final int MAX_SIZE = 800; // total, in bits
+    private static final int MAX_SIZE = 2000; // total, in bits
 
     private final BigInteger mNum;
     private final BigInteger mDen;
@@ -70,11 +70,11 @@
 
     /**
      * Convert to readable String.
-     * Intended for output output to user.  More expensive, less useful for debugging than
+     * Intended for output to user.  More expensive, less useful for debugging than
      * toString().  Not internationalized.
      */
     public String toNiceString() {
-        BoundedRational nicer = reduce().positiveDen();
+        final BoundedRational nicer = reduce().positiveDen();
         String result = nicer.mNum.toString();
         if (!nicer.mDen.equals(BigInteger.ONE)) {
             result += "/" + nicer.mDen;
@@ -90,6 +90,33 @@
     }
 
     /**
+     * Return a string with n copies of c.
+     */
+    private static String repeat(char c, int n) {
+        final StringBuilder result = new StringBuilder();
+        for (int i = 0; i < n; ++i) {
+            result.append(c);
+        }
+        return result.toString();
+    }
+
+    /*
+     * Returns a truncated (rounded towards 0) representation of the result.
+     * Includes n digits to the right of the decimal point.
+     * @param n result precision, >= 0
+     */
+    public String toString(int n) {
+        String digits = mNum.abs().multiply(BigInteger.TEN.pow(n)).divide(mDen.abs()).toString();
+        int len = digits.length();
+        if (len < n + 1) {
+            digits = repeat('0', n + 1 - len) + digits;
+            len = n + 1;
+        }
+        return (signum() < 0 ? "-" : "") + digits.substring(0, len - n) + "."
+                + digits.substring(len - n);
+    }
+
+    /**
      * Return a double approximation.
      * Primarily for debugging.
      */
diff --git a/src/com/android/calculator2/Calculator.java b/src/com/android/calculator2/Calculator.java
index dc08612..4f0de6c 100644
--- a/src/com/android/calculator2/Calculator.java
+++ b/src/com/android/calculator2/Calculator.java
@@ -939,8 +939,8 @@
      * Map them to the appropriate button pushes when possible.  Leftover characters
      * are added to mUnprocessedChars, which is presumed to immediately precede the newly
      * added characters.
-     * @param moreChars Characters to be added.
-     * @param explicit These characters were explicitly typed by the user, not pasted.
+     * @param moreChars characters to be added
+     * @param explicit these characters were explicitly typed by the user, not pasted
      */
     private void addChars(String moreChars, boolean explicit) {
         if (mUnprocessedChars != null) {
diff --git a/src/com/android/calculator2/CalculatorExpr.java b/src/com/android/calculator2/CalculatorExpr.java
index e2f6bee..59c800e 100644
--- a/src/com/android/calculator2/CalculatorExpr.java
+++ b/src/com/android/calculator2/CalculatorExpr.java
@@ -1130,6 +1130,15 @@
             val = v;
             ratVal = rv;
         }
+        /*
+         * Return decimal String result to the indicated precision.
+         * For rational values this is the exactly truncated result.
+         * Otherwise the error is < 1 in the last included digit.
+         * @param precOffset Always non-negative. 1 is accurate 1/10, 2 means 1/100, etc.
+         */
+        public String toString(int precOffset) {
+            return ratVal == null ? val.toString(precOffset) : ratVal.toString(precOffset);
+        }
     }
 
     /**
diff --git a/src/com/android/calculator2/CalculatorResult.java b/src/com/android/calculator2/CalculatorResult.java
index 646b772..84ac36a 100644
--- a/src/com/android/calculator2/CalculatorResult.java
+++ b/src/com/android/calculator2/CalculatorResult.java
@@ -67,16 +67,16 @@
                             // left of the display.  Zero means decimal point is barely displayed
                             // on the right.
     private int mLastPos;   // Position already reflected in display. Pixels.
-    private int mMinPos;    // Minimum position before all digits disappear off the right. Pixels.
+    private int mMinPos;    // Minimum position to avoid unnecessary blanks on the left. Pixels.
     private int mMaxPos;    // Maximum position before we start displaying the infinite
                             // sequence of trailing zeroes on the right. Pixels.
+    private int mWholeLen;  // Length of the whole part of current result.
     // In the following, we use a suffix of Offset to denote a character position in a numeric
     // string relative to the decimal point.  Positive is to the right and negative is to
     // the left. 1 = tenths position, -1 = units.  Integer.MAX_VALUE is sometimes used
     // for the offset of the last digit in an a nonterminating decimal expansion.
     // We use the suffix "Index" to denote a zero-based index into a string representing a
     // result.
-    // TODO: Apply the same convention to other classes.
     private int mMaxCharOffset;  // Character offset from decimal point of rightmost digit
                                  // that should be displayed.  Essentially the same as
     private int mLsdOffset;      // Position of least-significant digit in result
@@ -105,6 +105,14 @@
                             // have a decimal point and no ellipsis.
                             // We assume that we do not drop digits to make room for the decimal
                             // point in ordinary scientific notation. Thus >= 1.
+    private static final int MAX_COPY_EXTRA = 100;
+                            // The number of extra digits we are willing to compute to copy
+                            // a result as an exact number.
+    private static final int MAX_RECOMPUTE_DIGITS = 2000;
+                            // The maximum number of digits we're willing to recompute in the UI
+                            // thread.  We only do this for known rational results, where we
+                            // can bound the computation cost.
+
     private ActionMode mActionMode;
     private final ForegroundColorSpan mExponentColorSpan;
 
@@ -265,13 +273,13 @@
             }
             return;
         }
-        int wholeLen =  truncatedWholePart.length();
+        mWholeLen = truncatedWholePart.length();
         int negative = truncatedWholePart.charAt(0) == '-' ? 1 : 0;
-        if (msdIndex > wholeLen && msdIndex <= wholeLen + 3) {
+        if (msdIndex > mWholeLen && msdIndex <= mWholeLen + 3) {
             // Avoid tiny negative exponent; pretend msdIndex is just to the right of decimal point.
-            msdIndex = wholeLen - 1;
+            msdIndex = mWholeLen - 1;
         }
-        int minCharOffset = msdIndex - wholeLen;
+        int minCharOffset = msdIndex - mWholeLen;
                                 // Position of leftmost significant digit relative to dec. point.
                                 // Usually negative.
         mMaxCharOffset = MAX_RIGHT_SCROLL; // How far does it make sense to scroll right?
@@ -338,7 +346,7 @@
      * Unlike Evaluator.getMsdIndexOf, we treat a final 1 as significant.
      */
     public static int getNaiveMsdIndexOf(String s) {
-        int len = s.length();
+        final int len = s.length();
         for (int i = 0; i < len; ++i) {
             char c = s.charAt(i);
             if (c != '-' && c != '.' && c != '0') {
@@ -353,10 +361,10 @@
     // to getString and thus identifies the significance of the rightmost digit.
     // A value of 1 means the rightmost digits corresponds to tenths.
     // maxDigs is the maximum number of characters in the result.
-    // We set lastDisplayedOffset[0] to the offset of the last digit actually appearing in
-    // the display.
+    // If lastDisplayedOffset is not null, we set lastDisplayedOffset[0] to the offset of
+    // the last digit actually appearing in the display.
     // If forcePrecision is true, we make sure that the last displayed digit corresponds to
-    // precOffset, and allow maxDigs to be exceeded in assing the exponent.
+    // precOffset, and allow maxDigs to be exceeded in adding the exponent.
     // We add two distinct kinds of exponents:
     // (1) If the final result contains the leading digit we use standard scientific notation.
     // (2) If not, we add an exponent corresponding to an interpretation of the final result as
@@ -376,7 +384,9 @@
             // Ellipsis may be removed again in the type(1) scientific notation case.
         }
         final int decIndex = result.indexOf('.');
-        lastDisplayedOffset[0] = precOffset;
+        if (lastDisplayedOffset != null) {
+            lastDisplayedOffset[0] = precOffset;
+        }
         if ((decIndex == -1 || msdIndex != Evaluator.INVALID_MSD
                 && msdIndex - decIndex > MAX_LEADING_ZEROES + 1) &&  precOffset != -1) {
             // No decimal point displayed, and it's not just to the right of the last digit,
@@ -433,7 +443,9 @@
                     }
                 }
                 result = result.substring(0, result.length() - dropDigits);
-                lastDisplayedOffset[0] -= dropDigits;
+                if (lastDisplayedOffset != null) {
+                    lastDisplayedOffset[0] -= dropDigits;
+                }
             }
             result = result + "E" + Integer.toString(exponent);
         }
@@ -445,7 +457,7 @@
      * @param precOffset requested position (1 = tenths) of last included digit.
      * @param maxSize Maximum number of characters (more or less) in result.
      * @param lastDisplayedOffset Zeroth entry is set to actual offset of last included digit,
-     *                            after adjusting for exponent, etc.
+     *                            after adjusting for exponent, etc.  May be null.
      * @param forcePrecision Ensure that last included digit is at pos, at the expense
      *                       of treating maxSize as a soft limit.
      */
@@ -460,14 +472,14 @@
                 lastDisplayedOffset, forcePrecision);
    }
 
-    // Return entire result (within reason) up to current displayed precision.
+    /**
+     * Return entire result (within reason) up to current displayed precision.
+     */
     public String getFullText() {
         if (!mValid) return "";
         if (!mScrollable) return getText().toString();
-        int currentCharOffset = getCurrentCharOffset();
-        int unused[] = new int[1];
         return KeyMaps.translateResult(getFormattedResult(mLastDisplayedOffset, MAX_COPY_SIZE,
-                unused, true));
+                null, true));
     }
 
     public boolean fullTextIsExact() {
@@ -476,6 +488,27 @@
     }
 
     /**
+     * Get entire result up to current displayed precision, or up to MAX_COPY_EXTRA additional
+     * digits, if it will lead to an exact result.
+     */
+    public String getFullCopyText() {
+        if (!mValid
+                || mLsdOffset == Integer.MAX_VALUE
+                || fullTextIsExact()
+                || mWholeLen > MAX_RECOMPUTE_DIGITS
+                || mWholeLen + mLsdOffset > MAX_RECOMPUTE_DIGITS
+                || mLsdOffset - mLastDisplayedOffset > MAX_COPY_EXTRA) {
+            return getFullText();
+        }
+        // It's reasonable to compute and copy the exact result instead.
+        final int nonNegLsdOffset = Math.max(0, mLsdOffset);
+        final String rawResult = mEvaluator.getRational().toString(nonNegLsdOffset);
+        final String formattedResult = formatResult(rawResult, nonNegLsdOffset, MAX_COPY_SIZE,
+                false, rawResult.charAt(0) == '-', null, true);
+        return KeyMaps.translateResult(formattedResult);
+    }
+
+    /**
      * Return the maximum number of characters that will fit in the result display.
      * May be called asynchronously from non-UI thread.
      */
@@ -582,9 +615,14 @@
         public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
             switch (item.getItemId()) {
             case R.id.menu_copy:
-                copyContent();
-                mode.finish();
-                return true;
+                if (mEvaluator.reevaluationInProgress()) {
+                    // Refuse to copy placeholder characters.
+                    return false;
+                } else {
+                    copyContent();
+                    mode.finish();
+                    return true;
+                }
             default:
                 return false;
             }
@@ -638,7 +676,7 @@
     }
 
     private void copyContent() {
-        final CharSequence text = getFullText();
+        final CharSequence text = getFullCopyText();
         ClipboardManager clipboard =
                 (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
         // We include a tag URI, to allow us to recognize our own results and handle them
diff --git a/src/com/android/calculator2/Evaluator.java b/src/com/android/calculator2/Evaluator.java
index 839c19b..38e9fe0 100644
--- a/src/com/android/calculator2/Evaluator.java
+++ b/src/com/android/calculator2/Evaluator.java
@@ -350,7 +350,7 @@
                     return new InitialResult(R.string.timeout);
                 }
                 int precOffset = INIT_PREC;
-                String initResult = res.val.toString(precOffset);
+                String initResult = res.toString(precOffset);
                 int msd = getMsdIndexOf(initResult);
                 if (BoundedRational.asBigInteger(res.ratVal) == null
                         && msd == INVALID_MSD) {
@@ -364,7 +364,7 @@
                 final int newPrecOffset = initDisplayOffset + EXTRA_DIGITS;
                 if (newPrecOffset > precOffset) {
                     precOffset = newPrecOffset;
-                    initResult = res.val.toString(precOffset);
+                    initResult = res.toString(precOffset);
                 }
                 return new InitialResult(res.val, res.ratVal,
                         initResult, precOffset, initDisplayOffset);
@@ -905,6 +905,13 @@
     }
 
     /**
+     * Is a reevaluation still in progress?
+     */
+    public boolean reevaluationInProgress() {
+        return mCurrentReevaluator != null;
+    }
+
+    /**
      * Cancel all current background tasks.
      * @param quiet suppress cancellation message
      * @return      true if we cancelled an initial evaluation
diff --git a/tests/src/com/android/calculator2/BRTest.java b/tests/src/com/android/calculator2/BRTest.java
index 68214d0..f287a9e 100644
--- a/tests/src/com/android/calculator2/BRTest.java
+++ b/tests/src/com/android/calculator2/BRTest.java
@@ -128,7 +128,10 @@
     public void testBR() {
         BoundedRational b = new BoundedRational(4,-6);
         check(b.toString().equals("4/-6"), "toString(4/-6)");
-        check(b.toNiceString().equals("-2/3"),"toNiceString(4/-6)");
+        check(b.toNiceString().equals("-2/3"), "toNiceString(4/-6)");
+        check(b.toString(1).equals("-0.6"), "(4/-6).toString(1)");
+        check(BR_15.toString(0).equals("15."), "15.toString(1)");
+        check(BR_0.toString(2).equals("0.00"), "0.toString(2)");
         checkEq(BR_0, CR.valueOf(0), "0");
         checkEq(BR_390, CR.valueOf(390), "390");
         checkEq(BR_15, CR.valueOf(15), "15");