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 hd 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 android
.annotation
.SuppressLint
; 
  23 import android
.content
.BroadcastReceiver
; 
  24 import android
.content
.ComponentName
; 
  25 import android
.content
.Context
; 
  26 import android
.content
.Intent
; 
  27 import android
.content
.IntentFilter
; 
  28 import android
.content
.ServiceConnection
; 
  29 import android
.graphics
.drawable
.ColorDrawable
; 
  30 import android
.os
.Build
; 
  31 import android
.os
.Bundle
; 
  32 import android
.os
.Handler
; 
  33 import android
.os
.IBinder
; 
  34 import android
.os
.Message
; 
  35 import android
.support
.v4
.view
.GravityCompat
; 
  36 import android
.support
.v4
.view
.ViewPager
; 
  37 import android
.support
.v4
.widget
.DrawerLayout
; 
  38 import android
.support
.v7
.app
.ActionBar
; 
  39 import android
.view
.MenuItem
; 
  40 import android
.view
.View
; 
  41 import android
.view
.Window
; 
  43 import com
.ortiz
.touch
.ExtendedViewPager
; 
  44 import com
.owncloud
.android
.MainApp
; 
  45 import com
.owncloud
.android
.R
; 
  46 import com
.owncloud
.android
.authentication
.AccountUtils
; 
  47 import com
.owncloud
.android
.datamodel
.FileDataStorageManager
; 
  48 import com
.owncloud
.android
.datamodel
.OCFile
; 
  49 import com
.owncloud
.android
.files
.services
.FileDownloader
; 
  50 import com
.owncloud
.android
.files
.services
.FileDownloader
.FileDownloaderBinder
; 
  51 import com
.owncloud
.android
.files
.services
.FileUploader
; 
  52 import com
.owncloud
.android
.files
.services
.FileUploader
.FileUploaderBinder
; 
  53 import com
.owncloud
.android
.lib
.common
.operations
.OnRemoteOperationListener
; 
  54 import com
.owncloud
.android
.lib
.common
.operations
.RemoteOperation
; 
  55 import com
.owncloud
.android
.lib
.common
.operations
.RemoteOperationResult
; 
  56 import com
.owncloud
.android
.lib
.common
.operations
.RemoteOperationResult
.ResultCode
; 
  57 import com
.owncloud
.android
.lib
.common
.utils
.Log_OC
; 
  58 import com
.owncloud
.android
.operations
.CreateShareOperation
; 
  59 import com
.owncloud
.android
.operations
.RemoveFileOperation
; 
  60 import com
.owncloud
.android
.operations
.SynchronizeFileOperation
; 
  61 import com
.owncloud
.android
.operations
.UnshareLinkOperation
; 
  62 import com
.owncloud
.android
.ui
.activity
.FileActivity
; 
  63 import com
.owncloud
.android
.ui
.activity
.FileDisplayActivity
; 
  64 import com
.owncloud
.android
.ui
.fragment
.FileFragment
; 
  65 import com
.owncloud
.android
.utils
.DisplayUtils
; 
  69  *  Holds a swiping galley where image files contained in an ownCloud directory are shown 
  71 public class PreviewImageActivity 
extends FileActivity 
implements 
  72         FileFragment
.ContainerActivity
, 
  73         ViewPager
