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