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