1 /* ownCloud Android client application 
   2  *   Copyright (C) 2011  Bartek Przybylski 
   3  *   Copyright (C) 2012-2014 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
.AuthenticatorException
; 
  26 import android
.accounts
.OperationCanceledException
; 
  27 import android
.content
.ComponentName
; 
  28 import android
.content
.Context
; 
  29 import android
.content
.Intent
; 
  30 import android
.content
.ServiceConnection
; 
  31 import android
.os
.Bundle
; 
  32 import android
.os
.Handler
; 
  33 import android
.os
.IBinder
; 
  34 import android
.support
.v4
.app
.Fragment
; 
  35 import android
.support
.v4
.app
.FragmentManager
; 
  36 import android
.support
.v4
.app
.FragmentTransaction
; 
  37 import android
.widget
.Toast
; 
  39 import com
.actionbarsherlock
.app
.SherlockFragmentActivity
; 
  40 import com
.owncloud
.android
.MainApp
; 
  41 import com
.owncloud
.android
.R
; 
  42 import com
.owncloud
.android
.authentication
.AccountUtils
; 
  43 import com
.owncloud
.android
.authentication
.AuthenticatorActivity
; 
  44 import com
.owncloud
.android
.datamodel
.FileDataStorageManager
; 
  45 import com
.owncloud
.android
.datamodel
.OCFile
; 
  46 import com
.owncloud
.android
.files
.FileOperationsHelper
; 
  47 import com
.owncloud
.android
.files
.services
.FileDownloader
; 
  48 import com
.owncloud
.android
.files
.services
.FileUploader
; 
  49 import com
.owncloud
.android
.files
.services
.FileDownloader
.FileDownloaderBinder
; 
  50 import com
.owncloud
.android
.files
.services
.FileUploader
.FileUploaderBinder
; 
  51 import com
.owncloud
.android
.lib
.common
.operations
.OnRemoteOperationListener
; 
  52 import com
.owncloud
.android
.lib
.common
.operations
.RemoteOperation
; 
  53 import com
.owncloud
.android
.lib
.common
.operations
.RemoteOperationResult
; 
  54 import com
.owncloud
.android
.lib
.common
.operations
.RemoteOperationResult
.ResultCode
; 
  55 import com
.owncloud
.android
.operations
.CreateShareOperation
; 
  56 import com
.owncloud
.android
.operations
.UnshareLinkOperation
; 
  58 import com
.owncloud
.android
.services
.OperationsService
; 
  59 import com
.owncloud
.android
.services
.OperationsService
.OperationsServiceBinder
; 
  60 import com
.owncloud
.android
.ui
.dialog
.LoadingDialog
; 
  61 import com
.owncloud
.android
.utils
.Log_OC
; 
  65  * Activity with common behaviour for activities handling {@link OCFile}s in ownCloud {@link Account}s . 
  67  * @author David A. Velasco 
  69 public class FileActivity 
extends SherlockFragmentActivity 
 
  70 implements OnRemoteOperationListener
, ComponentsGetter 
{ 
  72     public static final String EXTRA_FILE 
= "com.owncloud.android.ui.activity.FILE"; 
  73     public static final String EXTRA_ACCOUNT 
= "com.owncloud.android.ui.activity.ACCOUNT"; 
  74     public static final String EXTRA_WAITING_TO_PREVIEW 
= "com.owncloud.android.ui.activity.WAITING_TO_PREVIEW"; 
  75     public static final String EXTRA_FROM_NOTIFICATION
= "com.owncloud.android.ui.activity.FROM_NOTIFICATION"; 
  77     public static final String TAG 
= FileActivity
.class.getSimpleName(); 
  79     private static final String DIALOG_WAIT_TAG 
= "DIALOG_WAIT"; 
  80     private static final String KEY_WAITING_FOR_OP_ID 
= "WAITING_FOR_OP_ID";; 
  83     /** OwnCloud {@link Account} where the main {@link OCFile} handled by the activity is located. */ 
  84     private Account mAccount
; 
  86     /** Main {@link OCFile} handled by the activity.*/ 
  89     /** Flag to signal that the activity will is finishing to enforce the creation of an ownCloud {@link Account} */ 
  90     private boolean mRedirectingToSetupAccount 
= false
; 
  92     /** Flag to signal when the value of mAccount was set */  
  93     private boolean mAccountWasSet
; 
  95     /** Flag to signal when the value of mAccount was restored from a saved state */  
  96     private boolean mAccountWasRestored
; 
  98     /** Flag to signal if the activity is launched by a notification */ 
  99     private boolean mFromNotification
; 
 101     /** Messages handler associated to the main thread and the life cycle of the activity */ 
 102     private Handler mHandler
