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