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