2  *   ownCloud Android client application 
   4  *   Copyright (C) 2015 ownCloud Inc. 
   6  *   This program is free software: you can redistribute it and/or modify 
   7  *   it under the terms of the GNU General Public License version 2, 
   8  *   as published by the Free Software Foundation. 
  10  *   This program is distributed in the hope that it will be useful, 
  11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of 
  12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
  13  *   GNU General Public License for more details. 
  15  *   You should have received a copy of the GNU General Public License 
  16  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. 
  20 package com
.owncloud
.android
.services
; 
  22 import android
.accounts
.Account
; 
  23 import android
.accounts
.AccountsException
; 
  24 import android
.accounts
.AuthenticatorException
; 
  25 import android
.accounts
.OperationCanceledException
; 
  26 import android
.app
.Service
; 
  27 import android
.content
.Intent
; 
  28 import android
.net
.Uri
; 
  29 import android
.os
.Binder
; 
  30 import android
.os
.Handler
; 
  31 import android
.os
.HandlerThread
; 
  32 import android
.os
.IBinder
; 
  33 import android
.os
.Looper
; 
  34 import android
.os
.Message
; 
  35 import android
.os
.Process
; 
  36 import android
.util
.Pair
; 
  38 import com
.owncloud
.android
.MainApp
; 
  39 import com
.owncloud
.android
.R
; 
  40 import com
.owncloud
.android
.datamodel
.FileDataStorageManager
; 
  41 import com
.owncloud
.android
.datamodel
.OCFile
; 
  42 import com
.owncloud
.android
.lib
.common
.OwnCloudAccount
; 
  43 import com
.owncloud
.android
.lib
.common
.OwnCloudClient
; 
  44 import com
.owncloud
.android
.lib
.common
.OwnCloudClientManagerFactory
; 
  45 import com
.owncloud
.android
.lib
.common
.OwnCloudCredentials
; 
  46 import com
.owncloud
.android
.lib
.common
.OwnCloudCredentialsFactory
; 
  47 import com
.owncloud
.android
.lib
.common
.accounts
.AccountUtils
.AccountNotFoundException
; 
  48 import com
.owncloud
.android
.lib
.common
.operations
.OnRemoteOperationListener
; 
  49 import com
.owncloud
.android
.lib
.common
.operations
.RemoteOperation
; 
  50 import com
.owncloud
.android
.lib
.common
.operations
.RemoteOperationResult
; 
  51 import com
.owncloud
.android
.lib
.common
.utils
.Log_OC
; 
  52 import com
.owncloud
.android
.lib
.resources
.shares
.ShareType
; 
  53 import com
.owncloud
.android
.lib
.resources
.status
.OwnCloudVersion
; 
  54 import com
.owncloud
.android
.lib
.resources
.users
.GetRemoteUserNameOperation
; 
  55 import com
.owncloud
.android
.operations
.CopyFileOperation
; 
  56 import com
.owncloud
.android
.operations
.CreateFolderOperation
; 
  57 import com
.owncloud
.android
.operations
.CreateShareViaLinkOperation
; 
  58 import com
.owncloud
.android
.operations
.CreateShareWithShareeOperation
; 
  59 import com
.owncloud
.android
.operations
.GetServerInfoOperation
; 
  60 import com
.owncloud
.android
.operations
.MoveFileOperation
; 
  61 import com
.owncloud
.android
.operations
.OAuth2GetAccessToken
; 
  62 import com
.owncloud
.android
.operations
.RemoveFileOperation
; 
  63 import com
.owncloud
.android
.operations
.RenameFileOperation
; 
  64 import com
.owncloud
.android
.operations
.SynchronizeFileOperation
; 
  65 import com
.owncloud
.android
.operations
.SynchronizeFolderOperation
; 
  66 import com
.owncloud
.android
.operations
.UnshareOperation
; 
  67 import com
.owncloud
.android
.operations
.UpdateShareViaLinkOperation
; 
  68 import com
.owncloud
.android
.operations
.common
.SyncOperation
; 
  70 import java
.io
.IOException
; 
  71 import java
.util
.Calendar
; 
  72 import java
.util
.Iterator
; 
  73 import java
.util
.concurrent
.ConcurrentHashMap
; 
  74 import java
.util
.concurrent
.ConcurrentLinkedQueue
; 
  75 import java
.util
.concurrent
.ConcurrentMap
; 
  77 public class OperationsService 
