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
.graphics
.drawable
.Drawable
; 
  30 import android
.os
.AsyncTask
; 
  31 import android
.os
.Bundle
; 
  32 import android
.support
.v4
.app
.FragmentStatePagerAdapter
; 
  33 import android
.view
.LayoutInflater
; 
  34 import android
.view
.Menu
; 
  35 import android
.view
.MenuInflater
; 
  36 import android
.view
.MenuItem
; 
  37 import android
.view
.View
; 
  38 import android
.view
.View
.OnClickListener
; 
  39 import android
.view
.ViewGroup
; 
  40 import android
.widget
.ImageView
; 
  41 import android
.widget
.ProgressBar
; 
  42 import android
.widget
.TextView
; 
  44 import com
.owncloud
.android
.R
; 
  45 import com
.owncloud
.android
.datamodel
.OCFile
; 
  46 import com
.owncloud
.android
.files
.FileMenuFilter
; 
  47 import com
.owncloud
.android
.lib
.common
.utils
.Log_OC
; 
  48 import com
.owncloud
.android
.ui
.dialog
.ConfirmationDialogFragment
; 
  49 import com
.owncloud
.android
.ui
.dialog
.RemoveFileDialogFragment
; 
  50 import com
.owncloud
.android
.ui
.fragment
.FileFragment
; 
  51 import com
.owncloud
.android
.utils
.BitmapUtils
; 
  52 import com
.owncloud
.android
.utils
.DisplayUtils
; 
  54 import third_parties
.michaelOrtiz
.TouchImageViewCustom
; 
  58  * This fragment shows a preview of a downloaded image. 
  60  * Trying to get an instance with a NULL {@link OCFile} will produce an 
  61  * {@link IllegalStateException}. 
  63  * If the {@link OCFile} passed is not downloaded, an {@link IllegalStateException} is generated on 
  66 public class PreviewImageFragment 
