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