summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorHans Boehm <hboehm@google.com>2015-07-09 10:41:25 -0700
committerHans Boehm <hboehm@google.com>2015-07-20 16:30:43 -0700
commit8a4f81c5b30edd4e62d222a17f4e0e2140bfd99d (patch)
treeef2d97689c3e94231222a3ceb88df04c206b2477 /src
parent22c0a189febb3ee2d0149f6babc3a84e25e3594d (diff)
downloadandroid_packages_apps_ExactCalculator-8a4f81c5b30edd4e62d222a17f4e0e2140bfd99d.tar.gz
android_packages_apps_ExactCalculator-8a4f81c5b30edd4e62d222a17f4e0e2140bfd99d.tar.bz2
android_packages_apps_ExactCalculator-8a4f81c5b30edd4e62d222a17f4e0e2140bfd99d.zip
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
Diffstat (limited to 'src')
-rw-r--r--src/com/android/calculator2/Calculator.java27
-rw-r--r--src/com/android/calculator2/CalculatorExpr.java45
-rw-r--r--src/com/android/calculator2/CalculatorText.java19
-rw-r--r--src/com/android/calculator2/KeyMaps.java51
4 files changed, 112 insertions, 30 deletions
diff --git a/src/com/android/calculator2/Calculator.java b/src/com/android/calculator2/Calculator.java
index f1c9835..4eecb50 100644
--- a/src/com/android/calculator2/Calculator.java
+++ b/src/com/android/calculator2/Calculator.java
@@ -14,8 +14,6 @@
* limitations under the License.
*/
-// TODO: Better indication of when the result is known to be exact.
-// TODO: Check and possibly fix accessability issues.
// TODO: Copy & more general paste in formula? Note that this requires
// great care: Currently the text version of a displayed formula
// is not directly useful for re-evaluating the formula later, since
@@ -44,8 +42,10 @@ import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.view.ViewPager;
import android.text.SpannableString;
+import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.style.ForegroundColorSpan;
+import android.text.TextUtils;
import android.util.Property;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
@@ -210,9 +210,13 @@ public class Calculator extends Activity
private View mCurrentButton;
private Animator mCurrentAnimator;
- private String mUnprocessedChars = null; // Characters that were recently entered
- // at the end of the display that have not yet
- // been added to the underlying expression.
+ // Characters that were recently entered at the end of the display that have not yet
+ // been added to the underlying expression.
+ private String mUnprocessedChars = null;
+
+ // Color to highlight unprocessed characters from physical keyboard.
+ // TODO: should probably match this to the error color?
+ private ForegroundColorSpan mUnprocessedColorSpan = new ForegroundColorSpan(Color.RED);
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -536,18 +540,13 @@ public class Calculator extends Activity
}
void redisplayFormula() {
- String formula = mEvaluator.getExpr().toString(this);
+ SpannableStringBuilder formula = mEvaluator.getExpr().toSpannableStringBuilder(this);
if (mUnprocessedChars != null) {
// Add and highlight characters we couldn't process.
- SpannableString formatted = new SpannableString(formula + mUnprocessedChars);
- // TODO: should probably match this to the error color.
- formatted.setSpan(new ForegroundColorSpan(Color.RED),
- formula.length(), formatted.length(),
- Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- mFormulaText.changeTextTo(formatted);
- } else {
- mFormulaText.changeTextTo(formula);
+ formula.append(mUnprocessedChars, mUnprocessedColorSpan,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
+ mFormulaText.changeTextTo(formula);
}
@Override
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.CR;
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 @@ class CalculatorExpr {
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 @@ class CalculatorExpr {
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 @@ class CalculatorExpr {
}
@Override
- String toString(Context context) {
+ CharSequence toCharSequence(Context context) {
return toString();
}
@@ -323,7 +344,7 @@ class CalculatorExpr {
}
}
@Override
- String toString(Context context) {
+ CharSequence toCharSequence(Context context) {
return KeyMaps.translateResult(mShortRep);
}
@Override
@@ -1019,11 +1040,11 @@ class CalculatorExpr {
}
// 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;
}
}
diff --git a/src/com/android/calculator2/CalculatorText.java b/src/com/android/calculator2/CalculatorText.java
index 4b0b0c9..109c2af 100644
--- a/src/com/android/calculator2/CalculatorText.java
+++ b/src/com/android/calculator2/CalculatorText.java
@@ -224,11 +224,22 @@ public class CalculatorText extends AlignedTextView implements View.OnLongClickL
* Otherwise, e.g. after deletion, announce the entire new text.
*/
public void changeTextTo(CharSequence newText) {
- CharSequence oldText = getText();
+ final CharSequence oldText = getText();
if (startsWith(newText, oldText)) {
- int newLen = newText.length();
- int oldLen = oldText.length();
- if (oldLen != newLen) {
+ final int newLen = newText.length();
+ final int oldLen = oldText.length();
+ if (newLen == oldLen + 1) {
+ // The algorithm for pronouncing a single character doesn't seem
+ // to respect our hints. Don't give it the choice.
+ final char c = newText.charAt(oldLen);
+ final int id = KeyMaps.keyForChar(c);
+ final String descr = KeyMaps.toDescriptiveString(getContext(), id);
+ if (descr != null) {
+ announceForAccessibility(descr);
+ } else {
+ announceForAccessibility(String.valueOf(c));
+ }
+ } else if (newLen > oldLen) {
announceForAccessibility(newText.subSequence(oldLen, newLen));
}
} else {
diff --git a/src/com/android/calculator2/KeyMaps.java b/src/com/android/calculator2/KeyMaps.java
index b4bfdf9..f99850b 100644
--- a/src/com/android/calculator2/KeyMaps.java
+++ b/src/com/android/calculator2/KeyMaps.java
@@ -115,6 +115,57 @@ public class KeyMaps {
}
/**
+ * Map key id to corresponding (internationalized) descriptive string that can be used
+ * to correctly read back a formula.
+ * Only used for operators and individual characters; not used inside constants.
+ * Returns null when we don't need a descriptive string.
+ * Pure function.
+ */
+ public static String toDescriptiveString(Context context, int id) {
+ switch(id) {
+ case R.id.op_fact:
+ return context.getString(R.string.desc_op_fact);
+ case R.id.fun_sin:
+ return context.getString(R.string.desc_fun_sin)
+ + " " + context.getString(R.string.desc_lparen);
+ case R.id.fun_cos:
+ return context.getString(R.string.desc_fun_cos)
+ + " " + context.getString(R.string.desc_lparen);
+ case R.id.fun_tan:
+ return context.getString(R.string.desc_fun_tan)
+ + " " + context.getString(R.string.desc_lparen);
+ case R.id.fun_arcsin:
+ return context.getString(R.string.desc_fun_arcsin)
+ + " " + context.getString(R.string.desc_lparen);
+ case R.id.fun_arccos:
+ return context.getString(R.string.desc_fun_arccos)
+ + " " + context.getString(R.string.desc_lparen);
+ case R.id.fun_arctan:
+ return context.getString(R.string.desc_fun_arctan)
+ + " " + context.getString(R.string.desc_lparen);
+ case R.id.fun_ln:
+ return context.getString(R.string.desc_fun_ln)
+ + " " + context.getString(R.string.desc_lparen);
+ case R.id.fun_log:
+ return context.getString(R.string.desc_fun_log)
+ + " " + context.getString(R.string.desc_lparen);
+ case R.id.fun_exp:
+ return context.getString(R.string.desc_fun_exp)
+ + " " + context.getString(R.string.desc_lparen);
+ case R.id.lparen:
+ return context.getString(R.string.desc_lparen);
+ case R.id.rparen:
+ return context.getString(R.string.desc_rparen);
+ case R.id.op_pow:
+ return context.getString(R.string.desc_op_pow);
+ case R.id.dec_point:
+ return context.getString(R.string.desc_dec_point);
+ default:
+ return null;
+ }
+ }
+
+ /**
* Does a button id correspond to a binary operator?
* Pure function.
*/