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