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