1 package com
.owncloud
.android
.files
.services
; 
   4 import java
.util
.AbstractList
; 
   5 import java
.util
.Iterator
; 
   6 import java
.util
.Vector
; 
   7 import java
.util
.concurrent
.ConcurrentHashMap
; 
   8 import java
.util
.concurrent
.ConcurrentMap
; 
  10 import com
.owncloud
.android
.db
.ProviderMeta
.ProviderTableMeta
; 
  11 import eu
.alefzero
.webdav
.OnDatatransferProgressListener
; 
  13 import com
.owncloud
.android
.network
.OwnCloudClientUtils
; 
  14 import com
.owncloud
.android
.operations
.DownloadFileOperation
; 
  15 import com
.owncloud
.android
.operations
.RemoteOperationResult
; 
  17 import android
.accounts
.Account
; 
  18 import android
.app
.Notification
; 
  19 import android
.app
.NotificationManager
; 
  20 import android
.app
.PendingIntent
; 
  21 import android
.app
.Service
; 
  22 import android
.content
.ContentValues
; 
  23 import android
.content
.Intent
; 
  24 import android
.net
.Uri
; 
  25 import android
.os
.Binder
; 
  26 import android
.os
.Environment
; 
  27 import android
.os
.Handler
; 
  28 import android
.os
.HandlerThread
; 
  29 import android
.os
.IBinder
; 
  30 import android
.os
.Looper
; 
  31 import android
.os
.Message
; 
  32 import android
.os
.Process
; 
  33 import android
.util
.Log
; 
  34 import android
.widget
.RemoteViews
; 
  35 import com
.owncloud
.android
.R
; 
  36 import eu
.alefzero
.webdav
.WebdavClient
; 
  38 public class FileDownloader 
