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