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
18package com.android.mail.ui;
19
20import android.app.Fragment;
21import android.app.FragmentManager;
22import android.content.res.Resources;
23import android.graphics.Rect;
24import android.os.Bundle;
25import android.view.LayoutInflater;
26import android.view.View;
27import android.view.ViewGroup;
28import android.webkit.WebSettings;
29
30import com.android.mail.FormattedDateBuilder;
31import com.android.mail.R;
32import com.android.mail.browse.ConversationMessage;
33import com.android.mail.browse.ConversationViewAdapter;
34import com.android.mail.browse.ConversationViewAdapter.MessageHeaderItem;
35import com.android.mail.browse.ConversationViewHeader;
36import com.android.mail.browse.InlineAttachmentViewIntentBuilderCreator;
37import com.android.mail.browse.InlineAttachmentViewIntentBuilderCreatorHolder;
38import com.android.mail.browse.MessageFooterView;
39import com.android.mail.browse.MessageHeaderView;
40import com.android.mail.browse.MessageScrollView;
41import com.android.mail.browse.MessageWebView;
42import com.android.mail.browse.ScrollNotifier.ScrollListener;
43import com.android.mail.browse.WebViewContextMenu;
44import com.android.mail.print.PrintUtils;
45import com.android.mail.providers.Conversation;
46import com.android.mail.providers.Message;
47import 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 */
55public 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