; 
 104     /** Access point to the cached database for the current ownCloud {@link Account} */ 
 105     private FileDataStorageManager mStorageManager 
= null
; 
 107     private FileOperationsHelper mFileOperationsHelper
; 
 109     private ServiceConnection mOperationsServiceConnection 
= null
; 
 111     private OperationsServiceBinder mOperationsServiceBinder 
= null
; 
 113     protected FileDownloaderBinder mDownloaderBinder 
= null
; 
 114     protected FileUploaderBinder mUploaderBinder 
= null
; 
 115     private ServiceConnection mDownloadServiceConnection
, mUploadServiceConnection 
= null
; 
 119      * Loads the ownCloud {@link Account} and main {@link OCFile} to be handled by the instance of  
 120      * the {@link FileActivity}. 
 122      * Grants that a valid ownCloud {@link Account} is associated to the instance, or that the user  
 123      * is requested to create a new one. 
 126     protected void onCreate(Bundle savedInstanceState
) { 
 127         super.onCreate(savedInstanceState
); 
 128         mHandler 
= new Handler(); 
 129         mFileOperationsHelper 
= new FileOperationsHelper(this); 
 131         if(savedInstanceState 
!= null
) { 
 132             account 
= savedInstanceState
.getParcelable(FileActivity
.EXTRA_ACCOUNT
); 
 133             mFile 
= savedInstanceState
.getParcelable(FileActivity
.EXTRA_FILE
); 
 134             mFromNotification 
= savedInstanceState
.getBoolean(FileActivity
.EXTRA_FROM_NOTIFICATION
); 
 135             mFileOperationsHelper
.setOpIdWaitingFor( 
 136                     savedInstanceState
.getLong(KEY_WAITING_FOR_OP_ID
, Long
.MAX_VALUE
) 
 139             account 
= getIntent().getParcelableExtra(FileActivity
.EXTRA_ACCOUNT
); 
 140             mFile 
= getIntent().getParcelableExtra(FileActivity
.EXTRA_FILE
); 
 141             mFromNotification 
= getIntent().getBooleanExtra(FileActivity
.EXTRA_FROM_NOTIFICATION
, false
); 
 144         setAccount(account
, savedInstanceState 
!= null
); 
 146         mOperationsServiceConnection 
= new OperationsServiceConnection(); 
 147         bindService(new Intent(this, OperationsService
.class), mOperationsServiceConnection
, Context
.BIND_AUTO_CREATE
); 
 149         mDownloadServiceConnection 
= newTransferenceServiceConnection(); 
 150         if (mDownloadServiceConnection 
!= null
) { 
 151             bindService(new Intent(this, FileDownloader
.class), mDownloadServiceConnection
, Context
.BIND_AUTO_CREATE
); 
 153         mUploadServiceConnection 
= newTransferenceServiceConnection(); 
 154         if (mUploadServiceConnection 
!= null
) { 
 155             bindService(new Intent(this, FileUploader
.class), mUploadServiceConnection
, Context
.BIND_AUTO_CREATE
); 
 162      *  Since ownCloud {@link Account}s can be managed from the system setting menu,  
 163      *  the existence of the {@link Account} associated to the instance must be checked  
 164      *  every time it is restarted. 
 167     protected void onRestart() { 
 169         boolean validAccount 
= (mAccount 
!= null 
&& AccountUtils
.setCurrentOwnCloudAccount(getApplicationContext(), mAccount
.name
)); 
 171             swapToDefaultAccount(); 
 177     protected void onStart() { 
 180         if (mAccountWasSet
) { 
 181             onAccountSet(mAccountWasRestored
); 
 186     protected void onResume() { 
 189         if (mOperationsServiceBinder 
!= null
) { 
 190             doOnResumeAndBound(); 
 196     protected void onPause()  { 
 197         if (mOperationsServiceBinder 
!= null
) { 
 198             mOperationsServiceBinder
.removeOperationListener(this); 
 206     protected void onDestroy() { 
 208         if (mOperationsServiceConnection 
!= null
) { 
 209             unbindService(mOperationsServiceConnection
); 
 210             mOperationsServiceBinder 
= null
; 
 212         if (mDownloadServiceConnection 
!= null
) { 
 213             unbindService(mDownloadServiceConnection
); 
 214             mDownloadServiceConnection 
= null
; 
 216         if (mUploadServiceConnection 
!= null
) { 
 217             unbindService(mUploadServiceConnection
); 
 218             mUploadServiceConnection 
= null
; 
 224      *  Sets and validates the ownCloud {@link Account} associated to the Activity.  
 226      *  If not valid, tries to swap it for other valid and existing ownCloud {@link Account}. 
 228      *  POSTCONDITION: updates {@link #mAccountWasSet} and {@link #mAccountWasRestored}.  
 230      *  @param account          New {@link Account} to set. 
 231      *  @param savedAccount     When 'true', account was retrieved from a saved instance state. 
 233     private void setAccount(Account account
, boolean savedAccount
) { 
 234         Account oldAccount 
= mAccount
; 
 235         boolean validAccount 
= (account 
!= null 
&& AccountUtils
.setCurrentOwnCloudAccount(getApplicationContext(), account
.name
)); 
 238             mAccountWasSet 
= true
; 
 239             mAccountWasRestored 
= (savedAccount 
|| mAccount
.equals(oldAccount
)); 
 242             swapToDefaultAccount(); 
 248      *  Tries to swap the current ownCloud {@link Account} for other valid and existing.  
 250      *  If no valid ownCloud {@link Account} exists, the the user is requested  
 251      *  to create a new ownCloud {@link Account}. 
 253      *  POSTCONDITION: updates {@link #mAccountWasSet} and {@link #mAccountWasRestored}. 
 255      *  @return     'True' if the checked {@link Account} was valid. 
 257     private void swapToDefaultAccount() { 
 258         // default to the most recently used account 
 259         Account newAccount  
= AccountUtils
.getCurrentOwnCloudAccount(getApplicationContext()); 
 260         if (newAccount 
== null
) { 
 261             /// no account available: force account creation 
 262             createFirstAccount(); 
 263             mRedirectingToSetupAccount 
= true
; 
 264             mAccountWasSet 
= false
; 
 265             mAccountWasRestored 
= false
; 
 268             mAccountWasSet 
= true
; 
 269             mAccountWasRestored 
= (newAccount
.equals(mAccount
)); 
 270             mAccount 
= newAccount
; 
 276      * Launches the account creation activity. To use when no ownCloud account is available 
 278     private void createFirstAccount() { 
 279         AccountManager am 
= AccountManager
.get(getApplicationContext()); 
 280         am
.addAccount(MainApp
.getAccountType(),  
 285                         new AccountCreationCallback(),                         
 294     protected void onSaveInstanceState(Bundle outState
) { 
 295         super.onSaveInstanceState(outState
); 
 296         outState
.putParcelable(FileActivity
.EXTRA_FILE
, mFile
); 
 297         outState
.putParcelable(FileActivity
.EXTRA_ACCOUNT
, mAccount
); 
 298         outState
.putBoolean(FileActivity
.EXTRA_FROM_NOTIFICATION
, mFromNotification
); 
 299         outState
.putLong(KEY_WAITING_FOR_OP_ID
, mFileOperationsHelper
.getOpIdWaitingFor()); 
 304      * Getter for the main {@link OCFile} handled by the activity. 
 306      * @return  Main {@link OCFile} handled by the activity. 
 308     public OCFile 
getFile() { 
 314      * Setter for the main {@link OCFile} handled by the activity. 
 316      * @param file  Main {@link OCFile} to be handled by the activity. 
 318     public void setFile(OCFile file
) { 
 324      * Getter for the ownCloud {@link Account} where the main {@link OCFile} handled by the activity is located. 
 326      * @return  OwnCloud {@link Account} where the main {@link OCFile} handled by the activity is located. 
 328     public Account 
getAccount() { 
 333      * @return Value of mFromNotification: True if the Activity is launched by a notification 
 335     public boolean fromNotification() { 
 336         return mFromNotification
; 
 340      * @return  'True' when the Activity is finishing to enforce the setup of a new account. 
 342     protected boolean isRedirectingToSetupAccount() { 
 343         return mRedirectingToSetupAccount
; 
 347     public OperationsServiceBinder 
getOperationsServiceBinder() { 
 348         return mOperationsServiceBinder
; 
 351     protected ServiceConnection 
newTransferenceServiceConnection() { 
 357      * Helper class handling a callback from the {@link AccountManager} after the creation of 
 358      * a new ownCloud {@link Account} finished, successfully or not. 
 360      * At this moment, only called after the creation of the first account. 
 362      * @author David A. Velasco 
 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         } else if (operation 
instanceof CreateShareOperation
) { 
 451             onCreateShareOperationFinish((CreateShareOperation
) operation
, result
); 
 453         } else if (operation 
instanceof UnshareLinkOperation
) { 
 454             onUnshareLinkOperationFinish((UnshareLinkOperation
)operation
, result
); 
 459     private void requestCredentialsUpdate() { 
 460         Intent updateAccountCredentials 
= new Intent(this, AuthenticatorActivity
.class); 
 461         updateAccountCredentials
.putExtra(AuthenticatorActivity
.EXTRA_ACCOUNT
, getAccount()); 
 462         updateAccountCredentials
.putExtra( 
 463                 AuthenticatorActivity
.EXTRA_ACTION
,  
 464                 AuthenticatorActivity
.ACTION_UPDATE_EXPIRED_TOKEN
); 
 465         updateAccountCredentials
.addFlags(Intent
.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
); 
 466         startActivity(updateAccountCredentials
); 
 470     private void onCreateShareOperationFinish(CreateShareOperation operation
, RemoteOperationResult result
) { 
 471         dismissLoadingDialog(); 
 472         if (result
.isSuccess()) { 
 475             Intent sendIntent 
= operation
.getSendIntent(); 
 476             startActivity(sendIntent
); 
 478         } else if (result
.getCode() == ResultCode
.SHARE_NOT_FOUND
)  {        // Error --> SHARE_NOT_FOUND 
 479                 Toast t 
= Toast
.makeText(this, getString(R
.string
.share_link_file_no_exist
), Toast
.LENGTH_LONG
); 
 481         } else {    // Generic error 
 482             // Show a Message, operation finished without success 
 483             Toast t 
= Toast
.makeText(this, getString(R
.string
.share_link_file_error
), Toast
.LENGTH_LONG
); 
 489     private void onUnshareLinkOperationFinish(UnshareLinkOperation operation
, RemoteOperationResult result
) { 
 490         dismissLoadingDialog(); 
 492         if (result
.isSuccess()){ 
 495         } else if (result
.getCode() == ResultCode
.SHARE_NOT_FOUND
)  {        // Error --> SHARE_NOT_FOUND 
 496             Toast t 
= Toast
.makeText(this, getString(R
.string
.unshare_link_file_no_exist
), Toast
.LENGTH_LONG
); 
 498         } else {    // Generic error 
 499             // Show a Message, operation finished without success 
 500             Toast t 
= Toast
.makeText(this, getString(R
.string
.unshare_link_file_error
), Toast
.LENGTH_LONG
); 
 507     private void updateFileFromDB(){ 
 508       OCFile file 
= getStorageManager().getFileByPath(getFile().getRemotePath()); 
 515      * Show loading dialog  
 517     public void showLoadingDialog() { 
 519         LoadingDialog loading 
= new LoadingDialog(getResources().getString(R
.string
.wait_a_moment
)); 
 520         FragmentManager fm 
= getSupportFragmentManager(); 
 521         FragmentTransaction ft 
= fm
.beginTransaction(); 
 522         loading
.show(ft
, DIALOG_WAIT_TAG
); 
 528      * Dismiss loading dialog 
 530     public void dismissLoadingDialog(){ 
 531         Fragment frag 
= getSupportFragmentManager().findFragmentByTag(DIALOG_WAIT_TAG
); 
 533             LoadingDialog loading 
= (LoadingDialog
) frag
; 
 539     private void doOnResumeAndBound() { 
 540         mOperationsServiceBinder
.addOperationListener(FileActivity
.this, mHandler
); 
 541         long waitingForOpId 
= mFileOperationsHelper
.getOpIdWaitingFor(); 
 542         if (waitingForOpId 
<= Integer
.MAX_VALUE
) { 
 543             mOperationsServiceBinder
.dispatchResultIfFinished((int)waitingForOpId
, this); 
 549      * Implements callback methods for service binding. Passed as a parameter to {  
 551     private class OperationsServiceConnection 
implements ServiceConnection 
{ 
 554         public void onServiceConnected(ComponentName component
, IBinder service
) { 
 555             if (component
.equals(new ComponentName(FileActivity
.this, OperationsService
.class))) { 
 556                 Log_OC
.d(TAG
, "Operations service connected"); 
 557                 mOperationsServiceBinder 
= (OperationsServiceBinder
) service
; 
 558                 /*if (!mOperationsServiceBinder.isPerformingBlockingOperation()) { 
 559                     dismissLoadingDialog(); 
 561                 doOnResumeAndBound(); 
 570         public void onServiceDisconnected(ComponentName component
) { 
 571             if (component
.equals(new ComponentName(FileActivity
.this, OperationsService
.class))) { 
 572                 Log_OC
.d(TAG
, "Operations service disconnected"); 
 573                 mOperationsServiceBinder 
= null
; 
 574                 // TODO whatever could be waiting for the service is unbound 
 581     public FileDownloaderBinder 
getFileDownloaderBinder() { 
 582         return mDownloaderBinder
; 
 587     public FileUploaderBinder 
getFileUploaderBinder() { 
 588         return mUploaderBinder
;