MessageAttachmentBar.java revision a95161cf364baeea1a9bb098b4f6db961ec8705b
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.AlertDialog;
21import android.content.ActivityNotFoundException;
22import android.content.Context;
23import android.content.Intent;
24import android.text.TextUtils;
25import android.util.AttributeSet;
26import android.view.LayoutInflater;
27import android.view.Menu;
28import android.view.MenuItem;
29import android.view.View;
30import android.view.View.OnClickListener;
31import android.view.ViewGroup;
32import android.widget.GridLayout;
33import android.widget.ImageButton;
34import android.widget.ImageView;
35import android.widget.PopupMenu;
36import android.widget.PopupMenu.OnMenuItemClickListener;
37import android.widget.ProgressBar;
38import android.widget.TextView;
39
40import com.android.mail.R;
41import com.android.mail.providers.Attachment;
42import com.android.mail.providers.UIProvider.AttachmentDestination;
43import com.android.mail.providers.UIProvider.AttachmentState;
44import com.android.mail.utils.AttachmentUtils;
45import com.android.mail.utils.LogTag;
46import com.android.mail.utils.LogUtils;
47import com.android.mail.utils.MimeType;
48import com.android.mail.utils.Utils;
49
50import java.util.List;
51/**
52 * View for a single attachment in conversation view. Shows download status and allows launching
53 * intents to act on an attachment.
54 *
55 */
56public class MessageAttachmentBar extends GridLayout implements OnClickListener,
57        OnMenuItemClickListener, AttachmentViewInterface {
58
59    private Attachment mAttachment;
60    private TextView mTitle;
61    private TextView mSubTitle;
62    private String mAttachmentSizeText;
63    private String mDisplayType;
64    private ProgressBar mProgress;
65    private ImageButton mCancelButton;
66    private PopupMenu mPopup;
67    private ImageView mOverflowButton;
68
69    private final AttachmentActionHandler mActionHandler;
70
71    private static final String LOG_TAG = LogTag.getLogTag();
72
73    public MessageAttachmentBar(Context context) {
74        this(context, null);
75    }
76
77    public MessageAttachmentBar(Context context, AttributeSet attrs) {
78        super(context, attrs);
79
80        mActionHandler = new AttachmentActionHandler(context, this);
81    }
82
83    public static MessageAttachmentBar inflate(LayoutInflater inflater, ViewGroup parent) {
84        MessageAttachmentBar view = (MessageAttachmentBar) inflater.inflate(
85                R.layout.conversation_message_attachment_bar, parent, false);
86        return view;
87    }
88
89    /**
90     * Render or update an attachment's view. This happens immediately upon instantiation, and
91     * repeatedly as status updates stream in, so only properties with new or changed values will
92     * cause sub-views to update.
93     *
94     */
95    public void render(Attachment attachment) {
96        final Attachment prevAttachment = mAttachment;
97        mAttachment = attachment;
98        mActionHandler.setAttachment(mAttachment);
99
100        LogUtils.d(LOG_TAG, "got attachment list row: name=%s state/dest=%d/%d dled=%d" +
101                " contentUri=%s MIME=%s", attachment.name, attachment.state,
102                attachment.destination, attachment.downloadedSize, attachment.contentUri,
103                attachment.contentType);
104
105        if (prevAttachment == null || !TextUtils.equals(attachment.name, prevAttachment.name)) {
106            mTitle.setText(attachment.name);
107        }
108
109        if (prevAttachment == null || attachment.size != prevAttachment.size) {
110            mAttachmentSizeText = AttachmentUtils.convertToHumanReadableSize(getContext(),
111                    attachment.size);
112            mDisplayType = AttachmentUtils.getDisplayType(getContext(), attachment);
113            updateSubtitleText(null);
114        }
115
116        mProgress.setMax(attachment.size);
117
118        updateActions();
119        mActionHandler.updateStatus();
120    }
121
122    @Override
123    protected void onFinishInflate() {
124        super.onFinishInflate();
125
126        mTitle = (TextView) findViewById(R.id.attachment_title);
127        mSubTitle = (TextView) findViewById(R.id.attachment_subtitle);
128        mProgress = (ProgressBar) findViewById(R.id.attachment_progress);
129        mOverflowButton = (ImageView) findViewById(R.id.overflow);
130        mCancelButton = (ImageButton) findViewById(R.id.cancel_attachment);
131
132        setOnClickListener(this);
133        mOverflowButton.setOnClickListener(this);
134        mCancelButton.setOnClickListener(this);
135    }
136
137    @Override
138    public void onClick(View v) {
139        onClick(v.getId(), v);
140    }
141
142    @Override
143    public boolean onMenuItemClick(MenuItem item) {
144        mPopup.dismiss();
145        return onClick(item.getItemId(), null);
146    }
147
148    private boolean onClick(int res, View v) {
149        switch (res) {
150            case R.id.preview_attachment:
151                previewAttachment();
152                break;
153            case R.id.save_attachment:
154                if (mAttachment.canSave()) {
155                    mActionHandler.startDownloadingAttachment(AttachmentDestination.EXTERNAL);
156                }
157                break;
158            case R.id.cancel_attachment:
159                mActionHandler.cancelAttachment();
160                break;
161            case R.id.overflow: {
162                final boolean canSave = mAttachment.canSave() && !mAttachment.isDownloading();
163                final boolean canPreview = (mAttachment.previewIntent != null);
164
165                // If no overflow items are visible, just bail out.
166                // We shouldn't be able to get here anyhow since the overflow
167                // button should be hidden.
168                if (!canSave && !canPreview) {
169                    break;
170                }
171
172                if (mPopup == null) {
173                    mPopup = new PopupMenu(getContext(), v);
174                    mPopup.getMenuInflater().inflate(R.menu.message_footer_overflow_menu,
175                            mPopup.getMenu());
176                    mPopup.setOnMenuItemClickListener(this);
177                }
178
179                final Menu menu = mPopup.getMenu();
180                menu.findItem(R.id.preview_attachment).setVisible(canPreview);
181                menu.findItem(R.id.save_attachment).setVisible(canSave);
182
183                mPopup.show();
184                break;
185            }
186            default:
187                // Handles clicking the attachment
188                // in any area that is not the overflow
189                // button or cancel button or one of the
190                // overflow items.
191
192                // If we can install, install.
193                if (MimeType.isInstallable(mAttachment.contentType)) {
194                    mActionHandler.showAttachment(AttachmentDestination.EXTERNAL);
195                }
196                // If we can view or play with an on-device app,
197                // view or play.
198                else if (MimeType.isPlayable(mAttachment.contentType)
199                        || MimeType.isViewable(getContext(), mAttachment.contentUri,
200                                mAttachment.contentType)) {
201                    mActionHandler.showAttachment(AttachmentDestination.CACHE);
202                }
203                // If we can only preview the attachment, preview.
204                else if (mAttachment.previewIntent != null) {
205                    previewAttachment();
206                }
207                // Otherwise, if we cannot do anything, show the info dialog.
208                else {
209                    AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
210                    int dialogMessage = MimeType.isBlocked(mAttachment.contentType)
211                            ? R.string.attachment_type_blocked : R.string.no_application_found;
212                    builder.setTitle(R.string.more_info_attachment)
213                           .setMessage(dialogMessage)
214                           .show();
215                }
216                break;
217        }
218
219        return true;
220    }
221
222    public void viewAttachment() {
223        if (mAttachment.contentUri == null) {
224            LogUtils.e(LOG_TAG, "viewAttachment with null content uri");
225            return;
226        }
227
228        Intent intent = new Intent(Intent.ACTION_VIEW);
229        intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
230                | Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
231        Utils.setIntentDataAndTypeAndNormalize(intent, mAttachment.contentUri,
232                mAttachment.contentType);
233        try {
234            getContext().startActivity(intent);
235        } catch (ActivityNotFoundException e) {
236            // couldn't find activity for View intent
237            LogUtils.e(LOG_TAG, e, "Couldn't find Activity for intent");
238        }
239    }
240
241    private void previewAttachment() {
242        getContext().startActivity(mAttachment.previewIntent);
243    }
244
245    private void setButtonVisible(View button, boolean visible) {
246        button.setVisibility(visible ? VISIBLE : GONE);
247    }
248
249    /**
250     * Update all actions based on current downloading state.
251     */
252    private void updateActions() {
253        // If the progress dialog is visible, skip any of the updating
254        if (mActionHandler.isProgressDialogVisible() || mActionHandler.dialogJustClosed()) {
255            return;
256        }
257
258        // To avoid visibility state transition bugs, every button's visibility should be touched
259        // once by this routine.
260
261        final boolean isDownloading = mAttachment.isDownloading();
262        final boolean canSave = mAttachment.canSave();
263        final boolean canPreview = (mAttachment.previewIntent != null);
264        final boolean isInstallable = MimeType.isInstallable(mAttachment.contentType);
265
266        setButtonVisible(mCancelButton, isDownloading);
267        setButtonVisible(mOverflowButton, !isDownloading && !isInstallable &&
268                (canSave || canPreview));
269    }
270
271    public void onUpdateStatus() {
272        if (mAttachment.state == AttachmentState.FAILED) {
273            mSubTitle.setText(getResources().getString(R.string.download_failed));
274        } else {
275            updateSubtitleText(mAttachment.isSavedToExternal() ?
276                    getResources().getString(R.string.saved) : null);
277        }
278    }
279
280    public void updateProgress(boolean showProgress) {
281        if (mAttachment.isDownloading()) {
282            mProgress.setProgress(mAttachment.downloadedSize);
283            mProgress.setIndeterminate(!showProgress);
284            mProgress.setVisibility(VISIBLE);
285        } else {
286            mProgress.setVisibility(INVISIBLE);
287        }
288    }
289
290    private void updateSubtitleText(String prefix) {
291        // TODO: make this a formatted resource when we have a UX design.
292        // not worth translation right now.
293        StringBuilder sb = new StringBuilder();
294        if (prefix != null) {
295            sb.append(prefix);
296        }
297        sb.append(mAttachmentSizeText);
298        sb.append(' ');
299        sb.append(mDisplayType);
300        mSubTitle.setText(sb.toString());
301    }
302
303    @Override
304    public List<Attachment> getAttachments() {
305        return null;
306    }
307}
308