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