1 /* 2 * Copyright (C) 2013 Google Inc. 3 * Licensed to The Android Open Source Project. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package com.android.mail.ui; 19 20 import android.app.Fragment; 21 import android.app.FragmentManager; 22 import android.content.res.Resources; 23 import android.graphics.Rect; 24 import android.os.Bundle; 25 import android.view.LayoutInflater; 26 import android.view.View; 27 import android.view.ViewGroup; 28 import android.webkit.WebSettings; 29 30 import com.android.mail.FormattedDateBuilder; 31 import com.android.mail.R; 32 import com.android.mail.browse.ConversationMessage; 33 import com.android.mail.browse.ConversationViewAdapter; 34 import com.android.mail.browse.ConversationViewAdapter.MessageHeaderItem; 35 import com.android.mail.browse.ConversationViewHeader; 36 import com.android.mail.browse.InlineAttachmentViewIntentBuilderCreator; 37 import com.android.mail.browse.InlineAttachmentViewIntentBuilderCreatorHolder; 38 import com.android.mail.browse.MessageFooterView; 39 import com.android.mail.browse.MessageHeaderView; 40 import com.android.mail.browse.MessageScrollView; 41 import com.android.mail.browse.MessageWebView; 42 import com.android.mail.browse.ScrollNotifier.ScrollListener; 43 import com.android.mail.browse.WebViewContextMenu; 44 import com.android.mail.print.PrintUtils; 45 import com.android.mail.providers.Conversation; 46 import com.android.mail.providers.Message; 47 import com.android.mail.utils.ConversationViewUtils; 48 49 /** 50 * Controller to do most of the heavy lifting for 51 * {@link SecureConversationViewFragment} and 52 * {@link com.android.mail.browse.EmlMessageViewFragment}. Currently that work 53 * is pretty much the rendering logic. 54 */ 55 public class SecureConversationViewController implements 56 MessageHeaderView.MessageHeaderViewCallbacks, ScrollListener, 57 MessageFooterView.MessageFooterCallbacks { 58 private static final String BEGIN_HTML = 59 "<body style=\"margin: 0 %spx;\"><div style=\"margin: 16px 0; font-size: 80%%\">"; 60 private static final String END_HTML = "</div></body>"; 61 62 private final SecureConversationViewControllerCallbacks mCallbacks; 63 64 private MessageWebView mWebView; 65 private ConversationViewHeader mConversationHeaderView; 66 private MessageHeaderView mMessageHeaderView; 67 private MessageHeaderView mSnapHeaderView; 68 private MessageFooterView mMessageFooterView; 69 private ConversationMessage mMessage; 70 private MessageScrollView mScrollView; 71 72 private ConversationViewProgressController mProgressController; 73 private FormattedDateBuilder mDateBuilder; 74 75 private int mSideMarginInWebPx; 76 77 public SecureConversationViewController(SecureConversationViewControllerCallbacks callbacks) { 78 mCallbacks = callbacks; 79 } 80 81 public View onCreateView(LayoutInflater inflater, ViewGroup container, 82 Bundle savedInstanceState) { 83 View rootView = inflater.inflate(R.layout.secure_conversation_view, container, false); 84 mScrollView = (MessageScrollView) rootView.findViewById(R.id.scroll_view); 85 mConversationHeaderView = (ConversationViewHeader) rootView.findViewById(R.id.conv_header); 86 mMessageHeaderView = (MessageHeaderView) rootView.findViewById(R.id.message_header); 87 mSnapHeaderView = (MessageHeaderView) rootView.findViewById(R.id.snap_header); 88 mMessageFooterView = (MessageFooterView) rootView.findViewById(R.id.message_footer); 89 90 mScrollView.addScrollListener(this); 91 92 // Add color backgrounds to the header and footer. 93 // Otherwise the backgrounds are grey. They can't 94 // be set in xml because that would add more overdraw 95 // in ConversationViewFragment. 96 final int color = rootView.getResources().getColor( 97 R.color.message_header_background_color); 98 mMessageHeaderView.setBackgroundColor(color); 99 mSnapHeaderView.setBackgroundColor(color); 100 mMessageFooterView.setBackgroundColor(color); 101 102 mProgressController = new ConversationViewProgressController( 103 mCallbacks.getFragment(), mCallbacks.getHandler()); 104 mProgressController.instantiateProgressIndicators(rootView); 105 mWebView = (MessageWebView) rootView.findViewById(R.id.webview); 106 mWebView.setOverScrollMode(View.OVER_SCROLL_NEVER); 107 mWebView.setWebViewClient(mCallbacks.getWebViewClient()); 108 final InlineAttachmentViewIntentBuilderCreator creator = 109 InlineAttachmentViewIntentBuilderCreatorHolder. 110 getInlineAttachmentViewIntentCreator(); 111 mWebView.setOnCreateContextMenuListener(new WebViewContextMenu( 112 mCallbacks.getFragment().getActivity(), 113 creator.createInlineAttachmentViewIntentBuilder(null, -1))); 114 mWebView.setFocusable(false); 115 final WebSettings settings = mWebView.getSettings(); 116 117 settings.setJavaScriptEnabled(false); 118 settings.setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL); 119 120 ConversationViewUtils.setTextZoom(mCallbacks.getFragment().getResources(), settings); 121 122 settings.setSupportZoom(true); 123 settings.setBuiltInZoomControls(true); 124 settings.setDisplayZoomControls(false); 125 126 mScrollView.setInnerScrollableView(mWebView); 127 128 return rootView; 129 } 130 131 public void onActivityCreated(Bundle savedInstanceState) { 132 mCallbacks.setupConversationHeaderView(mConversationHeaderView); 133 134 final Fragment fragment = mCallbacks.getFragment(); 135 136 mDateBuilder = new FormattedDateBuilder(fragment.getActivity()); 137 mMessageHeaderView.initialize( 138 mCallbacks.getConversationAccountController(), mCallbacks.getAddressCache()); 139 mMessageHeaderView.setContactInfoSource(mCallbacks.getContactInfoSource()); 140 mMessageHeaderView.setCallbacks(this); 141 mMessageHeaderView.setExpandable(false); 142 mMessageHeaderView.setViewOnlyMode(mCallbacks.isViewOnlyMode()); 143 144 mSnapHeaderView.setSnappy(); 145 mSnapHeaderView.initialize( 146 mCallbacks.getConversationAccountController(), mCallbacks.getAddressCache()); 147 mSnapHeaderView.setContactInfoSource(mCallbacks.getContactInfoSource()); 148 mSnapHeaderView.setCallbacks(this); 149 mSnapHeaderView.setExpandable(false); 150 mSnapHeaderView.setViewOnlyMode(mCallbacks.isViewOnlyMode()); 151 152 mCallbacks.setupMessageHeaderVeiledMatcher(mMessageHeaderView); 153 mCallbacks.setupMessageHeaderVeiledMatcher(mSnapHeaderView); 154 155 mMessageFooterView.initialize(fragment.getLoaderManager(), fragment.getFragmentManager(), 156 mCallbacks.getConversationAccountController(), this); 157 158 mCallbacks.startMessageLoader(); 159 160 mProgressController.showLoadingStatus(mCallbacks.isViewVisibleToUser()); 161 162 final Resources r = mCallbacks.getFragment().getResources(); 163 mSideMarginInWebPx = (int) (r.getDimensionPixelOffset( 164 R.dimen.conversation_message_content_margin_side) / r.getDisplayMetrics().density); 165 } 166 167 @Override 168 public void onNotifierScroll(final int y) { 169 // We need to decide whether or not to display the snap header. 170 // Get the location of the moveable message header inside the scroll view. 171 Rect rect = new Rect(); 172 mScrollView.offsetDescendantRectToMyCoords(mMessageHeaderView, rect); 173 174 // If we have scrolled further than the distance from the top of the scrollView to the top 175 // of the message header, then the message header is at least partially ofscreen. As soon 176 // as the message header goes partially offscreen we need to display the snap header. 177 // TODO - re-enable when dogfooders howl 178 // if (y > rect.top) { 179 // mSnapHeaderView.setVisibility(View.VISIBLE); 180 // } else { 181 mSnapHeaderView.setVisibility(View.GONE); 182 // } 183 } 184 185 /** 186 * Populate the adapter with overlay views (message headers, super-collapsed 187 * blocks, a conversation header), and return an HTML document with spacer 188 * divs inserted for all overlays. 189 */ 190 public void renderMessage(ConversationMessage message) { 191 mMessage = message; 192 193 final boolean alwaysShowImages = mCallbacks.shouldAlwaysShowImages(); 194 mWebView.getSettings().setBlockNetworkImage( 195 !alwaysShowImages && !mMessage.alwaysShowImages); 196 197 // Add formatting to message body 198 // At this point, only adds margins. 199 StringBuilder dataBuilder = new StringBuilder( 200 String.format(BEGIN_HTML, mSideMarginInWebPx)); 201 dataBuilder.append(mMessage.getBodyAsHtml()); 202 dataBuilder.append(END_HTML); 203 204 mWebView.loadDataWithBaseURL(mCallbacks.getBaseUri(), dataBuilder.toString(), 205 "text/html", "utf-8", null); 206 final MessageHeaderItem item = ConversationViewAdapter.newMessageHeaderItem( 207 null, mDateBuilder, mMessage, true, mMessage.alwaysShowImages); 208 // Clear out the old info from the header before (re)binding 209 mMessageHeaderView.unbind(); 210 mMessageHeaderView.bind(item, false); 211 212 mSnapHeaderView.unbind(); 213 mSnapHeaderView.bind(item, false); 214 215 if (mMessage.hasAttachments) { 216 mMessageFooterView.setVisibility(View.VISIBLE); 217 mMessageFooterView.bind(item, false); 218 } 219 } 220 221 public ConversationMessage getMessage() { 222 return mMessage; 223 } 224 225 public ConversationViewHeader getConversationHeaderView() { 226 return mConversationHeaderView; 227 } 228 229 public void dismissLoadingStatus() { 230 mProgressController.dismissLoadingStatus(); 231 } 232 233 public void setSubject(String subject) { 234 mConversationHeaderView.setSubject(subject); 235 } 236 237 public void printMessage() { 238 final Conversation conversation = mMessage.getConversation(); 239 PrintUtils.printMessage(mCallbacks.getFragment().getActivity(), mMessage, 240 conversation != null ? conversation.subject : mMessage.subject, 241 mCallbacks.getAddressCache(), mCallbacks.getBaseUri(), false /* useJavascript */); 242 243 } 244 245 // Start MessageHeaderViewCallbacks implementations 246 247 @Override 248 public void setMessageSpacerHeight(MessageHeaderItem item, int newSpacerHeight) { 249 // Do nothing. 250 } 251 252 @Override 253 public void setMessageExpanded(MessageHeaderItem item, int newSpacerHeight) { 254 // Do nothing. 255 } 256 257 @Override 258 public void setMessageDetailsExpanded(MessageHeaderItem i, boolean expanded, int heightBefore) { 259 // Do nothing. 260 } 261 262 @Override 263 public void showExternalResources(final Message msg) { 264 mWebView.getSettings().setBlockNetworkImage(false); 265 } 266 267 @Override 268 public void showExternalResources(final String rawSenderAddress) { 269 mWebView.getSettings().setBlockNetworkImage(false); 270 } 271 272 @Override 273 public boolean supportsMessageTransforms() { 274 return false; 275 } 276 277 @Override 278 public String getMessageTransforms(final Message msg) { 279 return null; 280 } 281 282 @Override 283 public boolean isSecure() { 284 return true; 285 } 286 287 @Override 288 public FragmentManager getFragmentManager() { 289 return mCallbacks.getFragment().getFragmentManager(); 290 } 291 292 // End MessageHeaderViewCallbacks implementations 293 } 294