Merge changes I72de7f22,I70992067 into ub-calculator-euler

* changes:
  Handle text animation in different display situations.
  Integrate database with history.
diff --git a/res/layout/history_item.xml b/res/layout/history_item.xml
index 1d45a14..87ef83b 100644
--- a/res/layout/history_item.xml
+++ b/res/layout/history_item.xml
@@ -54,6 +54,7 @@
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:bufferType="spannable"
+        android:maxLines="1"
         android:textColor="@color/display_result_text_color" />
 
 </LinearLayout>
diff --git a/src/com/android/calculator2/AlertDialogFragment.java b/src/com/android/calculator2/AlertDialogFragment.java
index e098022..12840ae 100644
--- a/src/com/android/calculator2/AlertDialogFragment.java
+++ b/src/com/android/calculator2/AlertDialogFragment.java
@@ -20,6 +20,7 @@
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.DialogFragment;
+import android.app.FragmentManager;
 import android.content.DialogInterface;
 import android.os.Bundle;
 import android.support.annotation.Nullable;
@@ -78,6 +79,10 @@
      */
     public static void showMessageDialog(Activity activity, @Nullable CharSequence title,
             CharSequence message, @Nullable CharSequence positiveButtonLabel) {
+        final FragmentManager manager = activity.getFragmentManager();
+        if (manager == null || manager.isDestroyed()) {
+            return;
+        }
         final AlertDialogFragment dialogFragment = new AlertDialogFragment();
         final Bundle args = new Bundle();
         args.putCharSequence(KEY_MESSAGE, message);
@@ -87,7 +92,7 @@
         }
         args.putCharSequence(KEY_TITLE, title);
         dialogFragment.setArguments(args);
-        dialogFragment.show(activity.getFragmentManager(), null /* tag */);
+        dialogFragment.show(manager, null /* tag */);
     }
 
     public AlertDialogFragment() {
diff --git a/src/com/android/calculator2/Calculator.java b/src/com/android/calculator2/Calculator.java
index 8aac061..41a041c 100644
--- a/src/com/android/calculator2/Calculator.java
+++ b/src/com/android/calculator2/Calculator.java
@@ -33,6 +33,7 @@
 import android.animation.PropertyValuesHolder;
 import android.app.ActionBar;
 import android.app.Activity;
+import android.app.FragmentManager;
 import android.app.FragmentTransaction;
 import android.content.ClipData;
 import android.content.DialogInterface;
@@ -189,7 +190,7 @@
 
         @Override
         public void onClosed() {
-            getFragmentManager().popBackStack();
+            popFragmentBackstack();
         }
 
         @Override
@@ -308,8 +309,10 @@
                 findViewById(R.id.op_sqr)
         };
 
-        mEvaluator = new Evaluator(this);
+        mEvaluator = Evaluator.getInstance(this);
         mResultText.setEvaluator(mEvaluator, Evaluator.MAIN_INDEX);
+        // This resultText should always use evaluateAndNotify, not requireResult().
+        mResultText.setShouldRequireResult(false);
         KeyMaps.setActivity(this);
 
         mDragLayout = (DragLayout) findViewById(R.id.drag_layout);
