1 /* ownCloud Android client application 
   2  *   Copyright (C) 2012-2014 ownCloud Inc. 
   4  *   This program is free software: you can redistribute it and/or modify 
   5  *   it under the terms of the GNU General Public License version 2, 
   6  *   as published by the Free Software Foundation. 
   8  *   This program is distributed in the hope that it will be useful, 
   9  *   but WITHOUT ANY WARRANTY; without even the implied warranty of 
  10  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
  11  *   GNU General Public License for more details. 
  13  *   You should have received a copy of the GNU General Public License 
  14  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. 
  18 package com
.owncloud
.android
.services
; 
  20 import java
.io
.IOException
; 
  21 import java
.util
.Iterator
; 
  22 import java
.util
.concurrent
.ConcurrentHashMap
; 
  23 import java
.util
.concurrent
.ConcurrentLinkedQueue
; 
  24 import java
.util
.concurrent
.ConcurrentMap
; 
  26 import com
.owncloud
.android
.MainApp
; 
  27 import com
.owncloud
.android
.R
; 
  28 import com
.owncloud
.android
.datamodel
.FileDataStorageManager
; 
  29 import com
.owncloud
.android
.lib
.common
.OwnCloudAccount
; 
  30 import com
.owncloud
.android
.lib
.common
.OwnCloudClient
; 
  31 import com
.owncloud
.android
.lib
.common
.OwnCloudClientManagerFactory
; 
  32 import com
.owncloud
.android
.lib
.common
.OwnCloudCredentials
; 
  33 import com
.owncloud
.android
.lib
.common
.OwnCloudCredentialsFactory
; 
  34 import com
.owncloud
.android
.lib
.common
.accounts
.AccountUtils
.AccountNotFoundException
; 
  35 import com
.owncloud
.android
.lib
.common
.operations
.OnRemoteOperationListener
; 
  36 import com
.owncloud
.android
.lib
.common
.operations
.RemoteOperation
; 
  37 import com
.owncloud
.android
.lib
.common
.operations
.RemoteOperationResult
; 
  38 import com
.owncloud
.android
.lib
.common
.utils
.Log_OC
; 
  39 import com
.owncloud
.android
.lib
.resources
.files
.ExistenceCheckRemoteOperation
; 
  40 import com
.owncloud
.android
.lib
.resources
.shares
.ShareType
; 
  41 import com
.owncloud
.android
.lib
.resources
.users
.GetRemoteUserNameOperation
; 
  42 import com
.owncloud
.android
.operations
.common
.SyncOperation
; 
  43 import com
.owncloud
.android
.operations
.CreateFolderOperation
; 
  44 import com
.owncloud
.android
.operations
.CreateShareOperation
; 
  45 import com
.owncloud
.android
.operations
.GetServerInfoOperation
; 
  46 import com
.owncloud
.android
.operations
.MoveFileOperation
; 
  47 import com
.owncloud
.android
.operations
.OAuth2GetAccessToken
; 
  48 import com
.owncloud
.android
.operations
.RemoveFileOperation
; 
  49 import com
.owncloud
.android
.operations
.RenameFileOperation
; 
  50 import com
.owncloud
.android
.operations
.SynchronizeFileOperation
; 
  51 import com
.owncloud
.android
.operations
.UnshareLinkOperation
; 
  53 import android
.accounts
.Account
; 
  54 import android
.accounts
.AccountsException
; 
  55 import android
.accounts
.AuthenticatorException
; 
  56 import android
.accounts
.OperationCanceledException
; 
  57 import android
.app
.Service
; 
  58 import android
.content
.Intent
; 
  59 import android
.net
.Uri
; 
  60 import android
.os
.Binder
; 
  61 import android
.os
.Handler
; 
  62 import android
.os
.HandlerThread
; 
  63 import android
.os
.IBinder
; 
  64 import android
.os
.Looper
; 
  65 import android
.os
.Message
; 
  66 import android
.os
.Process
; 
  67 import android
.util
.Pair
; 
  69 public class OperationsService 
extends Service 
{ 
  71     private static final String TAG 
= OperationsService
.class.getSimpleName(); 
  73     public static final String EXTRA_ACCOUNT 
= "ACCOUNT"; 
  74     public static final String EXTRA_SERVER_URL 
= "SERVER_URL"; 
  75     public static final String EXTRA_OAUTH2_QUERY_PARAMETERS 
= "OAUTH2_QUERY_PARAMETERS"; 
  76     public static final String EXTRA_REMOTE_PATH 
= "REMOTE_PATH"; 
  77     public static final String EXTRA_SEND_INTENT 
= "SEND_INTENT"; 
  78     public static final String EXTRA_NEWNAME 
= "NEWNAME"; 
  79     public static final String EXTRA_REMOVE_ONLY_LOCAL 
= "REMOVE_LOCAL_COPY"; 
  80     public static final String EXTRA_CREATE_FULL_PATH 
= "CREATE_FULL_PATH"; 
  81     public static final String EXTRA_SYNC_FILE_CONTENTS 
= "SYNC_FILE_CONTENTS"; 
  82     public static final String EXTRA_RESULT 
= "RESULT"; 
  83     public static final String EXTRA_NEW_PARENT_PATH 
= "NEW_PARENT_PATH"; 
  85     // TODO review if ALL OF THEM are necessary 
  86     public static final String EXTRA_SUCCESS_IF_ABSENT 
= "SUCCESS_IF_ABSENT"; 
  87     public static final String EXTRA_USERNAME 
= "USERNAME"; 
  88     public static final String EXTRA_PASSWORD 
= "PASSWORD"; 
  89     public static final String EXTRA_AUTH_TOKEN 
= "AUTH_TOKEN"; 
  90     public static final String EXTRA_COOKIE 
= "COOKIE"; 
  92     public static final String ACTION_CREATE_SHARE 
= "CREATE_SHARE"; 
  93     public static final String ACTION_UNSHARE 
= "UNSHARE"; 
  94     public static final String ACTION_GET_SERVER_INFO 
= "GET_SERVER_INFO"; 
  95     public static final String ACTION_OAUTH2_GET_ACCESS_TOKEN 
= "OAUTH2_GET_ACCESS_TOKEN"; 
  96     public static final String ACTION_EXISTENCE_CHECK 
= "EXISTENCE_CHECK"; 
  97     public static final String ACTION_GET_USER_NAME 
= "GET_USER_NAME"; 
  98     public static final String ACTION_RENAME 
= "RENAME"; 
  99     public static final String ACTION_REMOVE 
= "REMOVE"; 
 100     public static final String ACTION_CREATE_FOLDER 
= "CREATE_FOLDER"; 
 101     public static final String ACTION_SYNC_FILE 
= "SYNC_FILE"; 
 102     public static final String ACTION_SYNC_FOLDER 
= "SYNC_FOLDER";  // for the moment, just to download 
 103     public static final String ACTION_MOVE_FILE 
= "MOVE_FILE"; 
 105     public static final String ACTION_OPERATION_ADDED 
= OperationsService
.class.getName() + ".OPERATION_ADDED"; 
 106     public static final String ACTION_OPERATION_FINISHED 
= OperationsService
.class.getName() + ".OPERATION_FINISHED"; 
 109     private ConcurrentLinkedQueue
<Pair
<Target
, RemoteOperation
>> mPendingOperations 
=  
 110             new ConcurrentLinkedQueue
<Pair
<Target
, RemoteOperation
>>(); 
 112     private ConcurrentMap
<Integer
, Pair
<RemoteOperation
, RemoteOperationResult
>>  
 113         mUndispatchedFinishedOperations 
= 
 114             new ConcurrentHashMap
<Integer
, Pair
<RemoteOperation
, RemoteOperationResult
>>(); 
 116     private static class Target 
{ 
 117         public Uri mServerUrl 
= null
; 
 118         public Account mAccount 
= null
; 
 119         public String mUsername 
= null
; 
 120         public String mPassword 
= null
; 
 121         public String mAuthToken 
= null
; 
 122         public String mCookie 
= null
; 
 124         public Target(Account account
, Uri serverUrl
, String username
, String password
, String authToken
, 
 127             mServerUrl 
= serverUrl
; 
 128             mUsername 
= username
; 
 129             mPassword 
= password
; 
 130             mAuthToken 
= authToken
; 
 135     private Looper mServiceLooper
; 
 136     private ServiceHandler mServiceHandler
; 
 137     private OperationsServiceBinder mBinder
; 
 138     private OwnCloudClient mOwnCloudClient 
= null
; 
 139     private Target mLastTarget 
= null
; 
 140     private FileDataStorageManager mStorageManager
; 
 141     private RemoteOperation mCurrentOperation 
= null
; 
 145      * Service initialization 
 148     public void onCreate() { 
 150         HandlerThread thread 
= new HandlerThread("Operations service thread", Process
.THREAD_PRIORITY_BACKGROUND
); 
 152         mServiceLooper 
= thread
.getLooper(); 
 153         mServiceHandler 
= new ServiceHandler(mServiceLooper
, this); 
 154         mBinder 
= new OperationsServiceBinder(); 
 159      * Entry point to add a new operation to the queue of operations. 
 161      * New operations are added calling to startService(), resulting in a call to this method.  
 162      * This ensures the service will keep on working although the caller activity goes away. 
 165     public int onStartCommand(Intent intent
, int flags
, int startId
) { 
 166         if (ACTION_SYNC_FOLDER
.equals(intent
.getAction())) { 
 167             // WIP: for the moment, only SYNC_FOLDER is expected here; the rest of the operations are requested through 
 169             Pair
<Target
, RemoteOperation
> itemToQueue 
= newOperation(intent
); 
 170             if (itemToQueue 
!= null
) { 
 171                 mPendingOperations
.add(itemToQueue
); 
 175         Message msg 
= mServiceHandler
.obtainMessage(); 
 177         mServiceHandler
.sendMessage(msg
); 
 178         return START_NOT_STICKY
; 
 182     public void onDestroy() { 
 183         //Log_OC.wtf(TAG, "onDestroy init" ); 
 186             OwnCloudClientManagerFactory
.getDefaultSingleton(). 
 187                 saveAllClients(this, MainApp
.getAccountType()); 
 189             // TODO - get rid of these exceptions 
 190         } catch (AccountNotFoundException e
) { 
 192         } catch (AuthenticatorException e
) { 
 194         } catch (OperationCanceledException e
) { 
 196         } catch (IOException e
) { 
 200         //Log_OC.wtf(TAG, "Clear mUndispatchedFinisiedOperations" ); 
 201         mUndispatchedFinishedOperations
.clear(); 
 203         //Log_OC.wtf(TAG, "onDestroy end" ); 
 209      * Provides a binder object that clients can use to perform actions on the queue of operations,  
 210      * except the addition of new operations.  
 213     public IBinder 
onBind(Intent intent
) { 
 214         //Log_OC.wtf(TAG, "onBind" ); 
 220      * Called when ALL the bound clients were unbound. 
 223     public boolean onUnbind(Intent intent
) { 
 224         ((OperationsServiceBinder
)mBinder
).clearListeners(); 
 225         return false
;   // not accepting rebinding (default behaviour) 
 230      *  Binder to let client components to perform actions on the queue of operations. 
 232      *  It provides by itself the available operations. 
 234     public class OperationsServiceBinder 
extends Binder 
/* implements OnRemoteOperationListener */ { 
 237          * Map of listeners that will be reported about the end of operations from a {@link OperationsServiceBinder} instance  
 239         private ConcurrentMap
<OnRemoteOperationListener
, Handler
> mBoundListeners 
=  
 240                 new ConcurrentHashMap
<OnRemoteOperationListener
, Handler
>(); 
 243          * Cancels an operation 
 247         public void cancel() { 
 252         public void clearListeners() { 
 254             mBoundListeners
.clear(); 
 259          * Adds a listener interested in being reported about the end of operations. 
 261          * @param listener          Object to notify about the end of operations.     
 262          * @param callbackHandler   {@link Handler} to access the listener without breaking Android threading protection. 
 264         public void addOperationListener (OnRemoteOperationListener listener
, Handler callbackHandler
) { 
 265             synchronized (mBoundListeners
) { 
 266                 mBoundListeners
.put(listener
, callbackHandler
); 
 272          * Removes a listener from the list of objects interested in the being reported about the end of operations. 
 274          * @param listener      Object to notify about progress of transfer.     
 276         public void removeOperationListener (OnRemoteOperationListener listener
) { 
 277             synchronized (mBoundListeners
) { 
 278                 mBoundListeners
.remove(listener
); 
 284          * TODO - IMPORTANT: update implementation when more operations are moved into the service  
 286          * @return  'True' when an operation that enforces the user to wait for completion is in process. 
 288         public boolean isPerformingBlockingOperation() { 
 289             return (!mPendingOperations
.isEmpty()); 
 294          * Creates and adds to the queue a new operation, as described by operationIntent. 
 296          * Calls startService to make the operation is processed by the ServiceHandler. 
 298          * @param operationIntent       Intent describing a new operation to queue and execute. 
 299          * @return                      Identifier of the operation created, or null if failed. 
 301         public long queueNewOperation(Intent operationIntent
) { 
 302             Pair
<Target
, RemoteOperation
> itemToQueue 
= newOperation(operationIntent
); 
 303             if (itemToQueue 
!= null
) { 
 304                 mPendingOperations
.add(itemToQueue
); 
 305                 startService(new Intent(OperationsService
.this, OperationsService
.class)); 
 306                 return itemToQueue
.second
.hashCode(); 
 309                 return Long
.MAX_VALUE
; 
 314         public boolean dispatchResultIfFinished(int operationId
, OnRemoteOperationListener listener
) { 
 315             Pair
<RemoteOperation
, RemoteOperationResult
> undispatched 
=  
 316                     mUndispatchedFinishedOperations
.remove(operationId
); 
 317             if (undispatched 
!= null
) { 
 318                 listener
.onRemoteOperationFinish(undispatched
.first
, undispatched
.second
); 
 320                 //Log_OC.wtf(TAG, "Sending callback later"); 
 322                 if (!mPendingOperations
.isEmpty()) { 
 327                 //Log_OC.wtf(TAG, "Not finished yet"); 
 335      * Operations worker. Performs the pending operations in the order they were requested.  
 337      * Created with the Looper of a new thread, started in {@link OperationsService#onCreate()}.  
 339     private static class ServiceHandler 
extends Handler 
{ 
 340         // don't make it a final class, and don't remove the static ; lint will warn about a possible memory leak 
 341         OperationsService mService
; 
 342         public ServiceHandler(Looper looper
, OperationsService service
) { 
 344             if (service 
== null
) { 
 345                 throw new IllegalArgumentException("Received invalid NULL in parameter 'service'"); 
 351         public void handleMessage(Message msg
) { 
 352             mService
.nextOperation(); 
 353             mService
.stopSelf(msg
.arg1
); 
 359      * Creates a new operation, as described by operationIntent. 
 361      * @param operationIntent       Intent describing a new operation to queue and execute. 
 362      * @return                      Pair with the new operation object and the information about its target server. 
 364     private Pair
<Target 
, RemoteOperation
> newOperation(Intent operationIntent
) { 
 365         RemoteOperation operation 
= null
; 
 366         Target target 
= null
; 
 368             if (!operationIntent
.hasExtra(EXTRA_ACCOUNT
) &&  
 369                     !operationIntent
.hasExtra(EXTRA_SERVER_URL
)) { 
 370                 Log_OC
.e(TAG
, "Not enough information provided in intent"); 
 373                 Account account 
= operationIntent
.getParcelableExtra(EXTRA_ACCOUNT
); 
 374                 String serverUrl 
= operationIntent
.getStringExtra(EXTRA_SERVER_URL
); 
 375                 String username 
= operationIntent
.getStringExtra(EXTRA_USERNAME
); 
 376                 String password 
= operationIntent
.getStringExtra(EXTRA_PASSWORD
); 
 377                 String authToken 
= operationIntent
.getStringExtra(EXTRA_AUTH_TOKEN
); 
 378                 String cookie 
= operationIntent
.getStringExtra(EXTRA_COOKIE
); 
 381                         (serverUrl 
== null
) ? null 
: Uri
.parse(serverUrl
), 
 388                 String action 
= operationIntent
.getAction(); 
 389                 if (action
.equals(ACTION_CREATE_SHARE
)) {  // Create Share 
 390                     String remotePath 
= operationIntent
.getStringExtra(EXTRA_REMOTE_PATH
); 
 391                     Intent sendIntent 
= operationIntent
.getParcelableExtra(EXTRA_SEND_INTENT
); 
 392                     if (remotePath
.length() > 0) { 
 393                         operation 
= new CreateShareOperation(remotePath
, ShareType
.PUBLIC_LINK
,  
 394                                 "", false
, "", 1, sendIntent
); 
 397                 } else if (action
.equals(ACTION_UNSHARE
)) {  // Unshare file 
 398                     String remotePath 
= operationIntent
.getStringExtra(EXTRA_REMOTE_PATH
); 
 399                     if (remotePath
.length() > 0) { 
 400                         operation 
= new UnshareLinkOperation( 
 402                                 OperationsService
.this); 
 405                 } else if (action
.equals(ACTION_GET_SERVER_INFO
)) {  
 406                     // check OC server and get basic information from it 
 407                     operation 
= new GetServerInfoOperation(serverUrl
, OperationsService
.this); 
 409                 } else if (action
.equals(ACTION_OAUTH2_GET_ACCESS_TOKEN
)) { 
 410                     /// GET ACCESS TOKEN to the OAuth server 
 411                     String oauth2QueryParameters 
= 
 412                             operationIntent
.getStringExtra(EXTRA_OAUTH2_QUERY_PARAMETERS
); 
 413                     operation 
= new OAuth2GetAccessToken( 
 414                             getString(R
.string
.oauth2_client_id
),  
 415                             getString(R
.string
.oauth2_redirect_uri
),        
 416                             getString(R
.string
.oauth2_grant_type
), 
 417                             oauth2QueryParameters
); 
 419                 } else if (action
.equals(ACTION_EXISTENCE_CHECK
)) { 
 421                     String remotePath 
= operationIntent
.getStringExtra(EXTRA_REMOTE_PATH
); 
 422                     boolean successIfAbsent 
= operationIntent
.getBooleanExtra(EXTRA_SUCCESS_IF_ABSENT
, false
); 
 423                     operation 
= new ExistenceCheckRemoteOperation(remotePath
, OperationsService
.this, successIfAbsent
); 
 425                 } else if (action
.equals(ACTION_GET_USER_NAME
)) { 
 427                     operation 
= new GetRemoteUserNameOperation(); 
 429                 } else if (action
.equals(ACTION_RENAME
)) { 
 430                     // Rename file or folder 
 431                     String remotePath 
= operationIntent
.getStringExtra(EXTRA_REMOTE_PATH
); 
 432                     String newName 
= operationIntent
.getStringExtra(EXTRA_NEWNAME
); 
 433                     operation 
= new RenameFileOperation(remotePath
, newName
); 
 435                 } else if (action
.equals(ACTION_REMOVE
)) { 
 436                     // Remove file or folder 
 437                     String remotePath 
= operationIntent
.getStringExtra(EXTRA_REMOTE_PATH
); 
 438                     boolean onlyLocalCopy 
= operationIntent
.getBooleanExtra(EXTRA_REMOVE_ONLY_LOCAL
, false
); 
 439                     operation 
= new RemoveFileOperation(remotePath
, onlyLocalCopy
); 
 441                 } else if (action
.equals(ACTION_CREATE_FOLDER
)) { 
 443                     String remotePath 
= operationIntent
.getStringExtra(EXTRA_REMOTE_PATH
); 
 444                     boolean createFullPath 
= operationIntent
.getBooleanExtra(EXTRA_CREATE_FULL_PATH
, true
); 
 445                     operation 
= new CreateFolderOperation(remotePath
, createFullPath
); 
 447                 } else if (action
.equals(ACTION_SYNC_FILE
)) { 
 449                     String remotePath 
= operationIntent
.getStringExtra(EXTRA_REMOTE_PATH
); 
 450                     boolean syncFileContents 
= operationIntent
.getBooleanExtra(EXTRA_SYNC_FILE_CONTENTS
, true
); 
 451                     operation 
= new SynchronizeFileOperation( 
 452                             remotePath
, account
, syncFileContents
, getApplicationContext() 
 455                 } else if (action
.equals(ACTION_SYNC_FOLDER
)) { 
 457                     String remotePath 
= operationIntent
.getStringExtra(EXTRA_REMOTE_PATH
); 
 458                     boolean syncFileContents 
= operationIntent
.getBooleanExtra(EXTRA_SYNC_FILE_CONTENTS
, true
); 
 459                     /* TODO - merge code for new SynchronizeFolderOperation 
 460                     operation = new SynchronizeFolderOperation( 
 465                 } else if (action
.equals(ACTION_MOVE_FILE
)) { 
 467                     String remotePath 
= operationIntent
.getStringExtra(EXTRA_REMOTE_PATH
); 
 468                     String newParentPath 
= operationIntent
.getStringExtra(EXTRA_NEW_PARENT_PATH
); 
 469                     operation 
= new MoveFileOperation(remotePath
,newParentPath
,account
); 
 474         } catch (IllegalArgumentException e
) { 
 475             Log_OC
.e(TAG
, "Bad information provided in intent: " + e
.getMessage()); 
 479         if (operation 
!= null
) { 
 480             return new Pair
<Target 
, RemoteOperation
>(target
, operation
);   
 488      * Performs the next operation in the queue 
 490     private void nextOperation() { 
 492         //Log_OC.wtf(TAG, "nextOperation init" ); 
 494         Pair
<Target
, RemoteOperation
> next 
= null
; 
 495         synchronized(mPendingOperations
) { 
 496             next 
= mPendingOperations
.peek(); 
 501             mCurrentOperation 
= next
.second
; 
 502             RemoteOperationResult result 
= null
; 
 504                 /// prepare client object to send the request to the ownCloud server 
 505                 if (mLastTarget 
== null 
|| !mLastTarget
.equals(next
.first
)) { 
 506                     mLastTarget 
= next
.first
; 
 507                     if (mLastTarget
.mAccount 
!= null
) { 
 508                         OwnCloudAccount ocAccount 
= new OwnCloudAccount(mLastTarget
.mAccount
, this); 
 509                         mOwnCloudClient 
= OwnCloudClientManagerFactory
.getDefaultSingleton(). 
 510                                 getClientFor(ocAccount
, this); 
 512                                 new FileDataStorageManager( 
 513                                         mLastTarget
.mAccount
,  
 514                                         getContentResolver()); 
 516                         OwnCloudCredentials credentials 
= null
; 
 517                         if (mLastTarget
.mUsername 
!= null 
&&  
 518                                 mLastTarget
.mUsername
.length() > 0) { 
 519                             credentials 
= OwnCloudCredentialsFactory
.newBasicCredentials( 
 520                                     mLastTarget
.mUsername
,  
 521                                     mLastTarget
.mPassword
);  // basic 
 523                         } else if (mLastTarget
.mAuthToken 
!= null 
&&  
 524                                 mLastTarget
.mAuthToken
.length() > 0) { 
 525                             credentials 
= OwnCloudCredentialsFactory
.newBearerCredentials( 
 526                                     mLastTarget
.mAuthToken
);  // bearer token 
 528                         } else if (mLastTarget
.mCookie 
!= null 
&& 
 529                                 mLastTarget
.mCookie
.length() > 0) { 
 530                             credentials 
= OwnCloudCredentialsFactory
.newSamlSsoCredentials( 
 531                                     mLastTarget
.mCookie
); // SAML SSO 
 533                         OwnCloudAccount ocAccount 
= new OwnCloudAccount( 
 534                                 mLastTarget
.mServerUrl
, credentials
); 
 535                         mOwnCloudClient 
= OwnCloudClientManagerFactory
.getDefaultSingleton(). 
 536                                 getClientFor(ocAccount
, this); 
 537                         mStorageManager 
= null
; 
 541                 /// perform the operation 
 542                 if (mCurrentOperation 
instanceof SyncOperation
) { 
 543                     result 
= ((SyncOperation
)mCurrentOperation
).execute(mOwnCloudClient
, mStorageManager
); 
 545                     result 
= mCurrentOperation
.execute(mOwnCloudClient
); 
 548             } catch (AccountsException e
) { 
 549                 if (mLastTarget
.mAccount 
== null
) { 
 550                     Log_OC
.e(TAG
, "Error while trying to get authorization for a NULL account", e
); 
 552                     Log_OC
.e(TAG
, "Error while trying to get authorization for " + mLastTarget
.mAccount
.name
, e
); 
 554                 result 
= new RemoteOperationResult(e
); 
 556             } catch (IOException e
) { 
 557                 if (mLastTarget
.mAccount 
== null
) { 
 558                     Log_OC
.e(TAG
, "Error while trying to get authorization for a NULL account", e
); 
 560                     Log_OC
.e(TAG
, "Error while trying to get authorization for " + mLastTarget
.mAccount
.name
, e
); 
 562                 result 
= new RemoteOperationResult(e
); 
 563             } catch (Exception e
) { 
 564                 if (mLastTarget
.mAccount 
== null
) { 
 565                     Log_OC
.e(TAG
, "Unexpected error for a NULL account", e
); 
 567                     Log_OC
.e(TAG
, "Unexpected error for " + mLastTarget
.mAccount
.name
, e
); 
 569                 result 
= new RemoteOperationResult(e
); 
 572                 synchronized(mPendingOperations
) { 
 573                     mPendingOperations
.poll(); 
 577             //sendBroadcastOperationFinished(mLastTarget, mCurrentOperation, result); 
 578             dispatchResultToOperationListeners(mLastTarget
, mCurrentOperation
, result
); 
 584      * Sends a broadcast when a new operation is added to the queue. 
 586      * Local broadcasts are only delivered to activities in the same process, but can't be done sticky :\ 
 588      * @param target            Account or URL pointing to an OC server. 
 589      * @param operation         Added operation. 
 591     private void sendBroadcastNewOperation(Target target
, RemoteOperation operation
) { 
 592         Intent intent 
= new Intent(ACTION_OPERATION_ADDED
); 
 593         if (target
.mAccount 
!= null
) { 
 594             intent
.putExtra(EXTRA_ACCOUNT
, target
.mAccount
);     
 596             intent
.putExtra(EXTRA_SERVER_URL
, target
.mServerUrl
);     
 598         //LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this); 
 599         //lbm.sendBroadcast(intent); 
 600         sendStickyBroadcast(intent
); 
 604     // TODO - maybe add a notification for real start of operations 
 607      * Sends a LOCAL broadcast when an operations finishes in order to the interested activities can update their view 
 609      * Local broadcasts are only delivered to activities in the same process. 
 611      * @param target            Account or URL pointing to an OC server. 
 612      * @param operation         Finished operation. 
 613      * @param result            Result of the operation. 
 615     private void sendBroadcastOperationFinished(Target target
, RemoteOperation operation
, RemoteOperationResult result
) { 
 616         Intent intent 
= new Intent(ACTION_OPERATION_FINISHED
); 
 617         intent
.putExtra(EXTRA_RESULT
, result
); 
 618         if (target
.mAccount 
!= null
) { 
 619             intent
.putExtra(EXTRA_ACCOUNT
, target
.mAccount
);     
 621             intent
.putExtra(EXTRA_SERVER_URL
, target
.mServerUrl
);     
 623         //LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this); 
 624         //lbm.sendBroadcast(intent); 
 625         sendStickyBroadcast(intent
); 
 630      * Notifies the currently subscribed listeners about the end of an operation. 
 632      * @param target            Account or URL pointing to an OC server. 
 633      * @param operation         Finished operation. 
 634      * @param result            Result of the operation. 
 636     private void dispatchResultToOperationListeners( 
 637             Target target
, final RemoteOperation operation
, final RemoteOperationResult result
) { 
 639         Iterator
<OnRemoteOperationListener
> listeners 
= mBinder
.mBoundListeners
.keySet().iterator(); 
 640         while (listeners
.hasNext()) { 
 641             final OnRemoteOperationListener listener 
= listeners
.next(); 
 642             final Handler handler 
= mBinder
.mBoundListeners
.get(listener
); 
 643             if (handler 
!= null
) {  
 644                 handler
.post(new Runnable() { 
 647                         listener
.onRemoteOperationFinish(operation
, result
); 
 654             //mOperationResults.put(operation.hashCode(), result); 
 655             Pair
<RemoteOperation
, RemoteOperationResult
> undispatched 
=  
 656                     new Pair
<RemoteOperation
, RemoteOperationResult
>(operation
, result
); 
 657             mUndispatchedFinishedOperations
.put(operation
.hashCode(), undispatched
); 
 659         Log_OC
.d(TAG
, "Called " + count 
+ " listeners");