Miscellaneous fixes

-Fix fragment animation on exit
-Use DateUtils.RelativeTimeSpanString instead of SimpleDateFormat
-Fix RecyclerView ordering and remove arbitrary "25" pre-seeding
-Cancel evaluation for only the id corresponding to the recycled
ViewHolder

Fixes: 32918645
Fixes: 32945018
Fixes: 33000429

Change-Id: I55e3a101a02aec8fe03d43ad0b60343d1fa36940
diff --git a/src/com/android/calculator2/Calculator.java b/src/com/android/calculator2/Calculator.java
index ce80507..02047af 100644
--- a/src/com/android/calculator2/Calculator.java
+++ b/src/com/android/calculator2/Calculator.java
@@ -505,7 +505,6 @@
     public void onBackPressed() {
         if (!stopActionModeOrContextMenu()) {
             if (mDragLayout.isOpen()) {
-                // Close the layout and remove the fragment.
                 mDragLayout.setClosed();
                 popFragmentBackstack();
                 return;
diff --git a/src/com/android/calculator2/DragController.java b/src/com/android/calculator2/DragController.java
index b0ff122..4cc755e 100644
--- a/src/com/android/calculator2/DragController.java
+++ b/src/com/android/calculator2/DragController.java
@@ -53,10 +53,10 @@
 
         if (evaluator != null) {
             // Initialize controller
-            if (isDisplayEmpty()) {
+            if (EvaluatorStateUtils.isDisplayEmpty(mEvaluator)) {
                 // Empty display
                 mAnimationController = new EmptyAnimationController();
-            } else if (mEvaluator.hasResult(Evaluator.MAIN_INDEX)) {
+            } else if (isResultState()) {
                 // Result
                 mAnimationController = new ResultAnimationController();
             } else {
@@ -67,14 +67,8 @@
         }
     }
 
-    // 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;
-        }
+    private boolean isResultState() {
+        return mDisplayResult.getTranslationY() != 0;
     }
 
     public void setDisplayFormula(CalculatorFormula formula) {
@@ -92,7 +86,7 @@
     public void animateViews(float yFraction, RecyclerView recyclerView, int itemCount) {
         final HistoryAdapter.ViewHolder vh = (HistoryAdapter.ViewHolder)
                 recyclerView.findViewHolderForAdapterPosition(0);
-        if (vh != null && !isDisplayEmpty()) {
+        if (vh != null && !EvaluatorStateUtils.isDisplayEmpty(mEvaluator)) {
             final CalculatorFormula formula = vh.getFormula();
             final CalculatorResult result = vh.getResult();
             final TextView date = vh.getDate();
@@ -132,7 +126,7 @@
 
                 date.setTranslationY(mAnimationController.getDateTranslationY(yFraction));
             }
-        } else if (isDisplayEmpty()) {
+        } else if (EvaluatorStateUtils.isDisplayEmpty(mEvaluator)) {
             // There is no current expression but we still need to collect information
             // to translate the other viewholders.
             if (!mAnimationInitialized) {
diff --git a/src/com/android/calculator2/DragLayout.java b/src/com/android/calculator2/DragLayout.java
index 101b62e..e7920ce 100644
--- a/src/com/android/calculator2/DragLayout.java
+++ b/src/com/android/calculator2/DragLayout.java
@@ -184,7 +184,6 @@
             c.onClosed();
         }
         mDragHelper.smoothSlideViewTo(mHistoryFrame, 0, 0);
-        mHistoryFrame.setVisibility(GONE);
         mIsOpen = false;
     }
 
@@ -233,6 +232,7 @@
                 // The view stopped moving.
                 if (mDraggingBorder == 0) {
                     setClosed();
+                    mHistoryFrame.setVisibility(GONE);
                 } else if (mDraggingBorder == mVerticalRange) {
                     setOpen();
                 }
diff --git a/src/com/android/calculator2/EvaluatorStateUtils.java b/src/com/android/calculator2/EvaluatorStateUtils.java
new file mode 100644
index 0000000..2d8eddc
--- /dev/null
+++ b/src/com/android/calculator2/EvaluatorStateUtils.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.calculator2;
+
+/**
+ * Utils class to get the state of the passed-in Evaluator.
+ */
+
+public class EvaluatorStateUtils {
+    public static boolean isDisplayEmpty(Evaluator evaluator) {
+        if (evaluator != null) {
+            final CalculatorExpr mainExpr = evaluator.getExpr(Evaluator.MAIN_INDEX);
+            return mainExpr == null || mainExpr.isEmpty();
+        } else {
+            return true;
+        }
+    }
+}
diff --git a/src/com/android/calculator2/HistoryAdapter.java b/src/com/android/calculator2/HistoryAdapter.java
index 169e34f..ca5509c 100644
--- a/src/com/android/calculator2/HistoryAdapter.java
+++ b/src/com/android/calculator2/HistoryAdapter.java
@@ -39,13 +39,12 @@
 
     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;
+        setHasStableIds(true);
     }
 
     @Override
@@ -71,41 +70,44 @@
 
         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.mResult.setEvaluator(mEvaluator, item.getEvaluatorIndex());
+        if (item.getEvaluatorIndex() == Evaluator.MAIN_INDEX) {
             holder.mDate.setText(mCurrentExpressionDescription);
             holder.mDate.setContentDescription(mCurrentExpressionDescription);
         } else {
             holder.mDate.setText(item.getDateString());
-            holder.mDate.setContentDescription(item.getDateDescription());
         }
     }
 
-    public void setHasCurrentExpression(boolean has) {
-        mHasCurrentExpression = has;
-    }
-
     @Override
     public void onViewRecycled(ViewHolder holder) {
+        if (holder.getItemViewType() == EMPTY_VIEW_TYPE) {
+            return;
+        }
+        mEvaluator.cancel(holder.getItemId(), true);
+
         holder.mDate.setContentDescription(null);
         holder.mDate.setText(null);
         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 long getItemId(int position) {
+        return mDataSet.get(position).getEvaluatorIndex();
+    }
+
+    @Override
     public int getItemViewType(int position) {
         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));
+            final int evaluatorIndex = getEvaluatorIndex(position);
+            item = new HistoryItem(evaluatorIndex, mEvaluator.getTimeStamp(evaluatorIndex),
+                    mEvaluator.getExprAsSpannable(evaluatorIndex));
             mDataSet.set(position, item);
         }
         return item.isEmptyView() ? EMPTY_VIEW_TYPE : HISTORY_VIEW_TYPE;
@@ -120,6 +122,15 @@
         mDataSet = dataSet;
     }
 
+    private int getEvaluatorIndex(int position) {
+        if (EvaluatorStateUtils.isDisplayEmpty(mEvaluator)) {
+            return (int) mEvaluator.getMaxIndex() - position;
+        } else {
+            // Account for the additional "Current Expression" with the +1.
+            return (int) mEvaluator.getMaxIndex() - position + 1;
+        }
+    }
+
     public static class ViewHolder extends RecyclerView.ViewHolder {
 
         private TextView mDate;
diff --git a/src/com/android/calculator2/HistoryFragment.java b/src/com/android/calculator2/HistoryFragment.java
index aca7847..dcc7b25 100644
--- a/src/com/android/calculator2/HistoryFragment.java
+++ b/src/com/android/calculator2/HistoryFragment.java
@@ -19,7 +19,6 @@
 import android.animation.Animator;
 import android.animation.ObjectAnimator;
 import android.app.Fragment;
-import android.app.FragmentTransaction;
 import android.os.Bundle;
 import android.support.v7.widget.RecyclerView;
 import android.util.Log;
@@ -145,20 +144,13 @@
 
             final ArrayList<HistoryItem> newDataSet = new ArrayList<>();
 
-            if (!mEvaluator.getExpr(Evaluator.MAIN_INDEX).isEmpty()) {
+            if (!EvaluatorStateUtils.isDisplayEmpty(mEvaluator)) {
                 // 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*/,
+                newDataSet.add(new HistoryItem(Evaluator.MAIN_INDEX, System.currentTimeMillis(),
                         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) {
+            for (long i = 0; i < maxIndex; ++i) {
                 newDataSet.add(null);
             }
             if (maxIndex == 0) {
@@ -179,12 +171,11 @@
     public Animator onCreateAnimator(int transit, boolean enter, int nextAnim) {
         final View view = getView();
         final int height = getResources().getDisplayMetrics().heightPixels;
-        if (!enter) {
-            return ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, -height);
-        } else if (transit == FragmentTransaction.TRANSIT_FRAGMENT_OPEN) {
+        if (enter) {
             return ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, -height, 0f);
+        } else {
+            return ObjectAnimator.ofFloat(view, View.TRANSLATION_Y, -height);
         }
-        return null;
     }
 
     @Override
diff --git a/src/com/android/calculator2/HistoryItem.java b/src/com/android/calculator2/HistoryItem.java
index 63f46e9..5694cc7 100644
--- a/src/com/android/calculator2/HistoryItem.java
+++ b/src/com/android/calculator2/HistoryItem.java
@@ -17,33 +17,27 @@
 package com.android.calculator2;
 
 import android.text.Spannable;
-import android.text.format.DateFormat;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Locale;
+import android.text.format.DateUtils;
 
 public class HistoryItem {
 
-    private static final String dateFormat = "EEEMMMd";
-    private static final String descriptionFormat = "EEEEMMMMd";
-
-    private long mId;
-    private Date mDate;
+    private long mEvaluatorIndex;
+    /** Date in millis */
+    private long mTimeInMillis;
     private Spannable mFormula;
 
-    // This is true only for the "empty history" view.
+    /** This is true only for the "empty history" view. */
     private final boolean mIsEmpty;
 
-    public HistoryItem(long id, long millis, Spannable formula) {
-        mId = id;
-        mDate = new Date(millis);
+    public HistoryItem(long evaluatorIndex, long millis, Spannable formula) {
+        mEvaluatorIndex = evaluatorIndex;
+        mTimeInMillis = millis;
         mFormula = formula;
         mIsEmpty = false;
     }
 
-    public long getId() {
-        return mId;
+    public long getEvaluatorIndex() {
+        return mEvaluatorIndex;
     }
 
     public HistoryItem() {
@@ -54,17 +48,13 @@
         return mIsEmpty;
     }
 
-    public String getDateString() {
-        // TODO: Use DateUtils?
-        final Locale l = Locale.getDefault();
-        final String datePattern = DateFormat.getBestDateTimePattern(l, dateFormat);
-        return new SimpleDateFormat(datePattern, l).format(mDate);
-    }
-
-    public String getDateDescription() {
-        final Locale l = Locale.getDefault();
-        final String descriptionPattern = DateFormat.getBestDateTimePattern(l, descriptionFormat);
-        return new SimpleDateFormat(descriptionPattern, l).format(mDate);
+    /**
+     * @return String in format "n days ago"
+     * For n > 7, the date is returned.
+     */
+    public CharSequence getDateString() {
+        return DateUtils.getRelativeTimeSpanString(mTimeInMillis, System.currentTimeMillis(),
+                DateUtils.DAY_IN_MILLIS, DateUtils.FORMAT_ABBREV_RELATIVE);
     }
 
     public Spannable getFormula() {