1 /* ownCloud Android client application 
   3  *   @author David A. Velasco 
   4  *   Copyright (C) 2011  Bartek Przybylski 
   5  *   Copyright (C) 2012-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/>. 
  21 package com
.owncloud
.android
.ui
.activity
; 
  23 import android
.accounts
.Account
; 
  24 import android
.accounts
.AccountManager
; 
  25 import android
.accounts
.AccountManagerCallback
; 
  26 import android
.accounts
.AccountManagerFuture
; 
  27 import android
.accounts
.AuthenticatorException
; 
  28 import android
.accounts
.OperationCanceledException
; 
  29 import android
.content
.ComponentName
; 
  30 import android
.content
.Context
; 
  31 import android
.content
.Intent
; 
  32 import android
.content
.ServiceConnection
; 
  33 import android
.os
.Bundle
; 
  34 import android
.os
.Handler
; 
  35 import android
.os
.IBinder
; 
  36 import android
.support
.v4
.app
.Fragment
; 
  37 import android
.support
.v4
.app
.FragmentManager
; 
  38 import android
.support
.v4
.app
.FragmentTransaction
; 
  39 import android
.widget
.Toast
; 
  41 import com
.actionbarsherlock
.app
.SherlockFragmentActivity
; 
  42 import com
.owncloud
.android
.MainApp
; 
  43 import com
.owncloud
.android
.R
; 
  44 import com
.owncloud
.android
.authentication
.AccountUtils
; 
  45 import com
.owncloud
.android
.authentication
.AuthenticatorActivity
; 
  46 import com
.owncloud
.android
.datamodel
.FileDataStorageManager
; 
  47 import com
.owncloud
.android
.datamodel
.OCFile
; 
  48 import com
.owncloud
.android
.files
.FileOperationsHelper
; 
  49 import com
.owncloud
.android
.files
.services
.FileDownloader
; 
  50 import com
.owncloud
.android
.files
.services
.FileUploader
; 
  51 import com
.owncloud
.android
.files
.services
.FileDownloader
.FileDownloaderBinder
; 
  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
.SynchronizeFolderOperation
; 
  60 import com
.owncloud
.android
.operations
.UnshareLinkOperation
; 
  61 import com
.owncloud
.android
.services
.OperationsService
; 
  62 import com
.owncloud
.android
.services
.OperationsService
.OperationsServiceBinder
; 
  63 import com
.owncloud
.android
.ui
.dialog
.LoadingDialog
; 
  64 import com
.owncloud
.android
.utils
.ErrorMessageAdapter
; 
  68  * Activity with common behaviour for activities handling {@link OCFile}s in ownCloud {@link Account}s . 
  70 public class FileActivity 
extends SherlockFragmentActivity
 
  71         implements OnRemoteOperationListener
, ComponentsGetter 
{ 
  73     public static final String EXTRA_FILE 
= "com.owncloud.android.ui.activity.FILE"; 
  74     public static final String EXTRA_ACCOUNT 
= "com.owncloud.android.ui.activity.ACCOUNT"; 
  75     public static final String EXTRA_WAITING_TO_PREVIEW 
= "com.owncloud.android.ui.activity.WAITING_TO_PREVIEW"; 
  76     public static final String EXTRA_FROM_NOTIFICATION
= "com.owncloud.android.ui.activity.FROM_NOTIFICATION"; 
  78     public static final String TAG 
= FileActivity
.class.getSimpleName(); 
  80     private static final String DIALOG_WAIT_TAG 
= "DIALOG_WAIT"; 
  81     private static final String KEY_WAITING_FOR_OP_ID 
= "WAITING_FOR_OP_ID"; 
  83     protected static final long DELAY_TO_REQUEST_OPERATION_ON_ACTIVITY_RESULTS 
= 200; 
  86     /** OwnCloud {@link Account} where the main {@link OCFile} handled by the activity is located. */ 
  87     private Account mAccount
; 
  89     /** Main {@link OCFile} handled by the activity.*/ 
  92     /** Flag to signal that the activity will is finishing to enforce the creation of an ownCloud {@link Account} */ 
  93     private boolean mRedirectingToSetupAccount 
= false
; 
  95     /** Flag to signal when the value of mAccount was set */  
  96     private boolean mAccountWasSet
