Import translations. DO NOT MERGE
am: a7c62ac9fd  -s ours

Change-Id: Ib441d6f424da131e8b977d01241069bf7c4b95ea
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index b01e2c8..a7675b1 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -28,9 +28,14 @@
         <activity
             android:name=".Calculator"
             android:label="@string/app_name"
+            android:launchMode="singleTask"
             android:theme="@style/Theme.Calculator">
+            <layout
+                android:minHeight="220dp"
+                android:minWidth="230dp" />
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
+
                 <category android:name="android.intent.category.LAUNCHER" />
                 <category android:name="android.intent.category.APP_CALCULATOR" />
             </intent-filter>
@@ -38,9 +43,9 @@
 
         <activity
             android:name=".Licenses"
+            android:launchMode="singleTop"
             android:parentActivityName=".Calculator"
             android:theme="@style/Theme.Licenses" />
 
     </application>
-
 </manifest>
diff --git a/res/layout/activity_calculator_land.xml b/res/layout/activity_calculator_land.xml
index 1fe12db..5dd2c20 100644
--- a/res/layout/activity_calculator_land.xml
+++ b/res/layout/activity_calculator_land.xml
@@ -32,7 +32,7 @@
         android:layout_weight="1">
 
         <include layout="@layout/pad_numeric" />
-        <include layout="@layout/pad_operator_two_col" />
+        <include layout="@layout/pad_operator" />
         <include layout="@layout/pad_advanced" />
 
     </LinearLayout>
diff --git a/res/layout/activity_calculator_port.xml b/res/layout/activity_calculator_port.xml
index 0cb5dc7..4cafa94 100644
--- a/res/layout/activity_calculator_port.xml
+++ b/res/layout/activity_calculator_port.xml
@@ -21,10 +21,7 @@
     android:layout_height="match_parent"
     android:orientation="vertical">
 
-    <include
-        layout="@layout/display"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content" />
+    <include layout="@layout/display"/>
 
     <com.android.calculator2.CalculatorPadViewPager
         android:id="@+id/pad_pager"
@@ -38,7 +35,7 @@
             android:layout_height="match_parent">
 
             <include layout="@layout/pad_numeric" />
-            <include layout="@layout/pad_operator_one_col" />
+            <include layout="@layout/pad_operator" />
 
         </LinearLayout>
 
diff --git a/res/layout/activity_calculator_tablet_port.xml b/res/layout/activity_calculator_tablet_port.xml
index b56450e..9eab3af 100644
--- a/res/layout/activity_calculator_tablet_port.xml
+++ b/res/layout/activity_calculator_tablet_port.xml
@@ -21,17 +21,17 @@
     android:layout_height="match_parent"
     android:orientation="vertical">
 
-    <include
-        layout="@layout/display"
+    <include layout="@layout/display" />
+
+    <include layout="@layout/pad_advanced" />
+
+    <LinearLayout
         android:layout_width="match_parent"
-        android:layout_height="wrap_content" />
+        android:layout_height="0dip"
+        android:layout_weight="500">
 
-    <include layout="@layout/pad_advanced_tablet_port" />
-
-    <LinearLayout style="@style/PadLinearLayoutStyle">
-
-        <include layout="@layout/pad_numeric" />
-        <include layout="@layout/pad_operator_two_col" />
+        <include layout="@layout/pad_numeric"/>
+        <include layout="@layout/pad_operator" />
 
     </LinearLayout>
 
diff --git a/res/layout/display.xml b/res/layout/display.xml
deleted file mode 100644
index 638f0d2..0000000
--- a/res/layout/display.xml
+++ /dev/null
@@ -1,64 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  Copyright (C) 2014 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.
-  -->
-
-<RelativeLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/display"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:background="@color/display_background_color"
-    android:elevation="4dip">
-
-    <Toolbar
-        android:id="@+id/toolbar"
-        android:layout_width="match_parent"
-        android:layout_height="?android:attr/actionBarSize">
-
-       <TextView
-            android:id="@+id/mode"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:fontFamily="sans-serif-medium"
-            android:textAllCaps="true"
-            android:textSize="16sp" />
-
-    </Toolbar>
-
-    <com.android.calculator2.CalculatorText
-        android:id="@+id/formula"
-        style="@style/DisplayTextStyle.Formula"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_below="@id/toolbar"
-        android:ellipsize="none"
-        android:longClickable="true"
-        android:singleLine="true"
-        android:scrollHorizontally="true"
-        android:textColor="@color/display_formula_text_color"
-        android:textIsSelectable="false" />
-
-    <com.android.calculator2.CalculatorResult
-        android:id="@+id/result"
-        style="@style/DisplayTextStyle.Result"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_below="@id/formula"
-        android:bufferType="spannable"
-        android:singleLine="true"
-        android:textColor="@color/display_result_text_color" />
-
-</RelativeLayout>
diff --git a/res/layout/display_one_line.xml b/res/layout/display_one_line.xml
new file mode 100644
index 0000000..c016b15
--- /dev/null
+++ b/res/layout/display_one_line.xml
@@ -0,0 +1,70 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+
+<com.android.calculator2.CalculatorDisplay
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/display"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="@color/display_background_color"
+    android:clipChildren="false"
+    android:elevation="4dip"
+    android:orientation="vertical">
+
+    <include layout="@layout/toolbar" />
+
+    <FrameLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <com.android.calculator2.CalculatorScrollView
+            android:id="@+id/formula_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="bottom"
+            android:overScrollMode="never"
+            android:scrollbars="none">
+
+            <com.android.calculator2.CalculatorText
+                android:id="@+id/formula"
+                style="@style/DisplayTextStyle.Formula"
+                android:layout_width="wrap_content"
+                android:layout_height="match_parent"
+                android:layout_gravity="bottom|end"
+                android:ellipsize="none"
+                android:gravity="bottom|end"
+                android:longClickable="true"
+                android:singleLine="true"
+                android:textColor="@color/display_formula_text_color"
+                android:textIsSelectable="false" />
+
+        </com.android.calculator2.CalculatorScrollView>
+
+        <com.android.calculator2.CalculatorResult
+            android:id="@+id/result"
+            style="@style/DisplayTextStyle.Result"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="bottom"
+            android:bufferType="spannable"
+            android:singleLine="true"
+            android:textColor="@color/display_result_text_color"
+            android:visibility="invisible" />
+
+    </FrameLayout>
+
+</com.android.calculator2.CalculatorDisplay>
diff --git a/res/layout/display_two_line.xml b/res/layout/display_two_line.xml
new file mode 100644
index 0000000..3735a85
--- /dev/null
+++ b/res/layout/display_two_line.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2014 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.
+  -->
+
+<com.android.calculator2.CalculatorDisplay
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/display"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:background="@color/display_background_color"
+    android:elevation="4dip"
+    android:orientation="vertical">
+
+    <include layout="@layout/toolbar" />
+
+    <com.android.calculator2.CalculatorScrollView
+        android:id="@+id/formula_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:overScrollMode="never"
+        android:scrollbars="none">
+
+       <com.android.calculator2.CalculatorText
+            android:id="@+id/formula"
+            style="@style/DisplayTextStyle.Formula"
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:layout_gravity="bottom|end"
+            android:ellipsize="none"
+            android:longClickable="true"
+            android:singleLine="true"
+            android:textColor="@color/display_formula_text_color"
+            android:textIsSelectable="false" />
+
+    </com.android.calculator2.CalculatorScrollView>
+
+    <com.android.calculator2.CalculatorResult
+        android:id="@+id/result"
+        style="@style/DisplayTextStyle.Result"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:bufferType="spannable"
+        android:singleLine="true"
+        android:textColor="@color/display_result_text_color" />
+
+</com.android.calculator2.CalculatorDisplay>
diff --git a/res/layout/pad_advanced.xml b/res/layout/pad_advanced_3x5.xml
similarity index 100%
rename from res/layout/pad_advanced.xml
rename to res/layout/pad_advanced_3x5.xml
diff --git a/res/layout/pad_advanced_4x4.xml b/res/layout/pad_advanced_4x4.xml
new file mode 100644
index 0000000..ddbee47
--- /dev/null
+++ b/res/layout/pad_advanced_4x4.xml
@@ -0,0 +1,207 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+
+<GridLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/pad_advanced"
+    style="@style/PadLayoutStyle.Advanced"
+    android:rowCount="4"
+    android:columnCount="4"
+    android:background="@color/pad_advanced_background_color">
+
+    <Button
+        android:id="@+id/toggle_inv"
+        style="@style/PadButtonStyle.Advanced.Text"
+        android:layout_row="0"
+        android:layout_column="0"
+        android:background="@drawable/pad_button_inverse_background"
+        android:contentDescription="@string/desc_inv_off"
+        android:text="@string/inv" />
+
+    <Button
+        android:id="@+id/toggle_mode"
+        style="@style/PadButtonStyle.Advanced.Text"
+        android:layout_row="0"
+        android:layout_column="1"
+        android:contentDescription="@string/desc_switch_deg"
+        android:text="@string/mode_deg" />
+
+    <Button
+        android:id="@+id/op_pct"
+        style="@style/PadButtonStyle.Advanced"
+        android:layout_row="0"
+        android:layout_column="2"
+        android:contentDescription="@string/desc_op_pct"
+        android:text="@string/op_pct" />
+
+    <Button
+        android:id="@+id/fun_sin"
+        style="@style/PadButtonStyle.Advanced"
+        android:layout_row="1"
+        android:layout_column="0"
+        android:contentDescription="@string/desc_fun_sin"
+        android:text="@string/fun_sin" />
+
+    <Button
+        android:id="@+id/fun_arcsin"
+        style="@style/PadButtonStyle.Advanced"
+        android:layout_row="1"
+        android:layout_column="0"
+        android:contentDescription="@string/desc_fun_arcsin"
+        android:fontFamily="sans-serif-medium"
+        android:text="@string/fun_arcsin"
+        android:visibility="gone" />
+
+    <Button
+        android:id="@+id/fun_cos"
+        style="@style/PadButtonStyle.Advanced"
+        android:layout_row="1"
+        android:layout_column="1"
+        android:contentDescription="@string/desc_fun_cos"
+        android:text="@string/fun_cos" />
+
+    <Button
+        android:id="@+id/fun_arccos"
+        style="@style/PadButtonStyle.Advanced"
+        android:layout_row="1"
+        android:layout_column="1"
+        android:contentDescription="@string/desc_fun_arccos"
+        android:fontFamily="sans-serif-medium"
+        android:text="@string/fun_arccos"
+        android:visibility="gone" />
+
+    <Button
+        android:id="@+id/fun_tan"
+        style="@style/PadButtonStyle.Advanced"
+        android:layout_row="1"
+        android:layout_column="2"
+        android:contentDescription="@string/desc_fun_tan"
+        android:text="@string/fun_tan" />
+
+    <Button
+        android:id="@+id/fun_arctan"
+        style="@style/PadButtonStyle.Advanced"
+        android:layout_row="1"
+        android:layout_column="2"
+        android:contentDescription="@string/desc_fun_arctan"
+        android:fontFamily="sans-serif-medium"
+        android:text="@string/fun_arctan"
+        android:visibility="gone" />
+
+    <Button
+        android:id="@+id/const_pi"
+        style="@style/PadButtonStyle.Advanced"
+        android:layout_row="1"
+        android:layout_column="3"
+        android:contentDescription="@string/desc_const_pi"
+        android:text="@string/const_pi" />
+
+    <Button
+        android:id="@+id/fun_ln"
+        style="@style/PadButtonStyle.Advanced"
+        android:layout_row="2"
+        android:layout_column="0"
+        android:contentDescription="@string/desc_fun_ln"
+        android:text="@string/fun_ln" />
+
+    <Button
+        android:id="@+id/fun_exp"
+        style="@style/PadButtonStyle.Advanced"
+        android:layout_row="2"
+        android:layout_column="0"
+        android:contentDescription="@string/desc_fun_exp"
+        android:fontFamily="sans-serif-medium"
+        android:text="@string/fun_exp"
+        android:visibility="gone" />
+
+    <Button
+        android:id="@+id/fun_log"
+        style="@style/PadButtonStyle.Advanced"
+        android:layout_row="2"
+        android:layout_column="1"
+        android:contentDescription="@string/desc_fun_log"
+        android:text="@string/fun_log" />
+
+    <Button
+        android:id="@+id/fun_10pow"
+        style="@style/PadButtonStyle.Advanced"
+        android:layout_row="2"
+        android:layout_column="1"
+        android:contentDescription="@string/desc_fun_10pow"
+        android:fontFamily="sans-serif-medium"
+        android:text="@string/fun_10pow"
+        android:visibility="gone" />
+
+    <Button
+        android:id="@+id/op_fact"
+        style="@style/PadButtonStyle.Advanced"
+        android:layout_row="2"
+        android:layout_column="2"
+        android:contentDescription="@string/desc_op_fact"
+        android:text="@string/op_fact" />
+
+    <Button
+        android:id="@+id/const_e"
+        style="@style/PadButtonStyle.Advanced"
+        android:layout_row="2"
+        android:layout_column="3"
+        android:contentDescription="@string/desc_const_e"
+        android:text="@string/const_e" />
+
+    <Button
+        android:id="@+id/lparen"
+        style="@style/PadButtonStyle.Advanced"
+        android:layout_row="3"
+        android:layout_column="0"
+        android:contentDescription="@string/desc_lparen"
+        android:text="@string/lparen" />
+
+    <Button
+        android:id="@+id/rparen"
+        style="@style/PadButtonStyle.Advanced"
+        android:layout_row="3"
+        android:layout_column="1"
+        android:contentDescription="@string/desc_rparen"
+        android:text="@string/rparen" />
+
+    <Button
+        android:id="@+id/op_sqrt"
+        style="@style/PadButtonStyle.Advanced"
+        android:layout_row="3"
+        android:layout_column="2"
+        android:contentDescription="@string/desc_op_sqrt"
+        android:text="@string/op_sqrt" />
+
+    <Button
+        android:id="@+id/op_sqr"
+        style="@style/PadButtonStyle.Advanced"
+        android:layout_row="3"
+        android:layout_column="2"
+        android:contentDescription="@string/desc_op_sqr"
+        android:fontFamily="sans-serif-medium"
+        android:text="@string/op_sqr"
+        android:visibility="gone" />
+
+    <Button
+        android:id="@+id/op_pow"
+        style="@style/PadButtonStyle.Advanced"
+        android:layout_row="3"
+        android:layout_column="3"
+        android:contentDescription="@string/desc_op_pow"
+        android:text="@string/op_pow" />
+
+</GridLayout>
\ No newline at end of file
diff --git a/res/layout/pad_advanced_tablet_port.xml b/res/layout/pad_advanced_5x3.xml
similarity index 100%
rename from res/layout/pad_advanced_tablet_port.xml
rename to res/layout/pad_advanced_5x3.xml
diff --git a/res/layout/toolbar.xml b/res/layout/toolbar.xml
new file mode 100644
index 0000000..56f05b6
--- /dev/null
+++ b/res/layout/toolbar.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+
+<Toolbar
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/toolbar"
+    android:layout_width="match_parent"
+    android:layout_height="?android:attr/actionBarSize"
+    android:layout_gravity="top">
+
+    <TextView
+        android:id="@+id/mode"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:fontFamily="sans-serif-medium"
+        android:textAllCaps="true"
+        android:textSize="16sp" />
+
+</Toolbar>
\ No newline at end of file
diff --git a/res/mipmap-hdpi/ic_launcher_calculator.png b/res/mipmap-hdpi/ic_launcher_calculator.png
old mode 100755
new mode 100644
index 7db1239..e50e87e
--- a/res/mipmap-hdpi/ic_launcher_calculator.png
+++ b/res/mipmap-hdpi/ic_launcher_calculator.png
Binary files differ
diff --git a/res/mipmap-mdpi/ic_launcher_calculator.png b/res/mipmap-mdpi/ic_launcher_calculator.png
old mode 100755
new mode 100644
index bce23c3..2fcdc67
--- a/res/mipmap-mdpi/ic_launcher_calculator.png
+++ b/res/mipmap-mdpi/ic_launcher_calculator.png
Binary files differ
diff --git a/res/mipmap-xhdpi/ic_launcher_calculator.png b/res/mipmap-xhdpi/ic_launcher_calculator.png
old mode 100755
new mode 100644
index ad86415..67993fc
--- a/res/mipmap-xhdpi/ic_launcher_calculator.png
+++ b/res/mipmap-xhdpi/ic_launcher_calculator.png
Binary files differ
diff --git a/res/mipmap-xxhdpi/ic_launcher_calculator.png b/res/mipmap-xxhdpi/ic_launcher_calculator.png
old mode 100755
new mode 100644
index d799a8f..71387a9
--- a/res/mipmap-xxhdpi/ic_launcher_calculator.png
+++ b/res/mipmap-xxhdpi/ic_launcher_calculator.png
Binary files differ
diff --git a/res/mipmap-xxxhdpi/ic_launcher_calculator.png b/res/mipmap-xxxhdpi/ic_launcher_calculator.png
old mode 100755
new mode 100644
index 6f3111e..9d61dc4
--- a/res/mipmap-xxxhdpi/ic_launcher_calculator.png
+++ b/res/mipmap-xxxhdpi/ic_launcher_calculator.png
Binary files differ
diff --git a/res/values-af/strings.xml b/res/values-af/strings.xml
index 96c27d4..5edcae9 100644
--- a/res/values-af/strings.xml
+++ b/res/values-af/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"vee uit"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"wys omgekeerde funksies"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"versteek omgekeerde funksies"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Geen formule nie"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Getalle en basiese handelinge"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Gevorderde handelinge"</string>
     <string name="error_nan" msgid="5674077944929888710">"Nie getal nie"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Swak uitdrukking"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Gestaak"</string>
diff --git a/res/values-am/strings.xml b/res/values-am/strings.xml
index 0d84edf..14dbd5f 100644
--- a/res/values-am/strings.xml
+++ b/res/values-am/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"ሰርዝ"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"የተገላቢጦሽ ተግባሮችን አሳይ"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"የተገላቢጦሽ ተግባሮችን ደብቅ"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"ምንም ቀመር የለም"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"ቁጥሮች እና መሠረታዊ ክወናዎች"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"የላቁ ክወናዎች"</string>
     <string name="error_nan" msgid="5674077944929888710">"ቁጥር አይደለም"</string>
     <string name="error_syntax" msgid="4786987111228645602">"መጥፎ የሒሳብ ሐረግ"</string>
     <string name="error_aborted" msgid="3402238176316342537">"ተቋርጧል"</string>
diff --git a/res/values-ar/strings.xml b/res/values-ar/strings.xml
index 412d9ec..3085943 100644
--- a/res/values-ar/strings.xml
+++ b/res/values-ar/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"حذف"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"عرض الدالات العكسية"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"إخفاء الدالات العكسية"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"ليست هناك صيغة"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"الأرقام والعمليات الأساسية"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"العمليات المتقدمة"</string>
     <string name="error_nan" msgid="5674077944929888710">"ليس رقمًا"</string>
     <string name="error_syntax" msgid="4786987111228645602">"صيغة سيئة"</string>
     <string name="error_aborted" msgid="3402238176316342537">"تم الإلغاء"</string>
diff --git a/res/values-az-rAZ/strings.xml b/res/values-az-rAZ/strings.xml
index 09f7cb1..acd95fb 100644
--- a/res/values-az-rAZ/strings.xml
+++ b/res/values-az-rAZ/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"silin"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"əks funksiyaları göstərin"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"əks funksiyaları gizlədin"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Düstur yoxdur"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Nömrələr və əsas əməliyyatlar"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Qabaqcıl əməliyyatlar"</string>
     <string name="error_nan" msgid="5674077944929888710">"Rəqəm deyil"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Yalnış ifadə"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Ləğv edilib"</string>
diff --git a/res/values-b+sr+Latn/strings.xml b/res/values-b+sr+Latn/strings.xml
index 1869d48..24ad323 100644
--- a/res/values-b+sr+Latn/strings.xml
+++ b/res/values-b+sr+Latn/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"izbriši"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"prikaži inverzne funkcije"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"sakrij inverzne funkcije"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Nema formule"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Brojevi i osnovne operacije"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Napredne operacije"</string>
     <string name="error_nan" msgid="5674077944929888710">"Nije broj"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Neispravan izraz"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Otkazano je"</string>
diff --git a/res/values-be-rBY/strings.xml b/res/values-be-rBY/strings.xml
index 79ca42f..b5e63b8 100644
--- a/res/values-be-rBY/strings.xml
+++ b/res/values-be-rBY/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"выдаліць"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"паказаць адваротныя функцыі"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"схаваць адваротныя функцыі"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Няма формулы"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Лічбы і асноўныя аперацыі"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Дадатковыя аперацыі"</string>
     <string name="error_nan" msgid="5674077944929888710">"Не з\'яўляецца лікам"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Няправільны выраз"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Спынена"</string>
diff --git a/res/values-bg/strings.xml b/res/values-bg/strings.xml
index f442b04..5f8222f 100644
--- a/res/values-bg/strings.xml
+++ b/res/values-bg/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"изтриване"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"показване на обратните функции"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"скриване на обратните функции"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Няма формула"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Цифри и основни действия"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Разширени действия"</string>
     <string name="error_nan" msgid="5674077944929888710">"Не е число"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Неправилен израз"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Прекратено"</string>
diff --git a/res/values-bn-rBD/strings.xml b/res/values-bn-rBD/strings.xml
index 2742857..b6ed7c8 100644
--- a/res/values-bn-rBD/strings.xml
+++ b/res/values-bn-rBD/strings.xml
@@ -27,7 +27,7 @@
     <string name="desc_const_e" msgid="1889187337970539507">"ইউলার সংখ্যা"</string>
     <string name="desc_const_pi" msgid="5430918714009441655">"পাই"</string>
     <string name="desc_dec_point" msgid="5725254504360445023">"পয়েন্ট"</string>
-    <string name="desc_lparen" msgid="8688758170211924916">"বাঁ লঘুবন্ধনী"</string>
+    <string name="desc_lparen" msgid="8688758170211924916">"বাম লঘুবন্ধনী"</string>
     <string name="desc_rparen" msgid="7920608385146151731">"ডান লঘুবন্ধনী"</string>
     <string name="desc_fun_cos" msgid="3787913784504974731">"কোসাইন"</string>
     <string name="desc_fun_ln" msgid="2505119732546227166">"স্বাভাবিক লগারিদম"</string>
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"মুছুন"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"বিপরীত ক্রিয়াকলাপ দেখান"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"বিপরীত ক্রিয়াকলাপ লুকান"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"কোনো সূত্র নেই"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"নম্বর এবং মৌলিক ক্রিয়াকলাপ"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"উন্নত ক্রিয়াকলাপ"</string>
     <string name="error_nan" msgid="5674077944929888710">"একটি সংখ্যা নয়"</string>
     <string name="error_syntax" msgid="4786987111228645602">"খারাপ এক্সপ্রেশন"</string>
     <string name="error_aborted" msgid="3402238176316342537">"বাতিল করা হয়েছে"</string>
diff --git a/res/values-bs-rBA/strings.xml b/res/values-bs-rBA/strings.xml
index 7759882..4dcd00a 100644
--- a/res/values-bs-rBA/strings.xml
+++ b/res/values-bs-rBA/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"izbriši"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"prikaži inverzne funkcije"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"sakrij inverzne funkcije"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Nema formule"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Brojevi i osnovne operacije"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Napredne operacije"</string>
     <string name="error_nan" msgid="5674077944929888710">"Nije broj"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Nepravilan izraz"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Prekinuto"</string>
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 1800849..557e57e 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"suprimeix"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"mostra les funcions inverses"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"amaga les funcions inverses"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"No hi ha cap fórmula"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Números i operacions bàsiques"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Operacions avançades"</string>
     <string name="error_nan" msgid="5674077944929888710">"No és una xifra"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Expr. incorrecta"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Anul·lat"</string>
diff --git a/res/values-cs/strings.xml b/res/values-cs/strings.xml
index 7c49941..a61fda4 100644
--- a/res/values-cs/strings.xml
+++ b/res/values-cs/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"smazat"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"zobrazit inverzní funkce"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"skrýt inverzní funkce"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Žádný vzorec"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Čísla a základní operace"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Pokročilé operace"</string>
     <string name="error_nan" msgid="5674077944929888710">"Není číslo"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Chybný výraz"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Zrušeno"</string>
diff --git a/res/values-da/strings.xml b/res/values-da/strings.xml
index bac1079..a75d929 100644
--- a/res/values-da/strings.xml
+++ b/res/values-da/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"slet"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"vis inversfunktioner"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"skjul inversfunktioner"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Ingen formel"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Tal og grundlæggende handlinger"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Avancerede handlinger"</string>
     <string name="error_nan" msgid="5674077944929888710">"Ikke et tal"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Ugyldigt udtryk"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Afbrudt"</string>
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
index 7441e17..673c5cd 100644
--- a/res/values-de/strings.xml
+++ b/res/values-de/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"Löschen"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"Umkehrfunktionen anzeigen"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"Umkehrfunktionen ausblenden"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Keine Formel"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Zahlen und grundlegende Vorgänge"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Erweiterte Vorgänge"</string>
     <string name="error_nan" msgid="5674077944929888710">"Keine Zahl"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Ausdrucksfehler"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Abgebrochen"</string>
diff --git a/res/values-el/strings.xml b/res/values-el/strings.xml
index ea025ab..77903dc 100644
--- a/res/values-el/strings.xml
+++ b/res/values-el/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"διαγραφή"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"εμφάνιση αντίστροφων λειτουργιών"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"απόκρυψη αντίστροφων λειτουργιών"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Δεν υπάρχει τύπος"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Αριθμοί και βασικές λειτουργίες"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Προηγμένες λειτουργίες"</string>
     <string name="error_nan" msgid="5674077944929888710">"Όχι αριθμός"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Λάθος έκφραση"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Ακυρώθηκε"</string>
diff --git a/res/values-en-rAU/strings.xml b/res/values-en-rAU/strings.xml
index 15fd704..a93046c 100644
--- a/res/values-en-rAU/strings.xml
+++ b/res/values-en-rAU/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"Delete"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"show inverse functions"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"hide inverse functions"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"No formula"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Numbers and basic operations"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Advanced operations"</string>
     <string name="error_nan" msgid="5674077944929888710">"Not a number"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Bad expression"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Aborted"</string>
diff --git a/res/values-en-rGB/strings.xml b/res/values-en-rGB/strings.xml
index 15fd704..a93046c 100644
--- a/res/values-en-rGB/strings.xml
+++ b/res/values-en-rGB/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"Delete"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"show inverse functions"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"hide inverse functions"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"No formula"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Numbers and basic operations"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Advanced operations"</string>
     <string name="error_nan" msgid="5674077944929888710">"Not a number"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Bad expression"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Aborted"</string>
diff --git a/res/values-en-rIN/strings.xml b/res/values-en-rIN/strings.xml
index 15fd704..a93046c 100644
--- a/res/values-en-rIN/strings.xml
+++ b/res/values-en-rIN/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"Delete"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"show inverse functions"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"hide inverse functions"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"No formula"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Numbers and basic operations"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Advanced operations"</string>
     <string name="error_nan" msgid="5674077944929888710">"Not a number"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Bad expression"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Aborted"</string>
diff --git a/res/values-es-rUS/strings.xml b/res/values-es-rUS/strings.xml
index e13eb43..33a5f82 100644
--- a/res/values-es-rUS/strings.xml
+++ b/res/values-es-rUS/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"eliminar"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"mostrar funciones inversas"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"ocultar funciones inversas"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Sin fórmula"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Números y operaciones básicas"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Operaciones avanzadas"</string>
     <string name="error_nan" msgid="5674077944929888710">"No es número."</string>
     <string name="error_syntax" msgid="4786987111228645602">"Expresión incorrecta"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Interrumpida"</string>
diff --git a/res/values-es/strings.xml b/res/values-es/strings.xml
index c8ba9d8..ad9713f 100644
--- a/res/values-es/strings.xml
+++ b/res/values-es/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"eliminar"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"muestra las funciones inversas"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"oculta las funciones inversas"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Sin fórmula"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Números y operaciones básicas"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Operaciones avanzadas"</string>
     <string name="error_nan" msgid="5674077944929888710">"No es un número"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Expresión incorrecta"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Cancelado"</string>
diff --git a/res/values-et-rEE/strings.xml b/res/values-et-rEE/strings.xml
index 347d749..be62df1 100644
--- a/res/values-et-rEE/strings.xml
+++ b/res/values-et-rEE/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"kustutamine"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"pöördfunktsioonide kuvamine"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"pöördfunktsioonide peitmine"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Valem puudub"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Numbrid ja lihtsad toimingud"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Täpsemad toimingud"</string>
     <string name="error_nan" msgid="5674077944929888710">"Pole number"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Vale avaldis"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Katkestatud"</string>
diff --git a/res/values-eu-rES/strings.xml b/res/values-eu-rES/strings.xml
index 0f9000e..f1696bc 100644
--- a/res/values-eu-rES/strings.xml
+++ b/res/values-eu-rES/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"ezabatu"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"erakutsi alderantzizko funtzioak"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"ezkutatu alderantzizko funtzioak"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Ez dago formularik"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Zenbakiak eta oinarrizko eragiketak"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Eragiketa aurreratuak"</string>
     <string name="error_nan" msgid="5674077944929888710">"Ez da zenbakia"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Sintaxi-errorea"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Utzi egin da"</string>
diff --git a/res/values-fa/strings.xml b/res/values-fa/strings.xml
index 2c3be75..ed4e20e 100644
--- a/res/values-fa/strings.xml
+++ b/res/values-fa/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"حذف"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"نمایش توابع معکوس"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"پنهان کردن توابع معکوس"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"فرمولی وجود ندارد"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"عملیات عددی و پایه"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"عملیات پیشرفته"</string>
     <string name="error_nan" msgid="5674077944929888710">"پاسخ عددی ندارد"</string>
     <string name="error_syntax" msgid="4786987111228645602">"عبارت نادرست"</string>
     <string name="error_aborted" msgid="3402238176316342537">"لغو شد"</string>
diff --git a/res/values-fi/strings.xml b/res/values-fi/strings.xml
index 78daa5a..f270ba8 100644
--- a/res/values-fi/strings.xml
+++ b/res/values-fi/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"poista"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"näytä käänteisfunktiot"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"piilota käänteisfunktiot"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Ei kaavaa"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Numerot ja peruslaskutoimitukset"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Lisätoiminnot"</string>
     <string name="error_nan" msgid="5674077944929888710">"Ei numero"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Virhe ilmaisussa"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Keskeytettiin."</string>
diff --git a/res/values-fr-rCA/strings.xml b/res/values-fr-rCA/strings.xml
index e349bb5..03583ad 100644
--- a/res/values-fr-rCA/strings.xml
+++ b/res/values-fr-rCA/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"supprimer"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"afficher les fonctions inverses"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"masquer les fonctions inverses"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Aucune formule"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Chiffres et opérations de base"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Opérations avancées"</string>
     <string name="error_nan" msgid="5674077944929888710">"Pas un nombre"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Express. incorr."</string>
     <string name="error_aborted" msgid="3402238176316342537">"Annulée"</string>
diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml
index a7f558e..d78451b 100644
--- a/res/values-fr/strings.xml
+++ b/res/values-fr/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"supprimer"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"afficher les fonctions inverses"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"masquer les fonctions inverses"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Aucune formule"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Nombres et opérations de base"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Opérations avancées"</string>
     <string name="error_nan" msgid="5674077944929888710">"Pas un nombre"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Express. incorr."</string>
     <string name="error_aborted" msgid="3402238176316342537">"Annulée"</string>
diff --git a/res/values-gl-rES/strings.xml b/res/values-gl-rES/strings.xml
index 3a5ed43..bbe3517 100644
--- a/res/values-gl-rES/strings.xml
+++ b/res/values-gl-rES/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"eliminar"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"mostrar funcións inversas"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"ocultar funcións inversas"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Sen fórmula"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Números e operacións básicas"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Operacións avanzadas"</string>
     <string name="error_nan" msgid="5674077944929888710">"Non é número"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Expresión incorrecta"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Abortada"</string>
diff --git a/res/values-gu-rIN/strings.xml b/res/values-gu-rIN/strings.xml
index 487e6b6..8fd8d3b 100644
--- a/res/values-gu-rIN/strings.xml
+++ b/res/values-gu-rIN/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"કાઢી નાખો"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"વ્યુત્ક્રમ કાર્ય બતાવો"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"વ્યુત્ક્રમ કાર્ય છુપાવો"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"કોઇ ફોર્મૂલા નથી"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"નંબરો અને મૂળભૂત ઓપરેશન્સ"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"વિગતવાર ઓપરેશન્સ"</string>
     <string name="error_nan" msgid="5674077944929888710">"કોઈ સંખ્યા નથી"</string>
     <string name="error_syntax" msgid="4786987111228645602">"ખોટી પદાવલિ"</string>
     <string name="error_aborted" msgid="3402238176316342537">"રદ કરેલ"</string>
diff --git a/res/values-hi/strings.xml b/res/values-hi/strings.xml
index 6c23e92..cd1aaf3 100644
--- a/res/values-hi/strings.xml
+++ b/res/values-hi/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"हटाएं"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"प्रतिलोम फ़ंक्शन दिखाएं"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"प्रतिलोम फ़ंक्शन छिपाएं"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"कोई फ़ार्मूला नहीं"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"संख्याएं और मूल संचालन"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"उन्नत संचालन"</string>
     <string name="error_nan" msgid="5674077944929888710">"संख्या नहीं है"</string>
     <string name="error_syntax" msgid="4786987111228645602">"खराब व्यंजक"</string>
     <string name="error_aborted" msgid="3402238176316342537">"निरस्त किया गया"</string>
diff --git a/res/values-hr/strings.xml b/res/values-hr/strings.xml
index aa485a3..177dbde 100644
--- a/res/values-hr/strings.xml
+++ b/res/values-hr/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"izbriši"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"prikazuje inverzne funkcije"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"sakriva inverzne funkcije"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Nema formule"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Brojevi i osnovne operacije"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Napredne operacije"</string>
     <string name="error_nan" msgid="5674077944929888710">"Nije broj"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Neispravan izraz"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Prekinuto"</string>
diff --git a/res/values-hu/strings.xml b/res/values-hu/strings.xml
index 607a7f1..722d826 100644
--- a/res/values-hu/strings.xml
+++ b/res/values-hu/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"törlés"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"inverz függvények megjelenítése"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"inverz függvények elrejtése"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Nincs képlet"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Számok és alapműveletek"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Speciális műveletek"</string>
     <string name="error_nan" msgid="5674077944929888710">"Nem egy szám"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Hibás kifejezés"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Megszakítva"</string>
diff --git a/res/values-hy-rAM/strings.xml b/res/values-hy-rAM/strings.xml
index 909c4aa..dfb51f4 100644
--- a/res/values-hy-rAM/strings.xml
+++ b/res/values-hy-rAM/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"ջնջել"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"ցուցադրել հակադարձ գործառույթները"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"թաքցնել հակադարձ գործառույթները"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Բանաձև չկա"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Թվեր և հիմնական գործողություններ"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Լրացուցիչ գործողություններ"</string>
     <string name="error_nan" msgid="5674077944929888710">"Թիվ չէ"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Սխալ է"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Ընդհատված"</string>
diff --git a/res/values-in/strings.xml b/res/values-in/strings.xml
index 036cf3d..c1008ad 100644
--- a/res/values-in/strings.xml
+++ b/res/values-in/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"hapus"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"tampilkan fungsi balikan"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"semunyikan fungsi balikan"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Tidak ada formula"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Angka dan operasi dasar"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Operasi lanjutan"</string>
     <string name="error_nan" msgid="5674077944929888710">"Bukan angka"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Pernyataan salah"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Dibatalkan"</string>
diff --git a/res/values-is-rIS/strings.xml b/res/values-is-rIS/strings.xml
index 10978b7..89c8174 100644
--- a/res/values-is-rIS/strings.xml
+++ b/res/values-is-rIS/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"eyða"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"sýna andhverf föll"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"fela andhverf föll"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Engin formúla"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Tölur og einfaldar aðgerðir"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Ítarlegri aðgerðir"</string>
     <string name="error_nan" msgid="5674077944929888710">"Ekki tala"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Röng segð"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Hætt við"</string>
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
index bf4d880..1d2e8e4 100644
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"elimina"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"mostra funzioni inverse"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"nascondi funzioni inverse"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Nessuna formula"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Numeri e operazioni basilari"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Operazioni avanzate"</string>
     <string name="error_nan" msgid="5674077944929888710">"Non è un numero"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Espressione errata"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Interrotta"</string>
diff --git a/res/values-iw/strings.xml b/res/values-iw/strings.xml
index 6d24ffc..35d81af 100644
--- a/res/values-iw/strings.xml
+++ b/res/values-iw/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"מחק"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"הצג פונקציות אינברס"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"הסתר פונקציות אינברס"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"אין נוסחה"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"מספרים ופעולות בסיסיות"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"פעולות מתקדמות"</string>
     <string name="error_nan" msgid="5674077944929888710">"אינו מספר"</string>
     <string name="error_syntax" msgid="4786987111228645602">"ביטוי שגוי"</string>
     <string name="error_aborted" msgid="3402238176316342537">"בוטל"</string>
diff --git a/res/values-ja/strings.xml b/res/values-ja/strings.xml
index 45a62c1..26d017f 100644
--- a/res/values-ja/strings.xml
+++ b/res/values-ja/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"削除"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"逆関数を表示する"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"逆関数を表示しない"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"数式がありません"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"数字と基本的な演算"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"高度な演算"</string>
     <string name="error_nan" msgid="5674077944929888710">"数値以外です"</string>
     <string name="error_syntax" msgid="4786987111228645602">"式が無効です"</string>
     <string name="error_aborted" msgid="3402238176316342537">"中止しました"</string>
diff --git a/res/values-ka-rGE/strings.xml b/res/values-ka-rGE/strings.xml
index ca9e157..aec5fb1 100644
--- a/res/values-ka-rGE/strings.xml
+++ b/res/values-ka-rGE/strings.xml
@@ -57,8 +57,11 @@
     <string name="desc_del" msgid="7879515781929311432">"წაშლა"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"შებრუნებული ფუნქციების ჩვენება"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"შებრუნებული ფუნქციების დამალვა"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"ფორმულა არ არის"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"რიცხვები და ძირითადი ოპერაციები"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"დამატებითი ოპერაციები"</string>
     <string name="error_nan" msgid="5674077944929888710">"არ არის რიცხვი"</string>
-    <string name="error_syntax" msgid="4786987111228645602">"არასწორი გამოსახულება"</string>
+    <string name="error_syntax" msgid="4786987111228645602">"არასწორი ფრაზა"</string>
     <string name="error_aborted" msgid="3402238176316342537">"შეწყვეტილია"</string>
     <string name="error_overflow" msgid="7800547394563434764">"განუსაზღვრელი?"</string>
     <string name="error_zero_divide" msgid="458040988686661801">"0-ზე გაყოფა შეუძლებელია"</string>
diff --git a/res/values-kk-rKZ/strings.xml b/res/values-kk-rKZ/strings.xml
index f7d0662..5d2185e 100644
--- a/res/values-kk-rKZ/strings.xml
+++ b/res/values-kk-rKZ/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"жою"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"кері функцияларды көрсетеді"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"кері функцияларды жасырады"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Формуласы жоқ"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Сандар және негізгі операциялар"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Күрделі операциялар"</string>
     <string name="error_nan" msgid="5674077944929888710">"Сан емес"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Қате өрнек"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Тоқтатылды"</string>
diff --git a/res/values-km-rKH/strings.xml b/res/values-km-rKH/strings.xml
index 4b6ec9e..5b0b725 100644
--- a/res/values-km-rKH/strings.xml
+++ b/res/values-km-rKH/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"លុប"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"បង្ហាញមុខងារច្រាស"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"លាក់មុខងារច្រាស"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"គ្មានរូបមន្ត"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"លេខ និងប្រតិបត្តិការមូលដ្ឋាន"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"ប្រតិបត្តិការកម្រិតខ្ពស់"</string>
     <string name="error_nan" msgid="5674077944929888710">"មិនមែនជាលេខទេ"</string>
     <string name="error_syntax" msgid="4786987111228645602">"កន្សោមមិនត្រឹមត្រូវ"</string>
     <string name="error_aborted" msgid="3402238176316342537">"បោះបង់"</string>
diff --git a/res/values-kn-rIN/strings.xml b/res/values-kn-rIN/strings.xml
index c8b9aab..75debd8 100644
--- a/res/values-kn-rIN/strings.xml
+++ b/res/values-kn-rIN/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"ಅಳಿಸು"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"ವಿಲೋಮ ಫಂಕ್ಷನ್‌ಗಳನ್ನು ತೋರಿಸು"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"ವಿಲೋಮ ಫಂಕ್ಷನ್‌ಗಳನ್ನು ಮರೆಮಾಡು"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"ಯಾವುದೇ ಫಾರ್ಮುಲಾ ಇಲ್ಲ"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"ಸಂಖ್ಯೆಗಳು ಮತ್ತು ಮೂಲ ಕಾರ್ಯಾಚರಣೆಗಳು"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"ಸುಧಾರಿತ ಕಾರ್ಯಾಚರಣೆಗಳು"</string>
     <string name="error_nan" msgid="5674077944929888710">"ಸಂಖ್ಯೆಯಲ್ಲ"</string>
     <string name="error_syntax" msgid="4786987111228645602">"ತಪ್ಪು ಪದ"</string>
     <string name="error_aborted" msgid="3402238176316342537">"ರದ್ದು ಮಾಡಲಾಗಿದೆ"</string>
@@ -69,7 +72,7 @@
     <string name="dismiss" msgid="7872443888515066216">"ವಜಾಗೊಳಿಸು"</string>
     <string name="exact" msgid="2597237880041789948">"(ನಿಖರ)"</string>
     <string name="approximate" msgid="7117143366610670836">"(±1 ಕೊನೆಯ ಅಂಕಿಯಲ್ಲಿ)"</string>
-    <string name="menu_leading" msgid="2338520833272667740">"ಮೊದಲ ಅಂಕೆ ಜೊತೆ ಉತ್ತರಿಸಿ"</string>
-    <string name="menu_fraction" msgid="1247477377840252234">"ಭಾಗದಂತೆ ಉತ್ತರಿಸಿ"</string>
+    <string name="menu_leading" msgid="2338520833272667740">"ಮೊದಲ ಅಂಕೆ ಜೊತೆ ಉತ್ತರಿಸು"</string>
+    <string name="menu_fraction" msgid="1247477377840252234">"ಭಾಗದಂತೆ ಉತ್ತರಿಸು"</string>
     <string name="menu_licenses" msgid="1890541368064108592">"ಮುಕ್ತ ಮೂಲ ಪರವಾನಗಿಗಳು"</string>
 </resources>
diff --git a/res/values-ko/strings.xml b/res/values-ko/strings.xml
index e968fb8..15eeb0a 100644
--- a/res/values-ko/strings.xml
+++ b/res/values-ko/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"삭제"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"역함수 표시"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"역함수 숨기기"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"수식 없음"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"숫자 및 기본 작업"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"고급 작업"</string>
     <string name="error_nan" msgid="5674077944929888710">"숫자가 아님"</string>
     <string name="error_syntax" msgid="4786987111228645602">"잘못된 수식"</string>
     <string name="error_aborted" msgid="3402238176316342537">"중단됨"</string>
diff --git a/res/values-ky-rKG/strings.xml b/res/values-ky-rKG/strings.xml
index fdd6823..b6112c7 100644
--- a/res/values-ky-rKG/strings.xml
+++ b/res/values-ky-rKG/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"жок кылуу"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"карама-каршы функцияларды көрсөтүү"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"карама-каршы функцияларды жашыруу"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Формула жок"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Сандар жана негизги операциялар"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Өркүндөтүлгөн операциялар"</string>
     <string name="error_nan" msgid="5674077944929888710">"Сан эмес"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Начар туюнтма"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Үзгүлтккө учурды"</string>
diff --git a/res/values-lo-rLA/strings.xml b/res/values-lo-rLA/strings.xml
index 55ff0a5..eb8b553 100644
--- a/res/values-lo-rLA/strings.xml
+++ b/res/values-lo-rLA/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"ລຶບ"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"ສະ​ແດງ​ຟັງ​ຄ໌​ຊັນ​ທີ່​ຈຸ່ມ​ລົງ"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"ເຊື່ອງ​ຟັງ​ຄ໌​ຊັນ​ທີ່​ຈຸ່ມ​ລົງ"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"ບໍ່ມີສູດຄຳນວນ"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"ຄຳສັ່ງພື້ນຖານ ແລະ ຕົວເລກ"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"ຄຳສັ່ງຂັ້ນສູງ"</string>
     <string name="error_nan" msgid="5674077944929888710">"ບໍ່​ແມ່ນ​ໂຕ​ເລກ"</string>
     <string name="error_syntax" msgid="4786987111228645602">"ສຳ​ນວນ​ບໍ່​ດີ"</string>
     <string name="error_aborted" msgid="3402238176316342537">"ລົບ​ລ້າງ"</string>
diff --git a/res/values-lt/strings.xml b/res/values-lt/strings.xml
index 2faf0a0..1d87167 100644
--- a/res/values-lt/strings.xml
+++ b/res/values-lt/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"ištrinti"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"rodyti atvirkštines funkcijas"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"slėpti atvirkštines funkcijas"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Nėra formulės"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Skaičiai ir pagrindiniai veiksmai"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Išplėstiniai veiksmai"</string>
     <string name="error_nan" msgid="5674077944929888710">"Ne skaičius"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Blogas reiškinys"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Nutraukta"</string>
diff --git a/res/values-lv/strings.xml b/res/values-lv/strings.xml
index 91b447e..1ac91da 100644
--- a/res/values-lv/strings.xml
+++ b/res/values-lv/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"dzēst"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"rādīt inversās funkcijas"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"slēpt inversās funkcijas"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Nav formulas"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Skaitļi un pamatdarbības"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Papildu darbības"</string>
     <string name="error_nan" msgid="5674077944929888710">"Nav skaitlis"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Kļūd. izteiksme"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Pārtraukts"</string>
diff --git a/res/values-mk-rMK/strings.xml b/res/values-mk-rMK/strings.xml
index f7af89f..7ad6da7 100644
--- a/res/values-mk-rMK/strings.xml
+++ b/res/values-mk-rMK/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"избриши"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"прикажи инверзни функции"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"сокриј инверзни функции"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Нема формула"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Броеви и основни операции"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Напредни операции"</string>
     <string name="error_nan" msgid="5674077944929888710">"Не е број"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Погрешен израз"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Прекинато"</string>
diff --git a/res/values-ml-rIN/strings.xml b/res/values-ml-rIN/strings.xml
index e21e42b..45078ff 100644
--- a/res/values-ml-rIN/strings.xml
+++ b/res/values-ml-rIN/strings.xml
@@ -17,7 +17,7 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="1756503303878374382">"കാൽക്കുലേറ്റർ"</string>
+    <string name="app_name" msgid="1756503303878374382">"കാൽക്കുലേ."</string>
     <string name="dec_point" msgid="8920102493070918054">"."</string>
     <string name="mode_deg" msgid="1146123354434332479">"ഡിഗ്രി"</string>
     <string name="mode_rad" msgid="1434228830085760996">"rad"</string>
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"ഇല്ലാതാക്കുക"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"വിപരീത പ്രവർത്തനങ്ങൾ കാണിക്കുക"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"വിപരീത പ്രവർത്തനങ്ങൾ മറയ്‌ക്കുക"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"ഫോർമുലയില്ല"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"നമ്പറുകളും അടിസ്ഥാന പ്രവർത്തനങ്ങളും"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"വിപുലമായ പ്രവർത്തനങ്ങൾ"</string>
     <string name="error_nan" msgid="5674077944929888710">"സംഖ്യയല്ല"</string>
     <string name="error_syntax" msgid="4786987111228645602">"മോശം എക്‌സ്‌പ്രഷൻ"</string>
     <string name="error_aborted" msgid="3402238176316342537">"പരാജയപ്പെട്ടു"</string>
diff --git a/res/values-mn-rMN/strings.xml b/res/values-mn-rMN/strings.xml
index 536c5af..cb69f6e 100644
--- a/res/values-mn-rMN/strings.xml
+++ b/res/values-mn-rMN/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"устгах"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"урвуу фүнкцыг харуулах"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"урвуу функцийг нуух"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Томъёо байхгүй"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Тоо, үндсэн үйл ажиллагаа"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Сайжруулсан үйл ажиллагаа"</string>
     <string name="error_nan" msgid="5674077944929888710">"Тоо биш байна"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Буруу илэрхийлэл"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Зогсоосон"</string>
diff --git a/res/values-mr-rIN/strings.xml b/res/values-mr-rIN/strings.xml
index e3f10b8..810baec 100644
--- a/res/values-mr-rIN/strings.xml
+++ b/res/values-mr-rIN/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"हटवा"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"व्यस्त कार्ये दर्शवा"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"व्यस्त कार्ये लपवा"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"सूत्र नाही"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"संख्या आणि मूलभूत ऑपरेशन"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"प्रगत ऑपरेशन"</string>
     <string name="error_nan" msgid="5674077944929888710">"संख्या नाही"</string>
     <string name="error_syntax" msgid="4786987111228645602">"खराब पदावली"</string>
     <string name="error_aborted" msgid="3402238176316342537">"विलोपन केले"</string>
diff --git a/res/values-ms-rMY/strings.xml b/res/values-ms-rMY/strings.xml
index bec9757..4674f25 100644
--- a/res/values-ms-rMY/strings.xml
+++ b/res/values-ms-rMY/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"padam"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"tunjukkan fungsi songsang"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"sembunyikan fungsi songsang"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Tiada formula"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Nombor dan pengendalian asas"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Pengendalian lanjutan"</string>
     <string name="error_nan" msgid="5674077944929888710">"Bukan nombor"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Ungkapan salah"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Dihenti paksa"</string>
diff --git a/res/values-my-rMM/strings.xml b/res/values-my-rMM/strings.xml
index fb45bb8..2b35837 100644
--- a/res/values-my-rMM/strings.xml
+++ b/res/values-my-rMM/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"ဖျက်ရန်"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"ပြောင်းပြန်ဆောင်ရွက်ချက်များ ပြပါ"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"ပြောင်းပြန်ဆောင်ရွက်ချက်များ ဖျောက်ထားရန်"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"ဖော်မြူလာမရှိပါ"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"နံပါတ်နှင့် အခြေခံလုပ်ငန်းများ"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"အဆင့်မြင့် လုပ်ငန်းများ"</string>
     <string name="error_nan" msgid="5674077944929888710">"ဂဏန်း မဟုတ်"</string>
     <string name="error_syntax" msgid="4786987111228645602">"ဖော်ပြချက်အမှား"</string>
     <string name="error_aborted" msgid="3402238176316342537">"ဖျက်လိုက်ပြီ"</string>
diff --git a/res/values-nb/strings.xml b/res/values-nb/strings.xml
index fab30cd..bd2dc79 100644
--- a/res/values-nb/strings.xml
+++ b/res/values-nb/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"slett"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"vis omvendte funksjoner"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"skjul omvendte funksjoner"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Ingen formel"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Tall og grunnleggende operasjoner"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Avanserte operasjoner"</string>
     <string name="error_nan" msgid="5674077944929888710">"Ikke et tall"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Ugyldig uttrykk"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Avbrutt"</string>
diff --git a/res/values-ne-rNP/strings.xml b/res/values-ne-rNP/strings.xml
index fc96071..6528f13 100644
--- a/res/values-ne-rNP/strings.xml
+++ b/res/values-ne-rNP/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"मेटाउनुहोस्"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"व्युत्क्रम प्रकार्यहरु देखाउनुहोस्"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"व्युत्क्रम प्रकार्यहरु लुकाउनुहोस्"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"कुनै सूत्र छैन"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"संख्या र आधारभूत कारबाहीहरू"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"उन्नत कारबाहीहरू"</string>
     <string name="error_nan" msgid="5674077944929888710">"एक नम्बर होइन"</string>
     <string name="error_syntax" msgid="4786987111228645602">"खराब अभिव्यक्ति"</string>
     <string name="error_aborted" msgid="3402238176316342537">"रद्द गरियो"</string>
diff --git a/res/values-nl/strings.xml b/res/values-nl/strings.xml
index 183d4d6..aeac46f 100644
--- a/res/values-nl/strings.xml
+++ b/res/values-nl/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"verwijderen"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"inverse functies weergeven"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"inverse functies verbergen"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Geen formule"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Cijfers en basisbewerkingen"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Geavanceerde bewerkingen"</string>
     <string name="error_nan" msgid="5674077944929888710">"Is geen getal"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Ongeldige invoer"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Afgebroken"</string>
diff --git a/res/values-pa-rIN/strings.xml b/res/values-pa-rIN/strings.xml
index 30f249c..22bf90b 100644
--- a/res/values-pa-rIN/strings.xml
+++ b/res/values-pa-rIN/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"ਮਿਟਾਓ"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"ਉਲਟੇ ਫੰਕਸ਼ਨ ਦਿਖਾਓ"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"ਉਲਟੇ ਫੰਕਸ਼ਨ ਲੁਕਾਓ"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"ਕੋਈ ਫ਼ਾਰਮੂਲਾ ਨਹੀਂ"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"ਨੰਬਰ ਅਤੇ ਮੂਲ ਆਪਰੇਸ਼ਨ"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"ਉੱਨਤ ਆਪਰੇਸ਼ਨ"</string>
     <string name="error_nan" msgid="5674077944929888710">"ਇੱਕ ਸੰਖਿਆ ਨਹੀਂ"</string>
     <string name="error_syntax" msgid="4786987111228645602">"ਗਲਤ ਸਮੀਕਰਨ"</string>
     <string name="error_aborted" msgid="3402238176316342537">"ਬਰਖ਼ਾਸਤ ਕੀਤਾ ਗਿਆ"</string>
diff --git a/res/values-pl/strings.xml b/res/values-pl/strings.xml
index 2e372a4..4b1e736 100644
--- a/res/values-pl/strings.xml
+++ b/res/values-pl/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"usuń"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"pokaż funkcje odwrotne"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"ukryj funkcje odwrotne"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Brak formuły"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Liczby i operacje podstawowe"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Operacje zaawansowane"</string>
     <string name="error_nan" msgid="5674077944929888710">"To nie liczba"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Błędne wyrażenie"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Przerwano"</string>
diff --git a/res/values-pt-rBR/strings.xml b/res/values-pt-rBR/strings.xml
deleted file mode 100644
index 0a04f55..0000000
--- a/res/values-pt-rBR/strings.xml
+++ /dev/null
@@ -1,75 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- 
-  Copyright (C) 2015 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.
-   -->
-
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="app_name" msgid="1756503303878374382">"Calculadora"</string>
-    <string name="dec_point" msgid="8920102493070918054">","</string>
-    <string name="mode_deg" msgid="1146123354434332479">"graus"</string>
-    <string name="mode_rad" msgid="1434228830085760996">"rad"</string>
-    <string name="clr" msgid="6730945431543327337">"limp"</string>
-    <string name="cleared" msgid="3952521190281880880">"apagados"</string>
-    <string name="del" msgid="5878069000864178910">"del"</string>
-    <string name="desc_const_e" msgid="1889187337970539507">"Número de Euler"</string>
-    <string name="desc_const_pi" msgid="5430918714009441655">"pi"</string>
-    <string name="desc_dec_point" msgid="5725254504360445023">"ponto"</string>
-    <string name="desc_lparen" msgid="8688758170211924916">"parêntese esquerdo"</string>
-    <string name="desc_rparen" msgid="7920608385146151731">"parêntese direito"</string>
-    <string name="desc_fun_cos" msgid="3787913784504974731">"cosseno"</string>
-    <string name="desc_fun_ln" msgid="2505119732546227166">"logaritmo natural"</string>
-    <string name="desc_fun_log" msgid="4202230639814060062">"logaritmo"</string>
-    <string name="desc_fun_sin" msgid="7425661271929838876">"seno"</string>
-    <string name="desc_fun_tan" msgid="3545496562069310036">"tangente"</string>
-    <string name="desc_fun_arccos" msgid="7583077865497939123">"cosseno inverso"</string>
-    <string name="desc_fun_arcsin" msgid="8592309261123302915">"seno inverso"</string>
-    <string name="desc_fun_arctan" msgid="6891388393360447455">"tangente inversa"</string>
-    <string name="desc_fun_10pow" msgid="3972646467174520812">"dez elevado a"</string>
-    <string name="desc_fun_exp" msgid="657106837530588390">"função exponencial"</string>
-    <string name="desc_op_sqr" msgid="693577095029219627">"ao quadrado"</string>
-    <string name="desc_op_add" msgid="5539599782598050813">"mais"</string>
-    <string name="desc_op_div" msgid="3032399266115257498">"dividir"</string>
-    <string name="desc_op_fact" msgid="6142780103294179702">"fatorial"</string>
-    <string name="desc_op_mul" msgid="6882122010553947015">"multiplicar"</string>
-    <string name="desc_op_pct" msgid="2466179646911499434">"porcentagem"</string>
-    <string name="desc_op_pow" msgid="6010144442344099030">"potência"</string>
-    <string name="desc_op_sqrt" msgid="6882241473601721767">"raiz quadrada"</string>
-    <string name="desc_op_sub" msgid="2744940875059679129">"menos"</string>
-    <string name="desc_mode_deg" msgid="4129831241246511710">"modo grau"</string>
-    <string name="desc_mode_rad" msgid="730135521908627673">"modo radiano"</string>
-    <string name="desc_switch_deg" msgid="6675211986921592374">"alterar para graus"</string>
-    <string name="desc_switch_rad" msgid="2845875847488919914">"alterar para radianos"</string>
-    <string name="desc_eq" msgid="3349320880874699285">"igual"</string>
-    <string name="desc_clr" msgid="737502124268890797">"limpar"</string>
-    <string name="desc_del" msgid="7879515781929311432">"excluir"</string>
-    <string name="desc_inv_off" msgid="3672218250519616068">"mostrar funções inversas"</string>
-    <string name="desc_inv_on" msgid="2515675590767677178">"ocultar funções inversas"</string>
-    <string name="error_nan" msgid="5674077944929888710">"Não é número"</string>
-    <string name="error_syntax" msgid="4786987111228645602">"Expressão ruim"</string>
-    <string name="error_aborted" msgid="3402238176316342537">"Cancelada"</string>
-    <string name="error_overflow" msgid="7800547394563434764">"Infinito?"</string>
-    <string name="error_zero_divide" msgid="458040988686661801">"Impos. dividir por 0"</string>
-    <string name="text_copied_toast" msgid="2104264466812485425">"Texto copiado"</string>
-    <string name="cancelled" msgid="1618889609742874632">"Cálculo cancelado"</string>
-    <string name="timeout" msgid="802690170415591535">"Tempo limite atingido. O valor pode ser infinito ou indefinido."</string>
-    <string name="ok_remove_timeout" msgid="8344529153919268011">"Usar tempos limite mais longos"</string>
-    <string name="dismiss" msgid="7872443888515066216">"Dispensar"</string>
-    <string name="exact" msgid="2597237880041789948">"(exato)"</string>
-    <string name="approximate" msgid="7117143366610670836">"(±1 no último dígito)"</string>
-    <string name="menu_leading" msgid="2338520833272667740">"Resposta c/ dígitos iniciais"</string>
-    <string name="menu_fraction" msgid="1247477377840252234">"Responder como fração"</string>
-    <string name="menu_licenses" msgid="1890541368064108592">"Lic. cód. aberto"</string>
-</resources>
diff --git a/res/values-pt-rPT/strings.xml b/res/values-pt-rPT/strings.xml
index 85fb586..36930b2 100644
--- a/res/values-pt-rPT/strings.xml
+++ b/res/values-pt-rPT/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"eliminar"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"mostrar funções inversas"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"ocultar funções inversas"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Sem fórmula"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Números e operações básicas"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Operações avançadas"</string>
     <string name="error_nan" msgid="5674077944929888710">"Não é um núm."</string>
     <string name="error_syntax" msgid="4786987111228645602">"Expr. incorreta"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Interrompida"</string>
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 0a04f55..35dbf09 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -18,7 +18,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_name" msgid="1756503303878374382">"Calculadora"</string>
-    <string name="dec_point" msgid="8920102493070918054">","</string>
+    <string name="dec_point" msgid="8920102493070918054">"."</string>
     <string name="mode_deg" msgid="1146123354434332479">"graus"</string>
     <string name="mode_rad" msgid="1434228830085760996">"rad"</string>
     <string name="clr" msgid="6730945431543327337">"limp"</string>
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"excluir"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"mostrar funções inversas"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"ocultar funções inversas"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Nenhuma fórmula"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Números e operações básicas"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Operações avançadas"</string>
     <string name="error_nan" msgid="5674077944929888710">"Não é número"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Expressão ruim"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Cancelada"</string>
diff --git a/res/values-ro/strings.xml b/res/values-ro/strings.xml
index 9e3027b..1bcd940 100644
--- a/res/values-ro/strings.xml
+++ b/res/values-ro/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"șterge"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"afișează inversele funcțiilor"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"ascunde inversele funcțiilor"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Nicio formulă"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Numere și operații de bază"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Operații avansate"</string>
     <string name="error_nan" msgid="5674077944929888710">"Nu este număr"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Expresie greșită"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Anulat"</string>
diff --git a/res/values-ru/strings.xml b/res/values-ru/strings.xml
index 8116280..f7ad547 100644
--- a/res/values-ru/strings.xml
+++ b/res/values-ru/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"удалить"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"показать обратные функции"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"скрыть обратные функции"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Нет формулы"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Числа и основные операции"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Сложные операции"</string>
     <string name="error_nan" msgid="5674077944929888710">"Не число"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Неправ. выраж-е"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Отменено"</string>
diff --git a/res/values-si-rLK/strings.xml b/res/values-si-rLK/strings.xml
index 081c007..3e5843b 100644
--- a/res/values-si-rLK/strings.xml
+++ b/res/values-si-rLK/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"මකන්න"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"ප්‍රතිලෝම ශ්‍රිත පෙන්වන්න"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"ප්‍රතිලෝම ශ්‍රිත සඟවන්න"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"සූත්‍රයක් නැත"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"අංක සහ මූලික මෙහෙයුම්"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"උසස් මෙහෙයුම්"</string>
     <string name="error_nan" msgid="5674077944929888710">"අංකයක් නොවේ"</string>
     <string name="error_syntax" msgid="4786987111228645602">"වැරදි ප්‍රකාශනයක්"</string>
     <string name="error_aborted" msgid="3402238176316342537">"නතර කෙරිණි"</string>
diff --git a/res/values-sk/strings.xml b/res/values-sk/strings.xml
index 4b56384..8d65412 100644
--- a/res/values-sk/strings.xml
+++ b/res/values-sk/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"odstrániť"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"zobraziť inverzné funkcie"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"skryť inverzné funkcie"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Žiadny vzorec"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Čísla a základné operácie"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Pokročilé operácie"</string>
     <string name="error_nan" msgid="5674077944929888710">"Nie je číslo"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Chybný výraz"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Prerušené"</string>
diff --git a/res/values-sl/strings.xml b/res/values-sl/strings.xml
index 187dd9a..2741715 100644
--- a/res/values-sl/strings.xml
+++ b/res/values-sl/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"izbriši"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"prikaz inverznih funkcij"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"skritje inverznih funkcij"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Ni formule"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Številke in osnovni postopki"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Napredni postopki"</string>
     <string name="error_nan" msgid="5674077944929888710">"Ni število"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Napačen izraz"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Preklicano"</string>
diff --git a/res/values-sq-rAL/strings.xml b/res/values-sq-rAL/strings.xml
index 76833ee..2fac45d 100644
--- a/res/values-sq-rAL/strings.xml
+++ b/res/values-sq-rAL/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"fshi"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"shfaq funksionet e kundërta"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"fshih funksionet e kundërta"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Nuk ka formulë"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Numrat dhe veprimet bazë"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Veprimet e përparuara"</string>
     <string name="error_nan" msgid="5674077944929888710">"S\'është numër"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Shprehje e pasaktë"</string>
     <string name="error_aborted" msgid="3402238176316342537">"U ndërpre"</string>
diff --git a/res/values-sr/strings.xml b/res/values-sr/strings.xml
index 15fe0aa..f05ae7b 100644
--- a/res/values-sr/strings.xml
+++ b/res/values-sr/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"избриши"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"прикажи инверзне функције"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"сакриј инверзне функције"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Нема формуле"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Бројеви и основне операције"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Напредне операције"</string>
     <string name="error_nan" msgid="5674077944929888710">"Није број"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Неисправан израз"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Отказано је"</string>
diff --git a/res/values-sv/strings.xml b/res/values-sv/strings.xml
index d4c35fe..7d0369e 100644
--- a/res/values-sv/strings.xml
+++ b/res/values-sv/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"radera"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"visa inverterade funktioner"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"dölj inverterade funktioner"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Ingen formel"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Siffror och grundläggande åtgärder"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Avancerade åtgärder"</string>
     <string name="error_nan" msgid="5674077944929888710">"Inte ett tal"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Felaktigt uttryck"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Avbruten"</string>
diff --git a/res/values-sw/strings.xml b/res/values-sw/strings.xml
index 2225162..c624652 100644
--- a/res/values-sw/strings.xml
+++ b/res/values-sw/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"futa"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"Onyesha chaguo za kukokotoa za kinyume"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"ficha chaguo za kukokotoa za kinyume"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Hakuna fomula"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Nambari na utendaji wa msingi"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Utendaji wa kina"</string>
     <string name="error_nan" msgid="5674077944929888710">"Si nambari"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Hesabu yenye hitilafu"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Imeghairiwa"</string>
diff --git a/res/values-sw400dp-land/styles.xml b/res/values-sw400dp-land/styles.xml
deleted file mode 100644
index e37e8a0..0000000
--- a/res/values-sw400dp-land/styles.xml
+++ /dev/null
@@ -1,61 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  Copyright (C) 2014 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.
-  -->
-
-<!-- Styles for landscape phone (e.g. Nexus 4/5). -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <style name="DisplayTextStyle.Formula">
-        <item name="android:paddingTop">2dip</item>
-        <item name="android:paddingBottom">10dip</item>
-        <item name="android:paddingStart">36dip</item>
-        <item name="android:paddingEnd">36dip</item>
-        <item name="android:textSize">32sp</item>
-    </style>
-
-    <style name="DisplayTextStyle.Result">
-        <item name="android:paddingTop">12dip</item>
-        <item name="android:paddingBottom">18dip</item>
-        <item name="android:paddingStart">36dip</item>
-        <item name="android:paddingEnd">36dip</item>
-        <item name="android:textSize">32sp</item>
-    </style>
-
-    <style name="PadButtonStyle.Advanced">
-        <item name="android:background">@drawable/pad_button_advanced_background</item>
-        <item name="android:textColor">@color/pad_button_advanced_text_color</item>
-        <item name="android:textSize">17sp</item>
-    </style>
-
-    <style name="PadButtonStyle.Advanced.Text">
-        <item name="android:textAllCaps">true</item>
-        <item name="android:textSize">15sp</item>
-    </style>
-
-    <style name="PadButtonStyle.Numeric">
-        <item name="android:textSize">27sp</item>
-    </style>
-
-    <style name="PadButtonStyle.Operator">
-        <item name="android:textSize">24sp</item>
-    </style>
-
-    <style name="PadButtonStyle.Operator.Text">
-        <item name="android:textAllCaps">true</item>
-        <item name="android:textSize">15sp</item>
-    </style>
-
-</resources>
diff --git a/res/values-sw400dp-port/styles.xml b/res/values-sw400dp-port/styles.xml
deleted file mode 100644
index c715d52..0000000
--- a/res/values-sw400dp-port/styles.xml
+++ /dev/null
@@ -1,63 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  Copyright (C) 2014 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.
-  -->
-
-<!-- Styles for portrait phone (e.g. Nexus 4/5). -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <style name="DisplayTextStyle.Formula">
-        <item name="android:paddingTop">16dip</item>
-        <item name="android:paddingBottom">28dip</item>
-        <item name="android:paddingStart">16dip</item>
-        <item name="android:paddingEnd">16dip</item>
-        <item name="minTextSize">42sp</item>
-        <item name="maxTextSize">74sp</item>
-        <item name="stepTextSize">8sp</item>
-    </style>
-
-    <style name="DisplayTextStyle.Result">
-        <item name="android:paddingTop">16dip</item>
-        <item name="android:paddingBottom">42dip</item>
-        <item name="android:paddingStart">16dip</item>
-        <item name="android:paddingEnd">16dip</item>
-        <item name="android:textSize">42sp</item>
-    </style>
-
-    <style name="PadButtonStyle.Advanced">
-        <item name="android:background">@drawable/pad_button_advanced_background</item>
-        <item name="android:textColor">@color/pad_button_advanced_text_color</item>
-        <item name="android:textSize">23sp</item>
-    </style>
-
-    <style name="PadButtonStyle.Advanced.Text">
-        <item name="android:textAllCaps">true</item>
-        <item name="android:textSize">20sp</item>
-    </style>
-
-    <style name="PadButtonStyle.Numeric">
-        <item name="android:textSize">36sp</item>
-    </style>
-
-    <style name="PadButtonStyle.Operator">
-        <item name="android:textSize">27sp</item>
-    </style>
-
-    <style name="PadButtonStyle.Operator.Text">
-        <item name="android:textAllCaps">true</item>
-        <item name="android:textSize">20sp</item>
-    </style>
-
-</resources>
diff --git a/res/values-sw600dp-land/styles.xml b/res/values-sw600dp-land/styles.xml
deleted file mode 100644
index d25fe6b..0000000
--- a/res/values-sw600dp-land/styles.xml
+++ /dev/null
@@ -1,92 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  Copyright (C) 2014 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.
-  -->
-
-<!-- Styles for landscape 600dip-wide tablet (e.g. Nexus 7). -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <style name="DisplayTextStyle.Formula">
-        <item name="android:paddingTop">4dip</item>
-        <item name="android:paddingBottom">16dip</item>
-        <item name="android:paddingStart">44dip</item>
-        <item name="android:paddingEnd">44dip</item>
-        <item name="android:textSize">42sp</item>
-    </style>
-
-    <style name="DisplayTextStyle.Result">
-        <item name="android:paddingTop">18dip</item>
-        <item name="android:paddingBottom">30dip</item>
-        <item name="android:paddingStart">44dip</item>
-        <item name="android:paddingEnd">44dip</item>
-        <item name="android:textSize">42sp</item>
-    </style>
-
-    <style name="PadButtonStyle.Advanced">
-        <item name="android:background">@drawable/pad_button_advanced_background</item>
-        <item name="android:textColor">@color/pad_button_advanced_text_color</item>
-        <item name="android:textSize">27sp</item>
-    </style>
-
-    <style name="PadButtonStyle.Advanced.Text">
-        <item name="android:textAllCaps">true</item>
-        <item name="android:textSize">24sp</item>
-    </style>
-
-    <style name="PadButtonStyle.Numeric">
-        <item name="android:textSize">36sp</item>
-    </style>
-
-    <style name="PadButtonStyle.Numeric.Equals">
-        <item name="android:visibility">gone</item>
-    </style>
-
-    <style name="PadButtonStyle.Operator">
-        <item name="android:textSize">36sp</item>
-    </style>
-
-    <style name="PadButtonStyle.Operator.Text">
-        <item name="android:textAllCaps">true</item>
-        <item name="android:textSize">24sp</item>
-    </style>
-
-    <style name="PadLayoutStyle.Advanced">
-        <item name="android:layout_width">0dip</item>
-        <item name="android:layout_weight">500</item>
-        <item name="android:paddingTop">10dip</item>
-        <item name="android:paddingBottom">10dip</item>
-        <item name="android:paddingStart">18dip</item>
-        <item name="android:paddingEnd">18dip</item>
-    </style>
-
-    <style name="PadLayoutStyle.Numeric">
-        <item name="android:layout_width">0dip</item>
-        <item name="android:layout_weight">500</item>
-        <item name="android:paddingTop">10dip</item>
-        <item name="android:paddingBottom">10dip</item>
-        <item name="android:paddingStart">18dip</item>
-        <item name="android:paddingEnd">18dip</item>
-    </style>
-
-    <style name="PadLayoutStyle.Operator">
-        <item name="android:layout_width">0dip</item>
-        <item name="android:layout_weight">280</item>
-        <item name="android:paddingTop">10dip</item>
-        <item name="android:paddingBottom">10dip</item>
-        <item name="android:paddingStart">18dip</item>
-        <item name="android:paddingEnd">18dip</item>
-    </style>
-
-</resources>
diff --git a/res/values-sw768dp-land/styles.xml b/res/values-sw768dp-land/styles.xml
deleted file mode 100644
index 162e5bc..0000000
--- a/res/values-sw768dp-land/styles.xml
+++ /dev/null
@@ -1,63 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  Copyright (C) 2014 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.
-  -->
-
-<!-- Styles for landscape 800dip-wide tablet (e.g. Nexus 10). -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <style name="DisplayTextStyle.Formula">
-        <item name="android:paddingTop">16dip</item>
-        <item name="android:paddingBottom">32dip</item>
-        <item name="android:paddingStart">44dip</item>
-        <item name="android:paddingEnd">44dip</item>
-        <item name="minTextSize">56sp</item>
-        <item name="maxTextSize">72sp</item>
-        <item name="stepTextSize">8sp</item>
-    </style>
-
-    <style name="DisplayTextStyle.Result">
-        <item name="android:paddingTop">20dip</item>
-        <item name="android:paddingBottom">48dip</item>
-        <item name="android:paddingStart">44dip</item>
-        <item name="android:paddingEnd">44dip</item>
-        <item name="android:textSize">56sp</item>
-    </style>
-
-    <style name="PadButtonStyle.Advanced">
-        <item name="android:background">@drawable/pad_button_advanced_background</item>
-        <item name="android:textColor">@color/pad_button_advanced_text_color</item>
-        <item name="android:textSize">30sp</item>
-    </style>
-
-    <style name="PadButtonStyle.Advanced.Text">
-        <item name="android:textAllCaps">true</item>
-        <item name="android:textSize">26sp</item>
-    </style>
-
-    <style name="PadButtonStyle.Numeric">
-        <item name="android:textSize">36sp</item>
-    </style>
-
-    <style name="PadButtonStyle.Operator">
-        <item name="android:textSize">36sp</item>
-    </style>
-
-    <style name="PadButtonStyle.Operator.Text">
-        <item name="android:textAllCaps">true</item>
-        <item name="android:textSize">26sp</item>
-    </style>
-
-</resources>
diff --git a/res/values-sw768dp-port/styles.xml b/res/values-sw768dp-port/styles.xml
deleted file mode 100644
index c9bceba..0000000
--- a/res/values-sw768dp-port/styles.xml
+++ /dev/null
@@ -1,63 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  Copyright (C) 2014 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.
-  -->
-
-<!-- Styles for portrait 800dip-wide tablet (e.g. Nexus 10). -->
-<resources xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <style name="DisplayTextStyle.Formula">
-        <item name="android:paddingTop">24dip</item>
-        <item name="android:paddingBottom">32dip</item>
-        <item name="android:paddingStart">44dip</item>
-        <item name="android:paddingEnd">44dip</item>
-        <item name="minTextSize">56sp</item>
-        <item name="maxTextSize">80sp</item>
-        <item name="stepTextSize">8sp</item>
-    </style>
-
-    <style name="DisplayTextStyle.Result">
-        <item name="android:paddingTop">24dip</item>
-        <item name="android:paddingBottom">56dip</item>
-        <item name="android:paddingStart">44dip</item>
-        <item name="android:paddingEnd">44dip</item>
-        <item name="android:textSize">56sp</item>
-    </style>
-
-    <style name="PadButtonStyle.Advanced">
-        <item name="android:background">@drawable/pad_button_advanced_background</item>
-        <item name="android:textColor">@color/pad_button_advanced_text_color</item>
-        <item name="android:textSize">32sp</item>
-    </style>
-
-    <style name="PadButtonStyle.Advanced.Text">
-        <item name="android:textAllCaps">true</item>s
-        <item name="android:textSize">28sp</item>
-    </style>
-
-    <style name="PadButtonStyle.Numeric">
-        <item name="android:textSize">38sp</item>
-    </style>
-
-    <style name="PadButtonStyle.Operator">
-        <item name="android:textSize">38sp</item>
-    </style>
-
-    <style name="PadButtonStyle.Operator.Text">
-        <item name="android:textAllCaps">true</item>
-        <item name="android:textSize">28sp</item>
-    </style>
-
-</resources>
diff --git a/res/values-sw800dp-port/layout.xml b/res/values-sw800dp-port/layout.xml
deleted file mode 100644
index 46d71cb..0000000
--- a/res/values-sw800dp-port/layout.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  Copyright (C) 2014 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.
-  -->
-
-<resources>
-    <item name="activity_calculator" type="layout">@layout/activity_calculator_tablet_port</item>
-</resources>
\ No newline at end of file
diff --git a/res/values-ta-rIN/strings.xml b/res/values-ta-rIN/strings.xml
index 2e044e2..e386cb5 100644
--- a/res/values-ta-rIN/strings.xml
+++ b/res/values-ta-rIN/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"நீக்கு"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"நேர்மாறு சார்புகளைக் காட்டு"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"நேர்மாறு சார்புகளை மறை"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"சூத்திரம் இல்லை"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"எண்களும் அடிப்படை செயல்பாடுகளும்"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"மேம்பட்ட செயல்பாடுகள்"</string>
     <string name="error_nan" msgid="5674077944929888710">"எண் அல்ல"</string>
     <string name="error_syntax" msgid="4786987111228645602">"தவறான கோவை"</string>
     <string name="error_aborted" msgid="3402238176316342537">"நிறுத்தப்பட்டது"</string>
diff --git a/res/values-te-rIN/strings.xml b/res/values-te-rIN/strings.xml
index 73c5096..82f22b0 100644
--- a/res/values-te-rIN/strings.xml
+++ b/res/values-te-rIN/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"తొలగించు"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"విలోమ ఫంక్షన్‌లను చూపుతుంది"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"విలోమ ఫంక్షన్‌లను దాస్తుంది"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"సూత్రం లేదు"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"సంఖ్యలు మరియు ప్రాథమిక చర్యలు"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"అధునాతన చర్యలు"</string>
     <string name="error_nan" msgid="5674077944929888710">"సంఖ్య కాదు"</string>
     <string name="error_syntax" msgid="4786987111228645602">"తప్పు వ్యక్తీకరణ"</string>
     <string name="error_aborted" msgid="3402238176316342537">"రద్దు చేసారు"</string>
diff --git a/res/values-th/strings.xml b/res/values-th/strings.xml
index 28641af..40b84ff 100644
--- a/res/values-th/strings.xml
+++ b/res/values-th/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"ลบ"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"แสดงฟังก์ชันผกผัน"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"ซ่อนฟังก์ชันผกผัน"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"ไม่มีสูตร"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"แป้นตัวเลขและการคำนวนขั้นต้น"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"การใช้งานขั้นสูง"</string>
     <string name="error_nan" msgid="5674077944929888710">"ไม่ใช่ตัวเลข"</string>
     <string name="error_syntax" msgid="4786987111228645602">"นิพจน์ไม่ถูกต้อง"</string>
     <string name="error_aborted" msgid="3402238176316342537">"ล้มเลิกแล้ว"</string>
diff --git a/res/values-tl/strings.xml b/res/values-tl/strings.xml
index 8cb141a..a3659c8 100644
--- a/res/values-tl/strings.xml
+++ b/res/values-tl/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"i-delete"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"ipakita ang mga inverse function"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"itago ang mga inverse function"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Walang formula"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Mga numero at basic na pagpapatakbo"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Mga advanced na pagpapatakbo"</string>
     <string name="error_nan" msgid="5674077944929888710">"Hindi numero"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Bad expression"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Itinigil"</string>
diff --git a/res/values-tr/strings.xml b/res/values-tr/strings.xml
index 996a2df..d50ed11 100644
--- a/res/values-tr/strings.xml
+++ b/res/values-tr/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"sil"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"ters fonksiyonları göster"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"ters fonksiyonları gizle"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Formül yok"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Sayılar ve temel işlemler"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Gelişmiş işlemler"</string>
     <string name="error_nan" msgid="5674077944929888710">"Sayı değil"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Hatalı ifade"</string>
     <string name="error_aborted" msgid="3402238176316342537">"İptal edildi"</string>
diff --git a/res/values-uk/strings.xml b/res/values-uk/strings.xml
index c98ede1..4a5fa70 100755
--- a/res/values-uk/strings.xml
+++ b/res/values-uk/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"видалити"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"показати обернені функції"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"сховати обернені функції"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Немає формули"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Числа й основні операції"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Додаткові операції"</string>
     <string name="error_nan" msgid="5674077944929888710">"Не число"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Невірний вираз"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Скасовано"</string>
diff --git a/res/values-ur-rPK/strings.xml b/res/values-ur-rPK/strings.xml
index ff7119a..198ed61 100644
--- a/res/values-ur-rPK/strings.xml
+++ b/res/values-ur-rPK/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"حذف کریں"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"معکوس فنکشنز دکھائیں"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"معکوس فنکشنز چھپائیں"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"کوئی فارمولہ نہیں"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"اعداد اور بنیادی کاروائیاں"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"اعلی کاروا‏ئیاں"</string>
     <string name="error_nan" msgid="5674077944929888710">"نمبر نہیں ہے"</string>
     <string name="error_syntax" msgid="4786987111228645602">"غلط ایکسپریشن"</string>
     <string name="error_aborted" msgid="3402238176316342537">"ساقط کر دیا گیا"</string>
diff --git a/res/values-uz-rUZ/strings.xml b/res/values-uz-rUZ/strings.xml
index 24ed74d..4d45843 100644
--- a/res/values-uz-rUZ/strings.xml
+++ b/res/values-uz-rUZ/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"o‘chirish"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"teskari funksiyalarni ko‘rsatish"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"teskari funksiyalarni berkitish"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Formula yo‘q"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Sonlar va asosiy amallar"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Qo‘shimcha amallar"</string>
     <string name="error_nan" msgid="5674077944929888710">"Haqiqiy son emas"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Noto‘g‘ri ifoda"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Bekor qilindi"</string>
diff --git a/res/values-vi/strings.xml b/res/values-vi/strings.xml
index a12368d..940b3d5 100644
--- a/res/values-vi/strings.xml
+++ b/res/values-vi/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"gạch bỏ"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"hiển thị hàm nghịch"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"ẩn hàm nghịch"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Không có công thức"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Số và phép tính cơ bản"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Phép tính nâng cao"</string>
     <string name="error_nan" msgid="5674077944929888710">"Không phải số"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Biểu thức không hợp lệ"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Đã hủy bỏ"</string>
diff --git a/res/values-port/layout.xml b/res/values-w230dp-h220dp/layout.xml
similarity index 71%
copy from res/values-port/layout.xml
copy to res/values-w230dp-h220dp/layout.xml
index 99ea0b8..0e698d5 100644
--- a/res/values-port/layout.xml
+++ b/res/values-w230dp-h220dp/layout.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  Copyright (C) 2014 The Android Open Source Project
+  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.
@@ -17,4 +17,7 @@
 
 <resources>
     <item name="activity_calculator" type="layout">@layout/activity_calculator_port</item>
-</resources>
\ No newline at end of file
+    <item name="display" type="layout">@layout/display_one_line</item>
+    <item name="pad_operator" type="layout">@layout/pad_operator_one_col</item>
+    <item name="pad_advanced" type="layout">@layout/pad_advanced_4x4</item>
+</resources>
diff --git a/res/values-w230dp-h220dp/styles.xml b/res/values-w230dp-h220dp/styles.xml
new file mode 100644
index 0000000..88a52ce
--- /dev/null
+++ b/res/values-w230dp-h220dp/styles.xml
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <style name="DisplayTextStyle.Formula">
+        <item name="android:paddingTop">0dip</item>
+        <item name="android:paddingBottom">0dip</item>
+        <item name="android:paddingStart">16dip</item>
+        <item name="android:paddingEnd">16dip</item>
+        <item name="android:gravity">bottom</item>
+        <item name="minTextSize">28dip</item>
+        <item name="maxTextSize">28dip</item>
+        <item name="stepTextSize">8dip</item>
+    </style>
+
+    <style name="DisplayTextStyle.Result">
+        <item name="android:paddingTop">0dip</item>
+        <item name="android:paddingBottom">0dip</item>
+        <item name="android:paddingStart">16dip</item>
+        <item name="android:paddingEnd">16dip</item>
+        <item name="android:gravity">bottom</item>
+        <item name="android:textSize">28dip</item>
+    </style>
+
+    <style name="PadButtonStyle.Advanced">
+        <item name="android:background">@drawable/pad_button_advanced_background</item>
+        <item name="android:textColor">@color/pad_button_advanced_text_color</item>
+        <item name="android:textSize">14dip</item>
+    </style>
+
+    <style name="PadButtonStyle.Advanced.Text">
+        <item name="android:textAllCaps">true</item>
+        <item name="android:textSize">12dip</item>
+    </style>
+
+    <style name="PadButtonStyle.Numeric">
+        <item name="android:textSize">16dip</item>
+    </style>
+
+    <style name="PadButtonStyle.Numeric.Equals">
+        <item name="android:visibility">visible</item>
+    </style>
+
+    <style name="PadButtonStyle.Operator">
+        <item name="android:textSize">14dip</item>
+    </style>
+
+    <style name="PadButtonStyle.Operator.Text">
+        <item name="android:textAllCaps">true</item>
+        <item name="android:textSize">12dip</item>
+    </style>
+
+    <style name="PadLayoutStyle.Advanced">
+        <item name="android:elevation">4dip</item>
+        <item name="android:paddingTop">2dip</item>
+        <item name="android:paddingBottom">8dip</item>
+        <item name="android:paddingStart">18dip</item>
+        <item name="android:paddingEnd">18dip</item>
+    </style>
+
+    <style name="PadLayoutStyle.Numeric">
+        <item name="android:layout_width">0dip</item>
+        <item name="android:layout_weight">7</item>
+        <item name="android:paddingTop">2dip</item>
+        <item name="android:paddingBottom">8dip</item>
+        <item name="android:paddingStart">8dip</item>
+        <item name="android:paddingEnd">8dip</item>
+    </style>
+
+    <style name="PadLayoutStyle.Operator">
+        <item name="android:layout_width">0dip</item>
+        <item name="android:layout_weight">3</item>
+        <item name="android:paddingTop">2dip</item>
+        <item name="android:paddingBottom">8dip</item>
+        <item name="android:paddingStart">4dip</item>
+        <item name="android:paddingEnd">28dip</item>
+    </style>
+
+</resources>
diff --git a/res/values-port/layout.xml b/res/values-w230dp-h275dp/layout.xml
similarity index 71%
copy from res/values-port/layout.xml
copy to res/values-w230dp-h275dp/layout.xml
index 99ea0b8..f9a2d97 100644
--- a/res/values-port/layout.xml
+++ b/res/values-w230dp-h275dp/layout.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  Copyright (C) 2014 The Android Open Source Project
+  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.
@@ -17,4 +17,7 @@
 
 <resources>
     <item name="activity_calculator" type="layout">@layout/activity_calculator_port</item>
-</resources>
\ No newline at end of file
+    <item name="display" type="layout">@layout/display_two_line</item>
+    <item name="pad_operator" type="layout">@layout/pad_operator_one_col</item>
+    <item name="pad_advanced" type="layout">@layout/pad_advanced_4x4</item>
+</resources>
diff --git a/res/values-w230dp-h275dp/styles.xml b/res/values-w230dp-h275dp/styles.xml
new file mode 100644
index 0000000..2f715a3
--- /dev/null
+++ b/res/values-w230dp-h275dp/styles.xml
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <style name="DisplayTextStyle.Formula">
+        <item name="android:paddingTop">0dip</item>
+        <item name="android:paddingBottom">0dip</item>
+        <item name="android:paddingStart">16dip</item>
+        <item name="android:paddingEnd">16dip</item>
+        <item name="android:gravity">bottom</item>
+        <item name="minTextSize">28dip</item>
+        <item name="maxTextSize">28dip</item>
+        <item name="stepTextSize">8dip</item>
+    </style>
+
+    <style name="DisplayTextStyle.Result">
+        <item name="android:paddingTop">0dip</item>
+        <item name="android:paddingBottom">0dip</item>
+        <item name="android:paddingStart">16dip</item>
+        <item name="android:paddingEnd">16dip</item>
+        <item name="android:gravity">bottom</item>
+        <item name="android:textSize">28dip</item>
+    </style>
+
+    <style name="PadButtonStyle.Advanced">
+        <item name="android:background">@drawable/pad_button_advanced_background</item>
+        <item name="android:textColor">@color/pad_button_advanced_text_color</item>
+        <item name="android:textSize">16dip</item>
+    </style>
+
+    <style name="PadButtonStyle.Advanced.Text">
+        <item name="android:textAllCaps">true</item>
+        <item name="android:textSize">14dip</item>
+    </style>
+
+    <style name="PadButtonStyle.Numeric">
+        <item name="android:textSize">18dip</item>
+    </style>
+
+    <style name="PadButtonStyle.Numeric.Equals">
+        <item name="android:visibility">visible</item>
+    </style>
+
+    <style name="PadButtonStyle.Operator">
+        <item name="android:textSize">16dip</item>
+    </style>
+
+    <style name="PadButtonStyle.Operator.Text">
+        <item name="android:textAllCaps">true</item>
+        <item name="android:textSize">14dip</item>
+    </style>
+
+    <style name="PadLayoutStyle.Advanced">
+        <item name="android:elevation">4dip</item>
+        <item name="android:paddingTop">2dip</item>
+        <item name="android:paddingBottom">8dip</item>
+        <item name="android:paddingStart">18dip</item>
+        <item name="android:paddingEnd">18dip</item>
+    </style>
+
+    <style name="PadLayoutStyle.Numeric">
+        <item name="android:layout_width">0dip</item>
+        <item name="android:layout_weight">7</item>
+        <item name="android:paddingTop">2dip</item>
+        <item name="android:paddingBottom">8dip</item>
+        <item name="android:paddingStart">8dip</item>
+        <item name="android:paddingEnd">8dip</item>
+    </style>
+
+    <style name="PadLayoutStyle.Operator">
+        <item name="android:layout_width">0dip</item>
+        <item name="android:layout_weight">3</item>
+        <item name="android:paddingTop">2dip</item>
+        <item name="android:paddingBottom">8dip</item>
+        <item name="android:paddingStart">4dip</item>
+        <item name="android:paddingEnd">28dip</item>
+    </style>
+
+</resources>
diff --git a/res/values-port/layout.xml b/res/values-w230dp-h375dp/layout.xml
similarity index 71%
copy from res/values-port/layout.xml
copy to res/values-w230dp-h375dp/layout.xml
index 99ea0b8..f9a2d97 100644
--- a/res/values-port/layout.xml
+++ b/res/values-w230dp-h375dp/layout.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  Copyright (C) 2014 The Android Open Source Project
+  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.
@@ -17,4 +17,7 @@
 
 <resources>
     <item name="activity_calculator" type="layout">@layout/activity_calculator_port</item>
-</resources>
\ No newline at end of file
+    <item name="display" type="layout">@layout/display_two_line</item>
+    <item name="pad_operator" type="layout">@layout/pad_operator_one_col</item>
+    <item name="pad_advanced" type="layout">@layout/pad_advanced_4x4</item>
+</resources>
diff --git a/res/values-port/styles.xml b/res/values-w230dp-h375dp/styles.xml
similarity index 61%
copy from res/values-port/styles.xml
copy to res/values-w230dp-h375dp/styles.xml
index a6d2013..4aa32d8 100644
--- a/res/values-port/styles.xml
+++ b/res/values-w230dp-h375dp/styles.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  Copyright (C) 2014 The Android Open Source Project
+  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.
@@ -15,40 +15,39 @@
   limitations under the License.
   -->
 
-<!-- Styles for portrait phone (e.g. Nexus 4/5). -->
 <resources xmlns:android="http://schemas.android.com/apk/res/android">
 
     <style name="DisplayTextStyle.Formula">
-        <item name="android:paddingTop">8dip</item>
-        <item name="android:paddingBottom">24dip</item>
+        <item name="android:paddingTop">2dip</item>
+        <item name="android:paddingBottom">10dip</item>
         <item name="android:paddingStart">16dip</item>
         <item name="android:paddingEnd">16dip</item>
-        <item name="minTextSize">32sp</item>
-        <item name="maxTextSize">56sp</item>
-        <item name="stepTextSize">8sp</item>
+        <item name="minTextSize">32dip</item>
+        <item name="maxTextSize">32dip</item>
+        <item name="stepTextSize">8dip</item>
     </style>
 
     <style name="DisplayTextStyle.Result">
-        <item name="android:paddingTop">10dip</item>
-        <item name="android:paddingBottom">32dip</item>
+        <item name="android:paddingTop">12dip</item>
+        <item name="android:paddingBottom">18dip</item>
         <item name="android:paddingStart">16dip</item>
         <item name="android:paddingEnd">16dip</item>
-        <item name="android:textSize">32sp</item>
+        <item name="android:textSize">32dip</item>
     </style>
 
     <style name="PadButtonStyle.Advanced">
         <item name="android:background">@drawable/pad_button_advanced_background</item>
         <item name="android:textColor">@color/pad_button_advanced_text_color</item>
-        <item name="android:textSize">20sp</item>
+        <item name="android:textSize">17dip</item>
     </style>
 
     <style name="PadButtonStyle.Advanced.Text">
         <item name="android:textAllCaps">true</item>
-        <item name="android:textSize">17sp</item>
+        <item name="android:textSize">15dip</item>
     </style>
 
     <style name="PadButtonStyle.Numeric">
-        <item name="android:textSize">32sp</item>
+        <item name="android:textSize">27dip</item>
     </style>
 
     <style name="PadButtonStyle.Numeric.Equals">
@@ -56,36 +55,36 @@
     </style>
 
     <style name="PadButtonStyle.Operator">
-        <item name="android:textSize">23sp</item>
+        <item name="android:textSize">24dip</item>
     </style>
 
     <style name="PadButtonStyle.Operator.Text">
         <item name="android:textAllCaps">true</item>
-        <item name="android:textSize">15sp</item>
+        <item name="android:textSize">15dip</item>
     </style>
 
     <style name="PadLayoutStyle.Advanced">
         <item name="android:elevation">4dip</item>
-        <item name="android:paddingTop">12dip</item>
-        <item name="android:paddingBottom">20dip</item>
-        <item name="android:paddingStart">20dip</item>
-        <item name="android:paddingEnd">20dip</item>
+        <item name="android:paddingTop">2dip</item>
+        <item name="android:paddingBottom">8dip</item>
+        <item name="android:paddingStart">18dip</item>
+        <item name="android:paddingEnd">18dip</item>
     </style>
 
     <style name="PadLayoutStyle.Numeric">
         <item name="android:layout_width">0dip</item>
-        <item name="android:layout_weight">264</item>
-        <item name="android:paddingTop">12dip</item>
-        <item name="android:paddingBottom">20dip</item>
-        <item name="android:paddingStart">12dip</item>
-        <item name="android:paddingEnd">12dip</item>
+        <item name="android:layout_weight">7</item>
+        <item name="android:paddingTop">2dip</item>
+        <item name="android:paddingBottom">8dip</item>
+        <item name="android:paddingStart">8dip</item>
+        <item name="android:paddingEnd">8dip</item>
     </style>
 
     <style name="PadLayoutStyle.Operator">
         <item name="android:layout_width">0dip</item>
-        <item name="android:layout_weight">96</item>
-        <item name="android:paddingTop">8dip</item>
-        <item name="android:paddingBottom">24dip</item>
+        <item name="android:layout_weight">3</item>
+        <item name="android:paddingTop">2dip</item>
+        <item name="android:paddingBottom">8dip</item>
         <item name="android:paddingStart">4dip</item>
         <item name="android:paddingEnd">28dip</item>
     </style>
diff --git a/res/values-port/layout.xml b/res/values-w230dp-h475dp-port/layout.xml
similarity index 71%
copy from res/values-port/layout.xml
copy to res/values-w230dp-h475dp-port/layout.xml
index 99ea0b8..9f7bd6b 100644
--- a/res/values-port/layout.xml
+++ b/res/values-w230dp-h475dp-port/layout.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  Copyright (C) 2014 The Android Open Source Project
+  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.
@@ -17,4 +17,7 @@
 
 <resources>
     <item name="activity_calculator" type="layout">@layout/activity_calculator_port</item>
-</resources>
\ No newline at end of file
+    <item name="display" type="layout">@layout/display_two_line</item>
+    <item name="pad_operator" type="layout">@layout/pad_operator_one_col</item>
+    <item name="pad_advanced" type="layout">@layout/pad_advanced_3x5</item>
+</resources>
diff --git a/res/values-port/styles.xml b/res/values-w230dp-h475dp-port/styles.xml
similarity index 70%
copy from res/values-port/styles.xml
copy to res/values-w230dp-h475dp-port/styles.xml
index a6d2013..050de1e 100644
--- a/res/values-port/styles.xml
+++ b/res/values-w230dp-h475dp-port/styles.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  Copyright (C) 2014 The Android Open Source Project
+  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.
@@ -15,40 +15,39 @@
   limitations under the License.
   -->
 
-<!-- Styles for portrait phone (e.g. Nexus 4/5). -->
 <resources xmlns:android="http://schemas.android.com/apk/res/android">
 
     <style name="DisplayTextStyle.Formula">
         <item name="android:paddingTop">8dip</item>
-        <item name="android:paddingBottom">24dip</item>
+        <item name="android:paddingBottom">18dip</item>
         <item name="android:paddingStart">16dip</item>
         <item name="android:paddingEnd">16dip</item>
-        <item name="minTextSize">32sp</item>
-        <item name="maxTextSize">56sp</item>
-        <item name="stepTextSize">8sp</item>
+        <item name="minTextSize">32dip</item>
+        <item name="maxTextSize">56dip</item>
+        <item name="stepTextSize">8dip</item>
     </style>
 
     <style name="DisplayTextStyle.Result">
-        <item name="android:paddingTop">10dip</item>
-        <item name="android:paddingBottom">32dip</item>
+        <item name="android:paddingTop">18dip</item>
+        <item name="android:paddingBottom">36dip</item>
         <item name="android:paddingStart">16dip</item>
         <item name="android:paddingEnd">16dip</item>
-        <item name="android:textSize">32sp</item>
+        <item name="android:textSize">32dip</item>
     </style>
 
     <style name="PadButtonStyle.Advanced">
         <item name="android:background">@drawable/pad_button_advanced_background</item>
         <item name="android:textColor">@color/pad_button_advanced_text_color</item>
-        <item name="android:textSize">20sp</item>
+        <item name="android:textSize">20dip</item>
     </style>
 
     <style name="PadButtonStyle.Advanced.Text">
         <item name="android:textAllCaps">true</item>
-        <item name="android:textSize">17sp</item>
+        <item name="android:textSize">17dip</item>
     </style>
 
     <style name="PadButtonStyle.Numeric">
-        <item name="android:textSize">32sp</item>
+        <item name="android:textSize">32dip</item>
     </style>
 
     <style name="PadButtonStyle.Numeric.Equals">
@@ -56,34 +55,34 @@
     </style>
 
     <style name="PadButtonStyle.Operator">
-        <item name="android:textSize">23sp</item>
+        <item name="android:textSize">23dip</item>
     </style>
 
     <style name="PadButtonStyle.Operator.Text">
         <item name="android:textAllCaps">true</item>
-        <item name="android:textSize">15sp</item>
+        <item name="android:textSize">17dip</item>
     </style>
 
     <style name="PadLayoutStyle.Advanced">
         <item name="android:elevation">4dip</item>
         <item name="android:paddingTop">12dip</item>
         <item name="android:paddingBottom">20dip</item>
-        <item name="android:paddingStart">20dip</item>
-        <item name="android:paddingEnd">20dip</item>
+        <item name="android:paddingStart">18dip</item>
+        <item name="android:paddingEnd">18dip</item>
     </style>
 
     <style name="PadLayoutStyle.Numeric">
         <item name="android:layout_width">0dip</item>
-        <item name="android:layout_weight">264</item>
+        <item name="android:layout_weight">7</item>
         <item name="android:paddingTop">12dip</item>
         <item name="android:paddingBottom">20dip</item>
-        <item name="android:paddingStart">12dip</item>
-        <item name="android:paddingEnd">12dip</item>
+        <item name="android:paddingStart">8dip</item>
+        <item name="android:paddingEnd">8dip</item>
     </style>
 
     <style name="PadLayoutStyle.Operator">
         <item name="android:layout_width">0dip</item>
-        <item name="android:layout_weight">96</item>
+        <item name="android:layout_weight">3</item>
         <item name="android:paddingTop">8dip</item>
         <item name="android:paddingBottom">24dip</item>
         <item name="android:paddingStart">4dip</item>
diff --git a/res/values-port/layout.xml b/res/values-w375dp-h220dp/layout.xml
similarity index 71%
rename from res/values-port/layout.xml
rename to res/values-w375dp-h220dp/layout.xml
index 99ea0b8..ac4cde8 100644
--- a/res/values-port/layout.xml
+++ b/res/values-w375dp-h220dp/layout.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  Copyright (C) 2014 The Android Open Source Project
+  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.
@@ -17,4 +17,7 @@
 
 <resources>
     <item name="activity_calculator" type="layout">@layout/activity_calculator_port</item>
-</resources>
\ No newline at end of file
+    <item name="display" type="layout">@layout/display_one_line</item>
+    <item name="pad_operator" type="layout">@layout/pad_operator_two_col</item>
+    <item name="pad_advanced" type="layout">@layout/pad_advanced_4x4</item>
+</resources>
diff --git a/res/values-port/styles.xml b/res/values-w375dp-h220dp/styles.xml
similarity index 61%
copy from res/values-port/styles.xml
copy to res/values-w375dp-h220dp/styles.xml
index a6d2013..3c6fe85 100644
--- a/res/values-port/styles.xml
+++ b/res/values-w375dp-h220dp/styles.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  Copyright (C) 2014 The Android Open Source Project
+  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.
@@ -15,77 +15,78 @@
   limitations under the License.
   -->
 
-<!-- Styles for portrait phone (e.g. Nexus 4/5). -->
 <resources xmlns:android="http://schemas.android.com/apk/res/android">
 
     <style name="DisplayTextStyle.Formula">
-        <item name="android:paddingTop">8dip</item>
-        <item name="android:paddingBottom">24dip</item>
+        <item name="android:paddingTop">0dip</item>
+        <item name="android:paddingBottom">0dip</item>
         <item name="android:paddingStart">16dip</item>
         <item name="android:paddingEnd">16dip</item>
-        <item name="minTextSize">32sp</item>
-        <item name="maxTextSize">56sp</item>
-        <item name="stepTextSize">8sp</item>
+        <item name="android:gravity">bottom</item>
+        <item name="minTextSize">28dip</item>
+        <item name="maxTextSize">28dip</item>
+        <item name="stepTextSize">8dip</item>
     </style>
 
     <style name="DisplayTextStyle.Result">
-        <item name="android:paddingTop">10dip</item>
-        <item name="android:paddingBottom">32dip</item>
+        <item name="android:paddingTop">0dip</item>
+        <item name="android:paddingBottom">0dip</item>
         <item name="android:paddingStart">16dip</item>
         <item name="android:paddingEnd">16dip</item>
-        <item name="android:textSize">32sp</item>
+        <item name="android:gravity">bottom</item>
+        <item name="android:textSize">28dip</item>
     </style>
 
     <style name="PadButtonStyle.Advanced">
         <item name="android:background">@drawable/pad_button_advanced_background</item>
         <item name="android:textColor">@color/pad_button_advanced_text_color</item>
-        <item name="android:textSize">20sp</item>
+        <item name="android:textSize">14dip</item>
     </style>
 
     <style name="PadButtonStyle.Advanced.Text">
         <item name="android:textAllCaps">true</item>
-        <item name="android:textSize">17sp</item>
+        <item name="android:textSize">12dip</item>
     </style>
 
     <style name="PadButtonStyle.Numeric">
-        <item name="android:textSize">32sp</item>
+        <item name="android:textSize">16dip</item>
     </style>
 
     <style name="PadButtonStyle.Numeric.Equals">
-        <item name="android:visibility">visible</item>
+        <item name="android:visibility">gone</item>
     </style>
 
     <style name="PadButtonStyle.Operator">
-        <item name="android:textSize">23sp</item>
+        <item name="android:textSize">14dip</item>
     </style>
 
     <style name="PadButtonStyle.Operator.Text">
         <item name="android:textAllCaps">true</item>
-        <item name="android:textSize">15sp</item>
+        <item name="android:textSize">12dip</item>
     </style>
 
     <style name="PadLayoutStyle.Advanced">
         <item name="android:elevation">4dip</item>
-        <item name="android:paddingTop">12dip</item>
-        <item name="android:paddingBottom">20dip</item>
+        <item name="android:paddingTop">2dip</item>
+        <item name="android:paddingBottom">8dip</item>
         <item name="android:paddingStart">20dip</item>
         <item name="android:paddingEnd">20dip</item>
     </style>
 
     <style name="PadLayoutStyle.Numeric">
         <item name="android:layout_width">0dip</item>
-        <item name="android:layout_weight">264</item>
-        <item name="android:paddingTop">12dip</item>
-        <item name="android:paddingBottom">20dip</item>
-        <item name="android:paddingStart">12dip</item>
-        <item name="android:paddingEnd">12dip</item>
+        <item name="android:layout_weight">5</item>
+        <item name="android:paddingTop">2dip</item>
+        <item name="android:paddingBottom">8dip</item>
+        <item name="android:paddingStart">8dip</item>
+        <item name="android:paddingEnd">8dip</item>
     </style>
 
     <style name="PadLayoutStyle.Operator">
         <item name="android:layout_width">0dip</item>
-        <item name="android:layout_weight">96</item>
-        <item name="android:paddingTop">8dip</item>
-        <item name="android:paddingBottom">24dip</item>
+        <item name="android:layout_weight">3</item>
+        <item name="android:paddingTop">2dip</item>
+        <item name="android:paddingBottom">8dip</item>
         <item name="android:paddingStart">4dip</item>
         <item name="android:paddingEnd">28dip</item>
     </style>
diff --git a/res/values-port/layout.xml b/res/values-w375dp-h275dp/layout.xml
similarity index 71%
copy from res/values-port/layout.xml
copy to res/values-w375dp-h275dp/layout.xml
index 99ea0b8..3890667 100644
--- a/res/values-port/layout.xml
+++ b/res/values-w375dp-h275dp/layout.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  Copyright (C) 2014 The Android Open Source Project
+  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.
@@ -17,4 +17,7 @@
 
 <resources>
     <item name="activity_calculator" type="layout">@layout/activity_calculator_port</item>
-</resources>
\ No newline at end of file
+    <item name="display" type="layout">@layout/display_two_line</item>
+    <item name="pad_operator" type="layout">@layout/pad_operator_two_col</item>
+    <item name="pad_advanced" type="layout">@layout/pad_advanced_4x4</item>
+</resources>
diff --git a/res/values-port/styles.xml b/res/values-w375dp-h275dp/styles.xml
similarity index 61%
copy from res/values-port/styles.xml
copy to res/values-w375dp-h275dp/styles.xml
index a6d2013..c628645 100644
--- a/res/values-port/styles.xml
+++ b/res/values-w375dp-h275dp/styles.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  Copyright (C) 2014 The Android Open Source Project
+  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.
@@ -15,77 +15,78 @@
   limitations under the License.
   -->
 
-<!-- Styles for portrait phone (e.g. Nexus 4/5). -->
 <resources xmlns:android="http://schemas.android.com/apk/res/android">
 
     <style name="DisplayTextStyle.Formula">
-        <item name="android:paddingTop">8dip</item>
-        <item name="android:paddingBottom">24dip</item>
+        <item name="android:paddingTop">0dip</item>
+        <item name="android:paddingBottom">0dip</item>
         <item name="android:paddingStart">16dip</item>
         <item name="android:paddingEnd">16dip</item>
-        <item name="minTextSize">32sp</item>
-        <item name="maxTextSize">56sp</item>
-        <item name="stepTextSize">8sp</item>
+        <item name="android:gravity">bottom</item>
+        <item name="minTextSize">28dip</item>
+        <item name="maxTextSize">28dip</item>
+        <item name="stepTextSize">8dip</item>
     </style>
 
     <style name="DisplayTextStyle.Result">
-        <item name="android:paddingTop">10dip</item>
-        <item name="android:paddingBottom">32dip</item>
+        <item name="android:paddingTop">0dip</item>
+        <item name="android:paddingBottom">0dip</item>
         <item name="android:paddingStart">16dip</item>
         <item name="android:paddingEnd">16dip</item>
-        <item name="android:textSize">32sp</item>
+        <item name="android:gravity">bottom</item>
+        <item name="android:textSize">28dip</item>
     </style>
 
     <style name="PadButtonStyle.Advanced">
         <item name="android:background">@drawable/pad_button_advanced_background</item>
         <item name="android:textColor">@color/pad_button_advanced_text_color</item>
-        <item name="android:textSize">20sp</item>
+        <item name="android:textSize">17dip</item>
     </style>
 
     <style name="PadButtonStyle.Advanced.Text">
         <item name="android:textAllCaps">true</item>
-        <item name="android:textSize">17sp</item>
+        <item name="android:textSize">14dip</item>
     </style>
 
     <style name="PadButtonStyle.Numeric">
-        <item name="android:textSize">32sp</item>
+        <item name="android:textSize">20dip</item>
     </style>
 
     <style name="PadButtonStyle.Numeric.Equals">
-        <item name="android:visibility">visible</item>
+        <item name="android:visibility">gone</item>
     </style>
 
     <style name="PadButtonStyle.Operator">
-        <item name="android:textSize">23sp</item>
+        <item name="android:textSize">17dip</item>
     </style>
 
     <style name="PadButtonStyle.Operator.Text">
         <item name="android:textAllCaps">true</item>
-        <item name="android:textSize">15sp</item>
+        <item name="android:textSize">14dip</item>
     </style>
 
     <style name="PadLayoutStyle.Advanced">
         <item name="android:elevation">4dip</item>
-        <item name="android:paddingTop">12dip</item>
-        <item name="android:paddingBottom">20dip</item>
+        <item name="android:paddingTop">2dip</item>
+        <item name="android:paddingBottom">8dip</item>
         <item name="android:paddingStart">20dip</item>
         <item name="android:paddingEnd">20dip</item>
     </style>
 
     <style name="PadLayoutStyle.Numeric">
         <item name="android:layout_width">0dip</item>
-        <item name="android:layout_weight">264</item>
-        <item name="android:paddingTop">12dip</item>
-        <item name="android:paddingBottom">20dip</item>
-        <item name="android:paddingStart">12dip</item>
-        <item name="android:paddingEnd">12dip</item>
+        <item name="android:layout_weight">5</item>
+        <item name="android:paddingTop">2dip</item>
+        <item name="android:paddingBottom">8dip</item>
+        <item name="android:paddingStart">8dip</item>
+        <item name="android:paddingEnd">8dip</item>
     </style>
 
     <style name="PadLayoutStyle.Operator">
         <item name="android:layout_width">0dip</item>
-        <item name="android:layout_weight">96</item>
-        <item name="android:paddingTop">8dip</item>
-        <item name="android:paddingBottom">24dip</item>
+        <item name="android:layout_weight">3</item>
+        <item name="android:paddingTop">2dip</item>
+        <item name="android:paddingBottom">8dip</item>
         <item name="android:paddingStart">4dip</item>
         <item name="android:paddingEnd">28dip</item>
     </style>
diff --git a/res/values-port/layout.xml b/res/values-w375dp-h375dp/layout.xml
similarity index 71%
copy from res/values-port/layout.xml
copy to res/values-w375dp-h375dp/layout.xml
index 99ea0b8..3890667 100644
--- a/res/values-port/layout.xml
+++ b/res/values-w375dp-h375dp/layout.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  Copyright (C) 2014 The Android Open Source Project
+  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.
@@ -17,4 +17,7 @@
 
 <resources>
     <item name="activity_calculator" type="layout">@layout/activity_calculator_port</item>
-</resources>
\ No newline at end of file
+    <item name="display" type="layout">@layout/display_two_line</item>
+    <item name="pad_operator" type="layout">@layout/pad_operator_two_col</item>
+    <item name="pad_advanced" type="layout">@layout/pad_advanced_4x4</item>
+</resources>
diff --git a/res/values-port/styles.xml b/res/values-w375dp-h375dp/styles.xml
similarity index 66%
copy from res/values-port/styles.xml
copy to res/values-w375dp-h375dp/styles.xml
index a6d2013..14e96ca 100644
--- a/res/values-port/styles.xml
+++ b/res/values-w375dp-h375dp/styles.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  Copyright (C) 2014 The Android Open Source Project
+  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.
@@ -15,77 +15,76 @@
   limitations under the License.
   -->
 
-<!-- Styles for portrait phone (e.g. Nexus 4/5). -->
 <resources xmlns:android="http://schemas.android.com/apk/res/android">
 
     <style name="DisplayTextStyle.Formula">
-        <item name="android:paddingTop">8dip</item>
-        <item name="android:paddingBottom">24dip</item>
+        <item name="android:paddingTop">2dip</item>
+        <item name="android:paddingBottom">10dip</item>
         <item name="android:paddingStart">16dip</item>
         <item name="android:paddingEnd">16dip</item>
-        <item name="minTextSize">32sp</item>
-        <item name="maxTextSize">56sp</item>
-        <item name="stepTextSize">8sp</item>
+        <item name="minTextSize">32dip</item>
+        <item name="maxTextSize">32dip</item>
+        <item name="stepTextSize">8dip</item>
     </style>
 
     <style name="DisplayTextStyle.Result">
-        <item name="android:paddingTop">10dip</item>
-        <item name="android:paddingBottom">32dip</item>
+        <item name="android:paddingTop">12dip</item>
+        <item name="android:paddingBottom">18dip</item>
         <item name="android:paddingStart">16dip</item>
         <item name="android:paddingEnd">16dip</item>
-        <item name="android:textSize">32sp</item>
+        <item name="android:textSize">32dip</item>
     </style>
 
     <style name="PadButtonStyle.Advanced">
         <item name="android:background">@drawable/pad_button_advanced_background</item>
         <item name="android:textColor">@color/pad_button_advanced_text_color</item>
-        <item name="android:textSize">20sp</item>
+        <item name="android:textSize">17dip</item>
     </style>
 
     <style name="PadButtonStyle.Advanced.Text">
         <item name="android:textAllCaps">true</item>
-        <item name="android:textSize">17sp</item>
+        <item name="android:textSize">15dip</item>
     </style>
 
     <style name="PadButtonStyle.Numeric">
-        <item name="android:textSize">32sp</item>
+        <item name="android:textSize">27dip</item>
     </style>
 
     <style name="PadButtonStyle.Numeric.Equals">
-        <item name="android:visibility">visible</item>
+        <item name="android:visibility">gone</item>
     </style>
 
     <style name="PadButtonStyle.Operator">
-        <item name="android:textSize">23sp</item>
+        <item name="android:textSize">24dip</item>
     </style>
 
     <style name="PadButtonStyle.Operator.Text">
         <item name="android:textAllCaps">true</item>
-        <item name="android:textSize">15sp</item>
+        <item name="android:textSize">15dip</item>
     </style>
 
     <style name="PadLayoutStyle.Advanced">
         <item name="android:elevation">4dip</item>
-        <item name="android:paddingTop">12dip</item>
-        <item name="android:paddingBottom">20dip</item>
+        <item name="android:paddingTop">2dip</item>
+        <item name="android:paddingBottom">8dip</item>
         <item name="android:paddingStart">20dip</item>
         <item name="android:paddingEnd">20dip</item>
     </style>
 
     <style name="PadLayoutStyle.Numeric">
         <item name="android:layout_width">0dip</item>
-        <item name="android:layout_weight">264</item>
-        <item name="android:paddingTop">12dip</item>
-        <item name="android:paddingBottom">20dip</item>
-        <item name="android:paddingStart">12dip</item>
-        <item name="android:paddingEnd">12dip</item>
+        <item name="android:layout_weight">5</item>
+        <item name="android:paddingTop">8dip</item>
+        <item name="android:paddingBottom">8dip</item>
+        <item name="android:paddingStart">8dip</item>
+        <item name="android:paddingEnd">8dip</item>
     </style>
 
     <style name="PadLayoutStyle.Operator">
         <item name="android:layout_width">0dip</item>
-        <item name="android:layout_weight">96</item>
+        <item name="android:layout_weight">3</item>
         <item name="android:paddingTop">8dip</item>
-        <item name="android:paddingBottom">24dip</item>
+        <item name="android:paddingBottom">8dip</item>
         <item name="android:paddingStart">4dip</item>
         <item name="android:paddingEnd">28dip</item>
     </style>
diff --git a/res/values-port/layout.xml b/res/values-w375dp-h500dp-port/layout.xml
similarity index 71%
copy from res/values-port/layout.xml
copy to res/values-w375dp-h500dp-port/layout.xml
index 99ea0b8..9f7bd6b 100644
--- a/res/values-port/layout.xml
+++ b/res/values-w375dp-h500dp-port/layout.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  Copyright (C) 2014 The Android Open Source Project
+  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.
@@ -17,4 +17,7 @@
 
 <resources>
     <item name="activity_calculator" type="layout">@layout/activity_calculator_port</item>
-</resources>
\ No newline at end of file
+    <item name="display" type="layout">@layout/display_two_line</item>
+    <item name="pad_operator" type="layout">@layout/pad_operator_one_col</item>
+    <item name="pad_advanced" type="layout">@layout/pad_advanced_3x5</item>
+</resources>
diff --git a/res/values-port/styles.xml b/res/values-w375dp-h500dp-port/styles.xml
similarity index 75%
rename from res/values-port/styles.xml
rename to res/values-w375dp-h500dp-port/styles.xml
index a6d2013..066aa8e 100644
--- a/res/values-port/styles.xml
+++ b/res/values-w375dp-h500dp-port/styles.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  Copyright (C) 2014 The Android Open Source Project
+  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.
@@ -15,40 +15,39 @@
   limitations under the License.
   -->
 
-<!-- Styles for portrait phone (e.g. Nexus 4/5). -->
 <resources xmlns:android="http://schemas.android.com/apk/res/android">
 
     <style name="DisplayTextStyle.Formula">
-        <item name="android:paddingTop">8dip</item>
-        <item name="android:paddingBottom">24dip</item>
+        <item name="android:paddingTop">16dip</item>
+        <item name="android:paddingBottom">28dip</item>
         <item name="android:paddingStart">16dip</item>
         <item name="android:paddingEnd">16dip</item>
-        <item name="minTextSize">32sp</item>
-        <item name="maxTextSize">56sp</item>
-        <item name="stepTextSize">8sp</item>
+        <item name="minTextSize">42dip</item>
+        <item name="maxTextSize">74dip</item>
+        <item name="stepTextSize">8dip</item>
     </style>
 
     <style name="DisplayTextStyle.Result">
-        <item name="android:paddingTop">10dip</item>
-        <item name="android:paddingBottom">32dip</item>
+        <item name="android:paddingTop">16dip</item>
+        <item name="android:paddingBottom">42dip</item>
         <item name="android:paddingStart">16dip</item>
         <item name="android:paddingEnd">16dip</item>
-        <item name="android:textSize">32sp</item>
+        <item name="android:textSize">42dip</item>
     </style>
 
     <style name="PadButtonStyle.Advanced">
         <item name="android:background">@drawable/pad_button_advanced_background</item>
         <item name="android:textColor">@color/pad_button_advanced_text_color</item>
-        <item name="android:textSize">20sp</item>
+        <item name="android:textSize">23dip</item>
     </style>
 
     <style name="PadButtonStyle.Advanced.Text">
         <item name="android:textAllCaps">true</item>
-        <item name="android:textSize">17sp</item>
+        <item name="android:textSize">20dip</item>
     </style>
 
     <style name="PadButtonStyle.Numeric">
-        <item name="android:textSize">32sp</item>
+        <item name="android:textSize">36dip</item>
     </style>
 
     <style name="PadButtonStyle.Numeric.Equals">
@@ -56,12 +55,12 @@
     </style>
 
     <style name="PadButtonStyle.Operator">
-        <item name="android:textSize">23sp</item>
+        <item name="android:textSize">27dip</item>
     </style>
 
     <style name="PadButtonStyle.Operator.Text">
         <item name="android:textAllCaps">true</item>
-        <item name="android:textSize">15sp</item>
+        <item name="android:textSize">20dip</item>
     </style>
 
     <style name="PadLayoutStyle.Advanced">
@@ -74,7 +73,7 @@
 
     <style name="PadLayoutStyle.Numeric">
         <item name="android:layout_width">0dip</item>
-        <item name="android:layout_weight">264</item>
+        <item name="android:layout_weight">7</item>
         <item name="android:paddingTop">12dip</item>
         <item name="android:paddingBottom">20dip</item>
         <item name="android:paddingStart">12dip</item>
@@ -83,7 +82,7 @@
 
     <style name="PadLayoutStyle.Operator">
         <item name="android:layout_width">0dip</item>
-        <item name="android:layout_weight">96</item>
+        <item name="android:layout_weight">3</item>
         <item name="android:paddingTop">8dip</item>
         <item name="android:paddingBottom">24dip</item>
         <item name="android:paddingStart">4dip</item>
diff --git a/res/values-sw600dp-port/layout.xml b/res/values-w375dp-h768dp-port/layout.xml
similarity index 71%
rename from res/values-sw600dp-port/layout.xml
rename to res/values-w375dp-h768dp-port/layout.xml
index 46d71cb..98db0fe 100644
--- a/res/values-sw600dp-port/layout.xml
+++ b/res/values-w375dp-h768dp-port/layout.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  Copyright (C) 2014 The Android Open Source Project
+  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.
@@ -17,4 +17,7 @@
 
 <resources>
     <item name="activity_calculator" type="layout">@layout/activity_calculator_tablet_port</item>
-</resources>
\ No newline at end of file
+    <item name="display" type="layout">@layout/display_two_line</item>
+    <item name="pad_advanced" type="layout">@layout/pad_advanced_5x3</item>
+    <item name="pad_operator" type="layout">@layout/pad_operator_two_col</item>
+</resources>
diff --git a/res/values-sw600dp-port/styles.xml b/res/values-w375dp-h768dp-port/styles.xml
similarity index 78%
copy from res/values-sw600dp-port/styles.xml
copy to res/values-w375dp-h768dp-port/styles.xml
index f439521..ec91033 100644
--- a/res/values-sw600dp-port/styles.xml
+++ b/res/values-w375dp-h768dp-port/styles.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  Copyright (C) 2014 The Android Open Source Project
+  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.
@@ -15,8 +15,6 @@
   limitations under the License.
   -->
 
-
-<!-- Styles for portrait 600dip-wide tablet (e.g. Nexus 7). -->
 <resources xmlns:android="http://schemas.android.com/apk/res/android">
 
     <style name="DisplayTextStyle.Formula">
@@ -24,9 +22,9 @@
         <item name="android:paddingBottom">32dip</item>
         <item name="android:paddingStart">44dip</item>
         <item name="android:paddingEnd">44dip</item>
-        <item name="minTextSize">48sp</item>
-        <item name="maxTextSize">72sp</item>
-        <item name="stepTextSize">8sp</item>
+        <item name="minTextSize">48dip</item>
+        <item name="maxTextSize">72dip</item>
+        <item name="stepTextSize">8dip</item>
     </style>
 
     <style name="DisplayTextStyle.Result">
@@ -34,22 +32,22 @@
         <item name="android:paddingBottom">48dip</item>
         <item name="android:paddingStart">44dip</item>
         <item name="android:paddingEnd">44dip</item>
-        <item name="android:textSize">48sp</item>
+        <item name="android:textSize">48dip</item>
     </style>
 
     <style name="PadButtonStyle.Advanced">
         <item name="android:background">@drawable/pad_button_advanced_background</item>
         <item name="android:textColor">@color/pad_button_advanced_text_color</item>
-        <item name="android:textSize">27sp</item>
+        <item name="android:textSize">27dip</item>
     </style>
 
     <style name="PadButtonStyle.Advanced.Text">
         <item name="android:textAllCaps">true</item>
-        <item name="android:textSize">24sp</item>
+        <item name="android:textSize">24dip</item>
     </style>
 
     <style name="PadButtonStyle.Numeric">
-        <item name="android:textSize">36sp</item>
+        <item name="android:textSize">36dip</item>
     </style>
 
     <style name="PadButtonStyle.Numeric.Equals">
@@ -57,12 +55,12 @@
     </style>
 
     <style name="PadButtonStyle.Operator">
-        <item name="android:textSize">36sp</item>
+        <item name="android:textSize">36dip</item>
     </style>
 
     <style name="PadButtonStyle.Operator.Text">
         <item name="android:textAllCaps">true</item>
-        <item name="android:textSize">24sp</item>
+        <item name="android:textSize">24dip</item>
     </style>
 
     <style name="PadLayoutStyle.Advanced">
@@ -88,14 +86,8 @@
         <item name="android:layout_weight">264</item>
         <item name="android:paddingTop">8dip</item>
         <item name="android:paddingBottom">8dip</item>
-        <item name="android:paddingStart">8dip</item>
+        <item name="android:paddingStart">4dip</item>
         <item name="android:paddingEnd">8dip</item>
     </style>
 
-    <style name="PadLinearLayoutStyle">
-        <item name="android:layout_width">match_parent</item>
-        <item name="android:layout_height">0dip</item>
-        <item name="android:layout_weight">500</item>
-    </style>
-
 </resources>
diff --git a/res/values-land/layout.xml b/res/values-w520dp-h220dp-land/layout.xml
similarity index 71%
rename from res/values-land/layout.xml
rename to res/values-w520dp-h220dp-land/layout.xml
index 8bc268c..f4d3c4f 100644
--- a/res/values-land/layout.xml
+++ b/res/values-w520dp-h220dp-land/layout.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  Copyright (C) 2014 The Android Open Source Project
+  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.
@@ -17,4 +17,7 @@
 
 <resources>
     <item name="activity_calculator" type="layout">@layout/activity_calculator_land</item>
-</resources>
\ No newline at end of file
+    <item name="display" type="layout">@layout/display_one_line</item>
+    <item name="pad_operator" type="layout">@layout/pad_operator_two_col</item>
+    <item name="pad_advanced" type="layout">@layout/pad_advanced_4x4</item>
+</resources>
diff --git a/res/values-land/styles.xml b/res/values-w520dp-h220dp-land/styles.xml
similarity index 63%
rename from res/values-land/styles.xml
rename to res/values-w520dp-h220dp-land/styles.xml
index 3730145..e90e530 100644
--- a/res/values-land/styles.xml
+++ b/res/values-w520dp-h220dp-land/styles.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  Copyright (C) 2014 The Android Open Source Project
+  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.
@@ -15,38 +15,41 @@
   limitations under the License.
   -->
 
-<!-- Styles for landscape phone (e.g. Nexus 4/5). -->
 <resources xmlns:android="http://schemas.android.com/apk/res/android">
 
     <style name="DisplayTextStyle.Formula">
-        <item name="android:paddingTop">2dip</item>
-        <item name="android:paddingBottom">6dip</item>
+        <item name="android:paddingTop">0dip</item>
+        <item name="android:paddingBottom">0dip</item>
         <item name="android:paddingStart">36dip</item>
         <item name="android:paddingEnd">36dip</item>
-        <item name="android:textSize">24sp</item>
+        <item name="android:gravity">bottom</item>
+        <item name="minTextSize">28dip</item>
+        <item name="maxTextSize">28dip</item>
+        <item name="stepTextSize">8dip</item>
     </style>
 
     <style name="DisplayTextStyle.Result">
-        <item name="android:paddingTop">8dip</item>
-        <item name="android:paddingBottom">12dip</item>
+        <item name="android:paddingTop">0dip</item>
+        <item name="android:paddingBottom">0dip</item>
         <item name="android:paddingStart">36dip</item>
         <item name="android:paddingEnd">36dip</item>
-        <item name="android:textSize">24sp</item>
+        <item name="android:gravity">bottom</item>
+        <item name="android:textSize">28dip</item>
     </style>
 
     <style name="PadButtonStyle.Advanced">
         <item name="android:background">@drawable/pad_button_advanced_background</item>
         <item name="android:textColor">@color/pad_button_advanced_text_color</item>
-        <item name="android:textSize">15sp</item>
+        <item name="android:textSize">14dip</item>
     </style>
 
     <style name="PadButtonStyle.Advanced.Text">
         <item name="android:textAllCaps">true</item>
-        <item name="android:textSize">13sp</item>
+        <item name="android:textSize">12dip</item>
     </style>
 
     <style name="PadButtonStyle.Numeric">
-        <item name="android:textSize">23sp</item>
+        <item name="android:textSize">16dip</item>
     </style>
 
     <style name="PadButtonStyle.Numeric.Equals">
@@ -54,39 +57,39 @@
     </style>
 
     <style name="PadButtonStyle.Operator">
-        <item name="android:textSize">20sp</item>
+        <item name="android:textSize">14dip</item>
     </style>
 
     <style name="PadButtonStyle.Operator.Text">
         <item name="android:textAllCaps">true</item>
-        <item name="android:textSize">13sp</item>
+        <item name="android:textSize">12dip</item>
     </style>
 
     <style name="PadLayoutStyle.Advanced">
         <item name="android:layout_width">0dip</item>
-        <item name="android:layout_weight">208</item>
-        <item name="android:paddingTop">4dip</item>
-        <item name="android:paddingBottom">4dip</item>
+        <item name="android:layout_weight">4</item>
+        <item name="android:paddingTop">2dip</item>
+        <item name="android:paddingBottom">8dip</item>
         <item name="android:paddingStart">8dip</item>
         <item name="android:paddingEnd">8dip</item>
     </style>
 
     <style name="PadLayoutStyle.Numeric">
         <item name="android:layout_width">0dip</item>
-        <item name="android:layout_weight">240</item>
-        <item name="android:paddingTop">4dip</item>
-        <item name="android:paddingBottom">4dip</item>
+        <item name="android:layout_weight">3</item>
+        <item name="android:paddingTop">2dip</item>
+        <item name="android:paddingBottom">8dip</item>
         <item name="android:paddingStart">8dip</item>
         <item name="android:paddingEnd">8dip</item>
     </style>
 
     <style name="PadLayoutStyle.Operator">
         <item name="android:layout_width">0dip</item>
-        <item name="android:layout_weight">144</item>
-        <item name="android:paddingTop">4dip</item>
-        <item name="android:paddingBottom">4dip</item>
-        <item name="android:paddingStart">12dip</item>
-        <item name="android:paddingEnd">12dip</item>
+        <item name="android:layout_weight">2</item>
+        <item name="android:paddingTop">2dip</item>
+        <item name="android:paddingBottom">8dip</item>
+        <item name="android:paddingStart">8dip</item>
+        <item name="android:paddingEnd">8dip</item>
     </style>
 
 </resources>
diff --git a/res/values-land/layout.xml b/res/values-w520dp-h275dp-land/layout.xml
similarity index 71%
copy from res/values-land/layout.xml
copy to res/values-w520dp-h275dp-land/layout.xml
index 8bc268c..15b5b1b 100644
--- a/res/values-land/layout.xml
+++ b/res/values-w520dp-h275dp-land/layout.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  Copyright (C) 2014 The Android Open Source Project
+  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.
@@ -17,4 +17,7 @@
 
 <resources>
     <item name="activity_calculator" type="layout">@layout/activity_calculator_land</item>
-</resources>
\ No newline at end of file
+    <item name="display" type="layout">@layout/display_two_line</item>
+    <item name="pad_advanced" type="layout">@layout/pad_advanced_3x5</item>
+    <item name="pad_operator" type="layout">@layout/pad_operator_two_col</item>
+</resources>
diff --git a/res/values-land/styles.xml b/res/values-w520dp-h275dp-land/styles.xml
similarity index 63%
copy from res/values-land/styles.xml
copy to res/values-w520dp-h275dp-land/styles.xml
index 3730145..9d66a9b 100644
--- a/res/values-land/styles.xml
+++ b/res/values-w520dp-h275dp-land/styles.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  Copyright (C) 2014 The Android Open Source Project
+  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.
@@ -15,38 +15,39 @@
   limitations under the License.
   -->
 
-<!-- Styles for landscape phone (e.g. Nexus 4/5). -->
 <resources xmlns:android="http://schemas.android.com/apk/res/android">
 
     <style name="DisplayTextStyle.Formula">
         <item name="android:paddingTop">2dip</item>
-        <item name="android:paddingBottom">6dip</item>
+        <item name="android:paddingBottom">8dip</item>
         <item name="android:paddingStart">36dip</item>
         <item name="android:paddingEnd">36dip</item>
-        <item name="android:textSize">24sp</item>
+        <item name="minTextSize">28dip</item>
+        <item name="maxTextSize">28dip</item>
+        <item name="stepTextSize">8dip</item>
     </style>
 
     <style name="DisplayTextStyle.Result">
         <item name="android:paddingTop">8dip</item>
-        <item name="android:paddingBottom">12dip</item>
+        <item name="android:paddingBottom">16dip</item>
         <item name="android:paddingStart">36dip</item>
         <item name="android:paddingEnd">36dip</item>
-        <item name="android:textSize">24sp</item>
+        <item name="android:textSize">28dip</item>
     </style>
 
     <style name="PadButtonStyle.Advanced">
         <item name="android:background">@drawable/pad_button_advanced_background</item>
         <item name="android:textColor">@color/pad_button_advanced_text_color</item>
-        <item name="android:textSize">15sp</item>
+        <item name="android:textSize">17dip</item>
     </style>
 
     <style name="PadButtonStyle.Advanced.Text">
         <item name="android:textAllCaps">true</item>
-        <item name="android:textSize">13sp</item>
+        <item name="android:textSize">14dip</item>
     </style>
 
     <style name="PadButtonStyle.Numeric">
-        <item name="android:textSize">23sp</item>
+        <item name="android:textSize">20dip</item>
     </style>
 
     <style name="PadButtonStyle.Numeric.Equals">
@@ -54,37 +55,37 @@
     </style>
 
     <style name="PadButtonStyle.Operator">
-        <item name="android:textSize">20sp</item>
+        <item name="android:textSize">17dip</item>
     </style>
 
     <style name="PadButtonStyle.Operator.Text">
         <item name="android:textAllCaps">true</item>
-        <item name="android:textSize">13sp</item>
+        <item name="android:textSize">14dip</item>
     </style>
 
     <style name="PadLayoutStyle.Advanced">
+        <item name="android:layout_weight">5</item>
         <item name="android:layout_width">0dip</item>
-        <item name="android:layout_weight">208</item>
-        <item name="android:paddingTop">4dip</item>
-        <item name="android:paddingBottom">4dip</item>
-        <item name="android:paddingStart">8dip</item>
-        <item name="android:paddingEnd">8dip</item>
+        <item name="android:paddingTop">2dip</item>
+        <item name="android:paddingBottom">8dip</item>
+        <item name="android:paddingStart">12dip</item>
+        <item name="android:paddingEnd">12dip</item>
     </style>
 
     <style name="PadLayoutStyle.Numeric">
         <item name="android:layout_width">0dip</item>
-        <item name="android:layout_weight">240</item>
-        <item name="android:paddingTop">4dip</item>
-        <item name="android:paddingBottom">4dip</item>
-        <item name="android:paddingStart">8dip</item>
-        <item name="android:paddingEnd">8dip</item>
+        <item name="android:layout_weight">5</item>
+        <item name="android:paddingTop">2dip</item>
+        <item name="android:paddingBottom">8dip</item>
+        <item name="android:paddingStart">12dip</item>
+        <item name="android:paddingEnd">12dip</item>
     </style>
 
     <style name="PadLayoutStyle.Operator">
         <item name="android:layout_width">0dip</item>
-        <item name="android:layout_weight">144</item>
-        <item name="android:paddingTop">4dip</item>
-        <item name="android:paddingBottom">4dip</item>
+        <item name="android:layout_weight">3</item>
+        <item name="android:paddingTop">2dip</item>
+        <item name="android:paddingBottom">8dip</item>
         <item name="android:paddingStart">12dip</item>
         <item name="android:paddingEnd">12dip</item>
     </style>
diff --git a/res/values-land/layout.xml b/res/values-w520dp-h375dp-land/layout.xml
similarity index 71%
copy from res/values-land/layout.xml
copy to res/values-w520dp-h375dp-land/layout.xml
index 8bc268c..15b5b1b 100644
--- a/res/values-land/layout.xml
+++ b/res/values-w520dp-h375dp-land/layout.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  Copyright (C) 2014 The Android Open Source Project
+  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.
@@ -17,4 +17,7 @@
 
 <resources>
     <item name="activity_calculator" type="layout">@layout/activity_calculator_land</item>
-</resources>
\ No newline at end of file
+    <item name="display" type="layout">@layout/display_two_line</item>
+    <item name="pad_advanced" type="layout">@layout/pad_advanced_3x5</item>
+    <item name="pad_operator" type="layout">@layout/pad_operator_two_col</item>
+</resources>
diff --git a/res/values-land/styles.xml b/res/values-w520dp-h375dp-land/styles.xml
similarity index 63%
copy from res/values-land/styles.xml
copy to res/values-w520dp-h375dp-land/styles.xml
index 3730145..d89ea24 100644
--- a/res/values-land/styles.xml
+++ b/res/values-w520dp-h375dp-land/styles.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  Copyright (C) 2014 The Android Open Source Project
+  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.
@@ -15,38 +15,39 @@
   limitations under the License.
   -->
 
-<!-- Styles for landscape phone (e.g. Nexus 4/5). -->
 <resources xmlns:android="http://schemas.android.com/apk/res/android">
 
     <style name="DisplayTextStyle.Formula">
         <item name="android:paddingTop">2dip</item>
-        <item name="android:paddingBottom">6dip</item>
+        <item name="android:paddingBottom">10dip</item>
         <item name="android:paddingStart">36dip</item>
         <item name="android:paddingEnd">36dip</item>
-        <item name="android:textSize">24sp</item>
+        <item name="minTextSize">32dip</item>
+        <item name="maxTextSize">32dip</item>
+        <item name="stepTextSize">8dip</item>
     </style>
 
     <style name="DisplayTextStyle.Result">
-        <item name="android:paddingTop">8dip</item>
-        <item name="android:paddingBottom">12dip</item>
+        <item name="android:paddingTop">12dip</item>
+        <item name="android:paddingBottom">18dip</item>
         <item name="android:paddingStart">36dip</item>
         <item name="android:paddingEnd">36dip</item>
-        <item name="android:textSize">24sp</item>
+        <item name="android:textSize">32dip</item>
     </style>
 
     <style name="PadButtonStyle.Advanced">
         <item name="android:background">@drawable/pad_button_advanced_background</item>
         <item name="android:textColor">@color/pad_button_advanced_text_color</item>
-        <item name="android:textSize">15sp</item>
+        <item name="android:textSize">20dip</item>
     </style>
 
     <style name="PadButtonStyle.Advanced.Text">
         <item name="android:textAllCaps">true</item>
-        <item name="android:textSize">13sp</item>
+        <item name="android:textSize">17dip</item>
     </style>
 
     <style name="PadButtonStyle.Numeric">
-        <item name="android:textSize">23sp</item>
+        <item name="android:textSize">23dip</item>
     </style>
 
     <style name="PadButtonStyle.Numeric.Equals">
@@ -54,37 +55,37 @@
     </style>
 
     <style name="PadButtonStyle.Operator">
-        <item name="android:textSize">20sp</item>
+        <item name="android:textSize">20dip</item>
     </style>
 
     <style name="PadButtonStyle.Operator.Text">
         <item name="android:textAllCaps">true</item>
-        <item name="android:textSize">13sp</item>
+        <item name="android:textSize">17dip</item>
     </style>
 
     <style name="PadLayoutStyle.Advanced">
+        <item name="android:layout_weight">5</item>
         <item name="android:layout_width">0dip</item>
-        <item name="android:layout_weight">208</item>
-        <item name="android:paddingTop">4dip</item>
-        <item name="android:paddingBottom">4dip</item>
-        <item name="android:paddingStart">8dip</item>
-        <item name="android:paddingEnd">8dip</item>
+        <item name="android:paddingTop">8dip</item>
+        <item name="android:paddingBottom">8dip</item>
+        <item name="android:paddingStart">12dip</item>
+        <item name="android:paddingEnd">12dip</item>
     </style>
 
     <style name="PadLayoutStyle.Numeric">
         <item name="android:layout_width">0dip</item>
-        <item name="android:layout_weight">240</item>
-        <item name="android:paddingTop">4dip</item>
-        <item name="android:paddingBottom">4dip</item>
-        <item name="android:paddingStart">8dip</item>
-        <item name="android:paddingEnd">8dip</item>
+        <item name="android:layout_weight">5</item>
+        <item name="android:paddingTop">8dip</item>
+        <item name="android:paddingBottom">8dip</item>
+        <item name="android:paddingStart">12dip</item>
+        <item name="android:paddingEnd">12dip</item>
     </style>
 
     <style name="PadLayoutStyle.Operator">
         <item name="android:layout_width">0dip</item>
-        <item name="android:layout_weight">144</item>
-        <item name="android:paddingTop">4dip</item>
-        <item name="android:paddingBottom">4dip</item>
+        <item name="android:layout_weight">3</item>
+        <item name="android:paddingTop">8dip</item>
+        <item name="android:paddingBottom">8dip</item>
         <item name="android:paddingStart">12dip</item>
         <item name="android:paddingEnd">12dip</item>
     </style>
diff --git a/res/values-land/layout.xml b/res/values-w520dp-h500dp-land/layout.xml
similarity index 71%
copy from res/values-land/layout.xml
copy to res/values-w520dp-h500dp-land/layout.xml
index 8bc268c..15b5b1b 100644
--- a/res/values-land/layout.xml
+++ b/res/values-w520dp-h500dp-land/layout.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  Copyright (C) 2014 The Android Open Source Project
+  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.
@@ -17,4 +17,7 @@
 
 <resources>
     <item name="activity_calculator" type="layout">@layout/activity_calculator_land</item>
-</resources>
\ No newline at end of file
+    <item name="display" type="layout">@layout/display_two_line</item>
+    <item name="pad_advanced" type="layout">@layout/pad_advanced_3x5</item>
+    <item name="pad_operator" type="layout">@layout/pad_operator_two_col</item>
+</resources>
diff --git a/res/values-sw600dp-port/styles.xml b/res/values-w520dp-h500dp-land/styles.xml
similarity index 61%
copy from res/values-sw600dp-port/styles.xml
copy to res/values-w520dp-h500dp-land/styles.xml
index f439521..883f6cd 100644
--- a/res/values-sw600dp-port/styles.xml
+++ b/res/values-w520dp-h500dp-land/styles.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  Copyright (C) 2014 The Android Open Source Project
+  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.
@@ -15,41 +15,39 @@
   limitations under the License.
   -->
 
-
-<!-- Styles for portrait 600dip-wide tablet (e.g. Nexus 7). -->
 <resources xmlns:android="http://schemas.android.com/apk/res/android">
 
     <style name="DisplayTextStyle.Formula">
         <item name="android:paddingTop">16dip</item>
-        <item name="android:paddingBottom">32dip</item>
-        <item name="android:paddingStart">44dip</item>
-        <item name="android:paddingEnd">44dip</item>
-        <item name="minTextSize">48sp</item>
-        <item name="maxTextSize">72sp</item>
-        <item name="stepTextSize">8sp</item>
+        <item name="android:paddingBottom">28dip</item>
+        <item name="android:paddingStart">16dip</item>
+        <item name="android:paddingEnd">16dip</item>
+        <item name="minTextSize">42dip</item>
+        <item name="maxTextSize">74dip</item>
+        <item name="stepTextSize">8dip</item>
     </style>
 
     <style name="DisplayTextStyle.Result">
-        <item name="android:paddingTop">20dip</item>
-        <item name="android:paddingBottom">48dip</item>
-        <item name="android:paddingStart">44dip</item>
-        <item name="android:paddingEnd">44dip</item>
-        <item name="android:textSize">48sp</item>
+        <item name="android:paddingTop">16dip</item>
+        <item name="android:paddingBottom">42dip</item>
+        <item name="android:paddingStart">16dip</item>
+        <item name="android:paddingEnd">16dip</item>
+        <item name="android:textSize">42dip</item>
     </style>
 
     <style name="PadButtonStyle.Advanced">
         <item name="android:background">@drawable/pad_button_advanced_background</item>
         <item name="android:textColor">@color/pad_button_advanced_text_color</item>
-        <item name="android:textSize">27sp</item>
+        <item name="android:textSize">23dip</item>
     </style>
 
     <style name="PadButtonStyle.Advanced.Text">
         <item name="android:textAllCaps">true</item>
-        <item name="android:textSize">24sp</item>
+        <item name="android:textSize">20dip</item>
     </style>
 
     <style name="PadButtonStyle.Numeric">
-        <item name="android:textSize">36sp</item>
+        <item name="android:textSize">36dip</item>
     </style>
 
     <style name="PadButtonStyle.Numeric.Equals">
@@ -57,45 +55,39 @@
     </style>
 
     <style name="PadButtonStyle.Operator">
-        <item name="android:textSize">36sp</item>
+        <item name="android:textSize">27dip</item>
     </style>
 
     <style name="PadButtonStyle.Operator.Text">
         <item name="android:textAllCaps">true</item>
-        <item name="android:textSize">24sp</item>
+        <item name="android:textSize">20dip</item>
     </style>
 
     <style name="PadLayoutStyle.Advanced">
-        <item name="android:layout_height">0dip</item>
-        <item name="android:layout_weight">264</item>
+        <item name="android:layout_weight">5</item>
+        <item name="android:layout_width">0dip</item>
         <item name="android:paddingTop">8dip</item>
         <item name="android:paddingBottom">8dip</item>
-        <item name="android:paddingStart">16dip</item>
-        <item name="android:paddingEnd">16dip</item>
+        <item name="android:paddingStart">12dip</item>
+        <item name="android:paddingEnd">12dip</item>
     </style>
 
     <style name="PadLayoutStyle.Numeric">
         <item name="android:layout_width">0dip</item>
-        <item name="android:layout_weight">500</item>
+        <item name="android:layout_weight">5</item>
         <item name="android:paddingTop">8dip</item>
         <item name="android:paddingBottom">8dip</item>
-        <item name="android:paddingStart">16dip</item>
-        <item name="android:paddingEnd">16dip</item>
+        <item name="android:paddingStart">12dip</item>
+        <item name="android:paddingEnd">12dip</item>
     </style>
 
     <style name="PadLayoutStyle.Operator">
         <item name="android:layout_width">0dip</item>
-        <item name="android:layout_weight">264</item>
+        <item name="android:layout_weight">3</item>
         <item name="android:paddingTop">8dip</item>
         <item name="android:paddingBottom">8dip</item>
-        <item name="android:paddingStart">8dip</item>
-        <item name="android:paddingEnd">8dip</item>
-    </style>
-
-    <style name="PadLinearLayoutStyle">
-        <item name="android:layout_width">match_parent</item>
-        <item name="android:layout_height">0dip</item>
-        <item name="android:layout_weight">500</item>
+        <item name="android:paddingStart">12dip</item>
+        <item name="android:paddingEnd">12dip</item>
     </style>
 
 </resources>
diff --git a/res/values-land/layout.xml b/res/values-w520dp-h768dp-land/layout.xml
similarity index 71%
copy from res/values-land/layout.xml
copy to res/values-w520dp-h768dp-land/layout.xml
index 8bc268c..15b5b1b 100644
--- a/res/values-land/layout.xml
+++ b/res/values-w520dp-h768dp-land/layout.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  Copyright (C) 2014 The Android Open Source Project
+  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.
@@ -17,4 +17,7 @@
 
 <resources>
     <item name="activity_calculator" type="layout">@layout/activity_calculator_land</item>
-</resources>
\ No newline at end of file
+    <item name="display" type="layout">@layout/display_two_line</item>
+    <item name="pad_advanced" type="layout">@layout/pad_advanced_3x5</item>
+    <item name="pad_operator" type="layout">@layout/pad_operator_two_col</item>
+</resources>
diff --git a/res/values-w520dp-h768dp-land/styles.xml b/res/values-w520dp-h768dp-land/styles.xml
new file mode 100644
index 0000000..9fdf68a
--- /dev/null
+++ b/res/values-w520dp-h768dp-land/styles.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <style name="DisplayTextStyle.Formula">
+        <item name="android:paddingTop">24dip</item>
+        <item name="android:paddingBottom">32dip</item>
+        <item name="android:paddingStart">44dip</item>
+        <item name="android:paddingEnd">44dip</item>
+        <item name="minTextSize">44dip</item>
+        <item name="maxTextSize">76dip</item>
+        <item name="stepTextSize">8dip</item>
+    </style>
+
+    <style name="DisplayTextStyle.Result">
+        <item name="android:paddingTop">24dip</item>
+        <item name="android:paddingBottom">56dip</item>
+        <item name="android:paddingStart">44dip</item>
+        <item name="android:paddingEnd">44dip</item>
+        <item name="android:textSize">44dip</item>
+    </style>
+
+    <style name="PadButtonStyle.Advanced">
+        <item name="android:background">@drawable/pad_button_advanced_background</item>
+        <item name="android:textColor">@color/pad_button_advanced_text_color</item>
+        <item name="android:textSize">30dip</item>
+    </style>
+
+    <style name="PadButtonStyle.Advanced.Text">
+        <item name="android:textAllCaps">true</item>s
+        <item name="android:textSize">26dip</item>
+    </style>
+
+    <style name="PadButtonStyle.Numeric">
+        <item name="android:textSize">38dip</item>
+    </style>
+
+    <style name="PadButtonStyle.Numeric.Equals">
+        <item name="android:visibility">gone</item>
+    </style>
+
+    <style name="PadButtonStyle.Operator">
+        <item name="android:textSize">36dip</item>
+    </style>
+
+    <style name="PadButtonStyle.Operator.Text">
+        <item name="android:textAllCaps">true</item>
+        <item name="android:textSize">26dip</item>
+    </style>
+
+    <style name="PadLayoutStyle.Advanced">
+        <item name="android:layout_weight">5</item>
+        <item name="android:layout_width">0dip</item>
+        <item name="android:paddingTop">8dip</item>
+        <item name="android:paddingBottom">8dip</item>
+        <item name="android:paddingStart">12dip</item>
+        <item name="android:paddingEnd">12dip</item>
+    </style>
+
+    <style name="PadLayoutStyle.Numeric">
+        <item name="android:layout_width">0dip</item>
+        <item name="android:layout_weight">5</item>
+        <item name="android:paddingTop">12dip</item>
+        <item name="android:paddingBottom">20dip</item>
+        <item name="android:paddingStart">12dip</item>
+        <item name="android:paddingEnd">12dip</item>
+    </style>
+
+    <style name="PadLayoutStyle.Operator">
+        <item name="android:layout_width">0dip</item>
+        <item name="android:layout_weight">3</item>
+        <item name="android:paddingTop">8dip</item>
+        <item name="android:paddingBottom">24dip</item>
+        <item name="android:paddingStart">12dip</item>
+        <item name="android:paddingEnd">12dip</item>
+    </style>
+
+</resources>
diff --git a/res/values-sw600dp-port/layout.xml b/res/values-w520dp-h768dp-port/layout.xml
similarity index 71%
copy from res/values-sw600dp-port/layout.xml
copy to res/values-w520dp-h768dp-port/layout.xml
index 46d71cb..98db0fe 100644
--- a/res/values-sw600dp-port/layout.xml
+++ b/res/values-w520dp-h768dp-port/layout.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  Copyright (C) 2014 The Android Open Source Project
+  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.
@@ -17,4 +17,7 @@
 
 <resources>
     <item name="activity_calculator" type="layout">@layout/activity_calculator_tablet_port</item>
-</resources>
\ No newline at end of file
+    <item name="display" type="layout">@layout/display_two_line</item>
+    <item name="pad_advanced" type="layout">@layout/pad_advanced_5x3</item>
+    <item name="pad_operator" type="layout">@layout/pad_operator_two_col</item>
+</resources>
diff --git a/res/values-sw600dp-port/styles.xml b/res/values-w520dp-h768dp-port/styles.xml
similarity index 79%
rename from res/values-sw600dp-port/styles.xml
rename to res/values-w520dp-h768dp-port/styles.xml
index f439521..4d5e2db 100644
--- a/res/values-sw600dp-port/styles.xml
+++ b/res/values-w520dp-h768dp-port/styles.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  Copyright (C) 2014 The Android Open Source Project
+  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.
@@ -15,8 +15,6 @@
   limitations under the License.
   -->
 
-
-<!-- Styles for portrait 600dip-wide tablet (e.g. Nexus 7). -->
 <resources xmlns:android="http://schemas.android.com/apk/res/android">
 
     <style name="DisplayTextStyle.Formula">
@@ -24,9 +22,9 @@
         <item name="android:paddingBottom">32dip</item>
         <item name="android:paddingStart">44dip</item>
         <item name="android:paddingEnd">44dip</item>
-        <item name="minTextSize">48sp</item>
-        <item name="maxTextSize">72sp</item>
-        <item name="stepTextSize">8sp</item>
+        <item name="minTextSize">48dip</item>
+        <item name="maxTextSize">72dip</item>
+        <item name="stepTextSize">8dip</item>
     </style>
 
     <style name="DisplayTextStyle.Result">
@@ -34,22 +32,22 @@
         <item name="android:paddingBottom">48dip</item>
         <item name="android:paddingStart">44dip</item>
         <item name="android:paddingEnd">44dip</item>
-        <item name="android:textSize">48sp</item>
+        <item name="android:textSize">48dip</item>
     </style>
 
     <style name="PadButtonStyle.Advanced">
         <item name="android:background">@drawable/pad_button_advanced_background</item>
         <item name="android:textColor">@color/pad_button_advanced_text_color</item>
-        <item name="android:textSize">27sp</item>
+        <item name="android:textSize">32dip</item>
     </style>
 
     <style name="PadButtonStyle.Advanced.Text">
         <item name="android:textAllCaps">true</item>
-        <item name="android:textSize">24sp</item>
+        <item name="android:textSize">28dip</item>
     </style>
 
     <style name="PadButtonStyle.Numeric">
-        <item name="android:textSize">36sp</item>
+        <item name="android:textSize">38dip</item>
     </style>
 
     <style name="PadButtonStyle.Numeric.Equals">
@@ -57,12 +55,12 @@
     </style>
 
     <style name="PadButtonStyle.Operator">
-        <item name="android:textSize">36sp</item>
+        <item name="android:textSize">38dip</item>
     </style>
 
     <style name="PadButtonStyle.Operator.Text">
         <item name="android:textAllCaps">true</item>
-        <item name="android:textSize">24sp</item>
+        <item name="android:textSize">28dip</item>
     </style>
 
     <style name="PadLayoutStyle.Advanced">
@@ -92,10 +90,4 @@
         <item name="android:paddingEnd">8dip</item>
     </style>
 
-    <style name="PadLinearLayoutStyle">
-        <item name="android:layout_width">match_parent</item>
-        <item name="android:layout_height">0dip</item>
-        <item name="android:layout_weight">500</item>
-    </style>
-
 </resources>
diff --git a/res/values-zh-rCN/strings.xml b/res/values-zh-rCN/strings.xml
index 4a0bd18..6152663 100644
--- a/res/values-zh-rCN/strings.xml
+++ b/res/values-zh-rCN/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"删除"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"显示反函数"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"隐藏反函数"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"无任何公式"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"数字和基本操作"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"高级操作"</string>
     <string name="error_nan" msgid="5674077944929888710">"不是数字"</string>
     <string name="error_syntax" msgid="4786987111228645602">"表达式错误"</string>
     <string name="error_aborted" msgid="3402238176316342537">"已中止"</string>
diff --git a/res/values-zh-rHK/strings.xml b/res/values-zh-rHK/strings.xml
index cfd5565..c18bf17 100644
--- a/res/values-zh-rHK/strings.xml
+++ b/res/values-zh-rHK/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"刪除"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"顯示反函數"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"隱藏反函數"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"沒有公式"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"數字和基本操作"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"進階操作"</string>
     <string name="error_nan" msgid="5674077944929888710">"非數字"</string>
     <string name="error_syntax" msgid="4786987111228645602">"算式錯誤"</string>
     <string name="error_aborted" msgid="3402238176316342537">"已中止"</string>
diff --git a/res/values-zh-rTW/strings.xml b/res/values-zh-rTW/strings.xml
index c13934b..97a7a5b 100644
--- a/res/values-zh-rTW/strings.xml
+++ b/res/values-zh-rTW/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"刪除"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"顯示反函數"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"隱藏反函數"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"沒有公式"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"數字和基本操作"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"進階操作"</string>
     <string name="error_nan" msgid="5674077944929888710">"非數字"</string>
     <string name="error_syntax" msgid="4786987111228645602">"運算式無效"</string>
     <string name="error_aborted" msgid="3402238176316342537">"已中止"</string>
diff --git a/res/values-zu/strings.xml b/res/values-zu/strings.xml
index 65ac7a9..03ed1f6 100644
--- a/res/values-zu/strings.xml
+++ b/res/values-zu/strings.xml
@@ -57,6 +57,9 @@
     <string name="desc_del" msgid="7879515781929311432">"susa"</string>
     <string name="desc_inv_off" msgid="3672218250519616068">"bonisa imisebenzi yemuva"</string>
     <string name="desc_inv_on" msgid="2515675590767677178">"fihla imisebenzi yemuva"</string>
+    <string name="desc_formula" msgid="8056588859637585007">"Ayikho ifomula"</string>
+    <string name="desc_num_pad" msgid="515720457459745571">"Izinombolo nokusebenza okuyisisekelo"</string>
+    <string name="desc_adv_pad" msgid="3794276256462677914">"Ukusebenza okuthuthukisiwe"</string>
     <string name="error_nan" msgid="5674077944929888710">"Akuyona inombolo"</string>
     <string name="error_syntax" msgid="4786987111228645602">"Ukusho okubi"</string>
     <string name="error_aborted" msgid="3402238176316342537">"Kukhanseliwe"</string>
diff --git a/res/values-port/dimens.xml b/res/values/dimens.xml
similarity index 93%
rename from res/values-port/dimens.xml
rename to res/values/dimens.xml
index dd66e9a..5218acd 100644
--- a/res/values-port/dimens.xml
+++ b/res/values/dimens.xml
@@ -18,6 +18,6 @@
 <resources>
 
     <!-- The margin between the pad pages when displayed using a view pager. -->
-    <dimen name="pad_page_margin">-24dip</dimen>
+    <dimen name="pad_page_margin">24dip</dimen>
 
 </resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 50bc983..d3d6b18 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -213,6 +213,19 @@
     <!-- Content description for "inv" button to hide inverse functions. [CHAR_LIMIT=NONE] -->
     <string name="desc_inv_on">hide inverse functions</string>
 
+    <!-- Content description for formula field when it is empty. [CHAR_LIMIT=NONE] -->
+    <string name="desc_formula">No formula</string>
+
+    <!-- Content description for the numeric/operation pad when slide-able. [CHAR_LIMIT=NONE] -->
+    <string name="desc_num_pad">Numbers and basic operations</string>
+    <!-- Content description for the advanced pad when slide-able. [CHAR_LIMIT=NONE] -->
+    <string name="desc_adv_pad">Advanced operations</string>
+    <!-- Content description for the pad pages when slide-able. -->
+    <string-array name="desc_pad_pages" translatable="false">
+        <item>@string/desc_num_pad</item>
+        <item>@string/desc_adv_pad</item>
+    </string-array>
+
     <!-- Error displayed when expression evaluates to an undefined result. [CHAR_LIMIT=20] -->
     <string name="error_nan">Not a number</string>
     <!-- Error displayed when expression contains a syntax error. [CHAR_LIMIT=20] -->
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 2a074e8..4935103 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -17,24 +17,6 @@
 
 <resources xmlns:android="http://schemas.android.com/apk/res/android">
 
-    <style name="Theme" parent="@android:style/Theme.Material.Light.DarkActionBar">
-        <item name="android:colorPrimary">@color/calculator_accent_color</item>
-        <item name="android:statusBarColor">@color/calculator_accent_color</item>
-        <item name="android:windowSoftInputMode">stateAlwaysHidden</item>
-    </style>
-
-    <style name="Theme.Calculator">
-        <item name="android:windowActionBar">false</item>
-        <item name="android:windowActionModeOverlay">true</item>
-        <item name="android:windowContentOverlay">@null</item>
-        <item name="android:windowNoTitle">true</item>
-        <item name="android:layoutDirection">ltr</item>
-    </style>
-
-    <style name="Theme.Licenses">
-        <item name="android:title">@string/menu_licenses</item>
-    </style>
-
     <style name="DisplayTextStyle" parent="@android:style/Widget.Material.Light.TextView">
         <item name="android:background">@android:color/transparent</item>
         <item name="android:cursorVisible">false</item>
diff --git a/res/values/themes.xml b/res/values/themes.xml
new file mode 100644
index 0000000..b8c7600
--- /dev/null
+++ b/res/values/themes.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+
+<resources>
+
+    <style name="Theme" parent="@android:style/Theme.Material.Light.DarkActionBar">
+        <item name="android:colorPrimary">@color/calculator_accent_color</item>
+        <item name="android:statusBarColor">@color/calculator_accent_color</item>
+        <item name="android:windowSoftInputMode">stateAlwaysHidden</item>
+    </style>
+
+    <style name="Theme.Calculator">
+        <item name="android:windowActionBar">false</item>
+        <item name="android:windowActionModeOverlay">true</item>
+        <item name="android:windowContentOverlay">@null</item>
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:layoutDirection">ltr</item>
+    </style>
+
+    <style name="Theme.Licenses">
+        <item name="android:title">@string/menu_licenses</item>
+    </style>
+
+</resources>
diff --git a/src/com/android/calculator2/AlertDialogFragment.java b/src/com/android/calculator2/AlertDialogFragment.java
index 49f9549..47f482f 100644
--- a/src/com/android/calculator2/AlertDialogFragment.java
+++ b/src/com/android/calculator2/AlertDialogFragment.java
@@ -20,7 +20,6 @@
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.DialogFragment;
-import android.content.Context;
 import android.content.DialogInterface;
 import android.os.Bundle;
 import android.support.annotation.Nullable;
@@ -41,7 +40,7 @@
          *            {@link DialogInterface#BUTTON_POSITIVE}) or the position
          *            of the item clicked
          */
-        public void onClick(AlertDialogFragment fragment, int which);
+        void onClick(AlertDialogFragment fragment, int which);
     }
 
     private static final String NAME = AlertDialogFragment.class.getName();
@@ -76,18 +75,21 @@
     @Override
     public Dialog onCreateDialog(Bundle savedInstanceState) {
         final Bundle args = getArguments() == null ? Bundle.EMPTY : getArguments();
-        final Context context = getContext();
-        final LayoutInflater inflater = LayoutInflater.from(context);
-        final TextView textView = (TextView) inflater.inflate(R.layout.dialog_message,
-                null /* root */);
+        final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+
+        final LayoutInflater inflater = LayoutInflater.from(builder.getContext());
+        final TextView textView = (TextView) inflater.inflate(
+                R.layout.dialog_message, null /* root */);
         textView.setText(args.getCharSequence(KEY_MESSAGE));
-        final AlertDialog.Builder builder = new AlertDialog.Builder(context)
-                .setView(textView)
-                .setNegativeButton(args.getCharSequence(KEY_BUTTON_NEGATIVE), null /* listener */);
+
+        builder.setView(textView);
+        builder.setNegativeButton(args.getCharSequence(KEY_BUTTON_NEGATIVE), null /* listener */);
+
         final CharSequence positiveButtonLabel = args.getCharSequence(KEY_BUTTON_POSITIVE);
         if (positiveButtonLabel != null) {
             builder.setPositiveButton(positiveButtonLabel, this);
         }
+
         return builder.create();
     }
 
diff --git a/src/com/android/calculator2/BoundedRational.java b/src/com/android/calculator2/BoundedRational.java
index dc132f6..e9e6f05 100644
--- a/src/com/android/calculator2/BoundedRational.java
+++ b/src/com/android/calculator2/BoundedRational.java
@@ -16,6 +16,7 @@
 
 package com.android.calculator2;
 
+import java.util.Random;
 
 import java.math.BigInteger;
 import com.hp.creals.CR;
@@ -35,7 +36,7 @@
     // much faster.
     // TODO: Maybe eventually make this extend Number?
 
-    private static final int MAX_SIZE = 800; // total, in bits
+    private static final int MAX_SIZE = 10000; // total, in bits
 
     private final BigInteger mNum;
     private final BigInteger mDen;
@@ -70,11 +71,11 @@
 
     /**
      * Convert to readable String.
-     * Intended for output output to user.  More expensive, less useful for debugging than
+     * Intended for output to user.  More expensive, less useful for debugging than
      * toString().  Not internationalized.
      */
     public String toNiceString() {
-        BoundedRational nicer = reduce().positiveDen();
+        final BoundedRational nicer = reduce().positiveDen();
         String result = nicer.mNum.toString();
         if (!nicer.mDen.equals(BigInteger.ONE)) {
             result += "/" + nicer.mDen;
@@ -90,20 +91,51 @@
     }
 
     /**
+     * Returns a truncated (rounded towards 0) representation of the result.
+     * Includes n digits to the right of the decimal point.
+     * @param n result precision, >= 0
+     */
+    public String toStringTruncated(int n) {
+        String digits = mNum.abs().multiply(BigInteger.TEN.pow(n)).divide(mDen.abs()).toString();
+        int len = digits.length();
+        if (len < n + 1) {
+            digits = StringUtils.repeat('0', n + 1 - len) + digits;
+            len = n + 1;
+        }
+        return (signum() < 0 ? "-" : "") + digits.substring(0, len - n) + "."
+                + digits.substring(len - n);
+    }
+
+    /**
      * Return a double approximation.
-     * Primarily for debugging.
+     * The result is correctly rounded if numerator and denominator are
+     * exactly representable as a double.
+     * TODO: This should always be correctly rounded.
      */
     public double doubleValue() {
         return mNum.doubleValue() / mDen.doubleValue();
     }
 
-    public CR CRValue() {
+    public CR crValue() {
         return CR.valueOf(mNum).divide(CR.valueOf(mDen));
     }
 
+    public int intValue() {
+        BoundedRational reduced = reduce();
+        if (!reduced.mDen.equals(BigInteger.ONE)) {
+            throw new ArithmeticException("intValue of non-int");
+        }
+        return reduced.mNum.intValue();
+    }
+
     // Approximate number of bits to left of binary point.
+    // Negative indicates leading zeroes to the right of binary point.
     public int wholeNumberBits() {
-        return mNum.bitLength() - mDen.bitLength();
+        if (mNum.signum() == 0) {
+            return Integer.MIN_VALUE;
+        } else {
+            return mNum.bitLength() - mDen.bitLength();
+        }
     }
 
     private boolean tooBig() {
@@ -135,12 +167,15 @@
         return new BoundedRational(mNum.divide(divisor), mDen.divide(divisor));
     }
 
+    static Random sReduceRng = new Random();
+
     /**
      * Return a possibly reduced version of this that's not tooBig().
      * Return null if none exists.
      */
     private BoundedRational maybeReduce() {
-        if (!tooBig()) {
+        // Reduce randomly, with 1/16 probability, or if the result is too big.
+        if (!tooBig() && (sReduceRng.nextInt() & 0xf) != 0) {
             return this;
         }
         BoundedRational result = positiveDen();
@@ -193,6 +228,10 @@
         return new BoundedRational(num,den).maybeReduce();
     }
 
+    /**
+     * Return the argument, but with the opposite sign.
+     * Returns null only for a null argument.
+     */
     public static BoundedRational negate(BoundedRational r) {
         if (r == null) {
             return null;
@@ -204,15 +243,29 @@
         return add(r1, negate(r2));
     }
 
-    static BoundedRational multiply(BoundedRational r1, BoundedRational r2) {
+    /**
+     * Return product of r1 and r2 without reducing the result.
+     */
+    private static BoundedRational rawMultiply(BoundedRational r1, BoundedRational r2) {
         // It's tempting but marginally unsound to reduce 0 * null to 0.  The null could represent
         // an infinite value, for which we failed to throw an exception because it was too big.
         if (r1 == null || r2 == null) {
             return null;
         }
+        // Optimize the case of our special ONE constant, since that's cheap and somewhat frequent.
+        if (r1 == ONE) {
+            return r2;
+        }
+        if (r2 == ONE) {
+            return r1;
+        }
         final BigInteger num = r1.mNum.multiply(r2.mNum);
         final BigInteger den = r1.mDen.multiply(r2.mDen);
-        return new BoundedRational(num,den).maybeReduce();
+        return new BoundedRational(num,den);
+    }
+
+    static BoundedRational multiply(BoundedRational r1, BoundedRational r2) {
+        return rawMultiply(r1, r2).maybeReduce();
     }
 
     public static class ZeroDivisionException extends ArithmeticException {
@@ -222,7 +275,7 @@
     }
 
     /**
-     * Return the reciprocal of r (or null).
+     * Return the reciprocal of r (or null if the argument was null).
      */
     static BoundedRational inverse(BoundedRational r) {
         if (r == null) {
@@ -261,10 +314,15 @@
     public final static BoundedRational ZERO = new BoundedRational(0);
     public final static BoundedRational HALF = new BoundedRational(1,2);
     public final static BoundedRational MINUS_HALF = new BoundedRational(-1,2);
+    public final static BoundedRational THIRD = new BoundedRational(1,3);
+    public final static BoundedRational QUARTER = new BoundedRational(1,4);
+    public final static BoundedRational SIXTH = new BoundedRational(1,6);
     public final static BoundedRational ONE = new BoundedRational(1);
     public final static BoundedRational MINUS_ONE = new BoundedRational(-1);
     public final static BoundedRational TWO = new BoundedRational(2);
     public final static BoundedRational MINUS_TWO = new BoundedRational(-2);
+    public final static BoundedRational TEN = new BoundedRational(10);
+    public final static BoundedRational TWELVE = new BoundedRational(12);
     public final static BoundedRational THIRTY = new BoundedRational(30);
     public final static BoundedRational MINUS_THIRTY = new BoundedRational(-30);
     public final static BoundedRational FORTY_FIVE = new BoundedRational(45);
@@ -272,195 +330,40 @@
     public final static BoundedRational NINETY = new BoundedRational(90);
     public final static BoundedRational MINUS_NINETY = new BoundedRational(-90);
 
-    private static BoundedRational map0to0(BoundedRational r) {
-        if (r == null) {
-            return null;
-        }
-        if (r.mNum.signum() == 0) {
-            return ZERO;
-        }
-        return null;
-    }
-
-    private static BoundedRational map0to1(BoundedRational r) {
-        if (r == null) {
-            return null;
-        }
-        if (r.mNum.signum() == 0) {
-            return ONE;
-        }
-        return null;
-    }
-
-    private static BoundedRational map1to0(BoundedRational r) {
-        if (r == null) {
-            return null;
-        }
-        if (r.mNum.equals(r.mDen)) {
-            return ZERO;
-        }
-        return null;
-    }
-
-    // Throw an exception if the argument is definitely out of bounds for asin or acos.
-    private static void checkAsinDomain(BoundedRational r) {
-        if (r == null) {
-            return;
-        }
-        if (r.mNum.abs().compareTo(r.mDen.abs()) > 0) {
-            throw new ArithmeticException("inverse trig argument out of range");
-        }
-    }
-
-    public static BoundedRational sin(BoundedRational r) {
-        return map0to0(r);
-    }
-
-    private final static BigInteger BIG360 = BigInteger.valueOf(360);
-
-    public static BoundedRational degreeSin(BoundedRational r) {
-        final BigInteger r_BI = asBigInteger(r);
-        if (r_BI == null) {
-            return null;
-        }
-        final int r_int = r_BI.mod(BIG360).intValue();
-        if (r_int % 30 != 0) {
-            return null;
-        }
-        switch (r_int / 10) {
-        case 0:
-            return ZERO;
-        case 3: // 30 degrees
-            return HALF;
-        case 9:
-            return ONE;
-        case 15:
-            return HALF;
-        case 18: // 180 degrees
-            return ZERO;
-        case 21:
-            return MINUS_HALF;
-        case 27:
-            return MINUS_ONE;
-        case 33:
-            return MINUS_HALF;
-        default:
-            return null;
-        }
-    }
-
-    public static BoundedRational asin(BoundedRational r) {
-        checkAsinDomain(r);
-        return map0to0(r);
-    }
-
-    public static BoundedRational degreeAsin(BoundedRational r) {
-        checkAsinDomain(r);
-        final BigInteger r2_BI = asBigInteger(multiply(r, TWO));
-        if (r2_BI == null) {
-            return null;
-        }
-        final int r2_int = r2_BI.intValue();
-        // Somewhat surprisingly, it seems to be the case that the following covers all rational
-        // cases:
-        switch (r2_int) {
-        case -2: // Corresponding to -1 argument
-            return MINUS_NINETY;
-        case -1: // Corresponding to -1/2 argument
-            return MINUS_THIRTY;
-        case 0:
-            return ZERO;
-        case 1:
-            return THIRTY;
-        case 2:
-            return NINETY;
-        default:
-            throw new AssertionError("Impossible asin arg");
-        }
-    }
-
-    public static BoundedRational tan(BoundedRational r) {
-        // Unlike the degree case, we cannot check for the singularity, since it occurs at an
-        // irrational argument.
-        return map0to0(r);
-    }
-
-    public static BoundedRational degreeTan(BoundedRational r) {
-        final BoundedRational degSin = degreeSin(r);
-        final BoundedRational degCos = degreeCos(r);
-        if (degCos != null && degCos.mNum.signum() == 0) {
-            throw new ArithmeticException("Tangent undefined");
-        }
-        return divide(degSin, degCos);
-    }
-
-    public static BoundedRational atan(BoundedRational r) {
-        return map0to0(r);
-    }
-
-    public static BoundedRational degreeAtan(BoundedRational r) {
-        final BigInteger r_BI = asBigInteger(r);
-        if (r_BI == null) {
-            return null;
-        }
-        if (r_BI.abs().compareTo(BigInteger.ONE) > 0) {
-            return null;
-        }
-        final int r_int = r_BI.intValue();
-        // Again, these seem to be all rational cases:
-        switch (r_int) {
-        case -1:
-            return MINUS_FORTY_FIVE;
-        case 0:
-            return ZERO;
-        case 1:
-            return FORTY_FIVE;
-        default:
-            throw new AssertionError("Impossible atan arg");
-        }
-    }
-
-    public static BoundedRational cos(BoundedRational r) {
-        return map0to1(r);
-    }
-
-    public static BoundedRational degreeCos(BoundedRational r) {
-        return degreeSin(add(r, NINETY));
-    }
-
-    public static BoundedRational acos(BoundedRational r) {
-        checkAsinDomain(r);
-        return map1to0(r);
-    }
-
-    public static BoundedRational degreeAcos(BoundedRational r) {
-        final BoundedRational asin_r = degreeAsin(r);
-        return subtract(NINETY, asin_r);
-    }
-
     private static final BigInteger BIG_TWO = BigInteger.valueOf(2);
 
     /**
+     * Compute integral power of this, assuming this has been reduced and exp is >= 0.
+     */
+    private BoundedRational rawPow(BigInteger exp) {
+        if (exp.equals(BigInteger.ONE)) {
+            return this;
+        }
+        if (exp.and(BigInteger.ONE).intValue() == 1) {
+            return rawMultiply(rawPow(exp.subtract(BigInteger.ONE)), this);
+        }
+        if (exp.signum() == 0) {
+            return ONE;
+        }
+        BoundedRational tmp = rawPow(exp.shiftRight(1));
+        if (Thread.interrupted()) {
+            throw new CR.AbortedException();
+        }
+        return rawMultiply(tmp, tmp);
+    }
+
+    /**
      * Compute an integral power of this.
      */
-    private BoundedRational pow(BigInteger exp) {
+    public BoundedRational pow(BigInteger exp) {
         if (exp.signum() < 0) {
             return inverse(pow(exp.negate()));
         }
         if (exp.equals(BigInteger.ONE)) {
             return this;
         }
-        if (exp.and(BigInteger.ONE).intValue() == 1) {
-            return multiply(pow(exp.subtract(BigInteger.ONE)), this);
-        }
-        if (exp.signum() == 0) {
-            return ONE;
-        }
-        BoundedRational tmp = pow(exp.shiftRight(1));
-        if (Thread.interrupted()) {
-            throw new CR.AbortedException();
-        }
-        return multiply(tmp, tmp);
+        // Reducing once at the beginning means there's no point in reducing later.
+        return reduce().rawPow(exp);
     }
 
     public static BoundedRational pow(BoundedRational base, BoundedRational exp) {
@@ -482,111 +385,6 @@
         return base.pow(exp.mNum);
     }
 
-    public static BoundedRational ln(BoundedRational r) {
-        if (r != null && r.signum() <= 0) {
-            throw new ArithmeticException("log(non-positive)");
-        }
-        return map1to0(r);
-    }
-
-    public static BoundedRational exp(BoundedRational r) {
-        return map0to1(r);
-    }
-
-    /**
-     * Return the base 10 log of n, if n is a power of 10, -1 otherwise.
-     * n must be positive.
-     */
-    private static long b10Log(BigInteger n) {
-        // This algorithm is very naive, but we doubt it matters.
-        long count = 0;
-        while (n.mod(BigInteger.TEN).signum() == 0) {
-            if (Thread.interrupted()) {
-                throw new CR.AbortedException();
-            }
-            n = n.divide(BigInteger.TEN);
-            ++count;
-        }
-        if (n.equals(BigInteger.ONE)) {
-            return count;
-        }
-        return -1;
-    }
-
-    public static BoundedRational log(BoundedRational r) {
-        if (r == null) {
-            return null;
-        }
-        if (r.signum() <= 0) {
-            throw new ArithmeticException("log(non-positive)");
-        }
-        r = r.reduce().positiveDen();
-        if (r == null) {
-            return null;
-        }
-        if (r.mDen.equals(BigInteger.ONE)) {
-            long log = b10Log(r.mNum);
-            if (log != -1) {
-                return new BoundedRational(log);
-            }
-        } else if (r.mNum.equals(BigInteger.ONE)) {
-            long log = b10Log(r.mDen);
-            if (log != -1) {
-                return new BoundedRational(-log);
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Generalized factorial.
-     * Compute n * (n - step) * (n - 2 * step) * etc.  This can be used to compute factorial a bit
-     * faster, especially if BigInteger uses sub-quadratic multiplication.
-     */
-    private static BigInteger genFactorial(long n, long step) {
-        if (n > 4 * step) {
-            BigInteger prod1 = genFactorial(n, 2 * step);
-            if (Thread.interrupted()) {
-                throw new CR.AbortedException();
-            }
-            BigInteger prod2 = genFactorial(n - step, 2 * step);
-            if (Thread.interrupted()) {
-                throw new CR.AbortedException();
-            }
-            return prod1.multiply(prod2);
-        } else {
-            if (n == 0) {
-                return BigInteger.ONE;
-            }
-            BigInteger res = BigInteger.valueOf(n);
-            for (long i = n - step; i > 1; i -= step) {
-                res = res.multiply(BigInteger.valueOf(i));
-            }
-            return res;
-        }
-    }
-
-    /**
-     * Factorial function.
-     * Always produces non-null (or exception) when called on non-null r.
-     */
-    public static BoundedRational fact(BoundedRational r) {
-        if (r == null) {
-            return null;
-        }
-        final BigInteger rAsInt = asBigInteger(r);
-        if (rAsInt == null) {
-            throw new ArithmeticException("Non-integral factorial argument");
-        }
-        if (rAsInt.signum() < 0) {
-            throw new ArithmeticException("Negative factorial argument");
-        }
-        if (rAsInt.bitLength() > 30) {
-            // Will fail.  LongValue() may not work. Punt now.
-            throw new ArithmeticException("Factorial argument too big");
-        }
-        return new BoundedRational(genFactorial(rAsInt.longValue(), 1));
-    }
 
     private static final BigInteger BIG_FIVE = BigInteger.valueOf(5);
     private static final BigInteger BIG_MINUS_ONE = BigInteger.valueOf(-1);
diff --git a/src/com/android/calculator2/Calculator.java b/src/com/android/calculator2/Calculator.java
index 1cb3880..e2c16b3 100644
--- a/src/com/android/calculator2/Calculator.java
+++ b/src/com/android/calculator2/Calculator.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 The Android Open Source Project
+ * 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.
@@ -31,9 +31,10 @@
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.PropertyValuesHolder;
+import android.app.ActionBar;
 import android.app.Activity;
-import android.app.AlertDialog;
 import android.content.ClipData;
+import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.res.Resources;
@@ -42,23 +43,27 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.support.annotation.NonNull;
+import android.support.v4.content.ContextCompat;
 import android.support.v4.view.ViewPager;
-import android.text.SpannableString;
+import android.text.Editable;
 import android.text.SpannableStringBuilder;
 import android.text.Spanned;
-import android.text.style.ForegroundColorSpan;
 import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.text.style.ForegroundColorSpan;
 import android.util.Property;
+import android.view.ActionMode;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.View;
-import android.view.View.OnKeyListener;
 import android.view.View.OnLongClickListener;
 import android.view.ViewAnimationUtils;
 import android.view.ViewGroupOverlay;
+import android.view.ViewTreeObserver;
 import android.view.animation.AccelerateDecelerateInterpolator;
+import android.widget.HorizontalScrollView;
 import android.widget.TextView;
 import android.widget.Toolbar;
 
@@ -123,81 +128,55 @@
         }
     };
 
-    // We currently assume that the formula does not change out from under us in
-    // any way. We explicitly handle all input to the formula here.
-    private final OnKeyListener mFormulaOnKeyListener = new OnKeyListener() {
+    private static final String NAME = "Calculator";
+    private static final String KEY_DISPLAY_STATE = NAME + "_display_state";
+    private static final String KEY_UNPROCESSED_CHARS = NAME + "_unprocessed_chars";
+    /**
+     * Associated value is a byte array holding the evaluator state.
+     */
+    private static final String KEY_EVAL_STATE = NAME + "_eval_state";
+    private static final String KEY_INVERSE_MODE = NAME + "_inverse_mode";
+
+    private final ViewTreeObserver.OnPreDrawListener mPreDrawListener =
+            new ViewTreeObserver.OnPreDrawListener() {
         @Override
-        public boolean onKey(View view, int keyCode, KeyEvent keyEvent) {
-            stopActionMode();
-            // Never consume DPAD key events.
-            switch (keyCode) {
-                case KeyEvent.KEYCODE_DPAD_UP:
-                case KeyEvent.KEYCODE_DPAD_DOWN:
-                case KeyEvent.KEYCODE_DPAD_LEFT:
-                case KeyEvent.KEYCODE_DPAD_RIGHT:
-                    return false;
-            }
-            // Always cancel unrequested in-progress evaluation, so that we don't have
-            // to worry about subsequent asynchronous completion.
-            // Requested in-progress evaluations are handled below.
-            if (mCurrentState != CalculatorState.EVALUATE) {
-                mEvaluator.cancelAll(true);
-            }
-            // In other cases we go ahead and process the input normally after cancelling:
-            if (keyEvent.getAction() != KeyEvent.ACTION_UP) {
-                return true;
-            }
-            switch (keyCode) {
-                case KeyEvent.KEYCODE_NUMPAD_ENTER:
-                case KeyEvent.KEYCODE_ENTER:
-                case KeyEvent.KEYCODE_DPAD_CENTER:
-                    mCurrentButton = mEqualButton;
-                    onEquals();
-                    return true;
-                case KeyEvent.KEYCODE_DEL:
-                    mCurrentButton = mDeleteButton;
-                    onDelete();
-                    return true;
-                default:
-                    cancelIfEvaluating(false);
-                    final int raw = keyEvent.getKeyCharacterMap()
-                            .get(keyCode, keyEvent.getMetaState());
-                    if ((raw & KeyCharacterMap.COMBINING_ACCENT) != 0) {
-                        return true; // discard
-                    }
-                    // Try to discard non-printing characters and the like.
-                    // The user will have to explicitly delete other junk that gets past us.
-                    if (Character.isIdentifierIgnorable(raw)
-                            || Character.isWhitespace(raw)) {
-                        return true;
-                    }
-                    char c = (char) raw;
-                    if (c == '=') {
-                        mCurrentButton = mEqualButton;
-                        onEquals();
-                    } else {
-                        addChars(String.valueOf(c), true);
-                        redisplayAfterFormulaChange();
-                    }
+        public boolean onPreDraw() {
+            mFormulaContainer.scrollTo(mFormulaText.getRight(), 0);
+            final ViewTreeObserver observer = mFormulaContainer.getViewTreeObserver();
+            if (observer.isAlive()) {
+                observer.removeOnPreDrawListener(this);
             }
             return false;
         }
     };
 
-    private static final String NAME = Calculator.class.getName();
-    private static final String KEY_DISPLAY_STATE = NAME + "_display_state";
-    private static final String KEY_UNPROCESSED_CHARS = NAME + "_unprocessed_chars";
-    private static final String KEY_EVAL_STATE = NAME + "_eval_state";
-                // Associated value is a byte array holding both mCalculatorState
-                // and the (much more complex) evaluator state.
+    private final TextWatcher mFormulaTextWatcher = new TextWatcher() {
+        @Override
+        public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) {
+        }
+
+        @Override
+        public void onTextChanged(CharSequence charSequence, int start, int count, int after) {
+        }
+
+        @Override
+        public void afterTextChanged(Editable editable) {
+            final ViewTreeObserver observer = mFormulaContainer.getViewTreeObserver();
+            if (observer.isAlive()) {
+                observer.removeOnPreDrawListener(mPreDrawListener);
+                observer.addOnPreDrawListener(mPreDrawListener);
+            }
+        }
+    };
 
     private CalculatorState mCurrentState;
     private Evaluator mEvaluator;
 
-    private View mDisplayView;
+    private CalculatorDisplay mDisplayView;
     private TextView mModeView;
     private CalculatorText mFormulaText;
     private CalculatorResult mResultText;
+    private HorizontalScrollView mFormulaContainer;
 
     private ViewPager mPadViewPager;
     private View mDeleteButton;
@@ -221,6 +200,9 @@
     // TODO: should probably match this to the error color?
     private ForegroundColorSpan mUnprocessedColorSpan = new ForegroundColorSpan(Color.RED);
 
+    // Whether the display is one line.
+    private boolean mOneLine;
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -230,10 +212,19 @@
         // Hide all default options in the ActionBar.
         getActionBar().setDisplayOptions(0);
 
-        mDisplayView = findViewById(R.id.display);
+        // Ensure the toolbar stays visible while the options menu is displayed.
+        getActionBar().addOnMenuVisibilityListener(new ActionBar.OnMenuVisibilityListener() {
+            @Override
+            public void onMenuVisibilityChanged(boolean isVisible) {
+                mDisplayView.setForceToolbarVisible(isVisible);
+            }
+        });
+
+        mDisplayView = (CalculatorDisplay) findViewById(R.id.display);
         mModeView = (TextView) findViewById(R.id.mode);
         mFormulaText = (CalculatorText) findViewById(R.id.formula);
         mResultText = (CalculatorResult) findViewById(R.id.result);
+        mFormulaContainer = (HorizontalScrollView) findViewById(R.id.formula_container);
 
         mPadViewPager = (ViewPager) findViewById(R.id.pad_pager);
         mDeleteButton = findViewById(R.id.del);
@@ -246,6 +237,8 @@
         mInverseToggle = (TextView) findViewById(R.id.toggle_inv);
         mModeToggle = (TextView) findViewById(R.id.toggle_mode);
 
+        mOneLine = mResultText.getVisibility() == View.INVISIBLE;
+
         mInvertibleButtons = new View[] {
                 findViewById(R.id.fun_sin),
                 findViewById(R.id.fun_cos),
@@ -290,12 +283,13 @@
             mEvaluator.clear();
         }
 
-        mFormulaText.setOnKeyListener(mFormulaOnKeyListener);
         mFormulaText.setOnTextSizeChangeListener(this);
         mFormulaText.setOnPasteListener(this);
+        mFormulaText.addTextChangedListener(mFormulaTextWatcher);
         mDeleteButton.setOnLongClickListener(this);
 
-        onInverseToggled(mInverseToggle.isSelected());
+        onInverseToggled(savedInstanceState != null
+                && savedInstanceState.getBoolean(KEY_INVERSE_MODE));
         onModeChanged(mEvaluator.getDegreeMode());
 
         if (mCurrentState != CalculatorState.INPUT) {
@@ -313,6 +307,14 @@
     }
 
     @Override
+    protected void onResume() {
+        super.onResume();
+
+        // Always temporarily show the toolbar initially on launch.
+        showAndMaybeHideToolbar();
+    }
+
+    @Override
     protected void onSaveInstanceState(@NonNull Bundle outState) {
         mEvaluator.cancelAll(true);
         // If there's an animation in progress, cancel it first to ensure our state is up-to-date.
@@ -331,6 +333,7 @@
             throw new AssertionError("Impossible IO exception", e);
         }
         outState.putByteArray(KEY_EVAL_STATE, byteArrayStream.toByteArray());
+        outState.putBoolean(KEY_INVERSE_MODE, mInverseToggle.isSelected());
     }
 
     // Set the state, updating delete label and display colors.
@@ -352,47 +355,63 @@
                 mClearButton.setVisibility(View.GONE);
             }
 
+            if (mOneLine) {
+                if (mCurrentState == CalculatorState.RESULT
+                        || mCurrentState == CalculatorState.EVALUATE
+                        || mCurrentState == CalculatorState.ANIMATE) {
+                    mFormulaText.setVisibility(View.VISIBLE);
+                    mResultText.setVisibility(View.VISIBLE);
+                } else if (mCurrentState == CalculatorState.ERROR) {
+                    mFormulaText.setVisibility(View.INVISIBLE);
+                    mResultText.setVisibility(View.VISIBLE);
+                } else {
+                    mFormulaText.setVisibility(View.VISIBLE);
+                    mResultText.setVisibility(View.INVISIBLE);
+                }
+            }
+
             if (mCurrentState == CalculatorState.ERROR) {
-                final int errorColor = getColor(R.color.calculator_error_color);
+                final int errorColor =
+                        ContextCompat.getColor(this, R.color.calculator_error_color);
                 mFormulaText.setTextColor(errorColor);
                 mResultText.setTextColor(errorColor);
                 getWindow().setStatusBarColor(errorColor);
             } else if (mCurrentState != CalculatorState.RESULT) {
-                mFormulaText.setTextColor(getColor(R.color.display_formula_text_color));
-                mResultText.setTextColor(getColor(R.color.display_result_text_color));
-                getWindow().setStatusBarColor(getColor(R.color.calculator_accent_color));
+                mFormulaText.setTextColor(
+                        ContextCompat.getColor(this, R.color.display_formula_text_color));
+                mResultText.setTextColor(
+                        ContextCompat.getColor(this, R.color.display_result_text_color));
+                getWindow().setStatusBarColor(
+                        ContextCompat.getColor(this, R.color.calculator_accent_color));
             }
 
             invalidateOptionsMenu();
         }
     }
 
-    // Stop any active ActionMode.  Return true if there was one.
-    private boolean stopActionMode() {
-        if (mResultText.stopActionMode()) {
+    @Override
+    public void onActionModeStarted(ActionMode mode) {
+        super.onActionModeStarted(mode);
+        if (mode.getTag() == CalculatorText.TAG_ACTION_MODE) {
+            mFormulaContainer.scrollTo(mFormulaText.getRight(), 0);
+        }
+    }
+
+    /**
+     * Stop any active ActionMode or ContextMenu for copy/paste actions.
+     * Return true if there was one.
+     */
+    private boolean stopActionModeOrContextMenu() {
+        if (mResultText.stopActionModeOrContextMenu()) {
             return true;
         }
-        if (mFormulaText.stopActionMode()) {
+        if (mFormulaText.stopActionModeOrContextMenu()) {
             return true;
         }
         return false;
     }
 
     @Override
-    public void onBackPressed() {
-        if (!stopActionMode()) {
-            if (mPadViewPager != null && mPadViewPager.getCurrentItem() != 0) {
-                // Select the previous pad.
-                mPadViewPager.setCurrentItem(mPadViewPager.getCurrentItem() - 1);
-            } else {
-                // If the user is currently looking at the first pad (or the pad is not paged),
-                // allow the system to handle the Back button.
-                super.onBackPressed();
-            }
-        }
-    }
-
-    @Override
     public void onUserInteraction() {
         super.onUserInteraction();
 
@@ -403,12 +422,83 @@
         }
     }
 
+    @Override
+    public void onBackPressed() {
+        if (!stopActionModeOrContextMenu()) {
+            if (mPadViewPager != null && mPadViewPager.getCurrentItem() != 0) {
+                // Select the previous pad.
+                mPadViewPager.setCurrentItem(mPadViewPager.getCurrentItem() - 1);
+            } else {
+                // If the user is currently looking at the first pad (or the pad is not paged),
+                // allow the system to handle the Back button.
+                super.onBackPressed();
+            }
+        }
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        // Allow the system to handle special key codes (e.g. "BACK" or "DPAD").
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_BACK:
+            case KeyEvent.KEYCODE_DPAD_UP:
+            case KeyEvent.KEYCODE_DPAD_DOWN:
+            case KeyEvent.KEYCODE_DPAD_LEFT:
+            case KeyEvent.KEYCODE_DPAD_RIGHT:
+                return super.onKeyUp(keyCode, event);
+        }
+
+        // Stop the action mode or context menu if it's showing.
+        stopActionModeOrContextMenu();
+
+        // Always cancel unrequested in-progress evaluation, so that we don't have to worry about
+        // subsequent asynchronous completion.
+        // Requested in-progress evaluations are handled below.
+        if (mCurrentState != CalculatorState.EVALUATE) {
+            mEvaluator.cancelAll(true);
+        }
+
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_NUMPAD_ENTER:
+            case KeyEvent.KEYCODE_ENTER:
+            case KeyEvent.KEYCODE_DPAD_CENTER:
+                mCurrentButton = mEqualButton;
+                onEquals();
+                return true;
+            case KeyEvent.KEYCODE_DEL:
+                mCurrentButton = mDeleteButton;
+                onDelete();
+                return true;
+            default:
+                cancelIfEvaluating(false);
+                final int raw = event.getKeyCharacterMap().get(keyCode, event.getMetaState());
+                if ((raw & KeyCharacterMap.COMBINING_ACCENT) != 0) {
+                    return true; // discard
+                }
+                // Try to discard non-printing characters and the like.
+                // The user will have to explicitly delete other junk that gets past us.
+                if (Character.isIdentifierIgnorable(raw) || Character.isWhitespace(raw)) {
+                    return true;
+                }
+                char c = (char) raw;
+                if (c == '=') {
+                    mCurrentButton = mEqualButton;
+                    onEquals();
+                } else {
+                    addChars(String.valueOf(c), true);
+                    redisplayAfterFormulaChange();
+                }
+                return true;
+        }
+    }
+
     /**
      * Invoked whenever the inverse button is toggled to update the UI.
      *
      * @param showInverse {@code true} if inverse functions should be shown
      */
     private void onInverseToggled(boolean showInverse) {
+        mInverseToggle.setSelected(showInverse);
         if (showInverse) {
             mInverseToggle.setContentDescription(getString(R.string.desc_inv_on));
             for (View invertibleButton : mInvertibleButtons) {
@@ -447,6 +537,9 @@
             mModeToggle.setText(R.string.mode_deg);
             mModeToggle.setContentDescription(getString(R.string.desc_switch_deg));
         }
+
+        // Show the toolbar to highlight the mode change.
+        showAndMaybeHideToolbar();
     }
 
     /**
@@ -492,22 +585,53 @@
         // TODO: Could do this more incrementally.
         redisplayFormula();
         setState(CalculatorState.INPUT);
-        if (mEvaluator.getExpr().hasInterestingOps()) {
-            mEvaluator.evaluateAndShowResult();
-        } else {
+        if (haveUnprocessed()) {
             mResultText.clear();
+            // Force reevaluation when text is deleted, even if expression is unchanged.
+            mEvaluator.touch();
+        } else {
+            if (mEvaluator.getExpr().hasInterestingOps()) {
+                mEvaluator.evaluateAndShowResult();
+            } else {
+                mResultText.clear();
+            }
+        }
+    }
+
+    /**
+     * Show the toolbar.
+     * Automatically hide it again if it's not relevant to current formula.
+     */
+    private void showAndMaybeHideToolbar() {
+        final boolean shouldBeVisible =
+                mCurrentState == CalculatorState.INPUT && mEvaluator.hasTrigFuncs();
+        mDisplayView.showToolbar(!shouldBeVisible);
+    }
+
+    /**
+     * Display or hide the toolbar depending on calculator state.
+     */
+    private void showOrHideToolbar() {
+        final boolean shouldBeVisible =
+                mCurrentState == CalculatorState.INPUT && mEvaluator.hasTrigFuncs();
+        if (shouldBeVisible) {
+            mDisplayView.showToolbar(false);
+        } else {
+            mDisplayView.hideToolbar();
         }
     }
 
     public void onButtonClick(View view) {
         // Any animation is ended before we get here.
         mCurrentButton = view;
-        stopActionMode();
+        stopActionModeOrContextMenu();
+
         // See onKey above for the rationale behind some of the behavior below:
         if (mCurrentState != CalculatorState.EVALUATE) {
             // Cancel evaluations that were not specifically requested.
             mEvaluator.cancelAll(true);
         }
+
         final int id = view.getId();
         switch (id) {
             case R.id.eq:
@@ -518,7 +642,7 @@
                 break;
             case R.id.clr:
                 onClear();
-                break;
+                return;  // Toolbar visibility adjusted at end of animation.
             case R.id.toggle_inv:
                 final boolean selected = !mInverseToggle.isSelected();
                 mInverseToggle.setSelected(selected);
@@ -539,16 +663,23 @@
                 onModeChanged(mode);
                 setState(CalculatorState.INPUT);
                 mResultText.clear();
-                if (mEvaluator.getExpr().hasInterestingOps()) {
+                if (!haveUnprocessed() && mEvaluator.getExpr().hasInterestingOps()) {
                     mEvaluator.evaluateAndShowResult();
                 }
-                break;
+                return;  // onModeChanged adjusted toolbar visibility.
             default:
                 cancelIfEvaluating(false);
-                addExplicitKeyToExpr(id);
-                redisplayAfterFormulaChange();
+                if (haveUnprocessed()) {
+                    // For consistency, append as uninterpreted characters.
+                    // This may actually be useful for a left parenthesis.
+                    addChars(KeyMaps.toString(this, id), true);
+                } else {
+                    addExplicitKeyToExpr(id);
+                    redisplayAfterFormulaChange();
+                }
                 break;
         }
+        showOrHideToolbar();
     }
 
     void redisplayFormula() {
@@ -559,6 +690,8 @@
                     Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
         }
         mFormulaText.changeTextTo(formula);
+        mFormulaText.setContentDescription(TextUtils.isEmpty(formula)
+                ? getString(R.string.desc_formula) : null);
     }
 
     @Override
@@ -638,11 +771,20 @@
         }
     }
 
+    private boolean haveUnprocessed() {
+        return mUnprocessedChars != null && !mUnprocessedChars.isEmpty();
+    }
+
     private void onEquals() {
-        // In non-INPUT state assume this was redundant and ignore it.
-        if (mCurrentState == CalculatorState.INPUT && !mEvaluator.getExpr().isEmpty()) {
-            setState(CalculatorState.EVALUATE);
-            mEvaluator.requireResult();
+        // Ignore if in non-INPUT state, or if there are no operators.
+        if (mCurrentState == CalculatorState.INPUT) {
+            if (haveUnprocessed()) {
+                setState(CalculatorState.EVALUATE);
+                onError(R.string.error_syntax);
+            } else if (mEvaluator.getExpr().hasInterestingOps()) {
+                setState(CalculatorState.EVALUATE);
+                mEvaluator.requireResult();
+            }
         }
     }
 
@@ -655,18 +797,12 @@
         // If there is an in-progress explicit evaluation, just cancel it and return.
         if (cancelIfEvaluating(false)) return;
         setState(CalculatorState.INPUT);
-        if (mUnprocessedChars != null) {
-            int len = mUnprocessedChars.length();
-            if (len > 0) {
-                mUnprocessedChars = mUnprocessedChars.substring(0, len-1);
-            } else {
-                mEvaluator.delete();
-            }
+        if (haveUnprocessed()) {
+            mUnprocessedChars = mUnprocessedChars.substring(0, mUnprocessedChars.length() - 1);
         } else {
             mEvaluator.delete();
         }
-        if (mEvaluator.getExpr().isEmpty()
-                && (mUnprocessedChars == null || mUnprocessedChars.isEmpty())) {
+        if (mEvaluator.getExpr().isEmpty() && !haveUnprocessed()) {
             // Resulting formula won't be announced, since it's empty.
             announceClearedForAccessibility();
         }
@@ -685,7 +821,7 @@
         revealView.setBottom(displayRect.bottom);
         revealView.setLeft(displayRect.left);
         revealView.setRight(displayRect.right);
-        revealView.setBackgroundColor(getResources().getColor(colorRes));
+        revealView.setBackgroundColor(ContextCompat.getColor(this, colorRes));
         groupOverlay.add(revealView);
 
         final int[] clearLocation = new int[2];
@@ -732,7 +868,7 @@
     }
 
     private void onClear() {
-        if (mEvaluator.getExpr().isEmpty()) {
+        if (mEvaluator.getExpr().isEmpty() && !haveUnprocessed()) {
             return;
         }
         cancelIfEvaluating(true);
@@ -744,6 +880,7 @@
                 mResultText.clear();
                 mEvaluator.clear();
                 setState(CalculatorState.INPUT);
+                showOrHideToolbar();
                 redisplayFormula();
             }
         });
@@ -770,7 +907,6 @@
         }
     }
 
-
     // Animate movement of result into the top formula slot.
     // Result window now remains translated in the top slot while the result is displayed.
     // (We convert it back to formula use only when the user provides new input.)
@@ -797,9 +933,15 @@
 
         // Calculate the necessary translations so the result takes the place of the formula and
         // the formula moves off the top of the screen.
-        final float resultTranslationY = (mFormulaText.getBottom() - mResultText.getBottom())
+        final float resultTranslationY = (mFormulaContainer.getBottom() - mResultText.getBottom())
                 - (mFormulaText.getPaddingBottom() - mResultText.getPaddingBottom());
-        final float formulaTranslationY = -mFormulaText.getBottom();
+        float formulaTranslationY = -mFormulaContainer.getBottom();
+        if (mOneLine) {
+            // Position the result text.
+            mResultText.setY(mResultText.getBottom());
+            formulaTranslationY = -(findViewById(R.id.toolbar).getBottom()
+                    + mFormulaContainer.getBottom());
+        }
 
         // Change the result's textColor to match the formula.
         final int formulaTextColor = mFormulaText.getCurrentTextColor();
@@ -815,7 +957,8 @@
                             PropertyValuesHolder.ofFloat(View.SCALE_Y, resultScale),
                             PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, resultTranslationY)),
                     ObjectAnimator.ofArgb(mResultText, TEXT_COLOR, formulaTextColor),
-                    ObjectAnimator.ofFloat(mFormulaText, View.TRANSLATION_Y, formulaTranslationY));
+                    ObjectAnimator.ofFloat(mFormulaContainer, View.TRANSLATION_Y,
+                            formulaTranslationY));
             animatorSet.setDuration(getResources().getInteger(
                     android.R.integer.config_longAnimTime));
             animatorSet.addListener(new AnimatorListenerAdapter() {
@@ -833,7 +976,7 @@
             mResultText.setScaleY(resultScale);
             mResultText.setTranslationY(resultTranslationY);
             mResultText.setTextColor(formulaTextColor);
-            mFormulaText.setTranslationY(formulaTranslationY);
+            mFormulaContainer.setTranslationY(formulaTranslationY);
             setState(CalculatorState.RESULT);
         }
     }
@@ -848,7 +991,7 @@
         mResultText.setScaleY(1.0f);
         mResultText.setTranslationX(0.0f);
         mResultText.setTranslationY(0.0f);
-        mFormulaText.setTranslationY(0.0f);
+        mFormulaContainer.setTranslationY(0.0f);
 
         mFormulaText.requestFocus();
     }
@@ -878,7 +1021,7 @@
 
         // Show the fraction option when displaying a rational result.
         menu.findItem(R.id.menu_fraction).setVisible(mCurrentState == CalculatorState.RESULT
-                && mEvaluator.getRational() != null);
+                && mEvaluator.getResult().exactlyDisplayable());
 
         return true;
     }
@@ -905,14 +1048,14 @@
     }
 
     private void displayFraction() {
-        BoundedRational result = mEvaluator.getRational();
+        UnifiedReal result = mEvaluator.getResult();
         displayMessage(KeyMaps.translateResult(result.toNiceString()));
     }
 
     // Display full result to currently evaluated precision
     private void displayFull() {
         Resources res = getResources();
-        String msg = mResultText.getFullText() + " ";
+        String msg = mResultText.getFullText(true /* withSeparators */) + " ";
         if (mResultText.fullTextIsExact()) {
             msg += res.getString(R.string.exact);
         } else {
@@ -926,8 +1069,8 @@
      * Map them to the appropriate button pushes when possible.  Leftover characters
      * are added to mUnprocessedChars, which is presumed to immediately precede the newly
      * added characters.
-     * @param moreChars Characters to be added.
-     * @param explicit These characters were explicitly typed by the user, not pasted.
+     * @param moreChars characters to be added
+     * @param explicit these characters were explicitly typed by the user, not pasted
      */
     private void addChars(String moreChars, boolean explicit) {
         if (mUnprocessedChars != null) {
@@ -940,8 +1083,13 @@
             // Clear display immediately for incomplete function name.
             switchToInput(KeyMaps.keyForChar(moreChars.charAt(current)));
         }
+        char groupingSeparator = KeyMaps.translateResult(",").charAt(0);
         while (current < len) {
             char c = moreChars.charAt(current);
+            if (Character.isSpaceChar(c) || c == groupingSeparator) {
+                ++current;
+                continue;
+            }
             int k = KeyMaps.keyForChar(c);
             if (!explicit) {
                 int expEnd;
@@ -998,10 +1146,12 @@
             // There are characters left, but we can't convert them to button presses.
             mUnprocessedChars = moreChars.substring(current);
             redisplayAfterFormulaChange();
+            showOrHideToolbar();
             return;
         }
         mUnprocessedChars = null;
         redisplayAfterFormulaChange();
+        showOrHideToolbar();
     }
 
     @Override
@@ -1027,4 +1177,12 @@
         }
         return true;
     }
+
+    /**
+     * Clean up animation for context menu.
+     */
+    @Override
+    public void onContextMenuClosed(Menu menu) {
+        stopActionModeOrContextMenu();
+    }
 }
diff --git a/src/com/android/calculator2/CalculatorDisplay.java b/src/com/android/calculator2/CalculatorDisplay.java
new file mode 100644
index 0000000..728fc11
--- /dev/null
+++ b/src/com/android/calculator2/CalculatorDisplay.java
@@ -0,0 +1,197 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.transition.Fade;
+import android.transition.Transition;
+import android.transition.TransitionManager;
+import android.util.AttributeSet;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.accessibility.AccessibilityManager;
+import android.widget.LinearLayout;
+import android.widget.Toolbar;
+
+public class CalculatorDisplay extends LinearLayout
+        implements AccessibilityManager.AccessibilityStateChangeListener {
+
+    /**
+     * The duration in milliseconds after which to hide the toolbar.
+     */
+    private static final long AUTO_HIDE_DELAY_MILLIS = 3000L;
+
+    /**
+     * The duration in milliseconds to fade in/out the toolbar.
+     */
+    private static final long FADE_DURATION = 200L;
+
+    private final Runnable mHideToolbarRunnable = new Runnable() {
+        @Override
+        public void run() {
+            // Remove any duplicate callbacks to hide the toolbar.
+            removeCallbacks(this);
+
+            // Only animate if we have been laid out at least once.
+            if (isLaidOut()) {
+                TransitionManager.beginDelayedTransition(CalculatorDisplay.this, mTransition);
+            }
+            mToolbar.setVisibility(View.INVISIBLE);
+        }
+    };
+
+    private final AccessibilityManager mAccessibilityManager;
+    private final GestureDetector mTapDetector;
+
+    private Toolbar mToolbar;
+    private Transition mTransition;
+
+    private boolean mForceToolbarVisible;
+
+    public CalculatorDisplay(Context context) {
+        this(context, null /* attrs */);
+    }
+
+    public CalculatorDisplay(Context context, AttributeSet attrs) {
+        this(context, attrs, 0 /* defStyleAttr */);
+    }
+
+    public CalculatorDisplay(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+
+        mAccessibilityManager =
+                (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
+
+        mTapDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() {
+            @Override
+            public boolean onDown(MotionEvent e) {
+                // Remove callbacks to hide the toolbar.
+                removeCallbacks(mHideToolbarRunnable);
+
+                return true;
+            }
+
+            @Override
+            public boolean onSingleTapConfirmed(MotionEvent e) {
+                if (mToolbar.getVisibility() != View.VISIBLE) {
+                    showToolbar(true);
+                } else {
+                    hideToolbar();
+                }
+
+                return true;
+            }
+        });
+
+        // Draw the children in reverse order so that the toolbar is on top.
+        setChildrenDrawingOrderEnabled(true);
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        mToolbar = (Toolbar) findViewById(R.id.toolbar);
+        mTransition = new Fade()
+                .setDuration(FADE_DURATION)
+                .addTarget(mToolbar);
+    }
+
+    @Override
+    protected int getChildDrawingOrder(int childCount, int i) {
+        // Reverse the normal drawing order.
+        return (childCount - 1) - i;
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        mAccessibilityManager.addAccessibilityStateChangeListener(this);
+    }
+
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        mAccessibilityManager.removeAccessibilityStateChangeListener(this);
+    }
+
+    @Override
+    public void onAccessibilityStateChanged(boolean enabled) {
+        // Always show the toolbar whenever accessibility is enabled.
+        showToolbar(true);
+    }
+
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent event) {
+        mTapDetector.onTouchEvent(event);
+        return super.onInterceptTouchEvent(event);
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+        return mTapDetector.onTouchEvent(event) || super.onTouchEvent(event);
+    }
+
+    /**
+     * Returns {@code true} if the toolbar should remain visible.
+     */
+    public boolean getForceToolbarVisible() {
+        return mForceToolbarVisible || mAccessibilityManager.isEnabled();
+    }
+
+    /**
+     * Forces the toolbar to remain visible.
+     *
+     * @param forceToolbarVisible {@code true} to keep the toolbar visible
+     */
+    public void setForceToolbarVisible(boolean forceToolbarVisible) {
+        if (mForceToolbarVisible != forceToolbarVisible) {
+            mForceToolbarVisible = forceToolbarVisible;
+            showToolbar(!forceToolbarVisible);
+        }
+    }
+
+    /**
+     * Shows the toolbar.
+     * @param autoHide Automatically ide toolbar again after delay
+     */
+    public void showToolbar(boolean autoHide) {
+        // Only animate if we have been laid out at least once.
+        if (isLaidOut()) {
+            TransitionManager.beginDelayedTransition(this, mTransition);
+        }
+        mToolbar.setVisibility(View.VISIBLE);
+
+        // Remove callbacks to hide the toolbar.
+        removeCallbacks(mHideToolbarRunnable);
+
+        // Auto hide the toolbar after 3 seconds.
+        if (autoHide && !getForceToolbarVisible()) {
+            postDelayed(mHideToolbarRunnable, AUTO_HIDE_DELAY_MILLIS);
+        }
+    }
+
+    /**
+     * Hides the toolbar.
+     */
+    public void hideToolbar() {
+        if (!getForceToolbarVisible()) {
+            post(mHideToolbarRunnable);
+        }
+    }
+}
diff --git a/src/com/android/calculator2/CalculatorExpr.java b/src/com/android/calculator2/CalculatorExpr.java
index 14d9236..41dfe13 100644
--- a/src/com/android/calculator2/CalculatorExpr.java
+++ b/src/com/android/calculator2/CalculatorExpr.java
@@ -16,10 +16,6 @@
 
 package com.android.calculator2;
 
-
-import com.hp.creals.CR;
-import com.hp.creals.UnaryCRFunction;
-
 import android.content.Context;
 import android.text.SpannableString;
 import android.text.SpannableStringBuilder;
@@ -42,12 +38,13 @@
  * A token may also represent the result of a previously evaluated expression.
  * The add() method adds a token to the end of the expression.  The delete method() removes one.
  * Clear() deletes the entire expression contents. Eval() evaluates the expression,
- * producing both a constructive real (CR), and possibly a BoundedRational result.
+ * producing a UnifiedReal result.
  * Expressions are parsed only during evaluation; no explicit parse tree is maintained.
  *
- * The write() method is used to save the current expression.  Note that CR provides no
- * serialization facility.  Thus we save all previously computed values by writing out the
- * expression that was used to compute them, and reevaluate on input.
+ * The write() method is used to save the current expression.  Note that neither UnifiedReal
+ * nor the underlying CR provide a serialization facility.  Thus we save all previously
+ * computed values by writing out the expression that was used to compute them, and reevaluate
+ * when reading it back in.
  */
 class CalculatorExpr {
     private ArrayList<Token> mExpr;  // The actual representation
@@ -201,11 +198,17 @@
 
         /**
          * Produce human-readable string representation of constant, as typed.
+         * We do add digit grouping separators to the whole number, even if not typed.
          * Result is internationalized.
          */
         @Override
         public String toString() {
-            String result = mWhole;
+            String result;
+            if (mExponent != 0) {
+                result = mWhole;
+            } else {
+                result = StringUtils.addCommas(mWhole, 0, mWhole.length());
+            }
             if (mSawDecimal) {
                 result += '.';
                 result += mFraction;
@@ -265,11 +268,11 @@
 
     // Hash maps used to detect duplicate subexpressions when we write out CalculatorExprs and
     // read them back in.
-    private static final ThreadLocal<IdentityHashMap<CR,Integer>>outMap =
-            new ThreadLocal<IdentityHashMap<CR,Integer>>();
+    private static final ThreadLocal<IdentityHashMap<UnifiedReal, Integer>>outMap =
+            new ThreadLocal<IdentityHashMap<UnifiedReal, Integer>>();
         // Maps expressions to indices on output
-    private static final ThreadLocal<HashMap<Integer,PreEval>>inMap =
-            new ThreadLocal<HashMap<Integer,PreEval>>();
+    private static final ThreadLocal<HashMap<Integer, PreEval>>inMap =
+            new ThreadLocal<HashMap<Integer, PreEval>>();
         // Maps expressions to indices on output
     private static final ThreadLocal<Integer> exprIndex = new ThreadLocal<Integer>();
 
@@ -279,7 +282,7 @@
      * This avoids a potential exponential blow-up in the expression size.
      */
     public static void initExprOutput() {
-        outMap.set(new IdentityHashMap<CR,Integer>());
+        outMap.set(new IdentityHashMap<UnifiedReal, Integer>());
         exprIndex.set(Integer.valueOf(0));
     }
 
@@ -288,7 +291,7 @@
      * Initializes map that will be used to reconstruct shared subexpressions.
      */
     public static void initExprInput() {
-        inMap.set(new HashMap<Integer,PreEval>());
+        inMap.set(new HashMap<Integer, PreEval>());
     }
 
     /**
@@ -296,31 +299,26 @@
      * We treat previously evaluated subexpressions as tokens.  These are inserted when we either
      * continue an expression after evaluating some of it, or copy an expression and paste it back
      * in.
-     * The representation includes both CR and possibly BoundedRational values.  In order to
+     * The representation includes a UnifiedReal value.  In order to
      * support saving and restoring, we also include the underlying expression itself, and the
      * context (currently just degree mode) used to evaluate it.  The short string representation
      * is also stored in order to avoid potentially expensive recomputation in the UI thread.
      */
     private static class PreEval extends Token {
-        public final CR value;
-        public final BoundedRational ratValue;
+        public final UnifiedReal value;
         private final CalculatorExpr mExpr;
         private final EvalContext mContext;
         private final String mShortRep;  // Not internationalized.
-        PreEval(CR val, BoundedRational ratVal, CalculatorExpr expr,
-                EvalContext ec, String shortRep) {
+        PreEval(UnifiedReal val, CalculatorExpr expr, EvalContext ec, String shortRep) {
             value = val;
-            ratValue = ratVal;
             mExpr = expr;
             mContext = ec;
             mShortRep = shortRep;
         }
-        // In writing out PreEvals, we are careful to avoid writing
-        // out duplicates.  We assume that two expressions are
-        // duplicates if they have the same CR value.  This avoids a
-        // potential exponential blow up in certain off cases and
-        // redundant evaluation after reading them back in.
-        // The parameter hash map maps expressions we've seen
+        // In writing out PreEvals, we are careful to avoid writing out duplicates.  We conclude
+        // that two expressions are duplicates if they have the same UnifiedReal value.  This
+        // avoids a potential exponential blow up in certain off cases and redundant evaluation
+        // after reading them back in.  The parameter hash map maps expressions we've seen
         // before to their index.
         @Override
         public void write(DataOutput out) throws IOException {
@@ -359,12 +357,10 @@
                     Log.e("Calculator", "Unexpected syntax exception" + e);
                 }
                 value = res.val;
-                ratValue = res.ratVal;
                 mShortRep = in.readUTF();
                 inMap.get().put(index, this);
             } else {
                 value = prev.value;
-                ratValue = prev.ratValue;
                 mExpr = prev.mExpr;
                 mContext = prev.mContext;
                 mShortRep = prev.mShortRep;
@@ -619,28 +615,26 @@
      * The caller supplies the value, degree mode, and short string representation, which must
      * have been previously computed.  Thus this is guaranteed to terminate reasonably quickly.
      */
-    public CalculatorExpr abbreviate(CR val, BoundedRational ratVal,
-                              boolean dm, String sr) {
+    public CalculatorExpr abbreviate(UnifiedReal val, boolean dm, String sr) {
         CalculatorExpr result = new CalculatorExpr();
-        Token t = new PreEval(val, ratVal, new CalculatorExpr((ArrayList<Token>) mExpr.clone()),
+        @SuppressWarnings("unchecked")
+        Token t = new PreEval(val, new CalculatorExpr((ArrayList<Token>) mExpr.clone()),
                 new EvalContext(dm, mExpr.size()), sr);
         result.mExpr.add(t);
         return result;
     }
 
     /**
-     * Internal evaluation functions return an EvalRet triple.
+     * Internal evaluation functions return an EvalRet pair.
      * We compute rational (BoundedRational) results when possible, both as a performance
      * optimization, and to detect errors exactly when we can.
      */
     private static class EvalRet {
         public int pos; // Next position (expression index) to be parsed.
-        public final CR val; // Constructive Real result of evaluating subexpression.
-        public final BoundedRational ratVal;  // Exact Rational value or null.
-        EvalRet(int p, CR v, BoundedRational r) {
+        public final UnifiedReal val; // Constructive Real result of evaluating subexpression.
+        EvalRet(int p, UnifiedReal v) {
             pos = p;
             val = v;
-            ratVal = r;
         }
     }
 
@@ -664,21 +658,17 @@
         }
     }
 
-    private final CR RADIANS_PER_DEGREE = CR.PI.divide(CR.valueOf(180));
-
-    private final CR DEGREES_PER_RADIAN = CR.valueOf(180).divide(CR.PI);
-
-    private CR toRadians(CR x, EvalContext ec) {
+    private UnifiedReal toRadians(UnifiedReal x, EvalContext ec) {
         if (ec.mDegreeMode) {
-            return x.multiply(RADIANS_PER_DEGREE);
+            return x.multiply(UnifiedReal.RADIANS_PER_DEGREE);
         } else {
             return x;
         }
     }
 
-    private CR fromRadians(CR x, EvalContext ec) {
+    private UnifiedReal fromRadians(UnifiedReal x, EvalContext ec) {
         if (ec.mDegreeMode) {
-            return x.multiply(DEGREES_PER_RADIAN);
+            return x.divide(UnifiedReal.RADIANS_PER_DEGREE);
         } else {
             return x;
         }
@@ -719,255 +709,131 @@
 
     private EvalRet evalUnary(int i, EvalContext ec) throws SyntaxException {
         final Token t = mExpr.get(i);
-        BoundedRational ratVal;
         if (t instanceof Constant) {
             Constant c = (Constant)t;
-            ratVal = c.toRational();
-            return new EvalRet(i+1, ratVal.CRValue(), ratVal);
+            return new EvalRet(i+1,new UnifiedReal(c.toRational()));
         }
         if (t instanceof PreEval) {
             final PreEval p = (PreEval)t;
-            return new EvalRet(i+1, p.value, p.ratValue);
+            return new EvalRet(i+1, p.value);
         }
         EvalRet argVal;
         switch(((Operator)(t)).id) {
         case R.id.const_pi:
-            return new EvalRet(i+1, CR.PI, null);
+            return new EvalRet(i+1, UnifiedReal.PI);
         case R.id.const_e:
-            return new EvalRet(i+1, REAL_E, null);
+            return new EvalRet(i+1, UnifiedReal.E);
         case R.id.op_sqrt:
             // Seems to have highest precedence.
             // Does not add implicit paren.
             // Does seem to accept a leading minus.
             if (isOperator(i+1, R.id.op_sub, ec)) {
                 argVal = evalUnary(i+2, ec);
-                ratVal = BoundedRational.sqrt(BoundedRational.negate(argVal.ratVal));
-                if (ratVal != null) {
-                    break;
-                }
-                return new EvalRet(argVal.pos,
-                                   argVal.val.negate().sqrt(), null);
+                return new EvalRet(argVal.pos, argVal.val.negate().sqrt());
             } else {
                 argVal = evalUnary(i+1, ec);
-                ratVal = BoundedRational.sqrt(argVal.ratVal);
-                if (ratVal != null) {
-                    break;
-                }
-                return new EvalRet(argVal.pos, argVal.val.sqrt(), null);
+                return new EvalRet(argVal.pos, argVal.val.sqrt());
             }
         case R.id.lparen:
             argVal = evalExpr(i+1, ec);
             if (isOperator(argVal.pos, R.id.rparen, ec)) {
                 argVal.pos++;
             }
-            return new EvalRet(argVal.pos, argVal.val, argVal.ratVal);
+            return new EvalRet(argVal.pos, argVal.val);
         case R.id.fun_sin:
             argVal = evalExpr(i+1, ec);
             if (isOperator(argVal.pos, R.id.rparen, ec)) {
                 argVal.pos++;
             }
-            ratVal = ec.mDegreeMode ? BoundedRational.degreeSin(argVal.ratVal)
-                                     : BoundedRational.sin(argVal.ratVal);
-            if (ratVal != null) {
-                break;
-            }
-            return new EvalRet(argVal.pos, toRadians(argVal.val,ec).sin(), null);
+            return new EvalRet(argVal.pos, toRadians(argVal.val, ec).sin());
         case R.id.fun_cos:
             argVal = evalExpr(i+1, ec);
             if (isOperator(argVal.pos, R.id.rparen, ec)) {
                 argVal.pos++;
             }
-            ratVal = ec.mDegreeMode ? BoundedRational.degreeCos(argVal.ratVal)
-                                     : BoundedRational.cos(argVal.ratVal);
-            if (ratVal != null) {
-                break;
-            }
-            return new EvalRet(argVal.pos, toRadians(argVal.val,ec).cos(), null);
+            return new EvalRet(argVal.pos, toRadians(argVal.val,ec).cos());
         case R.id.fun_tan:
             argVal = evalExpr(i+1, ec);
             if (isOperator(argVal.pos, R.id.rparen, ec)) {
                 argVal.pos++;
             }
-            ratVal = ec.mDegreeMode ? BoundedRational.degreeTan(argVal.ratVal)
-                                     : BoundedRational.tan(argVal.ratVal);
-            if (ratVal != null) {
-                break;
-            }
-            CR argCR = toRadians(argVal.val, ec);
-            return new EvalRet(argVal.pos, argCR.sin().divide(argCR.cos()), null);
+            UnifiedReal arg = toRadians(argVal.val, ec);
+            return new EvalRet(argVal.pos, arg.sin().divide(arg.cos()));
         case R.id.fun_ln:
             argVal = evalExpr(i+1, ec);
             if (isOperator(argVal.pos, R.id.rparen, ec)) {
                 argVal.pos++;
             }
-            ratVal = BoundedRational.ln(argVal.ratVal);
-            if (ratVal != null) {
-                break;
-            }
-            return new EvalRet(argVal.pos, argVal.val.ln(), null);
+            return new EvalRet(argVal.pos, argVal.val.ln());
         case R.id.fun_exp:
             argVal = evalExpr(i+1, ec);
             if (isOperator(argVal.pos, R.id.rparen, ec)) {
                 argVal.pos++;
             }
-            ratVal = BoundedRational.exp(argVal.ratVal);
-            if (ratVal != null) {
-                break;
-            }
-            return new EvalRet(argVal.pos, argVal.val.exp(), null);
+            return new EvalRet(argVal.pos, argVal.val.exp());
         case R.id.fun_log:
             argVal = evalExpr(i+1, ec);
             if (isOperator(argVal.pos, R.id.rparen, ec)) {
                 argVal.pos++;
             }
-            ratVal = BoundedRational.log(argVal.ratVal);
-            if (ratVal != null) {
-                break;
-            }
-            return new EvalRet(argVal.pos, argVal.val.ln().divide(CR.valueOf(10).ln()), null);
+            return new EvalRet(argVal.pos, argVal.val.ln().divide(UnifiedReal.TEN.ln()));
         case R.id.fun_arcsin:
             argVal = evalExpr(i+1, ec);
             if (isOperator(argVal.pos, R.id.rparen, ec)) {
                 argVal.pos++;
             }
-            ratVal = ec.mDegreeMode ? BoundedRational.degreeAsin(argVal.ratVal)
-                                     : BoundedRational.asin(argVal.ratVal);
-            if (ratVal != null) {
-                break;
-            }
-            return new EvalRet(argVal.pos,
-                    fromRadians(UnaryCRFunction.asinFunction.execute(argVal.val),ec), null);
+            return new EvalRet(argVal.pos, fromRadians(argVal.val.asin(), ec));
         case R.id.fun_arccos:
             argVal = evalExpr(i+1, ec);
             if (isOperator(argVal.pos, R.id.rparen, ec)) {
                 argVal.pos++;
             }
-            ratVal = ec.mDegreeMode ? BoundedRational.degreeAcos(argVal.ratVal)
-                                     : BoundedRational.acos(argVal.ratVal);
-            if (ratVal != null) {
-                break;
-            }
-            return new EvalRet(argVal.pos,
-                    fromRadians(UnaryCRFunction.acosFunction.execute(argVal.val),ec), null);
+            return new EvalRet(argVal.pos, fromRadians(argVal.val.acos(), ec));
         case R.id.fun_arctan:
             argVal = evalExpr(i+1, ec);
             if (isOperator(argVal.pos, R.id.rparen, ec)) {
                 argVal.pos++;
             }
-            ratVal = ec.mDegreeMode ? BoundedRational.degreeAtan(argVal.ratVal)
-                                     : BoundedRational.atan(argVal.ratVal);
-            if (ratVal != null) {
-                break;
-            }
-            return new EvalRet(argVal.pos,
-                    fromRadians(UnaryCRFunction.atanFunction.execute(argVal.val),ec), null);
+            return new EvalRet(argVal.pos, fromRadians(argVal.val.atan(),ec));
         default:
             throw new SyntaxException("Unrecognized token in expression");
         }
-        // We have a rational value.
-        return new EvalRet(argVal.pos, ratVal.CRValue(), ratVal);
     }
 
-    /**
-     * Compute an integral power of a constructive real.
-     * Unlike the "general" case using logarithms, this handles a negative base.
-     */
-    private static CR pow(CR base, BigInteger exp) {
-        if (exp.compareTo(BigInteger.ZERO) < 0) {
-            return pow(base, exp.negate()).inverse();
-        }
-        if (exp.equals(BigInteger.ONE)) {
-            return base;
-        }
-        if (exp.and(BigInteger.ONE).intValue() == 1) {
-            return pow(base, exp.subtract(BigInteger.ONE)).multiply(base);
-        }
-        if (exp.equals(BigInteger.ZERO)) {
-            return CR.valueOf(1);
-        }
-        CR tmp = pow(base, exp.shiftRight(1));
-        return tmp.multiply(tmp);
-    }
-
-    // Number of bits past binary point to test for integer-ness.
-    private static final int TEST_PREC = -100;
-    private static final BigInteger MASK =
-            BigInteger.ONE.shiftLeft(-TEST_PREC).subtract(BigInteger.ONE);
-    private static final CR REAL_E = CR.valueOf(1).exp();
-    private static final CR REAL_ONE_HUNDREDTH = CR.valueOf(100).inverse();
-    private static final BoundedRational RATIONAL_ONE_HUNDREDTH = new BoundedRational(1,100);
-    private static boolean isApprInt(CR x) {
-        BigInteger appr = x.get_appr(TEST_PREC);
-        return appr.and(MASK).signum() == 0;
-    }
+    private static final UnifiedReal ONE_HUNDREDTH = new UnifiedReal(100).inverse();
 
     private EvalRet evalSuffix(int i, EvalContext ec) throws SyntaxException {
         final EvalRet tmp = evalUnary(i, ec);
         int cpos = tmp.pos;
-        CR crVal = tmp.val;
-        BoundedRational ratVal = tmp.ratVal;
+        UnifiedReal val = tmp.val;
+
         boolean isFact;
         boolean isSquared = false;
         while ((isFact = isOperator(cpos, R.id.op_fact, ec)) ||
                 (isSquared = isOperator(cpos, R.id.op_sqr, ec)) ||
                 isOperator(cpos, R.id.op_pct, ec)) {
             if (isFact) {
-                if (ratVal == null) {
-                    // Assume it was an integer, but we didn't figure it out.
-                    // KitKat may have used the Gamma function.
-                    if (!isApprInt(crVal)) {
-                        throw new ArithmeticException("factorial(non-integer)");
-                    }
-                    ratVal = new BoundedRational(crVal.BigIntegerValue());
-                }
-                ratVal = BoundedRational.fact(ratVal);
-                crVal = ratVal.CRValue();
+                val = val.fact();
             } else if (isSquared) {
-                ratVal = BoundedRational.multiply(ratVal, ratVal);
-                if (ratVal == null) {
-                    crVal = crVal.multiply(crVal);
-                } else {
-                    crVal = ratVal.CRValue();
-                }
+                val = val.multiply(val);
             } else /* percent */ {
-                ratVal = BoundedRational.multiply(ratVal, RATIONAL_ONE_HUNDREDTH);
-                if (ratVal == null) {
-                    crVal = crVal.multiply(REAL_ONE_HUNDREDTH);
-                } else {
-                    crVal = ratVal.CRValue();
-                }
+                val = val.multiply(ONE_HUNDREDTH);
             }
             ++cpos;
         }
-        return new EvalRet(cpos, crVal, ratVal);
+        return new EvalRet(cpos, val);
     }
 
     private EvalRet evalFactor(int i, EvalContext ec) throws SyntaxException {
         final EvalRet result1 = evalSuffix(i, ec);
         int cpos = result1.pos;  // current position
-        CR crVal = result1.val;   // value so far
-        BoundedRational ratVal = result1.ratVal;  // int value so far
+        UnifiedReal val = result1.val;   // value so far
         if (isOperator(cpos, R.id.op_pow, ec)) {
             final EvalRet exp = evalSignedFactor(cpos + 1, ec);
             cpos = exp.pos;
-            // Try completely rational evaluation first.
-            ratVal = BoundedRational.pow(ratVal, exp.ratVal);
-            if (ratVal != null) {
-                return new EvalRet(cpos, ratVal.CRValue(), ratVal);
-            }
-            // Power with integer exponent is defined for negative base.
-            // Thus we handle that case separately.
-            // We punt if the exponent is an integer computed from irrational
-            // values.  That wouldn't work reliably with floating point either.
-            BigInteger int_exp = BoundedRational.asBigInteger(exp.ratVal);
-            if (int_exp != null) {
-                crVal = pow(crVal, int_exp);
-            } else {
-                crVal = crVal.ln().multiply(exp.val).exp();
-            }
-            ratVal = null;
+            val = val.pow(exp.val);
         }
-        return new EvalRet(cpos, crVal, ratVal);
+        return new EvalRet(cpos, val);
     }
 
     private EvalRet evalSignedFactor(int i, EvalContext ec) throws SyntaxException {
@@ -975,10 +841,8 @@
         int cpos = negative ? i + 1 : i;
         EvalRet tmp = evalFactor(cpos, ec);
         cpos = tmp.pos;
-        CR crVal = negative ? tmp.val.negate() : tmp.val;
-        BoundedRational ratVal = negative ? BoundedRational.negate(tmp.ratVal)
-                                         : tmp.ratVal;
-        return new EvalRet(cpos, crVal, ratVal);
+        final UnifiedReal result = negative ? tmp.val.negate() : tmp.val;
+        return new EvalRet(cpos, result);
     }
 
     private boolean canStartFactor(int i) {
@@ -1001,32 +865,21 @@
         boolean is_mul = false;
         boolean is_div = false;
         int cpos = tmp.pos;   // Current position in expression.
-        CR crVal = tmp.val;    // Current value.
-        BoundedRational ratVal = tmp.ratVal; // Current rational value.
+        UnifiedReal val = tmp.val;    // Current value.
         while ((is_mul = isOperator(cpos, R.id.op_mul, ec))
                || (is_div = isOperator(cpos, R.id.op_div, ec))
                || canStartFactor(cpos)) {
             if (is_mul || is_div) ++cpos;
             tmp = evalSignedFactor(cpos, ec);
             if (is_div) {
-                ratVal = BoundedRational.divide(ratVal, tmp.ratVal);
-                if (ratVal == null) {
-                    crVal = crVal.divide(tmp.val);
-                } else {
-                    crVal = ratVal.CRValue();
-                }
+                val = val.divide(tmp.val);
             } else {
-                ratVal = BoundedRational.multiply(ratVal, tmp.ratVal);
-                if (ratVal == null) {
-                    crVal = crVal.multiply(tmp.val);
-                } else {
-                    crVal = ratVal.CRValue();
-                }
+                val = val.multiply(tmp.val);
             }
             cpos = tmp.pos;
             is_mul = is_div = false;
         }
-        return new EvalRet(cpos, crVal, ratVal);
+        return new EvalRet(cpos, val);
     }
 
     /**
@@ -1055,81 +908,45 @@
             return false;
         }
         Operator op = (Operator) mExpr.get(pos + 2);
-        return op.id == R.id.op_add || op.id == R.id.op_sub;
+        return op.id == R.id.op_add || op.id == R.id.op_sub || op.id == R.id.rparen;
     }
 
     /**
      * Compute the multiplicative factor corresponding to an N% addition or subtraction.
-     * @param pos position of Constant or PreEval expression token corresponding to N
-     * @param isSubtraction this is a subtraction, as opposed to addition
-     * @param ec usable evaluation contex; only length matters
-     * @return Rational and CR values; position is pos + 2, i.e. after percent sign
+     * @param pos position of Constant or PreEval expression token corresponding to N.
+     * @param isSubtraction this is a subtraction, as opposed to addition.
+     * @param ec usable evaluation contex; only length matters.
+     * @return UnifiedReal value and position, which is pos + 2, i.e. after percent sign
      */
     private EvalRet getPercentFactor(int pos, boolean isSubtraction, EvalContext ec)
             throws SyntaxException {
         EvalRet tmp = evalUnary(pos, ec);
-        BoundedRational ratVal = isSubtraction ? BoundedRational.negate(tmp.ratVal)
-                : tmp.ratVal;
-        CR crVal = isSubtraction ? tmp.val.negate() : tmp.val;
-        ratVal = BoundedRational.add(BoundedRational.ONE,
-                BoundedRational.multiply(ratVal, RATIONAL_ONE_HUNDREDTH));
-        if (ratVal == null) {
-            crVal = CR.ONE.add(crVal.multiply(REAL_ONE_HUNDREDTH));
-        } else {
-            crVal = ratVal.CRValue();
-        }
-        return new EvalRet(pos + 2 /* after percent sign */, crVal, ratVal);
+        UnifiedReal val = isSubtraction ? tmp.val.negate() : tmp.val;
+        val = UnifiedReal.ONE.add(val.multiply(ONE_HUNDREDTH));
+        return new EvalRet(pos + 2 /* after percent sign */, val);
     }
 
     private EvalRet evalExpr(int i, EvalContext ec) throws SyntaxException {
         EvalRet tmp = evalTerm(i, ec);
         boolean is_plus;
         int cpos = tmp.pos;
-        CR crVal = tmp.val;
-        BoundedRational ratVal = tmp.ratVal;
+        UnifiedReal val = tmp.val;
         while ((is_plus = isOperator(cpos, R.id.op_add, ec))
                || isOperator(cpos, R.id.op_sub, ec)) {
             if (isPercent(cpos + 1)) {
                 tmp = getPercentFactor(cpos + 1, !is_plus, ec);
-                ratVal = BoundedRational.multiply(ratVal, tmp.ratVal);
-                if (ratVal == null) {
-                    crVal = crVal.multiply(tmp.val);
-                } else {
-                    crVal = ratVal.CRValue();
-                }
+                val = val.multiply(tmp.val);
             } else {
                 tmp = evalTerm(cpos + 1, ec);
                 if (is_plus) {
-                    ratVal = BoundedRational.add(ratVal, tmp.ratVal);
-                    if (ratVal == null) {
-                        crVal = crVal.add(tmp.val);
-                    } else {
-                        crVal = ratVal.CRValue();
-                    }
+                    val = val.add(tmp.val);
                 } else {
-                    ratVal = BoundedRational.subtract(ratVal, tmp.ratVal);
-                    if (ratVal == null) {
-                        crVal = crVal.subtract(tmp.val);
-                    } else {
-                        crVal = ratVal.CRValue();
-                    }
+                    val = val.subtract(tmp.val);
                 }
             }
             cpos = tmp.pos;
         }
-        return new EvalRet(cpos, crVal, ratVal);
-    }
-
-    /**
-     * Externally visible evaluation result.
-     */
-    public static class EvalResult {
-        public final CR val;
-        public final BoundedRational ratVal;
-        EvalResult (CR v, BoundedRational rv) {
-            val = v;
-            ratVal = rv;
-        }
+        return new EvalRet(cpos, val);
     }
 
     /**
@@ -1168,6 +985,21 @@
     }
 
     /**
+     * Does the expression contain trig operations?
+     */
+    public boolean hasTrigFuncs() {
+        for (Token t: mExpr) {
+            if (t instanceof Operator) {
+                Operator o = (Operator)t;
+                if (KeyMaps.isTrigFunc(o.id)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
      * Evaluate the expression excluding trailing binary operators.
      * Errors result in exceptions, most of which are unchecked.  Should not be called
      * concurrently with modification of the expression.  May take a very long time; avoid calling
@@ -1175,8 +1007,8 @@
      *
      * @param degreeMode use degrees rather than radians
      */
-    EvalResult eval(boolean degreeMode) throws SyntaxException
-                        // And unchecked exceptions thrown by CR
+    UnifiedReal eval(boolean degreeMode) throws SyntaxException
+                        // And unchecked exceptions thrown by UnifiedReal, CR,
                         // and BoundedRational.
     {
         try {
@@ -1190,7 +1022,7 @@
             if (res.pos != prefixLen) {
                 throw new SyntaxException("Failed to parse full expression");
             }
-            return new EvalResult(res.val, res.ratVal);
+            return res.val;
         } catch (IndexOutOfBoundsException e) {
             throw new SyntaxException("Unexpected expression end");
         }
diff --git a/src/com/android/calculator2/CalculatorPadViewPager.java b/src/com/android/calculator2/CalculatorPadViewPager.java
index d4520c5..560260b 100644
--- a/src/com/android/calculator2/CalculatorPadViewPager.java
+++ b/src/com/android/calculator2/CalculatorPadViewPager.java
@@ -21,6 +21,7 @@
 import android.support.v4.view.PagerAdapter;
 import android.support.v4.view.ViewPager;
 import android.util.AttributeSet;
+import android.view.GestureDetector;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
@@ -34,8 +35,41 @@
         }
 
         @Override
-        public View instantiateItem(ViewGroup container, int position) {
-            return getChildAt(position);
+        public View instantiateItem(ViewGroup container, final int position) {
+            final View child = getChildAt(position);
+
+            // Set a OnClickListener to scroll to item's position when it isn't the current item.
+            child.setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    setCurrentItem(position, true /* smoothScroll */);
+                }
+            });
+            // Set an OnTouchListener to always return true for onTouch events so that a touch
+            // sequence cannot pass through the item to the item below.
+            child.setOnTouchListener(new OnTouchListener() {
+                @Override
+                public boolean onTouch(View v, MotionEvent event) {
+                    v.onTouchEvent(event);
+                    return true;
+                }
+            });
+
+            // Set an OnHoverListener to always return true for onHover events so that focus cannot
+            // pass through the item to the item below.
+            child.setOnHoverListener(new OnHoverListener() {
+                @Override
+                public boolean onHover(View v, MotionEvent event) {
+                    v.onHoverEvent(event);
+                    return true;
+                }
+            });
+            // Make the item focusable so it can be selected via a11y.
+            child.setFocusable(true);
+            // Set the content description of the item which will be used by a11y to identify it.
+            child.setContentDescription(getPageTitle(position));
+
+            return child;
         }
 
         @Override
@@ -52,6 +86,13 @@
         public float getPageWidth(int position) {
             return position == 1 ? 7.0f / 9.0f : 1.0f;
         }
+
+        @Override
+        public CharSequence getPageTitle(int position) {
+            final String[] pageDescriptions = getContext().getResources()
+                    .getStringArray(R.array.desc_pad_pages);
+            return pageDescriptions[position];
+        }
     };
 
     private final OnPageChangeListener mOnPageChangeListener = new SimpleOnPageChangeListener() {
@@ -59,12 +100,20 @@
         public void onPageSelected(int position) {
             for (int i = getChildCount() - 1; i >= 0; --i) {
                 final View child = getChildAt(i);
+                // Only the "peeking" or covered page should be clickable.
+                child.setClickable(i != position);
+
                 // Prevent clicks and accessibility focus from going through to descendants of
                 // other pages which are covered by the current page.
-                child.setClickable(i == position);
-                child.setImportantForAccessibility(i == position
-                        ? IMPORTANT_FOR_ACCESSIBILITY_AUTO
-                        : IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
+                if (child instanceof ViewGroup) {
+                    final ViewGroup childViewGroup = (ViewGroup) child;
+                    for (int j = childViewGroup.getChildCount() - 1; j >= 0; --j) {
+                        childViewGroup.getChildAt(j)
+                                .setImportantForAccessibility(i == position
+                                        ? IMPORTANT_FOR_ACCESSIBILITY_AUTO
+                                        : IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
+                    }
+                }
             }
         }
     };
@@ -84,6 +133,29 @@
         }
     };
 
+    private final GestureDetector.SimpleOnGestureListener mGestureWatcher =
+            new GestureDetector.SimpleOnGestureListener() {
+        @Override
+        public boolean onDown(MotionEvent e) {
+            // Return true so calls to onSingleTapUp are not blocked.
+            return true;
+        }
+
+        @Override
+        public boolean onSingleTapUp(MotionEvent ev) {
+            if (mClickedItemIndex != -1) {
+                getChildAt(mClickedItemIndex).performClick();
+                mClickedItemIndex = -1;
+                return true;
+            }
+            return super.onSingleTapUp(ev);
+        }
+    };
+
+    private final GestureDetector mGestureDetector;
+
+    private int mClickedItemIndex = -1;
+
     public CalculatorPadViewPager(Context context) {
         this(context, null /* attrs */);
     }
@@ -91,9 +163,12 @@
     public CalculatorPadViewPager(Context context, AttributeSet attrs) {
         super(context, attrs);
 
+        mGestureDetector = new GestureDetector(context, mGestureWatcher);
+        mGestureDetector.setIsLongpressEnabled(false);
+
         setAdapter(mStaticPagerAdapter);
         setBackgroundColor(Color.BLACK);
-        setPageMargin(getResources().getDimensionPixelSize(R.dimen.pad_page_margin));
+        setPageMargin(-getResources().getDimensionPixelSize(R.dimen.pad_page_margin));
         setPageTransformer(false, mPageTransformer);
         addOnPageChangeListener(mOnPageChangeListener);
     }
@@ -111,26 +186,48 @@
 
     @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
-        boolean shouldIntercept = super.onInterceptTouchEvent(ev);
+        // Always intercept touch events when a11y focused since otherwise they will be
+        // incorrectly offset by a11y before being dispatched to children.
+        boolean shouldIntercept = isAccessibilityFocused() || super.onInterceptTouchEvent(ev);
 
         // Only allow the current item to receive touch events.
         if (!shouldIntercept && ev.getActionMasked() == MotionEvent.ACTION_DOWN) {
             final int x = (int) ev.getX() + getScrollX();
             final int y = (int) ev.getY() + getScrollY();
 
+            // Reset the previously clicked item index.
+            mClickedItemIndex = -1;
+
             final int childCount = getChildCount();
             for (int i = childCount - 1; i >= 0; --i) {
                 final int childIndex = getChildDrawingOrder(childCount, i);
                 final View child = getChildAt(childIndex);
-                if (child.getVisibility() == View.VISIBLE
+                if (child.isAccessibilityFocused()) {
+                    // If a child is a11y focused then we must always intercept the touch event
+                    // since it will be incorrectly offset by a11y.
+                    shouldIntercept = true;
+                    mClickedItemIndex = childIndex;
+                    break;
+                } else if (mClickedItemIndex == -1
+                        && child.getVisibility() == VISIBLE
                         && x >= child.getLeft() && x < child.getRight()
                         && y >= child.getTop() && y < child.getBottom()) {
-                    shouldIntercept = (childIndex != getCurrentItem());
-                    break;
+                    shouldIntercept = childIndex != getCurrentItem();
+                    mClickedItemIndex = childIndex;
+                    // continue; since another child may be a11y focused.
                 }
             }
         }
 
         return shouldIntercept;
     }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        // Allow both the gesture detector and super to handle the touch event so they both see
+        // the full sequence of events. This should be safe since the gesture detector only
+        // handle clicks and super only handles swipes.
+        mGestureDetector.onTouchEvent(ev);
+        return super.onTouchEvent(ev);
+    }
 }
diff --git a/src/com/android/calculator2/CalculatorResult.java b/src/com/android/calculator2/CalculatorResult.java
index 5c96867..234f602 100644
--- a/src/com/android/calculator2/CalculatorResult.java
+++ b/src/com/android/calculator2/CalculatorResult.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 The Android Open Source Project
+ * 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.
@@ -16,11 +16,15 @@
 
 package com.android.calculator2;
 
+import android.annotation.TargetApi;
 import android.content.ClipData;
 import android.content.ClipDescription;
 import android.content.ClipboardManager;
 import android.content.Context;
 import android.graphics.Rect;
+import android.os.Build;
+import android.support.v4.content.ContextCompat;
+import android.support.v4.os.BuildCompat;
 import android.text.Layout;
 import android.text.Spannable;
 import android.text.SpannableString;
@@ -28,8 +32,10 @@
 import android.text.TextPaint;
 import android.text.style.BackgroundColorSpan;
 import android.text.style.ForegroundColorSpan;
+import android.text.style.RelativeSizeSpan;
 import android.util.AttributeSet;
 import android.view.ActionMode;
+import android.view.ContextMenu;
 import android.view.GestureDetector;
 import android.view.Menu;
 import android.view.MenuInflater;
@@ -41,19 +47,12 @@
 
 // A text widget that is "infinitely" scrollable to the right,
 // and obtains the text to display via a callback to Logic.
-public class CalculatorResult extends AlignedTextView {
+public class CalculatorResult extends AlignedTextView implements MenuItem.OnMenuItemClickListener {
     static final int MAX_RIGHT_SCROLL = 10000000;
     static final int INVALID = MAX_RIGHT_SCROLL + 10000;
         // A larger value is unlikely to avoid running out of space
     final OverScroller mScroller;
     final GestureDetector mGestureDetector;
-    class MyTouchListener implements View.OnTouchListener {
-        @Override
-        public boolean onTouch(View v, MotionEvent event) {
-            return mGestureDetector.onTouchEvent(event);
-        }
-    }
-    final MyTouchListener mTouchListener = new MyTouchListener();
     private Evaluator mEvaluator;
     private boolean mScrollable = false;
                             // A scrollable result is currently displayed.
@@ -67,23 +66,36 @@
                             // left of the display.  Zero means decimal point is barely displayed
                             // on the right.
     private int mLastPos;   // Position already reflected in display. Pixels.
-    private int mMinPos;    // Minimum position before all digits disappear off the right. Pixels.
+    private int mMinPos;    // Minimum position to avoid unnecessary blanks on the left. Pixels.
     private int mMaxPos;    // Maximum position before we start displaying the infinite
                             // sequence of trailing zeroes on the right. Pixels.
+    private int mWholeLen;  // Length of the whole part of current result.
     // In the following, we use a suffix of Offset to denote a character position in a numeric
     // string relative to the decimal point.  Positive is to the right and negative is to
     // the left. 1 = tenths position, -1 = units.  Integer.MAX_VALUE is sometimes used
     // for the offset of the last digit in an a nonterminating decimal expansion.
     // We use the suffix "Index" to denote a zero-based index into a string representing a
     // result.
-    // TODO: Apply the same convention to other classes.
     private int mMaxCharOffset;  // Character offset from decimal point of rightmost digit
-                                 // that should be displayed.  Essentially the same as
+                                 // that should be displayed, plus the length of any exponent
+                                 // needed to display that digit.
+                                 // Limited to MAX_RIGHT_SCROLL. Often the same as:
     private int mLsdOffset;      // Position of least-significant digit in result
     private int mLastDisplayedOffset; // Offset of last digit actually displayed after adding
                                       // exponent.
+    private boolean mWholePartFits;  // Scientific notation not needed for initial display.
+    private float mNoExponentCredit;
+                            // Fraction of digit width saved by avoiding scientific notation.
+                            // Only accessed from UI thread.
+    private boolean mAppendExponent;
+                            // The result fits entirely in the display, even with an exponent,
+                            // but not with grouping separators. Since the result is not
+                            // scrollable, and we do not add the exponent to max. scroll position,
+                            // append an exponent insteadd of replacing trailing digits.
     private final Object mWidthLock = new Object();
-                            // Protects the next two fields.
+                            // Protects the next five fields.  These fields are only
+                            // Updated by the UI thread, and read accesses by the UI thread
+                            // sometimes do not acquire the lock.
     private int mWidthConstraint = -1;
                             // Our total width in pixels minus space for ellipsis.
     private float mCharWidth = 1;
@@ -92,8 +104,15 @@
                             // TODO: We're not really using a fixed width font.  But it appears
                             // to be close enough for the characters we use that the difference
                             // is not noticeable.
+    private float mGroupingSeparatorWidthRatio;
+                            // Fraction of digit width occupied by a digit separator.
+    private float mDecimalCredit;
+                            // Fraction of digit width saved by replacing digit with decimal point.
+    private float mNoEllipsisCredit;
+                            // Fraction of digit width saved by both replacing ellipsis with digit
+                            // and avoiding scientific notation.
     private static final int MAX_WIDTH = 100;
-                            // Maximum number of digits displayed
+                            // Maximum number of digits displayed.
     public static final int MAX_LEADING_ZEROES = 6;
                             // Maximum number of leading zeroes after decimal point before we
                             // switch to scientific notation with negative exponent.
@@ -105,12 +124,26 @@
                             // have a decimal point and no ellipsis.
                             // We assume that we do not drop digits to make room for the decimal
                             // point in ordinary scientific notation. Thus >= 1.
-    private ActionMode mActionMode;
+    private static final int MAX_COPY_EXTRA = 100;
+                            // The number of extra digits we are willing to compute to copy
+                            // a result as an exact number.
+    private static final int MAX_RECOMPUTE_DIGITS = 2000;
+                            // The maximum number of digits we're willing to recompute in the UI
+                            // thread.  We only do this for known rational results, where we
+                            // can bound the computation cost.
     private final ForegroundColorSpan mExponentColorSpan;
+    private final BackgroundColorSpan mHighlightSpan;
+
+    private ActionMode mActionMode;
+    private ActionMode.Callback mCopyActionModeCallback;
+    private ContextMenu mContextMenu;
 
     public CalculatorResult(Context context, AttributeSet attrs) {
         super(context, attrs);
         mScroller = new OverScroller(context);
+        mHighlightSpan = new BackgroundColorSpan(getHighlightColor());
+        mExponentColorSpan = new ForegroundColorSpan(
+                ContextCompat.getColor(context, R.color.display_result_exponent_text_color));
         mGestureDetector = new GestureDetector(context,
             new GestureDetector.SimpleOnGestureListener() {
                 @Override
@@ -124,7 +157,7 @@
                         mCurrentPos = mScroller.getFinalX();
                     }
                     mScroller.forceFinished(true);
-                    stopActionMode();
+                    stopActionModeOrContextMenu();
                     CalculatorResult.this.cancelLongPress();
                     // Ignore scrolls of error string, etc.
                     if (!mScrollable) return true;
@@ -141,7 +174,7 @@
                         mCurrentPos = mScroller.getFinalX();
                     }
                     mScroller.forceFinished(true);
-                    stopActionMode();
+                    stopActionModeOrContextMenu();
                     CalculatorResult.this.cancelLongPress();
                     if (!mScrollable) return true;
                     if (mCurrentPos + distance < mMinPos) {
@@ -158,51 +191,152 @@
                 @Override
                 public void onLongPress(MotionEvent e) {
                     if (mValid) {
-                        mActionMode = startActionMode(mCopyActionModeCallback,
-                                ActionMode.TYPE_FLOATING);
+                        performLongClick();
                     }
                 }
             });
-        setOnTouchListener(mTouchListener);
-        setHorizontallyScrolling(false);  // do it ourselves
-        setCursorVisible(false);
-        mExponentColorSpan = new ForegroundColorSpan(
-                context.getColor(R.color.display_result_exponent_text_color));
+        setOnTouchListener(new View.OnTouchListener() {
+            @Override
+            public boolean onTouch(View v, MotionEvent event) {
+                return mGestureDetector.onTouchEvent(event);
+            }
+        });
 
-        // Copy ActionMode is triggered explicitly, not through
-        // setCustomSelectionActionModeCallback.
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            setupActionMode();
+        } else {
+            setupContextMenu();
+        }
+
+        setCursorVisible(false);
     }
 
     void setEvaluator(Evaluator evaluator) {
         mEvaluator = evaluator;
     }
 
+    // Compute maximum digit width the hard way.
+    private static float getMaxDigitWidth(TextPaint paint) {
+        // Compute the maximum advance width for each digit, thus accounting for between-character
+        // spaces. If we ever support other kinds of digits, we may have to avoid kerning effects
+        // that could reduce the advance width within this particular string.
+        final String allDigits = "0123456789";
+        final float[] widths = new float[allDigits.length()];
+        paint.getTextWidths(allDigits, widths);
+        float maxWidth = 0;
+        for (float x : widths) {
+            maxWidth = Math.max(x, maxWidth);
+        }
+        return maxWidth;
+    }
+
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        if (!isLaidOut()) {
+            // Set a minimum height so scaled error messages won't affect our layout.
+            setMinimumHeight(getLineHeight() + getCompoundPaddingBottom()
+                    + getCompoundPaddingTop());
+        }
 
         final TextPaint paint = getPaint();
         final Context context = getContext();
-        final float newCharWidth = Layout.getDesiredWidth("\u2007", paint);
+        final float newCharWidth = getMaxDigitWidth(paint);
         // Digits are presumed to have no more than newCharWidth.
-        // We sometimes replace a character by an ellipsis or, due to SCI_NOTATION_EXTRA, add
-        // an extra decimal separator beyond the maximum number of characters we normally allow.
-        // Empirically, our minus sign is also slightly wider than a digit, so we have to
-        // account for that.  We never have both an ellipsis and two minus signs, and
-        // we assume an ellipsis is no narrower than a minus sign.
+        // There are two instances when we know that the result is otherwise narrower than
+        // expected:
+        // 1. For standard scientific notation (our type 1), we know that we have a norrow decimal
+        // point and no (usually wide) ellipsis symbol. We allow one extra digit
+        // (SCI_NOTATION_EXTRA) to compensate, and consider that in determining available width.
+        // 2. If we are using digit grouping separators and a decimal point, we give ourselves
+        // a fractional extra space for those separators, the value of which depends on whether
+        // there is also an ellipsis.
+        //
+        // Maximum extra space we need in various cases:
+        // Type 1 scientific notation, assuming ellipsis, minus sign and E are wider than a digit:
+        //    Two minus signs + "E" + "." - 3 digits.
+        // Type 2 scientific notation:
+        //    Ellipsis + "E" + "-" - 3 digits.
+        // In the absence of scientific notation, we may need a little less space.
+        // We give ourselves a bit of extra credit towards comma insertion and give
+        // ourselves more if we have either
+        //    No ellipsis, or
+        //    A decimal separator.
+
+        // Calculate extra space we need to reserve, in addition to character count.
         final float decimalSeparatorWidth = Layout.getDesiredWidth(
                 context.getString(R.string.dec_point), paint);
-        final float minusExtraWidth = Layout.getDesiredWidth(
-                context.getString(R.string.op_sub), paint) - newCharWidth;
-        final float ellipsisExtraWidth = Layout.getDesiredWidth(KeyMaps.ELLIPSIS, paint)
-                - newCharWidth;
-        final int extraWidth = (int) (Math.ceil(Math.max(decimalSeparatorWidth + minusExtraWidth,
-                ellipsisExtraWidth)) + Math.max(minusExtraWidth, 0.0f));
+        final float minusWidth = Layout.getDesiredWidth(context.getString(R.string.op_sub), paint);
+        final float minusExtraWidth = Math.max(minusWidth - newCharWidth, 0.0f);
+        final float ellipsisWidth = Layout.getDesiredWidth(KeyMaps.ELLIPSIS, paint);
+        final float ellipsisExtraWidth =  Math.max(ellipsisWidth - newCharWidth, 0.0f);
+        final float expWidth = Layout.getDesiredWidth(KeyMaps.translateResult("e"), paint);
+        final float expExtraWidth =  Math.max(expWidth - newCharWidth, 0.0f);
+        final float type1Extra = 2 * minusExtraWidth + expExtraWidth + decimalSeparatorWidth;
+        final float type2Extra = ellipsisExtraWidth + expExtraWidth + minusExtraWidth;
+        final float extraWidth = Math.max(type1Extra, type2Extra);
+        final int intExtraWidth = (int) Math.ceil(extraWidth) + 1 /* to cover rounding sins */;
         final int newWidthConstraint = MeasureSpec.getSize(widthMeasureSpec)
-                - (getPaddingLeft() + getPaddingRight()) - extraWidth;
+                - (getPaddingLeft() + getPaddingRight()) - intExtraWidth;
+
+        // Calculate other width constants we need to handle grouping separators.
+        final float groupingSeparatorW =
+                Layout.getDesiredWidth(KeyMaps.translateResult(","), paint);
+        // Credits in the absence of any scientific notation:
+        float noExponentCredit = extraWidth - Math.max(ellipsisExtraWidth, minusExtraWidth);
+        final float noEllipsisCredit = extraWidth - minusExtraWidth;  // includes noExponentCredit.
+        final float decimalCredit = Math.max(newCharWidth - decimalSeparatorWidth, 0.0f);
+
+        mNoExponentCredit = noExponentCredit / newCharWidth;
         synchronized(mWidthLock) {
             mWidthConstraint = newWidthConstraint;
             mCharWidth = newCharWidth;
+            mNoEllipsisCredit = noEllipsisCredit / newCharWidth;
+            mDecimalCredit = decimalCredit / newCharWidth;
+            mGroupingSeparatorWidthRatio = groupingSeparatorW / newCharWidth;
+        }
+
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+    }
+
+    /**
+     * Return the number of additional digit widths required to add digit separators to
+     * the supplied string prefix.
+     * The string prefix is assumed to represent a whole number, after skipping leading non-digits.
+     * Callable from non-UI thread.
+     */
+    public float separatorChars(String s, int len) {
+        int start = 0;
+        while (start < len && !Character.isDigit(s.charAt(start))) {
+            ++start;
+        }
+        // We assume the rest consists of digits, and for consistency with the rest
+        // of the code, we assume all digits have width mCharWidth.
+        final int nDigits = len - start;
+        // We currently insert a digit separator every three digits.
+        final int nSeparators = (nDigits - 1) / 3;
+        synchronized(mWidthLock) {
+            // Always return an upper bound, even in the presence of rounding errors.
+            return nSeparators * mGroupingSeparatorWidthRatio;
+        }
+    }
+
+    /**
+     * Return extra width credit for absence of ellipsis, as fraction of a digit width.
+     * May be called by non-UI thread.
+     */
+    public float getNoEllipsisCredit() {
+        synchronized(mWidthLock) {
+            return mNoEllipsisCredit;
+        }
+    }
+
+    /**
+     * Return extra width credit for presence of a decimal point, as fraction of a digit width.
+     * May be called by non-UI thread.
+     */
+    public float getDecimalCredit() {
+        synchronized(mWidthLock) {
+            return mDecimalCredit;
         }
     }
 
@@ -217,6 +351,7 @@
 
     /**
      * Initiate display of a new result.
+     * Only called from UI thread.
      * The parameters specify various properties of the result.
      * @param initPrec Initial display precision computed by evaluator. (1 = tenths digit)
      * @param msd Position of most significant digit.  Offset from left of string.
@@ -238,47 +373,46 @@
      * will eventually be replaced by an exponent.
      * Just appending the exponent during formatting would be simpler, but would produce
      * jumpier results during transitions.
+     * Only called from UI thread.
      */
     private void initPositions(int initPrecOffset, int msdIndex, int lsdOffset,
             String truncatedWholePart) {
-        float charWidth;
         int maxChars = getMaxChars();
+        mWholeLen = truncatedWholePart.length();
+        // Allow a tiny amount of slop for associativity/rounding differences in length
+        // calculation.  If getPreferredPrec() decided it should fit, we want to make it fit, too.
+        // We reserved one extra pixel, so the extra length is OK.
+        final int nSeparatorChars = (int) Math.ceil(
+                separatorChars(truncatedWholePart, truncatedWholePart.length())
+                - getNoEllipsisCredit() - 0.0001f);
+        mWholePartFits = mWholeLen + nSeparatorChars <= maxChars;
         mLastPos = INVALID;
         mLsdOffset = lsdOffset;
-        synchronized(mWidthLock) {
-            charWidth = mCharWidth;
-        }
-        mCurrentPos = mMinPos = (int) Math.round(initPrecOffset * charWidth);
+        mAppendExponent = false;
         // Prevent scrolling past initial position, which is calculated to show leading digits.
+        mCurrentPos = mMinPos = (int) Math.round(initPrecOffset * mCharWidth);
         if (msdIndex == Evaluator.INVALID_MSD) {
             // Possible zero value
             if (lsdOffset == Integer.MIN_VALUE) {
                 // Definite zero value.
                 mMaxPos = mMinPos;
-                mMaxCharOffset = (int) Math.round(mMaxPos/charWidth);
+                mMaxCharOffset = (int) Math.round(mMaxPos/mCharWidth);
                 mScrollable = false;
             } else {
                 // May be very small nonzero value.  Allow user to find out.
                 mMaxPos = mMaxCharOffset = MAX_RIGHT_SCROLL;
-                mMinPos -= charWidth;  // Allow for future minus sign.
+                mMinPos -= mCharWidth;  // Allow for future minus sign.
                 mScrollable = true;
             }
             return;
         }
-        int wholeLen =  truncatedWholePart.length();
         int negative = truncatedWholePart.charAt(0) == '-' ? 1 : 0;
-        if (msdIndex > wholeLen && msdIndex <= wholeLen + 3) {
+        if (msdIndex > mWholeLen && msdIndex <= mWholeLen + 3) {
             // Avoid tiny negative exponent; pretend msdIndex is just to the right of decimal point.
-            msdIndex = wholeLen - 1;
+            msdIndex = mWholeLen - 1;
         }
-        int minCharOffset = msdIndex - wholeLen;
-                                // Position of leftmost significant digit relative to dec. point.
-                                // Usually negative.
-        mMaxCharOffset = MAX_RIGHT_SCROLL; // How far does it make sense to scroll right?
-        // If msd is left of decimal point should logically be
-        // mMinPos = - (int) Math.ceil(getPaint().measureText(truncatedWholePart)), but
-        // we eventually translate to a character position by dividing by mCharWidth.
-        // To avoid rounding issues, we use the analogous computation here.
+        // Set to position of leftmost significant digit relative to dec. point. Usually negative.
+        int minCharOffset = msdIndex - mWholeLen;
         if (minCharOffset > -1 && minCharOffset < MAX_LEADING_ZEROES + 2) {
             // Small number of leading zeroes, avoid scientific notation.
             minCharOffset = -1;
@@ -293,11 +427,16 @@
             if (mMaxCharOffset < -1) {
                 currentExpLen = expLen(-minCharOffset - 1);
             } else if (minCharOffset > -1 || mMaxCharOffset >= maxChars) {
-                // Number either entirely to the right of decimal point, or decimal point not
-                // visible when scrolled to the right.
+                // Number is either entirely to the right of decimal point, or decimal point is
+                // not visible when scrolled to the right.
                 currentExpLen = expLen(-minCharOffset);
             }
-            mScrollable = (mMaxCharOffset + currentExpLen - minCharOffset + negative >= maxChars);
+            // Exponent length does not included added decimal point.  But whenever we add a
+            // decimal point, we allow an extra character (SCI_NOTATION_EXTRA).
+            final int separatorLength = mWholePartFits && minCharOffset < -3 ? nSeparatorChars : 0;
+            mScrollable = (mMaxCharOffset + currentExpLen + separatorLength - minCharOffset
+                    + negative >= maxChars);
+            // Now adjust mMaxCharOffset for any required exponent.
             int newMaxCharOffset;
             if (currentExpLen > 0) {
                 if (mScrollable) {
@@ -310,10 +449,32 @@
                     // Very unlikely; just drop exponent.
                     mMaxCharOffset = -1;
                 } else {
-                    mMaxCharOffset = newMaxCharOffset;
+                    mMaxCharOffset = Math.min(newMaxCharOffset, MAX_RIGHT_SCROLL);
                 }
+                mMaxPos = Math.min((int) Math.round(mMaxCharOffset * mCharWidth),
+                        MAX_RIGHT_SCROLL);
+            } else if (!mWholePartFits && !mScrollable) {
+                // Corner case in which entire number fits, but not with grouping separators.  We
+                // will use an exponent in un-scrolled position, which may hide digits.  Scrolling
+                // by one character will remove the exponent and reveal the last digits.  Note
+                // that in the forced scientific notation case, the exponent length is not
+                // factored into mMaxCharOffset, since we do not want such an increase to impact
+                // scrolling behavior.  In the unscrollable case, we thus have to append the
+                // exponent at the end using the forcePrecision argument to formatResult, in order
+                // to ensure that we get the entire result.
+                mScrollable = (mMaxCharOffset + expLen(-minCharOffset - 1) - minCharOffset
+                        + negative >= maxChars);
+                if (mScrollable) {
+                    mMaxPos = (int) Math.ceil(mMinPos + mCharWidth);
+                    // Single character scroll will remove exponent and show remaining piece.
+                } else {
+                    mMaxPos = mMinPos;
+                    mAppendExponent = true;
+                }
+            } else {
+                mMaxPos = Math.min((int) Math.round(mMaxCharOffset * mCharWidth),
+                        MAX_RIGHT_SCROLL);
             }
-            mMaxPos = Math.min((int) Math.round(mMaxCharOffset * charWidth), MAX_RIGHT_SCROLL);
             if (!mScrollable) {
                 // Position the number consistently with our assumptions to make sure it
                 // actually fits.
@@ -325,10 +486,25 @@
         }
     }
 
+    /**
+     * Display error message indicated by resourceId.
+     * UI thread only.
+     */
     void displayError(int resourceId) {
         mValid = true;
         mScrollable = false;
-        setText(resourceId);
+        final String msg = getContext().getString(resourceId);
+        final float measuredWidth = Layout.getDesiredWidth(msg, getPaint());
+        if (measuredWidth > mWidthConstraint) {
+            // Multiply by .99 to avoid rounding effects.
+            final float scaleFactor = 0.99f * mWidthConstraint / measuredWidth;
+            final RelativeSizeSpan smallTextSpan = new RelativeSizeSpan(scaleFactor);
+            final SpannableString scaledMsg = new SpannableString(msg);
+            scaledMsg.setSpan(smallTextSpan, 0, msg.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+            setText(scaledMsg);
+        } else {
+            setText(msg);
+        }
     }
 
     private final int MAX_COPY_SIZE = 1000000;
@@ -336,9 +512,10 @@
     /*
      * Return the most significant digit position in the given string or Evaluator.INVALID_MSD.
      * Unlike Evaluator.getMsdIndexOf, we treat a final 1 as significant.
+     * Pure function; callable from anywhere.
      */
     public static int getNaiveMsdIndexOf(String s) {
-        int len = s.length();
+        final int len = s.length();
         for (int i = 0; i < len; ++i) {
             char c = s.charAt(i);
             if (c != '-' && c != '.' && c != '0') {
@@ -348,41 +525,64 @@
         return Evaluator.INVALID_MSD;
     }
 
-    // Format a result returned by Evaluator.getString() into a single line containing ellipses
-    // (if appropriate) and an exponent (if appropriate).  precOffset is the value that was passed
-    // to getString and thus identifies the significance of the rightmost digit.
-    // A value of 1 means the rightmost digits corresponds to tenths.
-    // maxDigs is the maximum number of characters in the result.
-    // We set lastDisplayedOffset[0] to the offset of the last digit actually appearing in
-    // the display.
-    // If forcePrecision is true, we make sure that the last displayed digit corresponds to
-    // precOffset, and allow maxDigs to be exceeded in assing the exponent.
-    // We add two distinct kinds of exponents:
-    // (1) If the final result contains the leading digit we use standard scientific notation.
-    // (2) If not, we add an exponent corresponding to an interpretation of the final result as
-    //     an integer.
-    // We add an ellipsis on the left if the result was truncated.
-    // We add ellipses and exponents in a way that leaves most digits in the position they
-    // would have been in had we not done so.
-    // This minimizes jumps as a result of scrolling.  Result is NOT internationalized,
-    // uses "E" for exponent.
-    public String formatResult(String in, int precOffset, int maxDigs, boolean truncated,
-            boolean negative, int lastDisplayedOffset[], boolean forcePrecision) {
+    /**
+     * Format a result returned by Evaluator.getString() into a single line containing ellipses
+     * (if appropriate) and an exponent (if appropriate).
+     * We add two distinct kinds of exponents:
+     * (1) If the final result contains the leading digit we use standard scientific notation.
+     * (2) If not, we add an exponent corresponding to an interpretation of the final result as
+     *     an integer.
+     * We add an ellipsis on the left if the result was truncated.
+     * We add ellipses and exponents in a way that leaves most digits in the position they
+     * would have been in had we not done so. This minimizes jumps as a result of scrolling.
+     * Result is NOT internationalized, uses "E" for exponent.
+     * Called only from UI thread; We sometimes omit locking for fields.
+     * @param precOffset The value that was passed to getString. Identifies the significance of
+                the rightmost digit. A value of 1 means the rightmost digits corresponds to tenths.
+     * @param maxDigs The maximum number of characters in the result
+     * @param truncated The in parameter was already truncated, beyond possibly removing the
+                minus sign.
+     * @param negative The in parameter represents a negative result. (Minus sign may be removed
+                without setting truncated.)
+     * @param lastDisplayedOffset  If not null, we set lastDisplayedOffset[0] to the offset of
+                the last digit actually appearing in the display.
+     * @param forcePrecision If true, we make sure that the last displayed digit corresponds to
+                precOffset, and allow maxDigs to be exceeded in adding the exponent and commas.
+     * @param forceSciNotation Force scientific notation. May be set because we don't have
+                space for grouping separators, but whole number otherwise fits.
+     * @param insertCommas Insert commas (literally, not internationalized) as digit separators.
+                We only ever do this for the integral part of a number, and only when no
+                exponent is displayed in the initial position. The combination of which means
+                that we only do it when no exponent is displayed.
+                We insert commas in a way that does consider the width of the actual localized digit
+                separator. Commas count towards maxDigs as the appropriate fraction of a digit.
+     */
+    private String formatResult(String in, int precOffset, int maxDigs, boolean truncated,
+            boolean negative, int lastDisplayedOffset[], boolean forcePrecision,
+            boolean forceSciNotation, boolean insertCommas) {
         final int minusSpace = negative ? 1 : 0;
         final int msdIndex = truncated ? -1 : getNaiveMsdIndexOf(in);  // INVALID_MSD is OK.
         String result = in;
+        boolean needEllipsis = false;
         if (truncated || (negative && result.charAt(0) != '-')) {
+            needEllipsis = true;
             result = KeyMaps.ELLIPSIS + result.substring(1, result.length());
             // Ellipsis may be removed again in the type(1) scientific notation case.
         }
         final int decIndex = result.indexOf('.');
-        lastDisplayedOffset[0] = precOffset;
-        if ((decIndex == -1 || msdIndex != Evaluator.INVALID_MSD
+        if (lastDisplayedOffset != null) {
+            lastDisplayedOffset[0] = precOffset;
+        }
+        if (forceSciNotation || (decIndex == -1 || msdIndex != Evaluator.INVALID_MSD
                 && msdIndex - decIndex > MAX_LEADING_ZEROES + 1) &&  precOffset != -1) {
-            // No decimal point displayed, and it's not just to the right of the last digit,
-            // or we should suppress leading zeroes.
+            // Either:
+            // 1) No decimal point displayed, and it's not just to the right of the last digit, or
+            // 2) we are at the front of a number whos integral part is too large to allow
+            // comma insertion, or
+            // 3) we should suppress leading zeroes.
             // Add an exponent to let the user track which digits are currently displayed.
             // Start with type (2) exponent if we dropped no digits. -1 accounts for decimal point.
+            // We currently never show digit separators together with an exponent.
             final int initExponent = precOffset > 0 ? -precOffset : -precOffset - 1;
             int exponent = initExponent;
             boolean hasPoint = false;
@@ -395,6 +595,13 @@
                 // delete leading zeroes.
                 // We try to keep leading digits roughly in position, and never
                 // lengthen the result by more than SCI_NOTATION_EXTRA.
+                if (decIndex > msdIndex) {
+                    // In the forceSciNotation, we can have a decimal point in the relevant digit
+                    // range. Remove it.
+                    result = result.substring(0, decIndex)
+                            + result.substring(decIndex + 1, result.length());
+                    // msdIndex and precOffset unaffected.
+                }
                 final int resLen = result.length();
                 String fraction = result.substring(msdIndex + 1, resLen);
                 result = (negative ? "-" : "") + result.substring(msdIndex, msdIndex + 1)
@@ -433,46 +640,121 @@
                     }
                 }
                 result = result.substring(0, result.length() - dropDigits);
-                lastDisplayedOffset[0] -= dropDigits;
+                if (lastDisplayedOffset != null) {
+                    lastDisplayedOffset[0] -= dropDigits;
+                }
             }
             result = result + "E" + Integer.toString(exponent);
+        } else if (insertCommas) {
+            // Add commas to the whole number section, and then truncate on left to fit,
+            // counting commas as a fractional digit.
+            final int wholeStart = needEllipsis ? 1 : 0;
+            int orig_length = result.length();
+            final float nCommaChars;
+            if (decIndex != -1) {
+                nCommaChars = separatorChars(result, decIndex);
+                result = StringUtils.addCommas(result, wholeStart, decIndex)
+                        + result.substring(decIndex, orig_length);
+            } else {
+                nCommaChars = separatorChars(result, orig_length);
+                result = StringUtils.addCommas(result, wholeStart, orig_length);
+            }
+            if (needEllipsis) {
+                orig_length -= 1;  // Exclude ellipsis.
+            }
+            final float len = orig_length + nCommaChars;
+            int deletedChars = 0;
+            final float ellipsisCredit = getNoEllipsisCredit();
+            final float decimalCredit = getDecimalCredit();
+            final float effectiveLen = len - (decIndex == -1 ? 0 : getDecimalCredit());
+            final float ellipsisAdjustment =
+                    needEllipsis ? mNoExponentCredit : getNoEllipsisCredit();
+            // As above, we allow for a tiny amount of extra length here, for consistency with
+            // getPreferredPrec().
+            if (effectiveLen - ellipsisAdjustment > (float) (maxDigs - wholeStart) + 0.0001f
+                && !forcePrecision) {
+                float deletedWidth = 0.0f;
+                while (effectiveLen - mNoExponentCredit - deletedWidth
+                        > (float) (maxDigs - 1 /* for ellipsis */)) {
+                    if (result.charAt(deletedChars) == ',') {
+                        deletedWidth += mGroupingSeparatorWidthRatio;
+                    } else {
+                        deletedWidth += 1.0f;
+                    }
+                    deletedChars++;
+                }
+            }
+            if (deletedChars > 0) {
+                result = KeyMaps.ELLIPSIS + result.substring(deletedChars, result.length());
+            } else if (needEllipsis) {
+                result = KeyMaps.ELLIPSIS + result;
+            }
         }
         return result;
     }
 
     /**
      * Get formatted, but not internationalized, result from mEvaluator.
-     * @param precOffset requested position (1 = tenths) of last included digit.
-     * @param maxSize Maximum number of characters (more or less) in result.
-     * @param lastDisplayedOffset Zeroth entry is set to actual offset of last included digit,
-     *                            after adjusting for exponent, etc.
+     * @param precOffset requested position (1 = tenths) of last included digit
+     * @param maxSize maximum number of characters (more or less) in result
+     * @param lastDisplayedOffset zeroth entry is set to actual offset of last included digit,
+     *                            after adjusting for exponent, etc.  May be null.
      * @param forcePrecision Ensure that last included digit is at pos, at the expense
      *                       of treating maxSize as a soft limit.
+     * @param forceSciNotation Force scientific notation, even if not required by maxSize.
+     * @param insertCommas Insert commas as digit separators.
      */
     private String getFormattedResult(int precOffset, int maxSize, int lastDisplayedOffset[],
-            boolean forcePrecision) {
+            boolean forcePrecision, boolean forceSciNotation, boolean insertCommas) {
         final boolean truncated[] = new boolean[1];
         final boolean negative[] = new boolean[1];
         final int requestedPrecOffset[] = {precOffset};
         final String rawResult = mEvaluator.getString(requestedPrecOffset, mMaxCharOffset,
                 maxSize, truncated, negative);
         return formatResult(rawResult, requestedPrecOffset[0], maxSize, truncated[0], negative[0],
-                lastDisplayedOffset, forcePrecision);
+                lastDisplayedOffset, forcePrecision, forceSciNotation, insertCommas);
    }
 
-    // Return entire result (within reason) up to current displayed precision.
-    public String getFullText() {
+    /**
+     * Return entire result (within reason) up to current displayed precision.
+     * @param withSeparators  Add digit separators
+     */
+    public String getFullText(boolean withSeparators) {
         if (!mValid) return "";
         if (!mScrollable) return getText().toString();
-        int currentCharOffset = getCurrentCharOffset();
-        int unused[] = new int[1];
         return KeyMaps.translateResult(getFormattedResult(mLastDisplayedOffset, MAX_COPY_SIZE,
-                unused, true));
+                null, true /* forcePrecision */, false /* forceSciNotation */, withSeparators));
     }
 
+    /**
+     * Did the above produce a correct result?
+     * UI thread only.
+     */
     public boolean fullTextIsExact() {
-        return !mScrollable
-                || mMaxCharOffset == getCurrentCharOffset() && mMaxCharOffset != MAX_RIGHT_SCROLL;
+        return !mScrollable || (mMaxCharOffset == getCharOffset(mCurrentPos)
+                && mMaxCharOffset != MAX_RIGHT_SCROLL);
+    }
+
+    /**
+     * Get entire result up to current displayed precision, or up to MAX_COPY_EXTRA additional
+     * digits, if it will lead to an exact result.
+     */
+    public String getFullCopyText() {
+        if (!mValid
+                || mLsdOffset == Integer.MAX_VALUE
+                || fullTextIsExact()
+                || mWholeLen > MAX_RECOMPUTE_DIGITS
+                || mWholeLen + mLsdOffset > MAX_RECOMPUTE_DIGITS
+                || mLsdOffset - mLastDisplayedOffset > MAX_COPY_EXTRA) {
+            return getFullText(false /* withSeparators */);
+        }
+        // It's reasonable to compute and copy the exact result instead.
+        final int nonNegLsdOffset = Math.max(0, mLsdOffset);
+        final String rawResult = mEvaluator.getResult().toStringTruncated(nonNegLsdOffset);
+        final String formattedResult = formatResult(rawResult, nonNegLsdOffset, MAX_COPY_SIZE,
+                false, rawResult.charAt(0) == '-', null, true /* forcePrecision */,
+                false /* forceSciNotation */, false /* insertCommas */);
+        return KeyMaps.translateResult(formattedResult);
     }
 
     /**
@@ -501,10 +783,12 @@
         return mScrollable;
     }
 
-    int getCurrentCharOffset() {
-        synchronized(mWidthLock) {
-            return (int) Math.round(mCurrentPos / mCharWidth);
-        }
+    /**
+     * Map pixel position to digit offset.
+     * UI thread only.
+     */
+    int getCharOffset(int pos) {
+        return (int) Math.round(pos / mCharWidth);  // Lock not needed.
     }
 
     void clear() {
@@ -513,11 +797,19 @@
         setText("");
     }
 
+    /**
+     * Refresh display.
+     * Only called in UI thread.
+     */
     void redisplay() {
-        int currentCharOffset = getCurrentCharOffset();
+        int currentCharOffset = getCharOffset(mCurrentPos);
         int maxChars = getMaxChars();
         int lastDisplayedOffset[] = new int[1];
-        String result = getFormattedResult(currentCharOffset, maxChars, lastDisplayedOffset, false);
+        String result = getFormattedResult(currentCharOffset, maxChars, lastDisplayedOffset,
+                mAppendExponent /* forcePrecision; preserve entire result */,
+                !mWholePartFits
+                &&  currentCharOffset == getCharOffset(mMinPos) /* forceSciNotation */,
+                mWholePartFits /* insertCommas */ );
         int expIndex = result.indexOf('E');
         result = KeyMaps.translateResult(result);
         if (expIndex > 0 && result.indexOf('.') == -1) {
@@ -538,7 +830,7 @@
         if (!mScrollable) return;
         if (mScroller.computeScrollOffset()) {
             mCurrentPos = mScroller.getCurrX();
-            if (mCurrentPos != mLastPos) {
+            if (getCharOffset(mCurrentPos) != getCharOffset(mLastPos)) {
                 mLastPos = mCurrentPos;
                 redisplay();
             }
@@ -548,76 +840,137 @@
         }
     }
 
-    // Copy support:
+    /**
+     * Use ActionMode for copy support on M and higher.
+     */
+    @TargetApi(Build.VERSION_CODES.M)
+    private void setupActionMode() {
+        mCopyActionModeCallback = new ActionMode.Callback2() {
 
-    private ActionMode.Callback2 mCopyActionModeCallback = new ActionMode.Callback2() {
+            @Override
+            public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+                final MenuInflater inflater = mode.getMenuInflater();
+                return createCopyMenu(inflater, menu);
+            }
 
-        private BackgroundColorSpan mHighlightSpan;
+            @Override
+            public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+                return false; // Return false if nothing is done
+            }
 
-        private void highlightResult() {
-            final Spannable text = (Spannable) getText();
-            mHighlightSpan = new BackgroundColorSpan(getHighlightColor());
-            text.setSpan(mHighlightSpan, 0, text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
-        }
+            @Override
+            public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+                if (onMenuItemClick(item)) {
+                    mode.finish();
+                    return true;
+                } else {
+                    return false;
+                }
+            }
 
-        private void unhighlightResult() {
-            final Spannable text = (Spannable) getText();
-            text.removeSpan(mHighlightSpan);
-        }
+            @Override
+            public void onDestroyActionMode(ActionMode mode) {
+                unhighlightResult();
+                mActionMode = null;
+            }
 
-        @Override
-        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
-            MenuInflater inflater = mode.getMenuInflater();
-            inflater.inflate(R.menu.copy, menu);
-            highlightResult();
-            return true;
-        }
+            @Override
+            public void onGetContentRect(ActionMode mode, View view, Rect outRect) {
+                super.onGetContentRect(mode, view, outRect);
 
-        @Override
-        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
-            return false; // Return false if nothing is done
-        }
+                outRect.left += view.getPaddingLeft();
+                outRect.top += view.getPaddingTop();
+                outRect.right -= view.getPaddingRight();
+                outRect.bottom -= view.getPaddingBottom();
+                final int width = (int) Layout.getDesiredWidth(getText(), getPaint());
+                if (width < outRect.width()) {
+                    outRect.left = outRect.right - width;
+                }
 
-        @Override
-        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
-            switch (item.getItemId()) {
-            case R.id.menu_copy:
-                copyContent();
-                mode.finish();
-                return true;
-            default:
+                if (!BuildCompat.isAtLeastN()) {
+                    // The CAB (prior to N) only takes the translation of a view into account, so
+                    // if a scale is applied to the view then the offset outRect will end up being
+                    // positioned incorrectly. We workaround that limitation by manually applying
+                    // the scale to the outRect, which the CAB will then offset to the correct
+                    // position.
+                    final float scaleX = view.getScaleX();
+                    final float scaleY = view.getScaleY();
+                    outRect.left *= scaleX;
+                    outRect.right *= scaleX;
+                    outRect.top *= scaleY;
+                    outRect.bottom *= scaleY;
+                }
+            }
+        };
+        setOnLongClickListener(new View.OnLongClickListener() {
+            @Override
+            public boolean onLongClick(View v) {
+                if (mValid) {
+                    mActionMode = startActionMode(mCopyActionModeCallback,
+                            ActionMode.TYPE_FLOATING);
+                    return true;
+                }
                 return false;
             }
-        }
+        });
+    }
 
-        @Override
-        public void onDestroyActionMode(ActionMode mode) {
-            unhighlightResult();
-            mActionMode = null;
-        }
-
-        @Override
-        public void onGetContentRect(ActionMode mode, View view, Rect outRect) {
-            super.onGetContentRect(mode, view, outRect);
-            outRect.left += getPaddingLeft();
-            outRect.top += getPaddingTop();
-            outRect.right -= getPaddingRight();
-            outRect.bottom -= getPaddingBottom();
-            final int width = (int) Layout.getDesiredWidth(getText(), getPaint());
-            if (width < outRect.width()) {
-                outRect.left = outRect.right - width;
+    /**
+     * Use ContextMenu for copy support on L and lower.
+     */
+    private void setupContextMenu() {
+        setOnCreateContextMenuListener(new OnCreateContextMenuListener() {
+            @Override
+            public void onCreateContextMenu(ContextMenu contextMenu, View view,
+                    ContextMenu.ContextMenuInfo contextMenuInfo) {
+                final MenuInflater inflater = new MenuInflater(getContext());
+                createCopyMenu(inflater, contextMenu);
+                mContextMenu = contextMenu;
+                for(int i = 0; i < contextMenu.size(); i ++) {
+                    contextMenu.getItem(i).setOnMenuItemClickListener(CalculatorResult.this);
+                }
             }
-        }
-    };
+        });
+        setOnLongClickListener(new View.OnLongClickListener() {
+            @Override
+            public boolean onLongClick(View v) {
+                if (mValid) {
+                    return showContextMenu();
+                }
+                return false;
+            }
+        });
+    }
 
-    public boolean stopActionMode() {
+    private boolean createCopyMenu(MenuInflater inflater, Menu menu) {
+        inflater.inflate(R.menu.copy, menu);
+        highlightResult();
+        return true;
+    }
+
+    public boolean stopActionModeOrContextMenu() {
         if (mActionMode != null) {
             mActionMode.finish();
             return true;
         }
+        if (mContextMenu != null) {
+            unhighlightResult();
+            mContextMenu.close();
+            return true;
+        }
         return false;
     }
 
+    private void highlightResult() {
+        final Spannable text = (Spannable) getText();
+        text.setSpan(mHighlightSpan, 0, text.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+    }
+
+    private void unhighlightResult() {
+        final Spannable text = (Spannable) getText();
+        text.removeSpan(mHighlightSpan);
+    }
+
     private void setPrimaryClip(ClipData clip) {
         ClipboardManager clipboard = (ClipboardManager) getContext().
                                                getSystemService(Context.CLIPBOARD_SERVICE);
@@ -625,7 +978,7 @@
     }
 
     private void copyContent() {
-        final CharSequence text = getFullText();
+        final CharSequence text = getFullCopyText();
         ClipboardManager clipboard =
                 (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE);
         // We include a tag URI, to allow us to recognize our own results and handle them
@@ -637,4 +990,20 @@
         Toast.makeText(getContext(), R.string.text_copied_toast, Toast.LENGTH_SHORT).show();
     }
 
+    @Override
+    public boolean onMenuItemClick(MenuItem item) {
+        switch (item.getItemId()) {
+            case R.id.menu_copy:
+                if (mEvaluator.reevaluationInProgress()) {
+                    // Refuse to copy placeholder characters.
+                    return false;
+                } else {
+                    copyContent();
+                    unhighlightResult();
+                    return true;
+                }
+            default:
+                return false;
+        }
+    }
 }
diff --git a/src/com/android/calculator2/CalculatorScrollView.java b/src/com/android/calculator2/CalculatorScrollView.java
new file mode 100644
index 0000000..bcf5650
--- /dev/null
+++ b/src/com/android/calculator2/CalculatorScrollView.java
@@ -0,0 +1,70 @@
+/*
+ * 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;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.HorizontalScrollView;
+
+public class CalculatorScrollView extends HorizontalScrollView {
+
+    public CalculatorScrollView(Context context) {
+        this(context, null /* attrs */);
+    }
+
+    public CalculatorScrollView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0 /* defStyleAttr */);
+    }
+
+    public CalculatorScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    @Override
+    protected void measureChild(View child, int parentWidthMeasureSpec,
+            int parentHeightMeasureSpec) {
+        // Allow child to be as wide as they want.
+        parentWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
+                MeasureSpec.getSize(parentWidthMeasureSpec), MeasureSpec.UNSPECIFIED);
+
+        final ViewGroup.LayoutParams lp = child.getLayoutParams();
+        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
+                0 /* padding */, lp.width);
+        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
+                getPaddingTop() + getPaddingBottom(), lp.height);
+
+        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+    }
+
+    @Override
+    protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
+            int parentHeightMeasureSpec, int heightUsed) {
+        // Allow child to be as wide as they want.
+        parentWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
+                MeasureSpec.getSize(parentWidthMeasureSpec), MeasureSpec.UNSPECIFIED);
+
+        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
+        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
+                lp.leftMargin + lp.rightMargin, lp.width);
+        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
+                getPaddingTop() + getPaddingBottom() + lp.topMargin + lp.bottomMargin, lp.height);
+
+        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+    }
+}
diff --git a/src/com/android/calculator2/CalculatorText.java b/src/com/android/calculator2/CalculatorText.java
index f944071..de2a843 100644
--- a/src/com/android/calculator2/CalculatorText.java
+++ b/src/com/android/calculator2/CalculatorText.java
@@ -16,17 +16,19 @@
 
 package com.android.calculator2;
 
+import android.annotation.TargetApi;
 import android.content.ClipData;
 import android.content.ClipboardManager;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Rect;
+import android.os.Build;
 import android.text.Layout;
 import android.text.TextPaint;
-import android.text.method.ScrollingMovementMethod;
 import android.util.AttributeSet;
 import android.util.TypedValue;
 import android.view.ActionMode;
+import android.view.ContextMenu;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
@@ -36,54 +38,9 @@
 /**
  * TextView adapted for Calculator display.
  */
-public class CalculatorText extends AlignedTextView implements View.OnLongClickListener {
+public class CalculatorText extends AlignedTextView implements MenuItem.OnMenuItemClickListener {
 
-    private final ActionMode.Callback2 mPasteActionModeCallback = new ActionMode.Callback2() {
-
-        @Override
-        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
-            if (item.getItemId() == R.id.menu_paste) {
-                paste();
-                mode.finish();
-                return true;
-            }
-            return false;
-        }
-
-        @Override
-        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
-            final ClipboardManager clipboard = (ClipboardManager) getContext()
-                    .getSystemService(Context.CLIPBOARD_SERVICE);
-            if (clipboard.hasPrimaryClip()) {
-                bringPointIntoView(length());
-                MenuInflater inflater = mode.getMenuInflater();
-                inflater.inflate(R.menu.paste, menu);
-                return true;
-            }
-            // Prevents the selection action mode on double tap.
-            return false;
-        }
-
-        @Override
-        public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
-            return false;
-        }
-
-        @Override
-        public void onDestroyActionMode(ActionMode mode) {
-            mActionMode = null;
-        }
-
-        @Override
-        public void onGetContentRect(ActionMode mode, View view, Rect outRect) {
-            super.onGetContentRect(mode, view, outRect);
-            outRect.top += getTotalPaddingTop();
-            outRect.right -= getTotalPaddingRight();
-            outRect.bottom -= getTotalPaddingBottom();
-            // Encourage menu positioning towards the right, possibly over formula.
-            outRect.left = outRect.right;
-        }
-    };
+    public static final String TAG_ACTION_MODE = "ACTION_MODE";
 
     // Temporary paint for use in layout methods.
     private final TextPaint mTempPaint = new TextPaint();
@@ -93,9 +50,9 @@
     private final float mStepTextSize;
 
     private int mWidthConstraint = -1;
-
     private ActionMode mActionMode;
-
+    private ActionMode.Callback mPasteActionModeCallback;
+    private ContextMenu mContextMenu;
     private OnPasteListener mOnPasteListener;
     private OnTextSizeChangeListener mOnTextSizeChangeListener;
 
@@ -120,47 +77,40 @@
                 (mMaximumTextSize - mMinimumTextSize) / 3);
         a.recycle();
 
-        // Allow scrolling by default.
-        setMovementMethod(ScrollingMovementMethod.getInstance());
-
-        // Reset the clickable flag, which is added when specifying a movement method.
-        setClickable(false);
-
-        // Add a long click to start the ActionMode manually.
-        setOnLongClickListener(this);
-    }
-
-    @Override
-    public boolean onLongClick(View v) {
-        mActionMode = startActionMode(mPasteActionModeCallback, ActionMode.TYPE_FLOATING);
-        return true;
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            setupActionMode();
+        } else {
+            setupContextMenu();
+        }
     }
 
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        if (!isLaidOut()) {
+            // Prevent shrinking/resizing with our variable textSize.
+            setTextSizeInternal(TypedValue.COMPLEX_UNIT_PX, mMaximumTextSize,
+                    false /* notifyListener */);
+            setMinimumHeight(getLineHeight() + getCompoundPaddingBottom()
+                    + getCompoundPaddingTop());
+        }
+
+        // Ensure we are at least as big as our parent.
+        final int width = MeasureSpec.getSize(widthMeasureSpec);
+        if (getMinimumWidth() != width) {
+            setMinimumWidth(width);
+        }
+
         // Re-calculate our textSize based on new width.
-        final int width = MeasureSpec.getSize(widthMeasureSpec)
+        mWidthConstraint = MeasureSpec.getSize(widthMeasureSpec)
                 - getPaddingLeft() - getPaddingRight();
-        if (mWidthConstraint != width) {
-            mWidthConstraint = width;
-
-            if (!isLaidOut()) {
-                // Prevent shrinking/resizing with our variable textSize.
-                setTextSizeInternal(TypedValue.COMPLEX_UNIT_PX, mMaximumTextSize,
-                        false /* notifyListener */);
-                setMinHeight(getLineHeight() + getCompoundPaddingBottom()
-                        + getCompoundPaddingTop());
-            }
-
-            setTextSizeInternal(TypedValue.COMPLEX_UNIT_PX, getVariableTextSize(getText()),
-                    false);
+        final float textSize = getVariableTextSize(getText());
+        if (getTextSize() != textSize) {
+            setTextSizeInternal(TypedValue.COMPLEX_UNIT_PX, textSize, false /* notifyListener */);
         }
 
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
     }
 
-    public int getWidthConstraint() { return mWidthConstraint; }
-
     @Override
     protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
         super.onTextChanged(text, start, lengthBefore, lengthAfter);
@@ -232,13 +182,13 @@
      */
     public void changeTextTo(CharSequence newText) {
         final CharSequence oldText = getText();
-        if (startsWith(newText, oldText)) {
-            final int newLen = newText.length();
-            final int oldLen = oldText.length();
-            if (newLen == oldLen + 1) {
+        final char separator = KeyMaps.translateResult(",").charAt(0);
+        final CharSequence added = StringUtils.getExtensionIgnoring(newText, oldText, separator);
+        if (added != null) {
+            if (added.length() == 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 char c = added.charAt(0);
                 final int id = KeyMaps.keyForChar(c);
                 final String descr = KeyMaps.toDescriptiveString(getContext(), id);
                 if (descr != null) {
@@ -246,20 +196,24 @@
                 } else {
                     announceForAccessibility(String.valueOf(c));
                 }
-            } else if (newLen > oldLen) {
-                announceForAccessibility(newText.subSequence(oldLen, newLen));
+            } else if (added.length() != 0) {
+                announceForAccessibility(added);
             }
         } else {
             announceForAccessibility(newText);
         }
-        setText(newText);
+        setText(newText, BufferType.SPANNABLE);
     }
 
-    public boolean stopActionMode() {
+    public boolean stopActionModeOrContextMenu() {
         if (mActionMode != null) {
             mActionMode.finish();
             return true;
         }
+        if (mContextMenu != null) {
+            mContextMenu.close();
+            return true;
+        }
         return false;
     }
 
@@ -271,6 +225,95 @@
         mOnPasteListener = listener;
     }
 
+    /**
+     * Use ActionMode for paste support on M and higher.
+     */
+    @TargetApi(Build.VERSION_CODES.M)
+    private void setupActionMode() {
+        mPasteActionModeCallback = new ActionMode.Callback2() {
+
+            @Override
+            public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+                if (onMenuItemClick(item)) {
+                    mode.finish();
+                    return true;
+                } else {
+                    return false;
+                }
+            }
+
+            @Override
+            public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+                mode.setTag(TAG_ACTION_MODE);
+                final MenuInflater inflater = mode.getMenuInflater();
+                return createPasteMenu(inflater, menu);
+            }
+
+            @Override
+            public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+                return false;
+            }
+
+            @Override
+            public void onDestroyActionMode(ActionMode mode) {
+                mActionMode = null;
+            }
+
+            @Override
+            public void onGetContentRect(ActionMode mode, View view, Rect outRect) {
+                super.onGetContentRect(mode, view, outRect);
+                outRect.top += getTotalPaddingTop();
+                outRect.right -= getTotalPaddingRight();
+                outRect.bottom -= getTotalPaddingBottom();
+                // Encourage menu positioning towards the right, possibly over formula.
+                outRect.left = outRect.right;
+            }
+        };
+        setOnLongClickListener(new View.OnLongClickListener() {
+            @Override
+            public boolean onLongClick(View v) {
+                mActionMode = startActionMode(mPasteActionModeCallback, ActionMode.TYPE_FLOATING);
+                return true;
+            }
+        });
+    }
+
+    /**
+     * Use ContextMenu for paste support on L and lower.
+     */
+    private void setupContextMenu() {
+        setOnCreateContextMenuListener(new OnCreateContextMenuListener() {
+            @Override
+            public void onCreateContextMenu(ContextMenu contextMenu, View view,
+                    ContextMenu.ContextMenuInfo contextMenuInfo) {
+                final MenuInflater inflater = new MenuInflater(getContext());
+                createPasteMenu(inflater, contextMenu);
+                mContextMenu = contextMenu;
+                for(int i = 0; i < contextMenu.size(); i++) {
+                    contextMenu.getItem(i).setOnMenuItemClickListener(CalculatorText.this);
+                }
+            }
+        });
+        setOnLongClickListener(new View.OnLongClickListener() {
+            @Override
+            public boolean onLongClick(View v) {
+                return showContextMenu();
+            }
+        });
+    }
+
+    private boolean createPasteMenu(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;
+        }
+        // Prevents the selection action mode on double tap.
+        return false;
+    }
+
     private void paste() {
         final ClipboardManager clipboard = (ClipboardManager) getContext()
                 .getSystemService(Context.CLIPBOARD_SERVICE);
@@ -280,6 +323,15 @@
         }
     }
 
+    @Override
+    public boolean onMenuItemClick(MenuItem item) {
+        if (item.getItemId() == R.id.menu_paste) {
+            paste();
+            return true;
+        }
+        return false;
+    }
+
     public interface OnTextSizeChangeListener {
         void onTextSizeChanged(TextView textView, float oldSize);
     }
diff --git a/src/com/android/calculator2/Evaluator.java b/src/com/android/calculator2/Evaluator.java
index 936d618..33960ba 100644
--- a/src/com/android/calculator2/Evaluator.java
+++ b/src/com/android/calculator2/Evaluator.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 The Android Open Source Project
+ * 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.
@@ -27,7 +27,7 @@
 import android.support.annotation.VisibleForTesting;
 import android.util.Log;
 
-import com.hp.creals.CR;
+import com.hp.creals.CR;  // For exception classes.
 
 import java.io.DataInput;
 import java.io.DataOutput;
@@ -61,15 +61,16 @@
  * return a result containing placeholder ' ' characters.  If we had to return palceholder
  * characters, we start a background task, which invokes the onReevaluate() callback when it
  * completes.  In either case, the background task computes the appropriate result digits by
- * evaluating the constructive real (CR) returned by CalculatorExpr.eval() to the required
+ * evaluating the UnifiedReal returned by CalculatorExpr.eval() to the required
  * precision.
  *
  * We cache the best decimal approximation we have already computed.  We compute generously to
  * allow for some scrolling without recomputation and to minimize the chance of digits flipping
  * from "0000" to "9999".  The best known result approximation is maintained as a string by
- * mResultString (and in a different format by the CR representation of the result).  When we are
- * in danger of not having digits to display in response to further scrolling, we also initiate a
- * background computation to higher precision, as if we had generated placeholder characters.
+ * mResultString (and often in a different format by the CR representation of the result).  When
+ * we are in danger of not having digits to display in response to further scrolling, we also
+ * initiate a background computation to higher precision, as if we had generated placeholder
+ * characters.
  *
  * The code is designed to ensure that the error in the displayed result (excluding any
  * placeholder characters) is always strictly less than 1 in the last displayed digit.  Typically
@@ -123,6 +124,8 @@
     // The largest number of digits to the right of the decimal point to which we will evaluate to
     // compute proper scientific notation for values close to zero.  Chosen to ensure that we
     // always to better than IEEE double precision at identifying nonzeros.
+    // This used only when we cannot a prior determine the most significant digit position, as
+    // we always can if we have a rational representation.
     private static final int MAX_MSD_PREC_OFFSET = 320;
 
     // If we can replace an exponent by this many leading zeroes, we do so.  Also used in
@@ -145,6 +148,9 @@
     // value.
     private boolean mChangedValue;
 
+    // The main expression contains trig functions.
+    private boolean mHasTrigFuncs;
+
     private SharedPreferences mSharedPrefs;
 
     private boolean mDegreeMode;       // Currently in degree (not radian) mode.
@@ -152,8 +158,7 @@
     private final Handler mTimeoutHandler;  // Used to schedule evaluation timeouts.
 
     // The following are valid only if an evaluation completed successfully.
-        private CR mVal;               // Value of mExpr as constructive real.
-        private BoundedRational mRatVal; // Value of mExpr as rational or null.
+        private UnifiedReal mVal;               // Value of mExpr as UnifiedReal.
 
     // We cache the best known decimal result in mResultString.  Whenever that is
     // non-null, it is computed to exactly mResultStringOffset, which is always > 0.
@@ -197,23 +202,20 @@
      */
     private static class InitialResult {
         public final int errorResourceId;    // Error string or INVALID_RES_ID.
-        public final CR val;                 // Constructive real value.
-        public final BoundedRational ratVal; // Rational value or null.
+        public final UnifiedReal val;        // Constructive real value.
         public final String newResultString;       // Null iff it can't be computed.
         public final int newResultStringOffset;
         public final int initDisplayOffset;
-        InitialResult(CR v, BoundedRational rv, String s, int p, int idp) {
+        InitialResult(UnifiedReal v, String s, int p, int idp) {
             errorResourceId = Calculator.INVALID_RES_ID;
             val = v;
-            ratVal = rv;
             newResultString = s;
             newResultStringOffset = p;
             initDisplayOffset = idp;
         }
         InitialResult(int errorId) {
             errorResourceId = errorId;
-            val = CR.valueOf(0);
-            ratVal = BoundedRational.ZERO;
+            val = UnifiedReal.ZERO;
             newResultString = "BAD";
             newResultStringOffset = 0;
             initDisplayOffset = 0;
@@ -275,6 +277,7 @@
 
     /**
      * Maximum result bit length for unrequested, speculative evaluations.
+     * Also used to bound evaluation precision for small non-zero fractions.
      */
     private final int QUICK_MAX_RESULT_BITS = 50000;
 
@@ -333,44 +336,50 @@
         /**
          * Is a computed result too big for decimal conversion?
          */
-        private boolean isTooBig(CalculatorExpr.EvalResult res) {
+        private boolean isTooBig(UnifiedReal res) {
             int maxBits = mRequired ? getMaxResultBits(mLongTimeout) : QUICK_MAX_RESULT_BITS;
-            if (res.ratVal != null) {
-                return res.ratVal.wholeNumberBits() > maxBits;
-            } else {
-                return res.val.get_appr(maxBits).bitLength() > 2;
-            }
+            return res.approxWholeNumberBitsGreaterThan(maxBits);
         }
         @Override
         protected InitialResult doInBackground(Void... nothing) {
             try {
-                CalculatorExpr.EvalResult res = mExpr.eval(mDm);
+                UnifiedReal res = mExpr.eval(mDm);
                 if (isTooBig(res)) {
                     // Avoid starting a long uninterruptible decimal conversion.
                     return new InitialResult(R.string.timeout);
                 }
                 int precOffset = INIT_PREC;
-                String initResult = res.val.toString(precOffset);
+                String initResult = res.toStringTruncated(precOffset);
                 int msd = getMsdIndexOf(initResult);
-                if (BoundedRational.asBigInteger(res.ratVal) == null
-                        && msd == INVALID_MSD) {
-                    precOffset = MAX_MSD_PREC_OFFSET;
-                    initResult = res.val.toString(precOffset);
-                    msd = getMsdIndexOf(initResult);
+                if (msd == INVALID_MSD) {
+                    int leadingZeroBits = res.leadingBinaryZeroes();
+                    if (leadingZeroBits < QUICK_MAX_RESULT_BITS) {
+                        // Enough initial nonzero digits for most displays.
+                        precOffset = 30 +
+                                (int)Math.ceil(Math.log(2.0d) / Math.log(10.0d) * leadingZeroBits);
+                        initResult = res.toStringTruncated(precOffset);
+                        msd = getMsdIndexOf(initResult);
+                        if (msd == INVALID_MSD) {
+                            throw new AssertionError("Impossible zero result");
+                        }
+                    } else {
+                        // Just try once more at higher fixed precision.
+                        precOffset = MAX_MSD_PREC_OFFSET;
+                        initResult = res.toStringTruncated(precOffset);
+                        msd = getMsdIndexOf(initResult);
+                    }
                 }
-                final int lsdOffset = getLsdOffset(res.ratVal, initResult,
-                        initResult.indexOf('.'));
+                final int lsdOffset = getLsdOffset(res, initResult, initResult.indexOf('.'));
                 final int initDisplayOffset = getPreferredPrec(initResult, msd, lsdOffset);
                 final int newPrecOffset = initDisplayOffset + EXTRA_DIGITS;
                 if (newPrecOffset > precOffset) {
                     precOffset = newPrecOffset;
-                    initResult = res.val.toString(precOffset);
+                    initResult = res.toStringTruncated(precOffset);
                 }
-                return new InitialResult(res.val, res.ratVal,
-                        initResult, precOffset, initDisplayOffset);
+                return new InitialResult(res, initResult, precOffset, initDisplayOffset);
             } catch (CalculatorExpr.SyntaxException e) {
                 return new InitialResult(R.string.error_syntax);
-            } catch (BoundedRational.ZeroDivisionException e) {
+            } catch (UnifiedReal.ZeroDivisionException e) {
                 return new InitialResult(R.string.error_zero_divide);
             } catch(ArithmeticException e) {
                 return new InitialResult(R.string.error_nan);
@@ -397,11 +406,6 @@
                 return;
             }
             mVal = result.val;
-            mRatVal = result.ratVal;
-            // TODO: If the new result ends in lots of zeroes, and we have a rational result which
-            // is greater than (in absolute value) the result string, we should subtract 1 ulp
-            // from the result string.  That will prevent a later change from zeroes to nines.  We
-            // know that the correct, rounded-toward-zero result has nines.
             mResultString = result.newResultString;
             mResultStringOffset = result.newResultStringOffset;
             final int dotIndex = mResultString.indexOf('.');
@@ -412,7 +416,7 @@
             // TODO: Could optimize by remembering display size and checking for change.
             int initPrecOffset = result.initDisplayOffset;
             final int msdIndex = getMsdIndexOf(mResultString);
-            final int leastDigOffset = getLsdOffset(mRatVal, mResultString, dotIndex);
+            final int leastDigOffset = getLsdOffset(mVal, mResultString, dotIndex);
             final int newInitPrecOffset = getPreferredPrec(mResultString, msdIndex, leastDigOffset);
             if (newInitPrecOffset < initPrecOffset) {
                 initPrecOffset = newInitPrecOffset;
@@ -461,10 +465,10 @@
         }
         // Earlier digits could not have changed without a 0 to 9 or 9 to 0 flip at end.
         // The former is OK.
-        if (!newDigs.substring(newLen - precDiff).equals(repeat('0', precDiff))) {
+        if (!newDigs.substring(newLen - precDiff).equals(StringUtils.repeat('0', precDiff))) {
             throw new AssertionError("New approximation invalidates old one!");
         }
-        return oldDigs + repeat('9', precDiff);
+        return oldDigs + StringUtils.repeat('9', precDiff);
     }
 
     /**
@@ -489,7 +493,7 @@
         protected ReevalResult doInBackground(Integer... prec) {
             try {
                 final int precOffset = prec[0].intValue();
-                return new ReevalResult(mVal.toString(precOffset), precOffset);
+                return new ReevalResult(mVal.toStringTruncated(precOffset), precOffset);
             } catch(ArithmeticException e) {
                 return null;
             } catch(CR.PrecisionOverflowException e) {
@@ -544,16 +548,16 @@
 
     /**
      * Return the rightmost nonzero digit position, if any.
-     * @param ratVal Rational value of result or null.
+     * @param val UnifiedReal value of result.
      * @param cache Current cached decimal string representation of result.
      * @param decIndex Index of decimal point in cache.
      * @result Position of rightmost nonzero digit relative to decimal point.
-     *         Integer.MIN_VALUE if ratVal is zero.  Integer.MAX_VALUE if there is no lsd,
+     *         Integer.MIN_VALUE if we cannot determine.  Integer.MAX_VALUE if there is no lsd,
      *         or we cannot determine it.
      */
-    int getLsdOffset(BoundedRational ratVal, String cache, int decIndex) {
-        if (ratVal != null && ratVal.signum() == 0) return Integer.MIN_VALUE;
-        int result = BoundedRational.digitsRequired(ratVal);
+    int getLsdOffset(UnifiedReal val, String cache, int decIndex) {
+        if (val.definitelyZero()) return Integer.MIN_VALUE;
+        int result = val.digitsRequired();
         if (result == 0) {
             int i;
             for (i = -1; decIndex + i > 0 && cache.charAt(decIndex + i) == '0'; --i) { }
@@ -578,19 +582,25 @@
     private int getPreferredPrec(String cache, int msd, int lastDigitOffset) {
         final int lineLength = mResult.getMaxChars();
         final int wholeSize = cache.indexOf('.');
+        final float rawSepChars = mResult.separatorChars(cache, wholeSize);
+        final float rawSepCharsNoDecimal = rawSepChars - mResult.getNoEllipsisCredit();
+        final float rawSepCharsWithDecimal = rawSepCharsNoDecimal - mResult.getDecimalCredit();
+        final int sepCharsNoDecimal = (int) Math.ceil(Math.max(rawSepCharsNoDecimal, 0.0f));
+        final int sepCharsWithDecimal = (int) Math.ceil(Math.max(rawSepCharsWithDecimal, 0.0f));
         final int negative = cache.charAt(0) == '-' ? 1 : 0;
         // Don't display decimal point if result is an integer.
         if (lastDigitOffset == 0) {
             lastDigitOffset = -1;
         }
         if (lastDigitOffset != Integer.MAX_VALUE) {
-            if (wholeSize <= lineLength && lastDigitOffset <= 0) {
+            if (wholeSize <= lineLength - sepCharsNoDecimal && lastDigitOffset <= 0) {
                 // Exact integer.  Prefer to display as integer, without decimal point.
                 return -1;
             }
             if (lastDigitOffset >= 0
-                    && wholeSize + lastDigitOffset + 1 /* decimal pt. */ <= lineLength) {
-                // Display full exact number wo scientific notation.
+                    && wholeSize + lastDigitOffset + 1 /* decimal pt. */
+                    <= lineLength - sepCharsWithDecimal) {
+                // Display full exact number without scientific notation.
                 return lastDigitOffset;
             }
         }
@@ -598,17 +608,27 @@
             // Display number without scientific notation.  Treat leading zero as msd.
             msd = wholeSize - 1;
         }
-        if (msd > wholeSize + MAX_MSD_PREC_OFFSET) {
-            // Display a probable but uncertain 0 as "0.000000000",
-            // without exponent.  That's a judgment call, but less likely
-            // to confuse naive users.  A more informative and confusing
-            // option would be to use a large negative exponent.
+        if (msd > QUICK_MAX_RESULT_BITS) {
+            // Display a probable but uncertain 0 as "0.000000000", without exponent.  That's a
+            // judgment call, but less likely to confuse naive users.  A more informative and
+            // confusing option would be to use a large negative exponent.
+            // Treat extremely large msd values as unknown to avoid slow computations.
             return lineLength - 2;
         }
-        // Return position corresponding to having msd at left, effectively
-        // presuming scientific notation that preserves the left part of the
-        // result.
-        return msd - wholeSize + lineLength - negative - 1;
+        // Return position corresponding to having msd at left, effectively presuming scientific
+        // notation that preserves the left part of the result.
+        // After adjustment for the space required by an exponent, evaluating to the resulting
+        // precision should not overflow the display.
+        int result = msd - wholeSize + lineLength - negative - 1;
+        if (wholeSize <= lineLength - sepCharsNoDecimal) {
+            // Fits without scientific notation; will need space for separators.
+            if (wholeSize < lineLength - sepCharsWithDecimal) {
+                result -= sepCharsWithDecimal;
+            } else {
+                result -= sepCharsNoDecimal;
+            }
+        }
+        return result;
     }
 
     private static final int SHORT_TARGET_LENGTH  = 8;
@@ -657,7 +677,7 @@
         }
         if (msdIndex > dotIndex) {
             if (msdIndex <= dotIndex + EXP_COST + 1) {
-                // Preferred display format inthis cases is with leading zeroes, even if
+                // Preferred display format in this case is with leading zeroes, even if
                 // it doesn't fit entirely.  Replicate that here.
                 msdIndex = dotIndex - 1;
             } else if (lsdOffset <= SHORT_TARGET_LENGTH - negative - 2
@@ -677,7 +697,8 @@
             final int totalDigits = lsdIndex - msdIndex + negative + 1;
             if (totalDigits <= SHORT_TARGET_LENGTH && dotIndex > msdIndex && lsdOffset >= -1) {
                 // Fits, no exponent needed.
-                return negativeSign + cache.substring(msdIndex, lsdIndex + 1);
+                final String wholeWithCommas = StringUtils.addCommas(cache, msdIndex, dotIndex);
+                return negativeSign + wholeWithCommas + cache.substring(dotIndex, lsdIndex + 1);
             }
             if (totalDigits <= SHORT_TARGET_LENGTH - 3) {
                 return negativeSign + cache.charAt(msdIndex) + "."
@@ -686,8 +707,10 @@
         }
         // We need to abbreviate.
         if (dotIndex > msdIndex && dotIndex < msdIndex + SHORT_TARGET_LENGTH - negative - 1) {
-            return negativeSign + cache.substring(msdIndex,
-                    msdIndex + SHORT_TARGET_LENGTH - negative - 1) + KeyMaps.ELLIPSIS;
+            final String wholeWithCommas = StringUtils.addCommas(cache, msdIndex, dotIndex);
+            return negativeSign + wholeWithCommas
+                    + cache.substring(dotIndex, msdIndex + SHORT_TARGET_LENGTH - negative - 1)
+                    + KeyMaps.ELLIPSIS;
         }
         // Need abbreviation + exponent
         return negativeSign + cache.charAt(msdIndex) + "."
@@ -731,7 +754,7 @@
             }
             return mMsdIndex;
         }
-        if (mRatVal != null && mRatVal.signum() == 0) {
+        if (mVal.definitelyZero()) {
             return INVALID_MSD;  // None exists
         }
         int result = INVALID_MSD;
@@ -741,17 +764,6 @@
         return result;
     }
 
-    /**
-     * Return a string with n copies of c.
-     */
-    private static String repeat(char c, int n) {
-        StringBuilder result = new StringBuilder();
-        for (int i = 0; i < n; ++i) {
-            result.append(c);
-        }
-        return result.toString();
-    }
-
     // Refuse to scroll past the point at which this many digits from the whole number
     // part of the result are still displayed.  Avoids sily displays like 1E1.
     private static final int MIN_DISPLAYED_DIGS = 5;
@@ -768,8 +780,8 @@
      * precOffset[0] = -1 means we drop the decimal point and start at the ones position.  Should
      * not be invoked before the onEvaluate() callback is received.  This essentially just returns
      * a substring of the full result; a leading minus sign or leading digits can be dropped.
-     * Result uses US conventions; is NOT internationalized.  Use getRational() to determine
-     * whether the result is exact, or whether we dropped trailing digits.
+     * Result uses US conventions; is NOT internationalized.  Use getResult() and UnifiedReal
+     * operations to determine whether the result is exact, or whether we dropped trailing digits.
      *
      * @param precOffset Zeroth element indicates desired and actual precision
      * @param maxPrecOffset Maximum adjusted precOffset[0]
@@ -819,7 +831,7 @@
         truncated[0] = (startIndex > getMsdIndex());
         String result = mResultString.substring(startIndex, endIndex);
         if (deficit > 0) {
-            result += repeat(' ', deficit);
+            result += StringUtils.repeat(' ', deficit);
             // Blank character is replaced during translation.
             // Since we always compute past the decimal point, this never fills in the spot
             // where the decimal point should go, and we can otherwise treat placeholders
@@ -833,8 +845,8 @@
      * Return null if the result is irrational, or we couldn't track the rational value,
      * e.g. because the denominator got too big.
      */
-    public BoundedRational getRational() {
-        return mRatVal;
+    public UnifiedReal getResult() {
+        return mVal;
     }
 
     private void clearCache() {
@@ -846,6 +858,7 @@
 
     private void clearPreservingTimeout() {
         mExpr.clear();
+        mHasTrigFuncs = false;
         clearCache();
     }
 
@@ -895,7 +908,7 @@
             // Notify immediately, reusing existing result.
             final int dotIndex = mResultString.indexOf('.');
             final String truncatedWholePart = mResultString.substring(0, dotIndex);
-            final int leastDigOffset = getLsdOffset(mRatVal, mResultString, dotIndex);
+            final int leastDigOffset = getLsdOffset(mVal, mResultString, dotIndex);
             final int msdIndex = getMsdIndex();
             final int preferredPrecOffset = getPreferredPrec(mResultString, msdIndex,
                     leastDigOffset);
@@ -905,6 +918,13 @@
     }
 
     /**
+     * Is a reevaluation still in progress?
+     */
+    public boolean reevaluationInProgress() {
+        return mCurrentReevaluator != null;
+    }
+
+    /**
      * Cancel all current background tasks.
      * @param quiet suppress cancellation message
      * @return      true if we cancelled an initial evaluation
@@ -949,6 +969,7 @@
             mExpr = new CalculatorExpr(in);
             mSavedName = in.readUTF();
             mSaved = new CalculatorExpr(in);
+            mHasTrigFuncs = mExpr.hasTrigFuncs();
         } catch (IOException e) {
             Log.v("Calculator", "Exception while restoring:\n" + e);
         }
@@ -984,7 +1005,14 @@
             return true;
         } else {
             mChangedValue = mChangedValue || !KeyMaps.isBinary(id);
-            return mExpr.add(id);
+            if (mExpr.add(id)) {
+                if (!mHasTrigFuncs) {
+                    mHasTrigFuncs = KeyMaps.isTrigFunc(id);
+                }
+                return true;
+            } else {
+                return false;
+            }
         }
     }
 
@@ -994,6 +1022,7 @@
         if (mExpr.isEmpty()) {
             mLongTimeout = false;
         }
+        mHasTrigFuncs = mExpr.hasTrigFuncs();
     }
 
     void setDegreeMode(boolean degreeMode) {
@@ -1014,8 +1043,8 @@
      */
     private CalculatorExpr getResultExpr() {
         final int dotIndex = mResultString.indexOf('.');
-        final int leastDigOffset = getLsdOffset(mRatVal, mResultString, dotIndex);
-        return mExpr.abbreviate(mVal, mRatVal, mDegreeMode,
+        final int leastDigOffset = getLsdOffset(mVal, mResultString, dotIndex);
+        return mExpr.abbreviate(mVal, mDegreeMode,
                 getShortString(mResultString, getMsdIndexOf(mResultString), leastDigOffset));
     }
 
@@ -1031,6 +1060,14 @@
         clearPreservingTimeout();
         mExpr.append(abbrvExpr);
         mChangedValue = true;
+        mHasTrigFuncs = false;  // Degree mode no longer affects expression value.
+    }
+
+    /**
+     * Mark the expression as changed, preventing next evaluation request from being ignored.
+     */
+    public void touch() {
+        mChangedValue = true;
     }
 
     /**
@@ -1109,6 +1146,14 @@
     }
 
     /**
+     * Does the current main expression contain trig functions?
+     * Might its value depend on DEG/RAD mode?
+     */
+    public boolean hasTrigFuncs() {
+        return mHasTrigFuncs;
+    }
+
+    /**
      * Maximum number of characters in a scientific notation exponent.
      */
     private static final int MAX_EXP_CHARS = 8;
diff --git a/src/com/android/calculator2/KeyMaps.java b/src/com/android/calculator2/KeyMaps.java
index f99850b..e82f35d 100644
--- a/src/com/android/calculator2/KeyMaps.java
+++ b/src/com/android/calculator2/KeyMaps.java
@@ -183,10 +183,10 @@
     }
 
     /**
-     * Does a button id correspond to a function that introduces an implicit lparen?
+     * Does a button id correspond to a trig function?
      * Pure function.
      */
-    public static boolean isFunc(int id) {
+    public static boolean isTrigFunc(int id) {
         switch(id) {
             case R.id.fun_sin:
             case R.id.fun_cos:
@@ -194,6 +194,21 @@
             case R.id.fun_arcsin:
             case R.id.fun_arccos:
             case R.id.fun_arctan:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * Does a button id correspond to a function that introduces an implicit lparen?
+     * Pure function.
+     */
+    public static boolean isFunc(int id) {
+        if (isTrigFunc(id)) {
+            return true;
+        }
+        switch(id) {
             case R.id.fun_ln:
             case R.id.fun_log:
             case R.id.fun_exp:
@@ -463,10 +478,17 @@
             sOutputForResultChar.put('E', "E");
             sOutputForResultChar.put(' ', String.valueOf(CHAR_DIGIT_UNKNOWN));
             sOutputForResultChar.put(ELLIPSIS.charAt(0), ELLIPSIS);
+            // Translate numbers for fraction display, but not the separating slash, which appears
+            // to be universal.  We also do not translate the ln, sqrt, pi
             sOutputForResultChar.put('/', "/");
-                        // Translate numbers for fraction display, but not
-                        // the separating slash, which appears to be
-                        // universal.
+            sOutputForResultChar.put('(', "(");
+            sOutputForResultChar.put(')', ")");
+            sOutputForResultChar.put('l', "l");
+            sOutputForResultChar.put('n', "n");
+            sOutputForResultChar.put(',',
+                    String.valueOf(DecimalFormatSymbols.getInstance().getGroupingSeparator()));
+            sOutputForResultChar.put('\u221A', "\u221A"); // SQUARE ROOT
+            sOutputForResultChar.put('\u03C0', "\u03C0"); // GREEK SMALL LETTER PI
             addButtonToOutputMap('-', R.id.op_sub);
             addButtonToOutputMap('.', R.id.dec_point);
             for (int i = 0; i <= 9; ++i) {
@@ -500,6 +522,7 @@
     /**
      * Return the localization of the string s representing a numeric answer.
      * Callable only from UI thread.
+     * A trailing e is treated as the mathematical constant, not an exponent.
      */
     public static String translateResult(String s) {
         StringBuilder result = new StringBuilder();
@@ -507,13 +530,15 @@
         validateMaps();
         for (int i = 0; i < len; ++i) {
             char c = s.charAt(i);
-            String translation = sOutputForResultChar.get(c);
-            if (translation == null) {
-                // Should not get here.  Report if we do.
-                Log.v("Calculator", "Bad character:" + c);
-                result.append(String.valueOf(c));
-            } else {
-                result.append(translation);
+            if (i < len - 1 || c != 'e') {
+                String translation = sOutputForResultChar.get(c);
+                if (translation == null) {
+                    // Should not get here.  Report if we do.
+                    Log.v("Calculator", "Bad character:" + c);
+                    result.append(String.valueOf(c));
+                } else {
+                    result.append(translation);
+                }
             }
         }
         return result.toString();
diff --git a/src/com/android/calculator2/StringUtils.java b/src/com/android/calculator2/StringUtils.java
new file mode 100644
index 0000000..4eef0e7
--- /dev/null
+++ b/src/com/android/calculator2/StringUtils.java
@@ -0,0 +1,94 @@
+/*
+ * 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;
+
+/**
+ * Some helpful methods operating on strings.
+ */
+
+public class StringUtils {
+
+    /**
+     * Return a string with n copies of c.
+     */
+    public static String repeat(char c, int n) {
+        final StringBuilder result = new StringBuilder();
+        for (int i = 0; i < n; ++i) {
+            result.append(c);
+        }
+        return result.toString();
+    }
+
+    /**
+     * Return a copy of the supplied string with commas added every three digits.
+     * The substring indicated by the supplied range is assumed to contain only
+     * a whole number, with no decimal point.
+     * Inserting a digit separator every 3 digits appears to be
+     * at least somewhat acceptable, though not necessarily preferred, everywhere.
+     * The grouping separator in the result is NOT localized.
+     */
+    public static String addCommas(String s, int begin, int end) {
+        // Resist the temptation to use Java's NumberFormat, which converts to long or double
+        // and hence doesn't handle very large numbers.
+        StringBuilder result = new StringBuilder();
+        int current = begin;
+        while (current < end && (s.charAt(current) == '-' || s.charAt(current) == ' ')) {
+            ++current;
+        }
+        result.append(s, begin, current);
+        while (current < end) {
+            result.append(s.charAt(current));
+            ++current;
+            if ((end - current) % 3 == 0 && end != current) {
+                result.append(',');
+            }
+        }
+        return result.toString();
+    }
+
+    /**
+     * Ignoring all occurrences of c in both strings, check whether old is a prefix of new.
+     * If so, return the remaining subsequence of whole. If not, return null.
+     */
+    public static CharSequence getExtensionIgnoring(CharSequence whole, CharSequence prefix,
+            char c) {
+        int wIndex = 0;
+        int pIndex = 0;
+        final int wLen = whole.length();
+        final int pLen = prefix.length();
+        while (true) {
+            while (pIndex < pLen && prefix.charAt(pIndex) == c) {
+                ++pIndex;
+            }
+            while (wIndex < wLen && whole.charAt(wIndex) == c) {
+                ++wIndex;
+            }
+            if (pIndex == pLen) {
+                break;
+            }
+            if (wIndex == wLen || whole.charAt(wIndex) != prefix.charAt(pIndex) ) {
+                return null;
+            }
+            ++pIndex;
+            ++wIndex;
+        }
+        while (wIndex < wLen && whole.charAt(wIndex) == c) {
+            ++wIndex;
+        }
+        return whole.subSequence(wIndex, wLen);
+    }
+}
diff --git a/src/com/android/calculator2/UnifiedReal.java b/src/com/android/calculator2/UnifiedReal.java
new file mode 100644
index 0000000..d3bc947
--- /dev/null
+++ b/src/com/android/calculator2/UnifiedReal.java
@@ -0,0 +1,1158 @@
+/*
+ * 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;
+
+import java.math.BigInteger;
+import com.hp.creals.CR;
+import com.hp.creals.UnaryCRFunction;
+
+/**
+ * Computable real numbers, represented so that we can get exact decidable comparisons
+ * for a number of interesting special cases, including rational computations.
+ *
+ * A real number is represented as the product of two numbers with different representations:
+ * A) A BoundedRational that can only represent a subset of the rationals, but supports
+ *    exact computable comparisons.
+ * B) A lazily evaluated "constructive real number" that provides operations to evaluate
+ *    itself to any requested number of digits.
+ * Whenever possible, we choose (B) to be one of a small set of known constants about which we
+ * know more.  For example, whenever we can, we represent rationals such that (B) is 1.
+ * This scheme allows us to do some very limited symbolic computation on numbers when both
+ * have the same (B) value, as well as in some other situations.  We try to maximize that
+ * possibility.
+ *
+ * Arithmetic operations and operations that produce finite approximations may throw unchecked
+ * exceptions produced by the underlying CR and BoundedRational packages, including
+ * CR.PrecisionOverflowException and CR.AbortedException.
+ */
+public class UnifiedReal {
+
+    private final BoundedRational mRatFactor;
+    private final CR mCrFactor;
+    // TODO: It would be helpful to add flags to indicate whether the result is known
+    // irrational, etc.  This sometimes happens even if mCrFactor is not one of the known ones.
+    // And exact comparisons between rationals and known irrationals are decidable.
+
+    /**
+     * Perform some nontrivial consistency checks.
+     * @hide
+     */
+    public static boolean enableChecks = true;
+
+    private static void check(boolean b) {
+        if (!b) {
+            throw new AssertionError();
+        }
+    }
+
+    private UnifiedReal(BoundedRational rat, CR cr) {
+        if (rat == null) {
+            throw new ArithmeticException("Building UnifiedReal from null");
+        }
+        // We don't normally traffic in null CRs, and hence don't test explicitly.
+        mCrFactor = cr;
+        mRatFactor = rat;
+    }
+
+    public UnifiedReal(CR cr) {
+        this(BoundedRational.ONE, cr);
+    }
+
+    public UnifiedReal(BoundedRational rat) {
+        this(rat, CR_ONE);
+    }
+
+    public UnifiedReal(BigInteger n) {
+        this(new BoundedRational(n));
+    }
+
+    public UnifiedReal(long n) {
+        this(new BoundedRational(n));
+    }
+
+    // Various helpful constants
+    private final static BigInteger BIG_24 = BigInteger.valueOf(24);
+    private final static int DEFAULT_COMPARE_TOLERANCE = -1000;
+
+    // Well-known CR constants we try to use in the mCrFactor position:
+    private final static CR CR_ONE = CR.ONE;
+    private final static CR CR_PI = CR.PI;
+    private final static CR CR_E = CR.ONE.exp();
+    private final static CR CR_SQRT2 = CR.valueOf(2).sqrt();
+    private final static CR CR_SQRT3 = CR.valueOf(3).sqrt();
+    private final static CR CR_LN2 = CR.valueOf(2).ln();
+    private final static CR CR_LN3 = CR.valueOf(3).ln();
+    private final static CR CR_LN5 = CR.valueOf(5).ln();
+    private final static CR CR_LN6 = CR.valueOf(6).ln();
+    private final static CR CR_LN7 = CR.valueOf(7).ln();
+    private final static CR CR_LN10 = CR.valueOf(10).ln();
+
+    // Square roots that we try to recognize.
+    // We currently recognize only a small fixed collection, since the sqrt() function needs to
+    // identify numbers of the form <SQRT[i]>*n^2, and we don't otherwise know of a good
+    // algorithm for that.
+    private final static CR sSqrts[] = {
+            null,
+            CR.ONE,
+            CR_SQRT2,
+            CR_SQRT3,
+            null,
+            CR.valueOf(5).sqrt(),
+            CR.valueOf(6).sqrt(),
+            CR.valueOf(7).sqrt(),
+            null,
+            null,
+            CR.valueOf(10).sqrt() };
+
+    // Natural logs of small integers that we try to recognize.
+    private final static CR sLogs[] = {
+            null,
+            null,
+            CR_LN2,
+            CR_LN3,
+            null,
+            CR_LN5,
+            CR_LN6,
+            CR_LN7,
+            null,
+            null,
+            CR_LN10 };
+
+
+    // Some convenient UnifiedReal constants.
+    public static final UnifiedReal PI = new UnifiedReal(CR_PI);
+    public static final UnifiedReal E = new UnifiedReal(CR_E);
+    public static final UnifiedReal ZERO = new UnifiedReal(BoundedRational.ZERO);
+    public static final UnifiedReal ONE = new UnifiedReal(BoundedRational.ONE);
+    public static final UnifiedReal MINUS_ONE = new UnifiedReal(BoundedRational.MINUS_ONE);
+    public static final UnifiedReal TWO = new UnifiedReal(BoundedRational.TWO);
+    public static final UnifiedReal MINUS_TWO = new UnifiedReal(BoundedRational.MINUS_TWO);
+    public static final UnifiedReal HALF = new UnifiedReal(BoundedRational.HALF);
+    public static final UnifiedReal MINUS_HALF = new UnifiedReal(BoundedRational.MINUS_HALF);
+    public static final UnifiedReal TEN = new UnifiedReal(BoundedRational.TEN);
+    public static final UnifiedReal RADIANS_PER_DEGREE
+            = new UnifiedReal(new BoundedRational(1, 180), CR_PI);
+    private static final UnifiedReal SIX = new UnifiedReal(6);
+    private static final UnifiedReal HALF_SQRT2 = new UnifiedReal(BoundedRational.HALF, CR_SQRT2);
+    private static final UnifiedReal SQRT3 = new UnifiedReal(CR_SQRT3);
+    private static final UnifiedReal HALF_SQRT3 = new UnifiedReal(BoundedRational.HALF, CR_SQRT3);
+    private static final UnifiedReal THIRD_SQRT3 = new UnifiedReal(BoundedRational.THIRD, CR_SQRT3);
+    private static final UnifiedReal PI_OVER_2 = new UnifiedReal(BoundedRational.HALF, CR_PI);
+    private static final UnifiedReal PI_OVER_3 = new UnifiedReal(BoundedRational.THIRD, CR_PI);
+    private static final UnifiedReal PI_OVER_4 = new UnifiedReal(BoundedRational.QUARTER, CR_PI);
+    private static final UnifiedReal PI_OVER_6 = new UnifiedReal(BoundedRational.SIXTH, CR_PI);
+
+
+    /**
+     * Given a constructive real cr, try to determine whether cr is the square root of
+     * a small integer.  If so, return its square as a BoundedRational.  Otherwise return null.
+     * We make this determination by simple table lookup, so spurious null returns are
+     * entirely possible, or even likely.
+     */
+    private static BoundedRational getSquare(CR cr) {
+        for (int i = 0; i < sSqrts.length; ++i) {
+             if (sSqrts[i] == cr) {
+                return new BoundedRational(i);
+             }
+        }
+        return null;
+    }
+
+    /**
+     * Given a constructive real cr, try to determine whether cr is the square root of
+     * a small integer.  If so, return its square as a BoundedRational.  Otherwise return null.
+     * We make this determination by simple table lookup, so spurious null returns are
+     * entirely possible, or even likely.
+     */
+    private BoundedRational getExp(CR cr) {
+        for (int i = 0; i < sLogs.length; ++i) {
+             if (sLogs[i] == cr) {
+                return new BoundedRational(i);
+             }
+        }
+        return null;
+    }
+
+    /**
+     * If the argument is a well-known constructive real, return its name.
+     * The name of "CR_ONE" is the empty string.
+     * No named constructive reals are rational multiples of each other.
+     * Thus two UnifiedReals with different named mCrFactors can be equal only if both
+     * mRatFactors are zero or possibly if one is CR_PI and the other is CR_E.
+     * (The latter is apparently an open problem.)
+     */
+    private static String crName(CR cr) {
+        if (cr == CR_ONE) {
+            return "";
+        }
+        if (cr == CR_PI) {
+            return "\u03C0";   // GREEK SMALL LETTER PI
+        }
+        if (cr == CR_E) {
+            return "e";
+        }
+        for (int i = 0; i < sSqrts.length; ++i) {
+            if (cr == sSqrts[i]) {
+                return "\u221A" /* SQUARE ROOT */ + i;
+            }
+        }
+        for (int i = 0; i < sLogs.length; ++i) {
+            if (cr == sLogs[i]) {
+                return "ln(" + i + ")";
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Would crName() return non-Null?
+     */
+    private static boolean isNamed(CR cr) {
+        if (cr == CR_ONE || cr == CR_PI || cr == CR_E) {
+            return true;
+        }
+        for (CR r: sSqrts) {
+            if (cr == r) {
+                return true;
+            }
+        }
+        for (CR r: sLogs) {
+            if (cr == r) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Is cr known to be algebraic (as opposed to transcendental)?
+     * Currently only produces meaningful results for the above known special
+     * constructive reals.
+     */
+    private static boolean definitelyAlgebraic(CR cr) {
+        return cr == CR_ONE || getSquare(cr) != null;
+    }
+
+    /**
+     * Is this number known to be rational?
+     */
+    public boolean definitelyRational() {
+        return mCrFactor == CR_ONE || mRatFactor.signum() == 0;
+    }
+
+    /**
+     * Is this number known to be irrational?
+     * TODO: We could track the fact that something is irrational with an explicit flag, which
+     * could cover many more cases.  Whether that matters in practice is TBD.
+     */
+    public boolean definitelyIrrational() {
+        return !definitelyRational() && isNamed(mCrFactor);
+    }
+
+    /**
+     * Is this number known to be algebraic?
+     */
+    public boolean definitelyAlgebraic() {
+        return definitelyAlgebraic(mCrFactor) || mRatFactor.signum() == 0;
+    }
+
+    /**
+     * Is this number known to be transcendental?
+     */
+    public boolean definitelyTranscendental() {
+        return !definitelyAlgebraic() && isNamed(mCrFactor);
+    }
+
+
+    /**
+     * Is it known that the two constructive reals differ by something other than a
+     * a rational factor, i.e. is it known that two UnifiedReals
+     * with those mCrFactors will compare unequal unless both mRatFactors are zero?
+     * If this returns true, then a comparison of two UnifiedReals using those two
+     * mCrFactors cannot diverge, though we don't know of a good runtime bound.
+     */
+    private static boolean definitelyIndependent(CR r1, CR r2) {
+        // The question here is whether r1 = x*r2, where x is rational, where r1 and r2
+        // are in our set of special known CRs, can have a solution.
+        // This cannot happen if one is CR_ONE and the other is not.
+        // (Since all others are irrational.)
+        // This cannot happen for two named square roots, which have no repeated factors.
+        // (To see this, square both sides of the equation and factor.  Each prime
+        // factor in the numerator and denominator occurs twice.)
+        // This cannot happen for e or pi on one side, and a square root on the other.
+        // (One is transcendental, the other is algebraic.)
+        // This cannot happen for two of our special natural logs.
+        // (Otherwise ln(m) = (a/b)ln(n) ==> m = n^(a/b) ==> m^b = n^a, which is impossible
+        // because either m or n includes a prime factor not shared by the other.)
+        // This cannot happen for a log and a square root.
+        // (The Lindemann-Weierstrass theorem tells us, among other things, that if
+        // a is algebraic, then exp(a) is transcendental.  Thus if l in our finite
+        // set of logs where algebraic, expl(l), must be transacendental.
+        // But exp(l) is an integer.  Thus the logs are transcendental.  But of course the
+        // square roots are algebraic.  Thus they can't be rational multiples.)
+        // Unfortunately, we do not know whether e/pi is rational.
+        if (r1 == r2) {
+            return false;
+        }
+        CR other;
+        if (r1 == CR_E || r1 == CR_PI) {
+            return definitelyAlgebraic(r2);
+        }
+        if (r2 == CR_E || r2 == CR_PI) {
+            return definitelyAlgebraic(r1);
+        }
+        return isNamed(r1) && isNamed(r2);
+    }
+
+    /**
+     * Convert to String reflecting raw representation.
+     * Debug or log messages only, not pretty.
+     */
+    public String toString() {
+        return mRatFactor.toString() + "*" + mCrFactor.toString();
+    }
+
+    /**
+     * Convert to readable String.
+     * Intended for user output.  Produces exact expression when possible.
+     */
+    public String toNiceString() {
+        if (mCrFactor == CR_ONE || mRatFactor.signum() == 0) {
+            return mRatFactor.toNiceString();
+        }
+        String name = crName(mCrFactor);
+        if (name != null) {
+            BigInteger bi = BoundedRational.asBigInteger(mRatFactor);
+            if (bi != null) {
+                if (bi.equals(BigInteger.ONE)) {
+                    return name;
+                }
+                return mRatFactor.toNiceString() + name;
+            }
+            return "(" + mRatFactor.toNiceString() + ")" + name;
+        }
+        if (mRatFactor.equals(BoundedRational.ONE)) {
+            return mCrFactor.toString();
+        }
+        return crValue().toString();
+    }
+
+    /**
+     * Will toNiceString() produce an exact representation?
+     */
+    public boolean exactlyDisplayable() {
+        return crName(mCrFactor) != null;
+    }
+
+    // Number of extra bits used in evaluation below to prefer truncation to rounding.
+    // Must be <= 30.
+    private final static int EXTRA_PREC = 10;
+
+    /*
+     * Returns a truncated representation of the result.
+     * If exactlyTruncatable(), we round correctly towards zero. Otherwise the resulting digit
+     * string may occasionally be rounded up instead.
+     * The result includes n digits to the right of the decimal point.
+     * @param n result precision, >= 0
+     */
+    public String toStringTruncated(int n) {
+        if (mCrFactor == CR_ONE || mRatFactor == BoundedRational.ZERO) {
+            return mRatFactor.toStringTruncated(n);
+        }
+        final CR scaled = CR.valueOf(BigInteger.TEN.pow(n)).multiply(crValue());
+        boolean negative = false;
+        BigInteger intScaled;
+        if (exactlyTruncatable()) {
+            intScaled = scaled.get_appr(0);
+            if (intScaled.signum() < 0) {
+                negative = true;
+                intScaled = intScaled.negate();
+            }
+            if (CR.valueOf(intScaled).compareTo(scaled.abs()) > 0) {
+                intScaled = intScaled.subtract(BigInteger.ONE);
+            }
+            check(CR.valueOf(intScaled).compareTo(scaled.abs()) < 0);
+        } else {
+            // Approximate case.  Exact comparisons are impossible.
+            intScaled = scaled.get_appr(-EXTRA_PREC);
+            if (intScaled.signum() < 0) {
+                negative = true;
+                intScaled = intScaled.negate();
+            }
+            intScaled = intScaled.shiftRight(EXTRA_PREC);
+        }
+        String digits = intScaled.toString();
+        int len = digits.length();
+        if (len < n + 1) {
+            digits = StringUtils.repeat('0', n + 1 - len) + digits;
+            len = n + 1;
+        }
+        return (negative ? "-" : "") + digits.substring(0, len - n) + "."
+                + digits.substring(len - n);
+    }
+
+    /*
+     * Can we compute correctly truncated approximations of this number?
+     */
+    public boolean exactlyTruncatable() {
+        // If the value is known rational, we can do exact comparisons.
+        // If the value is known irrational, then we can safely compare to rational approximations;
+        // equality is impossible; hence the comparison must converge.
+        // The only problem cases are the ones in which we don't know.
+        return mCrFactor == CR_ONE || mRatFactor == BoundedRational.ZERO || definitelyIrrational();
+    }
+
+    /**
+     * Return a double approximation.
+     * TODO: Result is correctly rounded if known to be rational.
+     */
+    public double doubleValue() {
+        if (mCrFactor == CR_ONE) {
+            return mRatFactor.doubleValue(); // Hopefully correctly rounded
+        } else {
+            return crValue().doubleValue(); // Approximately correctly rounded
+        }
+    }
+
+    public CR crValue() {
+        return mRatFactor.crValue().multiply(mCrFactor);
+    }
+
+    /**
+     * Are this and r exactly comparable?
+     */
+    public boolean isComparable(UnifiedReal u) {
+        // We check for ONE only to speed up the common case.
+        // The use of a tolerance here means we can spuriously return false, not true.
+        return mCrFactor == u.mCrFactor
+                && (isNamed(mCrFactor) || mCrFactor.signum(DEFAULT_COMPARE_TOLERANCE) != 0)
+                || mRatFactor.signum() == 0 && u.mRatFactor.signum() == 0
+                || definitelyIndependent(mCrFactor, u.mCrFactor)
+                || crValue().compareTo(u.crValue(), DEFAULT_COMPARE_TOLERANCE) != 0;
+    }
+
+    /**
+     * Return +1 if this is greater than r, -1 if this is less than r, or 0 of the two are
+     * known to be equal.
+     * May diverge if the two are equal and !isComparable(r).
+     */
+    public int compareTo(UnifiedReal u) {
+        if (definitelyZero() && u.definitelyZero()) return 0;
+        if (mCrFactor == u.mCrFactor) {
+            int signum = mCrFactor.signum();  // Can diverge if mCRFactor == 0.
+            return signum * mRatFactor.compareTo(u.mRatFactor);
+        }
+        return crValue().compareTo(u.crValue());  // Can also diverge.
+    }
+
+    /**
+     * Return +1 if this is greater than r, -1 if this is less than r, or possibly 0 of the two are
+     * within 2^a of each other.
+     */
+    public int compareTo(UnifiedReal u, int a) {
+        if (isComparable(u)) {
+            return compareTo(u);
+        } else {
+            return crValue().compareTo(u.crValue(), a);
+        }
+    }
+
+    /**
+     * Return compareTo(ZERO, a).
+     */
+    public int signum(int a) {
+        return compareTo(ZERO, a);
+    }
+
+    /**
+     * Return compareTo(ZERO).
+     * May diverge for ZERO argument if !isComparable(ZERO).
+     */
+    public int signum() {
+        return compareTo(ZERO);
+    }
+
+    /**
+     * Equality comparison.  May erroneously return true if values differ by less than 2^a,
+     * and !isComparable(u).
+     */
+    public boolean approxEquals(UnifiedReal u, int a) {
+        if (isComparable(u)) {
+            if (definitelyIndependent(mCrFactor, u.mCrFactor)
+                    && (mRatFactor.signum() != 0 || u.mRatFactor.signum() != 0)) {
+                // No need to actually evaluate, though we don't know which is larger.
+                return false;
+            } else {
+                return compareTo(u) == 0;
+            }
+        }
+        return crValue().compareTo(u.crValue(), a) == 0;
+    }
+
+    /**
+     * Returns true if values are definitely known to be equal, false in all other cases.
+     */
+    public boolean definitelyEquals(UnifiedReal u) {
+        return isComparable(u) && compareTo(u) == 0;
+    }
+
+    /**
+     * Returns true if values are definitely known not to be equal, false in all other cases.
+     */
+    public boolean definitelyNotEquals(UnifiedReal u) {
+        boolean isNamed = isNamed(mCrFactor);
+        boolean uIsNamed = isNamed(u.mCrFactor);
+        if (isNamed && uIsNamed) {
+            if (definitelyIndependent(mCrFactor, u.mCrFactor)) {
+                return mRatFactor.signum() != 0 || u.mRatFactor.signum() != 0;
+            } else if (mCrFactor == u.mCrFactor) {
+                return !mRatFactor.equals(u.mRatFactor);
+            }
+            return !mRatFactor.equals(u.mRatFactor);
+        }
+        if (mRatFactor.signum() == 0) {
+            return uIsNamed && u.mRatFactor.signum() != 0;
+        }
+        if (u.mRatFactor.signum() == 0) {
+            return isNamed && mRatFactor.signum() != 0;
+        }
+        return false;
+    }
+
+    // And some slightly faster convenience functions for special cases:
+
+    public boolean definitelyZero() {
+        return mRatFactor.signum() == 0;
+    }
+
+    public boolean definitelyNonZero() {
+        return isNamed(mCrFactor) && mRatFactor.signum() != 0;
+    }
+
+    public boolean definitelyOne() {
+        return mCrFactor == CR_ONE && mRatFactor.equals(BoundedRational.ONE);
+    }
+
+    /**
+     * Return equivalent BoundedRational, if known to exist, null otherwise
+     */
+    public BoundedRational boundedRationalValue() {
+        if (mCrFactor == CR_ONE || mRatFactor.signum() == 0) {
+            return mRatFactor;
+        }
+        return null;
+    }
+
+    /**
+     * Returns equivalent BigInteger result if it exists, null if not.
+     */
+    public BigInteger bigIntegerValue() {
+        final BoundedRational r = boundedRationalValue();
+        return BoundedRational.asBigInteger(r);
+    }
+
+    public UnifiedReal add(UnifiedReal u) {
+        if (mCrFactor == u.mCrFactor) {
+            BoundedRational nRatFactor = BoundedRational.add(mRatFactor, u.mRatFactor);
+            if (nRatFactor != null) {
+                return new UnifiedReal(nRatFactor, mCrFactor);
+            }
+        }
+        if (definitelyZero()) {
+            // Avoid creating new mCrFactor, even if they don't currently match.
+            return u;
+        }
+        if (u.definitelyZero()) {
+            return this;
+        }
+        return new UnifiedReal(crValue().add(u.crValue()));
+    }
+
+    public UnifiedReal negate() {
+        return new UnifiedReal(BoundedRational.negate(mRatFactor), mCrFactor);
+    }
+
+    public UnifiedReal subtract(UnifiedReal u) {
+        return add(u.negate());
+    }
+
+    public UnifiedReal multiply(UnifiedReal u) {
+        // Preserve a preexisting mCrFactor when we can.
+        if (mCrFactor == CR_ONE) {
+            BoundedRational nRatFactor = BoundedRational.multiply(mRatFactor, u.mRatFactor);
+            if (nRatFactor != null) {
+                return new UnifiedReal(nRatFactor, u.mCrFactor);
+            }
+        }
+        if (u.mCrFactor == CR_ONE) {
+            BoundedRational nRatFactor = BoundedRational.multiply(mRatFactor, u.mRatFactor);
+            if (nRatFactor != null) {
+                return new UnifiedReal(nRatFactor, mCrFactor);
+            }
+        }
+        if (definitelyZero() || u.definitelyZero()) {
+            return ZERO;
+        }
+        if (mCrFactor == u.mCrFactor) {
+            BoundedRational square = getSquare(mCrFactor);
+            if (square != null) {
+                BoundedRational nRatFactor = BoundedRational.multiply(
+                        BoundedRational.multiply(square, mRatFactor), u.mRatFactor);
+                if (nRatFactor != null) {
+                    return new UnifiedReal(nRatFactor);
+                }
+            }
+        }
+        // Probably a bit cheaper to multiply component-wise.
+        BoundedRational nRatFactor = BoundedRational.multiply(mRatFactor, u.mRatFactor);
+        if (nRatFactor != null) {
+            return new UnifiedReal(nRatFactor, mCrFactor.multiply(u.mCrFactor));
+        }
+        return new UnifiedReal(crValue().multiply(u.crValue()));
+    }
+
+    public static class ZeroDivisionException extends ArithmeticException {
+        public ZeroDivisionException() {
+            super("Division by zero");
+        }
+    }
+
+    /**
+     * Return the reciprocal.
+     */
+    public UnifiedReal inverse() {
+        if (definitelyZero()) {
+            throw new ZeroDivisionException();
+        }
+        BoundedRational square = getSquare(mCrFactor);
+        if (square != null) {
+            // 1/sqrt(n) = sqrt(n)/n
+            BoundedRational nRatFactor = BoundedRational.inverse(
+                    BoundedRational.multiply(mRatFactor, square));
+            if (nRatFactor != null) {
+                return new UnifiedReal(nRatFactor, mCrFactor);
+            }
+        }
+        return new UnifiedReal(BoundedRational.inverse(mRatFactor), mCrFactor.inverse());
+    }
+
+    public UnifiedReal divide(UnifiedReal u) {
+        if (mCrFactor == u.mCrFactor) {
+            if (u.definitelyZero()) {
+                throw new ZeroDivisionException();
+            }
+            BoundedRational nRatFactor = BoundedRational.divide(mRatFactor, u.mRatFactor);
+            if (nRatFactor != null) {
+                return new UnifiedReal(nRatFactor, CR_ONE);
+            }
+        }
+        return multiply(u.inverse());
+    }
+
+    public UnifiedReal sqrt() {
+        if (mCrFactor == CR_ONE) {
+            BoundedRational ratSqrt;
+            // Check for all arguments of the form <perfect rational square> * small_int,
+            // where small_int has a known sqrt.  This includes the small_int = 1 case.
+            for (int divisor = 1; divisor < sSqrts.length; ++divisor) {
+                if (sSqrts[divisor] != null) {
+                    ratSqrt = BoundedRational.sqrt(
+                            BoundedRational.divide(mRatFactor, new BoundedRational(divisor)));
+                    if (ratSqrt != null) {
+                        return new UnifiedReal(ratSqrt, sSqrts[divisor]);
+                    }
+                }
+            }
+        }
+        return new UnifiedReal(crValue().sqrt());
+    }
+
+    /**
+     * Return (this mod 2pi)/(pi/6) as a BigInteger, or null if that isn't easily possible.
+     */
+    private BigInteger getPiTwelfths() {
+        if (definitelyZero()) return BigInteger.ZERO;
+        if (mCrFactor == CR_PI) {
+            BigInteger quotient = BoundedRational.asBigInteger(
+                    BoundedRational.multiply(mRatFactor, BoundedRational.TWELVE));
+            if (quotient == null) {
+                return null;
+            }
+            return quotient.mod(BIG_24);
+        }
+        return null;
+    }
+
+    /**
+     * Computer the sin() for an integer multiple n of pi/12, if easily representable.
+     * @param n value between 0 and 23 inclusive.
+     */
+    private static UnifiedReal sinPiTwelfths(int n) {
+        if (n >= 12) {
+            UnifiedReal negResult = sinPiTwelfths(n - 12);
+            return negResult == null ? null : negResult.negate();
+        }
+        switch (n) {
+        case 0:
+            return ZERO;
+        case 2: // 30 degrees
+            return HALF;
+        case 3: // 45 degrees
+            return HALF_SQRT2;
+        case 4: // 60 degrees
+            return HALF_SQRT3;
+        case 6:
+            return ONE;
+        case 8:
+            return HALF_SQRT3;
+        case 9:
+            return HALF_SQRT2;
+        case 10:
+            return HALF;
+        default:
+            return null;
+        }
+    }
+
+    public UnifiedReal sin() {
+        BigInteger piTwelfths = getPiTwelfths();
+        if (piTwelfths != null) {
+            UnifiedReal result = sinPiTwelfths(piTwelfths.intValue());
+            if (result != null) {
+                return result;
+            }
+        }
+        return new UnifiedReal(crValue().sin());
+    }
+
+    private static UnifiedReal cosPiTwelfths(int n) {
+        int sinArg = n + 6;
+        if (sinArg >= 24) {
+            sinArg -= 24;
+        }
+        return sinPiTwelfths(sinArg);
+    }
+
+    public UnifiedReal cos() {
+        BigInteger piTwelfths = getPiTwelfths();
+        if (piTwelfths != null) {
+            UnifiedReal result = cosPiTwelfths(piTwelfths.intValue());
+            if (result != null) {
+                return result;
+            }
+        }
+        return new UnifiedReal(crValue().cos());
+    }
+
+    public UnifiedReal tan() {
+        BigInteger piTwelfths = getPiTwelfths();
+        if (piTwelfths != null) {
+            int i = piTwelfths.intValue();
+            if (i == 6 || i == 18) {
+                throw new ArithmeticException("Tangent undefined");
+            }
+            UnifiedReal top = sinPiTwelfths(i);
+            UnifiedReal bottom = cosPiTwelfths(i);
+            if (top != null && bottom != null) {
+                return top.divide(bottom);
+            }
+        }
+        return sin().divide(cos());
+    }
+
+    // Throw an exception if the argument is definitely out of bounds for asin or acos.
+    private void checkAsinDomain() {
+        if (isComparable(ONE) && (compareTo(ONE) > 0 || compareTo(MINUS_ONE) < 0)) {
+            throw new ArithmeticException("inverse trig argument out of range");
+        }
+    }
+
+    /**
+     * Return asin(n/2).  n is between -2 and 2.
+     */
+    public static UnifiedReal asinHalves(int n){
+        if (n < 0) {
+            return (asinHalves(-n).negate());
+        }
+        switch (n) {
+        case 0:
+            return ZERO;
+        case 1:
+            return new UnifiedReal(BoundedRational.SIXTH, CR.PI);
+        case 2:
+            return new UnifiedReal(BoundedRational.HALF, CR.PI);
+        }
+        throw new AssertionError("asinHalves: Bad argument");
+    }
+
+    /**
+     * Return asin of this, assuming this is not an integral multiple of a half.
+     */
+    public UnifiedReal asinNonHalves()
+    {
+        if (compareTo(ZERO, -10) < 0) {
+            return negate().asinNonHalves().negate();
+        }
+        if (definitelyEquals(HALF_SQRT2)) {
+            return new UnifiedReal(BoundedRational.QUARTER, CR_PI);
+        }
+        if (definitelyEquals(HALF_SQRT3)) {
+            return new UnifiedReal(BoundedRational.THIRD, CR_PI);
+        }
+        return new UnifiedReal(crValue().asin());
+    }
+
+    public UnifiedReal asin() {
+        checkAsinDomain();
+        final BigInteger halves = multiply(TWO).bigIntegerValue();
+        if (halves != null) {
+            return asinHalves(halves.intValue());
+        }
+        if (mCrFactor == CR.ONE || mCrFactor != CR_SQRT2 ||mCrFactor != CR_SQRT3) {
+            return asinNonHalves();
+        }
+        return new UnifiedReal(crValue().asin());
+    }
+
+    public UnifiedReal acos() {
+        return PI_OVER_2.subtract(asin());
+    }
+
+    public UnifiedReal atan() {
+        if (compareTo(ZERO, -10) < 0) {
+            return negate().atan().negate();
+        }
+        final BigInteger asBI = bigIntegerValue();
+        if (asBI != null && asBI.compareTo(BigInteger.ONE) <= 0) {
+            final int asInt = asBI.intValue();
+            // These seem to be all rational cases:
+            switch (asInt) {
+            case 0:
+                return ZERO;
+            case 1:
+                return PI_OVER_4;
+            default:
+                throw new AssertionError("Impossible r_int");
+            }
+        }
+        if (definitelyEquals(THIRD_SQRT3)) {
+            return PI_OVER_6;
+        }
+        if (definitelyEquals(SQRT3)) {
+            return PI_OVER_3;
+        }
+        return new UnifiedReal(UnaryCRFunction.atanFunction.execute(crValue()));
+    }
+
+    private static final BigInteger BIG_TWO = BigInteger.valueOf(2);
+
+    /**
+     * Compute an integral power of this.
+     */
+    private UnifiedReal pow(BigInteger exp) {
+        if (exp.signum() < 0) {
+            return pow(exp.negate()).inverse();
+        }
+        if (exp.equals(BigInteger.ONE)) {
+            return this;
+        }
+        if (exp.signum() == 0) {
+            // Questionable if base has undefined value.  Java.lang.Math.pow() returns 1 anyway,
+            // so we do the same.
+            return ONE;
+        }
+        if (mCrFactor == CR_ONE) {
+            final BoundedRational ratPow = mRatFactor.pow(exp);
+            if (ratPow != null) {
+                return new UnifiedReal(mRatFactor.pow(exp));
+            }
+        }
+        BoundedRational square = getSquare(mCrFactor);
+        if (square != null) {
+            final BoundedRational nRatFactor =
+                    BoundedRational.multiply(mRatFactor.pow(exp), square.pow(exp.shiftRight(1)));
+            if (nRatFactor != null) {
+                if (exp.and(BigInteger.ONE).intValue() == 1) {
+                    // Odd power: Multiply by remaining square root.
+                    return new UnifiedReal(nRatFactor, mCrFactor);
+                } else {
+                    return new UnifiedReal(nRatFactor);
+                }
+            }
+        }
+        return new UnifiedReal(crValue().ln().multiply(CR.valueOf(exp)).exp());
+    }
+
+    public UnifiedReal pow(UnifiedReal expon) {
+        if (mCrFactor == CR_E) {
+            if (mRatFactor.equals(BoundedRational.ONE)) {
+                return expon.exp();
+            } else {
+                UnifiedReal ratPart = new UnifiedReal(mRatFactor).pow(expon);
+                return expon.exp().multiply(ratPart);
+            }
+        }
+        final BoundedRational expAsBR = expon.boundedRationalValue();
+        if (expAsBR != null) {
+            BigInteger expAsBI = BoundedRational.asBigInteger(expAsBR);
+            if (expAsBI != null) {
+                return pow(expAsBI);
+            } else {
+                // Check for exponent that is a multiple of a half.
+                expAsBI = BoundedRational.asBigInteger(
+                        BoundedRational.multiply(BoundedRational.TWO, expAsBR));
+                if (expAsBI != null) {
+                    return pow(expAsBI).sqrt();
+                }
+            }
+        }
+        return new UnifiedReal(crValue().ln().multiply(expon.crValue()).exp());
+    }
+
+    /**
+     * Raise the argument to the 16th power.
+     */
+    private static long pow16(int n) {
+        if (n > 10) {
+            throw new AssertionError("Unexpexted pow16 argument");
+        }
+        long result = n*n;
+        result *= result;
+        result *= result;
+        result *= result;
+        return result;
+    }
+
+    /**
+     * Return the integral log with respect to the given base if it exists, 0 otherwise.
+     * n is presumed positive.
+     */
+    private static long getIntLog(BigInteger n, int base) {
+        double nAsDouble = n.doubleValue();
+        double approx = Math.log(nAsDouble)/Math.log(base);
+        // A relatively quick test first.
+        // Unfortunately, this doesn't help for values to big to fit in a Double.
+        if (!Double.isInfinite(nAsDouble) && Math.abs(approx - Math.rint(approx)) > 1.0e-6) {
+            return 0;
+        }
+        long result = 0;
+        BigInteger remaining = n;
+        BigInteger bigBase = BigInteger.valueOf(base);
+        BigInteger base16th = null;  // base^16, computed lazily
+        while (n.mod(bigBase).signum() == 0) {
+            if (Thread.interrupted()) {
+                throw new CR.AbortedException();
+            }
+            n = n.divide(bigBase);
+            ++result;
+            // And try a slightly faster computation for large n:
+            if (base16th == null) {
+                base16th = BigInteger.valueOf(pow16(base));
+            }
+            while (n.mod(base16th).signum() == 0) {
+                n = n.divide(base16th);
+                result += 16;
+            }
+        }
+        if (n.equals(BigInteger.ONE)) {
+            return result;
+        }
+        return 0;
+    }
+
+    public UnifiedReal ln() {
+        if (isComparable(ZERO)) {
+            if (signum() <= 0) {
+                throw new ArithmeticException("log(non-positive)");
+            }
+            int compare1 = compareTo(ONE, DEFAULT_COMPARE_TOLERANCE);
+            if (compare1 == 0) {
+                if (definitelyEquals(ONE)) {
+                    return ZERO;
+                }
+            } else if (compare1 < 0) {
+                return inverse().ln().negate();
+            }
+            final BigInteger bi = BoundedRational.asBigInteger(mRatFactor);
+            if (bi != null) {
+                if (mCrFactor == CR_ONE) {
+                    // Check for a power of a small integer.  We can use sLogs[] to return
+                    // a more useful answer for those.
+                    for (int i = 0; i < sLogs.length; ++i) {
+                        if (sLogs[i] != null) {
+                            long intLog = getIntLog(bi, i);
+                            if (intLog != 0) {
+                                return new UnifiedReal(new BoundedRational(intLog), sLogs[i]);
+                            }
+                        }
+                    }
+                } else {
+                    // Check for n^k * sqrt(n), for which we can also return a more useful answer.
+                    BoundedRational square = getSquare(mCrFactor);
+                    if (square != null) {
+                        int intSquare = square.intValue();
+                        if (sLogs[intSquare] != null) {
+                            long intLog = getIntLog(bi, intSquare);
+                            if (intLog != 0) {
+                                BoundedRational nRatFactor =
+                                        BoundedRational.add(new BoundedRational(intLog),
+                                        BoundedRational.HALF);
+                                if (nRatFactor != null) {
+                                    return new UnifiedReal(nRatFactor, sLogs[intSquare]);
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        return new UnifiedReal(crValue().ln());
+    }
+
+    public UnifiedReal exp() {
+        if (definitelyEquals(ZERO)) {
+            return ONE;
+        }
+        final BoundedRational crExp = getExp(mCrFactor);
+        if (crExp != null) {
+            if (mRatFactor.signum() < 0) {
+                return negate().exp().inverse();
+            }
+            boolean needSqrt = false;
+            BoundedRational ratExponent = mRatFactor;
+            BigInteger asBI = BoundedRational.asBigInteger(ratExponent);
+            if (asBI == null) {
+                // check for multiple of one half.
+                needSqrt = true;
+                ratExponent = BoundedRational.multiply(ratExponent, BoundedRational.TWO);
+            }
+            BoundedRational nRatFactor = BoundedRational.pow(crExp, ratExponent);
+            if (nRatFactor != null) {
+                UnifiedReal result = new UnifiedReal(nRatFactor);
+                if (needSqrt) {
+                    result = result.sqrt();
+                }
+                return result;
+            }
+        }
+        return new UnifiedReal(crValue().exp());
+    }
+
+
+    /**
+     * Generalized factorial.
+     * Compute n * (n - step) * (n - 2 * step) * etc.  This can be used to compute factorial a bit
+     * faster, especially if BigInteger uses sub-quadratic multiplication.
+     */
+    private static BigInteger genFactorial(long n, long step) {
+        if (n > 4 * step) {
+            BigInteger prod1 = genFactorial(n, 2 * step);
+            if (Thread.interrupted()) {
+                throw new CR.AbortedException();
+            }
+            BigInteger prod2 = genFactorial(n - step, 2 * step);
+            if (Thread.interrupted()) {
+                throw new CR.AbortedException();
+            }
+            return prod1.multiply(prod2);
+        } else {
+            if (n == 0) {
+                return BigInteger.ONE;
+            }
+            BigInteger res = BigInteger.valueOf(n);
+            for (long i = n - step; i > 1; i -= step) {
+                res = res.multiply(BigInteger.valueOf(i));
+            }
+            return res;
+        }
+    }
+
+
+    /**
+     * Factorial function.
+     * Fails if argument is clearly not an integer.
+     * May round to nearest integer if value is close.
+     */
+    public UnifiedReal fact() {
+        BigInteger asBI = bigIntegerValue();
+        if (asBI == null) {
+            asBI = crValue().get_appr(0);  // Correct if it was an integer.
+            if (!approxEquals(new UnifiedReal(asBI), DEFAULT_COMPARE_TOLERANCE)) {
+                throw new ArithmeticException("Non-integral factorial argument");
+            }
+        }
+        if (asBI.signum() < 0) {
+            throw new ArithmeticException("Negative factorial argument");
+        }
+        if (asBI.bitLength() > 20) {
+            // Will fail.  LongValue() may not work. Punt now.
+            throw new ArithmeticException("Factorial argument too big");
+        }
+        BigInteger biResult = genFactorial(asBI.longValue(), 1);
+        BoundedRational nRatFactor = new BoundedRational(biResult);
+        return new UnifiedReal(nRatFactor);
+    }
+
+    /**
+     * Return the number of decimal digits to the right of the decimal point required to represent
+     * the argument exactly.
+     * Return Integer.MAX_VALUE if that's not possible.  Never returns a value less than zero, even
+     * if r is a power of ten.
+     */
+    public int digitsRequired() {
+        if (mCrFactor == CR_ONE || mRatFactor.signum() == 0) {
+            return BoundedRational.digitsRequired(mRatFactor);
+        } else {
+            return Integer.MAX_VALUE;
+        }
+    }
+
+    /**
+     * Return an upper bound on the number of leading zero bits.
+     * These are the number of 0 bits
+     * to the right of the binary point and to the left of the most significant digit.
+     * Return Integer.MAX_VALUE if we cannot bound it.
+     */
+    public int leadingBinaryZeroes() {
+        if (isNamed(mCrFactor)) {
+            // Only ln(2) is smaller than one, and could possibly add one zero bit.
+            // Adding 3 gives us a somewhat sloppy upper bound.
+            final int wholeBits = mRatFactor.wholeNumberBits();
+            if (wholeBits == Integer.MIN_VALUE) {
+                return Integer.MAX_VALUE;
+            }
+            if (wholeBits >= 3) {
+                return 0;
+            } else {
+                return -wholeBits + 3;
+            }
+        }
+        return Integer.MAX_VALUE;
+    }
+
+    /**
+     * Is the number of bits to the left of the decimal point greater than bound?
+     * The result is inexact: We roughly approximate the whole number bits.
+     */
+    public boolean approxWholeNumberBitsGreaterThan(int bound) {
+        if (isNamed(mCrFactor)) {
+            return mRatFactor.wholeNumberBits() > bound;
+        } else {
+            return crValue().get_appr(bound - 2).bitLength() > 2;
+        }
+    }
+}
diff --git a/tests/src/com/android/calculator2/BRTest.java b/tests/src/com/android/calculator2/BoundedRationalTest.java
similarity index 61%
rename from tests/src/com/android/calculator2/BRTest.java
rename to tests/src/com/android/calculator2/BoundedRationalTest.java
index 68214d0..a53d6ad 100644
--- a/tests/src/com/android/calculator2/BRTest.java
+++ b/tests/src/com/android/calculator2/BoundedRationalTest.java
@@ -26,23 +26,19 @@
 
 import java.math.BigInteger;
 
-public class BRTest extends TestCase {
+public class BoundedRationalTest extends TestCase {
     private static void check(boolean x, String s) {
         if (!x) throw new AssertionFailedError(s);
     }
     final static int TEST_PREC = -100; // 100 bits to the right of
                                        // binary point.
     private static void checkEq(BoundedRational x, CR y, String s) {
-        check(x.CRValue().compareTo(y, TEST_PREC) == 0, s);
+        check(x.crValue().compareTo(y, TEST_PREC) == 0, s);
     }
     private static void checkWeakEq(BoundedRational x, CR y, String s) {
         if (x != null) checkEq(x, y, s);
     }
 
-    private final static UnaryCRFunction ASIN = UnaryCRFunction.asinFunction;
-    private final static UnaryCRFunction ACOS = UnaryCRFunction.acosFunction;
-    private final static UnaryCRFunction ATAN = UnaryCRFunction.atanFunction;
-    private final static UnaryCRFunction TAN = UnaryCRFunction.tanFunction;
     private final static BoundedRational BR_0 = new BoundedRational(0);
     private final static BoundedRational BR_M1 = new BoundedRational(-1);
     private final static BoundedRational BR_2 = new BoundedRational(2);
@@ -52,22 +48,10 @@
     private final static BoundedRational BR_M390 = new BoundedRational(-390);
     private final static CR CR_1 = CR.valueOf(1);
 
-    private final static CR RADIANS_PER_DEGREE = CR.PI.divide(CR.valueOf(180));
-    private final static CR DEGREES_PER_RADIAN = CR.valueOf(180).divide(CR.PI);
-    private final static CR LN10 = CR.valueOf(10).ln();
-
-    private static CR toRadians(CR x) {
-        return x.multiply(RADIANS_PER_DEGREE);
-    }
-
-    private static CR fromRadians(CR x) {
-        return x.multiply(DEGREES_PER_RADIAN);
-    }
-
     // We assume that x is simple enough that we don't overflow bounds.
     private static void checkBR(BoundedRational x) {
         check(x != null, "test data should not be null");
-        CR xAsCR = x.CRValue();
+        CR xAsCR = x.crValue();
         checkEq(BoundedRational.add(x, BoundedRational.ONE), xAsCR.add(CR_1),
                 "add 1:" + x);
         checkEq(BoundedRational.subtract(x, BoundedRational.MINUS_THIRTY),
@@ -76,48 +60,15 @@
                 xAsCR.multiply(CR.valueOf(15)), "multiply 15:" + x);
         checkEq(BoundedRational.divide(x, BR_15),
                 xAsCR.divide(CR.valueOf(15)), "divide 15:" + x);
-        checkWeakEq(BoundedRational.sin(x), xAsCR.sin(), "sin:" + x);
-        checkWeakEq(BoundedRational.cos(x), xAsCR.cos(), "cos:" + x);
-        checkWeakEq(BoundedRational.tan(x), TAN.execute(xAsCR), "tan:" + x);
-        checkWeakEq(BoundedRational.degreeSin(x), toRadians(xAsCR).sin(),
-                "degree sin:" + x);
-        checkWeakEq(BoundedRational.degreeCos(x), toRadians(xAsCR).cos(),
-                "degree cos:" + x);
         BigInteger big_x = BoundedRational.asBigInteger(x);
         long long_x = (big_x == null? 0 : big_x.longValue());
-        try {
-            checkWeakEq(BoundedRational.degreeTan(x),
-                        TAN.execute(toRadians(xAsCR)), "degree tan:" + x);
-            check((long_x - 90) % 180 != 0, "missed undefined tan: " + x);
-        } catch (ArithmeticException ignored) {
-            check((long_x - 90) % 180 == 0, "exception on defined tan: " + x);
-        }
         if (x.compareTo(BoundedRational.THIRTY) <= 0
                 && x.compareTo(BoundedRational.MINUS_THIRTY) >= 0) {
-            checkWeakEq(BoundedRational.exp(x), xAsCR.exp(), "exp:" + x);
             checkWeakEq(BoundedRational.pow(BR_15, x),
                     CR.valueOf(15).ln().multiply(xAsCR).exp(),
                     "pow(15,x):" + x);
         }
-        if (x.compareTo(BoundedRational.ONE) <= 0
-                && x.compareTo(BoundedRational.MINUS_ONE) >= 0) {
-            checkWeakEq(BoundedRational.asin(x), ASIN.execute(xAsCR),
-                        "asin:" + x);
-            checkWeakEq(BoundedRational.acos(x), ACOS.execute(xAsCR),
-                        "acos:" + x);
-            checkWeakEq(BoundedRational.degreeAsin(x),
-                        fromRadians(ASIN.execute(xAsCR)), "degree asin:" + x);
-            checkWeakEq(BoundedRational.degreeAcos(x),
-                        fromRadians(ACOS.execute(xAsCR)), "degree acos:" + x);
-        }
-        checkWeakEq(BoundedRational.atan(x), fromRadians(ATAN.execute(xAsCR)),
-                    "atan:" + x);
-        checkWeakEq(BoundedRational.degreeAtan(x),
-                    fromRadians(ATAN.execute(xAsCR)), "degree atan:" + x);
         if (x.signum() > 0) {
-            checkWeakEq(BoundedRational.ln(x), xAsCR.ln(), "ln:" + x);
-            checkWeakEq(BoundedRational.log(x), xAsCR.ln().divide(LN10),
-                        "log:" + x);
             checkWeakEq(BoundedRational.sqrt(x), xAsCR.sqrt(), "sqrt:" + x);
             checkEq(BoundedRational.pow(x, BR_15),
                     xAsCR.ln().multiply(CR.valueOf(15)).exp(),
@@ -128,7 +79,10 @@
     public void testBR() {
         BoundedRational b = new BoundedRational(4,-6);
         check(b.toString().equals("4/-6"), "toString(4/-6)");
-        check(b.toNiceString().equals("-2/3"),"toNiceString(4/-6)");
+        check(b.toNiceString().equals("-2/3"), "toNiceString(4/-6)");
+        check(b.toStringTruncated(1).equals("-0.6"), "(4/-6).toStringT(1)");
+        check(BR_15.toStringTruncated(0).equals("15."), "15.toStringT(1)");
+        check(BR_0.toStringTruncated(2).equals("0.00"), "0.toStringT(2)");
         checkEq(BR_0, CR.valueOf(0), "0");
         checkEq(BR_390, CR.valueOf(390), "390");
         checkEq(BR_15, CR.valueOf(15), "15");
@@ -151,10 +105,6 @@
                 "digitsRequired(-1/2)");
         check(BoundedRational.digitsRequired(new BoundedRational(1,-2)) == 1,
                 "digitsRequired(1/-2)");
-        check(BoundedRational.fact(BoundedRational.ZERO).equals(BoundedRational.ONE), "0!");
-        check(BoundedRational.fact(BoundedRational.ONE).equals(BoundedRational.ONE), "1!");
-        check(BoundedRational.fact(BoundedRational.TWO).equals(BoundedRational.TWO), "2!");
-        check(BoundedRational.fact(BR_15).equals(new BoundedRational(1307674368000L)), "15!");
         // We check values that include all interesting degree values.
         BoundedRational r = BR_M390;
         while (!r.equals(BR_390)) {
@@ -183,39 +133,25 @@
 
     public void testBRexceptions() {
         try {
-            BoundedRational.ln(BR_M1);
-            check(false, "ln(-1)");
-        } catch (ArithmeticException ignored) {}
-        try {
-            BoundedRational.log(BR_M2);
-            check(false, "log(-2)");
+            BoundedRational.divide(BR_390, BoundedRational.ZERO);
+            check(false, "390/0");
         } catch (ArithmeticException ignored) {}
         try {
             BoundedRational.sqrt(BR_M1);
             check(false, "sqrt(-1)");
         } catch (ArithmeticException ignored) {}
-        try {
-            BoundedRational.asin(BR_M2);
-            check(false, "asin(-2)");
-        } catch (ArithmeticException ignored) {}
-        try {
-            BoundedRational.degreeAcos(BR_2);
-            check(false, "degree acos(2)");
-        } catch (ArithmeticException ignored) {}
     }
 
     public void testBROverflow() {
         BoundedRational sum = new BoundedRational(0);
         long i;
-        for (i = 1; i < 1000; ++i) {
+        for (i = 1; i < 4000; ++i) {
              sum = BoundedRational.add(sum,
                         BoundedRational.inverse(new BoundedRational(i)));
              if (sum == null) break;
         }
-        // Experimentally, this overflows at 139, which seems
-        // plausible based on the Wolfram Alpha result.
-        // This test is robust against minor changes in MAX_SIZE.
-        check(i > 100, "Harmonic series overflowed at " + i);
-        check(i < 1000, "Harmonic series didn't overflow");
+        // With MAX_SIZE = 10000, we seem to overflow at 3488.
+        check(i > 3000, "Harmonic series overflowed at " + i);
+        check(i < 4000, "Harmonic series didn't overflow");
     }
 }
diff --git a/tests/src/com/android/calculator2/UnifiedRealTest.java b/tests/src/com/android/calculator2/UnifiedRealTest.java
new file mode 100644
index 0000000..20ac2b1
--- /dev/null
+++ b/tests/src/com/android/calculator2/UnifiedRealTest.java
@@ -0,0 +1,264 @@
+/*
+ * 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.
+ */
+
+// A test for UnifiedReal package.
+
+package com.android.calculator2;
+
+import com.hp.creals.CR;
+import com.hp.creals.UnaryCRFunction;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.TestCase;
+
+import java.math.BigInteger;
+
+public class UnifiedRealTest extends TestCase {
+    private static void check(boolean x, String s) {
+        if (!x) throw new AssertionFailedError(s);
+    }
+    final static int TEST_PREC = -100; // 100 bits to the right of
+                                       // binary point.
+    private static void checkEq(UnifiedReal x, CR y, String s) {
+        check(x.crValue().compareTo(y, TEST_PREC) == 0, s);
+    }
+
+    private final static UnaryCRFunction ASIN = UnaryCRFunction.asinFunction;
+    private final static UnaryCRFunction ACOS = UnaryCRFunction.acosFunction;
+    private final static UnaryCRFunction ATAN = UnaryCRFunction.atanFunction;
+    private final static UnaryCRFunction TAN = UnaryCRFunction.tanFunction;
+    private final static CR CR_1 = CR.ONE;
+
+    private final static CR RADIANS_PER_DEGREE = CR.PI.divide(CR.valueOf(180));
+    private final static CR DEGREES_PER_RADIAN = CR.valueOf(180).divide(CR.PI);
+    private final static CR LN10 = CR.valueOf(10).ln();
+
+    private final static UnifiedReal UR_30 = new UnifiedReal(30);
+    private final static UnifiedReal UR_MINUS30 = new UnifiedReal(-30);
+    private final static UnifiedReal UR_15 = new UnifiedReal(15);
+    private final static UnifiedReal UR_MINUS15 = new UnifiedReal(-15);
+
+    private static CR toRadians(CR x) {
+        return x.multiply(RADIANS_PER_DEGREE);
+    }
+
+    private static CR fromRadians(CR x) {
+        return x.multiply(DEGREES_PER_RADIAN);
+    }
+
+    private static UnifiedReal toRadians(UnifiedReal x) {
+        return x.multiply(UnifiedReal.RADIANS_PER_DEGREE);
+    }
+
+    private static UnifiedReal fromRadians(UnifiedReal x) {
+        return x.divide(UnifiedReal.RADIANS_PER_DEGREE);
+    }
+
+    // We assume that x is simple enough that we don't overflow bounds.
+    private static void checkUR(UnifiedReal x) {
+        CR xAsCr = x.crValue();
+        checkEq(x.add(UnifiedReal.ONE), xAsCr.add(CR_1), "add 1:" + x);
+        checkEq(x.subtract(UR_MINUS30), xAsCr.subtract(CR.valueOf(-30)), "sub -30:" + x);
+        checkEq(x.multiply(UR_15), xAsCr.multiply(CR.valueOf(15)), "multiply 15:" + x);
+        checkEq(x.divide(UR_15), xAsCr.divide(CR.valueOf(15)), "divide 15:" + x);
+        checkEq(x.sin(), xAsCr.sin(), "sin:" + x);
+        checkEq(x.cos(), xAsCr.cos(), "cos:" + x);
+        if (x.cos().definitelyNonZero()) {
+            checkEq(x.tan(), TAN.execute(xAsCr), "tan:" + x);
+        }
+        checkEq(toRadians(x).sin(), toRadians(xAsCr).sin(), "degree sin:" + x);
+        checkEq(toRadians(x).cos(), toRadians(xAsCr).cos(), "degree cos:" + x);
+        BigInteger big_x = x.bigIntegerValue();
+        long long_x = (big_x == null? 0 : big_x.longValue());
+        try {
+            checkEq(toRadians(x).tan(), TAN.execute(toRadians(xAsCr)), "degree tan:" + x);
+            check((long_x - 90) % 180 != 0, "missed undefined tan: " + x);
+        } catch (ArithmeticException ignored) {
+            check((long_x - 90) % 180 == 0, "exception on defined tan: " + x + " " + ignored);
+        }
+        if (x.compareTo(UR_30) <= 0 && x.compareTo(UR_MINUS30) >= 0) {
+            checkEq(x.exp(), xAsCr.exp(), "exp:" + x);
+            checkEq(UR_15.pow(x), CR.valueOf(15).ln().multiply(xAsCr).exp(), "pow(15,x):" + x);             }
+        if (x.compareTo(UnifiedReal.ONE) <= 0
+                && x.compareTo(UnifiedReal.ONE.negate()) >= 0) {
+            checkEq(x.asin(), ASIN.execute(xAsCr), "asin:" + x);
+            checkEq(x.acos(), ACOS.execute(xAsCr), "acos:" + x);
+            checkEq(fromRadians(x.asin()), fromRadians(ASIN.execute(xAsCr)), "degree asin:" + x);
+            checkEq(fromRadians(x.acos()), fromRadians(ACOS.execute(xAsCr)), "degree acos:" + x);
+        }
+        checkEq(x.atan(), ATAN.execute(xAsCr), "atan:" + x);
+        if (x.signum() > 0) {
+            checkEq(x.ln(), xAsCr.ln(), "ln:" + x);
+            checkEq(x.sqrt(), xAsCr.sqrt(), "sqrt:" + x);
+            checkEq(x.pow(UR_15), xAsCr.ln().multiply(CR.valueOf(15)).exp(), "pow(x,15):" + x);
+        }
+    }
+
+    public void testUR() {
+        UnifiedReal b = new UnifiedReal(new BoundedRational(4,-6));
+        check(b.toString().equals("4/-6*1.0000000000"), "toString(4/-6)");
+        check(b.toNiceString().equals("-2/3"), "toNiceString(4/-6)");
+        check(b.toStringTruncated(1).equals("-0.6"), "(4/-6).toString(1)");
+        check(UR_15.toStringTruncated(0).equals("15."), "15.toString(1)");
+        check(UnifiedReal.ZERO.toStringTruncated(2).equals("0.00"), "0.toString(2)");
+        checkEq(UnifiedReal.ZERO, CR.valueOf(0), "0");
+        checkEq(new UnifiedReal(390), CR.valueOf(390), "390");
+        checkEq(UR_15, CR.valueOf(15), "15");
+        checkEq(new UnifiedReal(390).negate(), CR.valueOf(-390), "-390");
+        checkEq(UnifiedReal.ONE.negate(), CR.valueOf(-1), "-1");
+        checkEq(new UnifiedReal(2), CR.valueOf(2), "2");
+        checkEq(new UnifiedReal(-2), CR.valueOf(-2), "-2");
+        check(UnifiedReal.ZERO.signum() == 0, "signum(0)");
+        check(UnifiedReal.ZERO.definitelyZero(), "definitelyZero(0)");
+        check(!UnifiedReal.ZERO.definitelyNonZero(), "definitelyNonZero(0)");
+        check(!UnifiedReal.PI.definitelyZero(), "definitelyZero(pi)");
+        check(UnifiedReal.PI.definitelyNonZero(), "definitelyNonZero(pi)");
+        check(UnifiedReal.ONE.negate().signum() == -1, "signum(-1)");
+        check(new UnifiedReal(2).signum() == 1, "signum(2)");
+        check(UnifiedReal.E.signum() == 1, "signum(e)");
+        check(new UnifiedReal(400).bigIntegerValue().intValue() == 400, "400.bigIntegerValue()");
+        check(UnifiedReal.HALF.bigIntegerValue() == null, "1/2.bigIntegerValue()");
+        check(UnifiedReal.HALF.negate().bigIntegerValue() == null, "-1/2.bigIntegerValue()");
+        check(new UnifiedReal(new BoundedRational(15, -5)).bigIntegerValue().intValue() == -3,
+                "-15/5.asBigInteger()");
+        check(UnifiedReal.ZERO.digitsRequired() == 0, "digitsRequired(0)");
+        check(UnifiedReal.HALF.digitsRequired() == 1, "digitsRequired(1)");
+        check(UnifiedReal.HALF.negate().digitsRequired() == 1, "digitsRequired(-1)");
+        check(UnifiedReal.ONE.divide(new UnifiedReal(-2)).digitsRequired() == 1,
+                "digitsRequired(-2)");
+        check(UnifiedReal.ZERO.fact().definitelyEquals(UnifiedReal.ONE), "0!");
+        check(UnifiedReal.ONE.fact().definitelyEquals(UnifiedReal.ONE), "1!");
+        check(UnifiedReal.TWO.fact().definitelyEquals(UnifiedReal.TWO), "2!");
+        check(new UnifiedReal(15).fact().definitelyEquals(new UnifiedReal(1307674368000L)), "15!");
+        check(UnifiedReal.ONE.exactlyDisplayable(), "1 displayable");
+        check(UnifiedReal.PI.exactlyDisplayable(), "PI displayable");
+        check(UnifiedReal.E.exactlyDisplayable(), "E displayable");
+        check(UnifiedReal.E.divide(UnifiedReal.E).exactlyDisplayable(), "E/E displayable");
+        check(!UnifiedReal.E.divide(UnifiedReal.PI).exactlyDisplayable(), "!E/PI displayable");
+        UnifiedReal r = new UnifiedReal(9).multiply(new UnifiedReal(3).sqrt()).ln();
+        checkEq(r, CR.valueOf(9).multiply(CR.valueOf(3).sqrt()).ln(), "ln(9sqrt(3))");
+        check(r.exactlyDisplayable(), "5/2log3");
+        checkEq(r.exp(), CR.valueOf(9).multiply(CR.valueOf(3).sqrt()), "9sqrt(3)");
+        check(r.exp().exactlyDisplayable(), "9sqrt(3)");
+        check(!UnifiedReal.E.divide(UnifiedReal.PI).definitelyEquals(
+                UnifiedReal.E.divide(UnifiedReal.PI)), "E/PI = E/PI not testable");
+        check(new UnifiedReal(32).sqrt().definitelyEquals(
+                (new UnifiedReal(2).sqrt().multiply(new UnifiedReal(4)))), "sqrt(32)");
+        check(new UnifiedReal(32).ln().divide(UnifiedReal.TWO.ln())
+                .definitelyEquals(new UnifiedReal(5)), "ln(32)");
+        check(new UnifiedReal(10).sqrt().multiply(UnifiedReal.TEN.sqrt())
+                .definitelyEquals(UnifiedReal.TEN), "sqrt(10)^2");
+        check(UnifiedReal.ZERO.leadingBinaryZeroes() == Integer.MAX_VALUE, "0.leadingBinaryZeros");
+        check(new UnifiedReal(new BoundedRational(7,1024)).leadingBinaryZeroes() >= 8,
+                "fract.leadingBinaryZeros");
+        UnifiedReal tmp = UnifiedReal.TEN.pow(new UnifiedReal(-1000));
+        int tmp2 = tmp.leadingBinaryZeroes();
+        check(tmp2 >= 3320 && tmp2 < 4000, "leadingBinaryZeroes(10^-1000)");
+        tmp2 = tmp.multiply(UnifiedReal.PI).leadingBinaryZeroes();
+        check(tmp2 >= 3319 && tmp2 < 4000, "leadingBinaryZeroes(pix10^-1000)");
+        // We check values that include all interesting degree values.
+        r = new UnifiedReal(-390);
+        int i = 0;
+        while (!r.definitelyEquals(new UnifiedReal(390))) {
+            check(i++ < 100, "int loop counter arithmetic failed!");
+            if (i > 100) {
+                break;
+            }
+            checkUR(r);
+            r = r.add(new UnifiedReal(15));
+        }
+        r = UnifiedReal.PI.multiply(new UnifiedReal(-3));
+        final UnifiedReal limit = r.negate();
+        final UnifiedReal increment = UnifiedReal.PI.divide(new UnifiedReal(24));
+        i = 0;
+        while (!r.definitelyEquals(limit)) {
+            check(i++ < 200, "transcendental loop counter arithmetic failed!");
+            if (i > 100) {
+                break;
+            }
+            checkUR(r);
+            r = r.add(increment);
+        }
+        checkUR(UnifiedReal.HALF);
+        checkUR(UnifiedReal.MINUS_HALF);
+        checkUR(UnifiedReal.ONE);
+        checkUR(UnifiedReal.MINUS_ONE);
+        checkUR(new UnifiedReal(1000));
+        checkUR(new UnifiedReal(100));
+        checkUR(new UnifiedReal(new BoundedRational(4,9)));
+        check(new UnifiedReal(new BoundedRational(4,9)).sqrt().definitelyEquals(
+                UnifiedReal.TWO.divide(new UnifiedReal(3))), "sqrt(4/9)");
+        checkUR(new UnifiedReal(new BoundedRational(4,9)).negate());
+        checkUR(new UnifiedReal(new BoundedRational(5,9)));
+        checkUR(new UnifiedReal(new BoundedRational(5,10)));
+        checkUR(new UnifiedReal(new BoundedRational(5,10)));
+        checkUR(new UnifiedReal(new BoundedRational(4,13)));
+        checkUR(new UnifiedReal(36));
+        checkUR(new UnifiedReal(36).negate());
+    }
+
+    public void testFunctionsOnSmall() {
+        // This checks some of the special cases we should handle semi-symbolically.
+        UnifiedReal small = new UnifiedReal(2).pow(new UnifiedReal(-1000));
+        UnifiedReal small2 = new UnifiedReal(-1000).exp();
+        for (int i = 0; i <= 10; i++) {
+            UnifiedReal r = new UnifiedReal(i);
+            UnifiedReal sqrt = r.sqrt();
+            if (i > 1 && i != 4 && i != 9) {
+                check(sqrt.definitelyIrrational() && !sqrt.definitelyRational(), "sqrt !rational");
+            } else {
+                check(!sqrt.definitelyIrrational() && sqrt.definitelyRational(), "sqrt rational");
+            }
+            check(sqrt.definitelyAlgebraic() && !sqrt.definitelyTranscendental(), "sqrt algenraic");
+            check(sqrt.multiply(sqrt).definitelyEquals(r), "sqrt " + i);
+            check(!sqrt.multiply(sqrt).definitelyEquals(r.add(small)), "sqrt small " + i);
+            check(!sqrt.multiply(sqrt).definitelyEquals(r.add(small2)), "sqrt small2 " + i);
+            if (i > 0) {
+                UnifiedReal log = r.ln();
+                check(log.exp().definitelyEquals(r), "log " + i);
+                if (i > 1) {
+                    check(log.definitelyTranscendental(), "log transcendental");
+                    check(!log.definitelyAlgebraic(), "log !algebraic");
+                    check(!log.definitelyRational(), "log !rational");
+                    check(log.definitelyIrrational(), "log !rational again");
+                } else {
+                    check(log.definitelyRational(), "log rational");
+                }
+                check(r.pow(r).ln().definitelyEquals(r.multiply(r.ln())), "ln(r^r)");
+            }
+        }
+    }
+
+    public void testURexceptions() {
+        try {
+            UnifiedReal.MINUS_ONE.ln();
+            check(false, "ln(-1)");
+        } catch (ArithmeticException ignored) {}
+        try {
+            UnifiedReal.MINUS_ONE.sqrt();
+            check(false, "sqrt(-1)");
+        } catch (ArithmeticException ignored) {}
+        try {
+            new UnifiedReal(-2).asin();
+            check(false, "asin(-2)");
+        } catch (ArithmeticException ignored) {}
+        try {
+            new UnifiedReal(-2).acos();
+            check(false, "acos(-2)");
+        } catch (ArithmeticException ignored) {}
+    }
+
+}