1 /* ownCloud Android client application 
   2  *   Copyright (C) 2011  Bartek Przybylski 
   3  *   Copyright (C) 2012-2013 ownCloud Inc. 
   5  *   This program is free software: you can redistribute it and/or modify 
   6  *   it under the terms of the GNU General Public License version 2, 
   7  *   as published by the Free Software Foundation. 
   9  *   This program is distributed in the hope that it will be useful, 
  10  *   but WITHOUT ANY WARRANTY; without even the implied warranty of 
  11  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
  12  *   GNU General Public License for more details. 
  14  *   You should have received a copy of the GNU General Public License 
  15  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. 
  19 package com
.owncloud
.android
.ui
.activity
; 
  21 import android
.accounts
.Account
; 
  22 import android
.accounts
.AccountManager
; 
  23 import android
.accounts
.AccountManagerCallback
; 
  24 import android
.accounts
.AccountManagerFuture
; 
  25 import android
.accounts
.OperationCanceledException
; 
  26 import android
.content
.ComponentName
; 
  27 import android
.content
.Context
; 
  28 import android
.content
.Intent
; 
  29 import android
.content
.ServiceConnection
; 
  30 import android
.os
.Bundle
; 
  31 import android
.os
.Handler
; 
  32 import android
.os
.IBinder
; 
  33 import android
.support
.v4
.app
.Fragment
; 
  34 import android
.support
.v4
.app
.FragmentManager
; 
  35 import android
.support
.v4
.app
.FragmentTransaction
; 
  36 import android
.widget
.Toast
; 
  38 import com
.actionbarsherlock
.app
.SherlockFragmentActivity
; 
  39 import com
.owncloud
.android
.MainApp
; 
  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
.FileOperationsHelper
; 
  45 import com
.owncloud
.android
.files
.services
.FileDownloader
; 
  46 import com
.owncloud
.android
.files
.services
.FileUploader
; 
  47 import com
.owncloud
.android
.files
.services
.FileDownloader
.FileDownloaderBinder
; 
  48 import com
.owncloud
.android
.files
.services
.FileUploader
.FileUploaderBinder
; 
  49 import com
.owncloud
.android
.lib
.common
.operations
.OnRemoteOperationListener
; 
  50 import com
.owncloud
.android
.lib
.common
.operations
.RemoteOperation
; 
  51 import com
.owncloud
.android
.lib
.common
.operations
.RemoteOperationResult
; 
  52 import com
.owncloud
.android
.lib
.common
.operations
.RemoteOperationResult
.ResultCode
; 
  53 import com
.owncloud
.android
.operations
.CreateShareOperation
; 
  54 import com
.owncloud
.android
.operations
.UnshareLinkOperation
; 
  56 import com
.owncloud
.android
.services
.OperationsService
; 
  57 import com
.owncloud
.android
.services
.OperationsService
.OperationsServiceBinder
; 
  58 import com
.owncloud
.android
.ui
.dialog
.LoadingDialog
; 
  59 import com
.owncloud
.android
.utils
.Log_OC
; 
  63  * Activity with common behaviour for activities handling {@link OCFile}s in ownCloud {@link Account}s . 
  65  * @author David A. Velasco 
  67 public class FileActivity 
extends SherlockFragmentActivity 
 
  68 implements OnRemoteOperationListener
, ComponentsGetter 
{ 
  70     public static final String EXTRA_FILE 
= "com.owncloud.android.ui.activity.FILE"; 
  71     public static final String EXTRA_ACCOUNT 
= "com.owncloud.android.ui.activity.ACCOUNT"; 
  72     public static final String EXTRA_WAITING_TO_PREVIEW 
= "com.owncloud.android.ui.activity.WAITING_TO_PREVIEW"; 
  73     public static final String EXTRA_FROM_NOTIFICATION
= "com.owncloud.android.ui.activity.FROM_NOTIFICATION"; 
  75     public static final String TAG 
= FileActivity
.class.getSimpleName(); 
  77     private static final String DIALOG_WAIT_TAG 
= "DIALOG_WAIT"; 
  80     /** OwnCloud {@link Account} where the main {@link OCFile} handled by the activity is located. */ 
  81     private Account mAccount
