Render conversations to 980px viewport

Expand all conversations to 980px width to allow double-tap to continue
to work on newer Chromium builds. See
https://codereview.chromium.org/18850005.

cool side effects
* much more usable double-tap zoom factor on KK+ (now "it just works")
* better text rendering fidelity in complex layouts (text no longer
  escapes bounds as it tended to do before)
* fixes text sizing edge cases where text was unexpectedly large and/or
  line heights messed with text rendering

Initially enable these changes on both JB- and KK+ WebViews. Disable
NARROW_COLUMNS layout on JB- to prevent very short columns.
pros on JB-: better rendering fidelity like on KK+, consistency with KK+
cons on JB-: some power users will miss pinch-then-double-tap-to-reflow
functionality

Possible future improvement: use device-width viewport when full convo
fits to inherit Chromium's new fast-click-handling behavior.

Bug: 12579959
Bug: 10695551
Change-Id: I13f85a6df909a966fcd0862e5bd292ec6ae77212
diff --git a/assets/script.js b/assets/script.js
index 79987d3..b8bd62c 100644
--- a/assets/script.js
+++ b/assets/script.js
@@ -175,9 +175,14 @@
     var i;
     var el;
     var documentWidth;
+    var goalWidth;
+    var origWidth;
     var newZoom, oldZoom;
+    var outerZoom;
+    var outerDiv;
 
     documentWidth = document.body.offsetWidth;
+    goalWidth = WEBVIEW_WIDTH - DOC_SIDE_MARGIN * 2;
 
     for (i = 0; i < elements.length; i++) {
         el = elements[i];
@@ -186,12 +191,21 @@
         if (oldZoom) {
             el.style.zoom = 1;
         }
+        origWidth = el.style.width;
+        el.style.width = goalWidth + "px";
         newZoom = documentWidth / el.scrollWidth;
-        transformContent(el, documentWidth, el.scrollWidth);
+        transformContent(el, goalWidth, el.scrollWidth);
         newZoom = documentWidth / el.scrollWidth;
         if (NORMALIZE_MESSAGE_WIDTHS) {
-            el.style.zoom = newZoom;
+            if (el.classList.contains("mail-message-content")) {
+                outerZoom = 1;
+            } else {
+                outerDiv = up(el, "mail-message-content");
+                outerZoom = outerDiv ? outerDiv.style.zoom : 1;
+            }
+            el.style.zoom = newZoom / outerZoom;
         }
+        el.style.width = origWidth;
     }
 }
 
diff --git a/res/raw/template_conversation_lower.html b/res/raw/template_conversation_lower.html
index 9c18cf5..1b78f1c 100644
--- a/res/raw/template_conversation_lower.html
+++ b/res/raw/template_conversation_lower.html
@@ -6,6 +6,7 @@
   var DOC_BASE_URI = '%s';
   var CONVERSATION_BASE_URI = '%s';
   var WIDE_VIEWPORT_WIDTH = %s;
+  var WEBVIEW_WIDTH = %s;
   var ENABLE_CONTENT_READY = %s;
   var NORMALIZE_MESSAGE_WIDTHS = %s;
   var ENABLE_MUNGE_TABLES = %s;
diff --git a/res/raw/template_conversation_upper.html b/res/raw/template_conversation_upper.html
index 4e07c42..0932614 100644
--- a/res/raw/template_conversation_upper.html
+++ b/res/raw/template_conversation_upper.html
@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <html>
 <head>
-  <meta id="meta-viewport" name="viewport" content="width=device-width,initial-scale=1.0"
+  <meta id="meta-viewport" name="viewport" content="width=%s"
       data-zoom-on="maximum-scale=2" data-zoom-off="user-scalable=no" />
   <style>
     .elided-text,
@@ -58,5 +58,8 @@
     %s
   </style>
 </head>
+<script>
+    var DOC_SIDE_MARGIN = %s;
+</script>
 <body style="margin: 0 %spx;">
 <div id="conversation-header" class="spacer" style="height: %spx;"></div>
diff --git a/src/com/android/mail/browse/ConversationWebView.java b/src/com/android/mail/browse/ConversationWebView.java
index 979e8c5..ec66c04 100644
--- a/src/com/android/mail/browse/ConversationWebView.java
+++ b/src/com/android/mail/browse/ConversationWebView.java
@@ -216,6 +216,10 @@
         return mViewportWidth;
     }
 