.OnPageChangeListener
, OnRemoteOperationListener 
{ 
  75     public static final int DIALOG_SHORT_WAIT 
= 0; 
  77     public static final String TAG 
= PreviewImageActivity
.class.getSimpleName(); 
  79     public static final String KEY_WAITING_TO_PREVIEW 
= "WAITING_TO_PREVIEW"; 
  80     private static final String KEY_WAITING_FOR_BINDER 
= "WAITING_FOR_BINDER"; 
  82     private static final int INITIAL_HIDE_DELAY 
= 0; // immediate hide 
  84     private ExtendedViewPager mViewPager
; 
  85     private PreviewImagePagerAdapter mPreviewImagePagerAdapter
; 
  86     private int mSavedPosition 
= 0; 
  87     private boolean mHasSavedPosition 
= false
; 
  89     private boolean mRequestWaitingForBinder
; 
  91     private DownloadFinishReceiver mDownloadFinishReceiver
; 
  93     private View mFullScreenAnchorView
; 
  97     protected void onCreate(Bundle savedInstanceState
) { 
  98         requestWindowFeature(Window
.FEATURE_ACTION_BAR_OVERLAY
); 
 100         super.onCreate(savedInstanceState
); 
 102         setContentView(R
.layout
.preview_image_activity
); 
 108         ActionBar actionBar 
= getSupportActionBar(); 
 109         updateActionBarTitleAndHomeButton(null
); 
 113         // Make sure we're running on Honeycomb or higher to use FullScreen and 
 115         if (isHoneycombOrHigher()) { 
 117             mFullScreenAnchorView 
= getWindow().getDecorView(); 
 118             // to keep our UI controls visibility in line with system bars 
 120             mFullScreenAnchorView
.setOnSystemUiVisibilityChangeListener
 
 121                     (new View
.OnSystemUiVisibilityChangeListener() { 
 122                 @SuppressLint("InlinedApi") 
 124                 public void onSystemUiVisibilityChange(int flags
) { 
 125                     boolean visible 
= (flags 
& View
.SYSTEM_UI_FLAG_HIDE_NAVIGATION
) == 0; 
 126                     ActionBar actionBar 
= getSupportActionBar(); 
 129                         mDrawerLayout
.setDrawerLockMode(DrawerLayout
.LOCK_MODE_UNLOCKED
); 
 132                         mDrawerLayout
.setDrawerLockMode(DrawerLayout
.LOCK_MODE_LOCKED_CLOSED
); 
 137             if (Build
.VERSION
.SDK_INT 
>= Build
.VERSION_CODES
.LOLLIPOP
) { 
 138                 getWindow().setStatusBarColor(getResources().getColor(R
.color
.owncloud_blue_dark_transparent
)); 
 142         if (savedInstanceState 
!= null
) { 
 143             mRequestWaitingForBinder 
= savedInstanceState
.getBoolean(KEY_WAITING_FOR_BINDER
); 
 145             mRequestWaitingForBinder 
= false
; 
 150     private void initViewPager() { 
 151         // get parent from path 
 152         String parentPath 
= getFile().getRemotePath().substring(0, 
 153                 getFile().getRemotePath().lastIndexOf(getFile().getFileName())); 
 154         OCFile parentFolder 
= getStorageManager().getFileByPath(parentPath
); 
 155         if (parentFolder 
== null
) { 
 156             // should not be necessary 
 157             parentFolder 
= getStorageManager().getFileByPath(OCFile
.ROOT_PATH
); 
 160         mPreviewImagePagerAdapter 
= new PreviewImagePagerAdapter(getSupportFragmentManager(), 
 161                 parentFolder
, getAccount(), getStorageManager(), MainApp
.getOnlyOnDevice()); 
 163         mViewPager 
= (ExtendedViewPager
) findViewById(R
.id
.fragmentPager
); 
 164         int position 
= mHasSavedPosition ? mSavedPosition 
: 
 165                 mPreviewImagePagerAdapter
.getFilePosition(getFile()); 
 166         position 
= (position 
>= 0) ? position 
: 0; 
 167         mViewPager
.setAdapter(mPreviewImagePagerAdapter
);  
 168         mViewPager
.setOnPageChangeListener(this); 
 169         mViewPager
.setCurrentItem(position
); 
 170         if (position 
== 0 && !getFile().isDown()) { 
 171             // this is necessary because mViewPager.setCurrentItem(0) just after setting the 
 172             // adapter does not result in a call to #onPageSelected(0) 
 173             mRequestWaitingForBinder 
= true
; 
 178     protected void onPostCreate(Bundle savedInstanceState
)  { 
 179         super.onPostCreate(savedInstanceState
); 
 181         // Trigger the initial hide() shortly after the activity has been  
 182         // created, to briefly hint to the user that UI controls  
 184         delayedHide(INITIAL_HIDE_DELAY
); 
 188     Handler mHideSystemUiHandler 
= new Handler() { 
 190         public void handleMessage(Message msg
) { 
 191             if (isHoneycombOrHigher()) { 
 192                 hideSystemUI(mFullScreenAnchorView
); 
 194             getSupportActionBar().hide(); 
 198     private void delayedHide(int delayMillis
)   { 
 199         mHideSystemUiHandler
.removeMessages(0); 
 200         mHideSystemUiHandler
.sendEmptyMessageDelayed(0, delayMillis
); 
 204     /// handle Window Focus changes 
 206     public void onWindowFocusChanged(boolean hasFocus
) { 
 207         super.onWindowFocusChanged(hasFocus
); 
 209         // When the window loses focus (e.g. the action overflow is shown), 
 210         // cancel any pending hide action. 
 212             mHideSystemUiHandler
.removeMessages(0); 
 219     public void onStart() { 
 224     protected void onSaveInstanceState(Bundle outState
) { 
 225         super.onSaveInstanceState(outState
); 
 226         outState
.putBoolean(KEY_WAITING_FOR_BINDER
, mRequestWaitingForBinder
);     
 230     public void onRemoteOperationFinish(RemoteOperation operation
, RemoteOperationResult result
) { 
 231         super.onRemoteOperationFinish(operation
, result
); 
 233         if (operation 
instanceof CreateShareOperation
) { 
 234             onCreateShareOperationFinish((CreateShareOperation
) operation
, result
); 
 236         } else if (operation 
instanceof UnshareLinkOperation
) { 
 237             onUnshareLinkOperationFinish((UnshareLinkOperation
) operation
, result
); 
 239         } else if (operation 
instanceof RemoveFileOperation
) { 
 241         } else if (operation 
instanceof SynchronizeFileOperation
) { 
 242             onSynchronizeFileOperationFinish((SynchronizeFileOperation
) operation
, result
); 
 248     private void onUnshareLinkOperationFinish(UnshareLinkOperation operation
, 
 249                                               RemoteOperationResult result
) { 
 250         if (result
.isSuccess()) { 
 251             OCFile file 
= getStorageManager().getFileByPath(getFile().getRemotePath()); 
 255             invalidateOptionsMenu(); 
 256         } else if  (result
.getCode() == ResultCode
.SHARE_NOT_FOUND
) { 
 257             backToDisplayActivity(); 
 262     private void onCreateShareOperationFinish(CreateShareOperation operation
, 
 263                                               RemoteOperationResult result
) { 
 264         if (result
.isSuccess()) { 
 265             OCFile file 
= getStorageManager().getFileByPath(getFile().getRemotePath()); 
 269             invalidateOptionsMenu(); 
 273     private void onSynchronizeFileOperationFinish(SynchronizeFileOperation operation
, 
 274                                                   RemoteOperationResult result
) { 
 275         if (result
.isSuccess()) { 
 276             invalidateOptionsMenu(); 
 282     protected ServiceConnection 
newTransferenceServiceConnection() { 
 283         return new PreviewImageServiceConnection(); 
 286     /** Defines callbacks for service binding, passed to bindService() */ 
 287     private class PreviewImageServiceConnection 
implements ServiceConnection 
{ 
 290         public void onServiceConnected(ComponentName component
, IBinder service
) { 
 292             if (component
.equals(new ComponentName(PreviewImageActivity
.this, 
 293                     FileDownloader
.class))) { 
 294                 mDownloaderBinder 
= (FileDownloaderBinder
) service
; 
 295                 if (mRequestWaitingForBinder
) { 
 296                     mRequestWaitingForBinder 
= false
; 
 297                     Log_OC
.d(TAG
, "Simulating reselection of current page after connection " + 
 298                             "of download binder"); 
 299                     onPageSelected(mViewPager
.getCurrentItem()); 
 302             } else if (component
.equals(new ComponentName(PreviewImageActivity
.this, 
 303                     FileUploader
.class))) { 
 304                 Log_OC
.d(TAG
, "Upload service connected"); 
 305                 mUploaderBinder 
= (FileUploaderBinder
) service
; 
 313         public void onServiceDisconnected(ComponentName component
) { 
 314             if (component
.equals(new ComponentName(PreviewImageActivity
.this, 
 315                     FileDownloader
.class))) { 
 316                 Log_OC
.d(TAG
, "Download service suddenly disconnected"); 
 317                 mDownloaderBinder 
= null
; 
 318             } else if (component
.equals(new ComponentName(PreviewImageActivity
.this, 
 319                     FileUploader
.class))) { 
 320                 Log_OC
.d(TAG
, "Upload service suddenly disconnected"); 
 321                 mUploaderBinder 
= null
; 
 328     public void onStop() { 
 334     public void onDestroy() { 
 339     public boolean onOptionsItemSelected(MenuItem item
) { 
 340         boolean returnValue 
= false
; 
 342         switch(item
.getItemId()){ 
 343         case android
.R
.id
.home
: 
 344             if (mDrawerLayout
.isDrawerOpen(GravityCompat
.START
)) { 
 345                 mDrawerLayout
.closeDrawer(GravityCompat
.START
); 
 347                 backToDisplayActivity(); 
 352                 returnValue 
= super.onOptionsItemSelected(item
); 
 360     protected void onResume() { 
 363         mDownloadFinishReceiver 
= new DownloadFinishReceiver(); 
 365         IntentFilter filter 
= new IntentFilter(FileDownloader
.getDownloadFinishMessage()); 
 366         filter
.addAction(FileDownloader
.getDownloadAddedMessage()); 
 367         registerReceiver(mDownloadFinishReceiver
, filter
); 
 371     protected void onPostResume() { 
 372         super.onPostResume(); 
 376     public void onPause() { 
 377         if (mDownloadFinishReceiver 
!= null
){ 
 378             unregisterReceiver(mDownloadFinishReceiver
); 
 379             mDownloadFinishReceiver 
= null
; 
 386     private void backToDisplayActivity() { 
 391     public void showDetails(OCFile file
) { 
 392         Intent showDetailsIntent 
= new Intent(this, FileDisplayActivity
.class); 
 393         showDetailsIntent
.setAction(FileDisplayActivity
.ACTION_DETAILS
); 
 394         showDetailsIntent
.putExtra(FileActivity
.EXTRA_FILE
, file
); 
 395         showDetailsIntent
.putExtra(FileActivity
.EXTRA_ACCOUNT
, 
 396                 AccountUtils
.getCurrentOwnCloudAccount(this)); 
 397         startActivity(showDetailsIntent
); 
 398         int pos 
= mPreviewImagePagerAdapter
.getFilePosition(file
); 
 399         file 
= mPreviewImagePagerAdapter
.getFileAt(pos
); 
 404     private void requestForDownload(OCFile file
) { 
 405         if (mDownloaderBinder 
== null
) { 
 406             Log_OC
.d(TAG
, "requestForDownload called without binder to download service"); 
 408         } else if (!mDownloaderBinder
.isDownloading(getAccount(), file
)) { 
 409             Intent i 
= new Intent(this, FileDownloader
.class); 
 410             i
.putExtra(FileDownloader
.EXTRA_ACCOUNT
, getAccount()); 
 411             i
.putExtra(FileDownloader
.EXTRA_FILE
, file
); 
 417      * This method will be invoked when a new page becomes selected. Animation is not necessarily 
 420      *  @param  position        Position index of the new selected page 
 423     public void onPageSelected(int position
) { 
 424         mSavedPosition 
= position
; 
 425         mHasSavedPosition 
= true
; 
 426         if (mDownloaderBinder 
== null
) { 
 427             mRequestWaitingForBinder 
= true
; 
 430             OCFile currentFile 
= mPreviewImagePagerAdapter
.getFileAt(position
);  
 431             getSupportActionBar().setTitle(currentFile
.getFileName()); 
 432             mDrawerToggle
.setDrawerIndicatorEnabled(false
); 
 434             // Call to reset image zoom to initial state 
 435             ((PreviewImagePagerAdapter
) mViewPager
.getAdapter()).resetZoom(); 
 441      * Called when the scroll state changes. Useful for discovering when the user begins dragging,  
 442      * when the pager is automatically settling to the current page. when it is fully stopped/idle. 
 444      * @param   state       The new scroll state (SCROLL_STATE_IDLE, _DRAGGING, _SETTLING 
 447     public void onPageScrollStateChanged(int state
) { 
 451      * This method will be invoked when the current page is scrolled, either as part of a 
 452      * programmatically initiated smooth scroll or a user initiated touch scroll. 
 454      * @param   position                Position index of the first page currently being displayed.  
 455      *                                  Page position+1 will be visible if positionOffset is 
 458      * @param   positionOffset          Value from [0, 1) indicating the offset from the page 
 460      * @param   positionOffsetPixels    Value in pixels indicating the offset from position.  
 463     public void onPageScrolled(int position
, float positionOffset
, int positionOffsetPixels
) { 
 468      * Class waiting for broadcast events from the {@link FileDownloader} service. 
 470      * Updates the UI when a download is started or finished, provided that it is relevant for the 
 471      * folder displayed in the gallery. 
 473     private class DownloadFinishReceiver 
extends BroadcastReceiver 
{ 
 475         public void onReceive(Context context
, Intent intent
) { 
 476             String accountName 
= intent
.getStringExtra(FileDownloader
.ACCOUNT_NAME
); 
 477             String downloadedRemotePath 
= intent
.getStringExtra(FileDownloader
.EXTRA_REMOTE_PATH
); 
 478             if (getAccount().name
.equals(accountName
) &&  
 479                     downloadedRemotePath 
!= null
) { 
 481                 OCFile file 
= getStorageManager().getFileByPath(downloadedRemotePath
); 
 482                 int position 
= mPreviewImagePagerAdapter
.getFilePosition(file
); 
 483                 boolean downloadWasFine 
= intent
.getBooleanExtra( 
 484                         FileDownloader
.EXTRA_DOWNLOAD_RESULT
, false
); 
 485                 //boolean isOffscreen =  Math.abs((mViewPager.getCurrentItem() - position)) 
 486                 // <= mViewPager.getOffscreenPageLimit(); 
 489                         intent
.getAction().equals(FileDownloader
.getDownloadFinishMessage())) { 
 490                     if (downloadWasFine
) { 
 491                         mPreviewImagePagerAdapter
.updateFile(position
, file
);    
 494                         mPreviewImagePagerAdapter
.updateWithDownloadError(position
); 
 496                     mPreviewImagePagerAdapter
.notifyDataSetChanged();   // will trigger the creation 
 500                     Log_OC
.d(TAG
, "Download finished, but the fragment is offscreen"); 
 504             removeStickyBroadcast(intent
); 
 509     @SuppressLint("InlinedApi") 
 510         public void toggleFullScreen() { 
 512         if (isHoneycombOrHigher()) { 
 514             boolean visible 
= (mFullScreenAnchorView
.getSystemUiVisibility() 
 515                     & View
.SYSTEM_UI_FLAG_HIDE_NAVIGATION
) == 0; 
 518                 hideSystemUI(mFullScreenAnchorView
); 
 519                 // actionBar.hide(); // propagated through 
 520                 // OnSystemUiVisibilityChangeListener() 
 522                 showSystemUI(mFullScreenAnchorView
); 
 523                 // actionBar.show(); // propagated through 
 524                 // OnSystemUiVisibilityChangeListener() 
 529             ActionBar actionBar 
= getSupportActionBar(); 
 530             if (!actionBar
.isShowing()) { 
 532                 mDrawerLayout
.setDrawerLockMode(DrawerLayout
.LOCK_MODE_UNLOCKED
); 
 536                 mDrawerLayout
.setDrawerLockMode(DrawerLayout
.LOCK_MODE_LOCKED_CLOSED
); 
 544     protected void onAccountSet(boolean stateWasRecovered
) { 
 545         super.onAccountSet(stateWasRecovered
); 
 546         if (getAccount() != null
) { 
 547             OCFile file 
= getFile(); 
 548             /// Validate handled file  (first image to preview) 
 550                 throw new IllegalStateException("Instanced with a NULL OCFile"); 
 552             if (!file
.isImage()) { 
 553                 throw new IllegalArgumentException("Non-image file passed as argument"); 
 556             // Update file according to DB file, if it is possible 
 557             if (file
.getFileId() > FileDataStorageManager
.ROOT_PARENT_ID
)             
 558                 file 
= getStorageManager().getFileById(file
.getFileId()); 
 561                 /// Refresh the activity according to the Account and OCFile set 
 562                 setFile(file
);  // reset after getting it fresh from storageManager 
 563                 getSupportActionBar().setTitle(getFile().getFileName()); 
 564                 //if (!stateWasRecovered) { 
 569                 // handled file not in the current Account 
 576     public void onBrowsedDownTo(OCFile folder
) { 
 577         // TODO Auto-generated method stub 
 582     public void onTransferStateChanged(OCFile file
, boolean downloading
, boolean uploading
) { 
 583         // TODO Auto-generated method stub 
 588     @SuppressLint("InlinedApi") 
 589         private void hideSystemUI(View anchorView
) { 
 590         anchorView
.setSystemUiVisibility( 
 591                 View
.SYSTEM_UI_FLAG_HIDE_NAVIGATION         
// hides NAVIGATION BAR; Android >= 4.0 
 592             |   View
.SYSTEM_UI_FLAG_FULLSCREEN              
// hides STATUS BAR;     Android >= 4.1 
 593             |   View
.SYSTEM_UI_FLAG_IMMERSIVE               
// stays interactive;    Android >= 4.4 
 594             |   View
.SYSTEM_UI_FLAG_LAYOUT_STABLE           
// draw full window;     Android >= 4.1 
 595             |   View
.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN       
// draw full window;     Android >= 4.1 
 596             |   View
.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION  
// draw full window;     Android >= 4.1 
 600     @SuppressLint("InlinedApi") 
 601     private void showSystemUI(View anchorView
) { 
 602         anchorView
.setSystemUiVisibility( 
 603                 View
.SYSTEM_UI_FLAG_LAYOUT_STABLE           
// draw full window;     Android >= 4.1 
 604             |   View
.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN       
// draw full window;     Android >= 4.1 
 605             |   View
.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION  
// draw full window;     Android >= 4.1 
 610      * Checks if OS version is Honeycomb one or higher 
 614     private boolean isHoneycombOrHigher() { 
 615         if (Build
.VERSION
.SDK_INT 
>= Build
.VERSION_CODES
.HONEYCOMB
) { 
 622     public void allFilesOption(){ 
 623         backToDisplayActivity(); 
 624         super.allFilesOption();