1 /* ownCloud Android client application 
   2  *   Copyright (C) 2012-2014 ownCloud Inc.  
   4  *   This program is free software: you can redistribute it and/or modify 
   5  *   it under the terms of the GNU General Public License version 2, 
   6  *   as published by the Free Software Foundation. 
   8  *   This program is distributed in the hope that it will be useful, 
   9  *   but WITHOUT ANY WARRANTY; without even the implied warranty of 
  10  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
  11  *   GNU General Public License for more details. 
  13  *   You should have received a copy of the GNU General Public License 
  14  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. 
  17 package com
.owncloud
.android
.ui
.preview
; 
  19 import java
.lang
.ref
.WeakReference
; 
  21 import android
.accounts
.Account
; 
  22 import android
.annotation
.SuppressLint
; 
  23 import android
.app
.Activity
; 
  24 import android
.graphics
.Bitmap
; 
  25 import android
.graphics
.BitmapFactory
; 
  26 import android
.os
.AsyncTask
; 
  27 import android
.os
.Bundle
; 
  28 import android
.support
.v4
.app
.FragmentStatePagerAdapter
; 
  29 import android
.view
.LayoutInflater
; 
  30 import android
.view
.View
; 
  31 import android
.view
.View
.OnClickListener
; 
  32 import android
.view
.ViewGroup
; 
  33 import android
.widget
.ImageView
; 
  34 import android
.widget
.ProgressBar
; 
  35 import android
.widget
.TextView
; 
  37 import com
.actionbarsherlock
.view
.Menu
; 
  38 import com
.actionbarsherlock
.view
.MenuInflater
; 
  39 import com
.actionbarsherlock
.view
.MenuItem
; 
  40 import com
.owncloud
.android
.R
; 
  41 import com
.owncloud
.android
.datamodel
.OCFile
; 
  42 import com
.owncloud
.android
.files
.FileMenuFilter
; 
  43 import com
.owncloud
.android
.lib
.common
.utils
.Log_OC
; 
  44 import com
.owncloud
.android
.ui
.dialog
.ConfirmationDialogFragment
; 
  45 import com
.owncloud
.android
.ui
.dialog
.RemoveFileDialogFragment
; 
  46 import com
.owncloud
.android
.ui
.fragment
.FileFragment
; 
  47 import com
.owncloud
.android
.utils
.TouchImageViewCustom
; 
  52  * This fragment shows a preview of a downloaded image. 
  54  * Trying to get an instance with NULL {@link OCFile} or ownCloud {@link Account} values will produce an {@link IllegalStateException}. 
  56  * If the {@link OCFile} passed is not downloaded, an {@link IllegalStateException} is generated on instantiation too. 
  58  * @author David A. Velasco 
  60 public class PreviewImageFragment 
extends FileFragment 
{ 
  62     public static final String EXTRA_FILE 
= "FILE"; 
  63     public static final String EXTRA_ACCOUNT 
= "ACCOUNT"; 
  66     private Account mAccount
; 
  67     private TouchImageViewCustom mImageView
; 
  68     private TextView mMessageView
; 
  69     private ProgressBar mProgressWheel
; 
  71     public Bitmap mBitmap 
= null
; 
  73     private static final String TAG 
= PreviewImageFragment
.class.getSimpleName(); 
  75     private boolean mIgnoreFirstSavedState
; 
  79      * Creates a fragment to preview an image. 
  81      * When 'imageFile' or 'ocAccount' are null 
  83      * @param imageFile                 An {@link OCFile} to preview as an image in the fragment 
  84      * @param ocAccount                 An ownCloud account; needed to start downloads 
  85      * @param ignoreFirstSavedState     Flag to work around an unexpected behaviour of {@link FragmentStatePagerAdapter}; TODO better solution  
  87     public PreviewImageFragment(OCFile fileToDetail
, Account ocAccount
, boolean ignoreFirstSavedState
) { 
  90         mIgnoreFirstSavedState 
= ignoreFirstSavedState
; 
  95      *  Creates an empty fragment for image previews. 
  97      *  MUST BE KEPT: the system uses it when tries to reinstantiate a fragment automatically (for instance, when the device is turned a aside). 
  99      *  DO NOT CALL IT: an {@link OCFile} and {@link Account} must be provided for a successful construction  
 101     public PreviewImageFragment() { 
 104         mIgnoreFirstSavedState 
= false
; 
 112     public void onCreate(Bundle savedInstanceState
) { 
 113         super.onCreate(savedInstanceState
); 
 114         setHasOptionsMenu(true
); 
 122     public View 
onCreateView(LayoutInflater inflater
, ViewGroup container
, 
 123             Bundle savedInstanceState
) { 
 124         super.onCreateView(inflater
, container
, savedInstanceState
); 
 125         mView 
= inflater
.inflate(R
.layout
.preview_image_fragment
, container
, false
); 
 126         mImageView 
= (TouchImageViewCustom
) mView
.findViewById(R
.id
.image
); 
 127         mImageView
.setVisibility(View
.GONE
); 
 128         mImageView
.setOnClickListener(new OnClickListener() { 
 130             public void onClick(View v
) { 
 131                 ((PreviewImageActivity
) getActivity()).toggleFullScreen(); 
 135         mMessageView 
= (TextView
)mView
.findViewById(R
.id
.message
); 
 136         mMessageView
.setVisibility(View
.GONE
); 
 137         mProgressWheel 
= (ProgressBar
)mView
.findViewById(R
.id
.progressWheel
); 
 138         mProgressWheel
.setVisibility(View
.VISIBLE
); 
 146     public void onActivityCreated(Bundle savedInstanceState
) { 
 147         super.onActivityCreated(savedInstanceState
); 
 148         if (savedInstanceState 
!= null
) { 
 149             if (!mIgnoreFirstSavedState
) { 
 150                 OCFile file 
= (OCFile
)savedInstanceState
.getParcelable(PreviewImageFragment
.EXTRA_FILE
); 
 152                 mAccount 
= savedInstanceState
.getParcelable(PreviewImageFragment
.EXTRA_ACCOUNT
); 
 154                 mIgnoreFirstSavedState 
= false
; 
 157         if (getFile() == null
) { 
 158             throw new IllegalStateException("Instanced with a NULL OCFile"); 
 160         if (mAccount 
== null
) { 
 161             throw new IllegalStateException("Instanced with a NULL ownCloud Account"); 
 163         if (!getFile().isDown()) { 
 164             throw new IllegalStateException("There is no local file to preview"); 
 173     public void onSaveInstanceState(Bundle outState
) { 
 174         super.onSaveInstanceState(outState
); 
 175         outState
.putParcelable(PreviewImageFragment
.EXTRA_FILE
, getFile()); 
 176         outState
.putParcelable(PreviewImageFragment
.EXTRA_ACCOUNT
, mAccount
); 
 181     public void onStart() { 
 183         if (getFile() != null
) { 
 184            BitmapLoader bl 
= new BitmapLoader(mImageView
, mMessageView
, mProgressWheel
); 
 185            bl
.execute(new String
[]{getFile().getStoragePath()}); 
 194     public void onCreateOptionsMenu(Menu menu
, MenuInflater inflater
) { 
 195         super.onCreateOptionsMenu(menu
, inflater
); 
 196         inflater
.inflate(R
.menu
.file_actions_menu
, menu
); 
 203     public void onPrepareOptionsMenu(Menu menu
) { 
 204         super.onPrepareOptionsMenu(menu
); 
 206         if (mContainerActivity
.getStorageManager() != null
) { 
 208             setFile(mContainerActivity
.getStorageManager().getFileById(getFile().getFileId())); 
 210             FileMenuFilter mf 
= new FileMenuFilter( 
 212                 mContainerActivity
.getStorageManager().getAccount(), 
 214                 getSherlockActivity() 
 219         // additional restriction for this fragment  
 220         // TODO allow renaming in PreviewImageFragment 
 221         MenuItem item 
= menu
.findItem(R
.id
.action_rename_file
); 
 223             item
.setVisible(false
); 
 224             item
.setEnabled(false
); 
 227         // additional restriction for this fragment  
 228         // TODO allow refresh file in PreviewImageFragment 
 229         item 
= menu
.findItem(R
.id
.action_sync_file
); 
 231             item
.setVisible(false
); 
 232             item
.setEnabled(false
); 
 235         // additional restriction for this fragment 
 236         item 
= menu
.findItem(R
.id
.action_move
); 
 238             item
.setVisible(false
); 
 239             item
.setEnabled(false
); 
 250     public boolean onOptionsItemSelected(MenuItem item
) { 
 251         switch (item
.getItemId()) { 
 252             case R
.id
.action_share_file
: { 
 253                 mContainerActivity
.getFileOperationsHelper().shareFileWithLink(getFile()); 
 256             case R
.id
.action_unshare_file
: { 
 257                 mContainerActivity
.getFileOperationsHelper().unshareFileWithLink(getFile()); 
 260             case R
.id
.action_open_file_with
: { 
 264             case R
.id
.action_remove_file
: { 
 265                 RemoveFileDialogFragment dialog 
= RemoveFileDialogFragment
.newInstance(getFile()); 
 266                 dialog
.show(getFragmentManager(), ConfirmationDialogFragment
.FTAG_CONFIRMATION
); 
 269             case R
.id
.action_see_details
: { 
 273             case R
.id
.action_send_file
: { 
 274                 mContainerActivity
.getFileOperationsHelper().sendDownloadedFile(getFile()); 
 277             case R
.id
.action_sync_file
: { 
 278                 mContainerActivity
.getFileOperationsHelper().syncFile(getFile()); 
 288     private void seeDetails() { 
 289         mContainerActivity
.showDetails(getFile());         
 294     public void onResume() { 
 300     public void onPause() { 
 305     public void onDestroy() { 
 306         if (mBitmap 
!= null
) { 
 314      * Opens the previewed image with an external application. 
 316     private void openFile() { 
 317         mContainerActivity
.getFileOperationsHelper().openFile(getFile()); 
 322     private class BitmapLoader 
extends AsyncTask
<String
, Void
, Bitmap
> { 
 325          * Weak reference to the target {@link ImageView} where the bitmap will be loaded into. 
 327          * Using a weak reference will avoid memory leaks if the target ImageView is retired from memory before the load finishes. 
 329         private final WeakReference
<ImageViewCustom
> mImageViewRef
; 
 332          * Weak reference to the target {@link TextView} where error messages will be written. 
 334          * Using a weak reference will avoid memory leaks if the target ImageView is retired from memory before the load finishes. 
 336         private final WeakReference
<TextView
> mMessageViewRef
; 
 340          * Weak reference to the target {@link Progressbar} shown while the load is in progress. 
 342          * Using a weak reference will avoid memory leaks if the target ImageView is retired from memory before the load finishes. 
 344         private final WeakReference
<ProgressBar
> mProgressWheelRef
; 
 348          * Error message to show when a load fails  
 350         private int mErrorMessageId
; 
 356          * @param imageView     Target {@link ImageView} where the bitmap will be loaded into. 
 358         public BitmapLoader(ImageViewCustom imageView
, TextView messageView
, ProgressBar progressWheel
) { 
 359             mImageViewRef 
= new WeakReference
<ImageViewCustom
>(imageView
); 
 360             mMessageViewRef 
= new WeakReference
<TextView
>(messageView
); 
 361             mProgressWheelRef 
= new WeakReference
<ProgressBar
>(progressWheel
); 
 366         protected Bitmap 
doInBackground(String
... params
) { 
 367             Bitmap result 
= null
; 
 368             if (params
.length 
!= 1) return result
; 
 369             String storagePath 
= params
[0]; 
 371                 //Decode file into a bitmap in real size for being able to make zoom on the image 
 372                 result 
= BitmapFactory
.decodeFile(storagePath
); 
 374                 if (result 
== null
) { 
 375                     mErrorMessageId 
= R
.string
.preview_image_error_unknown_format
; 
 376                     Log_OC
.e(TAG
, "File could not be loaded as a bitmap: " + storagePath
); 
 379             } catch (OutOfMemoryError e
) { 
 380                 mErrorMessageId 
= R
.string
.preview_image_error_unknown_format
; 
 381                 Log_OC
.e(TAG
, "Out of memory occured for file " + storagePath
, e
); 
 383             } catch (NoSuchFieldError e
) { 
 384                 mErrorMessageId 
= R
.string
.common_error_unknown
; 
 385                 Log_OC
.e(TAG
, "Error from access to unexisting field despite protection; file " + storagePath
, e
); 
 387             } catch (Throwable t
) { 
 388                 mErrorMessageId 
= R
.string
.common_error_unknown
; 
 389                 Log_OC
.e(TAG
, "Unexpected error loading " + getFile().getStoragePath(), t
); 
 396         protected void onPostExecute(Bitmap result
) { 
 398             if (result 
!= null
) { 
 399                 showLoadedImage(result
); 
 405         @SuppressLint("InlinedApi") 
 406         private void showLoadedImage(Bitmap result
) { 
 407             if (mImageViewRef 
!= null
) { 
 408                 final ImageViewCustom imageView 
= mImageViewRef
.get(); 
 409                 if (imageView 
!= null
) { 
 410                     imageView
.setBitmap(result
); 
 411                     imageView
.setImageBitmap(result
); 
 412                     imageView
.setVisibility(View
.VISIBLE
); 
 414                 } // else , silently finish, the fragment was destroyed 
 416             if (mMessageViewRef 
!= null
) { 
 417                 final TextView messageView 
= mMessageViewRef
.get(); 
 418                 if (messageView 
!= null
) { 
 419                     messageView
.setVisibility(View
.GONE
); 
 420                 } // else , silently finish, the fragment was destroyed 
 424         private void showErrorMessage() { 
 425             if (mImageViewRef 
!= null
) { 
 426                 final ImageView imageView 
= mImageViewRef
.get(); 
 427                 if (imageView 
!= null
) { 
 428                     // shows the default error icon 
 429                     imageView
.setVisibility(View
.VISIBLE
); 
 430                 } // else , silently finish, the fragment was destroyed 
 432             if (mMessageViewRef 
!= null
) { 
 433                 final TextView messageView 
= mMessageViewRef
.get(); 
 434                 if (messageView 
!= null
) { 
 435                     messageView
.setText(mErrorMessageId
); 
 436                     messageView
.setVisibility(View
.VISIBLE
); 
 437                 } // else , silently finish, the fragment was destroyed 
 441         private void hideProgressWheel() { 
 442             if (mProgressWheelRef 
!= null
) { 
 443                 final ProgressBar progressWheel 
= mProgressWheelRef
.get(); 
 444                 if (progressWheel 
!= null
) { 
 445                     progressWheel
.setVisibility(View
.GONE
); 
 453      * Helper method to test if an {@link OCFile} can be passed to a {@link PreviewImageFragment} to be previewed. 
 455      * @param file      File to test if can be previewed. 
 456      * @return          'True' if the file can be handled by the fragment. 
 458     public static boolean canBePreviewed(OCFile file
) { 
 459         return (file 
!= null 
&& file
.isImage()); 
 464      * Finishes the preview 
 466     private void finish() { 
 467         Activity container 
= getActivity(); 
 471     public TouchImageViewCustom 
getImageView() {