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