More correctly pronounce advanced operators in Talkback

Bug: 19190211
Bug: 19202945
Bug: 21052751
Bug: 19165054
Bug: 22594908

Add TtsSpans for operators that are otherwise misread by TalkBack.
Force correct reading for some individual characters.

This greatly improves Talkback for advanced operators in Calculator.

This is imperfect. There is no guarantee that the strings I'm
using will work in all languages.  But they're almost certainly better than
what we have now.  And it makes parentheses and factorial usable,
though perhaps a bit verbose.

We also no longer pronounce "sine" as "sin".

Removed some now obsolete TODO comments.

Change-Id: I5236f682be828699e08dca04ee6fa073269964f6
diff --git a/src/com/android/calculator2/CalculatorExpr.java b/src/com/android/calculator2/CalculatorExpr.java
index 3023b5c..8a008b8 100644
--- a/src/com/android/calculator2/CalculatorExpr.java
+++ b/src/com/android/calculator2/CalculatorExpr.java
@@ -21,6 +21,11 @@
 import com.hp.creals.UnaryCRFunction;
 
 import android.content.Context;
+import android.text.SpannableString;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.style.TtsSpan;
+import android.text.style.TtsSpan.TextBuilder;
 import android.util.Log;
 
 import java.math.BigInteger;
@@ -46,11 +51,19 @@
 
     private static abstract class Token {
         abstract TokenKind kind();
+
+        /**
+         * Write kind as Byte followed by data needed by subclass constructor.
+         */
         abstract void write(DataOutput out) throws IOException;
-                // Implementation writes kind as Byte followed by
-                // data read by constructor.
-        abstract String toString(Context context);
-                // We need the context to convert button ids to strings.
+
+        /**
+         * Return a textual representation of the token.
+         * The result is suitable for either display as part od the formula or TalkBack use.
+         * It may be a SpannableString that includes added TalkBack information.
+         * @param context context used for converting button ids to strings
+         */
+        abstract CharSequence toCharSequence(Context context);
     }
 
     // An operator token
@@ -68,8 +81,16 @@
             out.writeInt(mId);
         }
         @Override
-        public String toString(Context context) {
-            return KeyMaps.toString(context, mId);
+        public CharSequence toCharSequence(Context context) {
+            String desc = KeyMaps.toDescriptiveString(context, mId);
+            if (desc != null) {
+                SpannableString result = new SpannableString(KeyMaps.toString(context, mId));
+                Object descSpan = new TtsSpan.TextBuilder(desc).build();
+                result.setSpan(descSpan, 0, result.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+                return result;
+            } else {
+                return KeyMaps.toString(context, mId);
+            }
         }
         @Override
         TokenKind kind() { return TokenKind.OPERATOR; }
@@ -193,7 +214,7 @@
         }
 
         @Override
-        String toString(Context context) {
+        CharSequence toCharSequence(Context context) {
             return toString();
         }
 
@@ -323,7 +344,7 @@
             }
         }
         @Override
-        String toString(Context context) {
+        CharSequence toCharSequence(Context context) {
             return KeyMaps.translateResult(mShortRep);
         }
         @Override
@@ -1019,11 +1040,11 @@
     }
 
     // Produce a string representation of the expression itself
-    String toString(Context context) {
-        StringBuilder sb = new StringBuilder();
+    SpannableStringBuilder toSpannableStringBuilder(Context context) {
+        SpannableStringBuilder ssb = new SpannableStringBuilder();
         for (Token t: mExpr) {
-            sb.append(t.toString(context));
+            ssb.append(t.toCharSequence(context));
         }
-        return sb.toString();
+        return ssb;
     }
 }