Integrate database with history.

Bug: 31623549

Note: This CL operates under the assumption that there is something
in the current expression field (handling for various states of
CalculatorDisplay in ag/1613446)

Change-Id: I70992067ddc9c5eec079f00604549727787e26fe
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..a5d2e92 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,7 +309,7 @@
                 findViewById(R.id.op_sqr)
         };
 
-        mEvaluator = new Evaluator(this);
+        mEvaluator = Evaluator.getInstance(this);
         mResultText.setEvaluator(mEvaluator, Evaluator.MAIN_INDEX);
         KeyMaps.setActivity(this);
 
@@ -500,7 +501,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 +624,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 +1184,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 +1195,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..29c20f7 100644
--- a/src/com/android/calculator2/CalculatorResult.java
+++ b/src/com/android/calculator2/CalculatorResult.java
@@ -303,6 +303,18 @@
         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) {
+            final CalculatorExpr expr = mEvaluator.getExpr(mIndex);
+            if (expr != null && expr.hasInterestingOps()) {
+                mEvaluator.requireResult(mIndex, this, this);
+            }
+        }
+    }
+
     // From Evaluator.CharMetricsInfo.
     @Override
     public float separatorChars(String s, int len) {
diff --git a/src/com/android/calculator2/Evaluator.java b/src/com/android/calculator2/Evaluator.java
index d73534f..8f12ce1 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.
@@ -1731,6 +1739,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..19aa05b 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,17 @@
     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;
+
+    public HistoryAdapter(Calculator calculator, ArrayList<HistoryItem> dataSet,
+            String currentExpressionDescription) {
+        mEvaluator = Evaluator.getInstance(calculator);
+        mDataSet = dataSet;
+        mCurrentExpressionDescription = currentExpressionDescription;
     }
 
     @Override
@@ -64,21 +60,23 @@
     }
 
     @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 (isCurrentExpressionItem(position)) {
+            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());
     }
 
     @Override
@@ -88,12 +86,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);
+
+        // lazy-fill the data set
+        if (item == null) {
+            item = new HistoryItem(position, mEvaluator.getTimeStamp(position),
+                    mEvaluator.getExprAsSpannable(position));
+            mDataSet.add(position, item);
+        }
+        return item.isEmptyView() ? EMPTY_VIEW_TYPE : HISTORY_VIEW_TYPE;
     }
 
     @Override
@@ -102,7 +111,11 @@
     }
 
     private boolean isCurrentExpressionItem(int position) {
-        return position == mDataSet.size() - 1;
+        return position == 0;
+    }
+
+    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..15bf161 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,6 +50,9 @@
 
                 @Override
                 public void onClosed() {
+                    // TODO: only cancel historical evaluations
+                    mEvaluator.cancelAll(true);
+
                     mRecyclerView.scrollToPosition(mAdapter.getItemCount() - 1);
                 }
 
@@ -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));
     }
 
@@ -117,7 +124,6 @@
                 getActivity().onBackPressed();
             }
         });
-
         return view;
     }
 
@@ -129,6 +135,33 @@
         final DragLayout dragLayout = (DragLayout) getActivity().findViewById(R.id.drag_layout);
         dragLayout.removeDragCallback(mDragCallback);
         dragLayout.addDragCallback(mDragCallback);
+
+        mEvaluator = Evaluator.getInstance((Calculator) getActivity());
+
+        if (mEvaluator != null) {
+            final long maxIndex = mEvaluator.getMaxIndex();
+
+            final ArrayList<HistoryItem> newDataSet = new ArrayList<>();
+            // 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.
+            for (long i = maxIndex; 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 +182,8 @@
         if (dragLayout != null) {
             dragLayout.removeDragCallback(mDragCallback);
         }
+
+        mEvaluator.cancelAll(true);
         super.onDestroy();
     }
 
@@ -160,10 +195,6 @@
                 (CalculatorResult) getActivity().findViewById(R.id.result));
 
         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());
     }
 
     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