FileDownloader.java 31 KB

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