extends Service 
{ 
  79     private static final String TAG 
= OperationsService
.class.getSimpleName(); 
  81     public static final String EXTRA_ACCOUNT 
= "ACCOUNT"; 
  82     public static final String EXTRA_SERVER_URL 
= "SERVER_URL"; 
  83     public static final String EXTRA_OAUTH2_QUERY_PARAMETERS 
= "OAUTH2_QUERY_PARAMETERS"; 
  84     public static final String EXTRA_REMOTE_PATH 
= "REMOTE_PATH"; 
  85     public static final String EXTRA_SEND_INTENT 
= "SEND_INTENT"; 
  86     public static final String EXTRA_NEWNAME 
= "NEWNAME"; 
  87     public static final String EXTRA_REMOVE_ONLY_LOCAL 
= "REMOVE_LOCAL_COPY"; 
  88     public static final String EXTRA_CREATE_FULL_PATH 
= "CREATE_FULL_PATH"; 
  89     public static final String EXTRA_SYNC_FILE_CONTENTS 
= "SYNC_FILE_CONTENTS"; 
  90     public static final String EXTRA_RESULT 
= "RESULT"; 
  91     public static final String EXTRA_NEW_PARENT_PATH 
= "NEW_PARENT_PATH"; 
  92     public static final String EXTRA_FILE 
= "FILE"; 
  93     public static final String EXTRA_SHARE_PASSWORD 
= "SHARE_PASSWORD"; 
  94     public static final String EXTRA_SHARE_TYPE 
= "SHARE_TYPE"; 
  95     public static final String EXTRA_SHARE_WITH 
= "SHARE_WITH"; 
  96     public static final String EXTRA_SHARE_EXPIRATION_YEAR 
= "SHARE_EXPIRATION_YEAR"; 
  97     public static final String EXTRA_SHARE_EXPIRATION_MONTH_OF_YEAR 
= "SHARE_EXPIRATION_MONTH_OF_YEAR"; 
  98     public static final String EXTRA_SHARE_EXPIRATION_DAY_OF_MONTH 
= "SHARE_EXPIRATION_DAY_OF_MONTH"; 
 100     public static final String EXTRA_COOKIE 
= "COOKIE"; 
 102     public static final String ACTION_CREATE_SHARE_VIA_LINK 
= "CREATE_SHARE_VIA_LINK"; 
 103     public static final String ACTION_CREATE_SHARE_WITH_SHAREE 
= "CREATE_SHARE_WITH_SHAREE"; 
 104     public static final String ACTION_UNSHARE 
= "UNSHARE"; 
 105     public static final String ACTION_UPDATE_SHARE 
= "UPDATE_SHARE"; 
 106     public static final String ACTION_GET_SERVER_INFO 
= "GET_SERVER_INFO"; 
 107     public static final String ACTION_OAUTH2_GET_ACCESS_TOKEN 
= "OAUTH2_GET_ACCESS_TOKEN"; 
 108     public static final String ACTION_GET_USER_NAME 
= "GET_USER_NAME"; 
 109     public static final String ACTION_RENAME 
= "RENAME"; 
 110     public static final String ACTION_REMOVE 
= "REMOVE"; 
 111     public static final String ACTION_CREATE_FOLDER 
= "CREATE_FOLDER"; 
 112     public static final String ACTION_SYNC_FILE 
= "SYNC_FILE"; 
 113     public static final String ACTION_SYNC_FOLDER 
= "SYNC_FOLDER"; 
 114     public static final String ACTION_MOVE_FILE 
= "MOVE_FILE"; 
 115     public static final String ACTION_COPY_FILE 
= "COPY_FILE"; 
 117     public static final String ACTION_OPERATION_ADDED 
= OperationsService
.class.getName() + 
 119     public static final String ACTION_OPERATION_FINISHED 
= OperationsService
.class.getName() + 
 120             ".OPERATION_FINISHED"; 
 123     private ConcurrentMap
<Integer
, Pair
<RemoteOperation
, RemoteOperationResult
>> 
 124             mUndispatchedFinishedOperations 
= 
 125             new ConcurrentHashMap
<Integer
, Pair
<RemoteOperation
, RemoteOperationResult
>>(); 
 127     private static class Target 
{ 
 128         public Uri mServerUrl 
= null
; 
 129         public Account mAccount 
= null
; 
 130         public String mCookie 
= null
; 
 132         public Target(Account account
, Uri serverUrl
, String cookie
) { 
 134             mServerUrl 
= serverUrl
; 
 139     private ServiceHandler mOperationsHandler
; 
 140     private OperationsServiceBinder mOperationsBinder
; 
 142     private SyncFolderHandler mSyncFolderHandler
; 
 145      * Service initialization 
 148     public void onCreate() { 
 150         Log_OC
.d(TAG
, "Creating service"); 
 152         /// First worker thread for most of operations  
 153         HandlerThread thread 
= new HandlerThread("Operations thread", 
 154                 Process
.THREAD_PRIORITY_BACKGROUND
); 
 156         mOperationsHandler 
= new ServiceHandler(thread
.getLooper(), this); 
 157         mOperationsBinder 
= new OperationsServiceBinder(mOperationsHandler
); 
 159         /// Separated worker thread for download of folders (WIP) 
 160         thread 
= new HandlerThread("Syncfolder thread", Process
.THREAD_PRIORITY_BACKGROUND
); 
 162         mSyncFolderHandler 
= new SyncFolderHandler(thread
.getLooper(), this); 
 167      * Entry point to add a new operation to the queue of operations. 
 169      * New operations are added calling to startService(), resulting in a call to this method. 
 170      * This ensures the service will keep on working although the caller activity goes away. 
 173     public int onStartCommand(Intent intent
, int flags
, int startId
) { 
 174         Log_OC
.d(TAG
, "Starting command with id " + startId
); 
 176         // WIP: for the moment, only SYNC_FOLDER is expected here; 
 177         // the rest of the operations are requested through the Binder 
 178         if (ACTION_SYNC_FOLDER
.equals(intent
.getAction())) { 
 180             if (!intent
.hasExtra(EXTRA_ACCOUNT
) || !intent
.hasExtra(EXTRA_REMOTE_PATH
)) { 
 181                 Log_OC
.e(TAG
, "Not enough information provided in intent"); 
 182                 return START_NOT_STICKY
; 
 184             Account account 
= intent
.getParcelableExtra(EXTRA_ACCOUNT
); 
 185             String remotePath 
= intent
.getStringExtra(EXTRA_REMOTE_PATH
); 
 187             Pair
<Account
, String
> itemSyncKey 
=  new Pair
<Account 
, String
>(account
, remotePath
); 
 189             Pair
<Target
, RemoteOperation
> itemToQueue 
= newOperation(intent
); 
 190             if (itemToQueue 
!= null
) { 
 191                 mSyncFolderHandler
.add(account
, remotePath
, 
 192                         (SynchronizeFolderOperation
)itemToQueue
.second
); 
 193                 Message msg 
= mSyncFolderHandler
.obtainMessage(); 
 195                 msg
.obj 
= itemSyncKey
; 
 196                 mSyncFolderHandler
.sendMessage(msg
); 
 200             Message msg 
= mOperationsHandler
.obtainMessage(); 
 202             mOperationsHandler
.sendMessage(msg
); 
 205         return START_NOT_STICKY
; 
 209     public void onDestroy() { 
 210         Log_OC
.v(TAG
, "Destroying service" ); 
 213             OwnCloudClientManagerFactory
.getDefaultSingleton(). 
 214                     saveAllClients(this, MainApp
.getAccountType()); 
 216             // TODO - get rid of these exceptions 
 217         } catch (AccountNotFoundException e
) { 
 219         } catch (AuthenticatorException e
) { 
 221         } catch (OperationCanceledException e
) { 
 223         } catch (IOException e
) { 
 227         mUndispatchedFinishedOperations
.clear(); 
 229         mOperationsBinder 
= null
; 
 231         mOperationsHandler
.getLooper().quit(); 
 232         mOperationsHandler 
= null
; 
 234         mSyncFolderHandler
.getLooper().quit(); 
 235         mSyncFolderHandler 
= null
; 
 241      * Provides a binder object that clients can use to perform actions on the queue of operations, 
 242      * except the addition of new operations. 
 245     public IBinder 
onBind(Intent intent
) { 
 246         return mOperationsBinder
; 
 251      * Called when ALL the bound clients were unbound. 
 254     public boolean onUnbind(Intent intent
) { 
 255         mOperationsBinder
.clearListeners(); 
 256         return false
;   // not accepting rebinding (default behaviour) 
 261      * Binder to let client components to perform actions on the queue of operations. 
 263      * It provides by itself the available operations. 
 265     public class OperationsServiceBinder 
extends Binder 
/* implements OnRemoteOperationListener */ { 
 268          * Map of listeners that will be reported about the end of operations from a 
 269          * {@link OperationsServiceBinder} instance 
 271         private final ConcurrentMap
<OnRemoteOperationListener
, Handler
> mBoundListeners 
= 
 272                 new ConcurrentHashMap
<OnRemoteOperationListener
, Handler
>(); 
 274         private ServiceHandler mServiceHandler 
= null
; 
 276         public OperationsServiceBinder(ServiceHandler serviceHandler
) { 
 277             mServiceHandler 
= serviceHandler
; 
 282          * Cancels a pending or current synchronization. 
 284          * @param account       ownCloud account where the remote folder is stored. 
 285          * @param file          A folder in the queue of pending synchronizations 
 287         public void cancel(Account account
, OCFile file
) { 
 288             mSyncFolderHandler
.cancel(account
, file
); 
 292         public void clearListeners() { 
 294             mBoundListeners
.clear(); 
 299          * Adds a listener interested in being reported about the end of operations. 
 301          * @param listener          Object to notify about the end of operations. 
 302          * @param callbackHandler   {@link Handler} to access the listener without 
 303          *                                         breaking Android threading protection. 
 305         public void addOperationListener (OnRemoteOperationListener listener
, 
 306                                           Handler callbackHandler
) { 
 307             synchronized (mBoundListeners
) { 
 308                 mBoundListeners
.put(listener
, callbackHandler
); 
 314          * Removes a listener from the list of objects interested in the being reported about 
 315          * the end of operations. 
 317          * @param listener      Object to notify about progress of transfer.     
 319         public void removeOperationListener(OnRemoteOperationListener listener
) { 
 320             synchronized (mBoundListeners
) { 
 321                 mBoundListeners
.remove(listener
); 
 327          * TODO - IMPORTANT: update implementation when more operations are moved into the service 
 329          * @return  'True' when an operation that enforces the user to wait for completion is 
 332         public boolean isPerformingBlockingOperation() { 
 333             return (!mServiceHandler
.mPendingOperations
.isEmpty()); 
 338          * Creates and adds to the queue a new operation, as described by operationIntent. 
 340          * Calls startService to make the operation is processed by the ServiceHandler. 
 342          * @param operationIntent       Intent describing a new operation to queue and execute. 
 343          * @return                      Identifier of the operation created, or null if failed. 
 345         public long queueNewOperation(Intent operationIntent
) { 
 346             Pair
<Target
, RemoteOperation
> itemToQueue 
= newOperation(operationIntent
); 
 347             if (itemToQueue 
!= null
) { 
 348                 mServiceHandler
.mPendingOperations
.add(itemToQueue
); 
 349                 startService(new Intent(OperationsService
.this, OperationsService
.class)); 
 350                 return itemToQueue
.second
.hashCode(); 
 353                 return Long
.MAX_VALUE
; 
 358         public boolean dispatchResultIfFinished(int operationId
, 
 359                                                 OnRemoteOperationListener listener
) { 
 360             Pair
<RemoteOperation
, RemoteOperationResult
> undispatched 
=  
 361                     mUndispatchedFinishedOperations
.remove(operationId
); 
 362             if (undispatched 
!= null
) { 
 363                 listener
.onRemoteOperationFinish(undispatched
.first
, undispatched
.second
); 
 365                 //Log_OC.wtf(TAG, "Sending callback later"); 
 367                 return (!mServiceHandler
.mPendingOperations
.isEmpty()); 
 373          * Returns True when the file described by 'file' in the ownCloud account 'account' is 
 374          * downloading or waiting to download. 
 376          * If 'file' is a directory, returns 'true' if some of its descendant files is downloading 
 377          * or waiting to download. 
 379          * @param account       ownCloud account where the remote file is stored. 
 380          * @param remotePath    Path of the folder to check if something is synchronizing 
 381          *                      / downloading / uploading inside. 
 383         public boolean isSynchronizing(Account account
, String remotePath
) { 
 384             return mSyncFolderHandler
.isSynchronizing(account
, remotePath
); 
 391      * Operations worker. Performs the pending operations in the order they were requested. 
 393      * Created with the Looper of a new thread, started in {@link OperationsService#onCreate()}.  
 395     private static class ServiceHandler 
extends Handler 
{ 
 396         // don't make it a final class, and don't remove the static ; lint will warn about a p 
 397         // ossible memory leak 
 400         OperationsService mService
; 
 403         private ConcurrentLinkedQueue
<Pair
<Target
, RemoteOperation
>> mPendingOperations 
= 
 404                 new ConcurrentLinkedQueue
<Pair
<Target
, RemoteOperation
>>(); 
 405         private RemoteOperation mCurrentOperation 
= null
; 
 406         private Target mLastTarget 
= null
; 
 407         private OwnCloudClient mOwnCloudClient 
= null
; 
 408         private FileDataStorageManager mStorageManager
; 
 411         public ServiceHandler(Looper looper
, OperationsService service
) { 
 413             if (service 
== null
) { 
 414                 throw new IllegalArgumentException("Received invalid NULL in parameter 'service'"); 
 420         public void handleMessage(Message msg
) { 
 422             Log_OC
.d(TAG
, "Stopping after command with id " + msg
.arg1
); 
 423             mService
.stopSelf(msg
.arg1
); 
 428          * Performs the next operation in the queue 
 430         private void nextOperation() { 
 432             //Log_OC.wtf(TAG, "nextOperation init" ); 
 434             Pair
<Target
, RemoteOperation
> next 
= null
; 
 435             synchronized(mPendingOperations
) { 
 436                 next 
= mPendingOperations
.peek(); 
 441                 mCurrentOperation 
= next
.second
; 
 442                 RemoteOperationResult result 
= null
; 
 444                     /// prepare client object to send the request to the ownCloud server 
 445                     if (mLastTarget 
== null 
|| !mLastTarget
.equals(next
.first
)) { 
 446                         mLastTarget 
= next
.first
; 
 447                         if (mLastTarget
.mAccount 
!= null
) { 
 448                             OwnCloudAccount ocAccount 
= new OwnCloudAccount(mLastTarget
.mAccount
, 
 450                             mOwnCloudClient 
= OwnCloudClientManagerFactory
.getDefaultSingleton(). 
 451                                     getClientFor(ocAccount
, mService
); 
 453                             OwnCloudVersion version 
= com
.owncloud
.android
.authentication
.AccountUtils
.getServerVersion( 
 456                             mOwnCloudClient
.setOwnCloudVersion(version
); 
 458                             mStorageManager 
= new FileDataStorageManager( 
 459                                     mLastTarget
.mAccount
,  
 460                                     mService
.getContentResolver() 
 463                             OwnCloudCredentials credentials 
= null
; 
 464                             if (mLastTarget
.mCookie 
!= null 
&& 
 465                                     mLastTarget
.mCookie
.length() > 0) { 
 466                                 // just used for GetUserName 
 467                                 // TODO refactor to run GetUserName as AsyncTask in the context of 
 468                                 // AuthenticatorActivity 
 469                                 credentials 
= OwnCloudCredentialsFactory
.newSamlSsoCredentials( 
 471                                         mLastTarget
.mCookie
);   // SAML SSO 
 473                             OwnCloudAccount ocAccount 
= new OwnCloudAccount( 
 474                                     mLastTarget
.mServerUrl
, credentials
); 
 475                             mOwnCloudClient 
= OwnCloudClientManagerFactory
.getDefaultSingleton(). 
 476                                     getClientFor(ocAccount
, mService
); 
 477                             mStorageManager 
= null
; 
 481                     /// perform the operation 
 482                     if (mCurrentOperation 
instanceof SyncOperation
) { 
 483                         result 
= ((SyncOperation
)mCurrentOperation
).execute(mOwnCloudClient
, 
 486                         result 
= mCurrentOperation
.execute(mOwnCloudClient
); 
 489                 } catch (AccountsException e
) { 
 490                     if (mLastTarget
.mAccount 
== null
) { 
 491                         Log_OC
.e(TAG
, "Error while trying to get authorization for a NULL account", 
 494                         Log_OC
.e(TAG
, "Error while trying to get authorization for " + 
 495                                 mLastTarget
.mAccount
.name
, e
); 
 497                     result 
= new RemoteOperationResult(e
); 
 499                 } catch (IOException e
) { 
 500                     if (mLastTarget
.mAccount 
== null
) { 
 501                         Log_OC
.e(TAG
, "Error while trying to get authorization for a NULL account", 
 504                         Log_OC
.e(TAG
, "Error while trying to get authorization for " + 
 505                                 mLastTarget
.mAccount
.name
, e
); 
 507                     result 
= new RemoteOperationResult(e
); 
 508                 } catch (Exception e
) { 
 509                     if (mLastTarget
.mAccount 
== null
) { 
 510                         Log_OC
.e(TAG
, "Unexpected error for a NULL account", e
); 
 512                         Log_OC
.e(TAG
, "Unexpected error for " + mLastTarget
.mAccount
.name
, e
); 
 514                     result 
= new RemoteOperationResult(e
); 
 517                     synchronized(mPendingOperations
) { 
 518                         mPendingOperations
.poll(); 
 522                 //sendBroadcastOperationFinished(mLastTarget, mCurrentOperation, result); 
 523                 mService
.dispatchResultToOperationListeners(mCurrentOperation
, result
); 
 533      * Creates a new operation, as described by operationIntent. 
 535      * TODO - move to ServiceHandler (probably) 
 537      * @param operationIntent       Intent describing a new operation to queue and execute. 
 538      * @return                      Pair with the new operation object and the information about its 
 541     private Pair
<Target 
, RemoteOperation
> newOperation(Intent operationIntent
) { 
 542         RemoteOperation operation 
= null
; 
 543         Target target 
= null
; 
 545             if (!operationIntent
.hasExtra(EXTRA_ACCOUNT
) &&  
 546                     !operationIntent
.hasExtra(EXTRA_SERVER_URL
)) { 
 547                 Log_OC
.e(TAG
, "Not enough information provided in intent"); 
 550                 Account account 
= operationIntent
.getParcelableExtra(EXTRA_ACCOUNT
); 
 551                 String serverUrl 
= operationIntent
.getStringExtra(EXTRA_SERVER_URL
); 
 552                 String cookie 
= operationIntent
.getStringExtra(EXTRA_COOKIE
); 
 555                         (serverUrl 
== null
) ? null 
: Uri
.parse(serverUrl
), 
 559                 String action 
= operationIntent
.getAction(); 
 560                 if (action
.equals(ACTION_CREATE_SHARE_VIA_LINK
)) {  // Create public share via link 
 561                     String remotePath 
= operationIntent
.getStringExtra(EXTRA_REMOTE_PATH
); 
 562                     String password 
= operationIntent
.getStringExtra(EXTRA_SHARE_PASSWORD
); 
 563                     Intent sendIntent 
= operationIntent
.getParcelableExtra(EXTRA_SEND_INTENT
); 
 564                     if (remotePath
.length() > 0) { 
 565                         operation 
= new CreateShareViaLinkOperation( 
 572                 } else if (ACTION_UPDATE_SHARE
.equals(action
)) { 
 573                     String remotePath 
= operationIntent
.getStringExtra(EXTRA_REMOTE_PATH
); 
 574                     if (remotePath
.length() > 0) { 
 575                         operation 
= new UpdateShareViaLinkOperation(remotePath
); 
 577                         String password 
= operationIntent
.getStringExtra(EXTRA_SHARE_PASSWORD
); 
 578                         ((UpdateShareViaLinkOperation
)operation
).setPassword(password
); 
 580                         int year 
= operationIntent
.getIntExtra(EXTRA_SHARE_EXPIRATION_YEAR
, 0); 
 582                             // expiration date is set 
 583                             int monthOfYear 
= operationIntent
.getIntExtra( 
 584                                     EXTRA_SHARE_EXPIRATION_MONTH_OF_YEAR
, 0 
 586                             int dayOfMonth 
= operationIntent
.getIntExtra( 
 587                                     EXTRA_SHARE_EXPIRATION_DAY_OF_MONTH
, 1 
 589                             Calendar expirationDate 
= Calendar
.getInstance(); 
 590                             expirationDate
.set(Calendar
.YEAR
, year
); 
 591                             expirationDate
.set(Calendar
.MONTH
, monthOfYear
); 
 592                             expirationDate
.set(Calendar
.DAY_OF_MONTH
, dayOfMonth
); 
 593                             ((UpdateShareViaLinkOperation
)operation
).setExpirationDate( 
 597                         } else if (year 
< 0) { 
 598                             // expiration date to be cleared 
 599                             Calendar zeroDate 
= Calendar
.getInstance(); 
 601                             ((UpdateShareViaLinkOperation
)operation
).setExpirationDate( 
 605                         } // else, no update on expiration date 
 608                 } else if (action
.equals(ACTION_CREATE_SHARE_WITH_SHAREE
)) { 
 609                     // Create private share with user or group 
 610                     String remotePath 
= operationIntent
.getStringExtra(EXTRA_REMOTE_PATH
); 
 611                     String shareeName 
= operationIntent
.getStringExtra(EXTRA_SHARE_WITH
); 
 612                     ShareType shareType 
= (ShareType
) operationIntent
.getSerializableExtra(EXTRA_SHARE_TYPE
); 
 613                     if (remotePath
.length() > 0) { 
 614                         operation 
= new CreateShareWithShareeOperation( 
 621                 } else if (action
.equals(ACTION_UNSHARE
)) {  // Unshare file 
 622                     String remotePath 
= operationIntent
.getStringExtra(EXTRA_REMOTE_PATH
); 
 623                     ShareType shareType 
= (ShareType
) operationIntent
. 
 624                             getSerializableExtra(EXTRA_SHARE_TYPE
); 
 625                     String shareWith 
= operationIntent
.getStringExtra(EXTRA_SHARE_WITH
); 
 626                     if (remotePath
.length() > 0) { 
 627                         operation 
= new UnshareOperation( 
 631                                 OperationsService
.this 
 635                 } else if (action
.equals(ACTION_GET_SERVER_INFO
)) {  
 636                     // check OC server and get basic information from it 
 637                     operation 
= new GetServerInfoOperation(serverUrl
, OperationsService
.this); 
 639                 } else if (action
.equals(ACTION_OAUTH2_GET_ACCESS_TOKEN
)) { 
 640                     /// GET ACCESS TOKEN to the OAuth server 
 641                     String oauth2QueryParameters 
= 
 642                             operationIntent
.getStringExtra(EXTRA_OAUTH2_QUERY_PARAMETERS
); 
 643                     operation 
= new OAuth2GetAccessToken( 
 644                             getString(R
.string
.oauth2_client_id
),  
 645                             getString(R
.string
.oauth2_redirect_uri
),        
 646                             getString(R
.string
.oauth2_grant_type
), 
 647                             oauth2QueryParameters
); 
 649                 } else if (action
.equals(ACTION_GET_USER_NAME
)) { 
 651                     operation 
= new GetRemoteUserNameOperation(); 
 653                 } else if (action
.equals(ACTION_RENAME
)) { 
 654                     // Rename file or folder 
 655                     String remotePath 
= operationIntent
.getStringExtra(EXTRA_REMOTE_PATH
); 
 656                     String newName 
= operationIntent
.getStringExtra(EXTRA_NEWNAME
); 
 657                     operation 
= new RenameFileOperation(remotePath
, newName
); 
 659                 } else if (action
.equals(ACTION_REMOVE
)) { 
 660                     // Remove file or folder 
 661                     String remotePath 
= operationIntent
.getStringExtra(EXTRA_REMOTE_PATH
); 
 662                     boolean onlyLocalCopy 
= operationIntent
.getBooleanExtra(EXTRA_REMOVE_ONLY_LOCAL
, 
 664                     operation 
= new RemoveFileOperation(remotePath
, onlyLocalCopy
); 
 666                 } else if (action
.equals(ACTION_CREATE_FOLDER
)) { 
 668                     String remotePath 
= operationIntent
.getStringExtra(EXTRA_REMOTE_PATH
); 
 669                     boolean createFullPath 
= operationIntent
.getBooleanExtra(EXTRA_CREATE_FULL_PATH
, 
 671                     operation 
= new CreateFolderOperation(remotePath
, createFullPath
); 
 673                 } else if (action
.equals(ACTION_SYNC_FILE
)) { 
 675                     String remotePath 
= operationIntent
.getStringExtra(EXTRA_REMOTE_PATH
); 
 676                     boolean syncFileContents 
= 
 677                             operationIntent
.getBooleanExtra(EXTRA_SYNC_FILE_CONTENTS
, true
); 
 678                     operation 
= new SynchronizeFileOperation( 
 679                             remotePath
, account
, syncFileContents
, getApplicationContext() 
 682                 } else if (action
.equals(ACTION_SYNC_FOLDER
)) { 
 683                     // Sync folder (all its descendant files are sync'ed) 
 684                     String remotePath 
= operationIntent
.getStringExtra(EXTRA_REMOTE_PATH
); 
 685                     operation 
= new SynchronizeFolderOperation( 
 686                             this,                       // TODO remove this dependency from construction time 
 689                             System
.currentTimeMillis()  // TODO remove this dependency from construction time 
 692                 } else if (action
.equals(ACTION_MOVE_FILE
)) { 
 694                     String remotePath 
= operationIntent
.getStringExtra(EXTRA_REMOTE_PATH
); 
 695                     String newParentPath 
= operationIntent
.getStringExtra(EXTRA_NEW_PARENT_PATH
); 
 696                     operation 
= new MoveFileOperation(remotePath
, newParentPath
, account
); 
 698                 } else if (action
.equals(ACTION_COPY_FILE
)) { 
 700                     String remotePath 
= operationIntent
.getStringExtra(EXTRA_REMOTE_PATH
); 
 701                     String newParentPath 
= operationIntent
.getStringExtra(EXTRA_NEW_PARENT_PATH
); 
 702                     operation 
= new CopyFileOperation(remotePath
, newParentPath
, account
); 
 706         } catch (IllegalArgumentException e
) { 
 707             Log_OC
.e(TAG
, "Bad information provided in intent: " + e
.getMessage()); 
 711         if (operation 
!= null
) { 
 712             return new Pair
<Target 
, RemoteOperation
>(target
, operation
);   
 720      * Sends a broadcast when a new operation is added to the queue. 
 722      * Local broadcasts are only delivered to activities in the same process, but can't be 
 725      * @param target            Account or URL pointing to an OC server. 
 726      * @param operation         Added operation. 
 728     private void sendBroadcastNewOperation(Target target
, RemoteOperation operation
) { 
 729         Intent intent 
= new Intent(ACTION_OPERATION_ADDED
); 
 730         if (target
.mAccount 
!= null
) { 
 731             intent
.putExtra(EXTRA_ACCOUNT
, target
.mAccount
); 
 733             intent
.putExtra(EXTRA_SERVER_URL
, target
.mServerUrl
); 
 735         //LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this); 
 736         //lbm.sendBroadcast(intent); 
 737         sendStickyBroadcast(intent
); 
 741     // TODO - maybe add a notification for real start of operations 
 744      * Sends a LOCAL broadcast when an operations finishes in order to the interested activities c 
 745      * an update their view 
 747      * Local broadcasts are only delivered to activities in the same process. 
 749      * @param target    Account or URL pointing to an OC server. 
 750      * @param operation Finished operation. 
 751      * @param result    Result of the operation. 
 753     private void sendBroadcastOperationFinished(Target target
, RemoteOperation operation
, 
 754                                                 RemoteOperationResult result
) { 
 755         Intent intent 
= new Intent(ACTION_OPERATION_FINISHED
); 
 756         intent
.putExtra(EXTRA_RESULT
, result
); 
 757         if (target
.mAccount 
!= null
) { 
 758             intent
.putExtra(EXTRA_ACCOUNT
, target
.mAccount
); 
 760             intent
.putExtra(EXTRA_SERVER_URL
, target
.mServerUrl
); 
 762         //LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this); 
 763         //lbm.sendBroadcast(intent); 
 764         sendStickyBroadcast(intent
); 
 769      * Notifies the currently subscribed listeners about the end of an operation. 
 771      * @param operation         Finished operation. 
 772      * @param result            Result of the operation. 
 774     protected void dispatchResultToOperationListeners( 
 775             final RemoteOperation operation
, final RemoteOperationResult result
 
 778         Iterator
<OnRemoteOperationListener
> listeners 
= 
 779                 mOperationsBinder
.mBoundListeners
.keySet().iterator(); 
 780         while (listeners
.hasNext()) { 
 781             final OnRemoteOperationListener listener 
= listeners
.next(); 
 782             final Handler handler 
= mOperationsBinder
.mBoundListeners
.get(listener
); 
 783             if (handler 
!= null
) {  
 784                 handler
.post(new Runnable() { 
 787                         listener
.onRemoteOperationFinish(operation
, result
); 
 794             Pair
<RemoteOperation
, RemoteOperationResult
> undispatched 
= 
 795                     new Pair
<RemoteOperation
, RemoteOperationResult
>(operation
, result
); 
 796             mUndispatchedFinishedOperations
.put(((Runnable
) operation
).hashCode(), undispatched
); 
 798         Log_OC
.d(TAG
, "Called " + count 
+ " listeners");