Merge "Fix Calculator locale change log spam" into ub-calculator-euler
diff --git a/res/layout/pad_operator_two_col.xml b/res/layout/pad_operator_two_col.xml
index 0d9308d..e056ba5 100644
--- a/res/layout/pad_operator_two_col.xml
+++ b/res/layout/pad_operator_two_col.xml
@@ -58,14 +58,6 @@
         app:layout_column="0" />
 
     <Button
-        android:id="@+id/memory_store"
-        style="@style/PadButtonStyle.Operator.Text"
-        android:contentDescription="@string/desc_memory_store"
-        android:text="@string/memory_store"
-        app:layout_row="1"
-        app:layout_column="1" />
-
-    <Button
         android:id="@+id/op_sub"
         style="@style/PadButtonStyle.Operator"
         android:contentDescription="@string/desc_op_sub"
@@ -74,14 +66,6 @@
         app:layout_column="0" />
 
     <Button
-        android:id="@+id/memory_recall"
-        style="@style/PadButtonStyle.Operator.Text"
-        android:contentDescription="@string/desc_memory_recall"
-        android:text="@string/memory_recall"
-        app:layout_row="2"
-        app:layout_column="1" />
-
-    <Button
         android:id="@+id/op_add"
         style="@style/PadButtonStyle.Operator"
         android:contentDescription="@string/desc_op_add"
diff --git a/res/menu/copy.xml b/res/menu/copy.xml
deleted file mode 100644
index 5897f88..0000000
--- a/res/menu/copy.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
- * Copyright (C) 2011, 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.
- */
--->
-
-<menu xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <item android:id="@+id/menu_copy"
-        android:title="@android:string/copy"/>
-
-</menu>
diff --git a/res/menu/paste.xml b/res/menu/menu_formula.xml
similarity index 84%
rename from res/menu/paste.xml
rename to res/menu/menu_formula.xml
index 964be0d..8882c8a 100644
--- a/res/menu/paste.xml
+++ b/res/menu/menu_formula.xml
@@ -19,7 +19,10 @@
 
 <menu xmlns:android="http://schemas.android.com/apk/res/android">
 
+    <item android:id="@+id/memory_recall"
+          android:title="@string/memory_recall"/>
+
     <item android:id="@+id/menu_paste"
-        android:title="@android:string/paste"/>
+          android:title="@android:string/paste"/>
 
 </menu>
diff --git a/res/menu/paste.xml b/res/menu/menu_result.xml
similarity index 65%
copy from res/menu/paste.xml
copy to res/menu/menu_result.xml
index 964be0d..15e76cd 100644
--- a/res/menu/paste.xml
+++ b/res/menu/menu_result.xml
@@ -19,7 +19,20 @@
 
 <menu xmlns:android="http://schemas.android.com/apk/res/android">
 
-    <item android:id="@+id/menu_paste"
-        android:title="@android:string/paste"/>
+    <item
+        android:id="@+id/memory_store"
+        android:title="@string/memory_store" />
 
-</menu>
+    <item
+        android:id="@+id/memory_add"
+        android:title="@string/memory_add" />
+
+    <item
+        android:id="@+id/memory_subtract"
+        android:title="@string/memory_subtract" />
+
+    <item
+        android:id="@+id/menu_copy"
+        android:title="@android:string/copy" />
+
+</menu>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 24ef68c..e4345cd 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -127,10 +127,21 @@
     <!-- Toggle button to show/hide inverse functions. [CHAR_LIMIT=4] -->
     <string name="inv" translatable="false">inv</string>
 
