FileDownloader.java 31 KB

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