New method in IndexesForest: remove(Account account). Use this method for cancelling...
[pub/Android/ownCloud.git] / src / com / owncloud / android / files / services / FileDownloader.java
1 /* ownCloud Android client application
2 * Copyright (C) 2012 Bartek Przybylski
3 * Copyright (C) 2012-2015 ownCloud Inc.
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License version 2,
7 * as published by the Free Software Foundation.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 *
17 */
18
19 package com.owncloud.android.files.services;
20
21 import java.io.File;
22 import java.io.IOException;
23 import java.util.AbstractList;
24 import java.util.HashMap;
25 import java.util.Iterator;
26 import java.util.Map;
27 import java.util.Vector;
28 import java.util.concurrent.ConcurrentMap;
29
30 import com.owncloud.android.R;
31 import com.owncloud.android.authentication.AccountUtils;
32 import com.owncloud.android.authentication.AuthenticatorActivity;
33 import com.owncloud.android.datamodel.FileDataStorageManager;
34 import com.owncloud.android.datamodel.OCFile;
35
36 import com.owncloud.android.lib.common.network.OnDatatransferProgressListener;
37 import com.owncloud.android.lib.common.OwnCloudAccount;
38 import com.owncloud.android.lib.common.OwnCloudClient;
39 import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
40 import com.owncloud.android.notifications.NotificationBuilderWithProgressBar;
41 import com.owncloud.android.notifications.NotificationDelayer;
42 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
43 import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
44 import com.owncloud.android.lib.common.utils.Log_OC;
45 import com.owncloud.android.lib.resources.files.FileUtils;
46 import com.owncloud.android.operations.DownloadFileOperation;
47 import com.owncloud.android.ui.activity.FileActivity;
48 import com.owncloud.android.ui.activity.FileDisplayActivity;
49 import com.owncloud.android.ui.preview.PreviewImageActivity;
50 import com.owncloud.android.ui.preview.PreviewImageFragment;
51 import com.owncloud.android.utils.ErrorMessageAdapter;
52
53 import android.accounts.Account;
54 import android.accounts.AccountsException;
55 import android.app.NotificationManager;
56 import android.app.PendingIntent;
57 import android.app.Service;
58 import android.content.Intent;
59 import android.os.Binder;
60 import android.os.Handler;
61 import android.os.HandlerThread;
62 import android.os.IBinder;
63 import android.os.Looper;
64 import android.os.Message;
65 import android.os.Process;
66 import android.support.v4.app.NotificationCompat;
67 import android.util.Pair;
68
69 public class FileDownloader extends Service implements OnDatatransferProgressListener {
70
71 public static final String EXTRA_ACCOUNT = "ACCOUNT";
72 public static final String EXTRA_FILE = "FILE";
73
74 private static final String DOWNLOAD_ADDED_MESSAGE = "DOWNLOAD_ADDED";
75 private static final String DOWNLOAD_FINISH_MESSAGE = "DOWNLOAD_FINISH";
76 public static final String EXTRA_DOWNLOAD_RESULT = "RESULT";
77 public static final String EXTRA_FILE_PATH = "FILE_PATH";
78 public static final String EXTRA_REMOTE_PATH = "REMOTE_PATH";
79 public static final String EXTRA_LINKED_TO_PATH = "LINKED_TO";
80 public static final String ACCOUNT_NAME = "ACCOUNT_NAME";
81
82 private static final String TAG = "FileDownloader";
83
84 private Looper mServiceLooper;
85 private ServiceHandler mServiceHandler;
86 private IBinder mBinder;
87 private OwnCloudClient mDownloadClient = null;
88 private Account mCurrentAccount = null;
89 private FileDataStorageManager mStorageManager;
90
91 private IndexedForest<DownloadFileOperation> mPendingDownloads = new IndexedForest<DownloadFileOperation>();
92
93 private DownloadFileOperation mCurrentDownload = null;
94
95 private NotificationManager mNotificationManager;
96 private NotificationCompat.Builder mNotificationBuilder;
97 private int mLastPercent;
98
99
100 public static String getDownloadAddedMessage() {
101 return FileDownloader.class.getName() + DOWNLOAD_ADDED_MESSAGE;
102 }
103
104 public static String getDownloadFinishMessage() {
105 return FileDownloader.class.getName() + DOWNLOAD_FINISH_MESSAGE;
106 }
107
108 /**
109 * Service initialization
110 */
111 @Override
112 public void onCreate() {
113 super.onCreate();
114 Log_OC.d(TAG, "Creating service");
115 mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
116 HandlerThread thread = new HandlerThread("FileDownloaderThread", Process.THREAD_PRIORITY_BACKGROUND);
117 thread.start();
118 mServiceLooper = thread.getLooper();
119 mServiceHandler = new ServiceHandler(mServiceLooper, this);
120 mBinder = new FileDownloaderBinder();
121 }
122
123
124 /**
125 * Service clean up
126 */
127 @Override
128 public void onDestroy() {
129 Log_OC.v(TAG, "Destroying service");
130 mBinder = null;
131 mServiceHandler = null;
132 mServiceLooper.quit();
133 mServiceLooper = null;
134 mNotificationManager = null;
135 super.onDestroy();
136 }
137
138
139 /**
140 * Entry point to add one or several files to the queue of downloads.
141 * <p/>
142 * New downloads are added calling to startService(), resulting in a call to this method.
143 * This ensures the service will keep on working although the caller activity goes away.
144 */
145 @Override
146 public int onStartCommand(Intent intent, int flags, int startId) {
147 Log_OC.d(TAG, "Starting command with id " + startId);
148
149 if (!intent.hasExtra(EXTRA_ACCOUNT) ||
150 !intent.hasExtra(EXTRA_FILE)
151 ) {
152 Log_OC.e(TAG, "Not enough information provided in intent");
153 return START_NOT_STICKY;
154 } else {
155 final Account account = intent.getParcelableExtra(EXTRA_ACCOUNT);
156 final OCFile file = intent.getParcelableExtra(EXTRA_FILE);
157
158 /*Log_OC.v(
159 "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
160 "Received request to download file"
161 );*/
162
163 AbstractList<String> requestedDownloads = new Vector<String>();
164 try {
165 DownloadFileOperation newDownload = new DownloadFileOperation(account, file);
166 newDownload.addDatatransferProgressListener(this);
167 newDownload.addDatatransferProgressListener((FileDownloaderBinder) mBinder);
168 Pair<String, String> putResult = mPendingDownloads.putIfAbsent(
169 account, file.getRemotePath(), newDownload
170 );
171 String downloadKey = putResult.first;
172 requestedDownloads.add(downloadKey);
173 /*Log_OC.v(
174 "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
175 "Download on " + file.getRemotePath() + " added to queue"
176 );*/
177
178 // Store file on db with state 'downloading'
179 /*
180 TODO - check if helps with UI responsiveness, letting only folders use FileDownloaderBinder to check
181 FileDataStorageManager storageManager = new FileDataStorageManager(account, getContentResolver());
182 file.setDownloading(true);
183 storageManager.saveFile(file);
184 */
185
186 sendBroadcastNewDownload(newDownload, putResult.second);
187
188 } catch (IllegalArgumentException e) {
189 Log_OC.e(TAG, "Not enough information provided in intent: " + e.getMessage());
190 return START_NOT_STICKY;
191 }
192
193 if (requestedDownloads.size() > 0) {
194 Message msg = mServiceHandler.obtainMessage();
195 msg.arg1 = startId;
196 msg.obj = requestedDownloads;
197 mServiceHandler.sendMessage(msg);
198 }
199 //}
200 }
201
202 return START_NOT_STICKY;
203 }
204
205
206 /**
207 * Provides a binder object that clients can use to perform operations on the queue of downloads,
208 * excepting the addition of new files.
209 * <p/>
210 * Implemented to perform cancellation, pause and resume of existing downloads.
211 */
212 @Override
213 public IBinder onBind(Intent arg0) {
214 return mBinder;
215 }
216
217
218 /**
219 * Called when ALL the bound clients were onbound.
220 */
221 @Override
222 public boolean onUnbind(Intent intent) {
223 ((FileDownloaderBinder) mBinder).clearListeners();
224 return false; // not accepting rebinding (default behaviour)
225 }
226
227
228 /**
229 * Binder to let client components to perform operations on the queue of downloads.
230 * <p/>
231 * It provides by itself the available operations.
232 */
233 public class FileDownloaderBinder extends Binder implements OnDatatransferProgressListener {
234
235 /**
236 * Map of listeners that will be reported about progress of downloads from a {@link FileDownloaderBinder}
237 * instance.
238 */
239 private Map<Long, OnDatatransferProgressListener> mBoundListeners =
240 new HashMap<Long, OnDatatransferProgressListener>();
241
242
243 /**
244 * Cancels a pending or current download of a remote file.
245 *
246 * @param account ownCloud account where the remote file is stored.
247 * @param file A file in the queue of pending downloads
248 */
249 public void cancel(Account account, OCFile file) {
250 /*Log_OC.v(
251 "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
252 "Received request to cancel download of " + file.getRemotePath()
253 );
254 Log_OC.v( "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
255 "Removing download of " + file.getRemotePath());*/
256 Pair<DownloadFileOperation, String> removeResult =
257 mPendingDownloads.remove(account, file.getRemotePath());
258 DownloadFileOperation download = removeResult.first;
259 if (download != null) {
260 /*Log_OC.v( "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
261 "Canceling returned download of " + file.getRemotePath());*/
262 download.cancel();
263 } else {
264 if (mCurrentDownload != null && mCurrentAccount != null &&
265 mCurrentDownload.getRemotePath().startsWith(file.getRemotePath()) &&
266 account.name.equals(mCurrentAccount.name)) {
267 /*Log_OC.v( "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
268 "Canceling current sync as descendant: " + mCurrentDownload.getRemotePath());*/
269 mCurrentDownload.cancel();
270 }
271 }
272 }
273
274 /**
275 * Cancels a pending or current upload for an account
276 *
277 * @param account Owncloud accountName where the remote file will be stored.
278 */
279 public void cancel(Account account) {
280 Log_OC.d(TAG, "Account= " + account.name);
281
282 if (mCurrentDownload != null) {
283 Log_OC.d(TAG, "Current Download Account= " + mCurrentDownload.getAccount().name);
284 if (mCurrentDownload.getAccount().name.equals(account.name)) {
285 mCurrentDownload.cancel();
286 }
287 }
288 // Cancel pending downloads
289 cancelDownloadsForAccount(account);
290 }
291
292 public void clearListeners() {
293 mBoundListeners.clear();
294 }
295
296
297 /**
298 * Returns True when the file described by 'file' in the ownCloud account 'account' is downloading or
299 * waiting to download.
300 * <p/>
301 * If 'file' is a directory, returns 'true' if any of its descendant files is downloading or
302 * waiting to download.
303 *
304 * @param account ownCloud account where the remote file is stored.
305 * @param file A file that could be in the queue of downloads.
306 */
307 public boolean isDownloading(Account account, OCFile file) {
308 if (account == null || file == null) return false;
309 return (mPendingDownloads.contains(account, file.getRemotePath()));
310 }
311
312
313 /**
314 * Adds a listener interested in the progress of the download for a concrete file.
315 *
316 * @param listener Object to notify about progress of transfer.
317 * @param account ownCloud account holding the file of interest.
318 * @param file {@link OCFile} of interest for listener.
319 */
320 public void addDatatransferProgressListener(
321 OnDatatransferProgressListener listener, Account account, OCFile file
322 ) {
323 if (account == null || file == null || listener == null) return;
324 //String targetKey = buildKey(account, file.getRemotePath());
325 mBoundListeners.put(file.getFileId(), listener);
326 }
327
328
329 /**
330 * Removes a listener interested in the progress of the download for a concrete file.
331 *
332 * @param listener Object to notify about progress of transfer.
333 * @param account ownCloud account holding the file of interest.
334 * @param file {@link OCFile} of interest for listener.
335 */
336 public void removeDatatransferProgressListener(
337 OnDatatransferProgressListener listener, Account account, OCFile file
338 ) {
339 if (account == null || file == null || listener == null) return;
340 //String targetKey = buildKey(account, file.getRemotePath());
341 Long fileId = file.getFileId();
342 if (mBoundListeners.get(fileId) == listener) {
343 mBoundListeners.remove(fileId);
344 }
345 }
346
347 @Override
348 public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer,
349 String fileName) {
350 //String key = buildKey(mCurrentDownload.getAccount(), mCurrentDownload.getFile().getRemotePath());
351 OnDatatransferProgressListener boundListener = mBoundListeners.get(mCurrentDownload.getFile().getFileId());
352 if (boundListener != null) {
353 boundListener.onTransferProgress(progressRate, totalTransferredSoFar, totalToTransfer, fileName);
354 }
355 }
356
357 /**
358 * Review downloads and cancel it if its account doesn't exist
359 */
360 public void checkAccountOfCurrentDownload() {
361 if (mCurrentDownload != null &&
362 !AccountUtils.exists(mCurrentDownload.getAccount(), getApplicationContext())) {
363 mCurrentDownload.cancel();
364 }
365 // The rest of downloads are cancelled when they try to start
366 }
367
368 }
369
370
371 /**
372 * Download worker. Performs the pending downloads in the order they were requested.
373 * <p/>
374 * Created with the Looper of a new thread, started in {@link FileUploader#onCreate()}.
375 */
376 private static class ServiceHandler extends Handler {
377 // don't make it a final class, and don't remove the static ; lint will warn about a possible memory leak
378 FileDownloader mService;
379
380 public ServiceHandler(Looper looper, FileDownloader service) {
381 super(looper);
382 if (service == null)
383 throw new IllegalArgumentException("Received invalid NULL in parameter 'service'");
384 mService = service;
385 }
386
387 @Override
388 public void handleMessage(Message msg) {
389 @SuppressWarnings("unchecked")
390 AbstractList<String> requestedDownloads = (AbstractList<String>) msg.obj;
391 if (msg.obj != null) {
392 Iterator<String> it = requestedDownloads.iterator();
393 while (it.hasNext()) {
394 String next = it.next();
395 mService.downloadFile(next);
396 }
397 }
398 Log_OC.d(TAG, "Stopping after command with id " + msg.arg1);
399 mService.stopSelf(msg.arg1);
400 }
401 }
402
403
404 /**
405 * Core download method: requests a file to download and stores it.
406 *
407 * @param downloadKey Key to access the download to perform, contained in mPendingDownloads
408 */
409 private void downloadFile(String downloadKey) {
410
411 /*Log_OC.v( "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
412 "Getting download of " + downloadKey);*/
413 mCurrentDownload = mPendingDownloads.get(downloadKey);
414
415 if (mCurrentDownload != null) {
416 // Detect if the account exists
417 if (AccountUtils.exists(mCurrentDownload.getAccount(), getApplicationContext())) {
418 Log_OC.d(TAG, "Account " + mCurrentDownload.getAccount().name + " exists");
419 notifyDownloadStart(mCurrentDownload);
420
421 RemoteOperationResult downloadResult = null;
422 try {
423 /// prepare client object to send the request to the ownCloud server
424 if (mCurrentAccount == null || !mCurrentAccount.equals(mCurrentDownload.getAccount())) {
425 mCurrentAccount = mCurrentDownload.getAccount();
426 mStorageManager = new FileDataStorageManager(
427 mCurrentAccount,
428 getContentResolver()
429 );
430 } // else, reuse storage manager from previous operation
431
432 // always get client from client manager, to get fresh credentials in case of update
433 OwnCloudAccount ocAccount = new OwnCloudAccount(mCurrentAccount, this);
434 mDownloadClient = OwnCloudClientManagerFactory.getDefaultSingleton().
435 getClientFor(ocAccount, this);
436
437
438 /// perform the download
439 /*Log_OC.v( "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
440 "Executing download of " + mCurrentDownload.getRemotePath());*/
441 downloadResult = mCurrentDownload.execute(mDownloadClient);
442 if (downloadResult.isSuccess()) {
443 saveDownloadedFile();
444 }
445
446 } catch (AccountsException e) {
447 Log_OC.e(TAG, "Error while trying to get authorization for " + mCurrentAccount.name, e);
448 downloadResult = new RemoteOperationResult(e);
449 } catch (IOException e) {
450 Log_OC.e(TAG, "Error while trying to get authorization for " + mCurrentAccount.name, e);
451 downloadResult = new RemoteOperationResult(e);
452
453 } finally {
454 /*Log_OC.v( "NOW " + TAG + ", thread " + Thread.currentThread().getName(),
455 "Removing payload " + mCurrentDownload.getRemotePath());*/
456
457 Pair<DownloadFileOperation, String> removeResult =
458 mPendingDownloads.removePayload(mCurrentAccount, mCurrentDownload.getRemotePath());
459
460 /// notify result
461 notifyDownloadResult(mCurrentDownload, downloadResult);
462
463 sendBroadcastDownloadFinished(mCurrentDownload, downloadResult, removeResult.second);
464 }
465 } else {
466 // Cancel the transfer
467 Log_OC.d(TAG, "Account " + mCurrentDownload.getAccount().toString() + " doesn't exist");
468 cancelDownloadsForAccount(mCurrentDownload.getAccount());
469
470 }
471 }
472 }
473
474
475 /**
476 * Updates the OC File after a successful download.
477 */
478 private void saveDownloadedFile() {
479 OCFile file = mStorageManager.getFileById(mCurrentDownload.getFile().getFileId());
480 long syncDate = System.currentTimeMillis();
481 file.setLastSyncDateForProperties(syncDate);
482 file.setLastSyncDateForData(syncDate);
483 file.setNeedsUpdateThumbnail(true);
484 file.setModificationTimestamp(mCurrentDownload.getModificationTimestamp());
485 file.setModificationTimestampAtLastSyncForData(mCurrentDownload.getModificationTimestamp());
486 // file.setEtag(mCurrentDownload.getEtag()); // TODO Etag, where available
487 file.setMimetype(mCurrentDownload.getMimeType());
488 file.setStoragePath(mCurrentDownload.getSavePath());
489 file.setFileLength((new File(mCurrentDownload.getSavePath()).length()));
490 file.setRemoteId(mCurrentDownload.getFile().getRemoteId());
491 mStorageManager.saveFile(file);
492 mStorageManager.triggerMediaScan(file.getStoragePath());
493 }
494
495 /**
496 * Update the OC File after a unsuccessful download
497 */
498 private void updateUnsuccessfulDownloadedFile() {
499 OCFile file = mStorageManager.getFileById(mCurrentDownload.getFile().getFileId());
500 file.setDownloading(false);
501 mStorageManager.saveFile(file);
502 }
503
504
505 /**
506 * Creates a status notification to show the download progress
507 *
508 * @param download Download operation starting.
509 */
510 private void notifyDownloadStart(DownloadFileOperation download) {
511 /// create status notification with a progress bar
512 mLastPercent = 0;
513 mNotificationBuilder =
514 NotificationBuilderWithProgressBar.newNotificationBuilderWithProgressBar(this);
515 mNotificationBuilder
516 .setSmallIcon(R.drawable.notification_icon)
517 .setTicker(getString(R.string.downloader_download_in_progress_ticker))
518 .setContentTitle(getString(R.string.downloader_download_in_progress_ticker))
519 .setOngoing(true)
520 .setProgress(100, 0, download.getSize() < 0)
521 .setContentText(
522 String.format(getString(R.string.downloader_download_in_progress_content), 0,
523 new File(download.getSavePath()).getName())
524 );
525
526 /// includes a pending intent in the notification showing the details view of the file
527 Intent showDetailsIntent = null;
528 if (PreviewImageFragment.canBePreviewed(download.getFile())) {
529 showDetailsIntent = new Intent(this, PreviewImageActivity.class);
530 } else {
531 showDetailsIntent = new Intent(this, FileDisplayActivity.class);
532 }
533 showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, download.getFile());
534 showDetailsIntent.putExtra(FileActivity.EXTRA_ACCOUNT, download.getAccount());
535 showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
536
537 mNotificationBuilder.setContentIntent(PendingIntent.getActivity(
538 this, (int) System.currentTimeMillis(), showDetailsIntent, 0
539 ));
540
541 mNotificationManager.notify(R.string.downloader_download_in_progress_ticker, mNotificationBuilder.build());
542 }
543
544
545 /**
546 * Callback method to update the progress bar in the status notification.
547 */
548 @Override
549 public void onTransferProgress(long progressRate, long totalTransferredSoFar, long totalToTransfer, String filePath) {
550 int percent = (int) (100.0 * ((double) totalTransferredSoFar) / ((double) totalToTransfer));
551 if (percent != mLastPercent) {
552 mNotificationBuilder.setProgress(100, percent, totalToTransfer < 0);
553 String fileName = filePath.substring(filePath.lastIndexOf(FileUtils.PATH_SEPARATOR) + 1);
554 String text = String.format(getString(R.string.downloader_download_in_progress_content), percent, fileName);
555 mNotificationBuilder.setContentText(text);
556 mNotificationManager.notify(R.string.downloader_download_in_progress_ticker, mNotificationBuilder.build());
557 }
558 mLastPercent = percent;
559 }
560
561
562 /**
563 * Updates the status notification with the result of a download operation.
564 *
565 * @param downloadResult Result of the download operation.
566 * @param download Finished download operation
567 */
568 private void notifyDownloadResult(DownloadFileOperation download, RemoteOperationResult downloadResult) {
569 mNotificationManager.cancel(R.string.downloader_download_in_progress_ticker);
570 if (!downloadResult.isCancelled()) {
571 int tickerId = (downloadResult.isSuccess()) ? R.string.downloader_download_succeeded_ticker :
572 R.string.downloader_download_failed_ticker;
573
574 boolean needsToUpdateCredentials = (
575 downloadResult.getCode() == ResultCode.UNAUTHORIZED ||
576 downloadResult.isIdPRedirection()
577 );
578 tickerId = (needsToUpdateCredentials) ?
579 R.string.downloader_download_failed_credentials_error : tickerId;
580
581 mNotificationBuilder
582 .setTicker(getString(tickerId))
583 .setContentTitle(getString(tickerId))
584 .setAutoCancel(true)
585 .setOngoing(false)
586 .setProgress(0, 0, false);
587
588 if (needsToUpdateCredentials) {
589
590 // let the user update credentials with one click
591 Intent updateAccountCredentials = new Intent(this, AuthenticatorActivity.class);
592 updateAccountCredentials.putExtra(AuthenticatorActivity.EXTRA_ACCOUNT, download.getAccount());
593 updateAccountCredentials.putExtra(
594 AuthenticatorActivity.EXTRA_ACTION, AuthenticatorActivity.ACTION_UPDATE_EXPIRED_TOKEN
595 );
596 updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
597 updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
598 updateAccountCredentials.addFlags(Intent.FLAG_FROM_BACKGROUND);
599 mNotificationBuilder
600 .setContentIntent(PendingIntent.getActivity(
601 this, (int) System.currentTimeMillis(), updateAccountCredentials, PendingIntent.FLAG_ONE_SHOT));
602
603 } else {
604 // TODO put something smart in showDetailsIntent
605 Intent showDetailsIntent = new Intent();
606 mNotificationBuilder
607 .setContentIntent(PendingIntent.getActivity(
608 this, (int) System.currentTimeMillis(), showDetailsIntent, 0));
609 }
610
611 mNotificationBuilder.setContentText(
612 ErrorMessageAdapter.getErrorCauseMessage(downloadResult, download, getResources())
613 );
614 mNotificationManager.notify(tickerId, mNotificationBuilder.build());
615
616 // Remove success notification
617 if (downloadResult.isSuccess()) {
618 // Sleep 2 seconds, so show the notification before remove it
619 NotificationDelayer.cancelWithDelay(
620 mNotificationManager,
621 R.string.downloader_download_succeeded_ticker,
622 2000);
623 }
624
625 }
626 }
627
628
629 /**
630 * Sends a broadcast when a download finishes in order to the interested activities can update their view
631 *
632 * @param download Finished download operation
633 * @param downloadResult Result of the download operation
634 * @param unlinkedFromRemotePath Path in the downloads tree where the download was unlinked from
635 */
636 private void sendBroadcastDownloadFinished(
637 DownloadFileOperation download,
638 RemoteOperationResult downloadResult,
639 String unlinkedFromRemotePath) {
640 Intent end = new Intent(getDownloadFinishMessage());
641 end.putExtra(EXTRA_DOWNLOAD_RESULT, downloadResult.isSuccess());
642 end.putExtra(ACCOUNT_NAME, download.getAccount().name);
643 end.putExtra(EXTRA_REMOTE_PATH, download.getRemotePath());
644 end.putExtra(EXTRA_FILE_PATH, download.getSavePath());
645 if (unlinkedFromRemotePath != null) {
646 end.putExtra(EXTRA_LINKED_TO_PATH, unlinkedFromRemotePath);
647 }
648 sendStickyBroadcast(end);
649 }
650
651
652 /**
653 * Sends a broadcast when a new download is added to the queue.
654 *
655 * @param download Added download operation
656 * @param linkedToRemotePath Path in the downloads tree where the download was linked to
657 */
658 private void sendBroadcastNewDownload(DownloadFileOperation download, String linkedToRemotePath) {
659 Intent added = new Intent(getDownloadAddedMessage());
660 added.putExtra(ACCOUNT_NAME, download.getAccount().name);
661 added.putExtra(EXTRA_REMOTE_PATH, download.getRemotePath());
662 added.putExtra(EXTRA_FILE_PATH, download.getSavePath());
663 added.putExtra(EXTRA_LINKED_TO_PATH, linkedToRemotePath);
664 sendStickyBroadcast(added);
665 }
666
667 /**
668 * Remove downloads of an account
669 *
670 * @param account
671 */
672 private void cancelDownloadsForAccount(Account account) {
673 // Cancel pending downloads
674 mPendingDownloads.remove(account);
675 }
676 }