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