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