2  *   ownCloud Android client application 
   4  *   Copyright (C) 2012 Bartek Przybylski 
   5  *   Copyright (C) 2012-2015 ownCloud Inc. 
   7  *   This program is free software: you can redistribute it and/or modify 
   8  *   it under the terms of the GNU General Public License version 2, 
   9  *   as published by the Free Software Foundation. 
  11  *   This program is distributed in the hope that it will be useful, 
  12  *   but WITHOUT ANY WARRANTY; without even the implied warranty of 
  13  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
  14  *   GNU General Public License for more details. 
  16  *   You should have received a copy of the GNU General Public License 
  17  *   along with this program.  If not, see <http://www.gnu.org/licenses/>. 
  21 package com
.owncloud
.android
.files
.services
; 
  24 import java
.io
.IOException
; 
  25 import java
.util
.AbstractList
; 
  26 import java
.util
.HashMap
; 
  27 import java
.util
.Iterator
; 
  29 import java
.util
.Vector
; 
  31 import com
.owncloud
.android
.MainApp
; 
  32 import com
.owncloud
.android
.R
; 
  33 import com
.owncloud
.android
.authentication
.AccountUtils
; 
  34 import com
.owncloud
.android
.authentication
.AuthenticatorActivity
; 
  35 import com
.owncloud
.android
.datamodel
.FileDataStorageManager
; 
  36 import com
.owncloud
.android
.datamodel
.OCFile
; 
  38 import com
.owncloud
.android
.lib
.common
.network
.OnDatatransferProgressListener
; 
  39 import com
.owncloud
.android
.lib
.common
.OwnCloudAccount
; 
  40 import com
.owncloud
.android
.lib
.common
.OwnCloudClient
; 
  41 import com
.owncloud
.android
.lib
.common
.OwnCloudClientManagerFactory
; 
  42 import com
.owncloud
.android
.notifications
.NotificationBuilderWithProgressBar
; 
  43 import com
.owncloud
.android
.notifications
.NotificationDelayer
; 
  44 import com
.owncloud
.android
.lib
.common
.operations
.RemoteOperationResult
; 
  45 import com
.owncloud
.android
.lib
.common
.operations
.RemoteOperationResult
.ResultCode
; 
  46 import com
.owncloud
.android
.lib
.common
.utils
.Log_OC
; 
  47 import com
.owncloud
.android
.lib
.resources
.files
.FileUtils
; 
  48 import com
.owncloud
.android
.operations
.DownloadFileOperation
; 
  49 import com
.owncloud
.android
.ui
.activity
.FileActivity
; 
  50 import com
.owncloud
.android
.ui
.activity
.FileDisplayActivity
; 
  51 import com
.owncloud
.android
.ui
.preview
.PreviewImageActivity
; 
  52 import com
.owncloud
.android
.ui
.preview
.PreviewImageFragment
; 
  53 import com
.owncloud
.android
.utils
.ErrorMessageAdapter
; 
  55 import android
.accounts
.Account
; 
  56 import android
.accounts
.AccountManager
; 
  57 import android
.accounts
.AccountsException
; 
  58 import android
.accounts
.OnAccountsUpdateListener
; 
  59 import android
.app
.NotificationManager
; 
  60 import android
.app
.PendingIntent
; 
  61 import android
.app
.Service
; 
  62 import android
.content
.Intent
; 
  63 import android
.os
.Binder
; 
  64 import android
.os
.Handler
; 
  65 import android
.os
.HandlerThread
; 
  66 import android
.os
.IBinder
; 
  67 import android
.os
.Looper
; 
  68 import android
.os
.Message
; 
  69 import android
.os
.Process
; 
  70 import android
.support
.v4
.app
.NotificationCompat
; 
  71 import android
.util
.Pair
; 
  73 public class FileDownloader 
extends Service
 
  74         implements OnDatatransferProgressListener
