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