+    public int getWidthInDp() {
+        return (int) (getWidth() / mDensity);
+    }
+
     /**
      * Similar to {@link #getScale()}, except that it returns the initially expected scale, as
      * determined by the ratio of actual screen pixels to logical HTML pixels.
@@ -223,9 +227,15 @@
      * tag.
      */
     public float getInitialScale() {
-        // an HTML meta-viewport width of "device-width" and unspecified (medium) density means
-        // that the default scale is effectively the screen density.
-        return mDensity;
+        final float scale;
+        if (getSettings().getLoadWithOverviewMode()) {
+            // in overview mode (aka auto-fit mode), the base ratio is screen px : viewport px
+            scale = (float) getWidth() / getViewportWidth();
+        } else {
+            // in no-zoom mode, the base ratio is just screen px : mdpi css px (i.e. density)
+            scale = mDensity;
+        }
+        return scale;
     }
 
     public int screenPxToWebPx(int screenPx) {
diff --git a/src/com/android/mail/ui/ConversationViewFragment.java b/src/com/android/mail/ui/ConversationViewFragment.java
index abc0e9c..c719320 100644
--- a/src/com/android/mail/ui/ConversationViewFragment.java
+++ b/src/com/android/mail/ui/ConversationViewFragment.java
@@ -699,8 +699,8 @@
         final int convHeaderPos = mAdapter.addConversationHeader(mConversation);
         final int convHeaderPx = measureOverlayHeight(convHeaderPos);
 
-        mTemplates.startConversation(mWebView.screenPxToWebPx(mSideMarginPx),
-                mWebView.screenPxToWebPx(convHeaderPx));
+        mTemplates.startConversation(mWebView.getViewportWidth(),
+                mWebView.screenPxToWebPx(mSideMarginPx), mWebView.screenPxToWebPx(convHeaderPx));
 
         int collapsedStart = -1;
         ConversationMessage prevCollapsedMsg = null;
@@ -795,8 +795,8 @@
 
         // If the conversation has specified a base uri, use it here, otherwise use mBaseUri
         return mTemplates.endConversation(mBaseUri, mConversation.getBaseUri(mBaseUri),
-                mWebView.getViewportWidth(), enableContentReadySignal, isOverviewMode(mAccount),
-                applyTransforms, applyTransforms);
+                mWebView.getViewportWidth(), mWebView.getWidthInDp(), enableContentReadySignal,
+                isOverviewMode(mAccount), applyTransforms, applyTransforms);
     }
 
     private void renderSuperCollapsedBlock(int start, int end) {
@@ -1100,12 +1100,18 @@
         // gesture handling
         final boolean overviewMode = isOverviewMode(mAccount);
         final WebSettings settings = mWebView.getSettings();
+        final WebSettings.LayoutAlgorithm layout;
         settings.setUseWideViewPort(overviewMode);
         settings.setSupportZoom(overviewMode);
         settings.setBuiltInZoomControls(overviewMode);
+        settings.setLoadWithOverviewMode(overviewMode);
         if (overviewMode) {
             settings.setDisplayZoomControls(false);
+            layout = WebSettings.LayoutAlgorithm.NORMAL;
+        } else {
+            layout = WebSettings.LayoutAlgorithm.NARROW_COLUMNS;
         }
+        settings.setLayoutAlgorithm(layout);
     }
 
     public class ConversationWebViewClient extends AbstractConversationWebViewClient {
diff --git a/src/com/android/mail/ui/HtmlConversationTemplates.java b/src/com/android/mail/ui/HtmlConversationTemplates.java
index 3e37a68..c576713 100644
--- a/src/com/android/mail/ui/HtmlConversationTemplates.java
+++ b/src/com/android/mail/ui/HtmlConversationTemplates.java
@@ -158,7 +158,7 @@
         return MESSAGE_PREFIX + msg.getId();
     }
 
-    public void startConversation(int sideMargin, int conversationHeaderHeight) {
+    public void startConversation(int viewportWidth, int sideMargin, int conversationHeaderHeight) {
         if (mInProgress) {
             throw new IllegalStateException(
                     "Should not call start conversation until end conversation has been called");
@@ -167,13 +167,14 @@
         reset();
         final String border = Utils.isRunningKitkatOrLater() ?
                 "img[blocked-src] { border: 1px solid #CCCCCC; }" : "";
-        append(sConversationUpper, border,  sideMargin, conversationHeaderHeight);
+        append(sConversationUpper, viewportWidth, border,  sideMargin, sideMargin,
+                conversationHeaderHeight);
         mInProgress = true;
     }
 
     public String endConversation(String docBaseUri, String conversationBaseUri,
-            int viewportWidth, boolean enableContentReadySignal, boolean normalizeMessageWidths,
-            boolean enableMungeTables, boolean enableMungeImages) {
+            int viewportWidth, int webviewWidth, boolean enableContentReadySignal,
+            boolean normalizeMessageWidths, boolean enableMungeTables, boolean enableMungeImages) {
         if (!mInProgress) {
             throw new IllegalStateException("must call startConversation first");
         }
@@ -182,7 +183,7 @@
 
         append(sConversationLower, contentReadyClass, mContext.getString(R.string.hide_elided),
                 mContext.getString(R.string.show_elided), docBaseUri, conversationBaseUri,
-                viewportWidth, enableContentReadySignal, normalizeMessageWidths,
+                viewportWidth, webviewWidth, enableContentReadySignal, normalizeMessageWidths,
                 enableMungeTables, enableMungeImages);
 
         mInProgress = false;