, OnAccountsUpdateListener 
{ 
  76     public static final String EXTRA_ACCOUNT 
= "ACCOUNT"; 
  77     public static final String EXTRA_FILE 
= "FILE"; 
  79     private static final String DOWNLOAD_ADDED_MESSAGE 
= "DOWNLOAD_ADDED"; 
  80     private static final String DOWNLOAD_FINISH_MESSAGE 
= "DOWNLOAD_FINISH"; 
  81     public static final String EXTRA_DOWNLOAD_RESULT 
= "RESULT"; 
  82     public static final String EXTRA_FILE_PATH 
= "FILE_PATH"; 
  83     public static final String EXTRA_REMOTE_PATH 
= "REMOTE_PATH"; 
  84     public static final String EXTRA_LINKED_TO_PATH 
= "LINKED_TO"; 
  85     public static final String ACCOUNT_NAME 
= "ACCOUNT_NAME"; 
  87     private static final String TAG 
= "FileDownloader"; 
  89     private Looper mServiceLooper
; 
  90     private ServiceHandler mServiceHandler
; 
  91     private IBinder mBinder
; 
  92     private OwnCloudClient mDownloadClient 
= null
; 
  93     private Account mCurrentAccount 
= null
; 
  94     private FileDataStorageManager mStorageManager
; 
  96     private IndexedForest
<DownloadFileOperation
> mPendingDownloads 
= new IndexedForest
<DownloadFileOperation
>(); 
  98     private DownloadFileOperation mCurrentDownload 
= null
; 
 100     private NotificationManager mNotificationManager
; 
 101     private NotificationCompat
.Builder mNotificationBuilder
; 
 102     private int mLastPercent
; 
 105     public static String 
getDownloadAddedMessage() { 
 106         return FileDownloader
.class.getName() + DOWNLOAD_ADDED_MESSAGE
; 
 109     public static String 
getDownloadFinishMessage() { 
 110         return FileDownloader
.class.getName() + DOWNLOAD_FINISH_MESSAGE
; 
 114      * Service initialization 
 117     public void onCreate() { 
 119         Log_OC
.d(TAG
, "Creating service"); 
 120         mNotificationManager 
= (NotificationManager
) getSystemService(NOTIFICATION_SERVICE
); 
 121         HandlerThread thread 
= new HandlerThread("FileDownloaderThread", 
 122                 Process
.THREAD_PRIORITY_BACKGROUND
); 
 124         mServiceLooper 
= thread
.getLooper(); 
 125         mServiceHandler 
= new ServiceHandler(mServiceLooper
, this); 
 126         mBinder 
= new FileDownloaderBinder(); 
 128         // add AccountsUpdatedListener 
 129         AccountManager am 
= AccountManager
.get(getApplicationContext()); 
 130         am
.addOnAccountsUpdatedListener(this, null
, false
); 
 138     public void onDestroy() { 
 139         Log_OC
.v(TAG
, "Destroying service"); 
 141         mServiceHandler 
= null
; 
 142         mServiceLooper
.quit(); 
 143         mServiceLooper 
= null
; 
 144         mNotificationManager 
= null
; 
 146         // remove AccountsUpdatedListener 
 147         AccountManager am 
= AccountManager
.get(getApplicationContext()); 
 148         am
.removeOnAccountsUpdatedListener(this); 
 155      * Entry point to add one or several files to the queue of downloads. 
 157      * New downloads are added calling to startService(), resulting in a call to this method. 
 158      * This ensures the service will keep on working although the caller activity goes away. 
 161     public int onStartCommand(Intent intent
, int flags
, int startId
) { 
 162         Log_OC
.d(TAG
, "Starting command with id " + startId
); 
 164         if (!intent
.hasExtra(EXTRA_ACCOUNT
) || 
 165                 !intent
.hasExtra(EXTRA_FILE
) 
 167             Log_OC
.e(TAG
, "Not enough information provided in intent"); 
 168             return START_NOT_STICKY
; 
 170             final Account account 
= intent
.getParcelableExtra(EXTRA_ACCOUNT
); 
 171             final OCFile file 
= intent
.getParcelableExtra(EXTRA_FILE
); 
 174                     "NOW " + TAG + ", thread " + Thread.currentThread().getName(), 
 175                     "Received request to download file" 
 178             AbstractList
<String
> requestedDownloads 
= new Vector
<String
>(); 
 180                 DownloadFileOperation newDownload 
= new DownloadFileOperation(account
, file
); 
 181                 newDownload
.addDatatransferProgressListener(this); 
 182                 newDownload
.addDatatransferProgressListener((FileDownloaderBinder
) mBinder
); 
 183                 Pair
<String
, String
> putResult 
= mPendingDownloads
.putIfAbsent( 
 184                         account
, file
.getRemotePath(), newDownload
 
 186                 String downloadKey 
= putResult
.first
; 
 187                 requestedDownloads
.add(downloadKey
); 
 189                         "NOW " + TAG + ", thread " + Thread.currentThread().getName(), 
 190                         "Download on " + file.getRemotePath() + " added to queue" 
 193                 // Store file on db with state 'downloading' 
 195                     TODO - check if helps with UI responsiveness, 
 196                     letting only folders use FileDownloaderBinder to check 
 197                     FileDataStorageManager storageManager = 
 198                     new FileDataStorageManager(account, getContentResolver()); 
 199                     file.setDownloading(true); 
 200                     storageManager.saveFile(file); 
 203                 sendBroadcastNewDownload(newDownload
, putResult
.second
); 
 205             } catch (IllegalArgumentException e
) { 
 206                 Log_OC
.e(TAG
, "Not enough information provided in intent: " + e
.getMessage()); 
 207                 return START_NOT_STICKY
; 
 210             if (requestedDownloads
.size() > 0) { 
 211                 Message msg 
= mServiceHandler
.obtainMessage(); 
 213                 msg
.obj 
= requestedDownloads
; 
 214                 mServiceHandler
.sendMessage(msg
); 
 219         return START_NOT_STICKY
; 
 224      * Provides a binder object that clients can use to perform operations on the queue of downloads, 
 225      * excepting the addition of new files. 
 227      * Implemented to perform cancellation, pause and resume of existing downloads. 
 230     public IBinder 
onBind(Intent arg0
) { 
 236      * Called when ALL the bound clients were onbound. 
 239     public boolean onUnbind(Intent intent
) { 
 240         ((FileDownloaderBinder
) mBinder
).clearListeners(); 
 241         return false
;   // not accepting rebinding (default behaviour) 
 245     public void onAccountsUpdated(Account
[] accounts
) { 
 246          //review the current download and cancel it if its account doesn't exist 
 247         if (mCurrentDownload 
!= null 
&& 
 248                 !AccountUtils
.exists(mCurrentDownload
.getAccount(), getApplicationContext())) { 
 249             mCurrentDownload
.cancel(); 
 251         // The rest of downloads are cancelled when they try to start 
 256      * Binder to let client components to perform operations on the queue of downloads. 
 258      * It provides by itself the available operations. 
 260     public class FileDownloaderBinder 
extends Binder 
implements OnDatatransferProgressListener 
{ 
 263          * Map of listeners that will be reported about progress of downloads from a 
 264          * {@link FileDownloaderBinder} 
 267         private Map
<Long
, OnDatatransferProgressListener
> mBoundListeners 
= 
 268                 new HashMap
<Long
, OnDatatransferProgressListener
>(); 
 272          * Cancels a pending or current download of a remote file. 
 274          * @param account ownCloud account where the remote file is stored. 
 275          * @param file    A file in the queue of pending downloads 
 277         public void cancel(Account account
, OCFile file
) { 
 279                     "NOW " + TAG + ", thread " + Thread.currentThread().getName(), 
 280                     "Received request to cancel download of " + file.getRemotePath() 
 282             Log_OC.v(   "NOW " + TAG + ", thread " + Thread.currentThread().getName(), 
 283                     "Removing download of " + file.getRemotePath());*/ 
 284             Pair
<DownloadFileOperation
, String
> removeResult 
= 
 285                     mPendingDownloads
.remove(account
, file
.getRemotePath()); 
 286             DownloadFileOperation download 
= removeResult
.first
; 
 287             if (download 
!= null
) { 
 288                 /*Log_OC.v(   "NOW " + TAG + ", thread " + Thread.currentThread().getName(), 
 289                         "Canceling returned download of " + file.getRemotePath());*/ 
 292                 if (mCurrentDownload 
!= null 
&& mCurrentAccount 
!= null 
&& 
 293                         mCurrentDownload
.getRemotePath().startsWith(file
.getRemotePath()) && 
 294                         account
.name
.equals(mCurrentAccount
.name
)) { 
 295                     /*Log_OC.v(   "NOW " + TAG + ", thread " + Thread.currentThread().getName(), 
 296                      "Canceling current sync as descendant: " + mCurrentDownload.getRemotePath());*/ 
 297                     mCurrentDownload
.cancel(); 
 303          * Cancels a pending or current upload for an account 
 305          * @param account Owncloud accountName where the remote file will be stored. 
 307         public void cancel(Account account
) { 
 308             Log_OC
.d(TAG
, "Account= " + account
.name
); 
 310             if (mCurrentDownload 
!= null
) { 
 311                 Log_OC
.d(TAG
, "Current Download Account= " + mCurrentDownload
.getAccount().name
); 
 312                 if (mCurrentDownload
.getAccount().name
.equals(account
.name
)) { 
 313                     mCurrentDownload
.cancel(); 
 316             // Cancel pending downloads 
 317             cancelDownloadsForAccount(account
); 
 320         public void clearListeners() { 
 321             mBoundListeners
.clear(); 
 326          * Returns True when the file described by 'file' in the ownCloud account 'account' 
 327          * is downloading or waiting to download. 
 329          * If 'file' is a directory, returns 'true' if any of its descendant files is downloading or 
 330          * waiting to download. 
 332          * @param account ownCloud account where the remote file is stored. 
 333          * @param file    A file that could be in the queue of downloads. 
 335         public boolean isDownloading(Account account
, OCFile file
) { 
 336             if (account 
== null 
|| file 
== null
) return false
; 
 337             return (mPendingDownloads
.contains(account
, file
.getRemotePath())); 
 342          * Adds a listener interested in the progress of the download for a concrete file. 
 344          * @param listener Object to notify about progress of transfer. 
 345          * @param account  ownCloud account holding the file of interest. 
 346          * @param file     {@link OCFile} of interest for listener. 
 348         public void addDatatransferProgressListener( 
 349                 OnDatatransferProgressListener listener
, Account account
, OCFile file
 
 351             if (account 
== null 
|| file 
== null 
|| listener 
== null
) return; 
 352             //String targetKey = buildKey(account, file.getRemotePath()); 
 353             mBoundListeners
.put(file
.getFileId(), listener
); 
 358          * Removes a listener interested in the progress of the download for a concrete file. 
 360          * @param listener Object to notify about progress of transfer. 
 361          * @param account  ownCloud account holding the file of interest. 
 362          * @param file     {@link OCFile} of interest for listener. 
 364         public void removeDatatransferProgressListener( 
 365                 OnDatatransferProgressListener listener
, Account account
, OCFile file
 
 367             if (account 
== null 
|| file 
== null 
|| listener 
== null
) return; 
 368             //String targetKey = buildKey(account, file.getRemotePath()); 
 369             Long fileId 
= file
.getFileId(); 
 370             if (mBoundListeners
.get(fileId
) == listener
) { 
 371                 mBoundListeners
.remove(fileId
); 
 376         public void onTransferProgress(long progressRate
, long totalTransferredSoFar
, 
 377                                        long totalToTransfer
, String fileName
) { 
 378             //String key = buildKey(mCurrentDownload.getAccount(), 
 379             // mCurrentDownload.getFile().getRemotePath()); 
 380             OnDatatransferProgressListener boundListener 
= 
 381                     mBoundListeners
.get(mCurrentDownload
.getFile().getFileId()); 
 382             if (boundListener 
!= null
) { 
 383                 boundListener
.onTransferProgress(progressRate
, totalTransferredSoFar
, 
 384                         totalToTransfer
, fileName
); 
 389          * Review downloads and cancel it if its account doesn't exist 
 391         public void checkAccountOfCurrentDownload() { 
 392             if (mCurrentDownload 
!= null 
&& 
 393                     !AccountUtils
.exists(mCurrentDownload
.getAccount(), getApplicationContext())) { 
 394                 mCurrentDownload
.cancel(); 
 396             // The rest of downloads are cancelled when they try to start 
 403      * Download worker. Performs the pending downloads in the order they were requested. 
 405      * Created with the Looper of a new thread, started in {@link FileUploader#onCreate()}. 
 407     private static class ServiceHandler 
extends Handler 
{ 
 408         // don't make it a final class, and don't remove the static ; lint will warn about a 
 409         // possible memory leak 
 410         FileDownloader mService
; 
 412         public ServiceHandler(Looper looper
, FileDownloader service
) { 
 415                 throw new IllegalArgumentException("Received invalid NULL in parameter 'service'"); 
 420         public void handleMessage(Message msg
) { 
 421             @SuppressWarnings("unchecked") 
 422             AbstractList
<String
> requestedDownloads 
= (AbstractList
<String
>) msg
.obj
; 
 423             if (msg
.obj 
!= null
) { 
 424                 Iterator
<String
> it 
= requestedDownloads
.iterator(); 
 425                 while (it
.hasNext()) { 
 426                     String next 
= it
.next(); 
 427                     mService
.downloadFile(next
); 
 430             Log_OC
.d(TAG
, "Stopping after command with id " + msg
.arg1
); 
 431             mService
.stopSelf(msg
.arg1
); 
 437      * Core download method: requests a file to download and stores it. 
 439      * @param downloadKey Key to access the download to perform, contained in mPendingDownloads 
 441     private void downloadFile(String downloadKey
) { 
 443         /*Log_OC.v(   "NOW " + TAG + ", thread " + Thread.currentThread().getName(), 
 444                 "Getting download of " + downloadKey);*/ 
 445         mCurrentDownload 
= mPendingDownloads
.get(downloadKey
); 
 447         if (mCurrentDownload 
!= null
) { 
 448             // Detect if the account exists 
 449             if (AccountUtils
.exists(mCurrentDownload
.getAccount(), getApplicationContext())) { 
 450                 Log_OC
.d(TAG
, "Account " + mCurrentDownload
.getAccount().name 
+ " exists"); 
 451                 notifyDownloadStart(mCurrentDownload
); 
 453                 RemoteOperationResult downloadResult 
= null
; 
 455                     /// prepare client object to send the request to the ownCloud server 
 456                     if (mCurrentAccount 
== null 
|| 
 457                             !mCurrentAccount
.equals(mCurrentDownload
.getAccount())) { 
 458                         mCurrentAccount 
= mCurrentDownload
.getAccount(); 
 459                         mStorageManager 
= new FileDataStorageManager( 
 463                     }   // else, reuse storage manager from previous operation 
 465                     // always get client from client manager, to get fresh credentials in case 
 467                     OwnCloudAccount ocAccount 
= new OwnCloudAccount(mCurrentAccount
, this); 
 468                     mDownloadClient 
= OwnCloudClientManagerFactory
.getDefaultSingleton(). 
 469                             getClientFor(ocAccount
, this, MainApp
.getUserAgent()); 
 472                     /// perform the download 
 473                     /*Log_OC.v(   "NOW " + TAG + ", thread " + Thread.currentThread().getName(), 
 474                         "Executing download of " + mCurrentDownload.getRemotePath());*/ 
 475                     downloadResult 
= mCurrentDownload
.execute(mDownloadClient
, 
 476                             MainApp
.getUserAgent()); 
 477                     if (downloadResult
.isSuccess()) { 
 478                         saveDownloadedFile(); 
 481                 } catch (AccountsException e
) { 
 482                     Log_OC
.e(TAG
, "Error while trying to get authorization for " 
 483                             + mCurrentAccount
.name
, e
); 
 484                     downloadResult 
= new RemoteOperationResult(e
); 
 485                 } catch (IOException e
) { 
 486                     Log_OC
.e(TAG
, "Error while trying to get authorization for " 
 487                             + mCurrentAccount
.name
, e
); 
 488                     downloadResult 
= new RemoteOperationResult(e
); 
 491                 /*Log_OC.v(   "NOW " + TAG + ", thread " + Thread.currentThread().getName(), 
 492                         "Removing payload " + mCurrentDownload.getRemotePath());*/ 
 494                     Pair
<DownloadFileOperation
, String
> removeResult 
= 
 495                             mPendingDownloads
.removePayload(mCurrentAccount
, 
 496                                     mCurrentDownload
.getRemotePath()); 
 499                     notifyDownloadResult(mCurrentDownload
, downloadResult
); 
 501                     sendBroadcastDownloadFinished(mCurrentDownload
, downloadResult
, 
 502                             removeResult
.second
); 
 505                 // Cancel the transfer 
 506                 Log_OC
.d(TAG
, "Account " + mCurrentDownload
.getAccount().toString() + 
 508                 cancelDownloadsForAccount(mCurrentDownload
.getAccount()); 
 516      * Updates the OC File after a successful download. 
 518     private void saveDownloadedFile() { 
 519         OCFile file 
= mStorageManager
.getFileById(mCurrentDownload
.getFile().getFileId()); 
 520         long syncDate 
= System
.currentTimeMillis(); 
 521         file
.setLastSyncDateForProperties(syncDate
); 
 522         file
.setLastSyncDateForData(syncDate
); 
 523         file
.setNeedsUpdateThumbnail(true
); 
 524         file
.setModificationTimestamp(mCurrentDownload
.getModificationTimestamp()); 
 525         file
.setModificationTimestampAtLastSyncForData(mCurrentDownload
.getModificationTimestamp()); 
 526         // file.setEtag(mCurrentDownload.getEtag());    // TODO Etag, where available 
 527         file
.setMimetype(mCurrentDownload
.getMimeType()); 
 528         file
.setStoragePath(mCurrentDownload
.getSavePath()); 
 529         file
.setFileLength((new File(mCurrentDownload
.getSavePath()).length())); 
 530         file
.setRemoteId(mCurrentDownload
.getFile().getRemoteId()); 
 531         mStorageManager
.saveFile(file
); 
 532         mStorageManager
.triggerMediaScan(file
.getStoragePath()); 
 536      * Update the OC File after a unsuccessful download 
 538     private void updateUnsuccessfulDownloadedFile() { 
 539         OCFile file 
= mStorageManager
.getFileById(mCurrentDownload
.getFile().getFileId()); 
 540         file
.setDownloading(false
); 
 541         mStorageManager
.saveFile(file
); 
 546      * Creates a status notification to show the download progress 
 548      * @param download Download operation starting. 
 550     private void notifyDownloadStart(DownloadFileOperation download
) { 
 551         /// create status notification with a progress bar 
 553         mNotificationBuilder 
= 
 554                 NotificationBuilderWithProgressBar
.newNotificationBuilderWithProgressBar(this); 
 556                 .setSmallIcon(R
.drawable
.notification_icon
) 
 557                 .setTicker(getString(R
.string
.downloader_download_in_progress_ticker
)) 
 558                 .setContentTitle(getString(R
.string
.downloader_download_in_progress_ticker
)) 
 560                 .setProgress(100, 0, download
.getSize() < 0) 
 562                         String
.format(getString(R
.string
.downloader_download_in_progress_content
), 0, 
 563                                 new File(download
.getSavePath()).getName()) 
 566         /// includes a pending intent in the notification showing the details view of the file 
 567         Intent showDetailsIntent 
= null
; 
 568         if (PreviewImageFragment
.canBePreviewed(download
.getFile())) { 
 569             showDetailsIntent 
= new Intent(this, PreviewImageActivity
.class); 
 571             showDetailsIntent 
= new Intent(this, FileDisplayActivity
.class); 
 573         showDetailsIntent
.putExtra(FileActivity
.EXTRA_FILE
, download
.getFile()); 
 574         showDetailsIntent
.putExtra(FileActivity
.EXTRA_ACCOUNT
, download
.getAccount()); 
 575         showDetailsIntent
.setFlags(Intent
.FLAG_ACTIVITY_CLEAR_TOP
); 
 577         mNotificationBuilder
.setContentIntent(PendingIntent
.getActivity( 
 578                 this, (int) System
.currentTimeMillis(), showDetailsIntent
, 0 
 581         mNotificationManager
.notify(R
.string
.downloader_download_in_progress_ticker
, mNotificationBuilder
.build()); 
 586      * Callback method to update the progress bar in the status notification. 
 589     public void onTransferProgress(long progressRate
, long totalTransferredSoFar
, 
 590                                    long totalToTransfer
, String filePath
) { 
 591         int percent 
= (int) (100.0 * ((double) totalTransferredSoFar
) / ((double) totalToTransfer
)); 
 592         if (percent 
!= mLastPercent
) { 
 593             mNotificationBuilder
.setProgress(100, percent
, totalToTransfer 
< 0); 
 594             String fileName 
= filePath
.substring(filePath
.lastIndexOf(FileUtils
.PATH_SEPARATOR
) + 1); 
 595             String text 
= String
.format(getString(R
.string
.downloader_download_in_progress_content
), percent
, fileName
); 
 596             mNotificationBuilder
.setContentText(text
); 
 597             mNotificationManager
.notify(R
.string
.downloader_download_in_progress_ticker
, mNotificationBuilder
.build()); 
 599         mLastPercent 
= percent
; 
 604      * Updates the status notification with the result of a download operation. 
 606      * @param downloadResult Result of the download operation. 
 607      * @param download       Finished download operation 
 609     private void notifyDownloadResult(DownloadFileOperation download
, 
 610                                       RemoteOperationResult downloadResult
) { 
 611         mNotificationManager
.cancel(R
.string
.downloader_download_in_progress_ticker
); 
 612         if (!downloadResult
.isCancelled()) { 
 613             int tickerId 
= (downloadResult
.isSuccess()) ? R
.string
.downloader_download_succeeded_ticker 
: 
 614                     R
.string
.downloader_download_failed_ticker
; 
 616             boolean needsToUpdateCredentials 
= ( 
 617                     downloadResult
.getCode() == ResultCode
.UNAUTHORIZED 
|| 
 618                             downloadResult
.isIdPRedirection() 
 620             tickerId 
= (needsToUpdateCredentials
) ?
 
 621                     R
.string
.downloader_download_failed_credentials_error 
: tickerId
; 
 624                     .setTicker(getString(tickerId
)) 
 625                     .setContentTitle(getString(tickerId
)) 
 628                     .setProgress(0, 0, false
); 
 630             if (needsToUpdateCredentials
) { 
 632                 // let the user update credentials with one click 
 633                 Intent updateAccountCredentials 
= new Intent(this, AuthenticatorActivity
.class); 
 634                 updateAccountCredentials
.putExtra(AuthenticatorActivity
.EXTRA_ACCOUNT
, 
 635                         download
.getAccount()); 
 636                 updateAccountCredentials
.putExtra( 
 637                         AuthenticatorActivity
.EXTRA_ACTION
, 
 638                         AuthenticatorActivity
.ACTION_UPDATE_EXPIRED_TOKEN
 
 640                 updateAccountCredentials
.addFlags(Intent
.FLAG_ACTIVITY_NEW_TASK
); 
 641                 updateAccountCredentials
.addFlags(Intent
.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
); 
 642                 updateAccountCredentials
.addFlags(Intent
.FLAG_FROM_BACKGROUND
); 
 644                         .setContentIntent(PendingIntent
.getActivity( 
 645                                 this, (int) System
.currentTimeMillis(), updateAccountCredentials
, 
 646                                 PendingIntent
.FLAG_ONE_SHOT
)); 
 649                 // TODO put something smart in showDetailsIntent 
 650                 Intent showDetailsIntent 
= new Intent(); 
 652                         .setContentIntent(PendingIntent
.getActivity( 
 653                                 this, (int) System
.currentTimeMillis(), showDetailsIntent
, 0)); 
 656             mNotificationBuilder
.setContentText( 
 657                     ErrorMessageAdapter
.getErrorCauseMessage(downloadResult
, download
, 
 660             mNotificationManager
.notify(tickerId
, mNotificationBuilder
.build()); 
 662             // Remove success notification 
 663             if (downloadResult
.isSuccess()) { 
 664                 // Sleep 2 seconds, so show the notification before remove it 
 665                 NotificationDelayer
.cancelWithDelay( 
 666                         mNotificationManager
, 
 667                         R
.string
.downloader_download_succeeded_ticker
, 
 676      * Sends a broadcast when a download finishes in order to the interested activities can 
 679      * @param download               Finished download operation 
 680      * @param downloadResult         Result of the download operation 
 681      * @param unlinkedFromRemotePath Path in the downloads tree where the download was unlinked from 
 683     private void sendBroadcastDownloadFinished( 
 684             DownloadFileOperation download
, 
 685             RemoteOperationResult downloadResult
, 
 686             String unlinkedFromRemotePath
) { 
 687         Intent end 
= new Intent(getDownloadFinishMessage()); 
 688         end
.putExtra(EXTRA_DOWNLOAD_RESULT
, downloadResult
.isSuccess()); 
 689         end
.putExtra(ACCOUNT_NAME
, download
.getAccount().name
); 
 690         end
.putExtra(EXTRA_REMOTE_PATH
, download
.getRemotePath()); 
 691         end
.putExtra(EXTRA_FILE_PATH
, download
.getSavePath()); 
 692         if (unlinkedFromRemotePath 
!= null
) { 
 693             end
.putExtra(EXTRA_LINKED_TO_PATH
, unlinkedFromRemotePath
); 
 695         sendStickyBroadcast(end
); 
 700      * Sends a broadcast when a new download is added to the queue. 
 702      * @param download           Added download operation 
 703      * @param linkedToRemotePath Path in the downloads tree where the download was linked to 
 705     private void sendBroadcastNewDownload(DownloadFileOperation download
, 
 706                                           String linkedToRemotePath
) { 
 707         Intent added 
= new Intent(getDownloadAddedMessage()); 
 708         added
.putExtra(ACCOUNT_NAME
, download
.getAccount().name
); 
 709         added
.putExtra(EXTRA_REMOTE_PATH
, download
.getRemotePath()); 
 710         added
.putExtra(EXTRA_FILE_PATH
, download
.getSavePath()); 
 711         added
.putExtra(EXTRA_LINKED_TO_PATH
, linkedToRemotePath
); 
 712         sendStickyBroadcast(added
); 
 716      * Remove downloads of an account 
 718      * @param account       Downloads account to remove 
 720     private void cancelDownloadsForAccount(Account account
) { 
 721         // Cancel pending downloads 
 722         mPendingDownloads
.remove(account
);