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