@@ -319,9 +322,10 @@
         mHistoryFrame = (FrameLayout) findViewById(R.id.history_frame);
 
         if (savedInstanceState != null) {
-            setState(CalculatorState.values()[
-                savedInstanceState.getInt(KEY_DISPLAY_STATE,
-                                          CalculatorState.INPUT.ordinal())]);
+            final CalculatorState savedState = CalculatorState.values()[
+                    savedInstanceState.getInt(KEY_DISPLAY_STATE,
+                            CalculatorState.INPUT.ordinal())];
+            setState(savedState);
             CharSequence unprocessed = savedInstanceState.getCharSequence(KEY_UNPROCESSED_CHARS);
             if (unprocessed != null) {
                 mUnprocessedChars = unprocessed.toString();
@@ -500,7 +504,7 @@
             if (mDragLayout.isOpen()) {
                 // Close the layout and remove the fragment.
                 mDragLayout.setClosed();
-                getFragmentManager().popBackStack();
+                popFragmentBackstack();
                 return;
             }
             if (mPadViewPager != null && mPadViewPager.getCurrentItem() != 0) {
@@ -623,6 +627,13 @@
         }
     }
 
+    private void popFragmentBackstack() {
+        final FragmentManager manager = getFragmentManager();
+        if (manager == null || manager.isDestroyed()) {
+            return;
+        }
+        manager.popBackStack();
+    }
     /**
      * Switch to INPUT from RESULT state in response to input of the specified button_id.
      * View.NO_ID is treated as an incomplete function id.
@@ -1176,6 +1187,10 @@
     }
 
     private void showHistoryFragment(int transit) {
+        final FragmentManager manager = getFragmentManager();
+        if (manager == null || manager.isDestroyed()) {
+            return;
+        }
         if (!mDragLayout.isOpen()) {
             getFragmentManager().beginTransaction()
                     .replace(R.id.history_frame, mHistoryFragment, HistoryFragment.TAG)
@@ -1183,6 +1198,7 @@
                     .addToBackStack(HistoryFragment.TAG)
                     .commit();
         }
+        // TODO: pass current scroll position of result
     }
 
     private void displayMessage(String title, String message) {
diff --git a/src/com/android/calculator2/CalculatorExpr.java b/src/com/android/calculator2/CalculatorExpr.java
index f20d2bf..c6eb9af 100644
--- a/src/com/android/calculator2/CalculatorExpr.java
+++ b/src/com/android/calculator2/CalculatorExpr.java
@@ -21,15 +21,12 @@
 import android.text.SpannableStringBuilder;
 import android.text.Spanned;
 import android.text.style.TtsSpan;
-import android.text.style.TtsSpan.TextBuilder;
 
-import java.math.BigInteger;
 import java.io.DataInput;
 import java.io.DataOutput;
 import java.io.IOException;
+import java.math.BigInteger;
 import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.IdentityHashMap;
 
 /**
  * A mathematical expression represented as a sequence of "tokens".
diff --git a/src/com/android/calculator2/CalculatorResult.java b/src/com/android/calculator2/CalculatorResult.java
index caf72fe..bfabfce 100644
--- a/src/com/android/calculator2/CalculatorResult.java
+++ b/src/com/android/calculator2/CalculatorResult.java
@@ -113,6 +113,7 @@
     private float mNoEllipsisCredit;
                             // Fraction of digit width saved by both replacing ellipsis with digit
                             // and avoiding scientific notation.
+    private boolean mShouldRequireResult = true;
     private static final int MAX_WIDTH = 100;
                             // Maximum number of digits displayed.
     public static final int MAX_LEADING_ZEROES = 6;
@@ -303,6 +304,22 @@
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
     }
 
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+
+        if (mEvaluator != null && mShouldRequireResult) {
+            final CalculatorExpr expr = mEvaluator.getExpr(mIndex);
+            if (expr != null && expr.hasInterestingOps()) {
+                mEvaluator.requireResult(mIndex, this, this);
+            }
+        }
+    }
+
+    public void setShouldRequireResult(boolean should) {
+        mShouldRequireResult = should;
+    }
+
     // From Evaluator.CharMetricsInfo.
     @Override
     public float separatorChars(String s, int len) {
diff --git a/src/com/android/calculator2/DragController.java b/src/com/android/calculator2/DragController.java
index 737cbb6..b0ff122 100644
--- a/src/com/android/calculator2/DragController.java
+++ b/src/com/android/calculator2/DragController.java
@@ -25,6 +25,8 @@
  */
 public final class DragController {
 
+    private static final String TAG = "DragController";
+
     // References to views from the Calculator Display.
     private CalculatorFormula mDisplayFormula;
     private CalculatorResult mDisplayResult;
@@ -33,12 +35,48 @@
     private int mFormulaTranslationY;
     private int mFormulaTranslationX;
     private float mFormulaScale;
+    private float mResultScale;
 
     private int mResultTranslationY;
     private int mResultTranslationX;
 
+    private int mDisplayHeight;
+
     private boolean mAnimationInitialized;
 
+    private AnimationController mAnimationController;
+
+    private Evaluator mEvaluator;
+
+    public void setEvaluator(Evaluator evaluator) {
+        mEvaluator = evaluator;
+
+        if (evaluator != null) {
+            // Initialize controller
+            if (isDisplayEmpty()) {
+                // Empty display
+                mAnimationController = new EmptyAnimationController();
+            } else if (mEvaluator.hasResult(Evaluator.MAIN_INDEX)) {
+                // Result
+                mAnimationController = new ResultAnimationController();
+            } else {
+                // There is something in the formula field. There may or may not be
+                // a quick result.
+                mAnimationController = new AnimationController();
+            }
+        }
+    }
+
+    // There is no formula or result in the CalculatorDisplay.
+    private boolean isDisplayEmpty() {
+        if (mEvaluator != null) {
+            final CalculatorExpr mainExpr = mEvaluator.getExpr(Evaluator.MAIN_INDEX);
+            return mainExpr == null || mainExpr.isEmpty();
+        } else {
+            return false;
+        }
+    }
+
     public void setDisplayFormula(CalculatorFormula formula) {
         mDisplayFormula = formula;
     }
@@ -54,82 +92,346 @@
     public void animateViews(float yFraction, RecyclerView recyclerView, int itemCount) {
         final HistoryAdapter.ViewHolder vh = (HistoryAdapter.ViewHolder)
                 recyclerView.findViewHolderForAdapterPosition(0);
-        if (vh != null) {
+        if (vh != null && !isDisplayEmpty()) {
             final CalculatorFormula formula = vh.getFormula();
             final CalculatorResult result = vh.getResult();
             final TextView date = vh.getDate();
 
             if (!mAnimationInitialized) {
-                // Calculate the scale for the text
-                mFormulaScale = (mDisplayFormula.getTextSize() * 1.0f) / formula.getTextSize();
+                mAnimationController.initializeScales(formula, result);
 
-                // Baseline of formula moves by the difference in formula bottom padding.
-                mFormulaTranslationY =
-                        mDisplayFormula.getPaddingBottom() - formula.getPaddingBottom()
-                        + mDisplayResult.getHeight() - result.getHeight();
+                mAnimationController.initializeFormulaTranslationX(formula);
 
-                // Right border of formula moves by the difference in formula end padding.
-                mFormulaTranslationX = mDisplayFormula.getPaddingEnd() - formula.getPaddingEnd();
+                mAnimationController.initializeFormulaTranslationY(formula, result);
 
-                // Baseline of result moves by the difference in result bottom padding.
-                mResultTranslationY = mDisplayResult.getPaddingBottom() - result.getPaddingBottom();
+                mAnimationController.initializeResultTranslationX(result);
 
-                mResultTranslationX = mDisplayResult.getPaddingEnd() - result.getPaddingEnd();
+                mAnimationController.initializeResultTranslationY(result);
 
                 mAnimationInitialized = true;
             }
 
             if (mAnimationInitialized) {
+                result.setScaleX(mAnimationController.getResultScale(yFraction));
+                result.setScaleY(mAnimationController.getResultScale(yFraction));
+
+                formula.setScaleX(mAnimationController.getFormulaScale(yFraction));
+                formula.setScaleY(mAnimationController.getFormulaScale(yFraction));
+
                 formula.setPivotX(formula.getWidth() - formula.getPaddingEnd());
                 formula.setPivotY(formula.getHeight() - formula.getPaddingBottom());
 
                 result.setPivotX(result.getWidth() - result.getPaddingEnd());
                 result.setPivotY(result.getHeight() - result.getPaddingBottom());
 
-                final float resultTranslationX = (mResultTranslationX * yFraction)
-                        - mResultTranslationX;
-                result.setTranslationX(resultTranslationX);
+                formula.setTranslationX(mAnimationController.getFormulaTranslationX(yFraction));
+                formula.setTranslationY(mAnimationController.getFormulaTranslationY(yFraction));
 
-                // Scale linearly between -mResultTranslationY and 0.
-                final float resultTranslationY =
-                        (mResultTranslationY * yFraction) - mResultTranslationY;
-                result.setTranslationY(resultTranslationY);
+                result.setTranslationX(mAnimationController.getResultTranslationX(yFraction));
+                result.setTranslationY(mAnimationController.getResultTranslationY(yFraction));
 
-                final float scale = mFormulaScale - (mFormulaScale * yFraction) + yFraction;
-                formula.setScaleY(scale);
-                formula.setScaleX(scale);
+                date.setTranslationY(mAnimationController.getDateTranslationY(yFraction));
+            }
+        } else if (isDisplayEmpty()) {
+            // There is no current expression but we still need to collect information
+            // to translate the other viewholders.
+            if (!mAnimationInitialized) {
+                mAnimationController.initializeDisplayHeight();
 
-                final float formulaTranslationX = (mFormulaTranslationX * yFraction)
-                        - mFormulaTranslationX;
-                formula.setTranslationX(formulaTranslationX);
+                mAnimationInitialized = true;
+            }
+        }
 
-                // Scale linearly between -FormulaTranslationY and 0.
-                final float formulaTranslationY =
-                        (mFormulaTranslationY * yFraction) - mFormulaTranslationY;
-                formula.setTranslationY(formulaTranslationY);
-
-                // We want the date to start out above the visible screen with
-                // this distance decreasing as it's pulled down.
-                final float dateTranslationY =
-                        - mToolbar.getHeight() * (1 - yFraction)
-                        + formulaTranslationY
-                        - mDisplayFormula.getPaddingTop()
-                        + (mDisplayFormula.getPaddingTop() * yFraction);
-                date.setTranslationY(dateTranslationY);
-
-                // Translate items above the Current Expression to accommodate the size change.
-                // Move up all ViewHolders above the current expression.
-                for (int i = recyclerView.getChildCount() - 1; i > 0; --i) {
-                    final RecyclerView.ViewHolder vh2 =
-                            recyclerView.getChildViewHolder(recyclerView.getChildAt(i));
-                    if (vh2 != null) {
-                        final View view = vh2.itemView;
-                        if (view != null) {
-                            view.setTranslationY(dateTranslationY);
-                        }
-                    }
+        // Move up all ViewHolders above the current expression; if there is no current expression,
+        // we're translating all the viewholders.
+        for (int i = recyclerView.getChildCount() - 1;
+             i >= mAnimationController.getFirstTranslatedViewHolderIndex();
+             --i) {
+            final RecyclerView.ViewHolder vh2 =
+                    recyclerView.getChildViewHolder(recyclerView.getChildAt(i));
+            if (vh2 != null) {
+                final View view = vh2.itemView;
+                if (view != null) {
+                    view.setTranslationY(
+                        mAnimationController.getHistoryElementTranslationY(yFraction));
                 }
             }
         }
     }
+
+    /**
+     * Reset all initialized values whenever the History fragment is closed because the
+     * DisplayState may change.
+     */
+    public void resetAnimationInitialized() {
+        mAnimationInitialized = false;
+    }
+
+    public interface AnimateTextInterface {
+
+        void initializeDisplayHeight();
+
+        void initializeScales(CalculatorFormula formula, CalculatorResult result);
+
+        void initializeFormulaTranslationX(CalculatorFormula formula);
+
+        void initializeFormulaTranslationY(CalculatorFormula formula, CalculatorResult result);
+
+        void initializeResultTranslationX(CalculatorResult result);
+
+        void initializeResultTranslationY(CalculatorResult result);
+
+        float getResultTranslationX(float yFraction);
+
+        float getResultTranslationY(float yFraction);
+
+        float getResultScale(float yFraction);
+
+        float getFormulaScale(float yFraction);
+
+        float getFormulaTranslationX(float yFraction);
+
+        float getFormulaTranslationY(float yFraction);
+
+        float getDateTranslationY(float yFraction);
+
+        float getHistoryElementTranslationY(float yFraction);
+
+        // Return the lowest index of the first Viewholder to be translated upwards.
+        // If there is no current expression, we translate all the viewholders; otherwise,
+        // we start at index 1.
+        int getFirstTranslatedViewHolderIndex();
+    }
+
+    // The default AnimationController when Display is in INPUT state and DisplayFormula is not
+    // empty. There may or may not be a quick result.
+    public class AnimationController implements DragController.AnimateTextInterface {
+
+        public void initializeDisplayHeight() {
+            // no-op
+        }
+
+        public void initializeScales(CalculatorFormula formula, CalculatorResult result) {
+            // Calculate the scale for the text
+            mFormulaScale = (mDisplayFormula.getTextSize() * 1.0f) / formula.getTextSize();
+        }
+
+        public void initializeFormulaTranslationY(CalculatorFormula formula,
+                CalculatorResult result) {
+            // Baseline of formula moves by the difference in formula bottom padding and the
+            // difference in result height.
+            mFormulaTranslationY =
+                    mDisplayFormula.getPaddingBottom() - formula.getPaddingBottom()
+                            + mDisplayResult.getHeight() - result.getHeight();
+
+        }
+
+        public void initializeFormulaTranslationX(CalculatorFormula formula) {
+            // Right border of formula moves by the difference in formula end padding.
+            mFormulaTranslationX = mDisplayFormula.getPaddingEnd() - formula.getPaddingEnd();
+        }
+
+        public void initializeResultTranslationY(CalculatorResult result) {
+            // Baseline of result moves by the difference in result bottom padding.
+            mResultTranslationY = mDisplayResult.getPaddingBottom() - result.getPaddingBottom();
+        }
+
+        public void initializeResultTranslationX(CalculatorResult result) {
+            mResultTranslationX = mDisplayResult.getPaddingEnd() - result.getPaddingEnd();
+        }
+
+        public float getResultTranslationX(float yFraction) {
+            return (mResultTranslationX * yFraction) - mResultTranslationX;
+        }
+
+        public float getResultTranslationY(float yFraction) {
+            return (mResultTranslationY * yFraction) - mResultTranslationY;
+        }
+
+        public float getResultScale(float yFraction) {
+            return 1;
+        }
+
+        public float getFormulaScale(float yFraction) {
+            return mFormulaScale - (mFormulaScale * yFraction) + yFraction;
+        }
+
+        public float getFormulaTranslationX(float yFraction) {
+            return (mFormulaTranslationX * yFraction) -
+                    mFormulaTranslationX;
+        }
+
+        public float getFormulaTranslationY(float yFraction) {
+            // Scale linearly between -FormulaTranslationY and 0.
+            return (mFormulaTranslationY * yFraction) - mFormulaTranslationY;
+        }
+
+        public float getDateTranslationY(float yFraction) {
+            // We also want the date to start out above the visible screen with
+            // this distance decreasing as it's pulled down.
+            return -mToolbar.getHeight() * (1 - yFraction)
+                    + getResultTranslationY(yFraction)
+                    - mDisplayFormula.getPaddingTop() +
+                    (mDisplayFormula.getPaddingTop() * yFraction);
+        }
+
+        public float getHistoryElementTranslationY(float yFraction) {
+            return getDateTranslationY(yFraction);
+        }
+
+        public int getFirstTranslatedViewHolderIndex() {
+            return 1;
+        }
+    }
+
+    // The default AnimationController when Display is in RESULT state.
+    public class ResultAnimationController extends AnimationController
+            implements DragController.AnimateTextInterface {
+        @Override
+        public void initializeScales(CalculatorFormula formula, CalculatorResult result) {
+            final float textSize = mDisplayResult.getTextSize() * mDisplayResult.getScaleX();
+            mResultScale = textSize / result.getTextSize();
+
+            mFormulaScale = 1;
+        }
+
+        @Override
+        public void initializeFormulaTranslationY(CalculatorFormula formula,
+                CalculatorResult result) {
+            // Baseline of formula moves by the difference in formula bottom padding and the
+            // difference in the result height.
+            mFormulaTranslationY = mDisplayFormula.getPaddingBottom() - formula.getPaddingBottom()
+                            + mDisplayResult.getHeight() - result.getHeight();
+        }
+
+        @Override
+        public void initializeFormulaTranslationX(CalculatorFormula formula) {
+            // Right border of formula moves by the difference in formula end padding.
+            mFormulaTranslationX = mDisplayFormula.getPaddingEnd() - formula.getPaddingEnd();
+        }
+
+        @Override
+        public void initializeResultTranslationY(CalculatorResult result) {
+            // Baseline of result moves by the difference in result bottom padding.
+            mResultTranslationY = mDisplayResult.getBottom() - result.getBottom() +
+                    mDisplayResult.getPaddingBottom() - result.getPaddingBottom();
+        }
+
+        @Override
+        public void initializeResultTranslationX(CalculatorResult result) {
+            mResultTranslationX = mDisplayResult.getPaddingEnd() - result.getPaddingEnd();
+        }
+
+        @Override
+        public float getResultTranslationX(float yFraction) {
+            return (mResultTranslationX * yFraction) - mResultTranslationX;
+        }
+
+        @Override
+        public float getResultTranslationY(float yFraction) {
+            return (mResultTranslationY * yFraction) - mResultTranslationY;
+        }
+
+        @Override
+        public float getFormulaTranslationX(float yFraction) {
+            return (mFormulaTranslationX * yFraction) -
+                    mFormulaTranslationX;
+        }
+
+        @Override
+        public float getFormulaTranslationY(float yFraction) {
+            return getDateTranslationY(yFraction);
+        }
+
+        @Override
+        public float getResultScale(float yFraction) {
+            return mResultScale - (mResultScale * yFraction) + yFraction;
+        }
+
+        @Override
+        public float getFormulaScale(float yFraction) {
+            return 1;
+        }
+
+        @Override
+        public float getDateTranslationY(float yFraction) {
+            // We also want the date to start out above the visible screen with
+            // this distance decreasing as it's pulled down.
+            return -mToolbar.getHeight() * (1 - yFraction)
+                    + (mResultTranslationY * yFraction) - mResultTranslationY
+                    - mDisplayFormula.getPaddingTop() +
+                    (mDisplayFormula.getPaddingTop() * yFraction);
+        }
+
+        @Override
+        public int getFirstTranslatedViewHolderIndex() {
+            return 1;
+        }
+    }
+
+    // The default AnimationController when Display is completely empty.
+    public class EmptyAnimationController extends AnimationController
+            implements DragController.AnimateTextInterface {
+        @Override
+        public void initializeDisplayHeight() {
+            mDisplayHeight = mToolbar.getHeight() + mDisplayResult.getHeight()
+                    + mDisplayFormula.getHeight();
+        }
+
+        @Override
+        public void initializeScales(CalculatorFormula formula, CalculatorResult result) {
+            // no-op
+        }
+
+        @Override
+        public void initializeFormulaTranslationY(CalculatorFormula formula,
+                CalculatorResult result) {
+            // no-op
+        }
+
+        @Override
+        public void initializeFormulaTranslationX(CalculatorFormula formula) {
+            // no-op
+        }
+
+        @Override
+        public void initializeResultTranslationY(CalculatorResult result) {
+            // no-op
+        }
+
+        @Override
+        public void initializeResultTranslationX(CalculatorResult result) {
+            // no-op
+        }
+
+        @Override
+        public float getResultTranslationX(float yFraction) {
+            return 0;
+        }
+
+        @Override
+        public float getResultTranslationY(float yFraction) {
+            return 0;
+        }
+
+        @Override
+        public float getFormulaScale(float yFraction) {
+            return 1;
+        }
+
+        @Override
+        public float getDateTranslationY(float yFraction) {
+            return 0;
+        }
+
+        @Override
+        public float getHistoryElementTranslationY(float yFraction) {
+            return -mDisplayHeight * (1 - yFraction);
+        }
+
+        @Override
+        public int getFirstTranslatedViewHolderIndex() {
+            return 0;
+        }
+    }
 }
diff --git a/src/com/android/calculator2/Evaluator.java b/src/com/android/calculator2/Evaluator.java
index d73534f..2e2fe21 100644
--- a/src/com/android/calculator2/Evaluator.java
+++ b/src/com/android/calculator2/Evaluator.java
@@ -17,14 +17,13 @@
 package com.android.calculator2;
 
 import android.app.Activity;
-import android.app.AlertDialog;
-import android.content.DialogInterface;
 import android.content.SharedPreferences;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Handler;
 import android.preference.PreferenceManager;
 import android.support.annotation.VisibleForTesting;
+import android.text.Spannable;
 import android.util.Log;
 
 import com.hp.creals.CR;
@@ -38,11 +37,11 @@
 import java.io.IOException;
 import java.text.DateFormat;
 import java.text.SimpleDateFormat;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.concurrent.ConcurrentHashMap;
 import java.util.Date;
 import java.util.Random;
 import java.util.TimeZone;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicReference;
 
 /**
  * This implements the calculator evaluation logic.
@@ -98,6 +97,15 @@
  */
 public class Evaluator implements CalculatorExpr.ExprResolver {
 
+    private static Evaluator evaluator;
+
+    public static Evaluator getInstance(Calculator calculator) {
+        if (evaluator == null) {
+            evaluator = new Evaluator(calculator);
+        }
+        return evaluator;
+    }
+
     public interface EvaluationListener {
         /**
          * Called if evaluation was explicitly cancelled or evaluation timed out.
@@ -1146,6 +1154,14 @@
     }
 
     /**
+     * Whether this expression has explicitly been evaluated (User pressed "=")
+     */
+    public boolean hasResult(long index) {
+        final ExprInfo ei = ensureExprIsCached(index);
+        return ei.mResultString != null;
+    }
+
+    /**
      * Is a reevaluation still in progress?
      */
     public boolean evaluationInProgress(long index) {
@@ -1731,6 +1747,10 @@
         return getExpr(index).toSpannableStringBuilder(mActivity).toString();
     }
 
+    public Spannable getExprAsSpannable(long index) {
+        return getExpr(index).toSpannableStringBuilder(mActivity);
+    }
+
     /**
      * Generate a String representation of all expressions in the database.
      * Debugging only.
diff --git a/src/com/android/calculator2/HistoryAdapter.java b/src/com/android/calculator2/HistoryAdapter.java
index 25c7044..169e34f 100644
--- a/src/com/android/calculator2/HistoryAdapter.java
+++ b/src/com/android/calculator2/HistoryAdapter.java
@@ -23,7 +23,6 @@
 import android.widget.TextView;
 
 import java.util.ArrayList;
-import java.util.Calendar;
 import java.util.List;
 
 /**
@@ -34,20 +33,19 @@
     private static final int EMPTY_VIEW_TYPE = 0;
     private static final int HISTORY_VIEW_TYPE = 1;
 
-    private final List<HistoryItem> mDataSet = new ArrayList<>();
-    private String mCurrentExpression;
+    private final Evaluator mEvaluator;
+    /* Text/accessibility descriptor for the current expression item. */
+    private final String mCurrentExpressionDescription;
 
-    public HistoryAdapter(int[] dataset, String currentExpression) {
-        mCurrentExpression = currentExpression;
-        // Temporary dataset
-        final Calendar calendar = Calendar.getInstance();
-        for (int i: dataset) {
-            calendar.set(2016, 10, i);
-            mDataSet.add(new HistoryItem(calendar.getTimeInMillis(), Integer.toString(i) + "+1",
-                    Integer.toString(i+1)));
-        }
-        // Temporary: just testing the empty view placeholder
-        mDataSet.add(new HistoryItem());
+    private List<HistoryItem> mDataSet;
+
+    private boolean mHasCurrentExpression = true;
+
+    public HistoryAdapter(Calculator calculator, ArrayList<HistoryItem> dataSet,
+            String currentExpressionDescription) {
+        mEvaluator = Evaluator.getInstance(calculator);
+        mDataSet = dataSet;
+        mCurrentExpressionDescription = currentExpressionDescription;
     }
 
     @Override
@@ -64,21 +62,27 @@
     }
 
     @Override
-    public void onBindViewHolder(HistoryAdapter.ViewHolder holder, int position) {
+    public void onBindViewHolder(final HistoryAdapter.ViewHolder holder, int position) {
         final HistoryItem item = mDataSet.get(position);
 
         if (item.isEmptyView()) {
             return;
         }
-        if (!isCurrentExpressionItem(position)) {
+
+        holder.mFormula.setText(item.getFormula());
+        // Note: HistoryItems that are not the current expression will always have interesting ops.
+        holder.mResult.setEvaluator(mEvaluator, item.getId());
+        if (mHasCurrentExpression && position == 0) {
+            holder.mDate.setText(mCurrentExpressionDescription);
+            holder.mDate.setContentDescription(mCurrentExpressionDescription);
+        } else {
             holder.mDate.setText(item.getDateString());
             holder.mDate.setContentDescription(item.getDateDescription());
-        } else {
-            holder.mDate.setText(mCurrentExpression);
-            holder.mDate.setContentDescription(mCurrentExpression);
         }
-        holder.mFormula.setText(item.getFormula());
-        holder.mResult.setText(item.getResult());
+    }
+
+    public void setHasCurrentExpression(boolean has) {
+        mHasCurrentExpression = has;
     }
 
     @Override
@@ -88,12 +92,23 @@
         holder.mFormula.setText(null);
         holder.mResult.setText(null);
 
+        // TODO: Only cancel the calculation for the recycled view
+        mEvaluator.cancelAll(true);
+
         super.onViewRecycled(holder);
     }
 
     @Override
     public int getItemViewType(int position) {
-        return mDataSet.get(position).isEmptyView() ? EMPTY_VIEW_TYPE : HISTORY_VIEW_TYPE;
+        HistoryItem item = mDataSet.get(position);
+
+        // Continue to lazy-fill the data set
+        if (item == null) {
+            item = new HistoryItem(position, mEvaluator.getTimeStamp(position),
+                    mEvaluator.getExprAsSpannable(position));
+            mDataSet.set(position, item);
+        }
+        return item.isEmptyView() ? EMPTY_VIEW_TYPE : HISTORY_VIEW_TYPE;
     }
 
     @Override
@@ -101,8 +116,8 @@
         return mDataSet.size();
     }
 
-    private boolean isCurrentExpressionItem(int position) {
-        return position == mDataSet.size() - 1;
+    public void setDataSet(ArrayList<HistoryItem> dataSet) {
+        mDataSet = dataSet;
     }
 
     public static class ViewHolder extends RecyclerView.ViewHolder {
diff --git a/src/com/android/calculator2/HistoryFragment.java b/src/com/android/calculator2/HistoryFragment.java
index 984ee82..a8f984d 100644
--- a/src/com/android/calculator2/HistoryFragment.java
+++ b/src/com/android/calculator2/HistoryFragment.java
@@ -30,6 +30,8 @@
 import android.view.ViewGroup;
 import android.widget.Toolbar;
 
+import java.util.ArrayList;
+
 public class HistoryFragment extends Fragment {
 
     public static final String TAG = "HistoryFragment";
@@ -48,7 +50,10 @@
 
                 @Override
                 public void onClosed() {
-                    mRecyclerView.scrollToPosition(mAdapter.getItemCount() - 1);
+                    // TODO: only cancel historical evaluations
+                    mEvaluator.cancelAll(true);
+
+                    mDragController.resetAnimationInitialized();
                 }
 
                 @Override
@@ -78,13 +83,15 @@
     private RecyclerView mRecyclerView;
     private HistoryAdapter mAdapter;
 
+    private Evaluator mEvaluator;
+
+    private ArrayList<HistoryItem> mDataSet = new ArrayList<>();
+
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
-        // Temporary data
-        final int[] testArray = {7};
-        mAdapter = new HistoryAdapter(testArray,
+        mAdapter = new HistoryAdapter((Calculator) getActivity(), mDataSet,
                 getContext().getResources().getString(R.string.title_current_expression));
     }
 
@@ -125,10 +132,47 @@
     public void onActivityCreated(Bundle savedInstanceState) {
         super.onActivityCreated(savedInstanceState);
 
-        initializeController();
         final DragLayout dragLayout = (DragLayout) getActivity().findViewById(R.id.drag_layout);
         dragLayout.removeDragCallback(mDragCallback);
         dragLayout.addDragCallback(mDragCallback);
+
+        mEvaluator = Evaluator.getInstance((Calculator) getActivity());
+
+        if (mEvaluator != null) {
+            initializeController();
+
+            final long maxIndex = mEvaluator.getMaxIndex();
+
+            final ArrayList<HistoryItem> newDataSet = new ArrayList<>();
+
+            if (!mEvaluator.getExpr(Evaluator.MAIN_INDEX).isEmpty()) {
+                // Add the current expression as the first element in the list (the layout is reversed
+                // and we want the current expression to be the last one in the recyclerview).
+                newDataSet.add(new HistoryItem(Evaluator.MAIN_INDEX, 0 /* millis*/,
+                        mEvaluator.getExprAsSpannable(0)));
+            }
+            // We retrieve the current expression separately, so it's excluded from this loop.
+            // We lazy-fill, so just retrieve the first 25 expressions for now.
+            for (long i = Math.min(maxIndex, 25); i > 0; --i) {
+                final HistoryItem item = new HistoryItem(i, mEvaluator.getTimeStamp(i),
+                        mEvaluator.getExprAsSpannable(i));
+                newDataSet.add(item);
+            }
+            for (long i = Math.max(maxIndex - 25, 0); i > 0; --i) {
+                newDataSet.add(null);
+            }
+            if (maxIndex == 0) {
+                newDataSet.add(new HistoryItem());
+            }
+            mDataSet = newDataSet;
+            mAdapter.setDataSet(mDataSet);
+        }
+
+        mAdapter.notifyDataSetChanged();
+
+        // Initialize the current expression element to dimensions that match the display to
+        // avoid flickering and scrolling when elements expand on drag start.
+        mDragController.animateViews(1.0f, mRecyclerView, mAdapter.getItemCount());
     }
 
     @Override
@@ -149,6 +193,8 @@
         if (dragLayout != null) {
             dragLayout.removeDragCallback(mDragCallback);
         }
+
+        mEvaluator.cancelAll(true);
         super.onDestroy();
     }
 
@@ -161,9 +207,7 @@
 
         mDragController.setToolbar(getActivity().findViewById(R.id.toolbar));
 
-        // Initialize the current expression element to dimensions that match the display to avoid
-        // flickering and scrolling when elements expand on drag start.
-        mDragController.animateViews(1.0f, mRecyclerView, mAdapter.getItemCount());
+        mDragController.setEvaluator(mEvaluator);
     }
 
     private void clearHistory() {
diff --git a/src/com/android/calculator2/HistoryItem.java b/src/com/android/calculator2/HistoryItem.java
index 17f009f..63f46e9 100644
--- a/src/com/android/calculator2/HistoryItem.java
+++ b/src/com/android/calculator2/HistoryItem.java
@@ -16,6 +16,7 @@
 
 package com.android.calculator2;
 
+import android.text.Spannable;
 import android.text.format.DateFormat;
 
 import java.text.SimpleDateFormat;
@@ -27,20 +28,24 @@
     private static final String dateFormat = "EEEMMMd";
     private static final String descriptionFormat = "EEEEMMMMd";
 
+    private long mId;
     private Date mDate;
-    private String mFormula;
-    private String mResult;
+    private Spannable mFormula;
 
     // This is true only for the "empty history" view.
     private final boolean mIsEmpty;
 
-    public HistoryItem(long millis, String formula, String result) {
+    public HistoryItem(long id, long millis, Spannable formula) {
+        mId = id;
         mDate = new Date(millis);
         mFormula = formula;
-        mResult = result;
         mIsEmpty = false;
     }
 
+    public long getId() {
+        return mId;
+    }
+
     public HistoryItem() {
         mIsEmpty = true;
     }
@@ -62,11 +67,7 @@
         return new SimpleDateFormat(descriptionPattern, l).format(mDate);
     }
 
-    public String getFormula() {
+    public Spannable getFormula() {
         return mFormula;
     }
-
-    public String getResult() {
-        return mResult;
-    }
 }
\ No newline at end of file