Fix UI holes and bugs. Fix eval bugs.

Change layout to make the result display use a fixed font
size and limit the number of characters when it appears below the
formula.  This allows us to always get the proper expansion effect
and prevents scrolling from affecting the font size.

Add copy support for result display.

Add paste support for the formula.

Add keyboard input support.

Copy/paste can be used to remember old results in the calculator.
We save an identifying tag URI in the clip, in addition to text,
allowing us to paste old calculator results without precision
loss.

Copy/paste currently does not rely on selection at all.
I had trouble making it work that way in the formula.  It's
unclear that would be better, since we only allow copy of the
entire text and paste at the end.

Add a couple of alternate result display options to the
overflow menu.  (These appear quite useful, were trivial to
implement, and give us a better excuse for the overflow menu.)

Changed the behavior of the delete key in error state.
Changing it to CLEAR seemed unfriendly, since it prevents
corrections.  This is a change from L.

Made it clear that the CalculatorHitSomeButtons test is
currently 95% worthless.  It was apparentlly failing (due to test
infrastructure issues) but throwing an exception in a thread from
which it was not getting reported.  Decided to keep it, since I
would like a place to continue collecting regression tests, even
if we can't actually run them yet.

Includes some easy drive-by fixes for expression evaluation:

a) 2 / 2 * 3 was mis-parsed as 2 / (2 * 3).

b) Cosine evaluation had the sense of the test for a rational result reversed.

c) Constants without leading digits, like .1, are now handled correctly,
and decimal points in the formula are now internationalized.
(That's not yet true for the result.)

Change-Id: Ic24466b444b4a4633cfb036c67622c7f4fd644ec
diff --git a/src/com/android/calculator2/CalculatorEditText.java b/src/com/android/calculator2/CalculatorEditText.java
index 380aa81..4ff9678 100644
--- a/src/com/android/calculator2/CalculatorEditText.java
+++ b/src/com/android/calculator2/CalculatorEditText.java
@@ -16,18 +16,24 @@
 
 package com.android.calculator2;
 
+import android.content.ClipboardManager;
+import android.content.ClipData;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Paint;
 import android.graphics.Paint.FontMetricsInt;
 import android.graphics.Rect;
+import android.net.Uri;
 import android.os.Parcelable;
 import android.text.method.ScrollingMovementMethod;
 import android.text.TextPaint;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.util.TypedValue;
 import android.view.ActionMode;
+import android.view.GestureDetector;
 import android.view.Menu;
+import android.view.MenuInflater;
 import android.view.MenuItem;
 import android.view.MotionEvent;
 import android.widget.EditText;
@@ -39,15 +45,31 @@
 
 public class CalculatorEditText extends EditText {
 
-    private final static ActionMode.Callback NO_SELECTION_ACTION_MODE_CALLBACK =
+
+    private final ActionMode.Callback mPasteActionModeCallback =
             new ActionMode.Callback() {
         @Override
         public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
-            return false;
+            switch (item.getItemId()) {
+            case R.id.menu_paste:
+                pasteContent();
+                mode.finish();
+                return true;
+            default:
+                return false;
+            }
         }
 
         @Override
         public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+            ClipboardManager clipboard =
+                (ClipboardManager) getContext().getSystemService(
+                        Context.CLIPBOARD_SERVICE);
+            if (clipboard.hasPrimaryClip()) {
+                MenuInflater inflater = mode.getMenuInflater();
+                inflater.inflate(R.menu.paste, menu);
+                return true;
+            }
             // Prevents the selection action mode on double tap.
             return false;
         }
@@ -62,6 +84,25 @@
         }
     };
 
+    private PasteListener mPasteListener;
+
+    public void setPasteListener(PasteListener pasteListener) {
+        mPasteListener = pasteListener;
+    }
+
+    private void pasteContent() {
+        ClipboardManager clipboard =
+                (ClipboardManager) getContext().getSystemService(
+                        Context.CLIPBOARD_SERVICE);
+        ClipData cd = clipboard.getPrimaryClip();
+        ClipData.Item item = cd.getItemAt(0);
+        // TODO: Should we handle multiple selections?
+        Uri uri = item.getUri();
+        if (uri == null || !mPasteListener.paste(uri)) {
+            mPasteListener.paste(item.coerceToText(getContext()).toString());
+        }
+    }
+
     private final float mMaximumTextSize;
     private final float mMinimumTextSize;
     private final float mStepTextSize;
@@ -73,6 +114,14 @@
     private int mWidthConstraint = -1;
     private OnTextSizeChangeListener mOnTextSizeChangeListener;
 
+    final GestureDetector mLongTouchDetector =
+        new GestureDetector(new GestureDetector.SimpleOnGestureListener() {
+            @Override
+            public void onLongPress(MotionEvent e) {
+                startActionMode(mPasteActionModeCallback);
+            }
+        });
+
     public CalculatorEditText(Context context) {
         this(context, null);
     }
@@ -95,7 +144,9 @@
 
         a.recycle();
 
-        setCustomSelectionActionModeCallback(NO_SELECTION_ACTION_MODE_CALLBACK);
+        // Paste ActionMode is triggered explicitly, not through
+        // setCustomSelectionActionModeCallback.
+
         if (isFocusable()) {
             setMovementMethod(ScrollingMovementMethod.getInstance());
         }
@@ -104,13 +155,9 @@
     }
 
     @Override
-    public boolean onTouchEvent(MotionEvent event) {
-        if (event.getActionMasked() == MotionEvent.ACTION_UP) {
-            // Hack to prevent keyboard and insertion handle from showing.
-            cancelLongPress();
-        }
-        return super.onTouchEvent(event);
-    }
+    public boolean onTouchEvent(MotionEvent e) {
+        return mLongTouchDetector.onTouchEvent(e);
+    };
 
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
@@ -204,4 +251,9 @@
     public interface OnTextSizeChangeListener {
         void onTextSizeChanged(TextView textView, float oldSize);
     }
+
+    public interface PasteListener {
+        void paste(String s);
+        boolean paste(Uri u);
+    }
 }