1/*
2 * Copyright (C) 2012 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.content.Loader;
22import android.net.Uri;
23import android.os.Bundle;
24import android.view.LayoutInflater;
25import android.view.View;
26import android.view.ViewGroup;
27import android.webkit.WebResourceResponse;
28import android.webkit.WebView;
29
30import com.android.emailcommon.mail.Address;
31import com.android.mail.browse.ConversationAccountController;
32import com.android.mail.browse.ConversationMessage;
33import com.android.mail.browse.ConversationViewHeader;
34import com.android.mail.browse.MessageCursor;
35import com.android.mail.browse.MessageHeaderView;
36import com.android.mail.compose.ComposeActivity;
37import com.android.mail.content.ObjectCursor;
38import com.android.mail.providers.Account;
39import com.android.mail.providers.Conversation;
40import com.android.mail.utils.LogTag;
41import com.android.mail.utils.LogUtils;
42import com.google.common.collect.ImmutableList;
43import com.google.common.collect.Sets;
44
45import java.util.HashSet;
46import java.util.List;
47import java.util.Map;
48import java.util.Set;
49
50public class SecureConversationViewFragment extends AbstractConversationViewFragment
51        implements SecureConversationViewControllerCallbacks {
52    private static final String LOG_TAG = LogTag.getLogTag();
53
54    private SecureConversationViewController mViewController;
55
56    private class SecureConversationWebViewClient extends AbstractConversationWebViewClient {
57        public SecureConversationWebViewClient(Account account) {
58            super(account);
59        }
60
61        @Override
62        public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
63            // try to load the url assuming it is a cid url
64            final Uri uri = Uri.parse(url);
65            final WebResourceResponse response = loadCIDUri(uri, mViewController.getMessage());
66            if (response != null) {
67                return response;
68            }
69
70            // otherwise, attempt the default handling
71            return super.shouldInterceptRequest(view, url);
72        }
73
74        @Override
75        public void onPageFinished(WebView view, String url) {
76            // Ignore unsafe calls made after a fragment is detached from an activity.
77            // This method needs to, for example, get at the loader manager, which needs
78            // the fragment to be added.
79            if (!isAdded()) {
80                LogUtils.d(LOG_TAG, "ignoring SCVF.onPageFinished, url=%s fragment=%s", url,
81                        SecureConversationViewFragment.this);
82                return;
83            }
84
85            if (isUserVisible()) {
86                onConversationSeen();
87            }
88
89            mViewController.dismissLoadingStatus();
90
91            final Set<String> emailAddresses = Sets.newHashSet();
92            final List<Address> cacheCopy;
93            synchronized (mAddressCache) {
94                cacheCopy = ImmutableList.copyOf(mAddressCache.values());
95            }
96            for (Address addr : cacheCopy) {
97                emailAddresses.add(addr.getAddress());
98            }
99            final ContactLoaderCallbacks callbacks = getContactInfoSource();
100            callbacks.setSenders(emailAddresses);
101            getLoaderManager().restartLoader(CONTACT_LOADER, Bundle.EMPTY, callbacks);
102        }
103    }
104
105    /**
106     * Creates a new instance of {@link ConversationViewFragment}, initialized
107     * to display a conversation with other parameters inherited/copied from an
108     * existing bundle, typically one created using {@link #makeBasicArgs}.
109     */
110    public static SecureConversationViewFragment newInstance(Bundle existingArgs,
111            Conversation conversation) {
112        SecureConversationViewFragment f = new SecureConversationViewFragment();
113        Bundle args = new Bundle(existingArgs);
114        args.putParcelable(ARG_CONVERSATION, conversation);
115        f.setArguments(args);
116        return f;
117    }
118
119    /**
120     * Constructor needs to be public to handle orientation changes and activity
121     * lifecycle events.
122     */
123    public SecureConversationViewFragment() {}
124
125    @Override
126    public void onCreate(Bundle savedState) {
127        super.onCreate(savedState);
128
129        mWebViewClient = new SecureConversationWebViewClient(mAccount);
130        mViewController = new SecureConversationViewController(this);
131    }
132
133    @Override
134    public View onCreateView(LayoutInflater inflater, ViewGroup container,
135            Bundle savedInstanceState) {
136        return mViewController.onCreateView(inflater, container, savedInstanceState);
137    }
138
139    @Override
140    public void onActivityCreated(Bundle savedInstanceState) {
141        super.onActivityCreated(savedInstanceState);
142        mViewController.onActivityCreated(savedInstanceState);
143    }
144
145    // Start implementations of SecureConversationViewControllerCallbacks
146
147    @Override
148    public Fragment getFragment() {
149        return this;
150    }
151
152    @Override
153    public AbstractConversationWebViewClient getWebViewClient() {
154        return mWebViewClient;
155    }
156
157    @Override
158    public void setupConversationHeaderView(ConversationViewHeader headerView) {
159        headerView.setCallbacks(this, this, getListController());
160        headerView.setFolders(mConversation);
161        headerView.setSubject(mConversation.subject);
162        headerView.setStarred(mConversation.starred);
163    }
164
165    @Override
166    public boolean isViewVisibleToUser() {
167        return isUserVisible();
168    }
169
170    @Override
171    public ConversationAccountController getConversationAccountController() {
172        return this;
173    }
174
175    @Override
176    public Map<String, Address> getAddressCache() {
177        return mAddressCache;
178    }
179
180    @Override
181    public void setupMessageHeaderVeiledMatcher(MessageHeaderView messageHeaderView) {
182        messageHeaderView.setVeiledMatcher(
183                ((ControllableActivity) getActivity()).getAccountController()
184                        .getVeiledAddressMatcher());
185    }
186
187    @Override
188    public void startMessageLoader() {
189        getLoaderManager().initLoader(MESSAGE_LOADER, null, getMessageLoaderCallbacks());
190    }
191
192    @Override
193    public String getBaseUri() {
194        return mBaseUri;
195    }
196
197    @Override
198    public boolean isViewOnlyMode() {
199        return false;
200    }
201
202    // End implementations of SecureConversationViewControllerCallbacks
203
204    @Override
205    protected void markUnread() {
206        super.markUnread();
207        // Ignore unsafe calls made after a fragment is detached from an activity
208        final ControllableActivity activity = (ControllableActivity) getActivity();
209        final ConversationMessage message = mViewController.getMessage();
210        if (activity == null || mConversation == null || message == null) {
211            LogUtils.w(LOG_TAG, "ignoring markUnread for conv=%s",
212                    mConversation != null ? mConversation.id : 0);
213            return;
214        }
215        final HashSet<Uri> uris = new HashSet<Uri>();
216        uris.add(message.uri);
217        activity.getConversationUpdater().markConversationMessagesUnread(mConversation, uris,
218                mViewState.getConversationInfo());
219    }
220
221    @Override
222    public void onAccountChanged(Account newAccount, Account oldAccount) {
223        renderMessage(getMessageCursor());
224    }
225
226    @Override
227    public void onConversationViewHeaderHeightChange(int newHeight) {
228        // Do nothing.
229    }
230
231    @Override
232    public void onUserVisibleHintChanged() {
233        if (mActivity == null) {
234            return;
235        }
236        if (isUserVisible()) {
237            onConversationSeen();
238        }
239    }
240
241    @Override
242    protected void onMessageCursorLoadFinished(Loader<ObjectCursor<ConversationMessage>> loader,
243            MessageCursor newCursor, MessageCursor oldCursor) {
244        renderMessage(newCursor);
245    }
246
247    private void renderMessage(MessageCursor newCursor) {
248        // ignore cursors that are still loading results
249        if (newCursor == null || !newCursor.isLoaded()) {
250            LogUtils.i(LOG_TAG, "CONV RENDER: existing cursor is null, rendering from scratch");
251            return;
252        }
253        if (mActivity == null || mActivity.isFinishing()) {
254            // Activity is finishing, just bail.
255            return;
256        }
257        if (!newCursor.moveToFirst()) {
258            LogUtils.e(LOG_TAG, "unable to open message cursor");
259            return;
260        }
261
262        mViewController.renderMessage(newCursor.getMessage());
263    }
264
265    @Override
266    public void onConversationUpdated(Conversation conv) {
267        final ConversationViewHeader headerView = mViewController.getConversationHeaderView();
268        if (headerView != null) {
269            headerView.onConversationUpdated(conv);
270        }
271    }
272
273    // Need this stub here
274    @Override
275    public boolean supportsMessageTransforms() {
276        return false;
277    }
278
279    /**
280     * Users are expected to use the Print item in the Message overflow menu to print the single
281     * message.
282     *
283     * @return {@code false} because Print and Print All menu items are never shown in EMail.
284     */
285    @Override
286    protected boolean shouldShowPrintInOverflow() {
287        return false;
288    }
289
290    @Override
291    protected void printConversation() {
292        mViewController.printMessage();
293    }
294
295    @Override
296    protected void handleReply() {
297        final ConversationMessage msg = mViewController.getMessage();
298        if (msg != null) {
299            ComposeActivity.reply(getActivity(), mAccount, msg);
300        }
301    }
302
303    @Override
304    protected void handleReplyAll() {
305        final ConversationMessage msg = mViewController.getMessage();
306        if (msg != null) {
307            ComposeActivity.replyAll(getActivity(), mAccount, msg);
308        }
309    }
310}
311