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