Removed conflict badge when conflict is solved
[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 (Exception e) {
442 Log_OC.e(TAG, "Error downloading", e);
443 downloadResult = new RemoteOperationResult(e);
444
445 } finally {
446 Pair<DownloadFileOperation, String> removeResult =
447 mPendingDownloads.removePayload(mCurrentAccount,
448 mCurrentDownload.getRemotePath());
449
450 /// notify result
451 notifyDownloadResult(mCurrentDownload, downloadResult);
452
453 sendBroadcastDownloadFinished(mCurrentDownload, downloadResult, removeResult.second);
454 }
455
456 } else {
457 // Cancel the transfer
458 Log_OC.d(TAG, "Account " + mCurrentDownload.getAccount().toString() +
459 " doesn't exist");
460 cancelDownloadsForAccount(mCurrentDownload.getAccount());
461
462 }
463 }
464 }
465
466
467 /**
468 * Updates the OC File after a successful download.
469 *
470 * TODO move to DownloadFileOperation
471 */
472 private void saveDownloadedFile() {
473 OCFile file = mStorageManager.getFileById(mCurrentDownload.getFile().getFileId());
474 long syncDate = System.currentTimeMillis();
475 file.setLastSyncDateForProperties(syncDate);
476 file.setLastSyncDateForData(syncDate);
477 file.setNeedsUpdateThumbnail(true);
478 file.setModificationTimestamp(mCurrentDownload.getModificationTimestamp());
479 file.setModificationTimestampAtLastSyncForData(mCurrentDownload.getModificationTimestamp());
480 file.setEtag(mCurrentDownload.getEtag());
481 file.setMimetype(mCurrentDownload.getMimeType());
482 file.setStoragePath(mCurrentDownload.getSavePath());
483 file.setFileLength((new File(mCurrentDownload.getSavePath()).length()));
484 file.setRemoteId(mCurrentDownload.getFile().getRemoteId());
485 mStorageManager.saveFile(file);
486 mStorageManager.triggerMediaScan(file.getStoragePath());
487 mStorageManager.saveConflict(file, false);
488 }
489
490 /**
491 * Update the OC File after a unsuccessful download
492 */
493 private void updateUnsuccessfulDownloadedFile() {
494 OCFile file = mStorageManager.getFileById(mCurrentDownload.getFile().getFileId());
495 file.setDownloading(false);
496 mStorageManager.saveFile(file);
497 }
498
499
500 /**
501 * Creates a status notification to show the download progress
502 *
503 * @param download Download operation starting.
504 */
505 private void notifyDownloadStart(DownloadFileOperation download) {
506 /// create status notification with a progress bar
507 mLastPercent = 0;
508 mNotificationBuilder =
509 NotificationBuilderWithProgressBar.newNotificationBuilderWithProgressBar(this);
510 mNotificationBuilder
511 .setSmallIcon(R.drawable.notification_icon)
512 .setTicker(getString(R.string.downloader_download_in_progress_ticker))
513 .setContentTitle(getString(R.string.downloader_download_in_progress_ticker))
514 .setOngoing(true)
515 .setProgress(100, 0, download.getSize() < 0)
516 .setContentText(
517 String.format(getString(R.string.downloader_download_in_progress_content), 0,
518 new File(download.getSavePath()).getName())
519 );
520
521 /// includes a pending intent in the notification showing the details view of the file
522 Intent showDetailsIntent = null;
523 if (PreviewImageFragment.canBePreviewed(download.getFile())) {
524 showDetailsIntent = new Intent(this, PreviewImageActivity.class);
525 } else {
526 showDetailsIntent = new Intent(this, FileDisplayActivity.class);
527 }
528 showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, download.getFile());
529 showDetailsIntent.putExtra(FileActivity.EXTRA_ACCOUNT, download.getAccount());
530 showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
531
532 mNotificationBuilder.setContentIntent(PendingIntent.getActivity(
533 this, (int) System.currentTimeMillis(), showDetailsIntent, 0
534 ));
535
536 mNotificationManager.notify(R.string.downloader_download_in_progress_ticker, mNotificationBuilder.build());
537 }
538
539
540 /**
541 * Callback method to update the progress bar in the status notification.
542 */
543 @Override
544 public void onTransferProgress(long progressRate, long totalTransferredSoFar,
545 long totalToTransfer, String filePath) {
546 int percent = (int) (100.0 * ((double) totalTransferredSoFar) / ((double) totalToTransfer));
547 if (percent != mLastPercent) {
548 mNotificationBuilder.setProgress(100, percent, totalToTransfer < 0);
549 String fileName = filePath.substring(filePath.lastIndexOf(FileUtils.PATH_SEPARATOR) + 1);
550 String text = String.format(getString(R.string.downloader_download_in_progress_content), percent, fileName);
551 mNotificationBuilder.setContentText(text);
552 mNotificationManager.notify(R.string.downloader_download_in_progress_ticker, mNotificationBuilder.build());
553 }
554 mLastPercent = percent;
555 }
556
557
558 /**
559 * Updates the status notification with the result of a download operation.
560 *
561 * @param downloadResult Result of the download operation.
562 * @param download Finished download operation
563 */
564 private void notifyDownloadResult(DownloadFileOperation download,
565 RemoteOperationResult downloadResult) {
566 mNotificationManager.cancel(R.string.downloader_download_in_progress_ticker);
567 if (!downloadResult.isCancelled()) {
568 int tickerId = (downloadResult.isSuccess()) ? R.string.downloader_download_succeeded_ticker :
569 R.string.downloader_download_failed_ticker;
570
571 boolean needsToUpdateCredentials = (
572 downloadResult.getCode() == ResultCode.UNAUTHORIZED ||
573 downloadResult.isIdPRedirection()
574 );
575 tickerId = (needsToUpdateCredentials) ?
576 R.string.downloader_download_failed_credentials_error : tickerId;
577
578 mNotificationBuilder
579 .setTicker(getString(tickerId))
580 .setContentTitle(getString(tickerId))
581 .setAutoCancel(true)
582 .setOngoing(false)
583 .setProgress(0, 0, false);
584
585 if (needsToUpdateCredentials) {
586
587 // let the user update credentials with one click
588 Intent updateAccountCredentials = new Intent(this, AuthenticatorActivity.class);
589 updateAccountCredentials.putExtra(AuthenticatorActivity.EXTRA_ACCOUNT,
590 download.getAccount());
591 updateAccountCredentials.putExtra(
592 AuthenticatorActivity.EXTRA_ACTION,
593 AuthenticatorActivity.ACTION_UPDATE_EXPIRED_TOKEN
594 );
595 updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
596 updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
597 updateAccountCredentials.addFlags(Intent.FLAG_FROM_BACKGROUND);
598 mNotificationBuilder
599 .setContentIntent(PendingIntent.getActivity(
600 this, (int) System.currentTimeMillis(), updateAccountCredentials,
601 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,
613 getResources())
614 );
615 mNotificationManager.notify(tickerId, mNotificationBuilder.build());
616
617 // Remove success notification
618 if (downloadResult.isSuccess()) {
619 // Sleep 2 seconds, so show the notification before remove it
620 NotificationDelayer.cancelWithDelay(
621 mNotificationManager,
622 R.string.downloader_download_succeeded_ticker,
623 2000);
624 }
625
626 }
627 }
628
629
630 /**
631 * Sends a broadcast when a download finishes in order to the interested activities can
632 * update their view
633 *
634 * @param download Finished download operation
635 * @param downloadResult Result of the download operation
636 * @param unlinkedFromRemotePath Path in the downloads tree where the download was unlinked from
637 */
638 private void sendBroadcastDownloadFinished(
639 DownloadFileOperation download,
640 RemoteOperationResult downloadResult,
641 String unlinkedFromRemotePath) {
642
643 Intent end = new Intent(getDownloadFinishMessage());
644 end.putExtra(EXTRA_DOWNLOAD_RESULT, downloadResult.isSuccess());
645 end.putExtra(ACCOUNT_NAME, download.getAccount().name);
646 end.putExtra(EXTRA_REMOTE_PATH, download.getRemotePath());
647 end.putExtra(EXTRA_FILE_PATH, download.getSavePath());
648 if (unlinkedFromRemotePath != null) {
649 end.putExtra(EXTRA_LINKED_TO_PATH, unlinkedFromRemotePath);
650 }
651 sendStickyBroadcast(end);
652 }
653
654
655 /**
656 * Sends a broadcast when a new download is added to the queue.
657 *
658 * @param download Added download operation
659 * @param linkedToRemotePath Path in the downloads tree where the download was linked to
660 */
661 private void sendBroadcastNewDownload(DownloadFileOperation download,
662 String linkedToRemotePath) {
663 Intent added = new Intent(getDownloadAddedMessage());
664 added.putExtra(ACCOUNT_NAME, download.getAccount().name);
665 added.putExtra(EXTRA_REMOTE_PATH, download.getRemotePath());
666 added.putExtra(EXTRA_FILE_PATH, download.getSavePath());
667 added.putExtra(EXTRA_LINKED_TO_PATH, linkedToRemotePath);
668 sendStickyBroadcast(added);
669 }
670
671 /**
672 * Remove downloads of an account
673 *
674 * @param account Downloads account to remove
675 */
676 private void cancelDownloadsForAccount(Account account) {
677 // Cancel pending downloads
678 mPendingDownloads.remove(account);
679 }
680 }