extends FileFragment 
{ 
  68     public static final String EXTRA_FILE 
= "FILE"; 
  70     private static final String ARG_FILE 
= "FILE"; 
  71     private static final String ARG_IGNORE_FIRST 
= "IGNORE_FIRST"; 
  73     private TouchImageViewCustom mImageView
; 
  74     private TextView mMessageView
; 
  75     private ProgressBar mProgressWheel
; 
  77     public Bitmap mBitmap 
= null
; 
  79     private static final String TAG 
= PreviewImageFragment
.class.getSimpleName(); 
  81     private boolean mIgnoreFirstSavedState
; 
  83     private LoadBitmapTask mLoadBitmapTask 
= null
; 
  87      * Public factory method to create a new fragment that previews an image. 
  89      * Android strongly recommends keep the empty constructor of fragments as the only public 
  91      * use {@link #setArguments(Bundle)} to set the needed arguments. 
  93      * This method hides to client objects the need of doing the construction in two steps. 
  95      * @param imageFile                 An {@link OCFile} to preview as an image in the fragment 
  96      * @param ignoreFirstSavedState     Flag to work around an unexpected behaviour of 
  97      *                                  {@link FragmentStatePagerAdapter} 
  98      *                                  ; TODO better solution 
 100     public static PreviewImageFragment 
newInstance(OCFile imageFile
, boolean ignoreFirstSavedState
){ 
 101         PreviewImageFragment frag 
= new PreviewImageFragment(); 
 102         Bundle args 
= new Bundle(); 
 103         args
.putParcelable(ARG_FILE
, imageFile
); 
 104         args
.putBoolean(ARG_IGNORE_FIRST
, ignoreFirstSavedState
); 
 105         frag
.setArguments(args
); 
 112      *  Creates an empty fragment for image previews. 
 114      *  MUST BE KEPT: the system uses it when tries to reinstantiate a fragment automatically 
 115      *  (for instance, when the device is turned a aside). 
 117      *  DO NOT CALL IT: an {@link OCFile} and {@link Account} must be provided for a successful 
 120     public PreviewImageFragment() { 
 121         mIgnoreFirstSavedState 
= false
; 
 129     public void onCreate(Bundle savedInstanceState
) { 
 130         super.onCreate(savedInstanceState
); 
 131         Bundle args 
= getArguments(); 
 132         setFile((OCFile
)args
.getParcelable(ARG_FILE
)); 
 133             // TODO better in super, but needs to check ALL the class extending FileFragment; 
 136         mIgnoreFirstSavedState 
= args
.getBoolean(ARG_IGNORE_FIRST
); 
 137         setHasOptionsMenu(true
); 
 145     public View 
onCreateView(LayoutInflater inflater
, ViewGroup container
, 
 146             Bundle savedInstanceState
) { 
 147         super.onCreateView(inflater
, container
, savedInstanceState
); 
 148         View view 
= inflater
.inflate(R
.layout
.preview_image_fragment
, container
, false
); 
 149         mImageView 
= (TouchImageViewCustom
) view
.findViewById(R
.id
.image
); 
 150         mImageView
.setVisibility(View
.GONE
); 
 151         mImageView
.setOnClickListener(new OnClickListener() { 
 153             public void onClick(View v
) { 
 154                 ((PreviewImageActivity
) getActivity()).toggleFullScreen(); 
 158         mMessageView 
= (TextView
)view
.findViewById(R
.id
.message
); 
 159         mMessageView
.setVisibility(View
.GONE
); 
 160         mProgressWheel 
= (ProgressBar
)view
.findViewById(R
.id
.progressWheel
); 
 161         mProgressWheel
.setVisibility(View
.VISIBLE
); 
 169     public void onActivityCreated(Bundle savedInstanceState
) { 
 170         super.onActivityCreated(savedInstanceState
); 
 171         if (savedInstanceState 
!= null
) { 
 172             if (!mIgnoreFirstSavedState
) { 
 173                 OCFile file 
= savedInstanceState
.getParcelable(PreviewImageFragment
.EXTRA_FILE
); 
 176                 mIgnoreFirstSavedState 
= false
; 
 179         if (getFile() == null
) { 
 180             throw new IllegalStateException("Instanced with a NULL OCFile"); 
 182         if (!getFile().isDown()) { 
 183             throw new IllegalStateException("There is no local file to preview"); 
 192     public void onSaveInstanceState(Bundle outState
) { 
 193         super.onSaveInstanceState(outState
); 
 194         outState
.putParcelable(PreviewImageFragment
.EXTRA_FILE
, getFile()); 
 199     public void onStart() { 
 201         if (getFile() != null
) { 
 202             mLoadBitmapTask 
= new LoadBitmapTask(mImageView
, mMessageView
, mProgressWheel
); 
 203             //mLoadBitmapTask.execute(new String[]{getFile().getStoragePath()}); 
 204 //            mLoadBitmapTask.execute(getFile().getStoragePath()); 
 205             mLoadBitmapTask
.execute(getFile()); 
 211     public void onStop() { 
 212         Log_OC
.d(TAG
, "onStop starts"); 
 213         if (mLoadBitmapTask 
!= null
) { 
 214             mLoadBitmapTask
.cancel(true
); 
 215             mLoadBitmapTask 
= null
; 
 224     public void onCreateOptionsMenu(Menu menu
, MenuInflater inflater
) { 
 225         super.onCreateOptionsMenu(menu
, inflater
); 
 226         inflater
.inflate(R
.menu
.file_actions_menu
, menu
); 
 233     public void onPrepareOptionsMenu(Menu menu
) { 
 234         super.onPrepareOptionsMenu(menu
); 
 236         if (mContainerActivity
.getStorageManager() != null
) { 
 238             setFile(mContainerActivity
.getStorageManager().getFileById(getFile().getFileId())); 
 240             FileMenuFilter mf 
= new FileMenuFilter( 
 242                 mContainerActivity
.getStorageManager().getAccount(), 
 249         // additional restriction for this fragment  
 250         // TODO allow renaming in PreviewImageFragment 
 251         MenuItem item 
= menu
.findItem(R
.id
.action_rename_file
); 
 253             item
.setVisible(false
); 
 254             item
.setEnabled(false
); 
 257         // additional restriction for this fragment  
 258         // TODO allow refresh file in PreviewImageFragment 
 259         item 
= menu
.findItem(R
.id
.action_sync_file
); 
 261             item
.setVisible(false
); 
 262             item
.setEnabled(false
); 
 265         // additional restriction for this fragment 
 266         item 
= menu
.findItem(R
.id
.action_move
); 
 268             item
.setVisible(false
); 
 269             item
.setEnabled(false
); 
 280     public boolean onOptionsItemSelected(MenuItem item
) { 
 281         switch (item
.getItemId()) { 
 282             case R
.id
.action_share_file
: { 
 283                 mContainerActivity
.getFileOperationsHelper().shareFileWithLink(getFile()); 
 286             case R
.id
.action_unshare_file
: { 
 287                 mContainerActivity
.getFileOperationsHelper().unshareFileWithLink(getFile()); 
 290             case R
.id
.action_open_file_with
: { 
 294             case R
.id
.action_remove_file
: { 
 295                 RemoveFileDialogFragment dialog 
= RemoveFileDialogFragment
.newInstance(getFile()); 
 296                 dialog
.show(getFragmentManager(), ConfirmationDialogFragment
.FTAG_CONFIRMATION
); 
 299             case R
.id
.action_see_details
: { 
 303             case R
.id
.action_send_file
: { 
 304                 mContainerActivity
.getFileOperationsHelper().sendDownloadedFile(getFile()); 
 307             case R
.id
.action_sync_file
: { 
 308                 mContainerActivity
.getFileOperationsHelper().syncFile(getFile()); 
 311             case R
.id
.action_favorite_file
:{ 
 312                 mContainerActivity
.getFileOperationsHelper().toggleFavorite(getFile(), true
); 
 315             case R
.id
.action_unfavorite_file
:{ 
 316                 mContainerActivity
.getFileOperationsHelper().toggleFavorite(getFile(), false
); 
 325     private void seeDetails() { 
 326         mContainerActivity
.showDetails(getFile());         
 331     public void onResume() { 
 337     public void onPause() { 
 342     public void onDestroy() { 
 343         if (mBitmap 
!= null
) { 
 346                 // putting this in onStop() is just the same; the fragment is always destroyed by 
 347                 // {@link FragmentStatePagerAdapter} when the fragment in swiped further than the 
 348                 // valid offscreen distance, and onStop() is never called before than that 
 355      * Opens the previewed image with an external application. 
 357     private void openFile() { 
 358         mContainerActivity
.getFileOperationsHelper().openFile(getFile()); 
 363     private class LoadBitmapTask 
extends AsyncTask
<OCFile
, Void
, LoadImage
> { 
 366          * Weak reference to the target {@link ImageView} where the bitmap will be loaded into. 
 368          * Using a weak reference will avoid memory leaks if the target ImageView is retired from 
 369          * memory before the load finishes. 
 371         private final WeakReference
<ImageViewCustom
> mImageViewRef
; 
 374          * Weak reference to the target {@link TextView} where error messages will be written. 
 376          * Using a weak reference will avoid memory leaks if the target ImageView is retired from 
 377          * memory before the load finishes. 
 379         private final WeakReference
<TextView
> mMessageViewRef
; 
 383          * Weak reference to the target {@link ProgressBar} shown while the load is in progress. 
 385          * Using a weak reference will avoid memory leaks if the target ImageView is retired from 
 386          * memory before the load finishes. 
 388         private final WeakReference
<ProgressBar
> mProgressWheelRef
; 
 392          * Error message to show when a load fails  
 394         private int mErrorMessageId
; 
 400          * @param imageView     Target {@link ImageView} where the bitmap will be loaded into. 
 402         public LoadBitmapTask(ImageViewCustom imageView
, TextView messageView
, 
 403                               ProgressBar progressWheel
) { 
 404             mImageViewRef 
= new WeakReference
<ImageViewCustom
>(imageView
); 
 405             mMessageViewRef 
= new WeakReference
<TextView
>(messageView
); 
 406             mProgressWheelRef 
= new WeakReference
<ProgressBar
>(progressWheel
); 
 410         protected LoadImage 
doInBackground(OCFile
... params
) { 
 411             Bitmap result 
= null
; 
 412             if (params
.length 
!= 1) return null
; 
 413             OCFile ocFile 
= params
[0]; 
 414             String storagePath 
= ocFile
.getStoragePath(); 
 417                 int maxDownScale 
= 3;   // could be a parameter passed to doInBackground(...) 
 418                 Point screenSize 
= DisplayUtils
.getScreenSize(getActivity()); 
 419                 int minWidth 
= screenSize
.x
; 
 420                 int minHeight 
= screenSize
.y
; 
 421                 for (int i 
= 0; i 
< maxDownScale 
&& result 
== null
; i
++) { 
 422                     if (isCancelled()) return null
; 
 424                         result 
= BitmapUtils
.decodeSampledBitmapFromFile(storagePath
, minWidth
, 
 427                         if (isCancelled()) return new LoadImage(result
, ocFile
); 
 429                         if (result 
== null
) { 
 430                             mErrorMessageId 
= R
.string
.preview_image_error_unknown_format
; 
 431                             Log_OC
.e(TAG
, "File could not be loaded as a bitmap: " + storagePath
); 
 434                             // Rotate image, obeying exif tag. 
 435                             result 
= BitmapUtils
.rotateImage(result
, storagePath
); 
 438                     } catch (OutOfMemoryError e
) { 
 439                         mErrorMessageId 
= R
.string
.common_error_out_memory
; 
 440                         if (i 
< maxDownScale 
- 1) { 
 441                             Log_OC
.w(TAG
, "Out of memory rendering file " + storagePath 
+ 
 443                             minWidth 
= minWidth 
/ 2; 
 444                             minHeight 
= minHeight 
/ 2; 
 447                             Log_OC
.w(TAG
, "Out of memory rendering file " + storagePath 
+ 
 450                         if (result 
!= null
) { 
 457             } catch (NoSuchFieldError e
) { 
 458                 mErrorMessageId 
= R
.string
.common_error_unknown
; 
 459                 Log_OC
.e(TAG
, "Error from access to unexisting field despite protection; file "  
 462             } catch (Throwable t
) { 
 463                 mErrorMessageId 
= R
.string
.common_error_unknown
; 
 464                 Log_OC
.e(TAG
, "Unexpected error loading " + getFile().getStoragePath(), t
); 
 467             return new LoadImage(result
, ocFile
); 
 471         protected void onCancelled(LoadImage result
) { 
 472             if (result
.bitmap 
!= null
) { 
 473                 result
.bitmap
.recycle(); 
 478         protected void onPostExecute(LoadImage result
) { 
 480             if (result
.bitmap 
!= null
) { 
 481                 showLoadedImage(result
); 
 485             if (result
.bitmap 
!= null 
&& mBitmap 
!= result
.bitmap
)  { 
 486                 // unused bitmap, release it! (just in case) 
 487                 result
.bitmap
.recycle(); 
 491         @SuppressLint("InlinedApi") 
 492         private void showLoadedImage(LoadImage result
) { 
 493             final ImageViewCustom imageView 
= mImageViewRef
.get(); 
 494             Bitmap bitmap 
= result
.bitmap
; 
 495             if (imageView 
!= null
) { 
 496                 Log_OC
.d(TAG
, "Showing image with resolution " + bitmap
.getWidth() + "x" + 
 499                 if (result
.ocFile
.getMimetype().equalsIgnoreCase("image/png")){ 
 500                     Drawable backrepeat 
= getResources().getDrawable(R
.drawable
.backrepeat
); 
 501                     imageView
.setBackground(backrepeat
); 
 504                 if (result
.ocFile
.getMimetype().equalsIgnoreCase("image/gif")){ 
 505                     imageView
.setGifImage(result
.ocFile
); 
 507                     imageView
.setImageBitmap(bitmap
); 
 510                 imageView
.setVisibility(View
.VISIBLE
); 
 511                 mBitmap  
= bitmap
;  // needs to be kept for recycling when not useful 
 514             final TextView messageView 
= mMessageViewRef
.get(); 
 515             if (messageView 
!= null
) { 
 516                 messageView
.setVisibility(View
.GONE
); 
 517             } // else , silently finish, the fragment was destroyed 
 520         private void showErrorMessage() { 
 521             final ImageView imageView 
= mImageViewRef
.get(); 
 522             if (imageView 
!= null
) { 
 523                 // shows the default error icon 
 524                 imageView
.setVisibility(View
.VISIBLE
); 
 525             } // else , silently finish, the fragment was destroyed 
 527             final TextView messageView 
= mMessageViewRef
.get(); 
 528             if (messageView 
!= null
) { 
 529                 messageView
.setText(mErrorMessageId
); 
 530                 messageView
.setVisibility(View
.VISIBLE
); 
 531             } // else , silently finish, the fragment was destroyed 
 534         private void hideProgressWheel() { 
 535             final ProgressBar progressWheel 
= mProgressWheelRef
.get(); 
 536             if (progressWheel 
!= null
) { 
 537                 progressWheel
.setVisibility(View
.GONE
); 
 544      * Helper method to test if an {@link OCFile} can be passed to a {@link PreviewImageFragment} 
 547      * @param file      File to test if can be previewed. 
 548      * @return          'True' if the file can be handled by the fragment. 
 550     public static boolean canBePreviewed(OCFile file
) { 
 551         return (file 
!= null 
&& file
.isImage()); 
 556      * Finishes the preview 
 558     private void finish() { 
 559         Activity container 
= getActivity(); 
 563     public TouchImageViewCustom 
getImageView() { 
 567     private class LoadImage 
{ 
 568         private Bitmap bitmap
; 
 569         private OCFile ocFile
; 
 571         public LoadImage(Bitmap bitmap
, OCFile ocFile
){ 
 572             this.bitmap 
= bitmap
; 
 573             this.ocFile 
= ocFile
;