-    <!-- Memory store button. [CHAR_LIMIT=2] -->
-    <string name="memory_store">MS</string>
-    <!-- Memory recall button. [CHAR_LIMIT=2] -->
-    <string name="memory_recall">MR</string>
+    <!--
+      Item on Formula context menu used to paste from the Memory into the edit field. [CHAR_LIMIT=2]
+      -->
+    <string name="memory_recall" translatable="false">MR</string>
+    <!-- Item on Result context menu used to store the result in memory. [CHAR_LIMIT=2]
+      -->
+    <string name="memory_store" translatable="false">MS</string>
+    <!-- Item on Result context menu, which subtracts the current result from the number in memory.
+      [CHAR_LIMIT=2]
+      -->
+    <string name="memory_subtract" translatable="false">M-</string>
+    <!-- Item on Result context menu, which adds the current result to the number in memory.
+      [CHAR_LIMIT=2]
+      -->
+    <string name="memory_add" translatable="false">M+</string>
 
     <!-- Content description for 'e' button. [CHAR_LIMIT=NONE] -->
     <string name="desc_const_e">Euler\'s number</string>
@@ -218,11 +229,6 @@
     <!-- Content description for "inv" button to hide inverse functions. [CHAR_LIMIT=NONE] -->
     <string name="desc_inv_on">hide inverse functions</string>
 
-    <!-- Content description for memory store button. [CHAR_LIMIT=NONE] -->
-    <string name="desc_memory_store">memory store</string>
-    <!-- Content description for memory recall button. [CHAR_LIMIT=NONE] -->
-    <string name="desc_memory_recall">memory recall</string>
-
     <!-- Content description for formula field when it is empty. [CHAR_LIMIT=NONE] -->
     <string name="desc_formula">No formula</string>
     <!-- Content description for result field when it is empty. [CHAR_LIMIT=NONE] -->
diff --git a/src/com/android/calculator2/Calculator.java b/src/com/android/calculator2/Calculator.java
index e22e628..f13a9ed 100644
--- a/src/com/android/calculator2/Calculator.java
+++ b/src/com/android/calculator2/Calculator.java
@@ -80,8 +80,10 @@
 import java.io.ObjectOutput;
 import java.io.ObjectOutputStream;
 
+import static com.android.calculator2.CalculatorFormula.OnFormulaContextMenuClickListener;
+
 public class Calculator extends Activity
