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; } }