1 /* ownCloud Android client application 
   2  *   Copyright (C) 2012-2013  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 android
.annotation
.SuppressLint
; 
  20 import android
.content
.BroadcastReceiver
; 
  21 import android
.content
.ComponentName
; 
  22 import android
.content
.Context
; 
  23 import android
.content
.Intent
; 
  24 import android
.content
.IntentFilter
; 
  25 import android
.content
.ServiceConnection
; 
  26 import android
.content
.SharedPreferences
; 
  27 import android
.os
.Build
; 
  28 import android
.os
.Bundle
; 
  29 import android
.os
.Handler
; 
  30 import android
.os
.IBinder
; 
  31 import android
.os
.Message
; 
  32 import android
.preference
.PreferenceManager
; 
  33 import android
.support
.v4
.view
.ViewPager
; 
  34 import android
.view
.View
; 
  36 import com
.actionbarsherlock
.app
.ActionBar
; 
  37 import com
.actionbarsherlock
.view
.MenuItem
; 
  38 import com
.actionbarsherlock
.view
.Window
; 
  39 import com
.ortiz
.touch
.ExtendedViewPager
; 
  40 import com
.owncloud
.android
.R
; 
  41 import com
.owncloud
.android
.authentication
.AccountUtils
; 
  42 import com
.owncloud
.android
.datamodel
.FileDataStorageManager
; 
  43 import com
.owncloud
.android
.datamodel
.OCFile
; 
  44 import com
.owncloud
.android
.files
.services
.FileDownloader
; 
  45 import com
.owncloud
.android
.files
.services
.FileDownloader
.FileDownloaderBinder
; 
  46 import com
.owncloud
.android
.files
.services
.FileUploader
; 
  47 import com
.owncloud
.android
.files
.services
.FileUploader
.FileUploaderBinder
; 
  48 import com
.owncloud
.android
.lib
.common
.operations
.OnRemoteOperationListener
; 
  49 import com
.owncloud
.android
.lib
.common
.operations
.RemoteOperation
; 
  50 import com
.owncloud
.android
.lib
.common
.operations
.RemoteOperationResult
; 
  51 import com
.owncloud
.android
.lib
.common
.operations
.RemoteOperationResult
.ResultCode
; 
  52 import com
.owncloud
.android
.lib
.common
.utils
.Log_OC
; 
  53 import com
.owncloud
.android
.operations
.CreateShareOperation
; 
  54 import com
.owncloud
.android
.operations
.RemoveFileOperation
; 
  55 import com
.owncloud
.android
.operations
.UnshareLinkOperation
; 
  56 import com
.owncloud
.android
.ui
.activity
.FileActivity
; 
  57 import com
.owncloud
.android
.ui
.activity
.FileDisplayActivity
; 
  58 import com
.owncloud
.android
.ui
.activity
.PinCodeActivity
; 
  59 import com
.owncloud
.android
.ui
.fragment
.FileFragment
; 
  60 import com
.owncloud
.android
.utils
.DisplayUtils
; 
  64  *  Holds a swiping galley where image files contained in an ownCloud directory are shown 
  66  *  @author David A. Velasco 
  68 public class PreviewImageActivity 
extends FileActivity 
implements  
  69  FileFragment
.ContainerActivity
, 
  70 ViewPager
.OnPageChangeListener
, OnRemoteOperationListener 
{ 
  72     public static final int DIALOG_SHORT_WAIT 
= 0; 
  74     public static final String TAG 
= PreviewImageActivity
.class.getSimpleName(); 
  76     public static final String KEY_WAITING_TO_PREVIEW 
= "WAITING_TO_PREVIEW"; 
  77     private static final String KEY_WAITING_FOR_BINDER 
= "WAITING_FOR_BINDER"; 
  79     private static final int INITIAL_HIDE_DELAY 
= 0; // immediate hide 
  81     private ExtendedViewPager mViewPager
; 
  82     private PreviewImagePagerAdapter mPreviewImagePagerAdapter
;     
  84     private boolean mRequestWaitingForBinder
; 
  86     private DownloadFinishReceiver mDownloadFinishReceiver
; 
  88     private View mFullScreenAnchorView
; 
  92     protected void onCreate(Bundle savedInstanceState
) { 
  93         super.onCreate(savedInstanceState
); 
  95         requestWindowFeature(Window
.FEATURE_ACTION_BAR_OVERLAY
); 
  96         setContentView(R
.layout
.preview_image_activity
); 
  98         ActionBar actionBar 
= getSupportActionBar(); 
  99         actionBar
.setIcon(DisplayUtils
.getSeasonalIconId()); 
 100         actionBar
.setDisplayHomeAsUpEnabled(true
); 
 104         if (getIntent().getExtras() != null 
&& savedInstanceState 
== null 
&& fromNotification()) { 
 108         // Make sure we're running on Honeycomb or higher to use FullScreen and 
 110         if (isHoneycombOrHigher()) { 
 112             mFullScreenAnchorView 
= getWindow().getDecorView(); 
 113             // to keep our UI controls visibility in line with system bars 
 115             mFullScreenAnchorView
.setOnSystemUiVisibilityChangeListener(new View
.OnSystemUiVisibilityChangeListener() { 
 116                 @SuppressLint("InlinedApi") 
 118                 public void onSystemUiVisibilityChange(int flags
) { 
 119                     boolean visible 
= (flags 
& View
.SYSTEM_UI_FLAG_HIDE_NAVIGATION
) == 0; 
 120                     ActionBar actionBar 
= getSupportActionBar(); 
 131         if (savedInstanceState 
!= null
) { 
 132             mRequestWaitingForBinder 
= savedInstanceState
.getBoolean(KEY_WAITING_FOR_BINDER
); 
 134             mRequestWaitingForBinder 
= false
; 
 139     private void initViewPager() { 
 140         // get parent from path 
 141         String parentPath 
= getFile().getRemotePath().substring(0, getFile().getRemotePath().lastIndexOf(getFile().getFileName())); 
 142         OCFile parentFolder 
= getStorageManager().getFileByPath(parentPath
); 
 143         if (parentFolder 
== null
) { 
 144             // should not be necessary 
 145             parentFolder 
= getStorageManager().getFileByPath(OCFile
.ROOT_PATH
); 
 147         mPreviewImagePagerAdapter 
= new PreviewImagePagerAdapter(getSupportFragmentManager(), parentFolder
, getAccount(), getStorageManager()); 
 148         mViewPager 
= (ExtendedViewPager
) findViewById(R
.id
.fragmentPager
); 
 149         int position 
= mPreviewImagePagerAdapter
.getFilePosition(getFile()); 
 150         position 
= (position 
>= 0) ? position 
: 0; 
 151         mViewPager
.setAdapter(mPreviewImagePagerAdapter
);  
 152         mViewPager
.setOnPageChangeListener(this); 
 153         mViewPager
.setCurrentItem(position
); 
 154         if (position 
== 0 && !getFile().isDown()) { 
 155             // this is necessary because mViewPager.setCurrentItem(0) just after setting the adapter does not result in a call to #onPageSelected(0)  
 156             mRequestWaitingForBinder 
= true
; 
 161     protected void onPostCreate(Bundle savedInstanceState
)  { 
 162         super.onPostCreate(savedInstanceState
); 
 164         // Trigger the initial hide() shortly after the activity has been  
 165         // created, to briefly hint to the user that UI controls  
 167         delayedHide(INITIAL_HIDE_DELAY
); 
 171     Handler mHideSystemUiHandler 
= new Handler() { 
 173         public void handleMessage(Message msg
) { 
 174             if (isHoneycombOrHigher()) { 
 175                 hideSystemUI(mFullScreenAnchorView
); 
 177             getSupportActionBar().hide(); 
 181     private void delayedHide(int delayMillis
)   { 
 182         mHideSystemUiHandler
.removeMessages(0); 
 183         mHideSystemUiHandler
.sendEmptyMessageDelayed(0, delayMillis
); 
 187     /// handle Window Focus changes 
 189     public void onWindowFocusChanged(boolean hasFocus
) { 
 190         super.onWindowFocusChanged(hasFocus
); 
 192         // When the window loses focus (e.g. the action overflow is shown), 
 193         // cancel any pending hide action. 
 195             mHideSystemUiHandler
.removeMessages(0); 
 202     public void onStart() { 
 207     protected void onSaveInstanceState(Bundle outState
) { 
 208         super.onSaveInstanceState(outState
); 
 209         outState
.putBoolean(KEY_WAITING_FOR_BINDER
, mRequestWaitingForBinder
);     
 213     public void onRemoteOperationFinish(RemoteOperation operation
, RemoteOperationResult result
) { 
 214         super.onRemoteOperationFinish(operation
, result
); 
 216         if (operation 
instanceof CreateShareOperation
) { 
 217             onCreateShareOperationFinish((CreateShareOperation
) operation
, result
); 
 219         } else if (operation 
instanceof UnshareLinkOperation
) { 
 220             onUnshareLinkOperationFinish((UnshareLinkOperation
) operation
, result
); 
 222         } else if (operation 
instanceof RemoveFileOperation
) { 
 228     private void onUnshareLinkOperationFinish(UnshareLinkOperation operation
, RemoteOperationResult result
) { 
 229         if (result
.isSuccess()) { 
 230             OCFile file 
= getStorageManager().getFileByPath(getFile().getRemotePath()); 
 234             invalidateOptionsMenu(); 
 235         } else if  (result
.getCode() == ResultCode
.SHARE_NOT_FOUND
) { 
 236             backToDisplayActivity(); 
 241     private void onCreateShareOperationFinish(CreateShareOperation operation
, RemoteOperationResult result
) { 
 242         if (result
.isSuccess()) { 
 243             OCFile file 
= getStorageManager().getFileByPath(getFile().getRemotePath()); 
 247             invalidateOptionsMenu(); 
 252     protected ServiceConnection 
newTransferenceServiceConnection() { 
 253         return new PreviewImageServiceConnection(); 
 256     /** Defines callbacks for service binding, passed to bindService() */ 
 257     private class PreviewImageServiceConnection 
