2  *   ownCloud Android client application 
   4  *   @author David A. Velasco 
   5  *   Copyright (C) 2011  Bartek Przybylski 
   6  *   Copyright (C) 2015 ownCloud Inc. 
   8  *   This program is free software: you can redistribute it and/or modify 
   9  *   it under the terms of the GNU General Public License version 2, 
  10  *   as published by the Free Software Foundation. 
  12  *   This program is distributed in the hope that it will be useful, 
  13  *   but WITHOUT ANY WARRANTY; without even the implied warranty of 
  14  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
  15  *   GNU General Public License for more details. 
  17  *   You should have received a copy of the GNU General Public License 
  18  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. 
  22 package com
.owncloud
.android
.ui
.activity
; 
  24 import android
.accounts
.Account
; 
  25 import android
.accounts
.AccountManager
; 
  26 import android
.accounts
.AccountManagerCallback
; 
  27 import android
.accounts
.AccountManagerFuture
; 
  28 import android
.accounts
.AuthenticatorException
; 
  29 import android
.accounts
.OperationCanceledException
; 
  30 import android
.content
.ComponentName
; 
  31 import android
.content
.Context
; 
  32 import android
.content
.Intent
; 
  33 import android
.content
.ServiceConnection
; 
  34 import android
.os
.Bundle
; 
  35 import android
.os
.Handler
; 
  36 import android
.os
.IBinder
; 
  37 import android
.support
.v4
.app
.Fragment
; 
  38 import android
.support
.v4
.app
.FragmentManager
; 
  39 import android
.support
.v4
.app
.FragmentTransaction
; 
  40 import android
.support
.v7
.app
.AppCompatActivity
; 
  41 import android
.widget
.Toast
; 
  43 import com
.owncloud
.android
.MainApp
; 
  44 import com
.owncloud
.android
.R
; 
  45 import com
.owncloud
.android
.authentication
.AccountUtils
; 
  46 import com
.owncloud
.android
.authentication
.AuthenticatorActivity
; 
  47 import com
.owncloud
.android
.datamodel
.FileDataStorageManager
; 
  48 import com
.owncloud
.android
.datamodel
.OCFile
; 
  49 import com
.owncloud
.android
.files
.FileOperationsHelper
; 
  50 import com
.owncloud
.android
.files
.services
.FileDownloader
; 
  51 import com
.owncloud
.android
.files
.services
.FileUploader
; 
  52 import com
.owncloud
.android
.files
.services
.FileDownloader
.FileDownloaderBinder
; 
  53 import com
.owncloud
.android
.files
.services
.FileUploader
.FileUploaderBinder
; 
  54 import com
.owncloud
.android
.lib
.common
.operations
.OnRemoteOperationListener
; 
  55 import com
.owncloud
.android
.lib
.common
.operations
.RemoteOperation
; 
  56 import com
.owncloud
.android
.lib
.common
.operations
.RemoteOperationResult
; 
  57 import com
.owncloud
.android
.lib
.common
.operations
.RemoteOperationResult
.ResultCode
; 
  58 import com
.owncloud
.android
.lib
.common
.utils
.Log_OC
; 
  59 import com
.owncloud
.android
.operations
.CreateShareOperation
; 
  60 import com
.owncloud
.android
.operations
.SynchronizeFolderOperation
; 
  61 import com
.owncloud
.android
.operations
.UnshareLinkOperation
; 
  62 import com
.owncloud
.android
.services
.OperationsService
; 
  63 import com
.owncloud
.android
.services
.OperationsService
.OperationsServiceBinder
; 
  64 import com
.owncloud
.android
.ui
.dialog
.LoadingDialog
; 
  65 import com
.owncloud
.android
.ui
.dialog
.SharePasswordDialogFragment
; 
  66 import com
.owncloud
.android
.utils
.ErrorMessageAdapter
; 
  70  * Activity with common behaviour for activities handling {@link OCFile}s in ownCloud 
  73 public class FileActivity 
extends AppCompatActivity
 
  74         implements OnRemoteOperationListener
