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 18 package com.android.mail.photo; 19 20 import android.app.ActionBar; 21 import android.content.Context; 22 import android.database.Cursor; 23 import android.net.Uri; 24 import android.os.Bundle; 25 import android.os.Parcelable; 26 import android.view.Menu; 27 import android.view.MenuInflater; 28 import android.view.MenuItem; 29 import android.view.View; 30 import android.view.Window; 31 import android.widget.ImageView; 32 import android.widget.TextView; 33 34 import com.android.ex.photo.Intents; 35 import com.android.ex.photo.PhotoViewActivity; 36 import com.android.ex.photo.fragments.PhotoViewFragment; 37 import com.android.ex.photo.views.ProgressBarWrapper; 38 import com.android.mail.R; 39 import com.android.mail.browse.AttachmentActionHandler; 40 import com.android.mail.providers.Attachment; 41 import com.android.mail.providers.UIProvider; 42 import com.android.mail.providers.UIProvider.AttachmentDestination; 43 import com.android.mail.providers.UIProvider.AttachmentState; 44 import com.android.mail.utils.AttachmentUtils; 45 import com.android.mail.utils.Utils; 46 import com.google.common.collect.Lists; 47 48 import java.util.ArrayList; 49 import java.util.List; 50 51 /** 52 * Derives from {@link PhotoViewActivity} to allow customization 53 * to the {@link ActionBar} from the default implementation. 54 */ 55 public class MailPhotoViewActivity extends PhotoViewActivity { 56 private MenuItem mSaveItem; 57 private MenuItem mSaveAllItem; 58 private MenuItem mShareItem; 59 private MenuItem mShareAllItem; 60 /** 61 * Only for attachments that are currently downloading. Attachments that failed show the 62 * retry button. 63 */ 64 private MenuItem mDownloadAgainItem; 65 private AttachmentActionHandler mActionHandler; 66 private Menu mMenu; 67 68 /** 69 * Start a new MailPhotoViewActivity to view the given images. 70 * 71 * @param imageListUri The uri to query for the images that you want to view. The resulting 72 * cursor must have the columns as defined in 73 * {@link com.android.ex.photo.provider.PhotoContract.PhotoViewColumns}. 74 * @param photoIndex The index of the photo to show first. 75 */ 76 public static void startMailPhotoViewActivity(final Context context, final Uri imageListUri, 77 final int photoIndex) { 78 final Intents.PhotoViewIntentBuilder builder = 79 Intents.newPhotoViewIntentBuilder(context, MailPhotoViewActivity.class); 80 builder 81 .setPhotosUri(imageListUri.toString()) 82 .setProjection(UIProvider.ATTACHMENT_PROJECTION) 83 .setPhotoIndex(photoIndex); 84 85 context.startActivity(builder.build()); 86 } 87 88 /** 89 * Start a new MailPhotoViewActivity to view the given images. 90 * 91 * @param imageListUri The uri to query for the images that you want to view. The resulting 92 * cursor must have the columns as defined in 93 * {@link com.android.ex.photo.provider.PhotoContract.PhotoViewColumns}. 94 * @param initialPhotoUri The uri of the photo to show first. 95 */ 96 public static void startMailPhotoViewActivity(final Context context, final Uri imageListUri, 97 final String initialPhotoUri) { 98 final Intents.PhotoViewIntentBuilder builder = 99 Intents.newPhotoViewIntentBuilder(context, MailPhotoViewActivity.class); 100 builder 101 .setPhotosUri(imageListUri.toString()) 102 .setProjection(UIProvider.ATTACHMENT_PROJECTION) 103 .setInitialPhotoUri(initialPhotoUri); 104 105 context.startActivity(builder.build()); 106 } 107 108 @Override 109 protected void onCreate(Bundle savedInstanceState) { 110 requestWindowFeature(Window.FEATURE_PROGRESS); 111 super.onCreate(savedInstanceState); 112 113 mActionHandler = new AttachmentActionHandler(this, null); 114 mActionHandler.initialize(getFragmentManager()); 115 } 116 117 @Override 118 public boolean onCreateOptionsMenu(Menu menu) { 119 MenuInflater inflater = getMenuInflater(); 120 121 inflater.inflate(R.menu.photo_view_menu, menu); 122 mMenu = menu; 123 124 mSaveItem = mMenu.findItem(R.id.menu_save); 125 mSaveAllItem = mMenu.findItem(R.id.menu_save_all); 126 mShareItem = mMenu.findItem(R.id.menu_share); 127 mShareAllItem = mMenu.findItem(R.id.menu_share_all); 128 mDownloadAgainItem = mMenu.findItem(R.id.menu_download_again); 129 130 return true; 131 } 132 133 @Override 134 public boolean onPrepareOptionsMenu(Menu menu) { 135 updateActionItems(); 136 return true; 137 } 138 139 /** 140 * Updates the action items to tweak their visibility in case 141 * there is functionality that is not relevant (eg, the Save 142 * button should not appear if the photo has already been saved). 143 */ 144 @Override 145 protected void updateActionItems() { 146 final boolean runningJellyBeanOrLater = Utils.isRunningJellybeanOrLater(); 147 final Attachment attachment = getCurrentAttachment(); 148 149 if (attachment != null && mSaveItem != null && mShareItem != null) { 150 mSaveItem.setEnabled(!attachment.isDownloading() 151 && attachment.canSave() && !attachment.isSavedToExternal()); 152 mShareItem.setEnabled(attachment.canShare()); 153 mDownloadAgainItem.setEnabled(attachment.canSave() && attachment.isDownloading()); 154 } else { 155 if (mMenu != null) { 156 mMenu.setGroupEnabled(R.id.photo_view_menu_group, false); 157 } 158 return; 159 } 160 161 List<Attachment> attachments = getAllAttachments(); 162 if (attachments != null) { 163 boolean enabled = false; 164 for (final Attachment a : attachments) { 165 // If one attachment can be saved, enable save all 166 if (!a.isDownloading() && a.canSave() && !a.isSavedToExternal()) { 167 enabled = true; 168 break; 169 } 170 } 171 mSaveAllItem.setEnabled(enabled); 172 173 // all attachments must be present to be able to share all 174 enabled = true; 175 for (final Attachment a : attachments) { 176 if (!a.canShare()) { 177 enabled = false; 178 break; 179 } 180 } 181 mShareAllItem.setEnabled(enabled); 182 } 183 184 // Turn off the functionality that only works on JellyBean. 185 if (!runningJellyBeanOrLater) { 186 mShareItem.setVisible(false); 187 mShareAllItem.setVisible(false); 188 } 189 } 190 191 @Override 192 public boolean onOptionsItemSelected(MenuItem item) { 193 final int itemId = item.getItemId(); 194 if (itemId == android.R.id.home) { 195 // app icon in action bar clicked; go back to conversation 196 finish(); 197 return true; 198 } else if (itemId == R.id.menu_save) { // save the current photo 199 saveAttachment(); 200 return true; 201 } else if (itemId == R.id.menu_save_all) { // save all of the photos 202 saveAllAttachments(); 203 return true; 204 } else if (itemId == R.id.menu_share) { // share the current photo 205 shareAttachment(); 206 return true; 207 } else if (itemId == R.id.menu_share_all) { // share all of the photos 208 shareAllAttachments(); 209 return true; 210 } else if (itemId == R.id.menu_download_again) { // redownload the current photo 211 redownloadAttachment(); 212 return true; 213 } else { 214 return super.onOptionsItemSelected(item); 215 } 216 } 217 218 /** 219 * Adjusts the activity title and subtitle to reflect the image name and size. 220 */ 221 @Override 222 protected void updateActionBar() { 223 super.updateActionBar(); 224 225 final Attachment attachment = getCurrentAttachment(); 226 final ActionBar actionBar = getActionBar(); 227 final String size = AttachmentUtils.convertToHumanReadableSize(this, attachment.size); 228 229 // update the status 230 // There are 3 states 231 // 1. Saved, Attachment Size 232 // 2. Saving... 233 // 3. Default, Attachment Size 234 if (attachment.isSavedToExternal()) { 235 actionBar.setSubtitle(getResources().getString(R.string.saved, size)); 236 } else if (attachment.isDownloading() && 237 attachment.destination == AttachmentDestination.EXTERNAL) { 238 actionBar.setSubtitle(R.string.saving); 239 } else { 240 actionBar.setSubtitle(size); 241 } 242 updateActionItems(); 243 } 244 245 @Override 246 public void onFragmentVisible(PhotoViewFragment fragment) { 247 super.onFragmentVisible(fragment); 248 final Attachment attachment = getCurrentAttachment(); 249 if (attachment.state == AttachmentState.PAUSED) { 250 mActionHandler.setAttachment(attachment); 251 mActionHandler.startDownloadingAttachment(attachment.destination); 252 } 253 } 254 255 @Override 256 public void onCursorChanged(PhotoViewFragment fragment, Cursor cursor) { 257 super.onCursorChanged(fragment, cursor); 258 updateProgressAndEmptyViews(fragment, new Attachment(cursor)); 259 } 260 261 /** 262 * Updates the empty views of the fragment based upon the current 263 * state of the attachment. 264 * @param fragment the current fragment 265 */ 266 private void updateProgressAndEmptyViews( 267 final PhotoViewFragment fragment, final Attachment attachment) { 268 final ProgressBarWrapper progressBar = fragment.getPhotoProgressBar(); 269 final TextView emptyText = fragment.getEmptyText(); 270 final ImageView retryButton = fragment.getRetryButton(); 271 272 // update the progress 273 if (attachment.shouldShowProgress()) { 274 progressBar.setMax(attachment.size); 275 progressBar.setProgress(attachment.downloadedSize); 276 progressBar.setIndeterminate(false); 277 } else if (fragment.isProgressBarNeeded()) { 278 progressBar.setIndeterminate(true); 279 } 280 281 // If the download failed, show the empty text and retry button 282 if (attachment.isDownloadFailed()) { 283 emptyText.setText(R.string.photo_load_failed); 284 emptyText.setVisibility(View.VISIBLE); 285 retryButton.setVisibility(View.VISIBLE); 286 retryButton.setOnClickListener(new View.OnClickListener() { 287 @Override 288 public void onClick(View view) { 289 redownloadAttachment(); 290 emptyText.setVisibility(View.GONE); 291 retryButton.setVisibility(View.GONE); 292 } 293 }); 294 progressBar.setVisibility(View.GONE); 295 } 296 } 297 298 /** 299 * Save the current attachment. 300 */ 301 private void saveAttachment() { 302 saveAttachment(getCurrentAttachment()); 303 } 304 305 /** 306 * Redownloads the attachment. 307 */ 308 private void redownloadAttachment() { 309 final Attachment attachment = getCurrentAttachment(); 310 if (attachment != null && attachment.canSave()) { 311 // REDOWNLOADING command is only for attachments that are finished or failed. 312 // For an attachment that is downloading (or paused in the DownloadManager), we need to 313 // cancel it first. 314 mActionHandler.setAttachment(attachment); 315 mActionHandler.cancelAttachment(); 316 mActionHandler.startDownloadingAttachment(attachment.destination); 317 } 318 } 319 320 /** 321 * Saves the attachment. 322 * @param attachment the attachment to save. 323 */ 324 private void saveAttachment(final Attachment attachment) { 325 if (attachment != null && attachment.canSave()) { 326 mActionHandler.setAttachment(attachment); 327 mActionHandler.startDownloadingAttachment(AttachmentDestination.EXTERNAL); 328 } 329 } 330 331 /** 332 * Save all of the attachments in the cursor. 333 */ 334 private void saveAllAttachments() { 335 Cursor cursor = getCursorAtProperPosition(); 336 337 if (cursor == null) { 338 return; 339 } 340 341 int i = -1; 342 while (cursor.moveToPosition(++i)) { 343 saveAttachment(new Attachment(cursor)); 344 } 345 } 346 347 /** 348 * Share the current attachment. 349 */ 350 private void shareAttachment() { 351 shareAttachment(getCurrentAttachment()); 352 } 353 354 /** 355 * Shares the attachment 356 * @param attachment the attachment to share 357 */ 358 private void shareAttachment(final Attachment attachment) { 359 if (attachment != null) { 360 mActionHandler.setAttachment(attachment); 361 mActionHandler.shareAttachment(); 362 } 363 } 364 365 /** 366 * Share all of the attachments in the cursor. 367 */ 368 private void shareAllAttachments() { 369 Cursor cursor = getCursorAtProperPosition(); 370 371 if (cursor == null) { 372 return; 373 } 374 375 ArrayList<Parcelable> uris = new ArrayList<Parcelable>(); 376 int i = -1; 377 while (cursor.moveToPosition(++i)) { 378 uris.add(Utils.normalizeUri(new Attachment(cursor).contentUri)); 379 } 380 381 mActionHandler.shareAttachments(uris); 382 } 383 384 /** 385 * Helper method to get the currently visible attachment. 386 */ 387 protected Attachment getCurrentAttachment() { 388 final Cursor cursor = getCursorAtProperPosition(); 389 390 if (cursor == null) { 391 return null; 392 } 393 394 return new Attachment(cursor); 395 } 396 397 private List<Attachment> getAllAttachments() { 398 final Cursor cursor = getCursor(); 399 400 if (cursor == null || cursor.isClosed() || !cursor.moveToFirst()) { 401 return null; 402 } 403 404 List<Attachment> list = Lists.newArrayList(); 405 do { 406 list.add(new Attachment(cursor)); 407 } while (cursor.moveToNext()); 408 409 return list; 410 } 411 } 412