EmlMessageViewFragment.java revision 90b60f1bc30e7b0494789365ce80e50ddf44f274
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.browse;
19
20import android.app.Activity;
21import android.app.Fragment;
22import android.app.LoaderManager;
23import android.content.CursorLoader;
24import android.content.Loader;
25import android.database.Cursor;
26import android.net.Uri;
27import android.os.Bundle;
28import android.os.Handler;
29import android.provider.OpenableColumns;
30import android.support.v7.app.ActionBarActivity;
31import android.view.LayoutInflater;
32import android.view.Menu;
33import android.view.MenuInflater;
34import android.view.MenuItem;
35import android.view.View;
36import android.view.ViewGroup;
37import android.webkit.WebResourceResponse;
38import android.webkit.WebView;
39import android.widget.Toast;
40
41import com.android.emailcommon.mail.Address;
42import com.android.mail.R;
43import com.android.mail.providers.Account;
44import com.android.mail.ui.AbstractConversationWebViewClient;
45import com.android.mail.ui.ContactLoaderCallbacks;
46import com.android.mail.ui.SecureConversationViewController;
47import com.android.mail.ui.SecureConversationViewControllerCallbacks;
48import com.android.mail.utils.LogTag;
49import com.android.mail.utils.LogUtils;
50import com.android.mail.utils.Utils;
51import com.google.common.collect.ImmutableList;
52import com.google.common.collect.Sets;
53
54import java.util.Collections;
55import java.util.HashMap;
56import java.util.List;
57import java.util.Map;
58import java.util.Set;
59
60/**
61 * Fragment that is used to view EML files. It is mostly stubs
62 * that calls {@link SecureConversationViewController} to do most
63 * of the rendering work.
64 */
65public class EmlMessageViewFragment extends Fragment
66        implements SecureConversationViewControllerCallbacks {
67    private static final String ARG_EML_FILE_URI = "eml_file_uri";
68    private static final String BASE_URI = "x-thread://message/rfc822/";
69
70    private static final int MESSAGE_LOADER = 0;
71    private static final int CONTACT_LOADER = 1;
72    private static final int FILENAME_LOADER = 2;
73
74    private static final String LOG_TAG = LogTag.getLogTag();
75
76    private final Handler mHandler = new Handler();
77
78    private EmlWebViewClient mWebViewClient;
79    private SecureConversationViewController mViewController;
80    private ContactLoaderCallbacks mContactLoaderCallbacks;
81
82    private final MessageLoadCallbacks mMessageLoadCallbacks = new MessageLoadCallbacks();
83    private final FilenameLoadCallbacks mFilenameLoadCallbacks = new FilenameLoadCallbacks();
84
85    private Uri mEmlFileUri;
86
87    private boolean mMessageLoadFailed;
88
89    /**
90     * Cache of email address strings to parsed Address objects.
91     * <p>
92     * Remember to synchronize on the map when reading or writing to this cache, because some
93     * instances use it off the UI thread (e.g. from WebView).
94     */
95    protected final Map<String, Address> mAddressCache = Collections.synchronizedMap(
96            new HashMap<String, Address>());
97
98    private class EmlWebViewClient extends AbstractConversationWebViewClient {
99        public EmlWebViewClient(Account account) {
100            super(account);
101        }
102
103        @Override
104        public WebResourceResponse shouldInterceptRequest(WebView view, String url) {
105            // try to load the url assuming it is a cid url
106            final Uri uri = Uri.parse(url);
107            final WebResourceResponse response = loadCIDUri(uri, mViewController.getMessage());
108            if (response != null) {
109                return response;
110            }
111
112            return super.shouldInterceptRequest(view, url);
113        }
114
115        @Override
116        public void onPageFinished(WebView view, String url) {
117            // Ignore unsafe calls made after a fragment is detached from an activity.
118            // This method needs to, for example, get at the loader manager, which needs
119            // the fragment to be added.
120            if (!isAdded()) {
121                LogUtils.d(LOG_TAG, "ignoring EMLVF.onPageFinished, url=%s fragment=%s", url,
122                        EmlMessageViewFragment.this);
123                return;
124            }
125            mViewController.dismissLoadingStatus();
126
127            final Set<String> emailAddresses = Sets.newHashSet();
128            final List<Address> cacheCopy;
129            synchronized (mAddressCache) {
130                cacheCopy = ImmutableList.copyOf(mAddressCache.values());
131            }
132            for (Address addr : cacheCopy) {
133                emailAddresses.add(addr.getAddress());
134            }
135            final ContactLoaderCallbacks callbacks = getContactInfoSource();
136            callbacks.setSenders(emailAddresses);
137            getLoaderManager().restartLoader(CONTACT_LOADER, Bundle.EMPTY, callbacks);
138        }
139    }
140
141    /**
142     * Creates a new instance of {@link EmlMessageViewFragment},
143     * initialized to display an eml file from the specified {@link Uri}.
144     */
145    public static EmlMessageViewFragment newInstance(Uri emlFileUri, Uri accountUri) {
146        EmlMessageViewFragment f = new EmlMessageViewFragment();
147        Bundle args = new Bundle(1);
148        args.putParcelable(ARG_EML_FILE_URI, emlFileUri);
149        f.setArguments(args);
150        return f;
151    }
152
153    /**
154     * Constructor needs to be public to handle orientation changes and activity
155     * lifecycle events.
156     */
157    public EmlMessageViewFragment() {}
158
159    @Override
160    public void onCreate(Bundle savedState) {
161        super.onCreate(savedState);
162
163        Bundle args = getArguments();
164        mEmlFileUri = args.getParcelable(ARG_EML_FILE_URI);
165
166        mWebViewClient = new EmlWebViewClient(null);
167        mViewController = new SecureConversationViewController(this);
168
169        setHasOptionsMenu(true);
170    }
171
172    @Override
173    public View onCreateView(LayoutInflater inflater, ViewGroup container,
174            Bundle savedInstanceState) {
175        return mViewController.onCreateView(inflater, container, savedInstanceState);
176    }
177
178    @Override
179    public void onActivityCreated(Bundle savedInstanceState) {
180        super.onActivityCreated(savedInstanceState);
181        if (mMessageLoadFailed) {
182            bailOutOnLoadError();
183            return;
184        }
185        mWebViewClient.setActivity(getActivity());
186        mViewController.onActivityCreated(savedInstanceState);
187    }
188
189    @Override
190    public void onDestroyView() {
191        super.onDestroyView();
192        mViewController.onDestroyView();
193    }
194
195    @Override
196    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
197        if (Utils.isRunningKitkatOrLater()) {
198            inflater.inflate(R.menu.eml_fragment_menu, menu);
199        }
200    }
201
202    @Override
203    public boolean onOptionsItemSelected(MenuItem item) {
204        final int itemId = item.getItemId();
205        if (itemId == R.id.print_message) {
206            mViewController.printMessage();
207        } else {
208            return super.onOptionsItemSelected(item);
209        }
210
211        return true;
212    }
213
214    private void bailOutOnLoadError() {
215        final Activity activity = getActivity();
216        Toast.makeText(activity, R.string.eml_loader_error_toast, Toast.LENGTH_LONG).show();
217        activity.finish();
218    }
219
220    // Start SecureConversationViewControllerCallbacks
221
222    @Override
223    public Handler getHandler() {
224        return mHandler;
225    }
226
227    @Override
228    public AbstractConversationWebViewClient getWebViewClient() {
229        return mWebViewClient;
230    }
231
232    @Override
233    public Fragment getFragment() {
234        return this;
235    }
236
237    @Override
238    public void setupConversationHeaderView(ConversationViewHeader headerView) {
239        // DO NOTHING
240    }
241
242    @Override
243    public boolean isViewVisibleToUser() {
244        return true;
245    }
246
247    @Override
248    public ContactLoaderCallbacks getContactInfoSource() {
249        if (mContactLoaderCallbacks == null) {
250            mContactLoaderCallbacks = new ContactLoaderCallbacks(getActivity());
251        }
252        return mContactLoaderCallbacks;
253    }
254
255    @Override
256    public ConversationAccountController getConversationAccountController() {
257        return (EmlViewerActivity) getActivity();
258    }
259
260    @Override
261    public Map<String, Address> getAddressCache() {
262        return mAddressCache;
263    }
264
265    @Override
266    public void setupMessageHeaderVeiledMatcher(MessageHeaderView messageHeaderView) {
267        // DO NOTHING
268    }
269
270    @Override
271    public void startMessageLoader() {
272        final LoaderManager manager = getLoaderManager();
273        manager.initLoader(MESSAGE_LOADER, null, mMessageLoadCallbacks);
274        manager.initLoader(FILENAME_LOADER, null, mFilenameLoadCallbacks);
275    }
276
277    @Override
278    public String getBaseUri() {
279        return BASE_URI;
280    }
281
282    @Override
283    public boolean isViewOnlyMode() {
284        return true;
285    }
286
287    @Override
288    public boolean shouldAlwaysShowImages() {
289        return false;
290    }
291
292    // End SecureConversationViewControllerCallbacks
293
294    private class MessageLoadCallbacks
295            implements LoaderManager.LoaderCallbacks<ConversationMessage> {
296        @Override
297        public Loader<ConversationMessage> onCreateLoader(int id, Bundle args) {
298            switch (id) {
299                case MESSAGE_LOADER:
300                    return new EmlMessageLoader(getActivity(), mEmlFileUri);
301                default:
302                    return null;
303            }
304        }
305
306        @Override
307        public void onLoadFinished(Loader<ConversationMessage> loader, ConversationMessage data) {
308            if (data == null) {
309                final Activity activity = getActivity();
310                if (activity != null) {
311                    bailOutOnLoadError();
312                } else {
313                    mMessageLoadFailed = true;
314                }
315                return;
316            }
317            mViewController.setSubject(data.subject);
318            mViewController.renderMessage(data);
319        }
320
321        @Override
322        public void onLoaderReset(Loader<ConversationMessage> loader) {
323            // Do nothing
324        }
325    }
326
327    private class FilenameLoadCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {
328        @Override
329        public Loader<Cursor> onCreateLoader(int id, Bundle args) {
330            switch (id) {
331                case FILENAME_LOADER:
332                    return new CursorLoader(getActivity(), mEmlFileUri,
333                            new String[] { OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE },
334                            null, null, null);
335                default:
336                    return null;
337            }
338        }
339
340        @Override
341        public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
342            if (data == null || !data.moveToFirst()) {
343                return;
344            }
345
346            ((ActionBarActivity) getActivity()).getSupportActionBar().setTitle(
347                    data.getString(data.getColumnIndex(OpenableColumns.DISPLAY_NAME)));
348        }
349
350        @Override
351        public void onLoaderReset(Loader<Cursor> loader) {
352        }
353    }
354
355}
356