extends Service 
implements OnDatatransferProgressListener 
{ 
  39     public static final String DOWNLOAD_FINISH_MESSAGE 
= "DOWNLOAD_FINISH"; 
  40     public static final String EXTRA_DOWNLOAD_RESULT 
= "RESULT";     
  41     public static final String EXTRA_ACCOUNT 
= "ACCOUNT"; 
  42     public static final String EXTRA_FILE_PATH 
= "FILE_PATH"; 
  43     public static final String EXTRA_REMOTE_PATH 
= "REMOTE_PATH"; 
  44     public static final String EXTRA_FILE_SIZE 
= "FILE_SIZE"; 
  45     public static final String ACCOUNT_NAME 
= "ACCOUNT_NAME"; 
  47     private static final String TAG 
= "FileDownloader"; 
  49     private Looper mServiceLooper
; 
  50     private ServiceHandler mServiceHandler
; 
  51     private IBinder mBinder
; 
  52     private WebdavClient mDownloadClient 
= null
; 
  53     private Account mLastAccount 
= null
; 
  55     //private AbstractList<Account> mAccounts = new Vector<Account>(); 
  56     private ConcurrentMap
<String
, DownloadFileOperation
> mPendingDownloads 
= new ConcurrentHashMap
<String
, DownloadFileOperation
>(); 
  57     private DownloadFileOperation mCurrentDownload 
= null
; 
  60     private Account mAccount; 
  61     private String mFilePath; 
  62     private String mRemotePath; 
  63     private long mTotalDownloadSize; 
  64     private long mCurrentDownloadSize; 
  67     private NotificationManager mNotificationMngr
; 
  68     private Notification mNotification
; 
  69     private int mLastPercent
; 
  73      * Static map with the files being download and the path to the temporal file were are download 
  75     //private static Set<String> mDownloadsInProgress = Collections.synchronizedSet(new HashSet<String>()); 
  78      * Returns True when the file referred by 'remotePath' in the ownCloud account 'account' is downloading 
  80     /*public static boolean isDownloading(Account account, String remotePath) { 
  81         return (mDownloadsInProgress.contains(buildRemoteName(account.name, remotePath))); 
  85      * Builds a key for mDownloadsInProgress from the accountName and remotePath 
  87     private static String 
buildRemoteName(String accountName
, String remotePath
) { 
  88         return accountName 
+ remotePath
; 
  91     public static final String 
getSavePath(String accountName
) { 
  92         File sdCard 
= Environment
.getExternalStorageDirectory(); 
  93         return sdCard
.getAbsolutePath() + "/owncloud/" + Uri
.encode(accountName
, "@");    
  94             // URL encoding is an 'easy fix' to overcome that NTFS and FAT32 don't allow ":" in file names, that can be in the accountName since 0.1.190B 
  97     public static final String 
getTemporalPath(String accountName
) { 
  98         File sdCard 
= Environment
.getExternalStorageDirectory(); 
  99         return sdCard
.getAbsolutePath() + "/owncloud/tmp/" + Uri
.encode(accountName
, "@"); 
 100             // URL encoding is an 'easy fix' to overcome that NTFS and FAT32 don't allow ":" in file names, that can be in the accountName since 0.1.190B 
 105      * Service initialization 
 108     public void onCreate() { 
 110         mNotificationMngr 
= (NotificationManager
) getSystemService(NOTIFICATION_SERVICE
); 
 111         HandlerThread thread 
= new HandlerThread("FileDownladerThread", 
 112                 Process
.THREAD_PRIORITY_BACKGROUND
); 
 114         mServiceLooper 
= thread
.getLooper(); 
 115         mServiceHandler 
= new ServiceHandler(mServiceLooper
); 
 116         mBinder 
= new FileDownloaderBinder(); 
 121      * Entry point to add one or several files to the queue of downloads. 
 123      * New downloads are added calling to startService(), resulting in a call to this method. This ensures the service will keep on working  
 124      * although the caller activity goes away. 
 127     public int onStartCommand(Intent intent
, int flags
, int startId
) { 
 128         if (    !intent
.hasExtra(EXTRA_ACCOUNT
) || 
 129                 !intent
.hasExtra(EXTRA_FILE_PATH
) || 
 130                 !intent
.hasExtra(EXTRA_REMOTE_PATH
) 
 132             Log
.e(TAG
, "Not enough information provided in intent"); 
 133             return START_NOT_STICKY
; 
 135         Account account 
= intent
.getParcelableExtra(EXTRA_ACCOUNT
); 
 136         String filePath 
= intent
.getStringExtra(EXTRA_FILE_PATH
); 
 137         String remotePath 
= intent
.getStringExtra(EXTRA_REMOTE_PATH
); 
 138         long totalDownloadSize 
= intent
.getLongExtra(EXTRA_FILE_SIZE
, -1); 
 140         AbstractList
<String
> requestedDownloads 
= new Vector
<String
>(); // dvelasco: now this will always contain just one element, but that can change in a near future 
 141         String downloadKey 
= buildRemoteName(account
.name
, remotePath
); 
 143             DownloadFileOperation newDownload 
= new DownloadFileOperation(account
, filePath
, remotePath
, (String
)null
, totalDownloadSize
, false
);  
 144             mPendingDownloads
.putIfAbsent(downloadKey
, newDownload
); 
 145             newDownload
.addDatatransferProgressListener(this); 
 146             requestedDownloads
.add(downloadKey
); 
 148         } catch (IllegalArgumentException e
) { 
 149             Log
.e(TAG
, "Not enough information provided in intent: " + e
.getMessage()); 
 150             return START_NOT_STICKY
; 
 153         if (requestedDownloads
.size() > 0) { 
 154             Message msg 
= mServiceHandler
.obtainMessage(); 
 156             msg
.obj 
= requestedDownloads
; 
 157             mServiceHandler
.sendMessage(msg
); 
 160         return START_NOT_STICKY
; 
 165      * Provides a binder object that clients can use to perform operations on the queue of downloads, excepting the addition of new files.  
 167      * Implemented to perform cancellation, pause and resume of existing downloads. 
 170     public IBinder 
onBind(Intent arg0
) { 
 176      *  Binder to let client components to perform operations on the queue of downloads. 
 178      *  It provides by itself the available operations. 
 180     public class FileDownloaderBinder 
extends Binder 
{ 
 183          * Cancels a pending or current download of a remote file. 
 185          * @param account       Owncloud account where the remote file is stored. 
 186          * @param remotePath    URL to the remote file in the queue of downloads. 
 188         public void cancel(Account account
, String remotePath
) { 
 189             synchronized (mPendingDownloads
) { 
 190                 DownloadFileOperation download 
= mPendingDownloads
.remove(buildRemoteName(account
.name
, remotePath
)); 
 191                 if (download 
!= null
) { 
 199          * Returns True when the file referred by 'remotePath' in the ownCloud account 'account' is downloading 
 201          * @param account       Owncloud account where the remote file is stored. 
 202          * @param remotePath    URL to the remote file in the queue of downloads. 
 204         public boolean isDownloading(Account account
, String remotePath
) { 
 205             synchronized (mPendingDownloads
) { 
 206                 return (mPendingDownloads
.containsKey(buildRemoteName(account
.name
, remotePath
))); 
 212      * Download worker. Performs the pending downloads in the order they were requested.  
 214      * Created with the Looper of a new thread, started in {@link FileUploader#onCreate()}.  
 216     private final class ServiceHandler 
extends Handler 
{ 
 217         public ServiceHandler(Looper looper
) { 
 222         public void handleMessage(Message msg
) { 
 223             @SuppressWarnings("unchecked") 
 224             AbstractList
<String
> requestedDownloads 
= (AbstractList
<String
>) msg
.obj
; 
 225             if (msg
.obj 
!= null
) { 
 226                 Iterator
<String
> it 
= requestedDownloads
.iterator(); 
 227                 while (it
.hasNext()) { 
 228                     downloadFile(it
.next()); 
 238      * Core download method: requests a file to download and stores it. 
 240      * @param downloadKey   Key to access the download to perform, contained in mPendingDownloads  
 242     private void downloadFile(String downloadKey
) { 
 244         synchronized(mPendingDownloads
) { 
 245             mCurrentDownload 
= mPendingDownloads
.get(downloadKey
); 
 248         if (mCurrentDownload 
!= null
) { 
 250             notifyDownloadStart(mCurrentDownload
); 
 252             /// prepare client object to send the request to the ownCloud server 
 253             if (mDownloadClient 
== null 
|| mLastAccount 
!= mCurrentDownload
.getAccount()) { 
 254                 mLastAccount 
= mCurrentDownload
.getAccount(); 
 255                 mDownloadClient 
= OwnCloudClientUtils
.createOwnCloudClient(mLastAccount
, getApplicationContext()); 
 258             /// perform the download 
 259             //mDownloadsInProgress.add(buildRemoteName(mLastAccount.name, mCurrentDownload.getRemotePath())); 
 260             RemoteOperationResult downloadResult 
= null
; 
 261             File newLocalFile 
= null
; 
 263                 downloadResult 
= mCurrentDownload
.execute(mDownloadClient
); 
 264                 if (downloadResult
.isSuccess()) { 
 265                     ContentValues cv 
= new ContentValues(); 
 266                     newLocalFile 
= new File(getSavePath(mCurrentDownload
.getAccount().name
) + mCurrentDownload
.getLocalPath()); 
 267                     cv
.put(ProviderTableMeta
.FILE_STORAGE_PATH
, newLocalFile
.getAbsolutePath()); 
 268                     getContentResolver().update( 
 269                             ProviderTableMeta
.CONTENT_URI
, 
 271                             ProviderTableMeta
.FILE_NAME 
+ "=? AND " 
 272                                     + ProviderTableMeta
.FILE_ACCOUNT_OWNER 
+ "=?", 
 274                                     mCurrentDownload
.getLocalPath().substring(mCurrentDownload
.getLocalPath().lastIndexOf('/') + 1), 
 275                                     mLastAccount
.name 
}); 
 279                 mDownloadsInProgress.remove(buildRemoteName(mLastAccount.name, mCurrentDownload.getRemotePath())); 
 282             mPendingDownloads
.remove(downloadKey
); 
 285             notifyDownloadResult(mCurrentDownload
, downloadResult
); 
 287             sendFinalBroadcast(mCurrentDownload
, downloadResult
, (downloadResult
.isSuccess())? newLocalFile
.getAbsolutePath():null
); 
 293      * Callback method to update the progress bar in the status notification. 
 296     public void onTransferProgress(long progressRate
, long totalTransferredSoFar
, long totalToTransfer
, String fileName
) { 
 297         int percent 
= (int)(100.0*((double)totalTransferredSoFar
)/((double)totalToTransfer
)); 
 298         if (percent 
!= mLastPercent
) { 
 299           mNotification
.contentView
.setProgressBar(R
.id
.status_progress
, 100, percent
, totalToTransfer 
== -1); 
 300           mNotification
.contentView
.setTextViewText(R
.id
.status_text
, String
.format(getString(R
.string
.downloader_download_in_progress_content
), percent
, fileName
)); 
 301           mNotificationMngr
.notify(R
.string
.downloader_download_in_progress_ticker
, mNotification
); 
 303         mLastPercent 
= percent
; 
 308      * Callback method to update the progress bar in the status notification (old version) 
 311     public void onTransferProgress(long progressRate
) { 
 312         // NOTHING TO DO HERE ANYMORE 
 317      * Creates a status notification to show the download progress 
 319      * @param download  Download operation starting. 
 321     private void notifyDownloadStart(DownloadFileOperation download
) { 
 322         /// create status notification to show the download progress 
 324         mNotification 
= new Notification(R
.drawable
.icon
, getString(R
.string
.downloader_download_in_progress_ticker
), System
.currentTimeMillis()); 
 325         mNotification
.flags 
|= Notification
.FLAG_ONGOING_EVENT
; 
 326         mNotification
.contentView 
= new RemoteViews(getApplicationContext().getPackageName(), R
.layout
.progressbar_layout
); 
 327         mNotification
.contentView
.setProgressBar(R
.id
.status_progress
, 100, 0, download
.getSize() == -1); 
 328         mNotification
.contentView
.setTextViewText(R
.id
.status_text
, String
.format(getString(R
.string
.downloader_download_in_progress_content
), 0, new File(download
.getLocalPath()).getName())); 
 329         mNotification
.contentView
.setImageViewResource(R
.id
.status_icon
, R
.drawable
.icon
); 
 330         // TODO put something smart in the contentIntent below 
 331         mNotification
.contentIntent 
= PendingIntent
.getActivity(getApplicationContext(), 0, new Intent(), PendingIntent
.FLAG_UPDATE_CURRENT
); 
 332         mNotificationMngr
.notify(R
.string
.downloader_download_in_progress_ticker
, mNotification
); 
 337      * Updates the status notification with the result of a download operation. 
 339      * @param downloadResult    Result of the download operation. 
 340      * @param download          Finished download operation 
 342     private void notifyDownloadResult(DownloadFileOperation download
, RemoteOperationResult downloadResult
) { 
 343         mNotificationMngr
.cancel(R
.string
.downloader_download_in_progress_ticker
); 
 344         if (!downloadResult
.isCancelled()) { 
 345             int tickerId 
= (downloadResult
.isSuccess()) ? R
.string
.downloader_download_succeeded_ticker 
: R
.string
.downloader_download_failed_ticker
; 
 346             int contentId 
= (downloadResult
.isSuccess()) ? R
.string
.downloader_download_succeeded_content 
: R
.string
.downloader_download_failed_content
; 
 347             Notification finalNotification 
= new Notification(R
.drawable
.icon
, getString(tickerId
), System
.currentTimeMillis()); 
 348             finalNotification
.flags 
|= Notification
.FLAG_AUTO_CANCEL
; 
 349             // TODO put something smart in the contentIntent below 
 350             finalNotification
.contentIntent 
= PendingIntent
.getActivity(getApplicationContext(), 0, new Intent(), PendingIntent
.FLAG_UPDATE_CURRENT
); 
 351             finalNotification
.setLatestEventInfo(getApplicationContext(), getString(tickerId
), String
.format(getString(contentId
), new File(download
.getLocalPath()).getName()), finalNotification
.contentIntent
); 
 352             mNotificationMngr
.notify(tickerId
, finalNotification
); 
 358      * Sends a broadcast in order to the interested activities can update their view 
 360      * @param download          Finished download operation 
 361      * @param downloadResult    Result of the download operation 
 362      * @param newFilePath       Absolute path to the downloaded file 
 364     private void sendFinalBroadcast(DownloadFileOperation download
, RemoteOperationResult downloadResult
, String newFilePath
) { 
 365         Intent end 
= new Intent(DOWNLOAD_FINISH_MESSAGE
); 
 366         end
.putExtra(EXTRA_DOWNLOAD_RESULT
, downloadResult
.isSuccess()); 
 367         end
.putExtra(ACCOUNT_NAME
, download
.getAccount().name
); 
 368         end
.putExtra(EXTRA_REMOTE_PATH
, download
.getRemotePath()); 
 369         if (downloadResult
.isSuccess()) { 
 370             end
.putExtra(EXTRA_FILE_PATH
, newFilePath
);