diff --git a/app/src/main/java/com/owncloud/android/files/services/FileDownloader.java b/app/src/main/java/com/owncloud/android/files/services/FileDownloader.java
new file mode 100644
index 0000000000..cbe4d37aea
--- /dev/null
+++ b/app/src/main/java/com/owncloud/android/files/services/FileDownloader.java
@@ -0,0 +1,741 @@
+/*
+ * ownCloud Android client application
+ *
+ * Copyright (C) 2012 Bartek Przybylski
+ * Copyright (C) 2012-2016 ownCloud Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2,
+ * as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+
+package com.owncloud.android.files.services;
+
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.accounts.OnAccountsUpdateListener;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.Intent;
+import android.graphics.BitmapFactory;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.util.Pair;
+
+import com.nextcloud.client.account.User;
+import com.nextcloud.client.account.UserAccountManager;
+import com.nextcloud.client.files.downloader.DownloadTask;
+import com.nextcloud.java.util.Optional;
+import com.owncloud.android.R;
+import com.owncloud.android.authentication.AuthenticatorActivity;
+import com.owncloud.android.datamodel.FileDataStorageManager;
+import com.owncloud.android.datamodel.OCFile;
+import com.owncloud.android.datamodel.UploadsStorageManager;
+import com.owncloud.android.lib.common.OwnCloudAccount;
+import com.owncloud.android.lib.common.OwnCloudClient;
+import com.owncloud.android.lib.common.OwnCloudClientManagerFactory;
+import com.owncloud.android.lib.common.network.OnDatatransferProgressListener;
+import com.owncloud.android.lib.common.operations.RemoteOperationResult;
+import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode;
+import com.owncloud.android.lib.common.utils.Log_OC;
+import com.owncloud.android.lib.resources.files.FileUtils;
+import com.owncloud.android.operations.DownloadFileOperation;
+import com.owncloud.android.operations.DownloadType;
+import com.owncloud.android.providers.DocumentsStorageProvider;
+import com.owncloud.android.ui.activity.ConflictsResolveActivity;
+import com.owncloud.android.ui.activity.FileActivity;
+import com.owncloud.android.ui.activity.FileDisplayActivity;
+import com.owncloud.android.ui.dialog.SendShareDialog;
+import com.owncloud.android.ui.fragment.OCFileListFragment;
+import com.owncloud.android.ui.notifications.NotificationUtils;
+import com.owncloud.android.ui.preview.PreviewImageActivity;
+import com.owncloud.android.ui.preview.PreviewImageFragment;
+import com.owncloud.android.utils.ErrorMessageAdapter;
+import com.owncloud.android.utils.MimeTypeUtil;
+import com.owncloud.android.utils.theme.ViewThemeUtils;
+
+import java.io.File;
+import java.security.SecureRandom;
+import java.util.AbstractList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Vector;
+
+import javax.inject.Inject;
+
+import androidx.core.app.NotificationCompat;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+import dagger.android.AndroidInjection;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
+public class FileDownloader extends Service
+ implements OnDatatransferProgressListener, OnAccountsUpdateListener {
+
+ public static final String EXTRA_USER = "USER";
+ public static final String EXTRA_FILE = "FILE";
+
+ private static final String DOWNLOAD_ADDED_MESSAGE = "DOWNLOAD_ADDED";
+ private static final String DOWNLOAD_FINISH_MESSAGE = "DOWNLOAD_FINISH";
+ public static final String EXTRA_DOWNLOAD_RESULT = "RESULT";
+ public static final String EXTRA_REMOTE_PATH = "REMOTE_PATH";
+ public static final String EXTRA_LINKED_TO_PATH = "LINKED_TO";
+ public static final String ACCOUNT_NAME = "ACCOUNT_NAME";
+ public static final String DOWNLOAD_TYPE = "DOWNLOAD_TYPE";
+
+ private static final int FOREGROUND_SERVICE_ID = 412;
+
+ private static final String TAG = FileDownloader.class.getSimpleName();
+
+ private Looper mServiceLooper;
+ private ServiceHandler mServiceHandler;
+ private IBinder mBinder;
+ private OwnCloudClient mDownloadClient;
+ private Optional currentUser = Optional.empty();
+ private FileDataStorageManager mStorageManager;
+
+ private IndexedForest mPendingDownloads = new IndexedForest<>();
+
+ private DownloadFileOperation mCurrentDownload;
+
+ private NotificationManager mNotificationManager;
+ private NotificationCompat.Builder mNotificationBuilder;
+ private int mLastPercent;
+
+ private Notification mNotification;
+
+ private long conflictUploadId;
+
+ public boolean mStartedDownload = false;
+
+ @Inject UserAccountManager accountManager;
+ @Inject UploadsStorageManager uploadsStorageManager;
+ @Inject LocalBroadcastManager localBroadcastManager;
+ @Inject ViewThemeUtils viewThemeUtils;
+
+ public static String getDownloadAddedMessage() {
+ return FileDownloader.class.getName() + DOWNLOAD_ADDED_MESSAGE;
+ }
+
+ public static String getDownloadFinishMessage() {
+ return FileDownloader.class.getName() + DOWNLOAD_FINISH_MESSAGE;
+ }
+
+ /**
+ * Service initialization
+ */
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ AndroidInjection.inject(this);
+ Log_OC.d(TAG, "Creating service");
+ mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+ HandlerThread thread = new HandlerThread("FileDownloaderThread", Process.THREAD_PRIORITY_BACKGROUND);
+ thread.start();
+ mServiceLooper = thread.getLooper();
+ mServiceHandler = new ServiceHandler(mServiceLooper, this);
+ mBinder = new FileDownloaderBinder();
+
+ NotificationCompat.Builder builder = NotificationUtils.newNotificationBuilder(this, viewThemeUtils).setContentTitle(
+ getApplicationContext().getResources().getString(R.string.app_name))
+ .setContentText(getApplicationContext().getResources().getString(R.string.foreground_service_download))
+ .setSmallIcon(R.drawable.notification_icon)
+ .setLargeIcon(BitmapFactory.decodeResource(getResources(), R.drawable.notification_icon));
+
+
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
+ builder.setChannelId(NotificationUtils.NOTIFICATION_CHANNEL_DOWNLOAD);
+ }
+
+ mNotification = builder.build();
+
+ // add AccountsUpdatedListener
+ AccountManager am = AccountManager.get(getApplicationContext());
+ am.addOnAccountsUpdatedListener(this, null, false);
+ }
+
+
+ /**
+ * Service clean up
+ */
+ @Override
+ public void onDestroy() {
+ Log_OC.v(TAG, "Destroying service");
+ mBinder = null;
+ mServiceHandler = null;
+ mServiceLooper.quit();
+ mServiceLooper = null;
+ mNotificationManager = null;
+
+ // remove AccountsUpdatedListener
+ AccountManager am = AccountManager.get(getApplicationContext());
+ am.removeOnAccountsUpdatedListener(this);
+ super.onDestroy();
+ }
+
+
+ /**
+ * Entry point to add one or several files to the queue of downloads.
+ *
+ * New downloads are added calling to startService(), resulting in a call to this method.
+ * This ensures the service will keep on working although the caller activity goes away.
+ */
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ Log_OC.d(TAG, "Starting command with id " + startId);
+
+ startForeground(FOREGROUND_SERVICE_ID, mNotification);
+
+ if (intent == null || !intent.hasExtra(EXTRA_USER) || !intent.hasExtra(EXTRA_FILE)) {
+ Log_OC.e(TAG, "Not enough information provided in intent");
+ return START_NOT_STICKY;
+ } else {
+ final User user = intent.getParcelableExtra(EXTRA_USER);
+ final OCFile file = intent.getParcelableExtra(EXTRA_FILE);
+ final String behaviour = intent.getStringExtra(OCFileListFragment.DOWNLOAD_BEHAVIOUR);
+
+ DownloadType downloadType = DownloadType.DOWNLOAD;
+ if (intent.hasExtra(DOWNLOAD_TYPE)) {
+ downloadType = (DownloadType) intent.getSerializableExtra(DOWNLOAD_TYPE);
+ }
+ String activityName = intent.getStringExtra(SendShareDialog.ACTIVITY_NAME);
+ String packageName = intent.getStringExtra(SendShareDialog.PACKAGE_NAME);
+ conflictUploadId = intent.getLongExtra(ConflictsResolveActivity.EXTRA_CONFLICT_UPLOAD_ID, -1);
+ AbstractList requestedDownloads = new Vector();
+ try {
+ DownloadFileOperation newDownload = new DownloadFileOperation(user,
+ file,
+ behaviour,
+ activityName,
+ packageName,
+ getBaseContext(),
+ downloadType);
+ newDownload.addDatatransferProgressListener(this);
+ newDownload.addDatatransferProgressListener((FileDownloaderBinder) mBinder);
+ Pair putResult = mPendingDownloads.putIfAbsent(user.getAccountName(),
+ file.getRemotePath(),
+ newDownload);
+ if (putResult != null) {
+ String downloadKey = putResult.first;
+ requestedDownloads.add(downloadKey);
+ sendBroadcastNewDownload(newDownload, putResult.second);
+ } // else, file already in the queue of downloads; don't repeat the request
+
+ } catch (IllegalArgumentException e) {
+ Log_OC.e(TAG, "Not enough information provided in intent: " + e.getMessage());
+ return START_NOT_STICKY;
+ }
+
+ if (requestedDownloads.size() > 0) {
+ Message msg = mServiceHandler.obtainMessage();
+ msg.arg1 = startId;
+ msg.obj = requestedDownloads;
+ mServiceHandler.sendMessage(msg);
+ }
+ }
+
+ return START_NOT_STICKY;
+ }
+
+ /**
+ * Provides a binder object that clients can use to perform operations on the queue of downloads,
+ * excepting the addition of new files.
+ *
+ * Implemented to perform cancellation, pause and resume of existing downloads.
+ */
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mBinder;
+ }
+
+
+ /**
+ * Called when ALL the bound clients were onbound.
+ */
+ @Override
+ public boolean onUnbind(Intent intent) {
+ ((FileDownloaderBinder) mBinder).clearListeners();
+ return false; // not accepting rebinding (default behaviour)
+ }
+
+ @Override
+ public void onAccountsUpdated(Account[] accounts) {
+ //review the current download and cancel it if its account doesn't exist
+ if (mCurrentDownload != null && !accountManager.exists(mCurrentDownload.getUser().toPlatformAccount())) {
+ mCurrentDownload.cancel();
+ }
+ // The rest of downloads are cancelled when they try to start
+ }
+
+
+ /**
+ * Binder to let client components to perform operations on the queue of downloads.
+ *
+ * It provides by itself the available operations.
+ */
+ public class FileDownloaderBinder extends Binder implements OnDatatransferProgressListener {
+
+ /**
+ * Map of listeners that will be reported about progress of downloads from a
+ * {@link FileDownloaderBinder}
+ * instance.
+ */
+ private Map mBoundListeners =
+ new HashMap();
+
+
+ /**
+ * Cancels a pending or current download of a remote file.
+ *
+ * @param account ownCloud account where the remote file is stored.
+ * @param file A file in the queue of pending downloads
+ */
+ public void cancel(Account account, OCFile file) {
+ Pair removeResult =
+ mPendingDownloads.remove(account.name, file.getRemotePath());
+ DownloadFileOperation download = removeResult.first;
+ if (download != null) {
+ download.cancel();
+ } else {
+ if (mCurrentDownload != null && currentUser.isPresent() &&
+ mCurrentDownload.getRemotePath().startsWith(file.getRemotePath()) &&
+ account.name.equals(currentUser.get().getAccountName())) {
+ mCurrentDownload.cancel();
+ }
+ }
+ }
+
+ /**
+ * Cancels all the downloads for an account
+ */
+ public void cancel(String accountName) {
+ if (mCurrentDownload != null && mCurrentDownload.getUser().nameEquals(accountName)) {
+ mCurrentDownload.cancel();
+ }
+ // Cancel pending downloads
+ cancelPendingDownloads(accountName);
+ }
+
+ public void clearListeners() {
+ mBoundListeners.clear();
+ }
+
+
+ /**
+ * Returns True when the file described by 'file' in the ownCloud account 'account'
+ * is downloading or waiting to download.
+ *
+ * If 'file' is a directory, returns 'true' if any of its descendant files is downloading or
+ * waiting to download.
+ *
+ * @param user user where the remote file is stored.
+ * @param file A file that could be in the queue of downloads.
+ */
+ public boolean isDownloading(User user, OCFile file) {
+ return user != null && file != null && mPendingDownloads.contains(user.getAccountName(), file.getRemotePath());
+ }
+
+
+ /**
+ * Adds a listener interested in the progress of the download for a concrete file.
+ *
+ * @param listener Object to notify about progress of transfer.
+ * @param file {@link OCFile} of interest for listener.
+ */
+ public void addDatatransferProgressListener(OnDatatransferProgressListener listener, OCFile file) {
+ if (file == null || listener == null) {
+ return;
+ }
+ mBoundListeners.put(file.getFileId(), listener);
+ }
+
+
+ /**
+ * Removes a listener interested in the progress of the download for a concrete file.
+ *
+ * @param listener Object to notify about progress of transfer.
+ * @param file {@link OCFile} of interest for listener.
+ */
+ public void removeDatatransferProgressListener(OnDatatransferProgressListener listener, OCFile file) {
+ if (file == null || listener == null) {
+ return;
+ }
+ Long fileId = file.getFileId();
+ if (mBoundListeners.get(fileId) == listener) {
+ mBoundListeners.remove(fileId);
+ }
+ }
+
+ @Override
+ public void onTransferProgress(long progressRate, long totalTransferredSoFar,
+ long totalToTransfer, String fileName) {
+ OnDatatransferProgressListener boundListener =
+ mBoundListeners.get(mCurrentDownload.getFile().getFileId());
+ if (boundListener != null) {
+ boundListener.onTransferProgress(progressRate, totalTransferredSoFar,
+ totalToTransfer, fileName);
+ }
+ }
+
+ }
+
+ /**
+ * Download worker. Performs the pending downloads in the order they were requested.
+
+ * Created with the Looper of a new thread, started in {@link FileUploader#onCreate()}.
+ */
+ private static class ServiceHandler extends Handler {
+ // don't make it a final class, and don't remove the static ; lint will warn about a
+ // possible memory leak
+ FileDownloader mService;
+
+ public ServiceHandler(Looper looper, FileDownloader service) {
+ super(looper);
+ if (service == null) {
+ throw new IllegalArgumentException("Received invalid NULL in parameter 'service'");
+ }
+ mService = service;
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ @SuppressWarnings("unchecked")
+ AbstractList requestedDownloads = (AbstractList) msg.obj;
+ if (msg.obj != null) {
+ Iterator it = requestedDownloads.iterator();
+ while (it.hasNext()) {
+ String next = it.next();
+ mService.downloadFile(next);
+ }
+ }
+ mService.mStartedDownload=false;
+
+ (new Handler()).postDelayed(() -> {
+ if(!mService.mStartedDownload){
+ mService.mNotificationManager.cancel(R.string.downloader_download_in_progress_ticker);
+ }
+ Log_OC.d(TAG, "Stopping after command with id " + msg.arg1);
+ mService.mNotificationManager.cancel(FOREGROUND_SERVICE_ID);
+ mService.stopForeground(true);
+ mService.stopSelf(msg.arg1);
+ }, 2000);
+ }
+ }
+
+
+ /**
+ * Core download method: requests a file to download and stores it.
+ *
+ * @param downloadKey Key to access the download to perform, contained in mPendingDownloads
+ */
+ private void downloadFile(String downloadKey) {
+
+ mStartedDownload = true;
+ mCurrentDownload = mPendingDownloads.get(downloadKey);
+
+ if (mCurrentDownload != null) {
+ // Detect if the account exists
+ if (accountManager.exists(mCurrentDownload.getUser().toPlatformAccount())) {
+ notifyDownloadStart(mCurrentDownload);
+ RemoteOperationResult downloadResult = null;
+ try {
+ /// prepare client object to send the request to the ownCloud server
+ Account currentDownloadAccount = mCurrentDownload.getUser().toPlatformAccount();
+ Optional currentDownloadUser = accountManager.getUser(currentDownloadAccount.name);
+ if (!currentUser.equals(currentDownloadUser)) {
+ currentUser = currentDownloadUser;
+ mStorageManager = new FileDataStorageManager(currentUser.get(), getContentResolver());
+ } // else, reuse storage manager from previous operation
+
+ // always get client from client manager, to get fresh credentials in case
+ // of update
+ OwnCloudAccount ocAccount = currentDownloadUser.get().toOwnCloudAccount();
+ mDownloadClient = OwnCloudClientManagerFactory.getDefaultSingleton().
+ getClientFor(ocAccount, this);
+
+
+ /// perform the download
+ downloadResult = mCurrentDownload.execute(mDownloadClient);
+ if (downloadResult.isSuccess() && mCurrentDownload.getDownloadType() == DownloadType.DOWNLOAD) {
+ saveDownloadedFile();
+ }
+
+ } catch (Exception e) {
+ Log_OC.e(TAG, "Error downloading", e);
+ downloadResult = new RemoteOperationResult(e);
+
+ } finally {
+ Pair removeResult = mPendingDownloads.removePayload(
+ mCurrentDownload.getUser().getAccountName(), mCurrentDownload.getRemotePath());
+
+ if (downloadResult == null) {
+ downloadResult = new RemoteOperationResult(new RuntimeException("Error downloading…"));
+ }
+
+ /// notify result
+ notifyDownloadResult(mCurrentDownload, downloadResult);
+ sendBroadcastDownloadFinished(mCurrentDownload, downloadResult, removeResult.second);
+ }
+ } else {
+ cancelPendingDownloads(mCurrentDownload.getUser().getAccountName());
+ }
+ }
+ }
+
+
+ /**
+ * Updates the OC File after a successful download.
+ *
+ * TODO move to DownloadFileOperation
+ * unify with code from {@link DocumentsStorageProvider} and {@link DownloadTask}.
+ */
+ private void saveDownloadedFile() {
+ OCFile file = mStorageManager.getFileById(mCurrentDownload.getFile().getFileId());
+
+ if (file == null) {
+ // try to get file via path, needed for overwriting existing files on conflict dialog
+ file = mStorageManager.getFileByDecryptedRemotePath(mCurrentDownload.getFile().getRemotePath());
+ }
+
+ if (file == null) {
+ Log_OC.e(this, "Could not save " + mCurrentDownload.getFile().getRemotePath());
+ return;
+ }
+
+ long syncDate = System.currentTimeMillis();
+ file.setLastSyncDateForProperties(syncDate);
+ file.setLastSyncDateForData(syncDate);
+ file.setUpdateThumbnailNeeded(true);
+ file.setModificationTimestamp(mCurrentDownload.getModificationTimestamp());
+ file.setModificationTimestampAtLastSyncForData(mCurrentDownload.getModificationTimestamp());
+ file.setEtag(mCurrentDownload.getEtag());
+ file.setMimeType(mCurrentDownload.getMimeType());
+ file.setStoragePath(mCurrentDownload.getSavePath());
+ file.setFileLength(new File(mCurrentDownload.getSavePath()).length());
+ file.setRemoteId(mCurrentDownload.getFile().getRemoteId());
+ mStorageManager.saveFile(file);
+ if (MimeTypeUtil.isMedia(mCurrentDownload.getMimeType())) {
+ FileDataStorageManager.triggerMediaScan(file.getStoragePath(), file);
+ }
+ mStorageManager.saveConflict(file, null);
+ }
+
+ /**
+ * Creates a status notification to show the download progress
+ *
+ * @param download Download operation starting.
+ */
+ private void notifyDownloadStart(DownloadFileOperation download) {
+ /// create status notification with a progress bar
+ mLastPercent = 0;
+ mNotificationBuilder = NotificationUtils.newNotificationBuilder(this, viewThemeUtils);
+ mNotificationBuilder
+ .setSmallIcon(R.drawable.notification_icon)
+ .setTicker(getString(R.string.downloader_download_in_progress_ticker))
+ .setContentTitle(getString(R.string.downloader_download_in_progress_ticker))
+ .setOngoing(true)
+ .setProgress(100, 0, download.getSize() < 0)
+ .setContentText(
+ String.format(getString(R.string.downloader_download_in_progress_content), 0,
+ new File(download.getSavePath()).getName())
+ );
+
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
+ mNotificationBuilder.setChannelId(NotificationUtils.NOTIFICATION_CHANNEL_DOWNLOAD);
+ }
+
+ /// includes a pending intent in the notification showing the details view of the file
+ Intent showDetailsIntent = null;
+ if (PreviewImageFragment.canBePreviewed(download.getFile())) {
+ showDetailsIntent = new Intent(this, PreviewImageActivity.class);
+ } else {
+ showDetailsIntent = new Intent(this, FileDisplayActivity.class);
+ }
+ showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, download.getFile());
+ showDetailsIntent.putExtra(FileActivity.EXTRA_USER, download.getUser());
+ showDetailsIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+
+ mNotificationBuilder.setContentIntent(PendingIntent.getActivity(this, (int) System.currentTimeMillis(),
+ showDetailsIntent, PendingIntent.FLAG_IMMUTABLE));
+
+
+ if (mNotificationManager == null) {
+ mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+ }
+ if (mNotificationManager != null) {
+ mNotificationManager.notify(R.string.downloader_download_in_progress_ticker, mNotificationBuilder.build());
+ }
+ }
+
+
+ /**
+ * Callback method to update the progress bar in the status notification.
+ */
+ @Override
+ public void onTransferProgress(long progressRate, long totalTransferredSoFar,
+ long totalToTransfer, String filePath) {
+ int percent = (int) (100.0 * ((double) totalTransferredSoFar) / ((double) totalToTransfer));
+ if (percent != mLastPercent) {
+ mNotificationBuilder.setProgress(100, percent, totalToTransfer < 0);
+ String fileName = filePath.substring(filePath.lastIndexOf(FileUtils.PATH_SEPARATOR) + 1);
+ String text = String.format(getString(R.string.downloader_download_in_progress_content), percent, fileName);
+ mNotificationBuilder.setContentText(text);
+
+ if (mNotificationManager == null) {
+ mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+ }
+
+ if (mNotificationManager != null) {
+ mNotificationManager.notify(R.string.downloader_download_in_progress_ticker,
+ mNotificationBuilder.build());
+ }
+ }
+ mLastPercent = percent;
+ }
+
+
+ /**
+ * Updates the status notification with the result of a download operation.
+ *
+ * @param downloadResult Result of the download operation.
+ * @param download Finished download operation
+ */
+ @SuppressFBWarnings("DMI")
+ private void notifyDownloadResult(DownloadFileOperation download,
+ RemoteOperationResult downloadResult) {
+ if (mNotificationManager == null) {
+ mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+ }
+
+ if (!downloadResult.isCancelled()) {
+ if (downloadResult.isSuccess()) {
+ if (conflictUploadId > 0) {
+ uploadsStorageManager.removeUpload(conflictUploadId);
+ }
+ // Dont show notification except an error has occured.
+ return;
+ }
+ int tickerId = downloadResult.isSuccess() ?
+ R.string.downloader_download_succeeded_ticker : R.string.downloader_download_failed_ticker;
+
+ boolean needsToUpdateCredentials = ResultCode.UNAUTHORIZED == downloadResult.getCode();
+ tickerId = needsToUpdateCredentials ?
+ R.string.downloader_download_failed_credentials_error : tickerId;
+
+ mNotificationBuilder
+ .setTicker(getString(tickerId))
+ .setContentTitle(getString(tickerId))
+ .setAutoCancel(true)
+ .setOngoing(false)
+ .setProgress(0, 0, false);
+
+ if (needsToUpdateCredentials) {
+ configureUpdateCredentialsNotification(download.getUser());
+
+ } else {
+ // TODO put something smart in showDetailsIntent
+ Intent showDetailsIntent = new Intent();
+ mNotificationBuilder.setContentIntent(PendingIntent.getActivity(this, (int) System.currentTimeMillis(),
+ showDetailsIntent, PendingIntent.FLAG_IMMUTABLE));
+ }
+
+ mNotificationBuilder.setContentText(ErrorMessageAdapter.getErrorCauseMessage(downloadResult,
+ download, getResources()));
+
+ if (mNotificationManager != null) {
+ mNotificationManager.notify((new SecureRandom()).nextInt(), mNotificationBuilder.build());
+
+ // Remove success notification
+ if (downloadResult.isSuccess()) {
+ // Sleep 2 seconds, so show the notification before remove it
+ NotificationUtils.cancelWithDelay(mNotificationManager,
+ R.string.downloader_download_succeeded_ticker, 2000);
+ }
+ }
+ }
+ }
+
+ private void configureUpdateCredentialsNotification(User user) {
+ // let the user update credentials with one click
+ Intent updateAccountCredentials = new Intent(this, AuthenticatorActivity.class);
+ updateAccountCredentials.putExtra(AuthenticatorActivity.EXTRA_ACCOUNT, user.toPlatformAccount());
+ updateAccountCredentials.putExtra(
+ AuthenticatorActivity.EXTRA_ACTION,
+ AuthenticatorActivity.ACTION_UPDATE_EXPIRED_TOKEN
+ );
+ updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ updateAccountCredentials.addFlags(Intent.FLAG_FROM_BACKGROUND);
+ mNotificationBuilder.setContentIntent(
+ PendingIntent.getActivity(this,
+ (int) System.currentTimeMillis(),
+ updateAccountCredentials,
+ PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_IMMUTABLE)
+ );
+ }
+
+
+ /**
+ * Sends a broadcast when a download finishes in order to the interested activities can
+ * update their view
+ *
+ * @param download Finished download operation
+ * @param downloadResult Result of the download operation
+ * @param unlinkedFromRemotePath Path in the downloads tree where the download was unlinked from
+ */
+ private void sendBroadcastDownloadFinished(
+ DownloadFileOperation download,
+ RemoteOperationResult downloadResult,
+ String unlinkedFromRemotePath) {
+
+ Intent end = new Intent(getDownloadFinishMessage());
+ end.putExtra(EXTRA_DOWNLOAD_RESULT, downloadResult.isSuccess());
+ end.putExtra(ACCOUNT_NAME, download.getUser().getAccountName());
+ end.putExtra(EXTRA_REMOTE_PATH, download.getRemotePath());
+ end.putExtra(OCFileListFragment.DOWNLOAD_BEHAVIOUR, download.getBehaviour());
+ end.putExtra(SendShareDialog.ACTIVITY_NAME, download.getActivityName());
+ end.putExtra(SendShareDialog.PACKAGE_NAME, download.getPackageName());
+ if (unlinkedFromRemotePath != null) {
+ end.putExtra(EXTRA_LINKED_TO_PATH, unlinkedFromRemotePath);
+ }
+ end.setPackage(getPackageName());
+ localBroadcastManager.sendBroadcast(end);
+ }
+
+
+ /**
+ * Sends a broadcast when a new download is added to the queue.
+ *
+ * @param download Added download operation
+ * @param linkedToRemotePath Path in the downloads tree where the download was linked to
+ */
+ private void sendBroadcastNewDownload(DownloadFileOperation download,
+ String linkedToRemotePath) {
+ Intent added = new Intent(getDownloadAddedMessage());
+ added.putExtra(ACCOUNT_NAME, download.getUser().getAccountName());
+ added.putExtra(EXTRA_REMOTE_PATH, download.getRemotePath());
+ added.putExtra(EXTRA_LINKED_TO_PATH, linkedToRemotePath);
+ added.setPackage(getPackageName());
+ localBroadcastManager.sendBroadcast(added);
+ }
+
+ private void cancelPendingDownloads(String accountName) {
+ mPendingDownloads.remove(accountName);
+ }
+}
diff --git a/app/src/main/java/com/owncloud/android/files/services/FileDownloader.kt b/app/src/main/java/com/owncloud/android/files/services/FileDownloader.kt
deleted file mode 100644
index 6463950129..0000000000
--- a/app/src/main/java/com/owncloud/android/files/services/FileDownloader.kt
+++ /dev/null
@@ -1,750 +0,0 @@
-/*
- * ownCloud Android client application
- *
- * Copyright (C) 2012 Bartek Przybylski
- * Copyright (C) 2012-2016 ownCloud Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- *
- */
-package com.owncloud.android.files.services
-
-import android.accounts.Account
-import android.accounts.AccountManager
-import android.accounts.OnAccountsUpdateListener
-import android.app.Notification
-import android.app.NotificationManager
-import android.app.PendingIntent
-import android.app.Service
-import android.content.Intent
-import android.os.Binder
-import android.os.Build
-import android.os.Handler
-import android.os.HandlerThread
-import android.os.IBinder
-import android.os.Looper
-import android.os.Message
-import android.os.Process
-import androidx.core.app.NotificationCompat
-import androidx.localbroadcastmanager.content.LocalBroadcastManager
-import com.nextcloud.client.account.User
-import com.nextcloud.client.account.UserAccountManager
-import com.nextcloud.java.util.Optional
-import com.owncloud.android.R
-import com.owncloud.android.authentication.AuthenticatorActivity
-import com.owncloud.android.datamodel.FileDataStorageManager
-import com.owncloud.android.datamodel.OCFile
-import com.owncloud.android.datamodel.UploadsStorageManager
-import com.owncloud.android.lib.common.OwnCloudClient
-import com.owncloud.android.lib.common.OwnCloudClientManagerFactory
-import com.owncloud.android.lib.common.network.OnDatatransferProgressListener
-import com.owncloud.android.lib.common.operations.RemoteOperationResult
-import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode
-import com.owncloud.android.lib.common.utils.Log_OC
-import com.owncloud.android.operations.DownloadFileOperation
-import com.owncloud.android.operations.DownloadType
-import com.owncloud.android.ui.activity.ConflictsResolveActivity
-import com.owncloud.android.ui.activity.FileActivity
-import com.owncloud.android.ui.activity.FileDisplayActivity
-import com.owncloud.android.ui.dialog.SendShareDialog
-import com.owncloud.android.ui.fragment.OCFileListFragment
-import com.owncloud.android.ui.notifications.NotificationUtils
-import com.owncloud.android.ui.preview.PreviewImageActivity
-import com.owncloud.android.ui.preview.PreviewImageFragment
-import com.owncloud.android.utils.ErrorMessageAdapter
-import com.owncloud.android.utils.MimeTypeUtil
-import com.owncloud.android.utils.theme.ViewThemeUtils
-import dagger.android.AndroidInjection
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings
-import java.io.File
-import java.security.SecureRandom
-import java.util.AbstractList
-import javax.inject.Inject
-
-class FileDownloader : Service(), OnDatatransferProgressListener, OnAccountsUpdateListener {
- private var mServiceLooper: Looper? = null
- private var mServiceHandler: ServiceHandler? = null
- private var mBinder: IBinder? = null
- private var mDownloadClient: OwnCloudClient? = null
- private var currentUser = Optional.empty()
- private var mStorageManager: FileDataStorageManager? = null
- private val mPendingDownloads = IndexedForest()
- private var mCurrentDownload: DownloadFileOperation? = null
- private var notificationManager: NotificationManager? = null
- private var notification: Notification? = null
- private var notificationBuilder: NotificationCompat.Builder? = null
- private var mLastPercent = 0
- private var conflictUploadId: Long = 0
- var mStartedDownload = false
-
- @JvmField
- @Inject
- var accountManager: UserAccountManager? = null
-
- @JvmField
- @Inject
- var uploadsStorageManager: UploadsStorageManager? = null
-
- @JvmField
- @Inject
- var localBroadcastManager: LocalBroadcastManager? = null
-
- @JvmField
- @Inject
- var viewThemeUtils: ViewThemeUtils? = null
-
- /**
- * Service initialization
- */
- override fun onCreate() {
- super.onCreate()
-
- AndroidInjection.inject(this)
- Log_OC.d(TAG, "Creating service")
- initNotificationManager()
- val thread = HandlerThread("FileDownloaderThread", Process.THREAD_PRIORITY_BACKGROUND)
- thread.start()
- mServiceLooper = thread.looper
- mServiceHandler = ServiceHandler(mServiceLooper, this)
- mBinder = FileDownloaderBinder()
- initNotificationBuilder()
-
- // add AccountsUpdatedListener
- val am = AccountManager.get(applicationContext)
- am.addOnAccountsUpdatedListener(this, null, false)
- }
-
- private fun initNotificationManager() {
- if (notificationManager == null) {
- notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
- }
- }
-
- private fun initNotificationBuilder() {
- val resources = applicationContext.resources
- val title = resources.getString(R.string.foreground_service_download)
-
- notificationBuilder = NotificationUtils.newNotificationBuilder(this, viewThemeUtils)
- .setSmallIcon(R.drawable.notification_icon)
- .setOngoing(true)
- .setContentTitle(title)
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- notificationBuilder?.setChannelId(NotificationUtils.NOTIFICATION_CHANNEL_DOWNLOAD)
- }
- notification = notificationBuilder?.build()
- }
-
- private fun notifyNotificationManager() {
- notificationManager?.notify(R.string.downloader_download_in_progress_ticker, notificationBuilder?.build())
- }
-
- /**
- * Service clean up
- */
- override fun onDestroy() {
- Log_OC.v(TAG, "Destroying service")
- mBinder = null
- mServiceHandler = null
- mServiceLooper!!.quit()
- mServiceLooper = null
- notificationManager = null
- notification = null
- notificationBuilder = null
-
- // remove AccountsUpdatedListener
- val am = AccountManager.get(applicationContext)
- am.removeOnAccountsUpdatedListener(this)
- super.onDestroy()
- }
-
- /**
- * Entry point to add one or several files to the queue of downloads.
- *
- * New downloads are added calling to startService(), resulting in a call to this method.
- * This ensures the service will keep on working although the caller activity goes away.
- */
- @Suppress("LongParameterList")
- override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
- Log_OC.d(TAG, "Starting command with id $startId")
-
- startForeground(FOREGROUND_SERVICE_ID, notification)
-
- if (!intent.hasExtra(EXTRA_USER) || !intent.hasExtra(EXTRA_FILE)) {
- Log_OC.e(TAG, "Not enough information provided in intent")
- return START_NOT_STICKY
- }
-
- val user = intent.getParcelableExtra(EXTRA_USER)
- val file = intent.getParcelableExtra(EXTRA_FILE)
- val behaviour = intent.getStringExtra(OCFileListFragment.DOWNLOAD_BEHAVIOUR)
- var downloadType: DownloadType? = DownloadType.DOWNLOAD
- if (intent.hasExtra(DOWNLOAD_TYPE)) {
- downloadType = intent.getSerializableExtra(DOWNLOAD_TYPE) as DownloadType?
- }
- val activityName = intent.getStringExtra(SendShareDialog.ACTIVITY_NAME)
- val packageName = intent.getStringExtra(SendShareDialog.PACKAGE_NAME)
- conflictUploadId = intent.getLongExtra(ConflictsResolveActivity.EXTRA_CONFLICT_UPLOAD_ID, -1)
-
- val requestedDownloads = handleDownloadRequest(user, file, behaviour, downloadType, activityName, packageName)
-
- if (requestedDownloads.isNotEmpty()) {
- val msg = mServiceHandler?.obtainMessage()
- msg?.arg1 = startId
- msg?.obj = requestedDownloads
- msg?.let {
- mServiceHandler?.sendMessage(it)
- }
- }
-
- return START_NOT_STICKY
- }
-
- @Suppress("LongParameterList")
- private fun handleDownloadRequest(
- user: User?,
- file: OCFile?,
- behaviour: String?,
- downloadType: DownloadType?,
- activityName: String?,
- packageName: String?
- ): List {
- val requestedDownloads: MutableList = ArrayList()
-
- if (user == null || file == null) {
- return requestedDownloads
- }
-
- try {
- val newDownload = DownloadFileOperation(
- user,
- file,
- behaviour,
- activityName,
- packageName,
- baseContext,
- downloadType
- )
- newDownload.addDatatransferProgressListener(this)
- newDownload.addDatatransferProgressListener(mBinder as FileDownloaderBinder?)
-
- val putResult = mPendingDownloads.putIfAbsent(user.accountName, file.remotePath, newDownload)
-
- if (putResult != null) {
- val downloadKey = putResult.first
- requestedDownloads.add(downloadKey)
- sendBroadcastNewDownload(newDownload, putResult.second)
- }
- } catch (e: IllegalArgumentException) {
- Log_OC.e(TAG, "Not enough information provided in intent: " + e.message)
- }
-
- return requestedDownloads
- }
-
- /**
- * Provides a binder object that clients can use to perform operations on the queue of downloads,
- * excepting the addition of new files.
- *
- * Implemented to perform cancellation, pause and resume of existing downloads.
- */
- override fun onBind(intent: Intent): IBinder? {
- return mBinder
- }
-
- /**
- * Called when ALL the bound clients were onbound.
- */
- override fun onUnbind(intent: Intent): Boolean {
- (mBinder as FileDownloaderBinder?)!!.clearListeners()
- return false // not accepting rebinding (default behaviour)
- }
-
- override fun onAccountsUpdated(accounts: Array) {
- // review the current download and cancel it if its account doesn't exist
- if (mCurrentDownload != null && !accountManager!!.exists(mCurrentDownload!!.user.toPlatformAccount())) {
- mCurrentDownload!!.cancel()
- }
- // The rest of downloads are cancelled when they try to start
- }
-
- /**
- * Binder to let client components to perform operations on the queue of downloads.
- *
- *
- * It provides by itself the available operations.
- */
- inner class FileDownloaderBinder : Binder(), OnDatatransferProgressListener {
- /**
- * Map of listeners that will be reported about progress of downloads from a
- * [FileDownloaderBinder]
- * instance.
- */
- private val mBoundListeners: MutableMap = HashMap()
-
- /**
- * Cancels a pending or current download of a remote file.
- *
- * @param account ownCloud account where the remote file is stored.
- * @param file A file in the queue of pending downloads
- */
- @Suppress("ComplexMethod")
- fun cancel(account: Account, file: OCFile) {
- val removeResult = mPendingDownloads.remove(account.name, file.remotePath)
- val download = removeResult.first
-
- if (download != null) {
- download.cancel()
- } else {
- mCurrentDownload?.takeIf {
- it.remotePath.startsWith(file.remotePath) && account.name == currentUser?.get()?.accountName
- }?.cancel()
- }
- }
-
- /**
- * Cancels all the downloads for an account
- */
- fun cancel(accountName: String?) {
- if (mCurrentDownload != null && mCurrentDownload!!.user.nameEquals(accountName)) {
- mCurrentDownload!!.cancel()
- }
- // Cancel pending downloads
- cancelPendingDownloads(accountName)
- }
-
- fun clearListeners() {
- mBoundListeners.clear()
- }
-
- /**
- * Returns True when the file described by 'file' in the ownCloud account 'account'
- * is downloading or waiting to download.
- *
- * If 'file' is a directory, returns 'true' if any of its descendant files is downloading or
- * waiting to download.
- *
- * @param user user where the remote file is stored.
- * @param file A file that could be in the queue of downloads.
- */
- fun isDownloading(user: User?, file: OCFile?): Boolean {
- return user != null && file != null && mPendingDownloads.contains(user.accountName, file.remotePath)
- }
-
- /**
- * Adds a listener interested in the progress of the download for a concrete file.
- *
- * @param listener Object to notify about progress of transfer.
- * @param file [OCFile] of interest for listener.
- */
- fun addDataTransferProgressListener(listener: OnDatatransferProgressListener?, file: OCFile?) {
- if (file == null || listener == null) {
- return
- }
- mBoundListeners[file.fileId] = listener
- }
-
- /**
- * Removes a listener interested in the progress of the download for a concrete file.
- *
- * @param listener Object to notify about progress of transfer.
- * @param file [OCFile] of interest for listener.
- */
- fun removeDataTransferProgressListener(listener: OnDatatransferProgressListener?, file: OCFile?) {
- if (file == null || listener == null) {
- return
- }
- val fileId = file.fileId
- if (mBoundListeners[fileId] === listener) {
- mBoundListeners.remove(fileId)
- }
- }
-
- override fun onTransferProgress(
- progressRate: Long,
- totalTransferredSoFar: Long,
- totalToTransfer: Long,
- fileName: String
- ) {
- val boundListener = mBoundListeners[mCurrentDownload!!.file.fileId]
- boundListener?.onTransferProgress(
- progressRate,
- totalTransferredSoFar,
- totalToTransfer,
- fileName
- )
- }
- }
-
- /**
- * Download worker. Performs the pending downloads in the order they were requested.
- *
- * Created with the Looper of a new thread, started in [FileUploader.onCreate].
- */
- private class ServiceHandler(looper: Looper?, service: FileDownloader?) : Handler(looper!!) {
- // don't make it a final class, and don't remove the static ; lint will warn about a
- // possible memory leak
- var mService: FileDownloader
-
- init {
- requireNotNull(service) { "Received invalid NULL in parameter 'service'" }
- mService = service
- }
-
- @Suppress("MagicNumber")
- override fun handleMessage(msg: Message) {
- val requestedDownloads = msg.obj as AbstractList
- if (msg.obj != null) {
- val it: Iterator = requestedDownloads.iterator()
- while (it.hasNext()) {
- val next = it.next()
- mService.downloadFile(next)
- }
- }
- mService.mStartedDownload = false
-
- Handler(Looper.getMainLooper()).postDelayed({
- if (!mService.mStartedDownload) {
- mService.notificationManager!!.cancel(R.string.downloader_download_in_progress_ticker)
- }
- Log_OC.d(TAG, "Stopping after command with id " + msg.arg1)
- mService.notificationManager!!.cancel(FOREGROUND_SERVICE_ID)
- mService.stopForeground(true)
- mService.stopSelf(msg.arg1)
- }, 2000)
- }
- }
-
- /**
- * Core download method: requests a file to download and stores it.
- *
- * @param downloadKey Key to access the download to perform, contained in mPendingDownloads
- */
- @Suppress("NestedBlockDepth", "TooGenericExceptionCaught")
- private fun downloadFile(downloadKey: String) {
- mStartedDownload = true
- mCurrentDownload = mPendingDownloads[downloadKey]
-
- if (mCurrentDownload != null) {
- val isAccountExist = accountManager?.exists(mCurrentDownload!!.user.toPlatformAccount())
-
- if (isAccountExist == true) {
- notifyDownloadStart(mCurrentDownload!!)
- var downloadResult: RemoteOperationResult<*>? = null
- try {
- // / prepare client object to send the request to the ownCloud server
- val currentDownloadAccount = mCurrentDownload!!.user.toPlatformAccount()
- val currentDownloadUser = accountManager!!.getUser(currentDownloadAccount.name)
- if (currentUser != currentDownloadUser) {
- currentUser = currentDownloadUser
- mStorageManager = FileDataStorageManager(currentUser.get(), contentResolver)
- } // else, reuse storage manager from previous operation
-
- // always get client from client manager, to get fresh credentials in case
- // of update
- val ocAccount = currentDownloadUser.get().toOwnCloudAccount()
- mDownloadClient = OwnCloudClientManagerFactory.getDefaultSingleton().getClientFor(ocAccount, this)
-
- // / perform the download
- downloadResult = mCurrentDownload!!.execute(mDownloadClient)
- if (downloadResult.isSuccess && mCurrentDownload!!.downloadType === DownloadType.DOWNLOAD) {
- saveDownloadedFile()
- }
- } catch (e: Exception) {
- Log_OC.e(TAG, "Error downloading", e)
- downloadResult = RemoteOperationResult(e)
- } finally {
- val removeResult = mPendingDownloads.removePayload(
- mCurrentDownload!!.user.accountName,
- mCurrentDownload!!.remotePath
- )
- if (downloadResult == null) {
- downloadResult = RemoteOperationResult(RuntimeException("Error downloading…"))
- }
-
- // / notify result
- notifyDownloadResult(mCurrentDownload!!, downloadResult)
- sendBroadcastDownloadFinished(mCurrentDownload!!, downloadResult, removeResult.second)
- }
- } else {
- cancelPendingDownloads(mCurrentDownload!!.user.accountName)
- }
- }
- }
-
- /**
- * Updates the OC File after a successful download.
- *
- * TODO move to DownloadFileOperation
- * unify with code from [DocumentsStorageProvider] and [DownloadTask].
- */
- private fun saveDownloadedFile() {
- var file = mStorageManager?.getFileById(mCurrentDownload!!.file.fileId)
- if (file == null) {
- // try to get file via path, needed for overwriting existing files on conflict dialog
- file = mStorageManager?.getFileByDecryptedRemotePath(mCurrentDownload!!.file.remotePath)
- }
- if (file == null) {
- Log_OC.e(this, "Could not save " + mCurrentDownload!!.file.remotePath)
- return
- }
-
- val syncDate = System.currentTimeMillis()
- file.lastSyncDateForProperties = syncDate
- file.lastSyncDateForData = syncDate
- file.isUpdateThumbnailNeeded = true
- file.modificationTimestamp = mCurrentDownload!!.modificationTimestamp
- file.modificationTimestampAtLastSyncForData = mCurrentDownload!!.modificationTimestamp
- file.etag = mCurrentDownload!!.etag
- file.mimeType = mCurrentDownload!!.mimeType
- file.storagePath = mCurrentDownload!!.savePath
- file.fileLength = File(mCurrentDownload!!.savePath).length()
- file.remoteId = mCurrentDownload!!.file.remoteId
- mStorageManager!!.saveFile(file)
- if (MimeTypeUtil.isMedia(mCurrentDownload!!.mimeType)) {
- FileDataStorageManager.triggerMediaScan(file.storagePath, file)
- }
- mStorageManager!!.saveConflict(file, null)
- }
-
- /**
- * Creates a status notification to show the download progress
- *
- * @param download Download operation starting.
- */
- @Suppress("MagicNumber")
- private fun notifyDownloadStart(download: DownloadFileOperation) {
- val fileName = download.file.getFileNameWithExtension(10)
- val titlePrefix = getString(R.string.file_downloader_notification_title_prefix)
- val title = titlePrefix + fileName
-
- // / update status notification with a progress bar
- mLastPercent = 0
- notificationBuilder
- ?.setContentTitle(title)
- ?.setTicker(title)
- ?.setProgress(100, 0, download.size < 0)
-
- // / includes a pending intent in the notification showing the details view of the file
- val showDetailsIntent: Intent = if (PreviewImageFragment.canBePreviewed(download.file)) {
- Intent(this, PreviewImageActivity::class.java)
- } else {
- Intent(this, FileDisplayActivity::class.java)
- }
-
- showDetailsIntent.putExtra(FileActivity.EXTRA_FILE, download.file)
- showDetailsIntent.putExtra(FileActivity.EXTRA_USER, download.user)
- showDetailsIntent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP
- notificationBuilder?.setContentIntent(
- PendingIntent.getActivity(
- this,
- System.currentTimeMillis().toInt(),
- showDetailsIntent,
- PendingIntent.FLAG_IMMUTABLE
- )
- )
- initNotificationManager()
- notifyNotificationManager()
- }
-
- /**
- * Callback method to update the progress bar in the status notification.
- */
- @Suppress("MagicNumber")
- override fun onTransferProgress(
- progressRate: Long,
- totalTransferredSoFar: Long,
- totalToTransfer: Long,
- filePath: String
- ) {
- val percent = (100.0 * totalTransferredSoFar.toDouble() / totalToTransfer.toDouble()).toInt()
- if (percent != mLastPercent) {
- notificationBuilder?.setProgress(100, percent, totalToTransfer < 0)
- initNotificationManager()
- notifyNotificationManager()
- }
- mLastPercent = percent
- }
-
- /**
- * Updates the status notification with the result of a download operation.
- *
- * @param downloadResult Result of the download operation.
- * @param download Finished download operation
- */
- @SuppressFBWarnings("DMI")
- @Suppress("MagicNumber")
- private fun notifyDownloadResult(
- download: DownloadFileOperation,
- downloadResult: RemoteOperationResult<*>
- ) {
- initNotificationManager()
- if (!downloadResult.isCancelled) {
- if (downloadResult.isSuccess) {
- if (conflictUploadId > 0) {
- uploadsStorageManager!!.removeUpload(conflictUploadId)
- }
- // Don't show notification except an error has occurred.
- return
- }
-
- var tickerId = if (downloadResult.isSuccess) {
- R.string.downloader_download_succeeded_ticker
- } else {
- R.string.downloader_download_failed_ticker
- }
-
- val needsToUpdateCredentials = ResultCode.UNAUTHORIZED == downloadResult.code
-
- tickerId = if (needsToUpdateCredentials) {
- R.string.downloader_download_failed_credentials_error
- } else {
- tickerId
- }
-
- notificationBuilder
- ?.setSmallIcon(R.drawable.notification_icon)
- ?.setTicker(getString(tickerId))
- ?.setAutoCancel(true)
- ?.setOngoing(false)
- ?.setProgress(0, 0, false)
-
- if (needsToUpdateCredentials) {
- configureUpdateCredentialsNotification(download.user)
- } else {
- // TODO put something smart in showDetailsIntent
- val showDetailsIntent = Intent()
- notificationBuilder?.setContentIntent(
- PendingIntent.getActivity(
- this,
- System.currentTimeMillis().toInt(),
- showDetailsIntent,
- PendingIntent.FLAG_IMMUTABLE
- )
- )
- }
- notificationBuilder?.setContentText(
- ErrorMessageAdapter.getErrorCauseMessage(
- downloadResult,
- download,
- resources
- )
- )
- if (notificationManager != null) {
- notificationManager?.notify(SecureRandom().nextInt(), notificationBuilder?.build())
-
- // Remove success notification
- if (downloadResult.isSuccess) {
- // Sleep 2 seconds, so show the notification before remove it
- NotificationUtils.cancelWithDelay(
- notificationManager,
- R.string.downloader_download_succeeded_ticker,
- 2000
- )
- }
- }
- }
- }
-
- private fun configureUpdateCredentialsNotification(user: User) {
- // let the user update credentials with one click
- val updateAccountCredentials = Intent(this, AuthenticatorActivity::class.java)
- updateAccountCredentials.putExtra(AuthenticatorActivity.EXTRA_ACCOUNT, user.toPlatformAccount())
- updateAccountCredentials.putExtra(
- AuthenticatorActivity.EXTRA_ACTION,
- AuthenticatorActivity.ACTION_UPDATE_EXPIRED_TOKEN
- )
- updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
- updateAccountCredentials.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
- updateAccountCredentials.addFlags(Intent.FLAG_FROM_BACKGROUND)
- notificationBuilder!!.setContentIntent(
- PendingIntent.getActivity(
- this,
- System.currentTimeMillis().toInt(),
- updateAccountCredentials,
- PendingIntent.FLAG_ONE_SHOT or PendingIntent.FLAG_IMMUTABLE
- )
- )
- }
-
- /**
- * Sends a broadcast when a download finishes in order to the interested activities can
- * update their view
- *
- * @param download Finished download operation
- * @param downloadResult Result of the download operation
- * @param unlinkedFromRemotePath Path in the downloads tree where the download was unlinked from
- */
- private fun sendBroadcastDownloadFinished(
- download: DownloadFileOperation,
- downloadResult: RemoteOperationResult<*>,
- unlinkedFromRemotePath: String?
- ) {
- val end = Intent(downloadFinishMessage)
- end.putExtra(EXTRA_DOWNLOAD_RESULT, downloadResult.isSuccess)
- end.putExtra(ACCOUNT_NAME, download.user.accountName)
- end.putExtra(EXTRA_REMOTE_PATH, download.remotePath)
- end.putExtra(OCFileListFragment.DOWNLOAD_BEHAVIOUR, download.behaviour)
- end.putExtra(SendShareDialog.ACTIVITY_NAME, download.activityName)
- end.putExtra(SendShareDialog.PACKAGE_NAME, download.packageName)
- if (unlinkedFromRemotePath != null) {
- end.putExtra(EXTRA_LINKED_TO_PATH, unlinkedFromRemotePath)
- }
- end.setPackage(packageName)
- localBroadcastManager!!.sendBroadcast(end)
- }
-
- /**
- * Sends a broadcast when a new download is added to the queue.
- *
- * @param download Added download operation
- * @param linkedToRemotePath Path in the downloads tree where the download was linked to
- */
- private fun sendBroadcastNewDownload(
- download: DownloadFileOperation,
- linkedToRemotePath: String
- ) {
- val added = Intent(downloadAddedMessage)
- added.putExtra(ACCOUNT_NAME, download.user.accountName)
- added.putExtra(EXTRA_REMOTE_PATH, download.remotePath)
- added.putExtra(EXTRA_LINKED_TO_PATH, linkedToRemotePath)
- added.setPackage(packageName)
- localBroadcastManager!!.sendBroadcast(added)
- }
-
- private fun cancelPendingDownloads(accountName: String?) {
- mPendingDownloads.remove(accountName)
- }
-
- companion object {
- const val EXTRA_USER = "USER"
- const val EXTRA_FILE = "FILE"
- private const val DOWNLOAD_ADDED_MESSAGE = "DOWNLOAD_ADDED"
- private const val DOWNLOAD_FINISH_MESSAGE = "DOWNLOAD_FINISH"
- const val EXTRA_DOWNLOAD_RESULT = "RESULT"
- const val EXTRA_REMOTE_PATH = "REMOTE_PATH"
- const val EXTRA_LINKED_TO_PATH = "LINKED_TO"
- const val ACCOUNT_NAME = "ACCOUNT_NAME"
- const val DOWNLOAD_TYPE = "DOWNLOAD_TYPE"
- private const val FOREGROUND_SERVICE_ID = 412
- private val TAG = FileDownloader::class.java.simpleName
-
- @JvmStatic
- val downloadAddedMessage: String
- get() = FileDownloader::class.java.name + DOWNLOAD_ADDED_MESSAGE
-
- @JvmStatic
- val downloadFinishMessage: String
- get() = FileDownloader::class.java.name + DOWNLOAD_FINISH_MESSAGE
- }
-}
diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java
index 97f777d4c4..8396ceb3e8 100644
--- a/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java
+++ b/app/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java
@@ -693,7 +693,7 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
if (progressListener != null) {
if (containerActivity.getFileDownloaderBinder() != null) {
containerActivity.getFileDownloaderBinder().
- addDataTransferProgressListener(progressListener, getFile());
+ addDatatransferProgressListener(progressListener, getFile());
}
if (containerActivity.getFileUploaderBinder() != null) {
containerActivity.getFileUploaderBinder().
@@ -708,7 +708,7 @@ public class FileDetailFragment extends FileFragment implements OnClickListener,
if (progressListener != null) {
if (containerActivity.getFileDownloaderBinder() != null) {
containerActivity.getFileDownloaderBinder().
- removeDataTransferProgressListener(progressListener, getFile());
+ removeDatatransferProgressListener(progressListener, getFile());
}
if (containerActivity.getFileUploaderBinder() != null) {
containerActivity.getFileUploaderBinder().
diff --git a/app/src/main/java/com/owncloud/android/ui/preview/FileDownloadFragment.java b/app/src/main/java/com/owncloud/android/ui/preview/FileDownloadFragment.java
index 62a098e37d..ffca7c685a 100644
--- a/app/src/main/java/com/owncloud/android/ui/preview/FileDownloadFragment.java
+++ b/app/src/main/java/com/owncloud/android/ui/preview/FileDownloadFragment.java
@@ -261,7 +261,7 @@ public class FileDownloadFragment extends FileFragment implements OnClickListene
public void listenForTransferProgress() {
if (mProgressListener != null && !mListening && containerActivity.getFileDownloaderBinder() != null) {
- containerActivity.getFileDownloaderBinder().addDataTransferProgressListener(mProgressListener, getFile());
+ containerActivity.getFileDownloaderBinder().addDatatransferProgressListener(mProgressListener, getFile());
mListening = true;
setButtonsForTransferring();
}
@@ -271,7 +271,7 @@ public class FileDownloadFragment extends FileFragment implements OnClickListene
public void leaveTransferProgress() {
if (mProgressListener != null && containerActivity.getFileDownloaderBinder() != null) {
containerActivity.getFileDownloaderBinder()
- .removeDataTransferProgressListener(mProgressListener, getFile());
+ .removeDatatransferProgressListener(mProgressListener, getFile());
mListening = false;
}
}