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
.concurrent
.ConcurrentLinkedQueue
; 
  23 import com
.owncloud
.android
.datamodel
.FileDataStorageManager
; 
  25 import com
.owncloud
.android
.lib
.network
.OwnCloudClientFactory
; 
  26 import com
.owncloud
.android
.lib
.network
.OwnCloudClient
; 
  27 import com
.owncloud
.android
.operations
.GetSharesOperation
; 
  28 import com
.owncloud
.android
.operations
.common
.SyncOperation
; 
  29 import com
.owncloud
.android
.lib
.operations
.common
.RemoteOperation
; 
  30 import com
.owncloud
.android
.lib
.operations
.common
.RemoteOperationResult
; 
  31 import com
.owncloud
.android
.utils
.Log_OC
; 
  33 import android
.accounts
.Account
; 
  34 import android
.accounts
.AccountsException
; 
  35 import android
.app
.Service
; 
  36 import android
.content
.Intent
; 
  37 import android
.net
.Uri
; 
  38 import android
.os
.Binder
; 
  39 import android
.os
.Handler
; 
  40 import android
.os
.HandlerThread
; 
  41 import android
.os
.IBinder
; 
  42 import android
.os
.Looper
; 
  43 import android
.os
.Message
; 
  44 import android
.os
.Process
; 
  45 //import android.support.v4.content.LocalBroadcastManager; 
  46 import android
.util
.Pair
; 
  48 public class OperationsService 