, ComponentsGetter 
{ 
  76     public static final String EXTRA_FILE 
= "com.owncloud.android.ui.activity.FILE"; 
  77     public static final String EXTRA_ACCOUNT 
= "com.owncloud.android.ui.activity.ACCOUNT"; 
  78     public static final String EXTRA_WAITING_TO_PREVIEW 
= 
  79             "com.owncloud.android.ui.activity.WAITING_TO_PREVIEW"; 
  80     public static final String EXTRA_FROM_NOTIFICATION 
= 
  81             "com.owncloud.android.ui.activity.FROM_NOTIFICATION"; 
  83     public static final String TAG 
= FileActivity
.class.getSimpleName(); 
  85     private static final String DIALOG_WAIT_TAG 
= "DIALOG_WAIT"; 
  86     private static final String KEY_WAITING_FOR_OP_ID 
= "WAITING_FOR_OP_ID"; 
  87     private static final String DIALOG_SHARE_PASSWORD 
= "DIALOG_SHARE_PASSWORD"; 
  88     private static final String KEY_TRY_SHARE_AGAIN 
= "TRY_SHARE_AGAIN"; 
  90     protected static final long DELAY_TO_REQUEST_OPERATION_ON_ACTIVITY_RESULTS 
= 200; 
  93     /** OwnCloud {@link Account} where the main {@link OCFile} handled by the activity is located.*/ 
  94     private Account mAccount
; 
  96     /** Main {@link OCFile} handled by the activity.*/ 
  99     /** Flag to signal that the activity will is finishing to enforce the creation of an ownCloud 
 101     private boolean mRedirectingToSetupAccount 
= false
; 
 103     /** Flag to signal when the value of mAccount was set */  
 104     protected boolean mAccountWasSet
; 
 106     /** Flag to signal when the value of mAccount was restored from a saved state */  
 107     protected boolean mAccountWasRestored
; 
 109     /** Flag to signal if the activity is launched by a notification */ 
 110     private boolean mFromNotification
; 
 112     /** Messages handler associated to the main thread and the life cycle of the activity */ 
 113     private Handler mHandler
; 
 115     /** Access point to the cached database for the current ownCloud {@link Account} */ 
 116     private FileDataStorageManager mStorageManager 
= null
; 
 118     private FileOperationsHelper mFileOperationsHelper
; 
 120     private ServiceConnection mOperationsServiceConnection 
= null
; 
 122     private OperationsServiceBinder mOperationsServiceBinder 
= null
; 
 124     protected FileDownloaderBinder mDownloaderBinder 
= null
; 
 125     protected FileUploaderBinder mUploaderBinder 
= null
; 
 126     private ServiceConnection mDownloadServiceConnection
, mUploadServiceConnection 
= null
; 
 128     private boolean mTryShareAgain 
= false
; 
 132      * Loads the ownCloud {@link Account} and main {@link OCFile} to be handled by the instance of  
 133      * the {@link FileActivity}. 
 135      * Grants that a valid ownCloud {@link Account} is associated to the instance, or that the user  
 136      * is requested to create a new one. 
 139     protected void onCreate(Bundle savedInstanceState
) { 
 140         super.onCreate(savedInstanceState
); 
 141         mHandler 
= new Handler(); 
 142         mFileOperationsHelper 
= new FileOperationsHelper(this); 
 143         Account account 
= null
; 
 144         if(savedInstanceState 
!= null
) { 
 145             mFile 
= savedInstanceState
.getParcelable(FileActivity
.EXTRA_FILE
); 
 146             mFromNotification 
= savedInstanceState
.getBoolean(FileActivity
.EXTRA_FROM_NOTIFICATION
); 
 147             mFileOperationsHelper
.setOpIdWaitingFor( 
 148                     savedInstanceState
.getLong(KEY_WAITING_FOR_OP_ID
, Long
.MAX_VALUE
) 
 150             mTryShareAgain 
= savedInstanceState
.getBoolean(KEY_TRY_SHARE_AGAIN
); 
 152             account 
= getIntent().getParcelableExtra(FileActivity
.EXTRA_ACCOUNT
); 
 153             mFile 
= getIntent().getParcelableExtra(FileActivity
.EXTRA_FILE
); 
 154             mFromNotification 
= getIntent().getBooleanExtra(FileActivity
.EXTRA_FROM_NOTIFICATION
, 
 158         AccountUtils
.updateAccountVersion(this); // best place, before any access to AccountManager 
 161         setAccount(account
, savedInstanceState 
!= null
); 
 163         mOperationsServiceConnection 
= new OperationsServiceConnection(); 
 164         bindService(new Intent(this, OperationsService
.class), mOperationsServiceConnection
, 
 165                 Context
.BIND_AUTO_CREATE
); 
 167         mDownloadServiceConnection 
= newTransferenceServiceConnection(); 
 168         if (mDownloadServiceConnection 
!= null
) { 
 169             bindService(new Intent(this, FileDownloader
.class), mDownloadServiceConnection
, 
 170                     Context
.BIND_AUTO_CREATE
); 
 172         mUploadServiceConnection 
= newTransferenceServiceConnection(); 
 173         if (mUploadServiceConnection 
!= null
) { 
 174             bindService(new Intent(this, FileUploader
.class), mUploadServiceConnection
, 
 175                     Context
.BIND_AUTO_CREATE
); 
 181     protected void onNewIntent (Intent intent
) { 
 182         Log_OC
.v(TAG
, "onNewIntent() start"); 
 183         Account current 
= AccountUtils
.getCurrentOwnCloudAccount(this); 
 184         if (current 
!= null 
&& mAccount 
!= null 
&& !mAccount
.name
.equals(current
.name
)) { 
 187         Log_OC
.v(TAG
, "onNewIntent() stop"); 
 191      *  Since ownCloud {@link Account}s can be managed from the system setting menu,  
 192      *  the existence of the {@link Account} associated to the instance must be checked  
 193      *  every time it is restarted. 
 196     protected void onRestart() { 
 197         Log_OC
.v(TAG
, "onRestart() start"); 
 199         boolean validAccount 
= (mAccount 
!= null 
&& AccountUtils
.exists(mAccount
, this)); 
 201             swapToDefaultAccount(); 
 203         Log_OC
.v(TAG
, "onRestart() end"); 
 208     protected void onStart() { 
 211         if (mAccountWasSet
) { 
 212             onAccountSet(mAccountWasRestored
); 
 217     protected void onResume() { 
 220         if (mOperationsServiceBinder 
!= null
) { 
 221             doOnResumeAndBound(); 
 226     protected void onPause()  { 
 227         if (mOperationsServiceBinder 
!= null
) { 
 228             mOperationsServiceBinder
.removeOperationListener(this); 
 236     protected void onDestroy() { 
 237         if (mOperationsServiceConnection 
!= null
) { 
 238             unbindService(mOperationsServiceConnection
); 
 239             mOperationsServiceBinder 
= null
; 
 241         if (mDownloadServiceConnection 
!= null
) { 
 242             unbindService(mDownloadServiceConnection
); 
 243             mDownloadServiceConnection 
= null
; 
 245         if (mUploadServiceConnection 
!= null
) { 
 246             unbindService(mUploadServiceConnection
); 
 247             mUploadServiceConnection 
= null
; 
 255      *  Sets and validates the ownCloud {@link Account} associated to the Activity.  
 257      *  If not valid, tries to swap it for other valid and existing ownCloud {@link Account}. 
 259      *  POSTCONDITION: updates {@link #mAccountWasSet} and {@link #mAccountWasRestored}.  
 261      *  @param account          New {@link Account} to set. 
 262      *  @param savedAccount     When 'true', account was retrieved from a saved instance state. 
 264     protected void setAccount(Account account
, boolean savedAccount
) { 
 265         Account oldAccount 
= mAccount
; 
 266         boolean validAccount 
= 
 267                 (account 
!= null 
&& AccountUtils
.setCurrentOwnCloudAccount(getApplicationContext(), 
 271             mAccountWasSet 
= true
; 
 272             mAccountWasRestored 
= (savedAccount 
|| mAccount
.equals(oldAccount
)); 
 275             swapToDefaultAccount(); 
 281      *  Tries to swap the current ownCloud {@link Account} for other valid and existing.  
 283      *  If no valid ownCloud {@link Account} exists, the the user is requested  
 284      *  to create a new ownCloud {@link Account}. 
 286      *  POSTCONDITION: updates {@link #mAccountWasSet} and {@link #mAccountWasRestored}. 
 288     private void swapToDefaultAccount() { 
 289         // default to the most recently used account 
 290         Account newAccount  
= AccountUtils
.getCurrentOwnCloudAccount(getApplicationContext()); 
 291         if (newAccount 
== null
) { 
 292             /// no account available: force account creation 
 293             createFirstAccount(); 
 294             mRedirectingToSetupAccount 
= true
; 
 295             mAccountWasSet 
= false
; 
 296             mAccountWasRestored 
= false
; 
 299             mAccountWasSet 
= true
; 
 300             mAccountWasRestored 
= (newAccount
.equals(mAccount
)); 
 301             mAccount 
= newAccount
; 
 307      * Launches the account creation activity. To use when no ownCloud account is available 
 309     private void createFirstAccount() { 
 310         AccountManager am 
= AccountManager
.get(getApplicationContext()); 
 311         am
.addAccount(MainApp
.getAccountType(),  
 316                         new AccountCreationCallback(),                         
 325     protected void onSaveInstanceState(Bundle outState
) { 
 326         super.onSaveInstanceState(outState
); 
 327         outState
.putParcelable(FileActivity
.EXTRA_FILE
, mFile
); 
 328         outState
.putBoolean(FileActivity
.EXTRA_FROM_NOTIFICATION
, mFromNotification
); 
 329         outState
.putLong(KEY_WAITING_FOR_OP_ID
, mFileOperationsHelper
.getOpIdWaitingFor()); 
 330         outState
.putBoolean(KEY_TRY_SHARE_AGAIN
, mTryShareAgain
); 
 335      * Getter for the main {@link OCFile} handled by the activity. 
 337      * @return  Main {@link OCFile} handled by the activity. 
 339     public OCFile 
getFile() { 
 345      * Setter for the main {@link OCFile} handled by the activity. 
 347      * @param file  Main {@link OCFile} to be handled by the activity. 
 349     public void setFile(OCFile file
) { 
 355      * Getter for the ownCloud {@link Account} where the main {@link OCFile} handled by the activity 
 358      * @return  OwnCloud {@link Account} where the main {@link OCFile} handled by the activity 
 361     public Account 
getAccount() { 
 365     protected void setAccount(Account account
) { 
 370      * @return Value of mFromNotification: True if the Activity is launched by a notification 
 372     public boolean fromNotification() { 
 373         return mFromNotification
; 
 377      * @return  'True' when the Activity is finishing to enforce the setup of a new account. 
 379     protected boolean isRedirectingToSetupAccount() { 
 380         return mRedirectingToSetupAccount
; 
 383     public boolean isTryShareAgain(){ 
 384         return mTryShareAgain
; 
 387     public void setTryShareAgain(boolean tryShareAgain
) { 
 388        mTryShareAgain 
= tryShareAgain
; 
 391     public OperationsServiceBinder 
getOperationsServiceBinder() { 
 392         return mOperationsServiceBinder
; 
 395     protected ServiceConnection 
newTransferenceServiceConnection() { 
 400      * Helper class handling a callback from the {@link AccountManager} after the creation of 
 401      * a new ownCloud {@link Account} finished, successfully or not. 
 403      * At this moment, only called after the creation of the first account. 
 405     public class AccountCreationCallback 
implements AccountManagerCallback
<Bundle
> { 
 408         public void run(AccountManagerFuture
<Bundle
> future
) { 
 409             FileActivity
.this.mRedirectingToSetupAccount 
= false
; 
 410             boolean accountWasSet 
= false
; 
 411             if (future 
!= null
) { 
 414                     result 
= future
.getResult(); 
 415                     String name 
= result
.getString(AccountManager
.KEY_ACCOUNT_NAME
); 
 416                     String type 
= result
.getString(AccountManager
.KEY_ACCOUNT_TYPE
); 
 417                     if (AccountUtils
.setCurrentOwnCloudAccount(getApplicationContext(), name
)) { 
 418                         setAccount(new Account(name
, type
), false
); 
 419                         accountWasSet 
= true
; 
 421                 } catch (OperationCanceledException e
) { 
 422                     Log_OC
.d(TAG
, "Account creation canceled"); 
 424                 } catch (Exception e
) { 
 425                     Log_OC
.e(TAG
, "Account creation finished in exception: ", e
); 
 429                 Log_OC
.e(TAG
, "Account creation callback with null bundle"); 
 431             if (!accountWasSet
) { 
 432                 moveTaskToBack(true
); 
 440      *  Called when the ownCloud {@link Account} associated to the Activity was just updated. 
 442      *  Child classes must grant that state depending on the {@link Account} is updated. 
 444     protected void onAccountSet(boolean stateWasRecovered
) { 
 445         if (getAccount() != null
) { 
 446             mStorageManager 
= new FileDataStorageManager(getAccount(), getContentResolver()); 
 449             Log_OC
.wtf(TAG
, "onAccountChanged was called with NULL account associated!"); 
 454     public FileDataStorageManager 
getStorageManager() { 
 455         return mStorageManager
; 
 459     public OnRemoteOperationListener 
getRemoteOperationListener() { 
 464     public Handler 
getHandler() { 
 468     public FileOperationsHelper 
getFileOperationsHelper() { 
 469         return mFileOperationsHelper
; 
 474      * @param operation     Removal operation performed. 
 475      * @param result        Result of the removal. 
 478     public void onRemoteOperationFinish(RemoteOperation operation
, RemoteOperationResult result
) { 
 479         Log_OC
.d(TAG
, "Received result of operation in FileActivity - common behaviour for all the " + 
 482         mFileOperationsHelper
.setOpIdWaitingFor(Long
.MAX_VALUE
); 
 484         if (!result
.isSuccess() && ( 
 485                 result
.getCode() == ResultCode
.UNAUTHORIZED 
||  
 486                 result
.isIdPRedirection() || 
 487                 (result
.isException() && result
.getException() instanceof AuthenticatorException
) 
 490             requestCredentialsUpdate(); 
 492             if (result
.getCode() == ResultCode
.UNAUTHORIZED
) { 
 493                 dismissLoadingDialog(); 
 494                 Toast t 
= Toast
.makeText(this, ErrorMessageAdapter
.getErrorCauseMessage(result
, 
 495                                 operation
, getResources()), 
 499             mTryShareAgain 
= false
; 
 501         } else if (operation 
instanceof CreateShareOperation
) { 
 502             onCreateShareOperationFinish((CreateShareOperation
) operation
, result
); 
 504         } else if (operation 
instanceof UnshareLinkOperation
) { 
 505             onUnshareLinkOperationFinish((UnshareLinkOperation
)operation
, result
); 
 507         } else if (operation 
instanceof SynchronizeFolderOperation
) { 
 508             onSynchronizeFolderOperationFinish((SynchronizeFolderOperation
)operation
, result
); 
 513     protected void requestCredentialsUpdate() { 
 514         Intent updateAccountCredentials 
= new Intent(this, AuthenticatorActivity
.class); 
 515         updateAccountCredentials
.putExtra(AuthenticatorActivity
.EXTRA_ACCOUNT
, getAccount()); 
 516         updateAccountCredentials
.putExtra( 
 517                 AuthenticatorActivity
.EXTRA_ACTION
,  
 518                 AuthenticatorActivity
.ACTION_UPDATE_EXPIRED_TOKEN
); 
 519         updateAccountCredentials
.addFlags(Intent
.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
); 
 520         startActivity(updateAccountCredentials
); 
 524     private void onCreateShareOperationFinish(CreateShareOperation operation
, 
 525                                               RemoteOperationResult result
) { 
 526         dismissLoadingDialog(); 
 527         if (result
.isSuccess()) { 
 528             mTryShareAgain 
= false
; 
 531             Intent sendIntent 
= operation
.getSendIntent(); 
 532             startActivity(sendIntent
); 
 534             // Detect Failure (403) --> needs Password 
 535             if (result
.getCode() == ResultCode
.SHARE_FORBIDDEN
) { 
 536                 if (!isTryShareAgain()) { 
 537                     SharePasswordDialogFragment dialog 
= 
 538                             SharePasswordDialogFragment
.newInstance(new OCFile(operation
.getPath()), 
 539                                     operation
.getSendIntent()); 
 540                     dialog
.show(getSupportFragmentManager(), DIALOG_SHARE_PASSWORD
); 
 542                     Toast t 
= Toast
.makeText(this, 
 543                         ErrorMessageAdapter
.getErrorCauseMessage(result
, operation
, getResources()), 
 546                     mTryShareAgain 
= false
; 
 549                 Toast t 
= Toast
.makeText(this, 
 550                         ErrorMessageAdapter
.getErrorCauseMessage(result
, operation
, getResources()), 
 558     private void onUnshareLinkOperationFinish(UnshareLinkOperation operation
, 
 559                                               RemoteOperationResult result
) { 
 560         dismissLoadingDialog(); 
 562         if (result
.isSuccess()){ 
 566             Toast t 
= Toast
.makeText(this, ErrorMessageAdapter
.getErrorCauseMessage(result
, 
 567                             operation
, getResources()), Toast
.LENGTH_LONG
); 
 572     private void onSynchronizeFolderOperationFinish( 
 573             SynchronizeFolderOperation operation
, RemoteOperationResult result
 
 575         if (!result
.isSuccess() && result
.getCode() != ResultCode
.CANCELLED
){ 
 576             Toast t 
= Toast
.makeText(this, ErrorMessageAdapter
.getErrorCauseMessage(result
, 
 577                             operation
, getResources()), Toast
.LENGTH_LONG
); 
 582     protected void updateFileFromDB(){ 
 583         OCFile file 
= getFile(); 
 585             file 
= getStorageManager().getFileByPath(file
.getRemotePath()); 
 591      * Show loading dialog  
 593     public void showLoadingDialog() { 
 595         LoadingDialog loading 
= new LoadingDialog(getResources().getString(R
.string
.wait_a_moment
)); 
 596         FragmentManager fm 
= getSupportFragmentManager(); 
 597         FragmentTransaction ft 
= fm
.beginTransaction(); 
 598         loading
.show(ft
, DIALOG_WAIT_TAG
); 
 604      * Dismiss loading dialog 
 606     public void dismissLoadingDialog(){ 
 607         Fragment frag 
= getSupportFragmentManager().findFragmentByTag(DIALOG_WAIT_TAG
); 
 609             LoadingDialog loading 
= (LoadingDialog
) frag
; 
 615     private void doOnResumeAndBound() { 
 616         mOperationsServiceBinder
.addOperationListener(FileActivity
.this, mHandler
); 
 617         long waitingForOpId 
= mFileOperationsHelper
.getOpIdWaitingFor(); 
 618         if (waitingForOpId 
<= Integer
.MAX_VALUE
) { 
 619             boolean wait 
= mOperationsServiceBinder
.dispatchResultIfFinished((int)waitingForOpId
, 
 622                 dismissLoadingDialog(); 
 629      * Implements callback methods for service binding. Passed as a parameter to {  
 631     private class OperationsServiceConnection 
implements ServiceConnection 
{ 
 634         public void onServiceConnected(ComponentName component
, IBinder service
) { 
 635             if (component
.equals(new ComponentName(FileActivity
.this, OperationsService
.class))) { 
 636                 Log_OC
.d(TAG
, "Operations service connected"); 
 637                 mOperationsServiceBinder 
= (OperationsServiceBinder
) service
; 
 638                 /*if (!mOperationsServiceBinder.isPerformingBlockingOperation()) { 
 639                     dismissLoadingDialog(); 
 641                 doOnResumeAndBound(); 
 650         public void onServiceDisconnected(ComponentName component
) { 
 651             if (component
.equals(new ComponentName(FileActivity
.this, OperationsService
.class))) { 
 652                 Log_OC
.d(TAG
, "Operations service disconnected"); 
 653                 mOperationsServiceBinder 
= null
; 
 654                 // TODO whatever could be waiting for the service is unbound 
 661     public FileDownloaderBinder 
getFileDownloaderBinder() { 
 662         return mDownloaderBinder
; 
 667     public FileUploaderBinder 
getFileUploaderBinder() { 
 668         return mUploaderBinder
;