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