1 /* ownCloud Android client application 
   2  *   Copyright (C) 2012-2013 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
.HashMap
; 
  22 import java
.util
.Iterator
; 
  24 import java
.util
.concurrent
.ConcurrentLinkedQueue
; 
  26 import com
.owncloud
.android
.datamodel
.FileDataStorageManager
; 
  27 import com
.owncloud
.android
.lib
.common
.OwnCloudClientFactory
; 
  28 import com
.owncloud
.android
.lib
.common
.OwnCloudClient
; 
  29 import com
.owncloud
.android
.lib
.common
.operations
.OnRemoteOperationListener
; 
  30 import com
.owncloud
.android
.lib
.common
.operations
.RemoteOperation
; 
  31 import com
.owncloud
.android
.lib
.common
.operations
.RemoteOperationResult
; 
  32 import com
.owncloud
.android
.lib
.resources
.shares
.ShareType
; 
  33 import com
.owncloud
.android
.operations
.common
.SyncOperation
; 
  34 import com
.owncloud
.android
.operations
.CreateShareOperation
; 
  35 import com
.owncloud
.android
.operations
.UnshareLinkOperation
; 
  36 import com
.owncloud
.android
.utils
.Log_OC
; 
  38 import android
.accounts
.Account
; 
  39 import android
.accounts
.AccountsException
; 
  40 import android
.app
.Service
; 
  41 import android
.content
.Intent
; 
  42 import android
.net
.Uri
; 
  43 import android
.os
.Binder
; 
  44 import android
.os
.Handler
; 
  45 import android
.os
.HandlerThread
; 
  46 import android
.os
.IBinder
; 
  47 import android
.os
.Looper
; 
  48 import android
.os
.Message
; 
  49 import android
.os
.Process
; 
  50 //import android.support.v4.content.LocalBroadcastManager; 
  51 import android
.util
.Pair
; 
  53 public class OperationsService 
extends Service 
{ 
  55     private static final String TAG 
= OperationsService
.class.getSimpleName(); 
  57     public static final String EXTRA_ACCOUNT 
= "ACCOUNT"; 
  58     public static final String EXTRA_SERVER_URL 
= "SERVER_URL"; 
  59     public static final String EXTRA_REMOTE_PATH 
= "REMOTE_PATH"; 
  60     public static final String EXTRA_SEND_INTENT 
= "SEND_INTENT"; 
  61     public static final String EXTRA_RESULT 
= "RESULT"; 
  63     public static final String ACTION_CREATE_SHARE 
= "CREATE_SHARE"; 
  64     public static final String ACTION_UNSHARE 
= "UNSHARE"; 
  66     public static final String ACTION_OPERATION_ADDED 
= OperationsService
.class.getName() + ".OPERATION_ADDED"; 
  67     public static final String ACTION_OPERATION_FINISHED 
= OperationsService
.class.getName() + ".OPERATION_FINISHED"; 
  69     private ConcurrentLinkedQueue
<Pair
<Target
, RemoteOperation
>> mPendingOperations 
= new ConcurrentLinkedQueue
<Pair
<Target
, RemoteOperation
>>(); 
  71     private static class Target 
{ 
  72         public Uri mServerUrl 
= null
; 
  73         public Account mAccount 
= null
; 
  74         public Target(Account account
, Uri serverUrl
) { 
  76             mServerUrl 
= serverUrl
; 
  80     private Looper mServiceLooper
; 
  81     private ServiceHandler mServiceHandler
; 
  82     private OperationsServiceBinder mBinder
; 
  83     private OwnCloudClient mOwnCloudClient 
= null
; 
  84     private Target mLastTarget 
= null
; 
  85     private FileDataStorageManager mStorageManager
; 
  86     private RemoteOperation mCurrentOperation 
= null
; 
  90      * Service initialization 
  93     public void onCreate() { 
  95         HandlerThread thread 
= new HandlerThread("Operations service thread", Process
.THREAD_PRIORITY_BACKGROUND
); 
  97         mServiceLooper 
= thread
.getLooper(); 
  98         mServiceHandler 
= new ServiceHandler(mServiceLooper
, this); 
  99         mBinder 
= new OperationsServiceBinder(); 
 103      * Entry point to add a new operation to the queue of operations. 
 105      * New operations are added calling to startService(), resulting in a call to this method.  
 106      * This ensures the service will keep on working although the caller activity goes away. 
 108      * IMPORTANT: the only operations performed here right now is {@link GetSharedFilesOperation}. The class 
 109      * is taking advantage of it due to time constraints. 
 112     public int onStartCommand(Intent intent
, int flags
, int startId
) { 
 113         if (!intent
.hasExtra(EXTRA_ACCOUNT
) && !intent
.hasExtra(EXTRA_SERVER_URL
)) { 
 114             Log_OC
.e(TAG
, "Not enough information provided in intent"); 
 115             return START_NOT_STICKY
; 
 118             Account account 
= intent
.getParcelableExtra(EXTRA_ACCOUNT
); 
 119             String serverUrl 
= intent
.getStringExtra(EXTRA_SERVER_URL
); 
 121             Target target 
= new Target(account
, (serverUrl 
== null
) ? null 
: Uri
.parse(serverUrl
)); 
 122             RemoteOperation operation 
= null
; 
 124             String action 
= intent
.getAction(); 
 125             if (action
.equals(ACTION_CREATE_SHARE
)) {  // Create Share 
 126                 String remotePath 
= intent
.getStringExtra(EXTRA_REMOTE_PATH
); 
 127                 Intent sendIntent 
= intent
.getParcelableExtra(EXTRA_SEND_INTENT
); 
 128                 if (remotePath
.length() > 0) { 
 129                     operation 
= new CreateShareOperation(remotePath
, ShareType
.PUBLIC_LINK
,  
 130                             "", false
, "", 1, sendIntent
); 
 132             } else if (action
.equals(ACTION_UNSHARE
)) {  // Unshare file 
 133                 String remotePath 
= intent
.getStringExtra(EXTRA_REMOTE_PATH
); 
 134                 if (remotePath
.length() > 0) { 
 135                     operation 
= new UnshareLinkOperation(remotePath
, this.getApplicationContext()); 
 138                 // nothing we are going to handle 
 139                 return START_NOT_STICKY
; 
 142             mPendingOperations
.add(new Pair
<Target 
, RemoteOperation
>(target
, operation
)); 
 143             //sendBroadcastNewOperation(target, operation); 
 145             Message msg 
= mServiceHandler
.obtainMessage(); 
 147             mServiceHandler
.sendMessage(msg
); 
 149         } catch (IllegalArgumentException e
) { 
 150             Log_OC
.e(TAG
, "Bad information provided in intent: " + e
.getMessage()); 
 151             return START_NOT_STICKY
; 
 154         return START_NOT_STICKY
; 
 159      * Provides a binder object that clients can use to perform actions on the queue of operations,  
 160      * except the addition of new operations.  
 163     public IBinder 
onBind(Intent intent
) { 
 169      * Called when ALL the bound clients were unbound. 
 172     public boolean onUnbind(Intent intent
) { 
 173         //((OperationsServiceBinder)mBinder).clearListeners(); 
 174         return false
;   // not accepting rebinding (default behaviour) 
 179      *  Binder to let client components to perform actions on the queue of operations. 
 181      *  It provides by itself the available operations. 
 183     public class OperationsServiceBinder 
extends Binder 
/* implements OnRemoteOperationListener */ { 
 186          * Map of listeners that will be reported about the end of operations from a {@link OperationsServiceBinder} instance  
 188         private Map
<OnRemoteOperationListener
, Handler
> mBoundListeners 
= new HashMap
<OnRemoteOperationListener
, Handler
>(); 
 191          * Cancels an operation 
 195         public void cancel() { 
 200         public void clearListeners() { 
 202             mBoundListeners
.clear(); 
 207          * Adds a listener interested in being reported about the end of operations. 
 209          * @param listener          Object to notify about the end of operations.     
 210          * @param callbackHandler   {@link Handler} to access the listener without breaking Android threading protection. 
 212         public void addOperationListener (OnRemoteOperationListener listener
, Handler callbackHandler
) { 
 213             mBoundListeners
.put(listener
, callbackHandler
); 
 218          * Removes a listener from the list of objects interested in the being reported about the end of operations. 
 220          * @param listener      Object to notify about progress of transfer.     
 222         public void removeOperationListener (OnRemoteOperationListener listener
) { 
 223             mBoundListeners
.remove(listener
); 
 228          * TODO - IMPORTANT: update implementation when more operations are moved into the service  
 230          * @return  'True' when an operation that enforces the user to wait for completion is in process. 
 232         public boolean isPerformingBlockingOperation() { 
 233             return (!mPendingOperations
.isEmpty()); 
 240      * Operations worker. Performs the pending operations in the order they were requested.  
 242      * Created with the Looper of a new thread, started in {@link OperationsService#onCreate()}.  
 244     private static class ServiceHandler 
extends Handler 
{ 
 245         // don't make it a final class, and don't remove the static ; lint will warn about a possible memory leak 
 246         OperationsService mService
; 
 247         public ServiceHandler(Looper looper
, OperationsService service
) { 
 249             if (service 
== null
) { 
 250                 throw new IllegalArgumentException("Received invalid NULL in parameter 'service'"); 
 256         public void handleMessage(Message msg
) { 
 257             mService
.nextOperation(); 
 258             mService
.stopSelf(msg
.arg1
); 
 264      * Performs the next operation in the queue 
 266     private void nextOperation() { 
 268         Pair
<Target
, RemoteOperation
> next 
= null
; 
 269         synchronized(mPendingOperations
) { 
 270             next 
= mPendingOperations
.peek(); 
 275             mCurrentOperation 
= next
.second
; 
 276             RemoteOperationResult result 
= null
; 
 278                 /// prepare client object to send the request to the ownCloud server 
 279                 if (mLastTarget 
== null 
|| !mLastTarget
.equals(next
.first
)) { 
 280                     mLastTarget 
= next
.first
; 
 281                     if (mLastTarget
.mAccount 
!= null
) { 
 282                         mOwnCloudClient 
= OwnCloudClientFactory
.createOwnCloudClient(mLastTarget
.mAccount
, getApplicationContext()); 
 283                         mStorageManager 
= new FileDataStorageManager(mLastTarget
.mAccount
, getContentResolver()); 
 285                         mOwnCloudClient 
= OwnCloudClientFactory
.createOwnCloudClient(mLastTarget
.mServerUrl
, getApplicationContext(), true
);    // this is not good enough 
 286                         mStorageManager 
= null
; 
 290                 /// perform the operation 
 291                 if (mCurrentOperation 
instanceof SyncOperation
) { 
 292                     result 
= ((SyncOperation
)mCurrentOperation
).execute(mOwnCloudClient
, mStorageManager
); 
 294                     result 
= mCurrentOperation
.execute(mOwnCloudClient
); 
 297             } catch (AccountsException e
) { 
 298                 if (mLastTarget
.mAccount 
== null
) { 
 299                     Log_OC
.e(TAG
, "Error while trying to get autorization for a NULL account", e
); 
 301                     Log_OC
.e(TAG
, "Error while trying to get autorization for " + mLastTarget
.mAccount
.name
, e
); 
 303                 result 
= new RemoteOperationResult(e
); 
 305             } catch (IOException e
) { 
 306                 if (mLastTarget
.mAccount 
== null
) { 
 307                     Log_OC
.e(TAG
, "Error while trying to get autorization for a NULL account", e
); 
 309                     Log_OC
.e(TAG
, "Error while trying to get autorization for " + mLastTarget
.mAccount
.name
, e
); 
 311                 result 
= new RemoteOperationResult(e
); 
 312             } catch (Exception e
) { 
 313                 if (mLastTarget
.mAccount 
== null
) { 
 314                     Log_OC
.e(TAG
, "Unexpected error for a NULL account", e
); 
 316                     Log_OC
.e(TAG
, "Unexpected error for " + mLastTarget
.mAccount
.name
, e
); 
 318                 result 
= new RemoteOperationResult(e
); 
 321                 synchronized(mPendingOperations
) { 
 322                     mPendingOperations
.poll(); 
 326             //sendBroadcastOperationFinished(mLastTarget, mCurrentOperation, result); 
 327             callbackOperationListeners(mLastTarget
, mCurrentOperation
, result
); 
 333      * Sends a broadcast when a new operation is added to the queue. 
 335      * Local broadcasts are only delivered to activities in the same process, but can't be done sticky :\ 
 337      * @param target            Account or URL pointing to an OC server. 
 338      * @param operation         Added operation. 
 340     private void sendBroadcastNewOperation(Target target
, RemoteOperation operation
) { 
 341         Intent intent 
= new Intent(ACTION_OPERATION_ADDED
); 
 342         if (target
.mAccount 
!= null
) { 
 343             intent
.putExtra(EXTRA_ACCOUNT
, target
.mAccount
);     
 345             intent
.putExtra(EXTRA_SERVER_URL
, target
.mServerUrl
);     
 347         //LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this); 
 348         //lbm.sendBroadcast(intent); 
 349         sendStickyBroadcast(intent
); 
 353     // TODO - maybe add a notification for real start of operations 
 356      * Sends a LOCAL broadcast when an operations finishes in order to the interested activities can update their view 
 358      * Local broadcasts are only delivered to activities in the same process. 
 360      * @param target            Account or URL pointing to an OC server. 
 361      * @param operation         Finished operation. 
 362      * @param result            Result of the operation. 
 364     private void sendBroadcastOperationFinished(Target target
, RemoteOperation operation
, RemoteOperationResult result
) { 
 365         Intent intent 
= new Intent(ACTION_OPERATION_FINISHED
); 
 366         intent
.putExtra(EXTRA_RESULT
, result
); 
 367         if (target
.mAccount 
!= null
) { 
 368             intent
.putExtra(EXTRA_ACCOUNT
, target
.mAccount
);     
 370             intent
.putExtra(EXTRA_SERVER_URL
, target
.mServerUrl
);     
 372         //LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this); 
 373         //lbm.sendBroadcast(intent); 
 374         sendStickyBroadcast(intent
); 
 379      * Notifies the currently subscribed listeners about the end of an operation. 
 381      * @param target            Account or URL pointing to an OC server. 
 382      * @param operation         Finished operation. 
 383      * @param result            Result of the operation. 
 385     private void callbackOperationListeners(Target target
, final RemoteOperation operation
, final RemoteOperationResult result
) { 
 386         Iterator
<OnRemoteOperationListener
> listeners 
= mBinder
.mBoundListeners
.keySet().iterator(); 
 387         while (listeners
.hasNext()) { 
 388             final OnRemoteOperationListener listener 
= listeners
.next(); 
 389             final Handler handler 
= mBinder
.mBoundListeners
.get(listener
); 
 390             if (handler 
!= null
) {  
 391                 handler
.post(new Runnable() { 
 394                         listener
.onRemoteOperationFinish(operation
, result
);