com.android.mail.browse.MessageAttachmentBar.java Source code

Java tutorial

Introduction

Here is the source code for com.android.mail.browse.MessageAttachmentBar.java

Source

/*
 * 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.AlertDialog;
import android.app.FragmentManager;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.support.v4.text.BidiFormatter;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.PopupMenu;
import android.widget.PopupMenu.OnMenuItemClickListener;
import android.widget.ProgressBar;
import android.widget.TextView;

import com.android.mail.R;
import com.android.mail.analytics.Analytics;
import com.android.mail.providers.Account;
import com.android.mail.providers.Attachment;
import com.android.mail.providers.UIProvider.AttachmentDestination;
import com.android.mail.providers.UIProvider.AttachmentState;
import com.android.mail.ui.AccountFeedbackActivity;
import com.android.mail.utils.AttachmentUtils;
import com.android.mail.utils.LogTag;
import com.android.mail.utils.LogUtils;
import com.android.mail.utils.MimeType;
import com.android.mail.utils.Utils;

/**
 * View for a single attachment in conversation view. Shows download status and allows launching
 * intents to act on an attachment.
 *
 */
public class MessageAttachmentBar extends FrameLayout
        implements OnClickListener, OnMenuItemClickListener, AttachmentViewInterface {

    private Attachment mAttachment;
    private TextView mTitle;
    private TextView mSubTitle;
    private String mAttachmentSizeText;
    private String mDisplayType;
    private ProgressBar mProgress;
    private ImageButton mCancelButton;
    private PopupMenu mPopup;
    private ImageView mOverflowButton;

    private final AttachmentActionHandler mActionHandler;
    private boolean mSaveClicked;
    private Account mAccount;

    private final Runnable mUpdateRunnable = new Runnable() {
        @Override
        public void run() {
            updateActionsInternal();
        }
    };

    private static final String LOG_TAG = LogTag.getLogTag();

    /**
     * Boolean used to tell whether extra option 1 should always be hidden.
     * Currently makes sure that there is no conversation because that state
     * means that we're in the EML viewer.
     */
    private boolean mHideExtraOptionOne;

    public MessageAttachmentBar(Context context) {
        this(context, null);
    }

    public MessageAttachmentBar(Context context, AttributeSet attrs) {
        super(context, attrs);

        mActionHandler = new AttachmentActionHandler(context, this);
    }

    public void initialize(FragmentManager fragmentManager) {
        mActionHandler.initialize(fragmentManager);
    }

    public static MessageAttachmentBar inflate(LayoutInflater inflater, ViewGroup parent) {
        MessageAttachmentBar view = (MessageAttachmentBar) inflater
                .inflate(R.layout.conversation_message_attachment_bar, parent, false);
        return view;
    }

    /**
     * Render or update an attachment's view. This happens immediately upon instantiation, and
     * repeatedly as status updates stream in, so only properties with new or changed values will
     * cause sub-views to update.
     */
    public void render(Attachment attachment, Account account, ConversationMessage message, boolean loaderResult,
            BidiFormatter bidiFormatter) {
        // get account uri for potential eml viewer usage
        mAccount = account;

        final Attachment prevAttachment = mAttachment;
        mAttachment = attachment;
        if (mAccount != null) {
            mActionHandler.setAccount(mAccount.getEmailAddress());
        }
        mActionHandler.setMessage(message);
        mActionHandler.setAttachment(mAttachment);
        mHideExtraOptionOne = message.getConversation() == null;

        // reset mSaveClicked if we are not currently downloading
        // So if the download fails or the download completes, we stop
        // showing progress, etc
        mSaveClicked = !attachment.isDownloading() ? false : mSaveClicked;

        LogUtils.d(LOG_TAG,
                "got attachment list row: name=%s state/dest=%d/%d dled=%d" + " contentUri=%s MIME=%s flags=%d",
                attachment.getName(), attachment.state, attachment.destination, attachment.downloadedSize,
                attachment.contentUri, attachment.getContentType(), attachment.flags);

        final String attachmentName = attachment.getName();
        if ((attachment.flags & Attachment.FLAG_DUMMY_ATTACHMENT) != 0) {
            mTitle.setText(R.string.load_attachment);
        } else if (prevAttachment == null || !TextUtils.equals(attachmentName, prevAttachment.getName())) {
            mTitle.setText(attachmentName);
        }

        if (prevAttachment == null || attachment.size != prevAttachment.size) {
            mAttachmentSizeText = bidiFormatter
                    .unicodeWrap(AttachmentUtils.convertToHumanReadableSize(getContext(), attachment.size));
            mDisplayType = bidiFormatter.unicodeWrap(AttachmentUtils.getDisplayType(getContext(), attachment));
            updateSubtitleText();
        }

        updateActions();
        mActionHandler.updateStatus(loaderResult);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();

        mTitle = (TextView) findViewById(R.id.attachment_title);
        mSubTitle = (TextView) findViewById(R.id.attachment_subtitle);
        mProgress = (ProgressBar) findViewById(R.id.attachment_progress);
        mOverflowButton = (ImageView) findViewById(R.id.overflow);
        mCancelButton = (ImageButton) findViewById(R.id.cancel_attachment);

        setOnClickListener(this);
        mOverflowButton.setOnClickListener(this);
        mCancelButton.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        onClick(v.getId(), v);
    }

    @Override
    public boolean onMenuItemClick(MenuItem item) {
        mPopup.dismiss();
        return onClick(item.getItemId(), null);
    }

    private boolean onClick(final int res, final View v) {
        if (res == R.id.preview_attachment) {
            previewAttachment();
        } else if (res == R.id.save_attachment) {
            if (mAttachment.canSave()) {
                mActionHandler.startDownloadingAttachment(AttachmentDestination.EXTERNAL);
                mSaveClicked = true;

                Analytics.getInstance().sendEvent("save_attachment",
                        Utils.normalizeMimeType(mAttachment.getContentType()), "attachment_bar", mAttachment.size);
            }
        } else if (res == R.id.download_again) {
            if (mAttachment.isPresentLocally()) {
                mActionHandler.showDownloadingDialog();
                mActionHandler.startRedownloadingAttachment(mAttachment);

                Analytics.getInstance().sendEvent("redownload_attachment",
                        Utils.normalizeMimeType(mAttachment.getContentType()), "attachment_bar", mAttachment.size);
            }
        } else if (res == R.id.cancel_attachment) {
            mActionHandler.cancelAttachment();
            mSaveClicked = false;

            Analytics.getInstance().sendEvent("cancel_attachment",
                    Utils.normalizeMimeType(mAttachment.getContentType()), "attachment_bar", mAttachment.size);
        } else if (res == R.id.attachment_extra_option1) {
            mActionHandler.handleOption1();
        } else if (res == R.id.overflow) {
            // If no overflow items are visible, just bail out.
            // We shouldn't be able to get here anyhow since the overflow
            // button should be hidden.
            if (shouldShowOverflow()) {
                if (mPopup == null) {
                    mPopup = new PopupMenu(getContext(), v);
                    mPopup.getMenuInflater().inflate(R.menu.message_footer_overflow_menu, mPopup.getMenu());
                    mPopup.setOnMenuItemClickListener(this);
                }

                final Menu menu = mPopup.getMenu();
                menu.findItem(R.id.preview_attachment).setVisible(shouldShowPreview());
                menu.findItem(R.id.save_attachment).setVisible(shouldShowSave());
                menu.findItem(R.id.download_again).setVisible(shouldShowDownloadAgain());
                menu.findItem(R.id.attachment_extra_option1).setVisible(shouldShowExtraOption1());

                mPopup.show();
            }
        } else {
            // Handles clicking the attachment
            // in any area that is not the overflow
            // button or cancel button or one of the
            // overflow items.
            final String mime = Utils.normalizeMimeType(mAttachment.getContentType());
            final String action;

            if ((mAttachment.flags & Attachment.FLAG_DUMMY_ATTACHMENT) != 0) {
                // This is a dummy. We need to download it, but not attempt to open or preview.
                mActionHandler.showDownloadingDialog();
                mActionHandler.setViewOnFinish(false);
                mActionHandler.startDownloadingAttachment(AttachmentDestination.CACHE);

                action = null;
            }
            // If we can install, install.
            else if (MimeType.isInstallable(mAttachment.getContentType())) {
                // Save to external because the package manager only handles
                // file:// uris not content:// uris. We do the same
                // workaround in
                // UiProvider#getUiAttachmentsCursorForUIAttachments()
                mActionHandler.showAttachment(AttachmentDestination.EXTERNAL);

                action = "attachment_bar_install";
            }
            // If we can view or play with an on-device app,
            // view or play.
            else if (MimeType.isViewable(getContext(), mAttachment.contentUri, mAttachment.getContentType())) {
                mActionHandler.showAttachment(AttachmentDestination.CACHE);

                action = "attachment_bar";
            }
            // If we can only preview the attachment, preview.
            else if (mAttachment.canPreview()) {
                previewAttachment();

                action = null;
            }
            // Otherwise, if we cannot do anything, show the info dialog.
            else {
                AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
                int dialogMessage = R.string.no_application_found;
                builder.setTitle(R.string.more_info_attachment).setMessage(dialogMessage).show();

                action = "attachment_bar_no_viewer";
            }

            if (action != null) {
                Analytics.getInstance().sendEvent("view_attachment", mime, action, mAttachment.size);
            }
        }

        return true;
    }

    private boolean shouldShowPreview() {
        // state could be anything
        return mAttachment.canPreview();
    }

    private boolean shouldShowSave() {
        return mAttachment.canSave() && !mSaveClicked;
    }

    private boolean shouldShowDownloadAgain() {
        // implies state == SAVED || state == FAILED
        // and the attachment supports re-download
        return mAttachment.supportsDownloadAgain() && mAttachment.isDownloadFinishedOrFailed();
    }

    private boolean shouldShowExtraOption1() {
        return !mHideExtraOptionOne
                && mActionHandler.shouldShowExtraOption1(mAccount.getType(), mAttachment.getContentType());
    }

    private boolean shouldShowOverflow() {
        return (shouldShowPreview() || shouldShowSave() || shouldShowDownloadAgain() || shouldShowExtraOption1())
                && !shouldShowCancel();
    }

    private boolean shouldShowCancel() {
        return mAttachment.isDownloading() && mSaveClicked;
    }

    @Override
    public void viewAttachment() {
        if (mAttachment.contentUri == null) {
            LogUtils.e(LOG_TAG, "viewAttachment with null content uri");
            return;
        }

        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);

        final String contentType = mAttachment.getContentType();
        Utils.setIntentDataAndTypeAndNormalize(intent, mAttachment.contentUri, contentType);

        // For EML files, we want to open our dedicated
        // viewer rather than let any activity open it.
        if (MimeType.isEmlMimeType(contentType)) {
            intent.setPackage(getContext().getPackageName());
            intent.putExtra(AccountFeedbackActivity.EXTRA_ACCOUNT_URI, mAccount != null ? mAccount.uri : null);
        }

        try {
            getContext().startActivity(intent);
        } catch (ActivityNotFoundException e) {
            // couldn't find activity for View intent
            LogUtils.e(LOG_TAG, e, "Couldn't find Activity for intent");
        }
    }

    private void previewAttachment() {
        if (mAttachment.canPreview()) {
            final Intent previewIntent = new Intent(Intent.ACTION_VIEW, mAttachment.previewIntentUri);
            previewIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
            getContext().startActivity(previewIntent);

            Analytics.getInstance().sendEvent("preview_attachment",
                    Utils.normalizeMimeType(mAttachment.getContentType()), null, mAttachment.size);
        }
    }

    private static void setButtonVisible(View button, boolean visible) {
        button.setVisibility(visible ? VISIBLE : GONE);
    }

    /**
     * Update all actions based on current downloading state.
     */
    private void updateActions() {
        removeCallbacks(mUpdateRunnable);
        post(mUpdateRunnable);
    }

    private void updateActionsInternal() {
        // If the progress dialog is visible, skip any of the updating
        if (mActionHandler.isProgressDialogVisible()) {
            return;
        }

        // To avoid visibility state transition bugs, every button's visibility should be touched
        // once by this routine.
        setButtonVisible(mCancelButton, shouldShowCancel());
        setButtonVisible(mOverflowButton, shouldShowOverflow());
    }

    @Override
    public void onUpdateStatus() {
        updateSubtitleText();
    }

    @Override
    public void updateProgress(boolean showProgress) {
        if (mAttachment.isDownloading()) {
            mProgress.setMax(mAttachment.size);
            mProgress.setProgress(mAttachment.downloadedSize);
            mProgress.setIndeterminate(!showProgress);
            mProgress.setVisibility(VISIBLE);
            mSubTitle.setVisibility(INVISIBLE);
        } else {
            mProgress.setVisibility(INVISIBLE);
            mSubTitle.setVisibility(VISIBLE);
        }
    }

    private void updateSubtitleText() {
        // TODO: make this a formatted resource when we have a UX design.
        // not worth translation right now.
        final StringBuilder sb = new StringBuilder();
        if (mAttachment.state == AttachmentState.FAILED) {
            sb.append(getResources().getString(R.string.download_failed));
        } else {
            if (mAttachment.isSavedToExternal()) {
                sb.append(getResources().getString(R.string.saved, mAttachmentSizeText));
            } else {
                sb.append(mAttachmentSizeText);
            }
            if (mDisplayType != null) {
                sb.append(' ');
                sb.append(mDisplayType);
            }
        }
        mSubTitle.setText(sb.toString());
    }
}