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