-        implements OnTextSizeChangeListener, OnLongClickListener, CalculatorFormula.OnPasteListener,
+        implements OnTextSizeChangeListener, OnLongClickListener,
         AlertDialogFragment.OnClickListener, Evaluator.EvaluationListener /* for main result */ {
 
     /**
@@ -158,6 +160,48 @@
         }
     };
 
+    public final OnDisplayMemoryOperationsListener mOnDisplayMemoryOperationsListener =
+            new OnDisplayMemoryOperationsListener() {
+        @Override
+        public boolean shouldDisplayMemory() {
+            return mEvaluator.getMemoryIndex() != 0;
+        }
+    };
+
+    public final OnFormulaContextMenuClickListener mOnFormulaContextMenuClickListener =
+            new OnFormulaContextMenuClickListener() {
+        @Override
+        public boolean onPaste(ClipData clip) {
+            final ClipData.Item item = clip.getItemCount() == 0 ? null : clip.getItemAt(0);
+            if (item == null) {
+                // nothing to paste, bail early...
+                return false;
+            }
+
+            // Check if the item is a previously copied result, otherwise paste as raw text.
+            final Uri uri = item.getUri();
+            if (uri != null && mEvaluator.isLastSaved(uri)) {
+                clearIfNotInputState();
+                mEvaluator.appendExpr(mEvaluator.getSavedIndex());
+                redisplayAfterFormulaChange();
+            } else {
+                addChars(item.coerceToText(Calculator.this).toString(), false);
+            }
+            return true;
+        }
+
+        @Override
+        public void onMemoryRecall() {
+            clearIfNotInputState();
+            long memoryIndex = mEvaluator.getMemoryIndex();
+            if (memoryIndex != 0) {
+                mEvaluator.appendExpr(mEvaluator.getMemoryIndex());
+                redisplayAfterFormulaChange();
+            }  // FIXME: Avoid the 0 case, e.g. by graying out button when memory is unavailable.
+        }
+    };
+
+
     private final TextWatcher mFormulaTextWatcher = new TextWatcher() {
         @Override
         public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) {
@@ -253,9 +297,6 @@
 
     private HistoryFragment mHistoryFragment = new HistoryFragment();
 
-    // The user requested that the result currently being evaluated should be stored to "memory".
-    private boolean mStoreToMemoryRequested = false;
-
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -343,8 +384,10 @@
             mEvaluator.clearMain();
         }
 
+        mFormulaText.setOnContextMenuClickListener(mOnFormulaContextMenuClickListener);
+        mFormulaText.setOnDisplayMemoryOperationsListener(mOnDisplayMemoryOperationsListener);
+
         mFormulaText.setOnTextSizeChangeListener(this);
-        mFormulaText.setOnPasteListener(this);
         mFormulaText.addTextChangedListener(mFormulaTextWatcher);
         mDeleteButton.setOnLongClickListener(this);
 
@@ -768,12 +811,6 @@
                     mEvaluator.evaluateAndNotify(mEvaluator.MAIN_INDEX, this, mResultText);
                 }
                 return;
-            case R.id.memory_store:
-                onMemoryStore();
-                return;
-            case R.id.memory_recall:
-                onMemoryRecall();
-                return;
             default:
                 cancelIfEvaluating(false);
                 if (haveUnprocessed()) {
@@ -819,10 +856,7 @@
         if (index != Evaluator.MAIN_INDEX) {
             throw new AssertionError("Unexpected evaluation result index\n");
         }
-        if (mStoreToMemoryRequested) {
-            mEvaluator.copyToMemory(Evaluator.MAIN_INDEX);
-            mStoreToMemoryRequested = false;
-        }
+
         // Invalidate any options that may depend on the current result.
         invalidateOptionsMenu();
 
@@ -926,27 +960,6 @@
         redisplayAfterFormulaChange();
     }
 
-    private void onMemoryStore() {
-        if (mCurrentState == CalculatorState.RESULT) {
-            mEvaluator.copyToMemory(Evaluator.MAIN_INDEX);
-        } else {
-            // Defer the store until we have the actual result.
-            mStoreToMemoryRequested = true;
-            if (mCurrentState == CalculatorState.INPUT) {
-                onEquals();
-            }
-        }
-    }
-
-    private void onMemoryRecall() {
-        clearIfNotInputState();
-        long memoryIndex = mEvaluator.getMemoryIndex();
-        if (memoryIndex != 0) {
-            mEvaluator.appendExpr(mEvaluator.getMemoryIndex());
-            redisplayAfterFormulaChange();
-        }  // FIXME: Avoid the 0 case, e.g. by graying out button when memory is unavailable.
-    }
-
     private void reveal(View sourceView, int colorRes, AnimatorListener listener) {
         final ViewGroupOverlay groupOverlay =
                 (ViewGroupOverlay) getWindow().getDecorView().getOverlay();
@@ -1202,11 +1215,12 @@
             return;
         }
         if (!mDragLayout.isOpen()) {
-            getFragmentManager().beginTransaction()
+            manager.beginTransaction()
                     .replace(R.id.history_frame, mHistoryFragment, HistoryFragment.TAG)
                     .setTransition(transit)
                     .addToBackStack(HistoryFragment.TAG)
                     .commit();
+            manager.executePendingTransactions();
         }
         // TODO: pass current scroll position of result
     }
@@ -1337,26 +1351,6 @@
         return mHitRect.contains((int) event.getX(), (int) event.getY());
     }
 
-    @Override
-    public boolean onPaste(ClipData clip) {
-        final ClipData.Item item = clip.getItemCount() == 0 ? null : clip.getItemAt(0);
-        if (item == null) {
-            // nothing to paste, bail early...
-            return false;
-        }
-
-        // Check if the item is a previously copied result, otherwise paste as raw text.
-        final Uri uri = item.getUri();
-        if (uri != null && mEvaluator.isLastSaved(uri)) {
-            clearIfNotInputState();
-            mEvaluator.appendExpr(mEvaluator.getSavedIndex());
-            redisplayAfterFormulaChange();
-        } else {
-            addChars(item.coerceToText(this).toString(), false);
-        }
-        return true;
-    }
-
     /**
      * Clean up animation for context menu.
      */
@@ -1364,4 +1358,8 @@
     public void onContextMenuClosed(Menu menu) {
         stopActionModeOrContextMenu();
     }
+
+    public interface OnDisplayMemoryOperationsListener {
+        boolean shouldDisplayMemory();
+    }
 }
