| /* |
| * Copyright (C) 2012 Google Inc. |
| * Licensed to The Android Open Source Project. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.mail.browse; |
| |
| import android.app.FragmentManager; |
| import android.app.LoaderManager; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.Loader; |
| import android.database.Cursor; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.support.v4.text.BidiFormatter; |
| import android.text.TextUtils; |
| import android.util.AttributeSet; |
| import android.view.LayoutInflater; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.widget.LinearLayout; |
| import android.widget.TextView; |
| |
| import com.android.mail.R; |
| import com.android.mail.analytics.Analytics; |
| import com.android.mail.browse.AttachmentLoader.AttachmentCursor; |
| import com.android.mail.browse.ConversationContainer.DetachListener; |
| import com.android.mail.browse.ConversationViewAdapter.MessageFooterItem; |
| import com.android.mail.browse.ConversationViewAdapter.MessageHeaderItem; |
| import com.android.mail.providers.Account; |
| import com.android.mail.providers.Attachment; |
| import com.android.mail.providers.Conversation; |
| import com.android.mail.providers.Message; |
| import com.android.mail.ui.AccountFeedbackActivity; |
| import com.android.mail.ui.AttachmentTile; |
| import com.android.mail.ui.AttachmentTileGrid; |
| import com.android.mail.utils.LogTag; |
| import com.android.mail.utils.LogUtils; |
| import com.android.mail.utils.Utils; |
| import com.google.common.base.Objects; |
| import com.google.common.collect.Lists; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| public class MessageFooterView extends LinearLayout implements DetachListener, |
| LoaderManager.LoaderCallbacks<Cursor>, View.OnClickListener { |
| |
| private MessageFooterItem mMessageFooterItem; |
| private MessageHeaderItem mMessageHeaderItem; |
| private LoaderManager mLoaderManager; |
| private FragmentManager mFragmentManager; |
| private AttachmentCursor mAttachmentsCursor; |
| private View mViewEntireMessagePrompt; |
| private TextView mTitleText; |
| private AttachmentTileGrid mAttachmentGrid; |
| private LinearLayout mAttachmentBarList; |
| private View mAboveAttachmentBarListLayout; |
| |
| private final LayoutInflater mInflater; |
| |
| private static final String LOG_TAG = LogTag.getLogTag(); |
| |
| private ConversationAccountController mAccountController; |
| |
| private BidiFormatter mBidiFormatter; |
| |
| private MessageFooterCallbacks mCallbacks; |
| |
| /** |
| * Callbacks for the MessageFooterView to enable resizing the height. |
| */ |
| public interface MessageFooterCallbacks { |
| void setMessageSpacerHeight(MessageFooterItem item, int newSpacerHeight); |
| |
| MessageFooterView getViewForItem(MessageFooterItem item); |
| |
| int getUpdatedHeight(MessageFooterItem item); |
| } |
| |
| public MessageFooterView(Context context) { |
| this(context, null); |
| } |
| |
| public MessageFooterView(Context context, AttributeSet attrs) { |
| super(context, attrs); |
| |
| mInflater = LayoutInflater.from(context); |
| } |
| |
| @Override |
| protected void onFinishInflate() { |
| super.onFinishInflate(); |
| |
| mViewEntireMessagePrompt = findViewById(R.id.view_entire_message_prompt); |
| mTitleText = (TextView) findViewById(R.id.attachments_header_text); |
| mAttachmentGrid = (AttachmentTileGrid) findViewById(R.id.attachment_tile_grid); |
| mAttachmentBarList = (LinearLayout) findViewById(R.id.attachment_bar_list); |
| mAboveAttachmentBarListLayout = findViewById(R.id.above_attachment_bar_list_layout); |
| |
| mViewEntireMessagePrompt.setOnClickListener(this); |
| } |
| |
| public void initialize(LoaderManager loaderManager, FragmentManager fragmentManager, |
| ConversationAccountController accountController, MessageFooterCallbacks callbacks) { |
| mLoaderManager = loaderManager; |
| mFragmentManager = fragmentManager; |
| mAccountController = accountController; |
| mCallbacks = callbacks; |
| } |
| |
| public void bind( |
| MessageHeaderItem headerItem, MessageFooterItem footerItem, boolean measureOnly) { |
| // Resets the footer view. This step is only done if the |
| // attachmentsListUri changes so that we don't |
| // repeat the work of layout and measure when |
| // we're only updating the attachments. |
| if (mMessageHeaderItem != null && |
| mMessageHeaderItem.getMessage() != null && |
| mMessageHeaderItem.getMessage().attachmentListUri != null && |
| !mMessageHeaderItem.getMessage().attachmentListUri.equals( |
| headerItem.getMessage().attachmentListUri)) { |
| mAttachmentGrid.removeAllViewsInLayout(); |
| mAttachmentBarList.removeAllViewsInLayout(); |
| mViewEntireMessagePrompt.setVisibility(View.GONE); |
| mTitleText.setVisibility(View.GONE); |
| mAttachmentGrid.setVisibility(View.GONE); |
| mAttachmentBarList.setVisibility(View.GONE); |
| hideAboveAttachmentBarListLayout(); |
| } |
| |
| // If this MessageFooterView is being bound to a new attachment, we need to unbind with the |
| // old loader |
| final Integer oldAttachmentLoaderId = getAttachmentLoaderId(); |
| |
| mMessageFooterItem = footerItem; |
| mMessageHeaderItem = headerItem; |
| |
| final Integer attachmentLoaderId = getAttachmentLoaderId(); |
| // Destroy the loader if we are attempting to load a different attachment |
| if (oldAttachmentLoaderId != null && |
| !Objects.equal(oldAttachmentLoaderId, attachmentLoaderId)) { |
| mLoaderManager.destroyLoader(oldAttachmentLoaderId); |
| } |
| |
| // kick off load of Attachment objects in background thread |
| // but don't do any Loader work if we're only measuring |
| if (!measureOnly && attachmentLoaderId != null) { |
| LogUtils.i(LOG_TAG, "binding footer view, calling initLoader for message %d", |
| attachmentLoaderId); |
| mLoaderManager.initLoader(attachmentLoaderId, Bundle.EMPTY, this); |
| } |
| |
| // Do an initial render if initLoader didn't already do one |
| if (mAttachmentGrid.getChildCount() == 0 && |
| mAttachmentBarList.getChildCount() == 0) { |
| renderAttachments(false); |
| } |
| |
| final ConversationMessage message = mMessageHeaderItem.getMessage(); |
| mViewEntireMessagePrompt.setVisibility( |
| message.clipped && !TextUtils.isEmpty(message.permalink) ? VISIBLE : GONE); |
| setVisibility(mMessageHeaderItem.isExpanded() ? VISIBLE : GONE); |
| } |
| |
| private void hideAboveAttachmentBarListLayout() { |
| if (mAboveAttachmentBarListLayout != null) { |
| mAboveAttachmentBarListLayout.setVisibility(GONE); |
| } |
| } |
| |
| private void showAboveAttachmentBarListLayout() { |
| if (mAboveAttachmentBarListLayout != null) { |
| final Conversation conversation = mMessageHeaderItem.getMessage().getConversation(); |
| if (conversation == null) { |
| hideAboveAttachmentBarListLayout(); |
| return; |
| } |
| AttachmentActionHandler.registerDismissListener(conversation.uri, mMessageFooterItem); |
| mAboveAttachmentBarListLayout.setVisibility(VISIBLE); |
| AttachmentActionHandler.setupAboveBarAttachmentLayout(mAboveAttachmentBarListLayout); |
| } |
| } |
| |
| private void renderAttachments(boolean loaderResult) { |
| final List<Attachment> attachments; |
| if (mAttachmentsCursor != null && !mAttachmentsCursor.isClosed()) { |
| int i = -1; |
| attachments = Lists.newArrayList(); |
| while (mAttachmentsCursor.moveToPosition(++i)) { |
| attachments.add(mAttachmentsCursor.get()); |
| } |
| } else { |
| // before the attachment loader results are in, we can still render immediately using |
| // the basic info in the message's attachmentsJSON |
| attachments = mMessageHeaderItem.getMessage().getAttachments(); |
| } |
| renderAttachments(attachments, loaderResult); |
| } |
| |
| private void renderAttachments(List<Attachment> attachments, boolean loaderResult) { |
| if (attachments == null || attachments.isEmpty()) { |
| return; |
| } |
| |
| // filter the attachments into tiled and non-tiled |
| final int maxSize = attachments.size(); |
| final List<Attachment> tiledAttachments = new ArrayList<Attachment>(maxSize); |
| final List<Attachment> barAttachments = new ArrayList<Attachment>(maxSize); |
| |
| for (Attachment attachment : attachments) { |
| if (attachment.isInlineAttachment()) { |
| // skip non-standard (aka inline) attachments |
| continue; |
| } else if (AttachmentTile.isTiledAttachment(attachment)) { |
| tiledAttachments.add(attachment); |
| } else { |
| barAttachments.add(attachment); |
| } |
| } |
| |
| mMessageHeaderItem.getMessage().attachmentsJson = Attachment.toJSONArray(attachments); |
| |
| // All attachments are inline, don't display anything. |
| if (tiledAttachments.isEmpty() && barAttachments.isEmpty()) { |
| return; |
| } |
| |
| mTitleText.setVisibility(View.VISIBLE); |
| |
| if (!tiledAttachments.isEmpty()) { |
| renderTiledAttachments(tiledAttachments, loaderResult); |
| } |
| if (!barAttachments.isEmpty()) { |
| renderBarAttachments(barAttachments, loaderResult); |
| } |
| } |
| |
| private void renderTiledAttachments(List<Attachment> tiledAttachments, boolean loaderResult) { |
| mAttachmentGrid.setVisibility(View.VISIBLE); |
| |
| // Setup the tiles. |
| mAttachmentGrid.configureGrid(mFragmentManager, getAccount(), |
| mMessageHeaderItem.getMessage(), tiledAttachments, loaderResult); |
| } |
| |
| private void renderBarAttachments(List<Attachment> barAttachments, boolean loaderResult) { |
| mAttachmentBarList.setVisibility(View.VISIBLE); |
| |
| if (!barAttachments.isEmpty() && |
| AttachmentActionHandler.shouldShowAboveBarAttachmentLayout(getContext())) { |
| showAboveAttachmentBarListLayout(); |
| } else { |
| hideAboveAttachmentBarListLayout(); |
| } |
| |
| final Account account = getAccount(); |
| for (Attachment attachment : barAttachments) { |
| final Uri id = attachment.getIdentifierUri(); |
| MessageAttachmentBar barAttachmentView = |
| (MessageAttachmentBar) mAttachmentBarList.findViewWithTag(id); |
| |
| if (barAttachmentView == null) { |
| barAttachmentView = MessageAttachmentBar.inflate(mInflater, this); |
| barAttachmentView.setTag(id); |
| barAttachmentView.initialize(mFragmentManager); |
| mAttachmentBarList.addView(barAttachmentView); |
| } |
| |
| barAttachmentView.render(attachment, account, mMessageHeaderItem.getMessage(), |
| loaderResult, getBidiFormatter()); |
| } |
| } |
| |
| private Integer getAttachmentLoaderId() { |
| Integer id = null; |
| final Message msg = mMessageHeaderItem == null ? null : mMessageHeaderItem.getMessage(); |
| if (msg != null && msg.hasAttachments && msg.attachmentListUri != null) { |
| id = msg.attachmentListUri.hashCode(); |
| } |
| return id; |
| } |
| |
| @Override |
| public void onDetachedFromParent() { |
| // Do nothing. |
| } |
| |
| @Override |
| public Loader<Cursor> onCreateLoader(int id, Bundle args) { |
| return new AttachmentLoader(getContext(), |
| mMessageHeaderItem.getMessage().attachmentListUri); |
| } |
| |
| @Override |
| public void onLoadFinished(Loader<Cursor> loader, Cursor data) { |
| mAttachmentsCursor = (AttachmentCursor) data; |
| |
| if (mAttachmentsCursor == null || mAttachmentsCursor.isClosed()) { |
| return; |
| } |
| |
| renderAttachments(true); |
| } |
| |
| @Override |
| public void onLoaderReset(Loader<Cursor> loader) { |
| mAttachmentsCursor = null; |
| } |
| |
| private BidiFormatter getBidiFormatter() { |
| if (mBidiFormatter == null) { |
| final ConversationViewAdapter adapter = mMessageHeaderItem != null |
| ? mMessageHeaderItem.getAdapter() : null; |
| if (adapter == null) { |
| mBidiFormatter = BidiFormatter.getInstance(); |
| } else { |
| mBidiFormatter = adapter.getBidiFormatter(); |
| } |
| } |
| return mBidiFormatter; |
| } |
| |
| @Override |
| public void onClick(View v) { |
| viewEntireMessage(); |
| } |
| |
| private void viewEntireMessage() { |
| Analytics.getInstance().sendEvent("view_entire_message", "clicked", null, 0); |
| |
| final Context context = getContext(); |
| final Intent intent = new Intent(); |
| final String activityName = |
| context.getResources().getString(R.string.full_message_activity); |
| if (TextUtils.isEmpty(activityName)) { |
| LogUtils.wtf(LOG_TAG, "Trying to open clipped message with no activity defined"); |
| return; |
| } |
| intent.setClassName(context, activityName); |
| final Account account = getAccount(); |
| final ConversationMessage message = mMessageHeaderItem.getMessage(); |
| if (account != null && !TextUtils.isEmpty(message.permalink)) { |
| intent.putExtra(AccountFeedbackActivity.EXTRA_ACCOUNT_URI, account.uri); |
| intent.putExtra(FullMessageContract.EXTRA_PERMALINK, message.permalink); |
| intent.putExtra(FullMessageContract.EXTRA_ACCOUNT_NAME, account.getEmailAddress()); |
| intent.putExtra(FullMessageContract.EXTRA_SERVER_MESSAGE_ID, message.serverId); |
| context.startActivity(intent); |
| } |
| } |
| |
| private Account getAccount() { |
| return mAccountController != null ? mAccountController.getAccount() : null; |
| } |
| |
| public void collapseAboveBarAttachmentsView() { |
| int heightBefore = measureHeight(); |
| mAboveAttachmentBarListLayout.setVisibility(View.GONE); |
| updateSpacerHeight(); |
| } |
| |
| private int measureHeight() { |
| ViewGroup parent = (ViewGroup) getParent(); |
| if (parent == null) { |
| LogUtils.e(LOG_TAG, new Error(), "Unable to measure height of detached header"); |
| return getHeight(); |
| } |
| return Utils.measureViewHeight(this, parent); |
| } |
| |
| private void updateSpacerHeight() { |
| final int h = measureHeight(); |
| |
| mMessageFooterItem.setHeight(h); |
| if (mCallbacks != null) { |
| mCallbacks.setMessageSpacerHeight(mMessageFooterItem, h); |
| } |
| } |
| } |