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