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