1 package com
.owncloud
.android
.files
.services
; 
   4 import java
.util
.AbstractList
; 
   5 import java
.util
.Collections
; 
   6 import java
.util
.HashMap
; 
   7 import java
.util
.Iterator
; 
   9 import java
.util
.Vector
; 
  10 import java
.util
.concurrent
.ConcurrentHashMap
; 
  11 import java
.util
.concurrent
.ConcurrentMap
; 
  13 import com
.owncloud
.android
.authenticator
.AccountAuthenticator
; 
  14 import com
.owncloud
.android
.datamodel
.FileDataStorageManager
; 
  15 import com
.owncloud
.android
.datamodel
.OCFile
; 
  16 import com
.owncloud
.android
.files
.InstantUploadBroadcastReceiver
; 
  17 import com
.owncloud
.android
.files
.services
.FileDownloader
.FileDownloaderBinder
; 
  18 import com
.owncloud
.android
.operations
.ChunkedUploadFileOperation
; 
  19 import com
.owncloud
.android
.operations
.DownloadFileOperation
; 
  20 import com
.owncloud
.android
.operations
.RemoteOperationResult
; 
  21 import com
.owncloud
.android
.operations
.UploadFileOperation
; 
  22 import com
.owncloud
.android
.ui
.activity
.FileDetailActivity
; 
  23 import com
.owncloud
.android
.ui
.fragment
.FileDetailFragment
; 
  24 import com
.owncloud
.android
.utils
.OwnCloudVersion
; 
  26 import eu
.alefzero
.webdav
.OnDatatransferProgressListener
; 
  28 import com
.owncloud
.android
.network
.OwnCloudClientUtils
; 
  30 import android
.accounts
.Account
; 
  31 import android
.accounts
.AccountManager
; 
  32 import android
.app
.Notification
; 
  33 import android
.app
.NotificationManager
; 
  34 import android
.app
.PendingIntent
; 
  35 import android
.app
.Service
; 
  36 import android
.content
.Intent
; 
  37 import android
.os
.Binder
; 
  38 import android
.os
.Handler
; 
  39 import android
.os
.HandlerThread
; 
  40 import android
.os
.IBinder
; 
  41 import android
.os
.Looper
; 
  42 import android
.os
.Message
; 
  43 import android
.os
.Process
; 
  44 import android
.util
.Log
; 
  45 import android
.widget
.RemoteViews
; 
  47 import com
.owncloud
.android
.R
; 
  48 import eu
.alefzero
.webdav
.WebdavClient
; 
  50 public class FileUploader 
