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