FileDownloader.java 30 KB

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