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