2  *   ownCloud Android client application 
   4  *   @author David A. Velasco 
   5  *   Copyright (C) 2015 ownCloud Inc. 
   7  *   This program is free software: you can redistribute it and/or modify 
   8  *   it under the terms of the GNU General Public License version 2, 
   9  *   as published by the Free Software Foundation. 
  11  *   This program is distributed in the hope that it will be useful, 
  12  *   but WITHOUT ANY WARRANTY; without even the implied warranty of 
  13  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
  14  *   GNU General Public License for more details. 
  16  *   You should have received a copy of the GNU General Public License 
  17  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. 
  20 package com
.owncloud
.android
.ui
.preview
; 
  22 import java
.lang
.ref
.WeakReference
; 
  24 import android
.accounts
.Account
; 
  25 import android
.annotation
.SuppressLint
; 
  26 import android
.app
.Activity
; 
  27 import android
.graphics
.Bitmap
; 
  28 import android
.graphics
.Point
; 
  29 import android
.os
.AsyncTask
; 
  30 import android
.os
.Bundle
; 
  31 import android
.support
.v4
.app
.FragmentStatePagerAdapter
; 
  32 import android
.view
.LayoutInflater
; 
  33 import android
.view
.Menu
; 
  34 import android
.view
.MenuInflater
; 
  35 import android
.view
.MenuItem
; 
  36 import android
.view
.View
; 
  37 import android
.view
.View
.OnClickListener
; 
  38 import android
.view
.ViewGroup
; 
  39 import android
.widget
.ImageView
; 
  40 import android
.widget
.ProgressBar
; 
  41 import android
.widget
.TextView
; 
  43 import com
.owncloud
.android
.R
; 
  44 import com
.owncloud
.android
.datamodel
.OCFile
; 
  45 import com
.owncloud
.android
.files
.FileMenuFilter
; 
  46 import com
.owncloud
.android
.lib
.common
.utils
.Log_OC
; 
  47 import com
.owncloud
.android
.ui
.dialog
.ConfirmationDialogFragment
; 
  48 import com
.owncloud
.android
.ui
.dialog
.RemoveFileDialogFragment
; 
  49 import com
.owncloud
.android
.ui
.fragment
.FileFragment
; 
  50 import com
.owncloud
.android
.utils
.BitmapUtils
; 
  51 import com
.owncloud
.android
.utils
.DisplayUtils
; 
  53 import third_parties
.michaelOrtiz
.TouchImageViewCustom
; 
  57  * This fragment shows a preview of a downloaded image. 
  59  * Trying to get an instance with a NULL {@link OCFile} will produce an 
  60  * {@link IllegalStateException}. 
  62  * If the {@link OCFile} passed is not downloaded, an {@link IllegalStateException} is generated on 
  65 public class PreviewImageFragment 