; 
  83     /** Main {@link OCFile} handled by the activity.*/ 
  86     /** Flag to signal that the activity will is finishing to enforce the creation of an ownCloud {@link Account} */ 
  87     private boolean mRedirectingToSetupAccount 
= false
; 
  89     /** Flag to signal when the value of mAccount was set */  
  90     private boolean mAccountWasSet
; 
  92     /** Flag to signal when the value of mAccount was restored from a saved state */  
  93     private boolean mAccountWasRestored
; 
  95     /** Flag to signal if the activity is launched by a notification */ 
  96     private boolean mFromNotification
; 
  98     /** Messages handler associated to the main thread and the life cycle of the activity */ 
  99     private Handler mHandler
; 
 101     /** Access point to the cached database for the current ownCloud {@link Account} */ 
 102     private FileDataStorageManager mStorageManager 
= null
; 
 104     private FileOperationsHelper mFileOperationsHelper
; 
 106     private ServiceConnection mOperationsServiceConnection 
= null
; 
 108     private OperationsServiceBinder mOperationsServiceBinder 
= null
; 
 110     protected FileDownloaderBinder mDownloaderBinder 
= null
; 
 111     protected FileUploaderBinder mUploaderBinder 
= null
; 
 112     private ServiceConnection mDownloadServiceConnection