extends Service 
implements OnDatatransferProgressListener 
{ 
  52     public static final String UPLOAD_FINISH_MESSAGE 
= "UPLOAD_FINISH"; 
  53     public static final String EXTRA_PARENT_DIR_ID 
= "PARENT_DIR_ID"; 
  54     public static final String EXTRA_UPLOAD_RESULT 
= "RESULT"; 
  55     public static final String EXTRA_REMOTE_PATH 
= "REMOTE_PATH"; 
  56     public static final String EXTRA_FILE_PATH 
= "FILE_PATH"; 
  58     public static final String KEY_LOCAL_FILE 
= "LOCAL_FILE"; 
  59     public static final String KEY_REMOTE_FILE 
= "REMOTE_FILE"; 
  60     public static final String KEY_ACCOUNT 
= "ACCOUNT"; 
  61     public static final String KEY_UPLOAD_TYPE 
= "UPLOAD_TYPE"; 
  62     public static final String KEY_FORCE_OVERWRITE 
= "KEY_FORCE_OVERWRITE"; 
  63     public static final String ACCOUNT_NAME 
= "ACCOUNT_NAME";     
  64     public static final String KEY_MIME_TYPE 
= "MIME_TYPE"; 
  65     public static final String KEY_INSTANT_UPLOAD 
= "INSTANT_UPLOAD"; 
  67     public static final int UPLOAD_SINGLE_FILE 
= 0; 
  68     public static final int UPLOAD_MULTIPLE_FILES 
= 1; 
  70     private static final String TAG 
= FileUploader
.class.getSimpleName(); 
  72     private Looper mServiceLooper
; 
  73     private ServiceHandler mServiceHandler
; 
  74     private IBinder mBinder
; 
  75     private WebdavClient mUploadClient 
= null
; 
  76     private Account mLastAccount 
= null
, mLastAccountWhereInstantFolderWasCreated 
= null
; 
  77     private FileDataStorageManager mStorageManager
; 
  79     //private AbstractList<Account> mAccounts = new Vector<Account>(); 
  80     //private AbstractList<UploadFileOperation> mUploads = new Vector<UploadFileOperation>(); 
  81     //private int mCurrentIndexUpload; 
  82     private ConcurrentMap
<String
, UploadFileOperation
> mPendingUploads 
= new ConcurrentHashMap
<String
, UploadFileOperation
>(); 
  83     private UploadFileOperation mCurrentUpload 
= null
; 
  85     private NotificationManager mNotificationManager
; 
  86     private Notification mNotification
; 
  87     private int mLastPercent
; 
  88     private RemoteViews mDefaultNotificationContentView
; 
  89     /*private long mTotalDataToSend, mSendData; 
  90     private int mTotalFilesToSend, mPreviousPercent; 
  91     private int mSuccessCounter;*/ 
  95      * Builds a key for mPendingUploads from the account and file to upload 
  97      * @param account   Account where the file to download is stored 
  98      * @param file      File to download 
 100     private String 
buildRemoteName(Account account
, OCFile file
) { 
 101         return account
.name 
+ file
.getRemotePath(); 
 104     private String 
buildRemoteName(Account account
, String remotePath
) { 
 105         return account
.name 
+ remotePath
; 
 110      * Checks if an ownCloud server version should support chunked uploads. 
 112      * @param version   OwnCloud version instance corresponding to an ownCloud server. 
 113      * @return          'True' if the ownCloud server with version supports chunked uploads. 
 115     private static boolean chunkedUploadIsSupported(OwnCloudVersion version
) { 
 116         return (version 
!= null 
&& version
.compareTo(OwnCloudVersion
.owncloud_v4_5
) >= 0); 
 122      * Service initialization 
 125     public void onCreate() { 
 127         mNotificationManager 
= (NotificationManager
) getSystemService(NOTIFICATION_SERVICE
); 
 128         HandlerThread thread 
= new HandlerThread("FileUploaderThread", 
 129                 Process
.THREAD_PRIORITY_BACKGROUND
); 
 131         mServiceLooper 
= thread
.getLooper(); 
 132         mServiceHandler 
= new ServiceHandler(mServiceLooper
, this); 
 133         mBinder 
= new FileUploaderBinder(); 
 138      * Entry point to add one or several files to the queue of uploads. 
 140      * New uploads are added calling to startService(), resulting in a call to this method. This ensures the service will keep on working  
 141      * although the caller activity goes away. 
 144     public int onStartCommand(Intent intent
, int flags
, int startId
) { 
 145         if (!intent
.hasExtra(KEY_ACCOUNT
) || !intent
.hasExtra(KEY_UPLOAD_TYPE
)) { 
 146             Log
.e(TAG
, "Not enough information provided in intent"); 
 147             return Service
.START_NOT_STICKY
; 
 149         int uploadType 
= intent
.getIntExtra(KEY_UPLOAD_TYPE
, -1); 
 150         if (uploadType 
== -1) { 
 151             Log
.e(TAG
, "Incorrect upload type provided"); 
 152             return Service
.START_NOT_STICKY
; 
 154         Account account 
= intent
.getParcelableExtra(KEY_ACCOUNT
); 
 156         String
[] localPaths
, remotePaths
, mimeTypes
;  
 157         if (uploadType 
== UPLOAD_SINGLE_FILE
) { 
 158             localPaths 
= new String
[] { intent
.getStringExtra(KEY_LOCAL_FILE
) }; 
 159             remotePaths 
= new String
[] { intent
 
 160                     .getStringExtra(KEY_REMOTE_FILE
) }; 
 161             mimeTypes 
= new String
[] { intent
.getStringExtra(KEY_MIME_TYPE
) }; 
 163         } else { // mUploadType == UPLOAD_MULTIPLE_FILES 
 164             localPaths 
= intent
.getStringArrayExtra(KEY_LOCAL_FILE
); 
 165             remotePaths 
= intent
.getStringArrayExtra(KEY_REMOTE_FILE
); 
 166             mimeTypes 
= intent
.getStringArrayExtra(KEY_MIME_TYPE
); 
 169         if (localPaths 
== null
) { 
 170             Log
.e(TAG
, "Incorrect array for local paths provided in upload intent"); 
 171             return Service
.START_NOT_STICKY
; 
 173         if (remotePaths 
== null
) { 
 174             Log
.e(TAG
, "Incorrect array for remote paths provided in upload intent"); 
 175             return Service
.START_NOT_STICKY
; 
 178         if (localPaths
.length 
!= remotePaths
.length
) { 
 179             Log
.e(TAG
, "Different number of remote paths and local paths!"); 
 180             return Service
.START_NOT_STICKY
; 
 183         boolean isInstant 
= intent
.getBooleanExtra(KEY_INSTANT_UPLOAD
, false
);  
 184         boolean forceOverwrite 
= intent
.getBooleanExtra(KEY_FORCE_OVERWRITE
, false
); 
 186         OwnCloudVersion ocv 
= new OwnCloudVersion(AccountManager
.get(this).getUserData(account
, AccountAuthenticator
.KEY_OC_VERSION
)); 
 187         boolean chunked 
= FileUploader
.chunkedUploadIsSupported(ocv
); 
 188         AbstractList
<String
> requestedUploads 
= new Vector
<String
>(); 
 189         String uploadKey 
= null
; 
 190         UploadFileOperation newUpload 
= null
; 
 192             for (int i
=0; i 
< localPaths
.length
; i
++) { 
 193                 uploadKey 
= buildRemoteName(account
, remotePaths
[i
]); 
 195                     newUpload 
= new ChunkedUploadFileOperation(account
, localPaths
[i
], remotePaths
[i
], ((mimeTypes
!=null
)?mimeTypes
[i
]:(String
)null
), isInstant
, forceOverwrite
); 
 197                     newUpload 
= new UploadFileOperation(account
, localPaths
[i
], remotePaths
[i
], (mimeTypes
!=null?mimeTypes
[i
]:(String
)null
), isInstant
, forceOverwrite
); 
 199                 mPendingUploads
.putIfAbsent(uploadKey
, newUpload
); 
 200                 newUpload
.addDatatransferProgressListener(this); 
 201                 requestedUploads
.add(uploadKey
); 
 204         } catch (IllegalArgumentException e
) { 
 205             Log
.e(TAG
, "Not enough information provided in intent: " + e
.getMessage()); 
 206             return START_NOT_STICKY
; 
 209         if (requestedUploads
.size() > 0) { 
 210             Message msg 
= mServiceHandler
.obtainMessage(); 
 212             msg
.obj 
= requestedUploads
; 
 213             mServiceHandler
.sendMessage(msg
); 
 216         return Service
.START_NOT_STICKY
; 
 221      * Provides a binder object that clients can use to perform operations on the queue of uploads, excepting the addition of new files.  
 223      * Implemented to perform cancellation, pause and resume of existing uploads. 
 226     public IBinder 
onBind(Intent arg0
) { 
 231      *  Binder to let client components to perform operations on the queue of uploads. 
 233      *  It provides by itself the available operations. 
 235     public class FileUploaderBinder 
extends Binder 
{ 
 238          * Cancels a pending or current upload of a remote file. 
 240          * @param account       Owncloud account where the remote file will be stored. 
 241          * @param file          A file in the queue of pending uploads 
 243         public void cancel(Account account
, OCFile file
) { 
 244             UploadFileOperation upload 
= null
; 
 245             synchronized (mPendingUploads
) { 
 246                 upload 
= mPendingUploads
.remove(buildRemoteName(account
, file
)); 
 248             if (upload 
!= null
) { 
 249                 // TODO upload.cancel(); 
 255          * Returns True when the file described by 'file' is being uploaded to the ownCloud account 'account' or waiting for it 
 257          * @param account       Owncloud account where the remote file will be stored. 
 258          * @param file          A file that could be in the queue of pending uploads 
 260         public boolean isUploading(Account account
, OCFile file
) { 
 261             synchronized (mPendingUploads
) { 
 262                 return (mPendingUploads
.containsKey(buildRemoteName(account
, file
))); 
 271      * Upload worker. Performs the pending uploads in the order they were requested.  
 273      * Created with the Looper of a new thread, started in {@link FileUploader#onCreate()}.  
 275     private static class ServiceHandler 
extends Handler 
{ 
 276         // don't make it a final class, and don't remove the static ; lint will warn about a possible memory leak 
 277         FileUploader mService
; 
 278         public ServiceHandler(Looper looper
, FileUploader service
) { 
 281                 throw new IllegalArgumentException("Received invalid NULL in parameter 'service'"); 
 286         public void handleMessage(Message msg
) { 
 287             @SuppressWarnings("unchecked") 
 288             AbstractList
<String
> requestedUploads 
= (AbstractList
<String
>) msg
.obj
; 
 289             if (msg
.obj 
!= null
) { 
 290                 Iterator
<String
> it 
= requestedUploads
.iterator(); 
 291                 while (it
.hasNext()) { 
 292                     mService
.uploadFile(it
.next()); 
 295             mService
.stopSelf(msg
.arg1
); 
 303      * Core upload method: sends the file(s) to upload 
 305      * @param uploadKey   Key to access the upload to perform, contained in mPendingUploads 
 307     public void uploadFile(String uploadKey
) { 
 309         synchronized(mPendingUploads
) { 
 310             mCurrentUpload 
= mPendingUploads
.get(uploadKey
); 
 313         if (mCurrentUpload 
!= null
) { 
 315             /// prepare upload statistics 
 316             /*mTotalDataToSend = mSendData = mPreviousPercent = 0; 
 317             Iterator<UploadFileOperation> it = mUploads.iterator(); 
 318             while (it.hasNext()) { 
 319                 mTotalDataToSend += new File(it.next().getLocalPath()).length(); 
 321             mTotalFilesToSend = mUploads.size(); 
 322             Log.d(TAG, "Will upload " + mTotalDataToSend + " bytes, with " + mUploads.size() + " files");*/ 
 325             notifyUploadStart(mCurrentUpload
); 
 328             /// prepare client object to send requests to the ownCloud server 
 329             if (mUploadClient 
== null 
|| !mLastAccount
.equals(mCurrentUpload
.getAccount())) { 
 330                 mLastAccount 
= mCurrentUpload
.getAccount(); 
 331                 mStorageManager 
= new FileDataStorageManager(mLastAccount
, getContentResolver()); 
 332                 mUploadClient 
= OwnCloudClientUtils
.createOwnCloudClient(mLastAccount
, getApplicationContext()); 
 335             /// create remote folder for instant uploads, "if necessary" (would be great that HEAD to a folder worked as with files, we should check; but it's not WebDAV standard, anyway 
 336             if (mCurrentUpload
.isInstant() && !mLastAccountWhereInstantFolderWasCreated
.equals(mCurrentUpload
.getAccount())) { 
 337                 mLastAccountWhereInstantFolderWasCreated 
= mCurrentUpload
.getAccount(); 
 338                 createRemoteFolderForInstantUploads(mUploadClient
, mStorageManager
); 
 341             /// perform the upload 
 342             RemoteOperationResult uploadResult 
= null
; 
 343             long parentDirId 
= -1; 
 345                 File remote 
= new File(mCurrentUpload
.getRemotePath()); 
 346                 parentDirId 
= mStorageManager
.getFileByPath(remote
.getParent().endsWith("/")?remote
.getParent():remote
.getParent()+"/").getFileId(); 
 347                 File local 
= new File(mCurrentUpload
.getLocalPath()); 
 348                 long size 
= local
.length(); 
 349                 uploadResult 
= mCurrentUpload
.execute(mUploadClient
); 
 350                 if (uploadResult
.isSuccess()) { 
 351                     saveNewOCFile(mCurrentUpload
, mStorageManager
, parentDirId
, size
); 
 355                 synchronized(mPendingUploads
) { 
 356                     mPendingUploads
.remove(uploadKey
); 
 361             notifyUploadResult(uploadResult
, mCurrentUpload
); 
 363             sendFinalBroadcast(mCurrentUpload
, uploadResult
, parentDirId
); 
 370      * Create remote folder for instant uploads if necessary. 
 372      * @param client            WebdavClient to the ownCloud server. 
 373      * @param storageManager    Interface to the local database caching the data in the server. 
 374      * @return                  'True' if the folder exists when the methods finishes. 
 376     private boolean createRemoteFolderForInstantUploads(WebdavClient client
, FileDataStorageManager storageManager
) { 
 377         boolean result 
= true
; 
 378         OCFile instantUploadDir 
= storageManager
.getFileByPath(InstantUploadBroadcastReceiver
.INSTANT_UPLOAD_DIR
); 
 379         if (instantUploadDir 
== null
) { 
 380             result 
= client
.createDirectory(InstantUploadBroadcastReceiver
.INSTANT_UPLOAD_DIR
);    // fail could just mean that it already exists, but local database is not synchronized; the upload will be started anyway 
 381             OCFile newDir 
= new OCFile(InstantUploadBroadcastReceiver
.INSTANT_UPLOAD_DIR
); 
 382             newDir
.setMimetype("DIR"); 
 383             newDir
.setParentId(storageManager
.getFileByPath(OCFile
.PATH_SEPARATOR
).getFileId()); 
 384             storageManager
.saveFile(newDir
); 
 390      * Saves a new OC File after a successful upload. 
 392      * @param upload            Upload operation completed. 
 393      * @param storageManager    Interface to the database where the new OCFile has to be stored. 
 394      * @param parentDirId       Id of the parent OCFile. 
 395      * @param size              Size of the file. 
 397     private void saveNewOCFile(UploadFileOperation upload
, FileDataStorageManager storageManager
, long parentDirId
, long size
) { 
 398         OCFile newFile 
= new OCFile(upload
.getRemotePath()); 
 399         newFile
.setMimetype(upload
.getMimeType()); 
 400         newFile
.setFileLength(size
); 
 401         newFile
.setModificationTimestamp(System
.currentTimeMillis()); 
 402         newFile
.setLastSyncDate(0); 
 403         newFile
.setStoragePath(upload
.getLocalPath());          
 404         newFile
.setParentId(parentDirId
); 
 405         if (upload
.getForceOverwrite()) 
 406             newFile
.setKeepInSync(true
); 
 407         storageManager
.saveFile(newFile
); 
 411      * Creates a status notification to show the upload progress 
 413      * @param upload    Upload operation starting. 
 415     private void notifyUploadStart(UploadFileOperation upload
) { 
 416         /// create status notification with a progress bar 
 418         mNotification 
= new Notification(R
.drawable
.icon
, getString(R
.string
.uploader_upload_in_progress_ticker
), System
.currentTimeMillis()); 
 419         mNotification
.flags 
|= Notification
.FLAG_ONGOING_EVENT
; 
 420         mDefaultNotificationContentView 
= mNotification
.contentView
; 
 421         mNotification
.contentView 
= new RemoteViews(getApplicationContext().getPackageName(), R
.layout
.progressbar_layout
); 
 422         mNotification
.contentView
.setProgressBar(R
.id
.status_progress
, 100, 0, false
); 
 423         mNotification
.contentView
.setTextViewText(R
.id
.status_text
, String
.format(getString(R
.string
.uploader_upload_in_progress_content
), 0, new File(upload
.getLocalPath()).getName())); 
 424         mNotification
.contentView
.setImageViewResource(R
.id
.status_icon
, R
.drawable
.icon
); 
 426         /// includes a pending intent in the notification showing the details view of the file 
 428         Intent showDetailsIntent = new Intent(this, FileDetailActivity.class); 
 429         showDetailsIntent.putExtra(FileDetailFragment.EXTRA_FILE, upload.getFile()); 
 430         showDetailsIntent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, upload.getAccount()); 
 431         showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 
 432         mNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(), 0, showDetailsIntent, PendingIntent.FLAG_UPDATE_CURRENT); 
 435         mNotificationManager
.notify(R
.string
.uploader_upload_in_progress_ticker
, mNotification
); 
 440      * Callback method to update the progress bar in the status notification 
 443     public void onTransferProgress(long progressRate
, long totalTransferredSoFar
, long totalToTransfer
, String fileName
) { 
 444         int percent 
= (int)(100.0*((double)totalTransferredSoFar
)/((double)totalToTransfer
)); 
 445         if (percent 
!= mLastPercent
) { 
 446             mNotification
.contentView
.setProgressBar(R
.id
.status_progress
, 100, percent
, false
); 
 447             String text 
= String
.format(getString(R
.string
.uploader_upload_in_progress_content
), percent
, fileName
); 
 448             mNotification
.contentView
.setTextViewText(R
.id
.status_text
, text
); 
 449             mNotificationManager
.notify(R
.string
.uploader_upload_in_progress_ticker
, mNotification
); 
 451         mLastPercent 
= percent
; 
 456      * Callback method to update the progress bar in the status notification  (old version) 
 459     public void onTransferProgress(long progressRate
) { 
 460         // NOTHING TO DO HERE ANYMORE 
 465      * Updates the status notification with the result of an upload operation. 
 467      * @param uploadResult    Result of the upload operation. 
 468      * @param upload          Finished upload operation 
 470     private void notifyUploadResult(RemoteOperationResult uploadResult
, UploadFileOperation upload
) { 
 471         if (uploadResult
.isCancelled()) { 
 472             /// cancelled operation -> silent removal of progress notification 
 473             mNotificationManager
.cancel(R
.string
.uploader_upload_in_progress_ticker
); 
 475         } else if (uploadResult
.isSuccess()) { 
 476             /// success -> silent update of progress notification to success message  
 477             mNotification
.flags ^
= Notification
.FLAG_ONGOING_EVENT
; // remove the ongoing flag 
 478             mNotification
.flags 
|= Notification
.FLAG_AUTO_CANCEL
; 
 479             mNotification
.contentView 
= mDefaultNotificationContentView
; 
 481             /// includes a pending intent in the notification showing the details view of the file 
 483             Intent showDetailsIntent = new Intent(this, FileDetailActivity.class); 
 484             showDetailsIntent.putExtra(FileDetailFragment.EXTRA_FILE, upload.getFile()); 
 485             showDetailsIntent.putExtra(FileDetailFragment.EXTRA_ACCOUNT, upload.getAccount()); 
 486             showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 
 487             mNotification.contentIntent = PendingIntent.getActivity(getApplicationContext(), 0, showDetailsIntent, PendingIntent.FLAG_UPDATE_CURRENT); 
 490             mNotification
.setLatestEventInfo(   getApplicationContext(),  
 491                                                 getString(R
.string
.uploader_upload_succeeded_ticker
),  
 492                                                 String
.format(getString(R
.string
.uploader_upload_succeeded_content_single
), (new File(upload
.getLocalPath())).getName()),  
 493                                                 mNotification
.contentIntent
); 
 495             mNotificationManager
.notify(R
.string
.uploader_upload_in_progress_ticker
, mNotification
);    // NOT AN ERROR; uploader_upload_in_progress_ticker is the target, not a new notification 
 497             /* Notification about multiple uploads: pending of update 
 498             mNotification.setLatestEventInfo(   getApplicationContext(),  
 499                                                     getString(R.string.uploader_upload_succeeded_ticker),  
 500                                                     String.format(getString(R.string.uploader_upload_succeeded_content_multiple), mSuccessCounter),  
 501                                                     mNotification.contentIntent); 
 505             /// fail -> explicit failure notification 
 506             mNotificationManager
.cancel(R
.string
.uploader_upload_in_progress_ticker
); 
 507             Notification finalNotification 
= new Notification(R
.drawable
.icon
, getString(R
.string
.uploader_upload_failed_ticker
), System
.currentTimeMillis()); 
 508             finalNotification
.flags 
|= Notification
.FLAG_AUTO_CANCEL
; 
 509             // TODO put something smart in the contentIntent below 
 510             finalNotification
.contentIntent 
= PendingIntent
.getActivity(getApplicationContext(), 0, new Intent(), PendingIntent
.FLAG_UPDATE_CURRENT
); 
 511             finalNotification
.setLatestEventInfo(   getApplicationContext(),  
 512                                                     getString(R
.string
.uploader_upload_failed_ticker
),  
 513                                                     String
.format(getString(R
.string
.uploader_upload_failed_content_single
), (new File(upload
.getLocalPath())).getName()),  
 514                                                     finalNotification
.contentIntent
); 
 516             mNotificationManager
.notify(R
.string
.uploader_upload_failed_ticker
, finalNotification
); 
 518             /* Notification about multiple uploads failure: pending of update 
 519             finalNotification.setLatestEventInfo(   getApplicationContext(),  
 520                                                         getString(R.string.uploader_upload_failed_ticker),  
 521                                                         String.format(getString(R.string.uploader_upload_failed_content_multiple), mSuccessCounter, mTotalFilesToSend),  
 522                                                         finalNotification.contentIntent); 
 530      * Sends a broadcast in order to the interested activities can update their view 
 532      * @param upload          Finished upload operation 
 533      * @param uploadResult    Result of the upload operation 
 535     private void sendFinalBroadcast(UploadFileOperation upload
, RemoteOperationResult uploadResult
, long parentDirId
) { 
 536         Intent end 
= new Intent(UPLOAD_FINISH_MESSAGE
); 
 537         end
.putExtra(EXTRA_REMOTE_PATH
, upload
.getRemotePath()); 
 538         end
.putExtra(EXTRA_FILE_PATH
, upload
.getLocalPath()); 
 539         end
.putExtra(ACCOUNT_NAME
, upload
.getAccount().name
); 
 540         end
.putExtra(EXTRA_UPLOAD_RESULT
, uploadResult
.isSuccess()); 
 541         end
.putExtra(EXTRA_PARENT_DIR_ID
, parentDirId
);