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