, mUploadServiceConnection 
= null
; 
 116      * Loads the ownCloud {@link Account} and main {@link OCFile} to be handled by the instance of  
 117      * the {@link FileActivity}. 
 119      * Grants that a valid ownCloud {@link Account} is associated to the instance, or that the user  
 120      * is requested to create a new one. 
 123     protected void onCreate(Bundle savedInstanceState
) { 
 124         super.onCreate(savedInstanceState
); 
 125         mHandler 
= new Handler(); 
 126         mFileOperationsHelper 
= new FileOperationsHelper(this); 
 128         if(savedInstanceState 
!= null
) { 
 129             account 
= savedInstanceState
.getParcelable(FileActivity
.EXTRA_ACCOUNT
); 
 130             mFile 
= savedInstanceState
.getParcelable(FileActivity
.EXTRA_FILE
); 
 131             mFromNotification 
= savedInstanceState
.getBoolean(FileActivity
.EXTRA_FROM_NOTIFICATION
); 
 133             account 
= getIntent().getParcelableExtra(FileActivity
.EXTRA_ACCOUNT
); 
 134             mFile 
= getIntent().getParcelableExtra(FileActivity
.EXTRA_FILE
); 
 135             mFromNotification 
= getIntent().getBooleanExtra(FileActivity
.EXTRA_FROM_NOTIFICATION
, false
); 
 138         setAccount(account
, savedInstanceState 
!= null
); 
 140         mOperationsServiceConnection 
= new OperationsServiceConnection(); 
 141         bindService(new Intent(this, OperationsService
.class), mOperationsServiceConnection
, Context
.BIND_AUTO_CREATE
); 
 143         mDownloadServiceConnection 
= newTransferenceServiceConnection(); 
 144         if (mDownloadServiceConnection 
!= null
) { 
 145             bindService(new Intent(this, FileDownloader
.class), mDownloadServiceConnection
, Context
.BIND_AUTO_CREATE
); 
 147         mUploadServiceConnection 
= newTransferenceServiceConnection(); 
 148         if (mUploadServiceConnection 
!= null
) { 
 149             bindService(new Intent(this, FileUploader
.class), mUploadServiceConnection
, Context
.BIND_AUTO_CREATE
); 
 156      *  Since ownCloud {@link Account}s can be managed from the system setting menu,  
 157      *  the existence of the {@link Account} associated to the instance must be checked  
 158      *  every time it is restarted. 
 161     protected void onRestart() { 
 163         boolean validAccount 
= (mAccount 
!= null 
&& AccountUtils
.setCurrentOwnCloudAccount(getApplicationContext(), mAccount
.name
)); 
 165             swapToDefaultAccount(); 
 171     protected void onStart() { 
 174         if (mAccountWasSet
) { 
 175             onAccountSet(mAccountWasRestored
); 
 177         if (mOperationsServiceBinder 
!= null
) { 
 178             mOperationsServiceBinder
.addOperationListener(FileActivity
.this, mHandler
); 
 184     protected void onStop() { 
 186         if (mOperationsServiceBinder 
!= null
) { 
 187             mOperationsServiceBinder
.removeOperationListener(this); 
 195     protected void onDestroy() { 
 197         if (mOperationsServiceConnection 
!= null
) { 
 198             unbindService(mOperationsServiceConnection
); 
 199             mOperationsServiceBinder 
= null
; 
 201         if (mDownloadServiceConnection 
!= null
) { 
 202             unbindService(mDownloadServiceConnection
); 
 203             mDownloadServiceConnection 
= null
; 
 205         if (mUploadServiceConnection 
!= null
) { 
 206             unbindService(mUploadServiceConnection
); 
 207             mUploadServiceConnection 
= null
; 
 213      *  Sets and validates the ownCloud {@link Account} associated to the Activity.  
 215      *  If not valid, tries to swap it for other valid and existing ownCloud {@link Account}. 
 217      *  POSTCONDITION: updates {@link #mAccountWasSet} and {@link #mAccountWasRestored}.  
 219      *  @param account          New {@link Account} to set. 
 220      *  @param savedAccount     When 'true', account was retrieved from a saved instance state. 
 222     private void setAccount(Account account
, boolean savedAccount
) { 
 223         Account oldAccount 
= mAccount
; 
 224         boolean validAccount 
= (account 
!= null 
&& AccountUtils
.setCurrentOwnCloudAccount(getApplicationContext(), account
.name
)); 
 227             mAccountWasSet 
= true
; 
 228             mAccountWasRestored 
= (savedAccount 
|| mAccount
.equals(oldAccount
)); 
 231             swapToDefaultAccount(); 
 237      *  Tries to swap the current ownCloud {@link Account} for other valid and existing.  
 239      *  If no valid ownCloud {@link Account} exists, the the user is requested  
 240      *  to create a new ownCloud {@link Account}. 
 242      *  POSTCONDITION: updates {@link #mAccountWasSet} and {@link #mAccountWasRestored}. 
 244      *  @return     'True' if the checked {@link Account} was valid. 
 246     private void swapToDefaultAccount() { 
 247         // default to the most recently used account 
 248         Account newAccount  
= AccountUtils
.getCurrentOwnCloudAccount(getApplicationContext()); 
 249         if (newAccount 
== null
) { 
 250             /// no account available: force account creation 
 251             createFirstAccount(); 
 252             mRedirectingToSetupAccount 
= true
; 
 253             mAccountWasSet 
= false
; 
 254             mAccountWasRestored 
= false
; 
 257             mAccountWasSet 
= true
; 
 258             mAccountWasRestored 
= (newAccount
.equals(mAccount
)); 
 259             mAccount 
= newAccount
; 
 265      * Launches the account creation activity. To use when no ownCloud account is available 
 267     private void createFirstAccount() { 
 268         AccountManager am 
= AccountManager
.get(getApplicationContext()); 
 269         am
.addAccount(MainApp
.getAccountType(),  
 274                         new AccountCreationCallback(),                         
 283     protected void onSaveInstanceState(Bundle outState
) { 
 284         super.onSaveInstanceState(outState
); 
 285         outState
.putParcelable(FileActivity
.EXTRA_FILE
, mFile
); 
 286         outState
.putParcelable(FileActivity
.EXTRA_ACCOUNT
, mAccount
); 
 287         outState
.putBoolean(FileActivity
.EXTRA_FROM_NOTIFICATION
, mFromNotification
); 
 292      * Getter for the main {@link OCFile} handled by the activity. 
 294      * @return  Main {@link OCFile} handled by the activity. 
 296     public OCFile 
getFile() { 
 302      * Setter for the main {@link OCFile} handled by the activity. 
 304      * @param file  Main {@link OCFile} to be handled by the activity. 
 306     public void setFile(OCFile file
) { 
 312      * Getter for the ownCloud {@link Account} where the main {@link OCFile} handled by the activity is located. 
 314      * @return  OwnCloud {@link Account} where the main {@link OCFile} handled by the activity is located. 
 316     public Account 
getAccount() { 
 321      * @return Value of mFromNotification: True if the Activity is launched by a notification 
 323     public boolean fromNotification() { 
 324         return mFromNotification
; 
 328      * @return  'True' when the Activity is finishing to enforce the setup of a new account. 
 330     protected boolean isRedirectingToSetupAccount() { 
 331         return mRedirectingToSetupAccount
; 
 335     public OperationsServiceBinder 
getOperationsServiceBinder() { 
 336         return mOperationsServiceBinder
; 
 339     protected ServiceConnection 
newTransferenceServiceConnection() { 
 345      * Helper class handling a callback from the {@link AccountManager} after the creation of 
 346      * a new ownCloud {@link Account} finished, successfully or not. 
 348      * At this moment, only called after the creation of the first account. 
 350      * @author David A. Velasco 
 352     public class AccountCreationCallback 
implements AccountManagerCallback
<Bundle
> { 
 355         public void run(AccountManagerFuture
<Bundle
> future
) { 
 356             FileActivity
.this.mRedirectingToSetupAccount 
= false
; 
 357             boolean accountWasSet 
= false
; 
 358             if (future 
!= null
) { 
 361                     result 
= future
.getResult(); 
 362                     String name 
= result
.getString(AccountManager
.KEY_ACCOUNT_NAME
); 
 363                     String type 
= result
.getString(AccountManager
.KEY_ACCOUNT_TYPE
); 
 364                     if (AccountUtils
.setCurrentOwnCloudAccount(getApplicationContext(), name
)) { 
 365                         setAccount(new Account(name
, type
), false
); 
 366                         accountWasSet 
= true
; 
 368                 } catch (OperationCanceledException e
) { 
 369                     Log_OC
.d(TAG
, "Account creation canceled"); 
 371                 } catch (Exception e
) { 
 372                     Log_OC
.e(TAG
, "Account creation finished in exception: ", e
); 
 376                 Log_OC
.e(TAG
, "Account creation callback with null bundle"); 
 378             if (!accountWasSet
) { 
 379                 moveTaskToBack(true
); 
 387      *  Called when the ownCloud {@link Account} associated to the Activity was just updated. 
 389      *  Child classes must grant that state depending on the {@link Account} is updated. 
 391     protected void onAccountSet(boolean stateWasRecovered
) { 
 392         if (getAccount() != null
) { 
 393             mStorageManager 
= new FileDataStorageManager(getAccount(), getContentResolver()); 
 396             Log_OC
.wtf(TAG
, "onAccountChanged was called with NULL account associated!"); 
 401     public FileDataStorageManager 
getStorageManager() { 
 402         return mStorageManager
; 
 406     public OnRemoteOperationListener 
getRemoteOperationListener() { 
 411     public Handler 
getHandler() { 
 415     public FileOperationsHelper 
getFileOperationsHelper() { 
 416         return mFileOperationsHelper
; 
 421      * @param operation     Removal operation performed. 
 422      * @param result        Result of the removal. 
 425     public void onRemoteOperationFinish(RemoteOperation operation
, RemoteOperationResult result
) { 
 426         Log_OC
.d(TAG
, "Received result of operation in FileActivity - common behaviour for all the FileActivities "); 
 427         if (operation 
instanceof CreateShareOperation
) { 
 428             onCreateShareOperationFinish((CreateShareOperation
) operation
, result
); 
 430         } else if (operation 
instanceof UnshareLinkOperation
) { 
 431             onUnshareLinkOperationFinish((UnshareLinkOperation
)operation
, result
); 
 436     private void onCreateShareOperationFinish(CreateShareOperation operation
, RemoteOperationResult result
) { 
 437         dismissLoadingDialog(); 
 438         if (result
.isSuccess()) { 
 441             Intent sendIntent 
= operation
.getSendIntent(); 
 442             startActivity(sendIntent
); 
 444         } else if (result
.getCode() == ResultCode
.SHARE_NOT_FOUND
)  {        // Error --> SHARE_NOT_FOUND 
 445                 Toast t 
= Toast
.makeText(this, getString(R
.string
.share_link_file_no_exist
), Toast
.LENGTH_LONG
); 
 447         } else {    // Generic error 
 448             // Show a Message, operation finished without success 
 449             Toast t 
= Toast
.makeText(this, getString(R
.string
.share_link_file_error
), Toast
.LENGTH_LONG
); 
 455     private void onUnshareLinkOperationFinish(UnshareLinkOperation operation
, RemoteOperationResult result
) { 
 456         dismissLoadingDialog(); 
 458         if (result
.isSuccess()){ 
 461         } else if (result
.getCode() == ResultCode
.SHARE_NOT_FOUND
)  {        // Error --> SHARE_NOT_FOUND 
 462             Toast t 
= Toast
.makeText(this, getString(R
.string
.unshare_link_file_no_exist
), Toast
.LENGTH_LONG
); 
 464         } else {    // Generic error 
 465             // Show a Message, operation finished without success 
 466             Toast t 
= Toast
.makeText(this, getString(R
.string
.unshare_link_file_error
), Toast
.LENGTH_LONG
); 
 473     private void updateFileFromDB(){ 
 474       OCFile file 
= getStorageManager().getFileByPath(getFile().getRemotePath()); 
 481      * Show loading dialog  
 483     public void showLoadingDialog() { 
 485         LoadingDialog loading 
= new LoadingDialog(getResources().getString(R
.string
.wait_a_moment
)); 
 486         FragmentManager fm 
= getSupportFragmentManager(); 
 487         FragmentTransaction ft 
= fm
.beginTransaction(); 
 488         loading
.show(ft
, DIALOG_WAIT_TAG
); 
 494      * Dismiss loading dialog 
 496     public void dismissLoadingDialog(){ 
 497         Fragment frag 
= getSupportFragmentManager().findFragmentByTag(DIALOG_WAIT_TAG
); 
 499             LoadingDialog loading 
= (LoadingDialog
) frag
; 
 506      * Implements callback methods for service binding. Passed as a parameter to {  
 508     private class OperationsServiceConnection 
implements ServiceConnection 
{ 
 511         public void onServiceConnected(ComponentName component
, IBinder service
) { 
 512             if (component
.equals(new ComponentName(FileActivity
.this, OperationsService
.class))) { 
 513                 Log_OC
.d(TAG
, "Operations service connected"); 
 514                 mOperationsServiceBinder 
= (OperationsServiceBinder
) service
; 
 515                 mOperationsServiceBinder
.addOperationListener(FileActivity
.this, mHandler
); 
 516                 if (!mOperationsServiceBinder
.isPerformingBlockingOperation()) { 
 517                     dismissLoadingDialog(); 
 527         public void onServiceDisconnected(ComponentName component
) { 
 528             if (component
.equals(new ComponentName(FileActivity
.this, OperationsService
.class))) { 
 529                 Log_OC
.d(TAG
, "Operations service disconnected"); 
 530                 mOperationsServiceBinder 
= null
; 
 531                 // TODO whatever could be waiting for the service is unbound 
 538     public FileDownloaderBinder 
getFileDownloaderBinder() { 
 539         return mDownloaderBinder
; 
 544     public FileUploaderBinder 
getFileUploaderBinder() { 
 545         return mUploaderBinder
;