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 18 package com.android.mail.browse; 19 20 import android.app.Activity; 21 import android.app.Fragment; 22 import android.app.LoaderManager; 23 import android.content.CursorLoader; 24 import android.content.Loader; 25 import android.database.Cursor; 26 import android.net.Uri; 27 import android.os.Bundle; 28 import android.os.Handler; 29 import android.provider.OpenableColumns; 30 import android.support.v7.app.ActionBarActivity; 31 import android.view.LayoutInflater; 32 import android.view.Menu; 33 import android.view.MenuInflater; 34 import android.view.MenuItem; 35 import android.view.View; 36 import android.view.ViewGroup; 37 import android.webkit.WebResourceResponse; 38 import android.webkit.WebView; 39 import android.widget.Toast; 40 41 import com.android.emailcommon.mail.Address; 42 import com.android.mail.R; 43 import com.android.mail.providers.Account; 44 import com.android.mail.ui.AbstractConversationWebViewClient; 45 import com.android.mail.ui.ContactLoaderCallbacks; 46 import com.android.mail.ui.SecureConversationViewController; 47 import com.android.mail.ui.SecureConversationViewControllerCallbacks; 48 import com.android.mail.utils.LogTag; 49 import com.android.mail.utils.LogUtils; 50 import com.android.mail.utils.Utils; 51 import com.google.common.collect.ImmutableList; 52 import com.google.common.collect.Sets; 53 54 import java.util.Collections; 55 import java.util.HashMap; 56 import java.util.List; 57 import java.util.Map; 58 import 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 */ 65 public 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 onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 191 if (Utils.isRunningKitkatOrLater()) { 192 inflater.inflate(R.menu.eml_fragment_menu, menu); 193 } 194 } 195 196 @Override 197 public boolean onOptionsItemSelected(MenuItem item) { 198 final int itemId = item.getItemId(); 199 if (itemId == R.id.print_message) { 200 mViewController.printMessage(); 201 } else { 202 return super.onOptionsItemSelected(item); 203 } 204 205 return true; 206 } 207 208 private void bailOutOnLoadError() { 209 final Activity activity = getActivity(); 210 Toast.makeText(activity, R.string.eml_loader_error_toast, Toast.LENGTH_LONG).show(); 211 activity.finish(); 212 } 213 214 // Start SecureConversationViewControllerCallbacks 215 216 @Override 217 public Handler getHandler() { 218 return mHandler; 219 } 220 221 @Override 222 public AbstractConversationWebViewClient getWebViewClient() { 223 return mWebViewClient; 224 } 225 226 @Override 227 public Fragment getFragment() { 228 return this; 229 } 230 231 @Override 232 public void setupConversationHeaderView(ConversationViewHeader headerView) { 233 // DO NOTHING 234 } 235 236 @Override 237 public boolean isViewVisibleToUser() { 238 return true; 239 } 240 241 @Override 242 public ContactLoaderCallbacks getContactInfoSource() { 243 if (mContactLoaderCallbacks == null) { 244 mContactLoaderCallbacks = new ContactLoaderCallbacks(getActivity()); 245 } 246 return mContactLoaderCallbacks; 247 } 248 249 @Override 250 public ConversationAccountController getConversationAccountController() { 251 return (EmlViewerActivity) getActivity(); 252 } 253 254 @Override 255 public Map<String, Address> getAddressCache() { 256 return mAddressCache; 257 } 258 259 @Override 260 public void setupMessageHeaderVeiledMatcher(MessageHeaderView messageHeaderView) { 261 // DO NOTHING 262 } 263 264 @Override 265 public void startMessageLoader() { 266 final LoaderManager manager = getLoaderManager(); 267 manager.initLoader(MESSAGE_LOADER, null, mMessageLoadCallbacks); 268 manager.initLoader(FILENAME_LOADER, null, mFilenameLoadCallbacks); 269 } 270 271 @Override 272 public String getBaseUri() { 273 return BASE_URI; 274 } 275 276 @Override 277 public boolean isViewOnlyMode() { 278 return true; 279 } 280 281 @Override 282 public boolean shouldAlwaysShowImages() { 283 return false; 284 } 285 286 // End SecureConversationViewControllerCallbacks 287 288 private class MessageLoadCallbacks 289 implements LoaderManager.LoaderCallbacks<ConversationMessage> { 290 @Override 291 public Loader<ConversationMessage> onCreateLoader(int id, Bundle args) { 292 switch (id) { 293 case MESSAGE_LOADER: 294 return new EmlMessageLoader(getActivity(), mEmlFileUri); 295 default: 296 return null; 297 } 298 } 299 300 @Override 301 public void onLoadFinished(Loader<ConversationMessage> loader, ConversationMessage data) { 302 if (data == null) { 303 final Activity activity = getActivity(); 304 if (activity != null) { 305 bailOutOnLoadError(); 306 } else { 307 mMessageLoadFailed = true; 308 } 309 return; 310 } 311 mViewController.setSubject(data.subject); 312 mViewController.renderMessage(data); 313 } 314 315 @Override 316 public void onLoaderReset(Loader<ConversationMessage> loader) { 317 // Do nothing 318 } 319 } 320 321 private class FilenameLoadCallbacks implements LoaderManager.LoaderCallbacks<Cursor> { 322 @Override 323 public Loader<Cursor> onCreateLoader(int id, Bundle args) { 324 switch (id) { 325 case FILENAME_LOADER: 326 return new CursorLoader(getActivity(), mEmlFileUri, 327 new String[] { OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE }, 328 null, null, null); 329 default: 330 return null; 331 } 332 } 333 334 @Override 335 public void onLoadFinished(Loader<Cursor> loader, Cursor data) { 336 if (data == null || !data.moveToFirst()) { 337 return; 338 } 339 340 ((ActionBarActivity) getActivity()).getSupportActionBar().setTitle( 341 data.getString(data.getColumnIndex(OpenableColumns.DISPLAY_NAME))); 342 } 343 344 @Override 345 public void onLoaderReset(Loader<Cursor> loader) { 346 } 347 } 348 349 } 350