MessageFooterView.java revision 4f347e811052f446c3958c76db278bcd7b39a44f
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.browse; 19 20import android.app.FragmentManager; 21import android.app.LoaderManager; 22import android.content.Context; 23import android.content.Intent; 24import android.content.Loader; 25import android.database.Cursor; 26import android.net.Uri; 27import android.os.Bundle; 28import android.support.v4.text.BidiFormatter; 29import android.text.TextUtils; 30import android.util.AttributeSet; 31import android.view.LayoutInflater; 32import android.view.View; 33import android.widget.LinearLayout; 34import android.widget.TextView; 35 36import com.android.mail.R; 37import com.android.mail.browse.AttachmentLoader.AttachmentCursor; 38import com.android.mail.browse.ConversationContainer.DetachListener; 39import com.android.mail.browse.ConversationViewAdapter.MessageHeaderItem; 40import com.android.mail.providers.Account; 41import com.android.mail.providers.Attachment; 42import com.android.mail.providers.Conversation; 43import com.android.mail.providers.Message; 44import com.android.mail.ui.AccountFeedbackActivity; 45import com.android.mail.ui.AttachmentTile; 46import com.android.mail.ui.AttachmentTileGrid; 47import com.android.mail.utils.LogTag; 48import com.android.mail.utils.LogUtils; 49import com.google.common.base.Objects; 50import com.google.common.collect.Lists; 51 52import java.util.ArrayList; 53import java.util.List; 54 55public class MessageFooterView extends LinearLayout implements DetachListener, 56 LoaderManager.LoaderCallbacks<Cursor>, View.OnClickListener { 57 58 private MessageHeaderItem mMessageHeaderItem; 59 private LoaderManager mLoaderManager; 60 private FragmentManager mFragmentManager; 61 private AttachmentCursor mAttachmentsCursor; 62 private View mViewEntireMessagePrompt; 63 private TextView mTitleText; 64 private AttachmentTileGrid mAttachmentGrid; 65 private LinearLayout mAttachmentBarList; 66 67 private final LayoutInflater mInflater; 68 69 private static final String LOG_TAG = LogTag.getLogTag(); 70 71 private ConversationAccountController mAccountController; 72 73 private BidiFormatter mBidiFormatter; 74 75 public MessageFooterView(Context context) { 76 this(context, null); 77 } 78 79 public MessageFooterView(Context context, AttributeSet attrs) { 80 super(context, attrs); 81 82 mInflater = LayoutInflater.from(context); 83 } 84 85 @Override 86 protected void onFinishInflate() { 87 super.onFinishInflate(); 88 89 mViewEntireMessagePrompt = findViewById(R.id.view_entire_message_prompt); 90 mTitleText = (TextView) findViewById(R.id.attachments_header_text); 91 mAttachmentGrid = (AttachmentTileGrid) findViewById(R.id.attachment_tile_grid); 92 mAttachmentBarList = (LinearLayout) findViewById(R.id.attachment_bar_list); 93 94 mViewEntireMessagePrompt.setOnClickListener(this); 95 } 96 97 public void initialize(LoaderManager loaderManager, FragmentManager fragmentManager, 98 ConversationAccountController accountController) { 99 mLoaderManager = loaderManager; 100 mFragmentManager = fragmentManager; 101 mAccountController = accountController; 102 } 103 104 public void bind(MessageHeaderItem headerItem, boolean measureOnly) { 105 // Resets the footer view. This step is only done if the 106 // attachmentsListUri changes so that we don't 107 // repeat the work of layout and measure when 108 // we're only updating the attachments. 109 if (mMessageHeaderItem != null && 110 mMessageHeaderItem.getMessage() != null && 111 mMessageHeaderItem.getMessage().attachmentListUri != null && 112 !mMessageHeaderItem.getMessage().attachmentListUri.equals( 113 headerItem.getMessage().attachmentListUri)) { 114 mAttachmentGrid.removeAllViewsInLayout(); 115 mAttachmentBarList.removeAllViewsInLayout(); 116 mViewEntireMessagePrompt.setVisibility(View.GONE); 117 mTitleText.setVisibility(View.GONE); 118 mAttachmentGrid.setVisibility(View.GONE); 119 mAttachmentBarList.setVisibility(View.GONE); 120 } 121 122 // If this MessageFooterView is being bound to a new attachment, we need to unbind with the 123 // old loader 124 final Integer oldAttachmentLoaderId = getAttachmentLoaderId(); 125 126 mMessageHeaderItem = headerItem; 127 128 final Integer attachmentLoaderId = getAttachmentLoaderId(); 129 // Destroy the loader if we are attempting to load a different attachment 130 if (oldAttachmentLoaderId != null && 131 !Objects.equal(oldAttachmentLoaderId, attachmentLoaderId)) { 132 mLoaderManager.destroyLoader(oldAttachmentLoaderId); 133 } 134 135 // kick off load of Attachment objects in background thread 136 // but don't do any Loader work if we're only measuring 137 if (!measureOnly && attachmentLoaderId != null) { 138 LogUtils.i(LOG_TAG, "binding footer view, calling initLoader for message %d", 139 attachmentLoaderId); 140 mLoaderManager.initLoader(attachmentLoaderId, Bundle.EMPTY, this); 141 } 142 143 // Do an initial render if initLoader didn't already do one 144 if (mAttachmentGrid.getChildCount() == 0 && 145 mAttachmentBarList.getChildCount() == 0) { 146 renderAttachments(false); 147 } 148 149 mViewEntireMessagePrompt.setVisibility( 150 mMessageHeaderItem.getMessage().clipped ? VISIBLE : GONE); 151 setVisibility(mMessageHeaderItem.isExpanded() ? VISIBLE : GONE); 152 } 153 154 private void renderAttachments(boolean loaderResult) { 155 final List<Attachment> attachments; 156 if (mAttachmentsCursor != null && !mAttachmentsCursor.isClosed()) { 157 int i = -1; 158 attachments = Lists.newArrayList(); 159 while (mAttachmentsCursor.moveToPosition(++i)) { 160 attachments.add(mAttachmentsCursor.get()); 161 } 162 } else { 163 // before the attachment loader results are in, we can still render immediately using 164 // the basic info in the message's attachmentsJSON 165 attachments = mMessageHeaderItem.getMessage().getAttachments(); 166 } 167 renderAttachments(attachments, loaderResult); 168 } 169 170 private void renderAttachments(List<Attachment> attachments, boolean loaderResult) { 171 if (attachments == null || attachments.isEmpty()) { 172 return; 173 } 174 175 // filter the attachments into tiled and non-tiled 176 final int maxSize = attachments.size(); 177 final List<Attachment> tiledAttachments = new ArrayList<Attachment>(maxSize); 178 final List<Attachment> barAttachments = new ArrayList<Attachment>(maxSize); 179 180 for (Attachment attachment : attachments) { 181 if (attachment.isInlineAttachment()) { 182 // skip non-standard (aka inline) attachments 183 continue; 184 } else if (AttachmentTile.isTiledAttachment(attachment)) { 185 tiledAttachments.add(attachment); 186 } else { 187 barAttachments.add(attachment); 188 } 189 } 190 191 mMessageHeaderItem.getMessage().attachmentsJson = Attachment.toJSONArray(attachments); 192 193 // All attachments are inline, don't display anything. 194 if (tiledAttachments.isEmpty() && barAttachments.isEmpty()) { 195 return; 196 } 197 198 mTitleText.setVisibility(View.VISIBLE); 199 200 renderTiledAttachments(tiledAttachments, loaderResult); 201 renderBarAttachments(barAttachments, loaderResult); 202 } 203 204 private void renderTiledAttachments(List<Attachment> tiledAttachments, boolean loaderResult) { 205 mAttachmentGrid.setVisibility(View.VISIBLE); 206 207 // Setup the tiles. 208 mAttachmentGrid.configureGrid(mFragmentManager, getAccount(), 209 mMessageHeaderItem.getMessage(), tiledAttachments, loaderResult); 210 } 211 212 private void renderBarAttachments(List<Attachment> barAttachments, boolean loaderResult) { 213 mAttachmentBarList.setVisibility(View.VISIBLE); 214 215 final Account account = getAccount(); 216 for (Attachment attachment : barAttachments) { 217 final Uri id = attachment.getIdentifierUri(); 218 MessageAttachmentBar barAttachmentView = 219 (MessageAttachmentBar) mAttachmentBarList.findViewWithTag(id); 220 221 if (barAttachmentView == null) { 222 barAttachmentView = MessageAttachmentBar.inflate(mInflater, this); 223 barAttachmentView.setTag(id); 224 barAttachmentView.initialize(mFragmentManager); 225 mAttachmentBarList.addView(barAttachmentView); 226 } 227 228 barAttachmentView.render(attachment, account, mMessageHeaderItem.getMessage(), 229 loaderResult, getBidiFormatter()); 230 } 231 } 232 233 private Integer getAttachmentLoaderId() { 234 Integer id = null; 235 final Message msg = mMessageHeaderItem == null ? null : mMessageHeaderItem.getMessage(); 236 if (msg != null && msg.hasAttachments && msg.attachmentListUri != null) { 237 id = msg.attachmentListUri.hashCode(); 238 } 239 return id; 240 } 241 242 @Override 243 public void onDetachedFromParent() { 244 // Do nothing 245 } 246 247 @Override 248 public Loader<Cursor> onCreateLoader(int id, Bundle args) { 249 return new AttachmentLoader(getContext(), 250 mMessageHeaderItem.getMessage().attachmentListUri); 251 } 252 253 @Override 254 public void onLoadFinished(Loader<Cursor> loader, Cursor data) { 255 mAttachmentsCursor = (AttachmentCursor) data; 256 257 if (mAttachmentsCursor == null || mAttachmentsCursor.isClosed()) { 258 return; 259 } 260 261 renderAttachments(true); 262 } 263 264 @Override 265 public void onLoaderReset(Loader<Cursor> loader) { 266 mAttachmentsCursor = null; 267 } 268 269 private BidiFormatter getBidiFormatter() { 270 if (mBidiFormatter == null) { 271 final ConversationViewAdapter adapter = mMessageHeaderItem != null 272 ? mMessageHeaderItem.getAdapter() : null; 273 if (adapter == null) { 274 mBidiFormatter = BidiFormatter.getInstance(); 275 } else { 276 mBidiFormatter = adapter.getBidiFormatter(); 277 } 278 } 279 return mBidiFormatter; 280 } 281 282 @Override 283 public void onClick(View v) { 284 viewEntireMessage(); 285 } 286 287 private void viewEntireMessage() { 288 final Context context = getContext(); 289 final Intent intent = new Intent(); 290 final String activityName = 291 context.getResources().getString(R.string.full_message_activity); 292 if (TextUtils.isEmpty(activityName)) { 293 LogUtils.wtf(LOG_TAG, "Trying to open clipped message with no activity defined"); 294 return; 295 } 296 intent.setClassName(context, activityName); 297 final Account account = getAccount(); 298 if (account != null) { 299 final Conversation conv = mMessageHeaderItem.getMessage().getConversation(); 300 intent.putExtra(AccountFeedbackActivity.EXTRA_ACCOUNT_URI, account.uri); 301 intent.putExtra(FullMessageContract.EXTRA_PERMALINK, conv.permalink); 302 intent.putExtra(FullMessageContract.EXTRA_ACCOUNT_NAME, account.getEmailAddress()); 303 context.startActivity(intent); 304 } 305 } 306 307 private Account getAccount() { 308 return mAccountController != null ? mAccountController.getAccount() : null; 309 } 310} 311