; 
  98     /** Flag to signal when the value of mAccount was restored from a saved state */  
  99     private boolean mAccountWasRestored
; 
 101     /** Flag to signal if the activity is launched by a notification */ 
 102     private boolean mFromNotification
; 
 104     /** Messages handler associated to the main thread and the life cycle of the activity */ 
 105     private Handler mHandler
; 
 107     /** Access point to the cached database for the current ownCloud {@link Account} */ 
 108     private FileDataStorageManager mStorageManager 
= null
; 
 110     private FileOperationsHelper mFileOperationsHelper
; 
 112     private ServiceConnection mOperationsServiceConnection 
= null
; 
 114     private OperationsServiceBinder mOperationsServiceBinder 
= null
; 
 116     protected FileDownloaderBinder mDownloaderBinder 
= null
; 
 117     protected FileUploaderBinder mUploaderBinder 
= null
; 
 118     private ServiceConnection mDownloadServiceConnection
, mUploadServiceConnection 
= null
; 
 122      * Loads the ownCloud {@link Account} and main {@link OCFile} to be handled by the instance of  
 123      * the {@link FileActivity}. 
 125      * Grants that a valid ownCloud {@link Account} is associated to the instance, or that the user  
 126      * is requested to create a new one. 
 129     protected void onCreate(Bundle savedInstanceState
) { 
 130         super.onCreate(savedInstanceState
); 
 131         mHandler 
= new Handler(); 
 132         mFileOperationsHelper 
= new FileOperationsHelper(this); 
 134         if(savedInstanceState 
!= null
) { 
 135             account 
= savedInstanceState
.getParcelable(FileActivity
.EXTRA_ACCOUNT
); 
 136             mFile 
= savedInstanceState
.getParcelable(FileActivity
.EXTRA_FILE
); 
 137             mFromNotification 
= savedInstanceState
.getBoolean(FileActivity
.EXTRA_FROM_NOTIFICATION
); 
 138             mFileOperationsHelper
.setOpIdWaitingFor( 
 139                     savedInstanceState
.getLong(KEY_WAITING_FOR_OP_ID
, Long
.MAX_VALUE
) 
 142             account 
= getIntent().getParcelableExtra(FileActivity
.EXTRA_ACCOUNT
); 
 143             mFile 
= getIntent().getParcelableExtra(FileActivity
.EXTRA_FILE
); 
 144             mFromNotification 
= getIntent().getBooleanExtra(FileActivity
.EXTRA_FROM_NOTIFICATION
, false
); 
 147         setAccount(account
, savedInstanceState 
!= null
); 
 149         mOperationsServiceConnection 
= new OperationsServiceConnection(); 
 150         bindService(new Intent(this, OperationsService
.class), mOperationsServiceConnection
, Context
.BIND_AUTO_CREATE
); 
 152         mDownloadServiceConnection 
= newTransferenceServiceConnection(); 
 153         if (mDownloadServiceConnection 
!= null
) { 
 154             bindService(new Intent(this, FileDownloader
.class), mDownloadServiceConnection
, Context
.BIND_AUTO_CREATE
); 
 156         mUploadServiceConnection 
= newTransferenceServiceConnection(); 
 157         if (mUploadServiceConnection 
!= null
) { 
 158             bindService(new Intent(this, FileUploader
.class), mUploadServiceConnection
, Context
.BIND_AUTO_CREATE
); 
 165      *  Since ownCloud {@link Account}s can be managed from the system setting menu,  
 166      *  the existence of the {@link Account} associated to the instance must be checked  
 167      *  every time it is restarted. 
 170     protected void onRestart() { 
 172         boolean validAccount 
= (mAccount 
!= null 
&& AccountUtils
.setCurrentOwnCloudAccount(getApplicationContext(), mAccount
.name
)); 
 174             swapToDefaultAccount(); 
 180     protected void onStart() { 
 183         if (mAccountWasSet
) { 
 184             onAccountSet(mAccountWasRestored
); 
 189     protected void onResume() { 
 192         if (mOperationsServiceBinder 
!= null
) { 
 193             doOnResumeAndBound(); 
 199     protected void onPause()  { 
 201         if (mOperationsServiceBinder 
!= null
) { 
 202             mOperationsServiceBinder
.removeOperationListener(this); 
 210     protected void onDestroy() { 
 211         if (mOperationsServiceConnection 
!= null
) { 
 212             unbindService(mOperationsServiceConnection
); 
 213             mOperationsServiceBinder 
= null
; 
 215         if (mDownloadServiceConnection 
!= null
) { 
 216             unbindService(mDownloadServiceConnection
); 
 217             mDownloadServiceConnection 
= null
; 
 219         if (mUploadServiceConnection 
!= null
) { 
 220             unbindService(mUploadServiceConnection
); 
 221             mUploadServiceConnection 
= null
; 
 229      *  Sets and validates the ownCloud {@link Account} associated to the Activity.  
 231      *  If not valid, tries to swap it for other valid and existing ownCloud {@link Account}. 
 233      *  POSTCONDITION: updates {@link #mAccountWasSet} and {@link #mAccountWasRestored}.  
 235      *  @param account          New {@link Account} to set. 
 236      *  @param savedAccount     When 'true', account was retrieved from a saved instance state. 
 238     private void setAccount(Account account
, boolean savedAccount
) { 
 239         Account oldAccount 
= mAccount
; 
 240         boolean validAccount 
= (account 
!= null 
&& AccountUtils
.setCurrentOwnCloudAccount(getApplicationContext(), account
.name
)); 
 243             mAccountWasSet 
= true
; 
 244             mAccountWasRestored 
= (savedAccount 
|| mAccount
.equals(oldAccount
)); 
 247             swapToDefaultAccount(); 
 253      *  Tries to swap the current ownCloud {@link Account} for other valid and existing.  
 255      *  If no valid ownCloud {@link Account} exists, the the user is requested  
 256      *  to create a new ownCloud {@link Account}. 
 258      *  POSTCONDITION: updates {@link #mAccountWasSet} and {@link #mAccountWasRestored}. 
 260     private void swapToDefaultAccount() { 
 261         // default to the most recently used account 
 262         Account newAccount  
= AccountUtils
.getCurrentOwnCloudAccount(getApplicationContext()); 
 263         if (newAccount 
== null
) { 
 264             /// no account available: force account creation 
 265             createFirstAccount(); 
 266             mRedirectingToSetupAccount 
= true
; 
 267             mAccountWasSet 
= false
; 
 268             mAccountWasRestored 
= false
; 
 271             mAccountWasSet 
= true
; 
 272             mAccountWasRestored 
= (newAccount
.equals(mAccount
)); 
 273             mAccount 
= newAccount
; 
 279      * Launches the account creation activity. To use when no ownCloud account is available 
 281     private void createFirstAccount() { 
 282         AccountManager am 
= AccountManager
.get(getApplicationContext()); 
 283         am
.addAccount(MainApp
.getAccountType(),  
 288                         new AccountCreationCallback(),                         
 297     protected void onSaveInstanceState(Bundle outState
) { 
 298         super.onSaveInstanceState(outState
); 
 299         outState
.putParcelable(FileActivity
.EXTRA_FILE
, mFile
); 
 300         outState
.putParcelable(FileActivity
.EXTRA_ACCOUNT
, mAccount
); 
 301         outState
.putBoolean(FileActivity
.EXTRA_FROM_NOTIFICATION
, mFromNotification
); 
 302         outState
.putLong(KEY_WAITING_FOR_OP_ID
, mFileOperationsHelper
.getOpIdWaitingFor()); 
 307      * Getter for the main {@link OCFile} handled by the activity. 
 309      * @return  Main {@link OCFile} handled by the activity. 
 311     public OCFile 
getFile() { 
 317      * Setter for the main {@link OCFile} handled by the activity. 
 319      * @param file  Main {@link OCFile} to be handled by the activity. 
 321     public void setFile(OCFile file
) { 
 327      * Getter for the ownCloud {@link Account} where the main {@link OCFile} handled by the activity is located. 
 329      * @return  OwnCloud {@link Account} where the main {@link OCFile} handled by the activity is located. 
 331     public Account 
getAccount() { 
 336      * @return Value of mFromNotification: True if the Activity is launched by a notification 
 338     public boolean fromNotification() { 
 339         return mFromNotification
; 
 343      * @return  'True' when the Activity is finishing to enforce the setup of a new account. 
 345     protected boolean isRedirectingToSetupAccount() { 
 346         return mRedirectingToSetupAccount
; 
 350     public OperationsServiceBinder 
getOperationsServiceBinder() { 
 351         return mOperationsServiceBinder
; 
 354     protected ServiceConnection 
newTransferenceServiceConnection() { 
 359      * Helper class handling a callback from the {@link AccountManager} after the creation of 
 360      * a new ownCloud {@link Account} finished, successfully or not. 
 362      * At this moment, only called after the creation of the first account. 
 364     public class AccountCreationCallback 
implements AccountManagerCallback
<Bundle
> { 
 367         public void run(AccountManagerFuture
<Bundle
> future
) { 
 368             FileActivity
.this.mRedirectingToSetupAccount 
= false
; 
 369             boolean accountWasSet 
= false
; 
 370             if (future 
!= null
) { 
 373                     result 
= future
.getResult(); 
 374                     String name 
= result
.getString(AccountManager
.KEY_ACCOUNT_NAME
); 
 375                     String type 
= result
.getString(AccountManager
.KEY_ACCOUNT_TYPE
); 
 376                     if (AccountUtils
.setCurrentOwnCloudAccount(getApplicationContext(), name
)) { 
 377                         setAccount(new Account(name
, type
), false
); 
 378                         accountWasSet 
= true
; 
 380                 } catch (OperationCanceledException e
) { 
 381                     Log_OC
.d(TAG
, "Account creation canceled"); 
 383                 } catch (Exception e
) { 
 384                     Log_OC
.e(TAG
, "Account creation finished in exception: ", e
); 
 388                 Log_OC
.e(TAG
, "Account creation callback with null bundle"); 
 390             if (!accountWasSet
) { 
 391                 moveTaskToBack(true
); 
 399      *  Called when the ownCloud {@link Account} associated to the Activity was just updated. 
 401      *  Child classes must grant that state depending on the {@link Account} is updated. 
 403     protected void onAccountSet(boolean stateWasRecovered
) { 
 404         if (getAccount() != null
) { 
 405             mStorageManager 
= new FileDataStorageManager(getAccount(), getContentResolver()); 
 408             Log_OC
.wtf(TAG
, "onAccountChanged was called with NULL account associated!"); 
 413     public FileDataStorageManager 
getStorageManager() { 
 414         return mStorageManager
; 
 418     public OnRemoteOperationListener 
getRemoteOperationListener() { 
 423     public Handler 
getHandler() { 
 427     public FileOperationsHelper 
getFileOperationsHelper() { 
 428         return mFileOperationsHelper
; 
 433      * @param operation     Removal operation performed. 
 434      * @param result        Result of the removal. 
 437     public void onRemoteOperationFinish(RemoteOperation operation
, RemoteOperationResult result
) { 
 438         Log_OC
.d(TAG
, "Received result of operation in FileActivity - common behaviour for all the FileActivities "); 
 440         mFileOperationsHelper
.setOpIdWaitingFor(Long
.MAX_VALUE
); 
 442         if (!result
.isSuccess() && ( 
 443                 result
.getCode() == ResultCode
.UNAUTHORIZED 
||  
 444                 result
.isIdPRedirection() || 
 445                 (result
.isException() && result
.getException() instanceof AuthenticatorException
) 
 448             requestCredentialsUpdate(); 
 450             if (result
.getCode() == ResultCode
.UNAUTHORIZED
) { 
 451                 dismissLoadingDialog(); 
 452                 Toast t 
= Toast
.makeText(this, ErrorMessageAdapter
.getErrorCauseMessage(result
, operation
, getResources()),  
 457         } else if (operation 
instanceof CreateShareOperation
) { 
 458             onCreateShareOperationFinish((CreateShareOperation
) operation
, result
); 
 460         } else if (operation 
instanceof UnshareLinkOperation
) { 
 461             onUnshareLinkOperationFinish((UnshareLinkOperation
)operation
, result
); 
 463         } else if (operation 
instanceof SynchronizeFolderOperation
) { 
 464             onSynchronizeFolderOperationFinish((SynchronizeFolderOperation
)operation
, result
); 
 469     protected void requestCredentialsUpdate() { 
 470         Intent updateAccountCredentials 
= new Intent(this, AuthenticatorActivity
.class); 
 471         updateAccountCredentials
.putExtra(AuthenticatorActivity
.EXTRA_ACCOUNT
, getAccount()); 
 472         updateAccountCredentials
.putExtra( 
 473                 AuthenticatorActivity
.EXTRA_ACTION
,  
 474                 AuthenticatorActivity
.ACTION_UPDATE_EXPIRED_TOKEN
); 
 475         updateAccountCredentials
.addFlags(Intent
.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
); 
 476         startActivity(updateAccountCredentials
); 
 480     private void onCreateShareOperationFinish(CreateShareOperation operation
, RemoteOperationResult result
) { 
 481         dismissLoadingDialog(); 
 482         if (result
.isSuccess()) { 
 485             Intent sendIntent 
= operation
.getSendIntent(); 
 486             startActivity(sendIntent
); 
 489             Toast t 
= Toast
.makeText(this, ErrorMessageAdapter
.getErrorCauseMessage(result
, operation
, getResources()),  
 496     private void onUnshareLinkOperationFinish(UnshareLinkOperation operation
, RemoteOperationResult result
) { 
 497         dismissLoadingDialog(); 
 499         if (result
.isSuccess()){ 
 503             Toast t 
= Toast
.makeText(this, ErrorMessageAdapter
.getErrorCauseMessage(result
, operation
, getResources()),  
 509     private void onSynchronizeFolderOperationFinish(SynchronizeFolderOperation operation
, RemoteOperationResult result
) { 
 510         if (!result
.isSuccess() && result
.getCode() != ResultCode
.CANCELLED
){ 
 511             Toast t 
= Toast
.makeText(this, ErrorMessageAdapter
.getErrorCauseMessage(result
, operation
, getResources()), 
 517     protected void updateFileFromDB(){ 
 518         OCFile file 
= getFile(); 
 520             file 
= getStorageManager().getFileByPath(file
.getRemotePath()); 
 526      * Show loading dialog  
 528     public void showLoadingDialog() { 
 530         LoadingDialog loading 
= new LoadingDialog(getResources().getString(R
.string
.wait_a_moment
)); 
 531         FragmentManager fm 
= getSupportFragmentManager(); 
 532         FragmentTransaction ft 
= fm
.beginTransaction(); 
 533         loading
.show(ft
, DIALOG_WAIT_TAG
); 
 539      * Dismiss loading dialog 
 541     public void dismissLoadingDialog(){ 
 542         Fragment frag 
= getSupportFragmentManager().findFragmentByTag(DIALOG_WAIT_TAG
); 
 544             LoadingDialog loading 
= (LoadingDialog
) frag
; 
 550     private void doOnResumeAndBound() { 
 551         mOperationsServiceBinder
.addOperationListener(FileActivity
.this, mHandler
); 
 552         long waitingForOpId 
= mFileOperationsHelper
.getOpIdWaitingFor(); 
 553         if (waitingForOpId 
<= Integer
.MAX_VALUE
) { 
 554             boolean wait 
= mOperationsServiceBinder
.dispatchResultIfFinished((int)waitingForOpId
, this); 
 556                 dismissLoadingDialog(); 
 563      * Implements callback methods for service binding. Passed as a parameter to {  
 565     private class OperationsServiceConnection 
implements ServiceConnection 
{ 
 568         public void onServiceConnected(ComponentName component
, IBinder service
) { 
 569             if (component
.equals(new ComponentName(FileActivity
.this, OperationsService
.class))) { 
 570                 Log_OC
.d(TAG
, "Operations service connected"); 
 571                 mOperationsServiceBinder 
= (OperationsServiceBinder
) service
; 
 572                 /*if (!mOperationsServiceBinder.isPerformingBlockingOperation()) { 
 573                     dismissLoadingDialog(); 
 575                 doOnResumeAndBound(); 
 584         public void onServiceDisconnected(ComponentName component
) { 
 585             if (component
.equals(new ComponentName(FileActivity
.this, OperationsService
.class))) { 
 586                 Log_OC
.d(TAG
, "Operations service disconnected"); 
 587                 mOperationsServiceBinder 
= null
; 
 588                 // TODO whatever could be waiting for the service is unbound 
 595     public FileDownloaderBinder 
getFileDownloaderBinder() { 
 596         return mDownloaderBinder
; 
 601     public FileUploaderBinder 
getFileUploaderBinder() { 
 602         return mUploaderBinder
;