implements ServiceConnection 
{ 
 260         public void onServiceConnected(ComponentName component
, IBinder service
) { 
 262             if (component
.equals(new ComponentName(PreviewImageActivity
.this, FileDownloader
.class))) { 
 263                 mDownloaderBinder 
= (FileDownloaderBinder
) service
; 
 264                 if (mRequestWaitingForBinder
) { 
 265                     mRequestWaitingForBinder 
= false
; 
 266                     Log_OC
.d(TAG
, "Simulating reselection of current page after connection of download binder"); 
 267                     onPageSelected(mViewPager
.getCurrentItem()); 
 270             } else if (component
.equals(new ComponentName(PreviewImageActivity
.this, FileUploader
.class))) { 
 271                 Log_OC
.d(TAG
, "Upload service connected"); 
 272                 mUploaderBinder 
= (FileUploaderBinder
) service
; 
 280         public void onServiceDisconnected(ComponentName component
) { 
 281             if (component
.equals(new ComponentName(PreviewImageActivity
.this, FileDownloader
.class))) { 
 282                 Log_OC
.d(TAG
, "Download service suddenly disconnected"); 
 283                 mDownloaderBinder 
= null
; 
 284             } else if (component
.equals(new ComponentName(PreviewImageActivity
.this, FileUploader
.class))) { 
 285                 Log_OC
.d(TAG
, "Upload service suddenly disconnected"); 
 286                 mUploaderBinder 
= null
; 
 293     public void onStop() { 
 299     public void onDestroy() { 
 304     public boolean onOptionsItemSelected(MenuItem item
) { 
 305         boolean returnValue 
= false
; 
 307         switch(item
.getItemId()){ 
 308         case android
.R
.id
.home
: 
 309             backToDisplayActivity(); 
 313                 returnValue 
= super.onOptionsItemSelected(item
); 
 321     protected void onResume() { 
 323         //Log_OC.e(TAG, "ACTIVITY, ONRESUME"); 
 324         mDownloadFinishReceiver 
= new DownloadFinishReceiver(); 
 326         IntentFilter filter 
= new IntentFilter(FileDownloader
.getDownloadFinishMessage()); 
 327         filter
.addAction(FileDownloader
.getDownloadAddedMessage()); 
 328         registerReceiver(mDownloadFinishReceiver
, filter
); 
 332     protected void onPostResume() { 
 333         //Log_OC.e(TAG, "ACTIVITY, ONPOSTRESUME"); 
 334         super.onPostResume(); 
 338     public void onPause() { 
 339         unregisterReceiver(mDownloadFinishReceiver
); 
 340         mDownloadFinishReceiver 
= null
; 
 345     private void backToDisplayActivity() { 
 350     public void showDetails(OCFile file
) { 
 351         Intent showDetailsIntent 
= new Intent(this, FileDisplayActivity
.class); 
 352         showDetailsIntent
.setAction(FileDisplayActivity
.ACTION_DETAILS
); 
 353         showDetailsIntent
.putExtra(FileActivity
.EXTRA_FILE
, file
); 
 354         showDetailsIntent
.putExtra(FileActivity
.EXTRA_ACCOUNT
, AccountUtils
.getCurrentOwnCloudAccount(this)); 
 355         startActivity(showDetailsIntent
); 
 356         int pos 
= mPreviewImagePagerAdapter
.getFilePosition(file
); 
 357         file 
= mPreviewImagePagerAdapter
.getFileAt(pos
); 
 362     private void requestForDownload(OCFile file
) { 
 363         if (mDownloaderBinder 
== null
) { 
 364             Log_OC
.d(TAG
, "requestForDownload called without binder to download service"); 
 366         } else if (!mDownloaderBinder
.isDownloading(getAccount(), file
)) { 
 367             Intent i 
= new Intent(this, FileDownloader
.class); 
 368             i
.putExtra(FileDownloader
.EXTRA_ACCOUNT
, getAccount()); 
 369             i
.putExtra(FileDownloader
.EXTRA_FILE
, file
); 
 375      * This method will be invoked when a new page becomes selected. Animation is not necessarily complete. 
 377      *  @param  Position        Position index of the new selected page 
 380     public void onPageSelected(int position
) { 
 381         if (mDownloaderBinder 
== null
) { 
 382             mRequestWaitingForBinder 
= true
; 
 385             OCFile currentFile 
= mPreviewImagePagerAdapter
.getFileAt(position
);  
 386             getSupportActionBar().setTitle(currentFile
.getFileName()); 
 387             if (!currentFile
.isDown()) { 
 388                 if (!mPreviewImagePagerAdapter
.pendingErrorAt(position
)) { 
 389                     requestForDownload(currentFile
); 
 393             // Call to reset image zoom to initial state 
 394             ((PreviewImagePagerAdapter
) mViewPager
.getAdapter()).resetZoom(); 
 400      * Called when the scroll state changes. Useful for discovering when the user begins dragging,  
 401      * when the pager is automatically settling to the current page. when it is fully stopped/idle. 
 403      * @param   State       The new scroll state (SCROLL_STATE_IDLE, _DRAGGING, _SETTLING 
 406     public void onPageScrollStateChanged(int state
) { 
 410      * This method will be invoked when the current page is scrolled, either as part of a programmatically  
 411      * initiated smooth scroll or a user initiated touch scroll. 
 413      * @param   position                Position index of the first page currently being displayed.  
 414      *                                  Page position+1 will be visible if positionOffset is nonzero. 
 416      * @param   positionOffset          Value from [0, 1) indicating the offset from the page at position. 
 417      * @param   positionOffsetPixels    Value in pixels indicating the offset from position.  
 420     public void onPageScrolled(int position
, float positionOffset
, int positionOffsetPixels
) { 
 425      * Class waiting for broadcast events from the {@link FielDownloader} service. 
 427      * Updates the UI when a download is started or finished, provided that it is relevant for the 
 428      * folder displayed in the gallery. 
 430     private class DownloadFinishReceiver 
extends BroadcastReceiver 
{ 
 432         public void onReceive(Context context
, Intent intent
) { 
 433             String accountName 
= intent
.getStringExtra(FileDownloader
.ACCOUNT_NAME
); 
 434             String downloadedRemotePath 
= intent
.getStringExtra(FileDownloader
.EXTRA_REMOTE_PATH
); 
 435             if (getAccount().name
.equals(accountName
) &&  
 436                     downloadedRemotePath 
!= null
) { 
 438                 OCFile file 
= getStorageManager().getFileByPath(downloadedRemotePath
); 
 439                 int position 
= mPreviewImagePagerAdapter
.getFilePosition(file
); 
 440                 boolean downloadWasFine 
= intent
.getBooleanExtra(FileDownloader
.EXTRA_DOWNLOAD_RESULT
, false
); 
 441                 //boolean isOffscreen =  Math.abs((mViewPager.getCurrentItem() - position)) <= mViewPager.getOffscreenPageLimit(); 
 443                 if (position 
>= 0 && intent
.getAction().equals(FileDownloader
.getDownloadFinishMessage())) { 
 444                     if (downloadWasFine
) { 
 445                         mPreviewImagePagerAdapter
.updateFile(position
, file
);    
 448                         mPreviewImagePagerAdapter
.updateWithDownloadError(position
); 
 450                     mPreviewImagePagerAdapter
.notifyDataSetChanged();   // will trigger the creation of new fragments 
 453                     Log_OC
.d(TAG
, "Download finished, but the fragment is offscreen"); 
 457             removeStickyBroadcast(intent
); 
 462     @SuppressLint("InlinedApi") 
 463         public void toggleFullScreen() { 
 465         if (isHoneycombOrHigher()) { 
 467             boolean visible 
= (mFullScreenAnchorView
.getSystemUiVisibility() 
 468                     & View
.SYSTEM_UI_FLAG_HIDE_NAVIGATION
) == 0; 
 471                 hideSystemUI(mFullScreenAnchorView
); 
 472                 // actionBar.hide(); // propagated through 
 473                 // OnSystemUiVisibilityChangeListener() 
 475                 showSystemUI(mFullScreenAnchorView
); 
 476                 // actionBar.show(); // propagated through 
 477                 // OnSystemUiVisibilityChangeListener() 
 482             ActionBar actionBar 
= getSupportActionBar(); 
 483             if (!actionBar
.isShowing()) { 
 495     protected void onAccountSet(boolean stateWasRecovered
) { 
 496         super.onAccountSet(stateWasRecovered
); 
 497         if (getAccount() != null
) { 
 498             OCFile file 
= getFile(); 
 499             /// Validate handled file  (first image to preview) 
 501                 throw new IllegalStateException("Instanced with a NULL OCFile"); 
 503             if (!file
.isImage()) { 
 504                 throw new IllegalArgumentException("Non-image file passed as argument"); 
 507             // Update file according to DB file, if it is possible 
 508             if (file
.getFileId() > FileDataStorageManager
.ROOT_PARENT_ID
)             
 509                 file 
= getStorageManager().getFileById(file
.getFileId()); 
 512                 /// Refresh the activity according to the Account and OCFile set 
 513                 setFile(file
);  // reset after getting it fresh from storageManager 
 514                 getSupportActionBar().setTitle(getFile().getFileName()); 
 515                 //if (!stateWasRecovered) { 
 520                 // handled file not in the current Account 
 528      * Launch an intent to request the PIN code to the user before letting him use the app 
 530     private void requestPinCode() { 
 531         boolean pinStart 
= false
; 
 532         SharedPreferences appPrefs 
= PreferenceManager
.getDefaultSharedPreferences(getApplicationContext()); 
 533         pinStart 
= appPrefs
.getBoolean("set_pincode", false
); 
 535             Intent i 
= new Intent(getApplicationContext(), PinCodeActivity
.class); 
 536             i
.putExtra(PinCodeActivity
.EXTRA_ACTIVITY
, "PreviewImageActivity"); 
 542     public void onBrowsedDownTo(OCFile folder
) { 
 543         // TODO Auto-generated method stub 
 548     public void onTransferStateChanged(OCFile file
, boolean downloading
, boolean uploading
) { 
 549         // TODO Auto-generated method stub 
 554     @SuppressLint("InlinedApi") 
 555         private void hideSystemUI(View anchorView
) { 
 556         anchorView
.setSystemUiVisibility( 
 557                 View
.SYSTEM_UI_FLAG_HIDE_NAVIGATION         
// hides NAVIGATION BAR; Android >= 4.0 
 558             |   View
.SYSTEM_UI_FLAG_FULLSCREEN              
// hides STATUS BAR;     Android >= 4.1 
 559             |   View
.SYSTEM_UI_FLAG_IMMERSIVE               
// stays interactive;    Android >= 4.4 
 560             |   View
.SYSTEM_UI_FLAG_LAYOUT_STABLE           
// draw full window;     Android >= 4.1 
 561             |   View
.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN       
// draw full window;     Android >= 4.1 
 562             |   View
.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION  
// draw full window;     Android >= 4.1 
 566     @SuppressLint("InlinedApi") 
 567     private void showSystemUI(View anchorView
) { 
 568         anchorView
.setSystemUiVisibility( 
 569                 View
.SYSTEM_UI_FLAG_LAYOUT_STABLE           
// draw full window;     Android >= 4.1 
 570             |   View
.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN       
// draw full window;     Android >= 4.1 
 571             |   View
.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION  
// draw full window;     Android >= 4.1 
 576      * Checks if OS version is Honeycomb one or higher 
 580     private boolean isHoneycombOrHigher() { 
 581         if (Build
.VERSION
.SDK_INT 
>= Build
.VERSION_CODES
.HONEYCOMB
) {