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 
SecureConversationViewController(SecureConversationViewControllerCallbacks callbacks)77     public SecureConversationViewController(SecureConversationViewControllerCallbacks callbacks) {
78         mCallbacks = callbacks;
79     }
80 
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)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 
onActivityCreated(Bundle savedInstanceState)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
onNotifierScroll(final int y)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      */
renderMessage(ConversationMessage message)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 
getMessage()221     public ConversationMessage getMessage() {
222         return mMessage;
223     }
224 
getConversationHeaderView()225     public ConversationViewHeader getConversationHeaderView() {
226         return mConversationHeaderView;
227     }
228 
dismissLoadingStatus()229     public void dismissLoadingStatus() {
230         mProgressController.dismissLoadingStatus();
231     }
232 
setSubject(String subject)233     public void setSubject(String subject) {
234         mConversationHeaderView.setSubject(subject);
235     }
236 
printMessage()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
setMessageSpacerHeight(MessageHeaderItem item, int newSpacerHeight)248     public void setMessageSpacerHeight(MessageHeaderItem item, int newSpacerHeight) {
249         // Do nothing.
250     }
251 
252     @Override
setMessageExpanded(MessageHeaderItem item, int newSpacerHeight)253     public void setMessageExpanded(MessageHeaderItem item, int newSpacerHeight) {
254         // Do nothing.
255     }
256 
257     @Override
setMessageDetailsExpanded(MessageHeaderItem i, boolean expanded, int heightBefore)258     public void setMessageDetailsExpanded(MessageHeaderItem i, boolean expanded, int heightBefore) {
259         // Do nothing.
260     }
261 
262     @Override
showExternalResources(final Message msg)263     public void showExternalResources(final Message msg) {
264         mWebView.getSettings().setBlockNetworkImage(false);
265     }
266 
267     @Override
showExternalResources(final String rawSenderAddress)268     public void showExternalResources(final String rawSenderAddress) {
269         mWebView.getSettings().setBlockNetworkImage(false);
270     }
271 
272     @Override
supportsMessageTransforms()273     public boolean supportsMessageTransforms() {
274         return false;
275     }
276 
277     @Override
getMessageTransforms(final Message msg)278     public String getMessageTransforms(final Message msg) {
279         return null;
280     }
281 
282     @Override
isSecure()283     public boolean isSecure() {
284         return true;
285     }
286 
287     @Override
getFragmentManager()288     public FragmentManager getFragmentManager() {
289         return mCallbacks.getFragment().getFragmentManager();
290     }
291 
292     // End MessageHeaderViewCallbacks implementations
293 }
294