extends FileFragment 
{ 
  67     public static final String EXTRA_FILE 
= "FILE"; 
  69     private static final String ARG_FILE 
= "FILE"; 
  70     private static final String ARG_IGNORE_FIRST 
= "IGNORE_FIRST"; 
  72     private TouchImageViewCustom mImageView
; 
  73     private TextView mMessageView
; 
  74     private ProgressBar mProgressWheel
; 
  76     public Bitmap mBitmap 
= null
; 
  78     private static final String TAG 
= PreviewImageFragment
.class.getSimpleName(); 
  80     private boolean mIgnoreFirstSavedState
; 
  82     private LoadBitmapTask mLoadBitmapTask 
= null
; 
  86      * Public factory method to create a new fragment that previews an image. 
  88      * Android strongly recommends keep the empty constructor of fragments as the only public 
  90      * use {@link #setArguments(Bundle)} to set the needed arguments. 
  92      * This method hides to client objects the need of doing the construction in two steps. 
  94      * @param imageFile                 An {@link OCFile} to preview as an image in the fragment 
  95      * @param ignoreFirstSavedState     Flag to work around an unexpected behaviour of 
  96      *                                  {@link FragmentStatePagerAdapter} 
  97      *                                  ; TODO better solution 
  99     public static PreviewImageFragment 
newInstance(OCFile imageFile
, boolean ignoreFirstSavedState
){ 
 100         PreviewImageFragment frag 
= new PreviewImageFragment(); 
 101         Bundle args 
= new Bundle(); 
 102         args
.putParcelable(ARG_FILE
, imageFile
); 
 103         args
.putBoolean(ARG_IGNORE_FIRST
, ignoreFirstSavedState
); 
 104         frag
.setArguments(args
); 
 111      *  Creates an empty fragment for image previews. 
 113      *  MUST BE KEPT: the system uses it when tries to reinstantiate a fragment automatically 
 114      *  (for instance, when the device is turned a aside). 
 116      *  DO NOT CALL IT: an {@link OCFile} and {@link Account} must be provided for a successful 
 119     public PreviewImageFragment() { 
 120         mIgnoreFirstSavedState 
= false
; 
 128     public void onCreate(Bundle savedInstanceState
) { 
 129         super.onCreate(savedInstanceState
); 
 130         Bundle args 
= getArguments(); 
 131         setFile((OCFile
)args
.getParcelable(ARG_FILE
)); 
 132             // TODO better in super, but needs to check ALL the class extending FileFragment; 
 135         mIgnoreFirstSavedState 
= args
.getBoolean(ARG_IGNORE_FIRST
); 
 136         setHasOptionsMenu(true
); 
 144     public View 
onCreateView(LayoutInflater inflater
, ViewGroup container
, 
 145                              Bundle savedInstanceState
) { 
 146         super.onCreateView(inflater
, container
, savedInstanceState
); 
 147         View view 
= inflater
.inflate(R
.layout
.preview_image_fragment
, container
, false
); 
 148         mImageView 
= (TouchImageViewCustom
) view
.findViewById(R
.id
.image
); 
 149         mImageView
.setVisibility(View
.GONE
); 
 150         mImageView
.setOnClickListener(new OnClickListener() { 
 152             public void onClick(View v
) { 
 153                 ((PreviewImageActivity
) getActivity()).toggleFullScreen(); 
 157         mMessageView 
= (TextView
)view
.findViewById(R
.id
.message
); 
 158         mMessageView
.setVisibility(View
.GONE
); 
 159         mProgressWheel 
= (ProgressBar
)view
.findViewById(R
.id
.progressWheel
); 
 160         mProgressWheel
.setVisibility(View
.VISIBLE
); 
 168     public void onActivityCreated(Bundle savedInstanceState
) { 
 169         super.onActivityCreated(savedInstanceState
); 
 170         if (savedInstanceState 
!= null
) { 
 171             if (!mIgnoreFirstSavedState
) { 
 172                 OCFile file 
= savedInstanceState
.getParcelable(PreviewImageFragment
.EXTRA_FILE
); 
 175                 mIgnoreFirstSavedState 
= false
; 
 178         if (getFile() == null
) { 
 179             throw new IllegalStateException("Instanced with a NULL OCFile"); 
 181         if (!getFile().isDown()) { 
 182             throw new IllegalStateException("There is no local file to preview"); 
 191     public void onSaveInstanceState(Bundle outState
) { 
 192         super.onSaveInstanceState(outState
); 
 193         outState
.putParcelable(PreviewImageFragment
.EXTRA_FILE
, getFile()); 
 198     public void onStart() { 
 200         if (getFile() != null
) { 
 201             mLoadBitmapTask 
= new LoadBitmapTask(mImageView
, mMessageView
, mProgressWheel
); 
 202             //mLoadBitmapTask.execute(new String[]{getFile().getStoragePath()}); 
 203             mLoadBitmapTask
.execute(getFile().getStoragePath()); 
 209     public void onStop() { 
 210         Log_OC
.d(TAG
, "onStop starts"); 
 211         if (mLoadBitmapTask 
!= null
) { 
 212             mLoadBitmapTask
.cancel(true
); 
 213             mLoadBitmapTask 
= null
; 
 222     public void onCreateOptionsMenu(Menu menu
, MenuInflater inflater
) { 
 223         super.onCreateOptionsMenu(menu
, inflater
); 
 224         inflater
.inflate(R
.menu
.file_actions_menu
, menu
); 
 231     public void onPrepareOptionsMenu(Menu menu
) { 
 232         super.onPrepareOptionsMenu(menu
); 
 234         if (mContainerActivity
.getStorageManager() != null
) { 
 236             setFile(mContainerActivity
.getStorageManager().getFileById(getFile().getFileId())); 
 238             FileMenuFilter mf 
= new FileMenuFilter( 
 240                 mContainerActivity
.getStorageManager().getAccount(), 
 247         // additional restriction for this fragment  
 248         // TODO allow renaming in PreviewImageFragment 
 249         MenuItem item 
= menu
.findItem(R
.id
.action_rename_file
); 
 251             item
.setVisible(false
); 
 252             item
.setEnabled(false
); 
 255         // additional restriction for this fragment  
 256         // TODO allow refresh file in PreviewImageFragment 
 257         item 
= menu
.findItem(R
.id
.action_sync_file
); 
 259             item
.setVisible(false
); 
 260             item
.setEnabled(false
); 
 263         // additional restriction for this fragment 
 264         item 
= menu
.findItem(R
.id
.action_move
); 
 266             item
.setVisible(false
); 
 267             item
.setEnabled(false
); 
 270         // additional restriction for this fragment 
 271         item 
= menu
.findItem(R
.id
.action_copy
); 
 273             item
.setVisible(false
); 
 274             item
.setEnabled(false
); 
 284     public boolean onOptionsItemSelected(MenuItem item
) { 
 285         switch (item
.getItemId()) { 
 286             case R
.id
.action_share_file
: { 
 287                 mContainerActivity
.getFileOperationsHelper().shareFileWithLink(getFile()); 
 290             case R
.id
.action_unshare_file
: { 
 291                 mContainerActivity
.getFileOperationsHelper().unshareFileWithLink(getFile()); 
 294             case R
.id
.action_open_file_with
: { 
 298             case R
.id
.action_remove_file
: { 
 299                 RemoveFileDialogFragment dialog 
= RemoveFileDialogFragment
.newInstance(getFile()); 
 300                 dialog
.show(getFragmentManager(), ConfirmationDialogFragment
.FTAG_CONFIRMATION
); 
 303             case R
.id
.action_see_details
: { 
 307             case R
.id
.action_send_file
: { 
 308                 mContainerActivity
.getFileOperationsHelper().sendDownloadedFile(getFile()); 
 311             case R
.id
.action_sync_file
: { 
 312                 mContainerActivity
.getFileOperationsHelper().syncFile(getFile()); 
 315             case R
.id
.action_favorite_file
:{ 
 316                 mContainerActivity
.getFileOperationsHelper().toggleFavorite(getFile(), true
); 
 319             case R
.id
.action_unfavorite_file
:{ 
 320                 mContainerActivity
.getFileOperationsHelper().toggleFavorite(getFile(), false
); 
 329     private void seeDetails() { 
 330         mContainerActivity
.showDetails(getFile()); 
 335     public void onResume() { 
 341     public void onPause() { 
 346     public void onDestroy() { 
 347         if (mBitmap 
!= null
) { 
 350                 // putting this in onStop() is just the same; the fragment is always destroyed by 
 351                 // {@link FragmentStatePagerAdapter} when the fragment in swiped further than the 
 352                 // valid offscreen distance, and onStop() is never called before than that 
 359      * Opens the previewed image with an external application. 
 361     private void openFile() { 
 362         mContainerActivity
.getFileOperationsHelper().openFile(getFile()); 
 367     private class LoadBitmapTask 
extends AsyncTask
<String
, Void
, Bitmap
> { 
 370          * Weak reference to the target {@link ImageView} where the bitmap will be loaded into. 
 372          * Using a weak reference will avoid memory leaks if the target ImageView is retired from 
 373          * memory before the load finishes. 
 375         private final WeakReference
<ImageViewCustom
> mImageViewRef
; 
 378          * Weak reference to the target {@link TextView} where error messages will be written. 
 380          * Using a weak reference will avoid memory leaks if the target ImageView is retired from 
 381          * memory before the load finishes. 
 383         private final WeakReference
<TextView
> mMessageViewRef
; 
 387          * Weak reference to the target {@link ProgressBar} shown while the load is in progress. 
 389          * Using a weak reference will avoid memory leaks if the target ImageView is retired from 
 390          * memory before the load finishes. 
 392         private final WeakReference
<ProgressBar
> mProgressWheelRef
; 
 396          * Error message to show when a load fails 
 398         private int mErrorMessageId
; 
 404          * @param imageView Target {@link ImageView} where the bitmap will be loaded into. 
 406         public LoadBitmapTask(ImageViewCustom imageView
, TextView messageView
, 
 407                               ProgressBar progressWheel
) { 
 408             mImageViewRef 
= new WeakReference
<ImageViewCustom
>(imageView
); 
 409             mMessageViewRef 
= new WeakReference
<TextView
>(messageView
); 
 410             mProgressWheelRef 
= new WeakReference
<ProgressBar
>(progressWheel
); 
 415         protected Bitmap 
doInBackground(String
... params
) { 
 416             Bitmap result 
= null
; 
 417             if (params
.length 
!= 1) return null
; 
 418             String storagePath 
= params
[0]; 
 421                 int maxDownScale 
= 3;   // could be a parameter passed to doInBackground(...) 
 422                 Point screenSize 
= DisplayUtils
.getScreenSize(getActivity()); 
 423                 int minWidth 
= screenSize
.x
; 
 424                 int minHeight 
= screenSize
.y
; 
 425                 for (int i 
= 0; i 
< maxDownScale 
&& result 
== null
; i
++) { 
 426                     if (isCancelled()) return null
; 
 428                         result 
= BitmapUtils
.decodeSampledBitmapFromFile(storagePath
, minWidth
, 
 431                         if (isCancelled()) return result
; 
 433                         if (result 
== null
) { 
 434                             mErrorMessageId 
= R
.string
.preview_image_error_unknown_format
; 
 435                             Log_OC
.e(TAG
, "File could not be loaded as a bitmap: " + storagePath
); 
 438                             // Rotate image, obeying exif tag. 
 439                             result 
= BitmapUtils
.rotateImage(result
, storagePath
); 
 442                     } catch (OutOfMemoryError e
) { 
 443                         mErrorMessageId 
= R
.string
.common_error_out_memory
; 
 444                         if (i 
< maxDownScale 
- 1) { 
 445                             Log_OC
.w(TAG
, "Out of memory rendering file " + storagePath 
+ 
 447                             minWidth 
= minWidth 
/ 2; 
 448                             minHeight 
= minHeight 
/ 2; 
 451                             Log_OC
.w(TAG
, "Out of memory rendering file " + storagePath 
+ 
 454                         if (result 
!= null
) { 
 461             } catch (NoSuchFieldError e
) { 
 462                 mErrorMessageId 
= R
.string
.common_error_unknown
; 
 463                 Log_OC
.e(TAG
, "Error from access to unexisting field despite protection; file " 
 466             } catch (Throwable t
) { 
 467                 mErrorMessageId 
= R
.string
.common_error_unknown
; 
 468                 Log_OC
.e(TAG
, "Unexpected error loading " + getFile().getStoragePath(), t
); 
 476         protected void onCancelled(Bitmap result
) { 
 477             if (result 
!= null
) { 
 483         protected void onPostExecute(Bitmap result
) { 
 485             if (result 
!= null
) { 
 486                 showLoadedImage(result
); 
 491             if (result 
!= null 
&& mBitmap 
!= result
)  { 
 492                 // unused bitmap, release it! (just in case) 
 497         @SuppressLint("InlinedApi") 
 498         private void showLoadedImage(Bitmap result
) { 
 499             final ImageViewCustom imageView 
= mImageViewRef
.get(); 
 500             if (imageView 
!= null
) { 
 501                 Log_OC
.d(TAG
, "Showing image with resolution " + result
.getWidth() + "x" + 
 503                 imageView
.setImageBitmap(result
); 
 504                 imageView
.setVisibility(View
.VISIBLE
); 
 505                 mBitmap  
= result
;  // needs to be kept for recycling when not useful 
 508             final TextView messageView 
= mMessageViewRef
.get(); 
 509             if (messageView 
!= null
) { 
 510                 messageView
.setVisibility(View
.GONE
); 
 511             } // else , silently finish, the fragment was destroyed 
 514         private void showErrorMessage() { 
 515             final ImageView imageView 
= mImageViewRef
.get(); 
 516             if (imageView 
!= null
) { 
 517                 // shows the default error icon 
 518                 imageView
.setVisibility(View
.VISIBLE
); 
 519             } // else , silently finish, the fragment was destroyed 
 521             final TextView messageView 
= mMessageViewRef
.get(); 
 522             if (messageView 
!= null
) { 
 523                 messageView
.setText(mErrorMessageId
); 
 524                 messageView
.setVisibility(View
.VISIBLE
); 
 525             } // else , silently finish, the fragment was destroyed 
 528         private void hideProgressWheel() { 
 529             final ProgressBar progressWheel 
= mProgressWheelRef
.get(); 
 530             if (progressWheel 
!= null
) { 
 531                 progressWheel
.setVisibility(View
.GONE
); 
 538      * Helper method to test if an {@link OCFile} can be passed to a {@link PreviewImageFragment} 
 541      * @param file      File to test if can be previewed. 
 542      * @return          'True' if the file can be handled by the fragment. 
 544     public static boolean canBePreviewed(OCFile file
) { 
 545         return (file 
!= null 
&& file
.isImage()); 
 550      * Finishes the preview 
 552     private void finish() { 
 553         Activity container 
= getActivity(); 
 557     public TouchImageViewCustom 
getImageView() {