diff --git a/src/com/android/calculator2/CalculatorFormula.java b/src/com/android/calculator2/CalculatorFormula.java
index 8a7e4c5..210372c 100644
--- a/src/com/android/calculator2/CalculatorFormula.java
+++ b/src/com/android/calculator2/CalculatorFormula.java
@@ -58,8 +58,9 @@
     private ActionMode mActionMode;
     private ActionMode.Callback mPasteActionModeCallback;
     private ContextMenu mContextMenu;
-    private OnPasteListener mOnPasteListener;
     private OnTextSizeChangeListener mOnTextSizeChangeListener;
+    private OnFormulaContextMenuClickListener mOnContextMenuClickListener;
+    private Calculator.OnDisplayMemoryOperationsListener mOnDisplayMemoryOperationsListener;
 
     public CalculatorFormula(Context context) {
         this(context, null /* attrs */);
@@ -243,8 +244,13 @@
         mOnTextSizeChangeListener = listener;
     }
 
-    public void setOnPasteListener(OnPasteListener listener) {
-        mOnPasteListener = listener;
+    public void setOnContextMenuClickListener(OnFormulaContextMenuClickListener listener) {
+        mOnContextMenuClickListener = listener;
+    }
+
+    public void setOnDisplayMemoryOperationsListener(
+            Calculator.OnDisplayMemoryOperationsListener listener) {
+        mOnDisplayMemoryOperationsListener = listener;
     }
 
     /**
@@ -268,7 +274,7 @@
             public boolean onCreateActionMode(ActionMode mode, Menu menu) {
                 mode.setTag(TAG_ACTION_MODE);
                 final MenuInflater inflater = mode.getMenuInflater();
-                return createPasteMenu(inflater, menu);
+                return createContextMenu(inflater, menu);
             }
 
             @Override
@@ -309,7 +315,7 @@
             public void onCreateContextMenu(ContextMenu contextMenu, View view,
                     ContextMenu.ContextMenuInfo contextMenuInfo) {
                 final MenuInflater inflater = new MenuInflater(getContext());
-                createPasteMenu(inflater, contextMenu);
+                createContextMenu(inflater, contextMenu);
                 mContextMenu = contextMenu;
                 for(int i = 0; i < contextMenu.size(); i++) {
                     contextMenu.getItem(i).setOnMenuItemClickListener(CalculatorFormula.this);
@@ -324,41 +330,52 @@
         });
     }
 
-    private boolean createPasteMenu(MenuInflater inflater, Menu menu) {
+    private boolean createContextMenu(MenuInflater inflater, Menu menu) {
         final ClipboardManager clipboard = (ClipboardManager) getContext()
                 .getSystemService(Context.CLIPBOARD_SERVICE);
-        if (clipboard.hasPrimaryClip()) {
-            bringPointIntoView(length());
-            inflater.inflate(R.menu.paste, menu);
-            return true;
+        final boolean isPasteEnabled = clipboard.hasPrimaryClip();
+        final boolean isMemoryEnabled = isMemoryEnabled();
+        if (!isPasteEnabled && !isMemoryEnabled) {
+            return false;
         }
-        // Prevents the selection action mode on double tap.
-        return false;
+
+        bringPointIntoView(length());
+        inflater.inflate(R.menu.menu_formula, menu);
+        final MenuItem pasteItem = menu.findItem(R.id.menu_paste);
+        final MenuItem memoryRecallItem = menu.findItem(R.id.memory_recall);
+        pasteItem.setEnabled(isPasteEnabled);
+        memoryRecallItem.setEnabled(isMemoryEnabled);
+        return true;
     }
 
     private void paste() {
         final ClipboardManager clipboard = (ClipboardManager) getContext()
                 .getSystemService(Context.CLIPBOARD_SERVICE);
         final ClipData primaryClip = clipboard.getPrimaryClip();
-        if (primaryClip != null && mOnPasteListener != null) {
-            mOnPasteListener.onPaste(primaryClip);
+        if (primaryClip != null && mOnContextMenuClickListener != null) {
+            mOnContextMenuClickListener.onPaste(primaryClip);
         }
     }
 
     @Override
     public boolean onMenuItemClick(MenuItem item) {
-        if (item.getItemId() == R.id.menu_paste) {
-            paste();
-            return true;
+        switch (item.getItemId()) {
+            case R.id.memory_recall:
+                mOnContextMenuClickListener.onMemoryRecall();
+                return true;
+            case R.id.menu_paste:
+                paste();
+                return true;
+            default:
+                return false;
         }
-        return false;
     }
 
     @Override
     public void onPrimaryClipChanged() {
         final ClipData clip = mClipboardManager.getPrimaryClip();
         if (clip == null || clip.getItemCount() == 0) {
-            setLongClickable(false);
+            setLongClickable(isMemoryEnabled());
             return;
         }
         CharSequence clipText = null;
@@ -367,14 +384,20 @@
         } catch (Exception e) {
             Log.i("Calculator", "Error reading clipboard:", e);
         }
-        setLongClickable(!TextUtils.isEmpty(clipText));
+        setLongClickable(!TextUtils.isEmpty(clipText) || isMemoryEnabled());
+    }
+
+    private boolean isMemoryEnabled() {
+        return !(mOnDisplayMemoryOperationsListener == null || mOnContextMenuClickListener == null)
+                && mOnDisplayMemoryOperationsListener.shouldDisplayMemory();
     }
 
     public interface OnTextSizeChangeListener {
         void onTextSizeChanged(TextView textView, float oldSize);
     }
 
-    public interface OnPasteListener {
+    public interface OnFormulaContextMenuClickListener {
         boolean onPaste(ClipData clip);
+        void onMemoryRecall();
     }
 }
diff --git a/src/com/android/calculator2/CalculatorResult.java b/src/com/android/calculator2/CalculatorResult.java
index c604a6f..0b399c4 100644
--- a/src/com/android/calculator2/CalculatorResult.java
+++ b/src/com/android/calculator2/CalculatorResult.java
@@ -142,6 +142,9 @@
     private ActionMode.Callback mCopyActionModeCallback;
     private ContextMenu mContextMenu;
 
+    // The user requested that the result currently being evaluated should be stored to "memory".
+    private boolean mStoreToMemoryRequested = false;
+
     public CalculatorResult(Context context, AttributeSet attrs) {
         super(context, attrs);
         mScroller = new OverScroller(context);
@@ -384,10 +387,43 @@
     public void onEvaluate(long index, int initPrec, int msd, int leastDigPos,
             String truncatedWholePart) {
         initPositions(initPrec, msd, leastDigPos, truncatedWholePart);
+
+        if (mStoreToMemoryRequested) {
+            mEvaluator.copyToMemory(index);
+            mStoreToMemoryRequested = false;
+        }
         redisplay();
     }
 
     /**
+     * Store the result for this index if it is available.
+     * If it is unavailable, set mStoreToMemoryRequested to indicate that we should store
+     * when evaluation is complete.
+     */
+    public void onMemoryStore() {
+        if (mEvaluator.hasResult(mIndex)) {
+            mEvaluator.copyToMemory(mIndex);
+        } else {
+            mStoreToMemoryRequested = true;
+            mEvaluator.requireResult(mIndex, this /* listener */, this /* CharMetricsInfo */);
+        }
+    }
+
+    /**
+     * Add the result to the value currently in memory.
+     */
+    public void onMemoryAdd() {
+        mEvaluator.addToMemory(Evaluator.MAIN_INDEX);
+    }
+
+    /**
+     * Subtract the result from the value currently in memory.
+     */
+    public void onMemorySubtract() {
+        mEvaluator.subtractFromMemory(Evaluator.MAIN_INDEX);
+    }
+
+    /**
      * Set up scroll bounds (mMinPos, mMaxPos, etc.) and determine whether the result is
      * scrollable, based on the supplied information about the result.
      * This is unfortunately complicated because we need to predict whether trailing digits
@@ -513,6 +549,7 @@
      */
     @Override
     public void onError(long index, int resourceId) {
+        mStoreToMemoryRequested = false;
         mValid = true;
         setLongClickable(false);
         mScrollable = false;
@@ -818,6 +855,7 @@
     @Override
     public void onCancelled(long index) {
         clear();
+        mStoreToMemoryRequested = false;
     }
 
     /**
@@ -896,7 +934,7 @@
     }
 
     /**
-     * Use ActionMode for copy support on M and higher.
+     * Use ActionMode for copy/memory support on M and higher.
      */
     @TargetApi(Build.VERSION_CODES.M)
     private void setupActionMode() {
@@ -971,7 +1009,7 @@
     }
 
     /**
-     * Use ContextMenu for copy support on L and lower.
+     * Use ContextMenu for copy/memory support on L and lower.
      */
     private void setupContextMenu() {
         setOnCreateContextMenuListener(new OnCreateContextMenuListener() {
@@ -998,7 +1036,12 @@
     }
 
     private boolean createCopyMenu(MenuInflater inflater, Menu menu) {
-        inflater.inflate(R.menu.copy, menu);
+        inflater.inflate(R.menu.menu_result, menu);
+        final boolean displayMemory = mEvaluator.getMemoryIndex() != 0;
+        final MenuItem memoryAddItem = menu.findItem(R.id.memory_add);
+        final MenuItem memorySubtractItem = menu.findItem(R.id.memory_subtract);
+        memoryAddItem.setEnabled(displayMemory);
+        memorySubtractItem.setEnabled(displayMemory);
         highlightResult();
         return true;
     }
@@ -1048,6 +1091,15 @@
     @Override
     public boolean onMenuItemClick(MenuItem item) {
         switch (item.getItemId()) {
+            case R.id.memory_add:
+                onMemoryAdd();
+                return true;
+            case R.id.memory_subtract:
+                onMemorySubtract();
+                return true;
+            case R.id.memory_store:
+                onMemoryStore();
+                return true;
             case R.id.menu_copy:
                 if (mEvaluator.evaluationInProgress(mIndex)) {
                     // Refuse to copy placeholder characters.
@@ -1061,4 +1113,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 82a4d86..77c762d 100644
--- a/src/com/android/calculator2/Evaluator.java
+++ b/src/com/android/calculator2/Evaluator.java
@@ -1362,6 +1362,22 @@
     }
 
     /**
+     * Return an ExprInfo corresponding to the subtraction of the value at the subtrahend index
+     * from value at the minuend index (minuend - subtrahend = result). Both are presumed to have
+     * been previously evaluated. The result is unevaluated.
+     */
+    private ExprInfo difference(long minuendIndex, long subtrahendIndex) {
+        final CalculatorExpr resultExpr = new CalculatorExpr();
+        resultExpr.append(getCollapsedExpr(minuendIndex));
+        resultExpr.add(R.id.op_sub);
+        resultExpr.append(getCollapsedExpr(subtrahendIndex));
+        final ExprInfo result = new ExprInfo(resultExpr, false /* angular measure irrelevant */);
+        result.mLongTimeout = mExprs.get(minuendIndex).mLongTimeout
+                || mExprs.get(subtrahendIndex).mLongTimeout;
+        return result;
+    }
+
+    /**
      * Add the expression described by the argument to the database.
      * Returns the new row id in the database.
      * If in_history is true, add it with a positive index, so it will appear in the history.
@@ -1576,6 +1592,17 @@
     }
 
     /**
+     * Save an an expression representing the subtraction of the expression with the given index
+     * from "memory." Make mMemoryIndex point to it when we complete evaluating.
+     */
+    public void subtractFromMemory(long index) {
+        ExprInfo newEi = difference(mMemoryIndex, index);
+        long newIndex = addToDB(false, newEi);
+        mMemoryIndex = 0;  // Invalidate while we're evaluating.
+        setMemoryIndexWhenEvaluated(newIndex, true /* persist */);
+    }
+
+    /**
      * Return index of "saved" expression, or 0.
      */
     public long getSavedIndex() {