extends Service 
{ 
  50     private static final String TAG 
= OperationsService
.class.getSimpleName(); 
  52     public static final String EXTRA_ACCOUNT 
= "ACCOUNT"; 
  53     public static final String EXTRA_SERVER_URL 
= "SERVER_URL"; 
  54     public static final String EXTRA_RESULT 
= "RESULT";     
  56     public static final String ACTION_OPERATION_ADDED 
= OperationsService
.class.getName() + ".OPERATION_ADDED"; 
  57     public static final String ACTION_OPERATION_FINISHED 
= OperationsService
.class.getName() + ".OPERATION_FINISHED"; 
  59     private ConcurrentLinkedQueue
<Pair
<Target
, RemoteOperation
>> mPendingOperations 
= new ConcurrentLinkedQueue
<Pair
<Target
, RemoteOperation
>>(); 
  61     private static class Target 
{ 
  62         public Uri mServerUrl 
= null
; 
  63         public Account mAccount 
= null
; 
  64         public Target(Account account
, Uri serverUrl
) { 
  66             mServerUrl 
= serverUrl
; 
  70     private Looper mServiceLooper
; 
  71     private ServiceHandler mServiceHandler
; 
  72     private IBinder mBinder
; 
  73     private OwnCloudClient mOwnCloudClient 
= null
; 
  74     private Target mLastTarget 
= null
; 
  75     private FileDataStorageManager mStorageManager
; 
  76     private RemoteOperation mCurrentOperation 
= null
; 
  80      * Service initialization 
  83     public void onCreate() { 
  85         HandlerThread thread 
= new HandlerThread("Operations service thread", Process
.THREAD_PRIORITY_BACKGROUND
); 
  87         mServiceLooper 
= thread
.getLooper(); 
  88         mServiceHandler 
= new ServiceHandler(mServiceLooper
, this); 
  89         mBinder 
= new OperationsServiceBinder(); 
  93      * Entry point to add a new operation to the queue of operations. 
  95      * New operations are added calling to startService(), resulting in a call to this method.  
  96      * This ensures the service will keep on working although the caller activity goes away. 
  98      * IMPORTANT: the only operations performed here right now is {@link GetSharedFilesOperation}. The class 
  99      * is taking advantage of it due to time constraints. 
 102     public int onStartCommand(Intent intent
, int flags
, int startId
) { 
 103         if (!intent
.hasExtra(EXTRA_ACCOUNT
) && !intent
.hasExtra(EXTRA_SERVER_URL
)) { 
 104             Log_OC
.e(TAG
, "Not enough information provided in intent"); 
 105             return START_NOT_STICKY
; 
 108             Account account 
= intent
.getParcelableExtra(EXTRA_ACCOUNT
); 
 109             String serverUrl 
= intent
.getStringExtra(EXTRA_SERVER_URL
); 
 110             Target target 
= new Target(account
, (serverUrl 
== null
) ? null 
: Uri
.parse(serverUrl
)); 
 111             GetSharesOperation operation 
= new GetSharesOperation(); 
 112             mPendingOperations
.add(new Pair
<Target 
, RemoteOperation
>(target
, operation
)); 
 113             sendBroadcastNewOperation(target
, operation
); 
 115             Message msg 
= mServiceHandler
.obtainMessage(); 
 117             mServiceHandler
.sendMessage(msg
); 
 119         } catch (IllegalArgumentException e
) { 
 120             Log_OC
.e(TAG
, "Bad information provided in intent: " + e
.getMessage()); 
 121             return START_NOT_STICKY
; 
 124         return START_NOT_STICKY
; 
 129      * Provides a binder object that clients can use to perform actions on the queue of operations,  
 130      * except the addition of new operations.  
 133     public IBinder 
onBind(Intent intent
) { 
 139      * Called when ALL the bound clients were unbound. 
 142     public boolean onUnbind(Intent intent
) { 
 143         //((OperationsServiceBinder)mBinder).clearListeners(); 
 144         return false
;   // not accepting rebinding (default behaviour) 
 149      *  Binder to let client components to perform actions on the queue of operations. 
 151      *  It provides by itself the available operations. 
 153     public class OperationsServiceBinder 
extends Binder 
{ 
 159      * Operations worker. Performs the pending operations in the order they were requested.  
 161      * Created with the Looper of a new thread, started in {@link OperationsService#onCreate()}.  
 163     private static class ServiceHandler 
extends Handler 
{ 
 164         // don't make it a final class, and don't remove the static ; lint will warn about a possible memory leak 
 165         OperationsService mService
; 
 166         public ServiceHandler(Looper looper
, OperationsService service
) { 
 168             if (service 
== null
) { 
 169                 throw new IllegalArgumentException("Received invalid NULL in parameter 'service'"); 
 175         public void handleMessage(Message msg
) { 
 176             mService
.nextOperation(); 
 177             mService
.stopSelf(msg
.arg1
); 
 183      * Performs the next operation in the queue 
 185     private void nextOperation() { 
 187         Pair
<Target
, RemoteOperation
> next 
= null
; 
 188         synchronized(mPendingOperations
) { 
 189             next 
= mPendingOperations
.peek(); 
 194             mCurrentOperation 
= next
.second
; 
 195             RemoteOperationResult result 
= null
; 
 197                 /// prepare client object to send the request to the ownCloud server 
 198                 if (mLastTarget 
== null 
|| !mLastTarget
.equals(next
.first
)) { 
 199                     mLastTarget 
= next
.first
; 
 200                     if (mLastTarget
.mAccount 
!= null
) { 
 201                         mOwnCloudClient 
= OwnCloudClientFactory
.createOwnCloudClient(mLastTarget
.mAccount
, getApplicationContext()); 
 202                         mStorageManager 
= new FileDataStorageManager(mLastTarget
.mAccount
, getContentResolver()); 
 204                         mOwnCloudClient 
= OwnCloudClientFactory
.createOwnCloudClient(mLastTarget
.mServerUrl
, getApplicationContext(), true
);    // this is not good enough 
 205                         mStorageManager 
= null
; 
 209                 /// perform the operation 
 210                 if (mCurrentOperation 
instanceof SyncOperation
) { 
 211                     result 
= ((SyncOperation
)mCurrentOperation
).execute(mOwnCloudClient
, mStorageManager
); 
 213                     result 
= mCurrentOperation
.execute(mOwnCloudClient
); 
 216             } catch (AccountsException e
) { 
 217                 if (mLastTarget
.mAccount 
== null
) { 
 218                     Log_OC
.e(TAG
, "Error while trying to get autorization for a NULL account", e
); 
 220                     Log_OC
.e(TAG
, "Error while trying to get autorization for " + mLastTarget
.mAccount
.name
, e
); 
 222                 result 
= new RemoteOperationResult(e
); 
 224             } catch (IOException e
) { 
 225                 if (mLastTarget
.mAccount 
== null
) { 
 226                     Log_OC
.e(TAG
, "Error while trying to get autorization for a NULL account", e
); 
 228                     Log_OC
.e(TAG
, "Error while trying to get autorization for " + mLastTarget
.mAccount
.name
, e
); 
 230                 result 
= new RemoteOperationResult(e
); 
 231             } catch (Exception e
) { 
 232                 if (mLastTarget
.mAccount 
== null
) { 
 233                     Log_OC
.e(TAG
, "Unexpected error for a NULL account", e
); 
 235                     Log_OC
.e(TAG
, "Unexpected error for " + mLastTarget
.mAccount
.name
, e
); 
 237                 result 
= new RemoteOperationResult(e
); 
 240                 synchronized(mPendingOperations
) { 
 241                     mPendingOperations
.poll(); 
 245             sendBroadcastOperationFinished(mLastTarget
, mCurrentOperation
, result
); 
 251      * Sends a LOCAL broadcast when a new operation is added to the queue. 
 253      * Local broadcasts are only delivered to activities in the same process. 
 255      * @param target            Account or URL pointing to an OC server. 
 256      * @param operation         Added operation. 
 258     private void sendBroadcastNewOperation(Target target
, RemoteOperation operation
) { 
 259         Intent intent 
= new Intent(ACTION_OPERATION_ADDED
); 
 260         if (target
.mAccount 
!= null
) { 
 261             intent
.putExtra(EXTRA_ACCOUNT
, target
.mAccount
);     
 263             intent
.putExtra(EXTRA_SERVER_URL
, target
.mServerUrl
);     
 265         //LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this); 
 266         //lbm.sendBroadcast(intent); 
 267         sendStickyBroadcast(intent
); 
 271     // TODO - maybe add a notification for real start of operations 
 274      * Sends a LOCAL broadcast when an operations finishes in order to the interested activities can update their view 
 276      * Local broadcasts are only delivered to activities in the same process. 
 278      * @param target            Account or URL pointing to an OC server. 
 279      * @param operation         Finished operation. 
 280      * @param result            Result of the operation. 
 282     private void sendBroadcastOperationFinished(Target target
, RemoteOperation operation
, RemoteOperationResult result
) { 
 283         Intent intent 
= new Intent(ACTION_OPERATION_FINISHED
); 
 284         intent
.putExtra(EXTRA_RESULT
, result
); 
 285         if (target
.mAccount 
!= null
) { 
 286             intent
.putExtra(EXTRA_ACCOUNT
, target
.mAccount
);     
 288             intent
.putExtra(EXTRA_SERVER_URL
, target
.mServerUrl
);     
 290         //LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this); 
 291         //lbm.sendBroadcast(intent); 
 292         sendStickyBroadcast(intent
);