diff --git a/owncloudApp/src/main/AndroidManifest.xml b/owncloudApp/src/main/AndroidManifest.xml index c0f3e091bce..0abeee83540 100644 --- a/owncloudApp/src/main/AndroidManifest.xml +++ b/owncloudApp/src/main/AndroidManifest.xml @@ -178,7 +178,6 @@ - - * Copyright (C) 2012 Bartek Przybylski - * Copyright (C) 2020 ownCloud GmbH. - *

- * 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.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.Parcelable; -import android.os.Process; -import android.util.Pair; - -import androidx.annotation.Nullable; -import androidx.core.app.NotificationCompat; -import androidx.localbroadcastmanager.content.LocalBroadcastManager; -import com.owncloud.android.R; -import com.owncloud.android.authentication.AccountUtils; -import com.owncloud.android.datamodel.FileDataStorageManager; -import com.owncloud.android.datamodel.OCUpload; -import com.owncloud.android.datamodel.UploadsStorageManager; -import com.owncloud.android.datamodel.UploadsStorageManager.UploadStatus; -import com.owncloud.android.db.UploadResult; -import com.owncloud.android.domain.capabilities.model.OCCapability; -import com.owncloud.android.domain.files.model.OCFile; -import com.owncloud.android.lib.common.OwnCloudAccount; -import com.owncloud.android.lib.common.OwnCloudClient; -import com.owncloud.android.lib.common.SingleSessionManager; -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.resources.files.chunks.ChunkedUploadFromFileSystemOperation; -import com.owncloud.android.lib.resources.files.chunks.RemoveRemoteChunksFolderOperation; -import com.owncloud.android.operations.ChunkedUploadFileOperation; -import com.owncloud.android.operations.UploadFileOperation; -import com.owncloud.android.ui.activity.FileActivity; -import com.owncloud.android.ui.activity.UploadListActivity; -import com.owncloud.android.ui.errorhandling.ErrorMessageAdapter; -import com.owncloud.android.utils.Extras; -import com.owncloud.android.utils.NotificationUtils; -import com.owncloud.android.utils.SecurityUtils; -import kotlin.Unit; -import timber.log.Timber; - -import java.io.File; -import java.lang.ref.WeakReference; -import java.util.AbstractList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.Vector; - -import static com.owncloud.android.operations.UploadFileOperation.CREATED_AS_CAMERA_UPLOAD_PICTURE; -import static com.owncloud.android.operations.UploadFileOperation.CREATED_AS_CAMERA_UPLOAD_VIDEO; -import static com.owncloud.android.utils.NotificationConstantsKt.UPLOAD_NOTIFICATION_CHANNEL_ID; - -/** - * Service for uploading files. Invoke using context.startService(...). - *

- * Files to be uploaded are stored persistently using {@link UploadsStorageManager}. - *

- * On next invocation of {@link FileUploader} uploaded files which - * previously failed will be uploaded again until either upload succeeded or a - * fatal error occurred. - *

- * Every file passed to this service is uploaded. No filtering is performed. - */ -public class FileUploader extends Service - implements OnDatatransferProgressListener, OnAccountsUpdateListener, - UploadFileOperation.OnRenameListener { - - private static final String UPLOADS_ADDED_MESSAGE = "UPLOADS_ADDED"; - private static final String UPLOAD_START_MESSAGE = "UPLOAD_START"; - private static final String UPLOAD_FINISH_MESSAGE = "UPLOAD_FINISH"; - - protected static final String KEY_FILE = "FILE"; - protected static final String KEY_LOCAL_FILE = "LOCAL_FILE"; - protected static final String KEY_REMOTE_FILE = "REMOTE_FILE"; - protected static final String KEY_MIME_TYPE = "MIME_TYPE"; - protected static final String KEY_IS_AVAILABLE_OFFLINE_FILE = "KEY_IS_AVAILABLE_OFFLINE_FILE"; - protected static final String KEY_REQUESTED_FROM_WIFI_BACK_EVENT = "KEY_REQUESTED_FROM_WIFI_BACK_EVENT"; - - /** - * Call this Service with only this Intent key if all pending uploads are to be retried. - */ - protected static final String KEY_RETRY = "KEY_RETRY"; - /** - * Call this Service with KEY_RETRY and KEY_RETRY_UPLOAD to retry - * upload of file identified by KEY_RETRY_UPLOAD. - */ - protected static final String KEY_RETRY_UPLOAD = "KEY_RETRY_UPLOAD"; - /** - * {@link Account} to which file is to be uploaded. - */ - protected static final String KEY_ACCOUNT = "ACCOUNT"; - - /** - * Set to true if remote file is to be overwritten. Default action is to upload with different name. - */ - protected static final String KEY_FORCE_OVERWRITE = "KEY_FORCE_OVERWRITE"; - /** - * Key to signal what is the origin of the upload request - */ - protected static final String KEY_CREATED_BY = "CREATED_BY"; - - protected static final String KEY_LOCAL_BEHAVIOUR = "BEHAVIOUR"; - - public static final int LEGACY_LOCAL_BEHAVIOUR_COPY = 0; - public static final int LEGACY_LOCAL_BEHAVIOUR_MOVE = 1; - public static final int LEGACY_LOCAL_BEHAVIOUR_FORGET = 2; - - private OwnCloudClient mUploadClient = null; - private Account mCurrentAccount = null; - private FileDataStorageManager mStorageManager; - //since there can be only one instance of an Android service, there also just one db connection. - private UploadsStorageManager mUploadsStorageManager = null; - - private IndexedForest mPendingUploads = new IndexedForest<>(); - - private LocalBroadcastManager mLocalBroadcastManager; - - /** - * {@link UploadFileOperation} object of ongoing upload. Can be null. Note: There can only be one concurrent upload! - */ - private UploadFileOperation mCurrentUpload = null; - - private NotificationManager mNotificationManager; - private NotificationCompat.Builder mNotificationBuilder; - private int mLastPercent; - - public static String getUploadsAddedMessage() { - return FileUploader.class.getName() + UPLOADS_ADDED_MESSAGE; - } - - public static String getUploadStartMessage() { - return FileUploader.class.getName() + UPLOAD_START_MESSAGE; - } - - public static String getUploadFinishMessage() { - return FileUploader.class.getName() + UPLOAD_FINISH_MESSAGE; - } - - @Override - public void onRenameUpload() { - mUploadsStorageManager.updateDatabaseUploadStart(mCurrentUpload); - sendBroadcastUploadStarted(mCurrentUpload); - } - - /** - * Service initialization - */ - @Override - public void onCreate() { - super.onCreate(); - Timber.d("Creating service"); - - mNotificationBuilder = NotificationUtils.newNotificationBuilder(this, UPLOAD_NOTIFICATION_CHANNEL_ID); - - HandlerThread thread = new HandlerThread("FileUploaderThread", - Process.THREAD_PRIORITY_BACKGROUND); - thread.start(); - - mUploadsStorageManager = new UploadsStorageManager(getContentResolver()); - - int failedCounter = mUploadsStorageManager.failInProgressUploads( - UploadResult.SERVICE_INTERRUPTED // Add UploadResult.KILLED? - ); - if (failedCounter > 0) { - resurrection(); - } - - // add AccountsUpdatedListener - AccountManager am = AccountManager.get(getApplicationContext()); - am.addOnAccountsUpdatedListener(this, null, false); - - // create manager for local broadcasts - mLocalBroadcastManager = LocalBroadcastManager.getInstance(this); - } - - /** - * Service clean-up when restarted after being killed - */ - private void resurrection() { - // remove stucked notification - getNotificationManager().cancel(R.string.uploader_upload_in_progress_ticker); - } - - /** - * Service clean up - */ - @Override - public void onDestroy() { - Timber.v("Destroying service"); - mNotificationManager = null; - - // remove AccountsUpdatedListener - AccountManager am = AccountManager.get(getApplicationContext()); - am.removeOnAccountsUpdatedListener(this); - - super.onDestroy(); - } - - @Nullable - @Override - public IBinder onBind(Intent intent) { - return null; - } - - /** - * Entry point to add one or several files to the queue of uploads. - *

- * New uploads 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) { - Timber.d("Starting command with id %s", startId); - - int createdBy = intent.getIntExtra(KEY_CREATED_BY, UploadFileOperation.CREATED_BY_USER); - boolean isCameraUploadFile = - createdBy == CREATED_AS_CAMERA_UPLOAD_PICTURE || createdBy == CREATED_AS_CAMERA_UPLOAD_VIDEO; - boolean isAvailableOfflineFile = intent.getBooleanExtra(KEY_IS_AVAILABLE_OFFLINE_FILE, false); - boolean isRequestedFromWifiBackEvent = intent.getBooleanExtra( - KEY_REQUESTED_FROM_WIFI_BACK_EVENT, false - ); - - if ((isCameraUploadFile || isAvailableOfflineFile || isRequestedFromWifiBackEvent) && - Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - Timber.d("Starting FileUploader service in foreground"); - - if (isCameraUploadFile) { - mNotificationBuilder.setContentTitle(getString(R.string.uploader_upload_camera_upload_files)); - } else if (isAvailableOfflineFile) { - mNotificationBuilder.setContentTitle(getString(R.string.uploader_upload_available_offline_files)); - } else if (isRequestedFromWifiBackEvent) { - mNotificationBuilder.setContentTitle(getString(R.string.uploader_upload_requested_from_wifi_files)); - } - - /* - * After calling startForegroundService method for camera uploads or - * available offline, we have to call this within five seconds after the service is created to avoid - * an error - */ - startForeground(141, mNotificationBuilder.build()); - } - - boolean retry = intent.getBooleanExtra(KEY_RETRY, false); - AbstractList requestedUploads = new Vector<>(); - - if (!intent.hasExtra(KEY_ACCOUNT)) { - Timber.e("Not enough information provided in intent"); - return Service.START_NOT_STICKY; - } - - Account account = intent.getParcelableExtra(KEY_ACCOUNT); - Timber.d("Account to upload the file to: %s", account); - if (account == null || !AccountUtils.exists(account.name, getApplicationContext())) { - return Service.START_NOT_STICKY; - } - - if (!retry) { - if (!(intent.hasExtra(KEY_LOCAL_FILE) || intent.hasExtra(KEY_FILE))) { - Timber.e("Not enough information provided in intent"); - return Service.START_NOT_STICKY; - } - - String[] localPaths = null, remotePaths = null, mimeTypes = null; - OCFile[] files = null; - - if (intent.hasExtra(KEY_FILE)) { - Parcelable[] files_temp = intent.getParcelableArrayExtra(KEY_FILE); - files = new OCFile[files_temp.length]; - System.arraycopy(files_temp, 0, files, 0, files_temp.length); - - } else { - localPaths = intent.getStringArrayExtra(KEY_LOCAL_FILE); - remotePaths = intent.getStringArrayExtra(KEY_REMOTE_FILE); - mimeTypes = intent.getStringArrayExtra(KEY_MIME_TYPE); - } - - boolean forceOverwrite = intent.getBooleanExtra(KEY_FORCE_OVERWRITE, false); - int localAction = intent.getIntExtra(KEY_LOCAL_BEHAVIOUR, LEGACY_LOCAL_BEHAVIOUR_FORGET); - - if (intent.hasExtra(KEY_FILE) && files == null) { - Timber.e("Incorrect array for OCFiles provided in upload intent"); - return Service.START_NOT_STICKY; - - } else if (!intent.hasExtra(KEY_FILE)) { - if (localPaths == null) { - Timber.e("Incorrect array for local paths provided in upload intent"); - return Service.START_NOT_STICKY; - } - if (remotePaths == null) { - Timber.e("Incorrect array for remote paths provided in upload intent"); - return Service.START_NOT_STICKY; - } - if (localPaths.length != remotePaths.length) { - Timber.e("Different number of remote paths and local paths!"); - return Service.START_NOT_STICKY; - } - - files = new OCFile[localPaths.length]; - for (int i = 0; i < localPaths.length; i++) { - files[i] = UploadFileOperation.obtainNewOCFileToUpload( - remotePaths[i], - localPaths[i], - ((mimeTypes != null) ? mimeTypes[i] : null), - getApplicationContext() - ); - if (files[i] == null) { - Timber.e("obtainNewOCFileToUpload() returned null for remotePaths[i]:" + remotePaths[i] - + " and localPaths[i]:" + localPaths[i]); - return Service.START_NOT_STICKY; - } - } - } - // at this point variable "OCFile[] files" is loaded correctly. - - String uploadKey; - UploadFileOperation newUploadFileOperation; - try { - FileDataStorageManager storageManager = new FileDataStorageManager( - getApplicationContext(), - account, - getContentResolver() - ); - OCCapability capabilitiesForAccount = storageManager.getCapability(account.name); - boolean isChunkingAllowed = - capabilitiesForAccount != null && capabilitiesForAccount.isChunkingAllowed(); - Timber.d("Chunking is allowed: %s", isChunkingAllowed); - for (OCFile ocFile : files) { - - OCUpload ocUpload = new OCUpload(ocFile, account); - ocUpload.setFileSize(ocFile.getLength()); - ocUpload.setForceOverwrite(forceOverwrite); - ocUpload.setCreatedBy(createdBy); - ocUpload.setLocalAction(localAction); - /*ocUpload.setUseWifiOnly(isUseWifiOnly); - ocUpload.setWhileChargingOnly(isWhileChargingOnly);*/ - ocUpload.setUploadStatus(UploadStatus.UPLOAD_IN_PROGRESS); - - if (new File(ocFile.getStoragePath()).length() > - ChunkedUploadFromFileSystemOperation.CHUNK_SIZE && isChunkingAllowed) { - ocUpload.setTransferId( - SecurityUtils.stringToMD5Hash(ocFile.getRemotePath()) + System.currentTimeMillis()); - newUploadFileOperation = new ChunkedUploadFileOperation( - account, - ocFile, - ocUpload, - forceOverwrite, - localAction, - this - ); - } else { - newUploadFileOperation = new UploadFileOperation( - account, - ocFile, - ocUpload, - forceOverwrite, - localAction, - this - ); - } - - newUploadFileOperation.setCreatedBy(createdBy); - - newUploadFileOperation.addRenameUploadListener(this); - - Pair putResult = mPendingUploads.putIfAbsent( - account.name, - ocFile.getRemotePath(), - newUploadFileOperation - ); - if (putResult != null) { - uploadKey = putResult.first; - requestedUploads.add(uploadKey); - - // Save upload in database - long id = mUploadsStorageManager.storeUpload(ocUpload); - newUploadFileOperation.setOCUploadId(id); - } - } - - } catch (IllegalArgumentException e) { - Timber.e(e, "Not enough information provided in intent: %s", e.getMessage()); - return START_NOT_STICKY; - - } catch (IllegalStateException e) { - Timber.e(e, "Bad information provided in intent: %s", e.getMessage()); - return START_NOT_STICKY; - - } catch (Exception e) { - Timber.e(e, "Unexpected exception while processing upload intent"); - return START_NOT_STICKY; - - } - // *** TODO REWRITE: block inserted to request A retry; too many code copied, no control exception ***/ - } else { - if (!intent.hasExtra(KEY_ACCOUNT) || !intent.hasExtra(KEY_RETRY_UPLOAD)) { - Timber.e("Not enough information provided in intent: no KEY_RETRY_UPLOAD_KEY"); - return START_NOT_STICKY; - } - OCUpload upload = intent.getParcelableExtra(KEY_RETRY_UPLOAD); - - UploadFileOperation newUploadFileOperation; - - if (upload.getFileSize() > ChunkedUploadFromFileSystemOperation.CHUNK_SIZE) { - upload.setTransferId( - SecurityUtils.stringToMD5Hash(upload.getRemotePath()) + System.currentTimeMillis()); - newUploadFileOperation = new ChunkedUploadFileOperation( - account, - null, - upload, - upload.isForceOverwrite(), - upload.getLocalAction(), - this - ); - } else { - newUploadFileOperation = new UploadFileOperation( - account, - null, - upload, - upload.isForceOverwrite(), - upload.getLocalAction(), - this - ); - } - - newUploadFileOperation.addRenameUploadListener(this); - - Pair putResult = mPendingUploads.putIfAbsent( - account.name, - upload.getRemotePath(), - newUploadFileOperation - ); - if (putResult != null) { - String uploadKey = putResult.first; - requestedUploads.add(uploadKey); - - // Update upload in database - upload.setUploadStatus(UploadStatus.UPLOAD_IN_PROGRESS); - mUploadsStorageManager.updateUpload(upload); - } - } - // *** TODO REWRITE END ***/ - - if (requestedUploads.size() > 0) { - sendBroadcastUploadsAdded(); - } - return Service.START_NOT_STICKY; - } - - @Override - public void onAccountsUpdated(Account[] accounts) { - // Review current upload, and cancel it if its account doesn't exist - if (mCurrentUpload != null && - !AccountUtils.exists(mCurrentUpload.getAccount().name, getApplicationContext())) { - mCurrentUpload.cancel(); - } - // The rest of uploads are cancelled when they try to start - } - - /** - * Core upload method: sends the file(s) to upload - * - * @param uploadKey Key to access the upload to perform, contained in mPendingUploads - */ - public void uploadFile(String uploadKey) { - - mCurrentUpload = mPendingUploads.get(uploadKey); - - if (mCurrentUpload != null) { - - /// Check account existence - if (!AccountUtils.exists(mCurrentUpload.getAccount().name, this)) { - Timber.w("Account " + mCurrentUpload.getAccount().name + " does not exist anymore -> cancelling all " + - "its uploads"); - cancelUploadsForAccount(mCurrentUpload.getAccount()); - return; - } - - /// OK, let's upload - mUploadsStorageManager.updateDatabaseUploadStart(mCurrentUpload); - - notifyUploadStart(mCurrentUpload); - - sendBroadcastUploadStarted(mCurrentUpload); - - RemoteOperationResult uploadResult = null; - - try { - /// prepare client object to send the request to the ownCloud server - if (mCurrentAccount == null || - !mCurrentAccount.equals(mCurrentUpload.getAccount())) { - mCurrentAccount = mCurrentUpload.getAccount(); - mStorageManager = new FileDataStorageManager( - getApplicationContext(), - mCurrentAccount, - getContentResolver() - ); - } // else, reuse storage manager from previous operation - - // always get client from client manager to get fresh credentials in case of update - OwnCloudAccount ocAccount = new OwnCloudAccount( - mCurrentAccount, - this - ); - mUploadClient = SingleSessionManager.getDefaultSingleton(). - getClientFor(ocAccount, this); - - /// perform the upload - uploadResult = mCurrentUpload.execute(mUploadClient, mStorageManager); - - } catch (Exception e) { - Timber.e(e, "Error uploading"); - uploadResult = new RemoteOperationResult(e); - - } finally { - Pair removeResult; - if (mCurrentUpload.wasRenamed()) { - removeResult = mPendingUploads.removePayload( - mCurrentAccount.name, - mCurrentUpload.getOldFile().getRemotePath() - ); - /* TODO: grant that name is also updated for mCurrentUpload.getOCUploadId */ - - } else { - removeResult = mPendingUploads.removePayload( - mCurrentAccount.name, - mCurrentUpload.getRemotePath() - ); - } - - if (uploadResult != null && !uploadResult.isSuccess()) { - - } else { - String stringToLog = String.format( - "Success OR fail without exception for %1s in %2s", - mCurrentUpload.getRemotePath(), - mCurrentAccount.name - ); - Timber.v("%s", stringToLog); - } - - if (uploadResult != null) { - mUploadsStorageManager.updateDatabaseUploadResult(uploadResult, mCurrentUpload); - /// notify result - notifyUploadResult(mCurrentUpload, uploadResult); - } - - sendBroadcastUploadFinished(mCurrentUpload, uploadResult, removeResult.second); - } - } - } - - private void removeChunksFolder(long ocUploadId) { - RemoveRemoteChunksFolderOperation remoteChunksFolderOperation = new RemoveRemoteChunksFolderOperation( - String.valueOf(ocUploadId) - ); - - RemoteOperationResult result = remoteChunksFolderOperation.execute(mUploadClient); - - if (!result.isSuccess()) { - Timber.e("Error deleting chunks folder after cancelling chunked upload"); - } - } - - /** - * Creates a status notification to show the upload progress - * - * @param upload Upload operation starting. - */ - private void notifyUploadStart(UploadFileOperation upload) { - Timber.d("Notifying upload start"); - - // / create status notification with a progress bar - mLastPercent = 0; - mNotificationBuilder - .setOngoing(true) - .setTicker(getString(R.string.uploader_upload_in_progress_ticker)) - .setContentTitle(getString(R.string.uploader_upload_in_progress_ticker)) - .setProgress(100, 0, false) - .setContentText( - String.format(getString(R.string.uploader_upload_in_progress_content), 0, upload.getFileName())) - .setWhen(System.currentTimeMillis()); - - /// includes a pending intent in the notification showing the details - Intent showUploadListIntent = new Intent(this, UploadListActivity.class); - showUploadListIntent.putExtra(FileActivity.EXTRA_FILE, upload.getFile()); - showUploadListIntent.putExtra(FileActivity.EXTRA_ACCOUNT, upload.getAccount()); - showUploadListIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - mNotificationBuilder.setContentIntent(PendingIntent.getActivity(this, (int) System.currentTimeMillis(), - showUploadListIntent, NotificationUtils.INSTANCE.getPendingIntentFlags())); - - if (!upload.isCameraUploadsPicture() && !upload.isCameraUploadsVideo()) { - getNotificationManager().notify(R.string.uploader_upload_in_progress_ticker, - mNotificationBuilder.build()); - }// else wait until the upload really start (onTransferProgress is called), so that if it's discarded - // due to lack of Wifi, no notification is shown - } - - /** - * 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, false); - String fileName = filePath.substring(filePath.lastIndexOf(File.separator) + 1); - String text = String.format(getString(R.string.uploader_upload_in_progress_content), percent, fileName); - mNotificationBuilder.setContentText(text); - getNotificationManager().notify(R.string.uploader_upload_in_progress_ticker, mNotificationBuilder.build()); - } - mLastPercent = percent; - } - - /** - * Updates the status notification with the result of an upload operation. - * - * @param uploadResult Result of the upload operation. - * @param upload Finished upload operation - */ - private void notifyUploadResult(UploadFileOperation upload, - RemoteOperationResult uploadResult) { - Timber.d("NotifyUploadResult with resultCode: %s", uploadResult.getCode()); - // / cancelled operation or success -> silent removal of progress notification - getNotificationManager().cancel(R.string.uploader_upload_in_progress_ticker); - - if (uploadResult.isCancelled() && upload instanceof ChunkedUploadFileOperation) { - removeChunksFolder(upload.getOCUploadId()); - } - - if (!uploadResult.isCancelled() && - !uploadResult.getCode().equals(ResultCode.DELAYED_FOR_WIFI)) { - - // Show the result: success or fail notification - int tickerId = (uploadResult.isSuccess()) ? R.string.uploader_upload_succeeded_ticker : - R.string.uploader_upload_failed_ticker; - - String content; - - // check credentials error - boolean needsToUpdateCredentials = (ResultCode.UNAUTHORIZED.equals(uploadResult.getCode())); - tickerId = (needsToUpdateCredentials) ? - R.string.uploader_upload_failed_credentials_error : tickerId; - - mNotificationBuilder - .setTicker(getString(tickerId)) - .setContentTitle(getString(tickerId)) - .setAutoCancel(true) - .setOngoing(false) - .setProgress(0, 0, false); - - content = ErrorMessageAdapter.Companion.getResultMessage( - uploadResult, upload, getResources() - ); - - if (needsToUpdateCredentials) { - // let the user update credentials with one click - PendingIntent pendingIntentToRefreshCredentials = - NotificationUtils.INSTANCE.composePendingIntentToRefreshCredentials(this, upload.getAccount()); - - mNotificationBuilder.setContentIntent(pendingIntentToRefreshCredentials); - - } else { - mNotificationBuilder.setContentText(content); - } - - if (!uploadResult.isSuccess() && !needsToUpdateCredentials) { - //in case of failure, do not show details file view (because there is no file!) - Intent showUploadListIntent = new Intent(this, UploadListActivity.class); - showUploadListIntent.putExtra(FileActivity.EXTRA_FILE, upload.getFile()); - showUploadListIntent.putExtra(FileActivity.EXTRA_ACCOUNT, upload.getAccount()); - showUploadListIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); - mNotificationBuilder.setContentIntent(PendingIntent.getActivity(this, (int) System.currentTimeMillis(), - showUploadListIntent, NotificationUtils.INSTANCE.getPendingIntentFlags())); - } - - mNotificationBuilder.setContentText(content); - - getNotificationManager().notify(tickerId, mNotificationBuilder.build()); - - if (uploadResult.isSuccess()) { - mPendingUploads.remove(upload.getAccount().name, upload.getFile().getRemotePath()); - // remove success notification, with a delay of 2 seconds - NotificationUtils.cancelWithDelay( - mNotificationManager, - R.string.uploader_upload_succeeded_ticker, - 2000); - - } - } - } - - /** - * Sends a broadcast in order to the interested activities can update their - * view - *

- * TODO - no more broadcasts, replace with a callback to subscribed listeners - */ - private void sendBroadcastUploadsAdded() { - Intent start = new Intent(getUploadsAddedMessage()); - // nothing else needed right now - mLocalBroadcastManager.sendBroadcast(start); - } - - /** - * Sends a broadcast in order to the interested activities can update their - * view - *

- * TODO - no more broadcasts, replace with a callback to subscribed listeners - * - * @param upload Finished upload operation - */ - private void sendBroadcastUploadStarted( - UploadFileOperation upload) { - - Intent start = new Intent(getUploadStartMessage()); - start.putExtra(Extras.EXTRA_REMOTE_PATH, upload.getRemotePath()); // real remote - start.putExtra(Extras.EXTRA_OLD_FILE_PATH, upload.getOriginalStoragePath()); - start.putExtra(Extras.EXTRA_ACCOUNT_NAME, upload.getAccount().name); - - mLocalBroadcastManager.sendBroadcast(start); - } - - /** - * Sends a broadcast in order to the interested activities can update their - * view - *

- * TODO - no more broadcasts, replace with a callback to subscribed listeners - * - * @param upload Finished upload operation - * @param uploadResult Result of the upload operation - * @param unlinkedFromRemotePath Path in the uploads tree where the upload was unlinked from - */ - private void sendBroadcastUploadFinished( - UploadFileOperation upload, - RemoteOperationResult uploadResult, - String unlinkedFromRemotePath) { - - Intent end = new Intent(getUploadFinishMessage()); - end.putExtra(Extras.EXTRA_REMOTE_PATH, upload.getRemotePath()); // real remote - // path, after - // possible - // automatic - // renaming - if (upload.wasRenamed()) { - end.putExtra(Extras.EXTRA_OLD_REMOTE_PATH, upload.getOldFile().getRemotePath()); - } - end.putExtra(Extras.EXTRA_OLD_FILE_PATH, upload.getOriginalStoragePath()); - end.putExtra(Extras.EXTRA_ACCOUNT_NAME, upload.getAccount().name); - end.putExtra(Extras.EXTRA_UPLOAD_RESULT, uploadResult.isSuccess()); - if (unlinkedFromRemotePath != null) { - end.putExtra(Extras.EXTRA_LINKED_TO_PATH, unlinkedFromRemotePath); - } - - mLocalBroadcastManager.sendBroadcast(end); - } - - /** - * Remove and 'forgets' pending uploads of an account. - * - * @param account Account which uploads will be cancelled - */ - private void cancelUploadsForAccount(Account account) { - mPendingUploads.remove(account.name); - mUploadsStorageManager.removeUploads(account.name); - } - - private NotificationManager getNotificationManager() { - if (mNotificationManager == null) { - mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); - } - return mNotificationManager; - } -} diff --git a/owncloudApp/src/main/java/com/owncloud/android/operations/ChunkedUploadFileOperation.java b/owncloudApp/src/main/java/com/owncloud/android/operations/ChunkedUploadFileOperation.java deleted file mode 100644 index 338f0b49eb5..00000000000 --- a/owncloudApp/src/main/java/com/owncloud/android/operations/ChunkedUploadFileOperation.java +++ /dev/null @@ -1,110 +0,0 @@ -/** - * ownCloud Android client application - * - * @author David González Verdugo - * Copyright (C) 2020 ownCloud GmbH. - *

- * 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.operations; - -import android.accounts.Account; -import android.content.Context; - -import com.owncloud.android.datamodel.OCUpload; -import com.owncloud.android.domain.files.model.OCFile; -import com.owncloud.android.lib.common.OwnCloudClient; -import com.owncloud.android.lib.common.network.OnDatatransferProgressListener; -import com.owncloud.android.lib.common.operations.OperationCancelledException; -import com.owncloud.android.lib.common.operations.RemoteOperationResult; -import com.owncloud.android.lib.resources.files.FileUtils; -import com.owncloud.android.lib.resources.files.chunks.ChunkedUploadFromFileSystemOperation; -import com.owncloud.android.lib.resources.files.services.implementation.OCChunkService; -import com.owncloud.android.operations.common.SyncOperation; - -import java.io.File; -import java.util.Iterator; - -public class ChunkedUploadFileOperation extends UploadFileOperation { - - private String mTransferId; - - public ChunkedUploadFileOperation(Account account, OCFile file, OCUpload upload, boolean forceOverwrite, - int localBehaviour, Context context) { - super(account, file, upload, forceOverwrite, localBehaviour, context); - mTransferId = upload.getTransferId(); - } - - @Override - protected RemoteOperationResult uploadRemoteFile(OwnCloudClient client, File temporalFile, File originalFile, - String expectedPath, File expectedFile, String timeStamp) { - try { - RemoteOperationResult result; - - // Step 1, create folder where we put the uploaded file chunks - result = createChunksFolder(String.valueOf(mTransferId)); - - if (!result.isSuccess()) { - return result; - } - - // Step 2, start to upload chunks - mUploadOperation = new ChunkedUploadFromFileSystemOperation(mTransferId, mFile.getStoragePath(), - mFile.getRemotePath(), mFile.getMimeType(), timeStamp, mFile.getEtagInConflict()); - - // TODO: Chunks should be moved to a worker and in that case, we need to add the transfer - // progress listener in some way. -// Iterator listener = mDataTransferListeners.iterator(); -// while (listener.hasNext()) { -// mUploadOperation.addDataTransferProgressListener(listener.next()); -// } - - if (mCancellationRequested.get()) { - throw new OperationCancelledException(); - } - - result = mUploadOperation.execute(client); - - // File chunks not properly uploaded - if (!result.isSuccess()) { - return result; - } - - // Step 3, move remote file to final remote destination - moveChunksFileToFinalDestination(timeStamp, originalFile.length()); - - // Step 4, move local file to final local destination - moveTemporalOriginalFiles(temporalFile, originalFile, expectedPath, expectedFile); - - return result; - } catch (Exception e) { - return new RemoteOperationResult(e); - } - } - - private RemoteOperationResult createChunksFolder(String remoteChunksFolder) { - SyncOperation syncOperation = new CreateChunksFolderOperation(remoteChunksFolder); - return syncOperation.execute(getClient(), getStorageManager()); - } - - private void moveChunksFileToFinalDestination(String fileLastModifTimestamp, long fileLength) { - OCChunkService ocChunkService = new OCChunkService(getClient()); - ocChunkService.moveFile( - mTransferId + File.separator + FileUtils.FINAL_CHUNKS_FILE, - mFile.getRemotePath(), - fileLastModifTimestamp, - fileLength - ); - } -} \ No newline at end of file diff --git a/owncloudApp/src/main/java/com/owncloud/android/operations/CreateChunksFolderOperation.java b/owncloudApp/src/main/java/com/owncloud/android/operations/CreateChunksFolderOperation.java deleted file mode 100644 index 6442f873395..00000000000 --- a/owncloudApp/src/main/java/com/owncloud/android/operations/CreateChunksFolderOperation.java +++ /dev/null @@ -1,56 +0,0 @@ -/** - * ownCloud Android client application - * - * @author David González Verdugo - * Copyright (C) 2020 ownCloud GmbH. - *

- * 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.operations; - -import com.owncloud.android.lib.common.OwnCloudClient; -import com.owncloud.android.lib.common.operations.RemoteOperationResult; -import com.owncloud.android.lib.resources.files.CreateRemoteFolderOperation; -import timber.log.Timber; - -public class CreateChunksFolderOperation extends CreateFolderOperation { - - /** - * Constructor - * - * @param remotePath Path in which create the chunks folder in server - */ - public CreateChunksFolderOperation(String remotePath) { - super(remotePath, false); - } - - @Override - protected RemoteOperationResult run(OwnCloudClient client) { - CreateRemoteFolderOperation createRemoteFolderOperation = new CreateRemoteFolderOperation( - mRemotePath, - mCreateFullPath, - true - ); - - RemoteOperationResult result = createRemoteFolderOperation.execute(client); - - if (result.isSuccess()) { - Timber.w("Remote chunks folder " + mRemotePath + " was created"); - } else { - Timber.e("%s hasn't been created", mRemotePath); - } - - return result; - } -} diff --git a/owncloudApp/src/main/java/com/owncloud/android/operations/CreateFolderOperation.java b/owncloudApp/src/main/java/com/owncloud/android/operations/CreateFolderOperation.java deleted file mode 100644 index 614267cf3da..00000000000 --- a/owncloudApp/src/main/java/com/owncloud/android/operations/CreateFolderOperation.java +++ /dev/null @@ -1,118 +0,0 @@ -/** - * ownCloud Android client application - * - * @author David A. Velasco - * @author masensio - * @author David González Verdugo - * Copyright (C) 2020 ownCloud GmbH. - *

- * 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.operations; - -import com.owncloud.android.domain.files.model.OCFile; -import com.owncloud.android.lib.common.OwnCloudClient; -import com.owncloud.android.lib.common.operations.RemoteOperationResult; -import com.owncloud.android.lib.resources.files.CreateRemoteFolderOperation; -import com.owncloud.android.operations.common.SyncOperation; -import com.owncloud.android.utils.FileStorageUtils; -import timber.log.Timber; - -import java.io.File; - -/** - * Access to remote operation performing the creation of a new folder in the ownCloud server. - * Save the new folder in Database - */ -@Deprecated -// Call usecase instead of this operation. Remove as soon as possible- -public class CreateFolderOperation extends SyncOperation { - - protected String mRemotePath; - protected boolean mCreateFullPath; - - /** - * Constructor - * - * @param createFullPath 'True' means that all the ancestor folders should be created - * if don't exist yet. - */ - public CreateFolderOperation(String remotePath, boolean createFullPath) { - mRemotePath = remotePath; - mCreateFullPath = createFullPath; - } - - @Override - protected RemoteOperationResult run(OwnCloudClient client) { - CreateRemoteFolderOperation createRemoteFolderOperation = new CreateRemoteFolderOperation( - mRemotePath, - mCreateFullPath, - false - ); - RemoteOperationResult result = createRemoteFolderOperation.execute(client); - - if (result.isSuccess()) { - OCFile newDir = saveFolderInDB(); - String localPath = FileStorageUtils.getDefaultSavePathFor( - getStorageManager().getAccount().name, newDir - ); - File localFile = new File(localPath); - boolean created = localFile.mkdirs(); - if (!created) { - Timber.w("Local folder " + localPath + " was not fully created"); - } - } else { - Timber.e("%s hasn't been created", mRemotePath); - } - - return result; - } - - /** - * Save new directory in local database - * - * @return Instance of {@link OCFile} just created - */ - private OCFile saveFolderInDB() { - OCFile newDir = null; - if (mCreateFullPath && getStorageManager(). - getFileByPath(FileStorageUtils.getParentPath(mRemotePath)) == null) {// When parent - // of remote path - // is not created - String[] subFolders = mRemotePath.split("/"); - String composedRemotePath = "/"; - - // Create all the ancestors - for (String subFolder : subFolders) { - if (!subFolder.isEmpty()) { - composedRemotePath = composedRemotePath + subFolder + "/"; - mRemotePath = composedRemotePath; - newDir = saveFolderInDB(); - } - } - } else { // Create directory on DB - // FIXME: 13/10/2020 : New_arch: Migration -// newDir = new OCFile(mRemotePath); -// newDir.setMimetype(MimeTypeConstantsKt.MIME_DIR); -// long parentId = getStorageManager(). -// getFileByPath(FileStorageUtils.getParentPath(mRemotePath)).getFileId(); -// newDir.setParentId(parentId); -// newDir.setModificationTimestamp(System.currentTimeMillis()); -// getStorageManager().saveFile(newDir); - - Timber.d("Create directory " + mRemotePath + " in Database"); - } - return newDir; - } -} diff --git a/owncloudApp/src/main/java/com/owncloud/android/operations/SynchronizeFileOperation.java b/owncloudApp/src/main/java/com/owncloud/android/operations/SynchronizeFileOperation.java index 4c03255f3e2..ad4f1194088 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/operations/SynchronizeFileOperation.java +++ b/owncloudApp/src/main/java/com/owncloud/android/operations/SynchronizeFileOperation.java @@ -191,7 +191,6 @@ protected RemoteOperationResult run(OwnCloudClient client) { if (mPushOnly) { // prevent accidental override of unnoticed change in server; // dirty trick, more refactoring is needed, but not today; - // works due to {@link UploadFileOperation#L364,L367} mLocalFile.setEtagInConflict(mLocalFile.getEtag()); } requestForUpload(mLocalFile); @@ -202,7 +201,7 @@ protected RemoteOperationResult run(OwnCloudClient client) { requestForDownload(mLocalFile); // mLocalFile, not mServerFile; we want to keep the value of // available-offline property - // the update of local data will be done later by the FileUploader + // the update of local data will be done later by the usecase // service when the upload finishes result = new RemoteOperationResult<>(ResultCode.OK); @@ -224,7 +223,7 @@ protected RemoteOperationResult run(OwnCloudClient client) { } /** - * Requests for an upload to the FileUploader service + * Requests for an upload * * @param file OCFile object representing the file to upload */ @@ -236,6 +235,7 @@ private void requestForUpload(OCFile file) { file.getStoragePath(), file.getParentRemotePath() ); + uploadFileInConflictUseCase.execute(params); mTransferWasRequested = true; } diff --git a/owncloudApp/src/main/java/com/owncloud/android/operations/UploadFileOperation.java b/owncloudApp/src/main/java/com/owncloud/android/operations/UploadFileOperation.java deleted file mode 100644 index 7f184b79045..00000000000 --- a/owncloudApp/src/main/java/com/owncloud/android/operations/UploadFileOperation.java +++ /dev/null @@ -1,720 +0,0 @@ -/** - * ownCloud Android client application - * - * @author David A. Velasco - * @author David González Verdugo - * Copyright (C) 2020 ownCloud GmbH. - *

- * 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.operations; - -import android.accounts.Account; -import android.content.Context; -import android.net.Uri; - -import com.owncloud.android.datamodel.OCUpload; -import com.owncloud.android.domain.files.model.OCFile; -import com.owncloud.android.files.services.FileUploader; -import com.owncloud.android.lib.common.OwnCloudClient; -import com.owncloud.android.lib.common.http.HttpConstants; -import com.owncloud.android.lib.common.operations.OperationCancelledException; -import com.owncloud.android.lib.common.operations.RemoteOperation; -import com.owncloud.android.lib.common.operations.RemoteOperationResult; -import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode; -import com.owncloud.android.lib.resources.files.CheckPathExistenceRemoteOperation; -import com.owncloud.android.lib.resources.files.ReadRemoteFileOperation; -import com.owncloud.android.lib.resources.files.RemoteFile; -import com.owncloud.android.lib.resources.files.UploadFileFromFileSystemOperation; -import com.owncloud.android.operations.common.SyncOperation; -import com.owncloud.android.utils.FileStorageUtils; -import com.owncloud.android.utils.MimetypeIconUtil; -import com.owncloud.android.utils.RemoteFileUtils; -import timber.log.Timber; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.nio.channels.FileChannel; -import java.util.HashSet; -import java.util.Set; -import java.util.concurrent.atomic.AtomicBoolean; - -import static com.owncloud.android.domain.files.model.MimeTypeConstantsKt.MIME_DIR; -import static com.owncloud.android.utils.UriUtils.URI_CONTENT_SCHEME; - -/** - * Operation performing the update in the ownCloud server - * of a file that was modified locally. - */ -public class UploadFileOperation extends SyncOperation { - - public static final int CREATED_BY_USER = 0; - public static final int CREATED_AS_CAMERA_UPLOAD_PICTURE = 1; - public static final int CREATED_AS_CAMERA_UPLOAD_VIDEO = 2; - protected final AtomicBoolean mCancellationRequested = new AtomicBoolean(false); - private final AtomicBoolean mUploadStarted = new AtomicBoolean(false); - private Account mAccount; - /** - * OCFile which is to be uploaded. - */ - protected OCFile mFile; - /** - * Original OCFile which is to be uploaded in case file had to be renamed - * (if forceOverwrite==false and remote file already exists). - */ - private OCFile mOldFile; - private String mRemotePath; - - // LEGACY - TO BE REMOVED - @Deprecated - private boolean mRemoteFolderToBeCreated; - private boolean mForceOverwrite; - private int mLocalBehaviour; - private int mCreatedBy; - private boolean mWasRenamed = false; - private long mOCUploadId; - /** - * Local path to file which is to be uploaded (before any possible renaming or moving). - */ - private String mOriginalStoragePath; - private OnRenameListener mRenameUploadListener; - private Context mContext; - protected UploadFileFromFileSystemOperation mUploadOperation; - - public UploadFileOperation(Account account, - OCFile file, - OCUpload upload, - boolean forceOverwrite, - int localBehaviour, - Context context - ) { - if (account == null) { - throw new IllegalArgumentException("Illegal NULL account in UploadFileOperation " + - "creation"); - } - if (upload == null) { - throw new IllegalArgumentException("Illegal NULL file in UploadFileOperation creation"); - } - if (upload.getLocalPath() == null || upload.getLocalPath().length() <= 0) { - throw new IllegalArgumentException( - "Illegal file in UploadFileOperation; storage path invalid: " - + upload.getLocalPath()); - } - - mAccount = account; - if (file == null) { - mFile = obtainNewOCFileToUpload( - upload.getRemotePath(), - upload.getLocalPath(), - upload.getMimeType(), - context - ); - } else { - mFile = file; - } - mRemotePath = upload.getRemotePath(); - mForceOverwrite = forceOverwrite; - mLocalBehaviour = localBehaviour; - mOriginalStoragePath = mFile.getStoragePath(); - mContext = context; - mOCUploadId = upload.getUploadId(); - mCreatedBy = upload.getCreatedBy(); - mRemoteFolderToBeCreated = upload.createsRemoteFolder(); - - } - - public static OCFile obtainNewOCFileToUpload(String remotePath, String localPath, String mimeType, - Context context) { - // FIXME: 13/10/2020 : New_arch: Upload - // MIME type - if (mimeType == null || mimeType.length() <= 0) { - mimeType = MimetypeIconUtil.getBestMimeTypeByFilename(localPath); - } - - OCFile newFile = new OCFile(remotePath, mimeType, (long) 0, ""); - - newFile.setStoragePath(localPath); - newFile.setLastSyncDateForProperties(0L); - newFile.setLastSyncDateForData(0L); - - // size - if (localPath != null && localPath.length() > 0) { - File localFile = new File(localPath); - newFile.setLength(localFile.length()); - newFile.setLastSyncDateForData(localFile.lastModified()); - } // don't worry about not assigning size, the problems with localPath - // are checked when the UploadFileOperation instance is created - - newFile.setMimeType(mimeType); - - return newFile; - } - - public Account getAccount() { - return mAccount; - } - - public String getFileName() { - return (mFile != null) ? mFile.getFileName() : null; - } - - public OCFile getFile() { - return mFile; - } - - /** - * If remote file was renamed, return original OCFile which was uploaded. Is - * null is file was not renamed. - */ - public OCFile getOldFile() { - return mOldFile; - } - - public String getOriginalStoragePath() { - return mOriginalStoragePath; - } - - public String getStoragePath() { - return mFile.getStoragePath(); - } - - public String getRemotePath() { - return mFile.getRemotePath(); - } - - public String getMimeType() { - return mFile.getMimeType(); - } - - public int getLocalBehaviour() { - return mLocalBehaviour; - } - - public void setRemoteFolderToBeCreated() { - mRemoteFolderToBeCreated = true; - } - - public boolean wasRenamed() { - return mWasRenamed; - } - - public int getCreatedBy() { - return mCreatedBy; - } - - public void setCreatedBy(int createdBy) { - mCreatedBy = createdBy; - if (createdBy < CREATED_BY_USER || CREATED_AS_CAMERA_UPLOAD_VIDEO < createdBy) { - mCreatedBy = CREATED_BY_USER; - } - } - - public boolean isCameraUploadsPicture() { - return mCreatedBy == CREATED_AS_CAMERA_UPLOAD_PICTURE; - } - - public boolean isCameraUploadsVideo() { - return mCreatedBy == CREATED_AS_CAMERA_UPLOAD_VIDEO; - } - - public long getOCUploadId() { - return mOCUploadId; - } - - public void setOCUploadId(long id) { - mOCUploadId = id; - } - - public void addRenameUploadListener(OnRenameListener listener) { - mRenameUploadListener = listener; - } - - @Override - protected RemoteOperationResult run(OwnCloudClient client) { - mCancellationRequested.set(false); - mUploadStarted.set(true); - RemoteOperationResult result = null; - File temporalFile = null, originalFile = new File(mOriginalStoragePath), expectedFile = null; - - try { - - /// check if the file continues existing before schedule the operation - if (!originalFile.exists()) { - Timber.d("%s not exists anymore", mOriginalStoragePath); - return new RemoteOperationResult(ResultCode.LOCAL_FILE_NOT_FOUND); - } - - /// check the existence of the parent folder for the file to upload - String remoteParentPath = new File(getRemotePath()).getParent(); - remoteParentPath = remoteParentPath.endsWith(File.separator) ? - remoteParentPath : remoteParentPath + File.separator; - result = grantFolderExistence(remoteParentPath, client); - - if (!result.isSuccess()) { - return result; - } - - /// set parent local id in uploading file - OCFile parent = getStorageManager().getFileByPath(remoteParentPath); - mFile.setParentId(parent.getId()); - - /// automatic rename of file to upload in case of name collision in server - Timber.d("Checking name collision in server"); - if (!mForceOverwrite) { - String remotePath = RemoteFileUtils.Companion.getAvailableRemotePath(client, mRemotePath); - mWasRenamed = !mRemotePath.equals(remotePath); - if (mWasRenamed) { - createNewOCFile(remotePath); - Timber.d("File renamed as %s", remotePath); - } - mRemotePath = remotePath; - mRenameUploadListener.onRenameUpload(); - } - - if (mCancellationRequested.get()) { - throw new OperationCancelledException(); - } - - String expectedPath = FileStorageUtils.getDefaultSavePathFor(mAccount.name, mFile); - expectedFile = new File(expectedPath); - - /// copy the file locally before uploading - if (mLocalBehaviour == FileUploader.LEGACY_LOCAL_BEHAVIOUR_COPY && - !mOriginalStoragePath.equals(expectedPath)) { - - String temporalPath = FileStorageUtils.getTemporalPath(mAccount.name) + mFile.getRemotePath(); - mFile.setStoragePath(temporalPath); - temporalFile = new File(temporalPath); - - result = copy(originalFile, temporalFile); - if (result != null) { - return result; - } - } - - if (mCancellationRequested.get()) { - throw new OperationCancelledException(); - } - - // Get the last modification date of the file from the file system - Long timeStampLong = originalFile.lastModified() / 1000; - String timeStamp = timeStampLong.toString(); - - // Perform the upload - result = uploadRemoteFile(client, temporalFile, originalFile, expectedPath, expectedFile, timeStamp); - - } catch (Exception e) { - result = new RemoteOperationResult(e); - - } finally { - mUploadStarted.set(false); - if (temporalFile != null && !originalFile.equals(temporalFile)) { - temporalFile.delete(); - } - if (result == null) { - result = new RemoteOperationResult(ResultCode.UNKNOWN_ERROR); - } - if (result.isSuccess()) { - Timber.i("Upload of " + mOriginalStoragePath + " to " + mRemotePath + ": " + - result.getLogMessage()); - } else { - if (result.getException() != null) { - if (result.isCancelled()) { - Timber.w("Upload of " + mOriginalStoragePath + " to " + mRemotePath + ": " + result.getLogMessage()); - } else { - Timber.e(result.getException(), "Upload of " + mOriginalStoragePath + " to " + mRemotePath + - ": " + result.getLogMessage()); - } - - } else { - Timber.e("Upload of " + mOriginalStoragePath + " to " + mRemotePath + ": " + result.getLogMessage()); - } - } - } - - if (result.isSuccess()) { - saveUploadedFile(client); - - } else if (result.getCode() == ResultCode.SYNC_CONFLICT) { - getStorageManager().saveConflict(mFile, mFile.getEtagInConflict()); - } - - return result; - } - - /** - * Upload file to server - * - * @param client - * @param temporalFile file copied locally before uploading the file - * @param originalFile local file to upload - * @param expectedPath path in which the file should be uploaded - * @param expectedFile resulting file - * @param timeStamp - * @return {@link RemoteOperationResult} representing the upload operation result - */ - protected RemoteOperationResult uploadRemoteFile(OwnCloudClient client, File temporalFile, File originalFile, - String expectedPath, File expectedFile, String timeStamp) { - RemoteOperationResult result; - - try { - mUploadOperation = new UploadFileFromFileSystemOperation(mFile.getStoragePath(), mFile.getRemotePath(), - mFile.getMimeType(), timeStamp, mFile.getEtagInConflict()); - - if (mCancellationRequested.get()) { - throw new OperationCancelledException(); - } - - result = mUploadOperation.execute(client); - - /// move local temporal file or original file to its corresponding - // location in the ownCloud local folder - if (result.isSuccess()) { - moveTemporalOriginalFiles(temporalFile, originalFile, expectedPath, expectedFile); - - } else if (result.getHttpCode() == HttpConstants.HTTP_PRECONDITION_FAILED) { - result = new RemoteOperationResult(ResultCode.SYNC_CONFLICT); - } - } catch (Exception e) { - result = new RemoteOperationResult(e); - } - - return result; - } - - /** - * Move local temporal file or original file to its corresponding location in the ownCloud local folder - * - * @param temporalFile file copied locally before uploading the file - * @param originalFile local file to upload - * @param expectedPath path in which the file should be uploaded - * @param expectedFile resulting file - * @param - */ - protected void moveTemporalOriginalFiles(File temporalFile, File originalFile, String expectedPath, - File expectedFile) - throws IOException { - if (mLocalBehaviour == FileUploader.LEGACY_LOCAL_BEHAVIOUR_FORGET) { - String temporalPath = FileStorageUtils.getTemporalPath(mAccount.name) + mFile.getRemotePath(); - if (mOriginalStoragePath.equals(temporalPath)) { - // delete local file is was pre-copied in temporary folder (see .ui.helpers.UriUploader) - temporalFile = new File(temporalPath); - temporalFile.delete(); - } - mFile.setStoragePath(""); - - } else { - mFile.setStoragePath(expectedPath); - - if (temporalFile != null) { // FileUploader.LOCAL_BEHAVIOUR_COPY - move(temporalFile, expectedFile); - } else { // FileUploader.LOCAL_BEHAVIOUR_MOVE - move(originalFile, expectedFile); - } - } - } - - /** - * Checks the existence of the folder where the current file will be uploaded both - * in the remote server and in the local database. - *

- * If the upload is set to enforce the creation of the folder, the method tries to - * create it both remote and locally. - * - * @param pathToGrant Full remote path whose existence will be granted. - * @return An {@link OCFile} instance corresponding to the folder where the file - * will be uploaded. - */ - private RemoteOperationResult grantFolderExistence(String pathToGrant, OwnCloudClient client) { - RemoteOperation checkPathExistenceOperation = new CheckPathExistenceRemoteOperation(pathToGrant, false); - RemoteOperationResult result = checkPathExistenceOperation.execute(client); - if (!result.isSuccess() && result.getCode() == ResultCode.FILE_NOT_FOUND && mRemoteFolderToBeCreated) { - SyncOperation syncOp = new CreateFolderOperation(pathToGrant, true); - result = syncOp.execute(client, getStorageManager()); - } - if (result.isSuccess()) { - OCFile parentDir = getStorageManager().getFileByPath(pathToGrant); - if (parentDir == null) { - parentDir = createLocalFolder(pathToGrant); - } - if (parentDir != null) { - result = new RemoteOperationResult(ResultCode.OK); - } else { - result = new RemoteOperationResult(ResultCode.UNKNOWN_ERROR); - } - } - return result; - } - - private OCFile createLocalFolder(String remotePath) { - String parentPath = new File(remotePath).getParent(); - parentPath = parentPath.endsWith(File.separator) ? - parentPath : parentPath + File.separator; - OCFile parent = getStorageManager().getFileByPath(parentPath); - if (parent == null) { - parent = createLocalFolder(parentPath); - } - if (parent != null) { - OCFile createdFolder = new OCFile(remotePath, MIME_DIR, parent.getId(), parent.getOwner()); - getStorageManager().saveFile(createdFolder); - return createdFolder; - } - return null; - } - - /** - * Create a new OCFile mFile with new remote path. This is required if forceOverwrite==false. - * New file is stored as mFile, original as mOldFile. - * - * @param newRemotePath new remote path - */ - private void createNewOCFile(String newRemotePath) { - // a new OCFile instance must be created for a new remote path - OCFile newFile = new OCFile(newRemotePath, mFile.getMimeType(), mFile.getParentId(), mFile.getOwner()); - newFile.setCreationTimestamp(mFile.getCreationTimestamp()); - newFile.setLength(mFile.getLength()); - newFile.setModificationTimestamp(mFile.getModificationTimestamp()); - newFile.setModifiedAtLastSyncForData(mFile.getModifiedAtLastSyncForData()); - newFile.setEtag(mFile.getEtag()); - // FIXME: 19/10/2020 : New_arch: Av.Offline - // newFile.setAvailableOfflineStatus(mFile.getAvailableOfflineStatus()); - newFile.setLastSyncDateForProperties(mFile.getLastSyncDateForProperties()); - newFile.setLastSyncDateForData(mFile.getLastSyncDateForData()); - newFile.setStoragePath(mFile.getStoragePath()); - mOldFile = mFile; - mFile = newFile; - } - - /** - * Allows to cancel the actual upload operation. If actual upload operating - * is in progress it is cancelled, if upload preparation is being performed - * upload will not take place. - */ - public void cancel() { - if (mUploadOperation == null) { - if (mUploadStarted.get()) { - Timber.d("Cancelling upload during upload preparations."); - mCancellationRequested.set(true); - } else { - Timber.e("No upload in progress. This should not happen."); - } - } else { - Timber.d("Cancelling upload during actual upload operation."); - mUploadOperation.cancel(); - } - } - - /** - * As soon as this method return true, upload can be cancel via cancel(). - */ - public boolean isUploadInProgress() { - return mUploadStarted.get(); - - } - - /** - * TODO rewrite with homogeneous fail handling, remove dependency on {@link RemoteOperationResult}, - * TODO use Exceptions instead - * - * @param sourceFile Source file to copy. - * @param targetFile Target location to copy the file. - * @return {@link RemoteOperationResult} - * @throws IOException - */ - private RemoteOperationResult copy(File sourceFile, File targetFile) throws IOException { - Timber.d("Copying local file"); - - RemoteOperationResult result = null; - - if (FileStorageUtils.getUsableSpace() < sourceFile.length()) { - result = new RemoteOperationResult(ResultCode.LOCAL_STORAGE_FULL); - return result; // error condition when the file should be copied - - } else { - Timber.d("Creating temporal folder"); - File temporalParent = targetFile.getParentFile(); - temporalParent.mkdirs(); - if (!temporalParent.isDirectory()) { - throw new IOException( - "Unexpected error: parent directory could not be created"); - } - Timber.d("Creating temporal file"); - targetFile.createNewFile(); - if (!targetFile.isFile()) { - throw new IOException( - "Unexpected error: target file could not be created"); - } - - Timber.d("Copying file contents"); - InputStream in = null; - OutputStream out = null; - - try { - if (!mOriginalStoragePath.equals(targetFile.getAbsolutePath())) { - // In case document provider schema as 'content://' - if (mOriginalStoragePath.startsWith(URI_CONTENT_SCHEME)) { - Uri uri = Uri.parse(mOriginalStoragePath); - in = mContext.getContentResolver().openInputStream(uri); - } else { - in = new FileInputStream(sourceFile); - } - out = new FileOutputStream(targetFile); - int nRead; - byte[] buf = new byte[4096]; - while (!mCancellationRequested.get() && - (nRead = in.read(buf)) > -1) { - out.write(buf, 0, nRead); - } - out.flush(); - - } // else: weird but possible situation, nothing to copy - - if (mCancellationRequested.get()) { - result = new RemoteOperationResult(new OperationCancelledException()); - return result; - } - - } catch (Exception e) { - result = new RemoteOperationResult(ResultCode.LOCAL_STORAGE_NOT_COPIED); - return result; - - } finally { - try { - if (in != null) { - in.close(); - } - } catch (Exception e) { - Timber.e(e, "Weird exception while closing input stream for " + mOriginalStoragePath + " " + - "(ignoring)"); - } - try { - if (out != null) { - out.close(); - } - } catch (Exception e) { - Timber.e(e, "Weird exception while closing output stream for " + targetFile.getAbsolutePath() + - " (ignoring)"); - } - } - } - return result; - } - - /** - * TODO rewrite with homogeneous fail handling, remove dependency on {@link RemoteOperationResult}, - * TODO use Exceptions instead - *

- * TODO refactor both this and 'copy' in a single method - * - * @param sourceFile Source file to move. - * @param targetFile Target location to move the file. - * @throws IOException - */ - private void move(File sourceFile, File targetFile) throws IOException { - - if (!targetFile.equals(sourceFile)) { - File expectedFolder = targetFile.getParentFile(); - expectedFolder.mkdirs(); - - if (expectedFolder.isDirectory()) { - if (!sourceFile.renameTo(targetFile)) { - // try to copy and then delete - targetFile.createNewFile(); - FileChannel inChannel = new FileInputStream(sourceFile).getChannel(); - FileChannel outChannel = new FileOutputStream(targetFile).getChannel(); - try { - inChannel.transferTo(0, inChannel.size(), outChannel); - sourceFile.delete(); - } catch (Exception e) { - mFile.setStoragePath(""); // forget the local file - // by now, treat this as a success; the file was uploaded - // the best option could be show a warning message - } finally { - if (inChannel != null) { - inChannel.close(); - } - if (outChannel != null) { - outChannel.close(); - } - } - } - - } else { - mFile.setStoragePath(""); - } - } - } - - /** - * Saves a OC File after a successful upload. - *

- * A PROPFIND is necessary to keep the props in the local database - * synchronized with the server, specially the modification time and Etag - * (where available) - */ - private void saveUploadedFile(OwnCloudClient client) { - OCFile file = mFile; - if (file.getFileExists()) { - file = getStorageManager().getFileById(file.getId()); - } - long syncDate = System.currentTimeMillis(); - file.setLastSyncDateForData(syncDate); - - // new PROPFIND to keep data consistent with server - // in theory, should return the same we already have - // TODO from the appropriate OC server version, get data from last PUT response headers, instead - // TODO of a new PROPFIND; the latter may fail, specially for chunked uploads - ReadRemoteFileOperation operation = new ReadRemoteFileOperation(getRemotePath()); - RemoteOperationResult result = operation.execute(client); - if (result.isSuccess()) { - updateOCFile(file, result.getData()); - file.setLastSyncDateForProperties(syncDate); - } else { - Timber.e("Error reading properties of file after successful upload; this is gonna hurt..."); - } - - if (mWasRenamed) { - OCFile oldFile = getStorageManager().getFileByPath(mOldFile.getRemotePath()); - if (oldFile != null) { - oldFile.setStoragePath(null); - getStorageManager().saveFile(oldFile); - getStorageManager().saveConflict(oldFile, null); - } - // else: it was just an automatic renaming due to a name - // coincidence; nothing else is needed, the storagePath is right - // in the instance returned by mCurrentUpload.getFile() - } - file.setNeedsToUpdateThumbnail(true); - getStorageManager().saveFile(file); - getStorageManager().saveConflict(file, null); - } - - private void updateOCFile(OCFile file, RemoteFile remoteFile) { - file.setCreationTimestamp(remoteFile.getCreationTimestamp()); - file.setLength(remoteFile.getLength()); - file.setMimeType(remoteFile.getMimeType()); - file.setModificationTimestamp(remoteFile.getModifiedTimestamp()); - file.setModifiedAtLastSyncForData(remoteFile.getModifiedTimestamp()); - file.setEtag(remoteFile.getEtag()); - file.setRemoteId(remoteFile.getRemoteId()); - } - - public interface OnRenameListener { - void onRenameUpload(); - } -} diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/UploadPathActivity.java b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/UploadPathActivity.java index 5e124281b7a..d79beefa9b4 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/UploadPathActivity.java +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/UploadPathActivity.java @@ -26,7 +26,6 @@ import com.owncloud.android.domain.files.model.OCFile; import com.owncloud.android.presentation.ui.files.filelist.MainFileListFragment; import com.owncloud.android.ui.fragment.FileFragment; -import com.owncloud.android.ui.fragment.OCFileListFragment; public class UploadPathActivity extends FolderPickerActivity implements FileFragment.ContainerActivity, OnClickListener, OnEnforceableRefreshListener { diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/adapter/FileListListAdapter.java b/owncloudApp/src/main/java/com/owncloud/android/ui/adapter/FileListListAdapter.java deleted file mode 100644 index 70a5b0df753..00000000000 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/adapter/FileListListAdapter.java +++ /dev/null @@ -1,521 +0,0 @@ -/** - * ownCloud Android client application - * - * @author Bartek Przybylski - * @author Tobias Kaminsky - * @author David A. Velasco - * @author masensio - * @author Christian Schabesberger - * @author David González Verdugo - * @author Shashvat Kedia - * @author Abel García de Prada - * @author John Kalimeris - * @author David Crespo Ríos - * Copyright (C) 2011 Bartek Przybylski - * Copyright (C) 2020 ownCloud GmbH. - *

- * 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.ui.adapter; - -import android.accounts.Account; -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.Color; -import android.util.SparseBooleanArray; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AbsListView; -import android.widget.BaseAdapter; -import android.widget.GridView; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.ListAdapter; -import android.widget.TextView; - -import androidx.constraintlayout.widget.ConstraintLayout; -import androidx.work.WorkManager; -import com.owncloud.android.R; -import com.owncloud.android.authentication.AccountUtils; -import com.owncloud.android.data.preferences.datasources.SharedPreferencesProvider; -import com.owncloud.android.data.preferences.datasources.implementation.SharedPreferencesProviderImpl; -import com.owncloud.android.datamodel.FileDataStorageManager; -import com.owncloud.android.datamodel.ThumbnailsCacheManager; -import com.owncloud.android.db.PreferenceManager; -import com.owncloud.android.domain.files.model.OCFile; -import com.owncloud.android.extensions.WorkManagerExtKt; -import com.owncloud.android.presentation.ui.settings.fragments.SettingsAdvancedFragment; -import com.owncloud.android.services.OperationsService.OperationsServiceBinder; -import com.owncloud.android.ui.activity.ComponentsGetter; -import com.owncloud.android.utils.DisplayUtils; -import com.owncloud.android.utils.FileStorageUtils; -import com.owncloud.android.utils.MimetypeIconUtil; -import com.owncloud.android.utils.PreferenceUtils; -import com.owncloud.android.utils.SortFilesUtils; - -import java.util.ArrayList; -import java.util.List; -import java.util.Vector; - -/** - * This Adapter populates a ListView with all files and folders in an ownCloud - * instance. - */ -public class FileListListAdapter extends BaseAdapter implements ListAdapter { - - private final Context mContext; - private List mImmutableFilesList = null; // List containing the database files, doesn't change with search - private List mFiles = null; // List that can be changed when using search - private final boolean mJustFolders; - private final boolean mOnlyAvailableOffline; - private final boolean mSharedByLinkFiles; - private final boolean mFolderPicker; - - private FileDataStorageManager mStorageManager; - private Account mAccount; - private final ComponentsGetter mTransferServiceGetter; - - public FileListListAdapter( - boolean justFolders, - boolean onlyAvailableOffline, - boolean sharedByLinkFiles, - boolean folderPicker, - Context context, - ComponentsGetter transferServiceGetter - ) { - mJustFolders = justFolders; - mOnlyAvailableOffline = onlyAvailableOffline; - mSharedByLinkFiles = sharedByLinkFiles; - mFolderPicker = folderPicker; - mContext = context; - mAccount = AccountUtils.getCurrentOwnCloudAccount(mContext); - - mTransferServiceGetter = transferServiceGetter; - - // Read sorting order, default to sort by name ascending - FileStorageUtils.mSortOrderFileDisp = PreferenceManager.getSortOrder(mContext, - FileStorageUtils.FILE_DISPLAY_SORT); - FileStorageUtils.mSortAscendingFileDisp = PreferenceManager.getSortAscending(mContext, - FileStorageUtils.FILE_DISPLAY_SORT); - - // initialise thumbnails cache on background thread - new ThumbnailsCacheManager.InitDiskCacheTask().execute(); - } - - @Override - public boolean areAllItemsEnabled() { - return true; - } - - @Override - public boolean isEnabled(int position) { - // Disable click for files when selecting a folder in copying and moving operations - return !mFolderPicker || mFiles.get(position).isFolder(); - } - - @Override - public int getCount() { - return mFiles != null ? mFiles.size() : 0; - } - - @Override - public Object getItem(int position) { - if (mFiles == null || mFiles.size() <= position) { - return null; - } - return mFiles.get(position); - } - - @Override - public long getItemId(int position) { - if (mFiles == null || mFiles.size() <= position) { - return 0; - } - return mFiles.get(position).getId(); - } - - @Override - public int getItemViewType(int position) { - return 0; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - - View view = convertView; - OCFile file = null; - LayoutInflater inflator = (LayoutInflater) mContext - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); - - if (mFiles != null && mFiles.size() > position) { - file = mFiles.get(position); - } - - // Find out which layout should be displayed - final ViewType viewType; - if (parent instanceof GridView) { - if (file != null && file.isImage()) { - viewType = ViewType.GRID_IMAGE; - } else { - viewType = ViewType.GRID_ITEM; - } - } else { - viewType = ViewType.LIST_ITEM; - } - - // create view only if differs, otherwise reuse - if (convertView == null || convertView.getTag() != viewType) { - switch (viewType) { - case GRID_IMAGE: - view = inflator.inflate(R.layout.grid_image, parent, false); - view.setTag(ViewType.GRID_IMAGE); - break; - case GRID_ITEM: - view = inflator.inflate(R.layout.grid_item, parent, false); - view.setTag(ViewType.GRID_ITEM); - break; - case LIST_ITEM: - view = inflator.inflate(R.layout.item_file_list, parent, false); - view.setTag(ViewType.LIST_ITEM); - // Allow or disallow touches with other visible windows - view.setFilterTouchesWhenObscured( - PreferenceUtils.shouldDisallowTouchesWithOtherVisibleWindows(mContext) - ); - break; - } - } - - if (file != null) { - final ImageView localStateView = view.findViewById(R.id.localFileIndicator); - final ImageView fileIcon = view.findViewById(R.id.thumbnail); - - fileIcon.setTag(file.getId()); - TextView fileName; - String name = file.getFileName(); - - final LinearLayout linearLayout = view.findViewById(R.id.ListItemLayout); - if (linearLayout != null) { - linearLayout.setContentDescription("LinearLayout-" + name); - - // Allow or disallow touches with other visible windows - linearLayout.setFilterTouchesWhenObscured( - PreferenceUtils.shouldDisallowTouchesWithOtherVisibleWindows(mContext) - ); - } - - switch (viewType) { - case LIST_ITEM: - ConstraintLayout constraintLayout = view.findViewById(R.id.file_list_constraint_layout); - - // Allow or disallow touches with other visible windows - constraintLayout.setFilterTouchesWhenObscured( - PreferenceUtils.shouldDisallowTouchesWithOtherVisibleWindows(mContext)); - - TextView fileSizeTV = view.findViewById(R.id.file_list_size); - TextView lastModTV = view.findViewById(R.id.file_list_last_mod); - fileSizeTV.setText(DisplayUtils.bytesToHumanReadable(file.getLength(), mContext)); - lastModTV.setText(DisplayUtils.getRelativeTimestamp(mContext, file.getModificationTimestamp())); - - if (mOnlyAvailableOffline || mSharedByLinkFiles) { - TextView filePath = view.findViewById(R.id.file_list_path); - filePath.setVisibility(View.VISIBLE); - filePath.setText(file.getRemotePath()); - } - - case GRID_ITEM: - // filename - fileName = view.findViewById(R.id.Filename); - name = file.getFileName(); - fileName.setText(name); - - case GRID_IMAGE: - // sharedIcon - ImageView sharedIconV = view.findViewById(R.id.sharedIcon); - if (file.getSharedByLink()) { - sharedIconV.setImageResource(R.drawable.ic_shared_by_link); - sharedIconV.setVisibility(View.VISIBLE); - sharedIconV.bringToFront(); - } else if (file.getSharedWithSharee() || file.isSharedWithMe()) { - sharedIconV.setImageResource(R.drawable.shared_via_users); - sharedIconV.setVisibility(View.VISIBLE); - sharedIconV.bringToFront(); - } else { - sharedIconV.setVisibility(View.GONE); - } - break; - } - - // For all Views - setIconPinAccordingToFilesLocalState(localStateView, file); - - final ImageView checkBoxV = view.findViewById(R.id.custom_checkbox); - checkBoxV.setVisibility(View.GONE); - view.setBackgroundColor(Color.WHITE); - - AbsListView parentList = (AbsListView) parent; - if (parentList.getChoiceMode() != AbsListView.CHOICE_MODE_NONE && - parentList.getCheckedItemCount() > 0 - ) { - if (parentList.isItemChecked(position)) { - view.setBackgroundColor(mContext.getResources().getColor( - R.color.selected_item_background)); - checkBoxV.setImageResource( - R.drawable.ic_checkbox_marked); - } else { - view.setBackgroundColor(Color.WHITE); - checkBoxV.setImageResource( - R.drawable.ic_checkbox_blank_outline); - } - checkBoxV.setVisibility(View.VISIBLE); - } - - if (file.isFolder()) { - // Folder - fileIcon.setImageResource( - MimetypeIconUtil.getFolderTypeIconId( - file.isSharedWithMe() || file.getSharedWithSharee(), - file.getSharedByLink())); - } else { - // Set file icon depending on its mimetype. Ask for thumbnail later. - fileIcon.setImageResource(MimetypeIconUtil.getFileTypeIconId(file.getMimeType(), file.getFileName())); - if (file.getRemoteId() != null) { - // Thumbnail in Cache? - Bitmap thumbnail = ThumbnailsCacheManager.getBitmapFromDiskCache(file.getRemoteId()); - if (thumbnail != null) { - fileIcon.setImageBitmap(thumbnail); - } - if (file.getNeedsToUpdateThumbnail()) { - // generate new Thumbnail - if (ThumbnailsCacheManager.cancelPotentialThumbnailWork(file, fileIcon)) { - final ThumbnailsCacheManager.ThumbnailGenerationTask task = - new ThumbnailsCacheManager.ThumbnailGenerationTask(fileIcon, mAccount); - final ThumbnailsCacheManager.AsyncThumbnailDrawable asyncDrawable = - new ThumbnailsCacheManager.AsyncThumbnailDrawable( - mContext.getResources(), - thumbnail, - task - ); - // If drawable is not visible, do not update it. - if (asyncDrawable.getMinimumHeight() > 0 && asyncDrawable.getMinimumWidth() > 0) { - fileIcon.setImageDrawable(asyncDrawable); - } - task.execute(file); - } - } - - if (file.getMimeType().equalsIgnoreCase("image/png")) { - fileIcon.setBackgroundColor(mContext.getResources() - .getColor(R.color.background_color)); - } - } - - } - } - return view; - } - - private void setIconPinAccordingToFilesLocalState(ImageView localStateView, OCFile file) { - // local state - localStateView.bringToFront(); - final WorkManager workManager = WorkManager.getInstance(mContext); - final OperationsServiceBinder opsBinder = - mTransferServiceGetter.getOperationsServiceBinder(); - - localStateView.setVisibility(View.INVISIBLE); // default first - - if (opsBinder != null && opsBinder.isSynchronizing(mAccount, file)) { - //syncing - localStateView.setImageResource(R.drawable.sync_pin); - localStateView.setVisibility(View.VISIBLE); - } else if (WorkManagerExtKt.isDownloadPending(workManager, mAccount, file)) { - // downloading - localStateView.setImageResource(R.drawable.sync_pin); - localStateView.setVisibility(View.VISIBLE); - } else if (WorkManagerExtKt.isUploadPending(workManager, mAccount, file)) { - // uploading - localStateView.setImageResource(R.drawable.sync_pin); - localStateView.setVisibility(View.VISIBLE); - } else if (file.getEtagInConflict() != null) { - // conflict - localStateView.setImageResource(R.drawable.error_pin); - localStateView.setVisibility(View.VISIBLE); - } else { - if (file.isAvailableLocally()) { - localStateView.setVisibility(View.VISIBLE); - localStateView.setImageResource(R.drawable.downloaded_pin); - } - - // FIXME: 13/10/2020 : New_arch: Av.Offline -// if (file.isAvailableOffline()) { -// localStateView.setVisibility(View.VISIBLE); -// localStateView.setImageResource(R.drawable.offline_available_pin); -// } - } - } - - @Override - public int getViewTypeCount() { - return 1; - } - - @Override - public boolean hasStableIds() { - return true; - } - - @Override - public boolean isEmpty() { - return (mFiles == null || mFiles.isEmpty()); - } - - /** - * Change the adapted directory for a new one - * - * @param folder New folder to adapt. Can be NULL, meaning - * "no content to adapt". - * @param updatedStorageManager Optional updated storage manager; used to replace - * mStorageManager if is different (and not NULL) - */ - public void swapDirectory(OCFile folder, FileDataStorageManager updatedStorageManager) { - if (updatedStorageManager != null && updatedStorageManager != mStorageManager) { - mStorageManager = updatedStorageManager; - mAccount = AccountUtils.getCurrentOwnCloudAccount(mContext); - } - - boolean isRootFolder = folder.equals(updatedStorageManager.getFileByPath(OCFile.ROOT_PATH)); - - if (mStorageManager != null) { - // FIXME: 13/10/2020 : New_arch: Av.Offline - if (mOnlyAvailableOffline && (isRootFolder)){ // || !folder.isAvailableOffline())) { - mImmutableFilesList = updatedStorageManager.getAvailableOfflineFilesFromCurrentAccount(); - } else if (mSharedByLinkFiles && isRootFolder) { - mImmutableFilesList = updatedStorageManager.sharedByLinkFilesFromCurrentAccount(); - } else { - mImmutableFilesList = mStorageManager.getFolderContent(folder); - } - - mImmutableFilesList = new SortFilesUtils().sortFiles(mImmutableFilesList, FileStorageUtils.mSortOrderFileDisp, - FileStorageUtils.mSortAscendingFileDisp); - - mFiles = mImmutableFilesList; - - if (mJustFolders) { - mFiles = getFolders(mFiles); - } - } else { - mFiles = null; - } - - SharedPreferencesProvider sharedPreferencesProvider = new SharedPreferencesProviderImpl(mContext); - boolean showHiddenFiles = sharedPreferencesProvider.getBoolean(SettingsAdvancedFragment.PREF_SHOW_HIDDEN_FILES, false); - - if (!showHiddenFiles) { - filterByHiddenFiles(); - } - - notifyDataSetChanged(); - } - - /** - * Filter for getting only the folders - * - * @param files Collection of files to filter - * @return Folders in the input - */ - private List getFolders(List files) { - List ret = new Vector<>(); - OCFile current; - for (int i = 0; i < files.size(); i++) { - current = files.get(i); - if (current.isFolder()) { - ret.add(current); - } - } - return ret; - } - - public void setSortOrder(Integer order, boolean ascending) { - - PreferenceManager.setSortOrder(order, mContext, FileStorageUtils.FILE_DISPLAY_SORT); - PreferenceManager.setSortAscending(ascending, mContext, FileStorageUtils.FILE_DISPLAY_SORT); - - FileStorageUtils.mSortOrderFileDisp = order; - FileStorageUtils.mSortAscendingFileDisp = ascending; - - mFiles = new SortFilesUtils().sortFiles(mFiles, FileStorageUtils.mSortOrderFileDisp, - FileStorageUtils.mSortAscendingFileDisp); - notifyDataSetChanged(); - } - - public ArrayList getCheckedItems(AbsListView parentList) { - SparseBooleanArray checkedPositions = parentList.getCheckedItemPositions(); - ArrayList files = new ArrayList<>(); - Object item; - for (int i = 0; i < checkedPositions.size(); i++) { - if (checkedPositions.valueAt(i)) { - item = getItem(checkedPositions.keyAt(i)); - if (item != null) { - files.add((OCFile) item); - } - } - } - return files; - } - - public void filterBySearch(String query) { - clearFilterBySearch(); - - List filteredList = new ArrayList<>(); - - // Gather files matching the query - for (OCFile fileToAdd : mFiles) { - final String nameOfTheFileToAdd = fileToAdd.getFileName().toLowerCase(); - if (nameOfTheFileToAdd.contains(query)) { - filteredList.add(fileToAdd); - } - } - - // Remove not matching files from the filelist - for (int i = mFiles.size() - 1; i >= 0; i--) { - if (!filteredList.contains(mFiles.get(i))) { - mFiles.remove(i); - } - } - - // Add matching files to the filelist - for (int i = 0; i < filteredList.size(); i++) { - if (!mFiles.contains(filteredList.get(i))) { - mFiles.add(i, filteredList.get(i)); - } - } - - notifyDataSetChanged(); - } - - public void clearFilterBySearch() { - mFiles = new ArrayList(mImmutableFilesList); - notifyDataSetChanged(); - } - - public void filterByHiddenFiles() { - for (int i = 0; i < mFiles.size(); i++) { - if (mFiles.get(i).getFileName().startsWith(".")) { - mFiles.remove(i); - i--; // Otherwise it will skip the element after the removed index - } - } - } - - private enum ViewType {LIST_ITEM, GRID_IMAGE, GRID_ITEM} -} diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/errorhandling/ErrorMessageAdapter.kt b/owncloudApp/src/main/java/com/owncloud/android/ui/errorhandling/ErrorMessageAdapter.kt index b2daa924cee..93392eca03d 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/errorhandling/ErrorMessageAdapter.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/errorhandling/ErrorMessageAdapter.kt @@ -25,14 +25,18 @@ package com.owncloud.android.ui.errorhandling import android.content.res.Resources import com.owncloud.android.R +import com.owncloud.android.domain.exceptions.FileNotFoundException +import com.owncloud.android.domain.exceptions.ForbiddenException +import com.owncloud.android.domain.exceptions.InvalidCharacterException +import com.owncloud.android.domain.exceptions.LocalStorageFullException +import com.owncloud.android.domain.exceptions.LocalStorageNotCopiedException +import com.owncloud.android.domain.exceptions.QuotaExceededException import com.owncloud.android.extensions.parseError import com.owncloud.android.lib.common.operations.RemoteOperation import com.owncloud.android.lib.common.operations.RemoteOperationResult import com.owncloud.android.lib.common.operations.RemoteOperationResult.ResultCode -import com.owncloud.android.operations.CreateFolderOperation import com.owncloud.android.operations.SynchronizeFileOperation import com.owncloud.android.operations.SynchronizeFolderOperation -import com.owncloud.android.operations.UploadFileOperation import com.owncloud.android.ui.errorhandling.TransferOperation.Download import java.io.File import java.net.SocketTimeoutException @@ -88,6 +92,10 @@ class ErrorMessageAdapter { File(transferOperation.downloadPath).name ) } + is TransferOperation.Upload -> formatter.format( + R.string.uploader_upload_succeeded_content_single, + transferOperation.fileName + ) } } else { val genericMessage = when (transferOperation) { @@ -95,11 +103,49 @@ class ErrorMessageAdapter { R.string.downloader_download_failed_content, File(transferOperation.downloadPath).name ) + is TransferOperation.Upload -> { + getMessageForFailedUpload(formatter, throwable, transferOperation) + } } return throwable.parseError(genericMessage, resources, true).toString() } } + private fun getMessageForFailedUpload( + formatter: Formatter, + throwable: Throwable, + transferOperation: TransferOperation.Upload + ): String { + return when (throwable) { + is LocalStorageFullException -> formatter.format( + R.string.error__upload__local_file_not_copied, + transferOperation.fileName, R.string.app_name + ) + is LocalStorageNotCopiedException -> formatter.format( + R.string.error__upload__local_file_not_copied, + transferOperation.fileName, R.string.app_name + ) + is ForbiddenException -> { + formatter.format( + R.string.forbidden_permissions, + R.string.uploader_upload_forbidden_permissions + ) + } + is InvalidCharacterException -> { + formatter.format( + R.string.filename_forbidden_characters_from_server + ) + } + is QuotaExceededException -> + formatter.format(R.string.failed_upload_quota_exceeded_text) + is FileNotFoundException -> { + formatter.format(R.string.uploads_view_upload_status_failed_folder_error) + } + else -> formatter.format(R.string.uploader_upload_failed_content_single, transferOperation.fileName) + + } + } + /** * Return an internationalized user message corresponding to an operation result * and the operation performed. @@ -115,35 +161,14 @@ class ErrorMessageAdapter { resources: Resources ): String { val formatter = Formatter(resources) - if (result.isSuccess) { - when (operation) { - is UploadFileOperation -> return formatter.format( - R.string.uploader_upload_succeeded_content_single, - operation.fileName - ) - } - } if (operation is SynchronizeFileOperation && !operation.transferWasRequested()) { return formatter.format(R.string.sync_file_nothing_to_do_msg) } return when (result.code) { - ResultCode.LOCAL_STORAGE_FULL -> formatter.format( - R.string.error__upload__local_file_not_copied, - (operation as UploadFileOperation).fileName, R.string.app_name - ) - ResultCode.LOCAL_STORAGE_NOT_COPIED -> formatter.format( - R.string.error__upload__local_file_not_copied, - (operation as UploadFileOperation).fileName, R.string.app_name - ) ResultCode.FORBIDDEN -> { - if (operation is UploadFileOperation) formatter.format( - R.string.forbidden_permissions, - R.string.uploader_upload_forbidden_permissions - ) - if (operation is CreateFolderOperation) formatter.forbidden(R.string.forbidden_permissions_create) - else formatter.format( + formatter.format( R.string.filename_forbidden_characters_from_server ) } @@ -152,8 +177,6 @@ class ErrorMessageAdapter { ResultCode.QUOTA_EXCEEDED -> formatter.format(R.string.failed_upload_quota_exceeded_text) ResultCode.FILE_NOT_FOUND -> { - if (operation is UploadFileOperation) - formatter.format(R.string.uploads_view_upload_status_failed_folder_error) if (operation is SynchronizeFolderOperation) formatter.format( R.string.sync_current_folder_was_removed, @@ -232,8 +255,6 @@ class ErrorMessageAdapter { val formatter = Formatter(res) return when (operation) { - is UploadFileOperation -> formatter.format(R.string.uploader_upload_failed_content_single, operation.fileName) - is CreateFolderOperation -> formatter.format(R.string.create_dir_fail_msg) is SynchronizeFolderOperation -> formatter.format( R.string.sync_folder_failed_content, File(operation.folderPath).name diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/errorhandling/TransferOperation.kt b/owncloudApp/src/main/java/com/owncloud/android/ui/errorhandling/TransferOperation.kt index dfb8fa2d3d0..5748f740016 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/errorhandling/TransferOperation.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/errorhandling/TransferOperation.kt @@ -18,6 +18,7 @@ */ package com.owncloud.android.ui.errorhandling -sealed class TransferOperation { - data class Download(val downloadPath: String) : TransferOperation() +sealed interface TransferOperation { + data class Download(val downloadPath: String) : TransferOperation + data class Upload(val fileName: String) : TransferOperation } diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/fragment/ExpandableListFragment.java b/owncloudApp/src/main/java/com/owncloud/android/ui/fragment/ExpandableListFragment.java index 1bdce9a1455..a760584ceee 100755 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/fragment/ExpandableListFragment.java +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/fragment/ExpandableListFragment.java @@ -30,12 +30,13 @@ import android.widget.ExpandableListView; import android.widget.ExpandableListView.OnChildClickListener; +import androidx.core.content.res.ResourcesCompat; import com.owncloud.android.R; import com.owncloud.android.utils.PreferenceUtils; import timber.log.Timber; /** - * Extending ExtendedListFragment. This allows dividing list in groups. + * Extending ExtendedListFragment. This allows dividing list in groups. */ public class ExpandableListFragment extends ExtendedListFragment implements OnChildClickListener { @@ -65,7 +66,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa mList = v.findViewById(R.id.list_root); mList.setOnChildClickListener(this); - mList.setDivider(getResources().getDrawable(R.drawable.uploader_list_separator)); + mList.setDivider(ResourcesCompat.getDrawable(getResources(), R.drawable.uploader_list_separator, null)); mList.setDividerHeight(1); // if (savedInstanceState != null) { diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/fragment/ExtendedListFragment.java b/owncloudApp/src/main/java/com/owncloud/android/ui/fragment/ExtendedListFragment.java index b752824fb46..7bd6ba2c408 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/fragment/ExtendedListFragment.java +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/fragment/ExtendedListFragment.java @@ -23,27 +23,17 @@ package com.owncloud.android.ui.fragment; import android.os.Bundle; -import android.view.LayoutInflater; import android.view.View; -import android.view.ViewGroup; import android.widget.AbsListView; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; -import android.widget.GridView; -import android.widget.ListAdapter; -import android.widget.ProgressBar; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; -import com.getbase.floatingactionbutton.FloatingActionButton; -import com.getbase.floatingactionbutton.FloatingActionsMenu; import com.owncloud.android.R; -import com.owncloud.android.ui.ExtendedListView; import com.owncloud.android.ui.activity.OnEnforceableRefreshListener; -import com.owncloud.android.utils.PreferenceUtils; -import third_parties.in.srain.cube.GridViewWithHeaderAndFooter; import timber.log.Timber; import java.util.ArrayList; @@ -58,26 +48,12 @@ public class ExtendedListFragment extends Fragment private static final String KEY_TOPS = "TOPS"; private static final String KEY_HEIGHT_CELL = "HEIGHT_CELL"; private static final String KEY_EMPTY_LIST_MESSAGE = "EMPTY_LIST_MESSAGE"; - private static final String KEY_IS_GRID_VISIBLE = "IS_GRID_VISIBLE"; - - static final String ARG_JUST_FOLDERS = ExtendedListFragment.class.getCanonicalName() + ".JUST_FOLDERS"; - static final String ARG_LIST_FILE_OPTION = ExtendedListFragment.class.getCanonicalName() + - ".LIST_FILE_OPTION"; - static final String ARG_PICKING_A_FOLDER = ExtendedListFragment.class.getCanonicalName() + - ".ARG_PICKING_A_FOLDER"; - - private ProgressBar mProgressBar; - private View mShadowView; SwipeRefreshLayout mRefreshListLayout; private SwipeRefreshLayout mRefreshGridLayout; SwipeRefreshLayout mRefreshEmptyLayout; TextView mEmptyListMessage; - private FloatingActionsMenu mFabMain; - private FloatingActionButton mFabUpload; - private FloatingActionButton mFabMkdir; - // Save the state of the scroll in browsing private ArrayList mIndexes; private ArrayList mFirstPositions; @@ -87,120 +63,11 @@ public class ExtendedListFragment extends Fragment private SwipeRefreshLayout.OnRefreshListener mOnRefreshListener = null; AbsListView mCurrentListView; - private ExtendedListView mListView; - private View mListFooterView; - private GridViewWithHeaderAndFooter mGridView; - private View mGridFooterView; - - private ListAdapter mAdapter; - - void setListAdapter(ListAdapter listAdapter) { - mAdapter = listAdapter; - mCurrentListView.setAdapter(listAdapter); - mCurrentListView.invalidateViews(); - } protected AbsListView getListView() { return mCurrentListView; } - FloatingActionButton getFabUpload() { - return mFabUpload; - } - - FloatingActionButton getFabMkdir() { - return mFabMkdir; - } - - public FloatingActionsMenu getFabMain() { - return mFabMain; - } - - void switchToGridView() { - if (!isGridEnabled()) { - mListView.setAdapter(null); - mRefreshListLayout.setVisibility(View.GONE); - mRefreshGridLayout.setVisibility(View.VISIBLE); - mCurrentListView = mGridView; - setListAdapter(mAdapter); - } - } - - void switchToListView() { - if (isGridEnabled()) { - mGridView.setAdapter(null); - mRefreshGridLayout.setVisibility(View.GONE); - mRefreshListLayout.setVisibility(View.VISIBLE); - mCurrentListView = mListView; - setListAdapter(mAdapter); - } - } - - public boolean isGridEnabled() { - return (mCurrentListView == mGridView); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - Timber.v("onCreateView"); - View v = inflater.inflate(R.layout.list_fragment, null); - - mProgressBar = v.findViewById(R.id.syncProgressBar); - mShadowView = v.findViewById(R.id.shadow_view); - - mListView = v.findViewById(R.id.list_root); - mListView.setOnItemClickListener(this); - mListFooterView = inflater.inflate(R.layout.list_footer, null, false); - - mListFooterView.setFilterTouchesWhenObscured( - PreferenceUtils.shouldDisallowTouchesWithOtherVisibleWindows(getContext()) - ); - - mGridView = v.findViewById(R.id.grid_root); - mGridView.setNumColumns(GridView.AUTO_FIT); - mGridView.setOnItemClickListener(this); - - mGridFooterView = inflater.inflate(R.layout.list_footer, null, false); - - mGridFooterView.setFilterTouchesWhenObscured( - PreferenceUtils.shouldDisallowTouchesWithOtherVisibleWindows(getContext()) - ); - - // Pull-down to refresh layout - mRefreshListLayout = v.findViewById(R.id.swipe_containing_list); - mRefreshGridLayout = v.findViewById(R.id.swipe_containing_grid); - mRefreshEmptyLayout = v.findViewById(R.id.swipe_containing_empty); - mEmptyListMessage = v.findViewById(R.id.empty_list_view); - - onCreateSwipeToRefresh(mRefreshListLayout); - onCreateSwipeToRefresh(mRefreshGridLayout); - onCreateSwipeToRefresh(mRefreshEmptyLayout); - - mListView.setEmptyView(mRefreshEmptyLayout); - mGridView.setEmptyView(mRefreshEmptyLayout); - - mFabMain = v.findViewById(R.id.fab_main); - mFabUpload = v.findViewById(R.id.fab_upload); - mFabMkdir = v.findViewById(R.id.fab_mkdir); - - mCurrentListView = mListView; // list by default - if (savedInstanceState != null) { - if (savedInstanceState.getBoolean(KEY_IS_GRID_VISIBLE, false)) { - switchToGridView(); - } - int referencePosition = savedInstanceState.getInt(KEY_SAVED_LIST_POSITION); - if (isGridEnabled()) { - Timber.v("Setting grid position %s", referencePosition); - mGridView.setSelection(referencePosition); - } else { - Timber.v("Setting and centering around list position %s", referencePosition); - mListView.setAndCenterSelection(referencePosition); - } - } - - return v; - } - /** * {@inheritDoc} */ @@ -226,7 +93,6 @@ public void onActivityCreated(Bundle savedInstanceState) { public void onSaveInstanceState(@NonNull Bundle savedInstanceState) { super.onSaveInstanceState(savedInstanceState); Timber.v("onSaveInstanceState()"); - savedInstanceState.putBoolean(KEY_IS_GRID_VISIBLE, isGridEnabled()); savedInstanceState.putInt(KEY_SAVED_LIST_POSITION, getReferencePosition()); savedInstanceState.putIntegerArrayList(KEY_INDEXES, mIndexes); savedInstanceState.putIntegerArrayList(KEY_FIRST_POSITIONS, mFirstPositions); @@ -255,58 +121,6 @@ protected int getReferencePosition() { } } - /* - * Restore index and position - */ - protected void restoreIndexAndTopPosition() { - if (mIndexes.size() > 0) { - // needs to be checked; not every browse-up had a browse-down before - - int index = mIndexes.remove(mIndexes.size() - 1); - final int firstPosition = mFirstPositions.remove(mFirstPositions.size() - 1); - int top = mTops.remove(mTops.size() - 1); - - Timber.v("Setting selection to position: " + firstPosition + "; top: " + top + "; index: " + index); - - if (mCurrentListView == mListView) { - if (mHeightCell * index <= mListView.getHeight()) { - mListView.setSelectionFromTop(firstPosition, top); - } else { - mListView.setSelectionFromTop(index, 0); - } - - } else { - if (mHeightCell * index <= mGridView.getHeight()) { - mGridView.setSelection(firstPosition); - //mGridView.smoothScrollToPosition(firstPosition); - } else { - mGridView.setSelection(index); - //mGridView.smoothScrollToPosition(index); - } - } - - } - } - - /* - * Save index and top position - */ - protected void saveIndexAndTopPosition(int index) { - - mIndexes.add(index); - - int firstPosition = mCurrentListView.getFirstVisiblePosition(); - mFirstPositions.add(firstPosition); - - View view = mCurrentListView.getChildAt(0); - int top = (view == null) ? 0 : view.getTop(); - - mTops.add(top); - - // Save the height of a cell - mHeightCell = (view == null || mHeightCell != 0) ? mHeightCell : view.getHeight(); - } - @Override public void onItemClick(AdapterView parent, View view, int position, long id) { // to be @overridden @@ -327,36 +141,6 @@ public void setOnRefreshListener(OnEnforceableRefreshListener listener) { mOnRefreshListener = listener; } - /** - * Disables swipe gesture. - *

- * Sets the 'enabled' state of the refresh layouts contained in the fragment. - *

- * When 'false' is set, prevents user gestures but keeps the option to refresh programatically, - * - * @param enabled Desired state for capturing swipe gesture. - */ - public void setSwipeEnabled(boolean enabled) { - mRefreshListLayout.setEnabled(enabled); - mRefreshGridLayout.setEnabled(enabled); - mRefreshEmptyLayout.setEnabled(enabled); - } - - /** - * Sets the 'visibility' state of the FAB contained in the fragment. - *

- * When 'false' is set, FAB visibility is set to View.GONE programatically, - * - * @param enabled Desired visibility for the FAB. - */ - public void setFabEnabled(boolean enabled) { - if (enabled) { - mFabMain.setVisibility(View.VISIBLE); - } else { - mFabMain.setVisibility(View.GONE); - } - } - /** * Set message for empty list view */ @@ -393,90 +177,4 @@ public void onRefresh(boolean ignoreETag) { mOnRefreshListener.onRefresh(); } } - - protected void setChoiceMode(int choiceMode) { - mListView.setChoiceMode(choiceMode); - mGridView.setChoiceMode(choiceMode); - } - - protected void setMultiChoiceModeListener(AbsListView.MultiChoiceModeListener listener) { - mListView.setMultiChoiceModeListener(listener); - mGridView.setMultiChoiceModeListener(listener); - } - - /** - * TODO doc - * To be called before setAdapter, or GridViewWithHeaderAndFooter will throw an exception - * - * @param enabled - */ - protected void setFooterEnabled(boolean enabled) { - if (enabled) { - if (mGridView.getFooterViewCount() == 0) { - if (mGridFooterView.getParent() != null) { - ((ViewGroup) mGridFooterView.getParent()).removeView(mGridFooterView); - } - try { - mGridView.addFooterView(mGridFooterView, null, false); - } catch (IllegalStateException ie) { - Timber.w("Could not add footer to grid view, because it exists"); - } - } - mGridFooterView.invalidate(); - - if (mListView.getFooterViewsCount() == 0) { - if (mListFooterView.getParent() != null) { - ((ViewGroup) mListFooterView.getParent()).removeView(mListFooterView); - } - mListView.addFooterView(mListFooterView, null, false); - } - mListFooterView.invalidate(); - - } else { - mGridView.removeFooterView(mGridFooterView); - mListView.removeFooterView(mListFooterView); - } - } - - public ProgressBar getProgressBar() { - return mProgressBar; - } - - public View getShadowView() { - return mShadowView; - } - - /** - * TODO doc - * - * @param text - */ - protected void setFooterText(String text) { - if (text != null && text.length() > 0) { - ((TextView) mListFooterView.findViewById(R.id.footerText)).setText(text); - ((TextView) mGridFooterView.findViewById(R.id.footerText)).setText(text); - setFooterEnabled(true); - - } else { - setFooterEnabled(false); - } - } - - public void setProgressBarAsIndeterminate(boolean indeterminate) { - Timber.d("Setting progress visibility to %s", indeterminate); - mShadowView.setVisibility(View.GONE); - mProgressBar.setVisibility(View.VISIBLE); - mProgressBar.setIndeterminate(indeterminate); - mProgressBar.postInvalidate(); - } - - boolean isShowingJustFolders() { - Bundle args = getArguments(); - return ((args != null) && args.getBoolean(ARG_JUST_FOLDERS, false)); - } - - boolean isPickingAFolder() { - Bundle args = getArguments(); - return ((args != null) && args.getBoolean(ARG_PICKING_A_FOLDER, false)); - } } diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java b/owncloudApp/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java deleted file mode 100644 index afca3795a8f..00000000000 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.java +++ /dev/null @@ -1,1352 +0,0 @@ -/** - * ownCloud Android client application - * - * @author Bartek Przybylski - * @author masensio - * @author David A. Velasco - * @author Christian Schabesberger - * @author David González Verdugo - * @author Shashvat Kedia - * @author Abel García de Prada - * @author David Crespo Rios - * Copyright (C) 2011 Bartek Przybylski - * Copyright (C) 2022 ownCloud GmbH. - *

- * 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.ui.fragment; - -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.os.Bundle; -import android.os.Handler; -import android.os.PowerManager; -import android.preference.PreferenceManager; -import android.util.SparseBooleanArray; -import android.view.ActionMode; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.view.Window; -import android.view.animation.Animation; -import android.view.animation.AnimationUtils; -import android.widget.AbsListView; -import android.widget.AdapterView; -import android.widget.ListView; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.widget.SearchView; -import androidx.coordinatorlayout.widget.CoordinatorLayout; -import androidx.drawerlayout.widget.DrawerLayout; -import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; -import androidx.work.WorkManager; -import com.google.android.material.bottomsheet.BottomSheetBehavior; -import com.google.android.material.bottomsheet.BottomSheetDialog; -import com.google.android.material.snackbar.Snackbar; -import com.owncloud.android.R; -import com.owncloud.android.authentication.AccountUtils; -import com.owncloud.android.datamodel.FileDataStorageManager; -import com.owncloud.android.extensions.DialogExtKt; -import com.owncloud.android.extensions.FragmentExtKt; -import com.owncloud.android.domain.files.model.OCFile; -import com.owncloud.android.extensions.ThrowableExtKt; -import com.owncloud.android.extensions.WorkManagerExtKt; -import com.owncloud.android.files.FileMenuFilter; -import com.owncloud.android.lib.resources.status.OwnCloudVersion; -import com.owncloud.android.presentation.UIResult; -import com.owncloud.android.presentation.ui.common.BottomSheetFragmentItemView; -import com.owncloud.android.presentation.ui.files.SortBottomSheetFragment; -import com.owncloud.android.presentation.ui.files.SortOptionsView; -import com.owncloud.android.presentation.ui.files.SortOrder; -import com.owncloud.android.presentation.ui.files.SortType; -import com.owncloud.android.presentation.ui.files.ViewType; -import com.owncloud.android.presentation.ui.files.createfolder.CreateFolderDialogFragment; -import com.owncloud.android.presentation.viewmodels.files.FilesViewModel; -import com.owncloud.android.ui.activity.FileActivity; -import com.owncloud.android.ui.activity.FileDisplayActivity; -import com.owncloud.android.domain.files.model.FileListOption; -import com.owncloud.android.ui.activity.FolderPickerActivity; -import com.owncloud.android.ui.activity.OnEnforceableRefreshListener; -import com.owncloud.android.ui.adapter.FileListListAdapter; -import com.owncloud.android.ui.dialog.ConfirmationDialogFragment; -import com.owncloud.android.presentation.ui.files.removefile.RemoveFilesDialogFragment; -import com.owncloud.android.ui.dialog.RenameFileDialogFragment; -import com.owncloud.android.ui.helpers.SparseBooleanArrayParcelable; -import com.owncloud.android.ui.preview.PreviewAudioFragment; -import com.owncloud.android.ui.preview.PreviewImageFragment; -import com.owncloud.android.ui.preview.PreviewTextFragment; -import com.owncloud.android.ui.preview.PreviewVideoFragment; -import com.owncloud.android.utils.FileStorageUtils; -import com.owncloud.android.utils.PreferenceUtils; -import kotlin.Unit; -import org.jetbrains.annotations.NotNull; -import timber.log.Timber; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; - -import static org.koin.java.KoinJavaComponent.get; - -/** - * A Fragment that lists all files and folders in a given path. - *

- * TODO refactor to get rid of direct dependency on FileDisplayActivity - */ -public class OCFileListFragment extends ExtendedListFragment implements - SearchView.OnQueryTextListener, View.OnFocusChangeListener, SortOptionsView.SortOptionsListener, - SortBottomSheetFragment.SortDialogListener, SortOptionsView.CreateFolderListener, CreateFolderDialogFragment.CreateFolderListener { - - private static final String MY_PACKAGE = OCFileListFragment.class.getPackage() != null ? - OCFileListFragment.class.getPackage().getName() : "com.owncloud.android.ui.fragment"; - - private final static String ARG_ALLOW_CONTEXTUAL_MODE = MY_PACKAGE + ".ALLOW_CONTEXTUAL"; - private final static String ARG_HIDE_FAB = MY_PACKAGE + ".HIDE_FAB"; - - private static final String KEY_FILE = MY_PACKAGE + ".extra.FILE"; - private static final String KEY_FILE_LIST_OPTION = "FILE_LIST_OPTION"; - private static final String KEY_FAB_EVER_CLICKED = "FAB_EVER_CLICKED"; - - private static final String GRID_IS_PREFERED_PREFERENCE = "gridIsPrefered"; - - private static String DIALOG_CREATE_FOLDER = "DIALOG_CREATE_FOLDER"; - - private final String ALL_FILES_SAF_REGEX = "*/*"; - - private FileFragment.ContainerActivity mContainerActivity; - - private FileListOption mFileListOption = null; - private OCFile mFile = null; - private FileListListAdapter mFileListAdapter; - - private boolean mEnableSelectAll = true; - - private int mStatusBarColorActionMode; - private int mStatusBarColor; - - private boolean mHideFab = true; - - private boolean miniFabClicked = false; - private ActionMode mActiveActionMode; - private OCFileListFragment.MultiChoiceModeListener mMultiChoiceModeListener; - - private SearchView mSearchView; - - private SortOptionsView mSortOptionsView; - - /** - * Public factory method to create new {@link OCFileListFragment} instances. - * - * @param justFolders When 'true', only folders will be shown to the user, not files. - * @param fileListOption File list option to show. All files by default. - * @param pickingAFolder When 'true', only folders will be clickable when selecting a folder when copying or - * moving files or configuring upload path for camera uploads - * @param hideFAB When 'true', floating action button is hidden. - * @param allowContextualMode When 'true', contextual action mode is enabled long-pressing an item. - * @return New fragment with arguments set. - */ - public static OCFileListFragment newInstance( - boolean justFolders, - FileListOption fileListOption, - boolean pickingAFolder, - boolean hideFAB, - boolean allowContextualMode - ) { - OCFileListFragment frag = new OCFileListFragment(); - Bundle args = new Bundle(); - args.putBoolean(ARG_JUST_FOLDERS, justFolders); - if (fileListOption == null) { - fileListOption = FileListOption.ALL_FILES; - } - if (!fileListOption.isAllFiles()) { - hideFAB = true; - } - args.putParcelable(ARG_LIST_FILE_OPTION, fileListOption); - args.putBoolean(ARG_PICKING_A_FOLDER, pickingAFolder); - args.putBoolean(ARG_HIDE_FAB, hideFAB); - args.putBoolean(ARG_ALLOW_CONTEXTUAL_MODE, allowContextualMode); - frag.setArguments(args); - return frag; - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setHasOptionsMenu(true); - mStatusBarColorActionMode = getResources().getColor(R.color.action_mode_status_bar_background); - } - - /** - * {@inheritDoc} - */ - @Override - public void onAttach(@NonNull Context context) { - super.onAttach(context); - Timber.v("onAttach"); - try { - mContainerActivity = (FileFragment.ContainerActivity) context; - - } catch (ClassCastException e) { - throw new ClassCastException(context.toString() + " must implement " + - FileFragment.ContainerActivity.class.getSimpleName()); - } - try { - setOnRefreshListener((OnEnforceableRefreshListener) context); - - } catch (ClassCastException e) { - throw new ClassCastException(context.toString() + " must implement " + - SwipeRefreshLayout.OnRefreshListener.class.getSimpleName()); - } - } - - /** - * {@inheritDoc} - */ - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - Timber.i("onCreateView() start"); - View v = super.onCreateView(inflater, container, savedInstanceState); - Bundle args = getArguments(); - boolean allowContextualActions = (args != null) && args.getBoolean(ARG_ALLOW_CONTEXTUAL_MODE, false); - if (allowContextualActions) { - setChoiceModeAsMultipleModal(savedInstanceState); - } - - Timber.i("onCreateView() end"); - return v; - } - - @Override - public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - mSortOptionsView = view.findViewById(R.id.options_layout); - if (mSortOptionsView != null) { - mSortOptionsView.setOnSortOptionsListener(this); - if (isPickingAFolder()) { - mSortOptionsView.setOnCreateFolderListener(this); - mSortOptionsView.selectAdditionalView(SortOptionsView.AdditionalView.CREATE_FOLDER); - } - } - } - - @Override - public void onDetach() { - setOnRefreshListener(null); - mContainerActivity = null; - super.onDetach(); - } - - /** - * {@inheritDoc} - */ - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - Timber.v("onActivityCreated() start"); - - if (savedInstanceState != null) { - mFile = savedInstanceState.getParcelable(KEY_FILE); - mFileListOption = savedInstanceState.getParcelable(KEY_FILE_LIST_OPTION); - } - - if (mFileListOption == null && getArguments() != null && getArguments().getParcelable(ARG_LIST_FILE_OPTION) != null) { - mFileListOption = getArguments().getParcelable(ARG_LIST_FILE_OPTION); - } - - if (mFileListOption == null) { - mFileListOption = FileListOption.ALL_FILES; - } - updateListOfFiles(mFileListOption); - } - - @Override - public void onCreateOptionsMenu(@NonNull Menu menu, @NonNull MenuInflater inflater) { - super.onCreateOptionsMenu(menu, inflater); - mSearchView = (SearchView) menu.findItem(R.id.action_search).getActionView(); - mSearchView.setMaxWidth(Integer.MAX_VALUE); - mSearchView.setQueryHint(getResources().getString(R.string.actionbar_search)); - mSearchView.setOnQueryTextFocusChangeListener(this); - mSearchView.setOnQueryTextListener(this); - - if (isPickingAFolder()) { - menu.removeItem(menu.findItem(R.id.action_share_current_folder).getItemId()); - } - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == R.id.action_share_current_folder) { - mContainerActivity.getFileOperationsHelper().showShareFile(mFile); - } - return super.onOptionsItemSelected(item); - } - - public void updateFileListOption(FileListOption newFileListOption) { - updateListOfFiles(newFileListOption); - mFileListOption = newFileListOption; - mFile = mContainerActivity.getStorageManager().getFileByPath(OCFile.ROOT_PATH); - listDirectory(true); - } - - private void updateListOfFiles( - FileListOption fileListOption - ) { - boolean justFolders = isShowingJustFolders(); - setFooterEnabled(!justFolders); - - boolean folderPicker = isPickingAFolder(); - - mFileListAdapter = new FileListListAdapter( - justFolders, - fileListOption.isAvailableOffline(), - fileListOption.isSharedByLink(), - folderPicker, - getActivity(), - mContainerActivity - ); - setListAdapter(mFileListAdapter); - - mHideFab = !fileListOption.isAllFiles() || folderPicker; - if (mHideFab) { - setFabEnabled(false); - } else { - setFabEnabled(true); - registerFabListeners(); - - // detect if a mini FAB has ever been clicked - final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity()); - if (prefs.getLong(KEY_FAB_EVER_CLICKED, 0) > 0) { - miniFabClicked = true; - } - - // add labels to the min FABs when none of them has ever been clicked on - if (!miniFabClicked) { - setFabLabels(); - } else { - removeFabLabels(); - } - } - - // Allow or disallow touches with other visible windows - CoordinatorLayout coordinatorLayout = requireActivity().findViewById(R.id.coordinator_layout); - coordinatorLayout.setFilterTouchesWhenObscured( - PreferenceUtils.shouldDisallowTouchesWithOtherVisibleWindows(getContext()) - ); - } - - /** - * adds labels to all mini FABs. - */ - private void setFabLabels() { - getFabUpload().setTitle(getResources().getString(R.string.actionbar_upload)); - getFabMkdir().setTitle(getResources().getString(R.string.actionbar_mkdir)); - } - - /** - * registers all listeners on all mini FABs. - */ - private void registerFabListeners() { - registerFabUploadListeners(); - registerFabMkDirListeners(); - } - - /** - * registers {@link android.view.View.OnClickListener} and {@link android.view.View.OnLongClickListener} - * on the Upload mini FAB for the linked action an {@link Snackbar} showing the underlying action. - */ - private void registerFabUploadListeners() { - getFabUpload().setOnClickListener(v -> { - final View uploadBottomSheet = getLayoutInflater().inflate(R.layout.upload_bottom_sheet_fragment, null); - final BottomSheetDialog dialog = new BottomSheetDialog(requireContext()); - dialog.setContentView(uploadBottomSheet); - final BottomSheetFragmentItemView uploadFromFilesItemView = uploadBottomSheet.findViewById(R.id.upload_from_files_item_view); - BottomSheetFragmentItemView uploadFromCameraItemView = - uploadBottomSheet.findViewById(R.id.upload_from_camera_item_view); - TextView uploadToTextView = uploadBottomSheet.findViewById(R.id.upload_to_text_view); - uploadFromFilesItemView.setOnTouchListener((v13, event) -> { - Intent action = new Intent(Intent.ACTION_GET_CONTENT); - action = action.setType(ALL_FILES_SAF_REGEX).addCategory(Intent.CATEGORY_OPENABLE); - action.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); - getActivity().startActivityForResult( - Intent.createChooser(action, getString(R.string.upload_chooser_title)), - FileDisplayActivity.REQUEST_CODE__SELECT_CONTENT_FROM_APPS - ); - dialog.hide(); - return false; - }); - uploadFromCameraItemView.setOnTouchListener((v12, event) -> { - ((FileDisplayActivity) getActivity()).getFilesUploadHelper().uploadFromCamera(FileDisplayActivity.REQUEST_CODE__UPLOAD_FROM_CAMERA); - dialog.hide(); - return false; - }); - uploadToTextView.setText(String.format(getResources().getString(R.string.upload_to), - getResources().getString(R.string.app_name))); - final BottomSheetBehavior uploadBottomSheetBehavior = - BottomSheetBehavior.from((View) uploadBottomSheet.getParent()); - dialog.setOnShowListener(dialog1 -> - uploadBottomSheetBehavior.setPeekHeight(uploadBottomSheet.getMeasuredHeight())); - dialog.show(); - DialogExtKt.avoidScreenshotsIfNeeded(dialog); - getFabMain().collapse(); - recordMiniFabClick(); - }); - - getFabUpload().setOnLongClickListener(v -> { - showSnackMessage(R.string.actionbar_upload); - return true; - }); - } - - /** - * Registers {@link android.view.View.OnClickListener} and {@link android.view.View.OnLongClickListener} - * on the 'Create Dir' mini FAB for the linked action and {@link Snackbar} showing the underlying action. - */ - private void registerFabMkDirListeners() { - getFabMkdir().setOnClickListener(v -> { - CreateFolderDialogFragment dialog = CreateFolderDialogFragment.newInstance(mFile, this); - dialog.show(requireActivity().getSupportFragmentManager(), DIALOG_CREATE_FOLDER); - getFabMain().collapse(); - recordMiniFabClick(); - }); - - getFabMkdir().setOnLongClickListener(v -> { - showSnackMessage(R.string.actionbar_mkdir); - return true; - }); - } - - /** - * records a click on a mini FAB and thus: - *

    - *
  1. persists the click fact
  2. - *
  3. removes the mini FAB labels
  4. - *
- */ - private void recordMiniFabClick() { - // only record if it hasn't been done already at some other time - if (!miniFabClicked) { - final SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(getActivity()); - sp.edit().putLong(KEY_FAB_EVER_CLICKED, 1).apply(); - miniFabClicked = true; - } - } - - /** - * removes the labels on all known min FABs. - */ - private void removeFabLabels() { - getFabUpload().setTitle(null); - getFabMkdir().setTitle(null); - ((TextView) getFabUpload().getTag(com.getbase.floatingactionbutton.R.id.fab_label)).setVisibility(View.GONE); - ((TextView) getFabMkdir().getTag(com.getbase.floatingactionbutton.R.id.fab_label)).setVisibility(View.GONE); - } - - @Override - public boolean onQueryTextSubmit(String query) { - return false; - } - - @Override - public boolean onQueryTextChange(String query) { - mFileListAdapter.filterBySearch(query); - return true; - } - - @Override - public void onFocusChange(View v, boolean hasFocus) { - if (hasFocus) { - setMessageForEmptyList(getString(R.string.local_file_list_search_with_no_matches)); - } else { // Set default message for empty list of files -// if (requireActivity() instanceof FileDisplayActivity) { -// ((FileDisplayActivity) requireActivity()).setBackgroundText(); -// } else if (requireActivity() instanceof FolderPickerActivity) { -// ((FolderPickerActivity) requireActivity()).setBackgroundText(); -// } - } - } - - public boolean isSingleItemChecked() { - return mFileListAdapter.getCheckedItems(getListView()).size() == 1; - } - - @Override - public void onViewTypeListener(@NotNull ViewType viewType) { - mSortOptionsView.setViewTypeSelected(viewType); - if (viewType == ViewType.VIEW_TYPE_LIST) { - setListAsPreferred(); - } else { - setGridAsPreferred(); - } - } - - @Override - public void onSortSelected(@NotNull SortType sortType) { - mSortOptionsView.setSortTypeSelected(sortType); - - boolean isAscending = mSortOptionsView.getSortOrderSelected().equals(SortOrder.SORT_ORDER_ASCENDING); - if (sortType == SortType.SORT_TYPE_BY_NAME) { - sortByName(isAscending); - } else if (sortType == SortType.SORT_TYPE_BY_DATE) { - sortByDate(isAscending); - } else if (sortType == SortType.SORT_TYPE_BY_SIZE) { - sortBySize(isAscending); - } - - } - - @Override - public void onSortTypeListener(@NotNull SortType sortType, @NotNull SortOrder sortOrder) { - SortBottomSheetFragment sortBottomSheetFragment = SortBottomSheetFragment.Companion.newInstance(sortType, sortOrder); - sortBottomSheetFragment.setSortDialogListener(this); - sortBottomSheetFragment.show(getChildFragmentManager(), SortBottomSheetFragment.TAG); - } - - @Override - public void onCreateFolderListener() { - CreateFolderDialogFragment dialog = CreateFolderDialogFragment.newInstance(mFile, this::onFolderNameSet); - dialog.show(requireActivity().getSupportFragmentManager(), DIALOG_CREATE_FOLDER); - - } - - @Override - public void onFolderNameSet(@NotNull String newFolderName, @NotNull OCFile parentFolder) { - FilesViewModel filesViewModel = get(FilesViewModel.class); - - filesViewModel.createFolder(parentFolder, newFolderName); - filesViewModel.getCreateFolder().observe(this, uiResultEvent -> { - UIResult uiResult = uiResultEvent.peekContent(); - if (uiResult.isSuccess()) { - listDirectory(true); - } else { - Throwable throwable = uiResult.getThrowableOrNull(); - CharSequence errorMessage = ThrowableExtKt.parseError(throwable, getResources().getString(R.string.create_dir_fail_msg), - getResources(), false); - showSnackMessage(errorMessage.toString()); - } - - }); - } - - /** - * Handler for multiple selection mode. - *

- * Manages input from the user when one or more files or folders are selected in the list. - *

- * Also listens to changes in navigation drawer to hide and recover multiple selection when it's opened - * and closed. - */ - private class MultiChoiceModeListener - implements AbsListView.MultiChoiceModeListener, DrawerLayout.DrawerListener { - - private static final String KEY_ACTION_MODE_CLOSED_BY_DRAWER = "KILLED_ACTION_MODE"; - private static final String KEY_SELECTION_WHEN_CLOSED_BY_DRAWER = "CHECKED_ITEMS"; - - /** - * True when action mode is finished because the drawer was opened - */ - private boolean mActionModeClosedByDrawer = false; - - /** - * Selected items in list when action mode is closed by drawer - */ - private SparseBooleanArray mSelectionWhenActionModeClosedByDrawer = null; - - @Override - public void onDrawerSlide(@NonNull View drawerView, float slideOffset) { - // nothing to do - } - - @Override - public void onDrawerOpened(@NonNull View drawerView) { - clearLocalSearchView(); - } - - /** - * When the navigation drawer is closed, action mode is recovered in the same state as was - * when the drawer was (started to be) opened. - * - * @param drawerView Navigation drawer just closed. - */ - @Override - public void onDrawerClosed(@NonNull View drawerView) { - if (mSelectionWhenActionModeClosedByDrawer != null && mActionModeClosedByDrawer) { - for (int i = 0; i < mSelectionWhenActionModeClosedByDrawer.size(); i++) { - if (mSelectionWhenActionModeClosedByDrawer.valueAt(i)) { - getListView().setItemChecked( - mSelectionWhenActionModeClosedByDrawer.keyAt(i), - true - ); - } - } - } - mSelectionWhenActionModeClosedByDrawer = null; - } - - /** - * If the action mode is active when the navigation drawer starts to move, the action - * mode is closed and the selection stored to be recovered when the drawer is closed. - * - * @param newState One of STATE_IDLE, STATE_DRAGGING or STATE_SETTLING. - */ - @Override - public void onDrawerStateChanged(int newState) { - if (DrawerLayout.STATE_DRAGGING == newState && mActiveActionMode != null) { - mSelectionWhenActionModeClosedByDrawer = getListView().getCheckedItemPositions().clone(); - mActiveActionMode.finish(); - mActionModeClosedByDrawer = true; - } - } - - /** - * Update action mode bar when an item is selected / unselected in the list - */ - @Override - public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) { - getListView().invalidateViews(); - mode.invalidate(); - if (mFileListAdapter.getCheckedItems(getListView()).size() == mFileListAdapter.getCount()) { - mEnableSelectAll = false; - } else { - if (!checked) { - mEnableSelectAll = true; - } - } - } - - /** - * Load menu and customize UI when action mode is started. - */ - @Override - public boolean onCreateActionMode(ActionMode mode, Menu menu) { - mActiveActionMode = mode; - - MenuInflater inflater = requireActivity().getMenuInflater(); - inflater.inflate(R.menu.file_actions_menu, menu); - mode.invalidate(); - - //set gray color - Window w = getActivity().getWindow(); - mStatusBarColor = w.getStatusBarColor(); - w.setStatusBarColor(mStatusBarColorActionMode); - - // hide FAB in multi selection mode - setFabEnabled(false); - ((FileDisplayActivity) mContainerActivity).showBottomNavBar(false); - - // Hide sort options view in multi-selection mode - mSortOptionsView.setVisibility(View.GONE); - - return true; - } - - /** - * Updates available action in menu depending on current selection. - */ - @Override - public boolean onPrepareActionMode(ActionMode mode, Menu menu) { - List checkedFiles = mFileListAdapter.getCheckedItems(getListView()); - final int checkedCount = checkedFiles.size(); - String title = getResources().getQuantityString( - R.plurals.items_selected_count, - checkedCount, - checkedCount - ); - mode.setTitle(title); - FileMenuFilter mf = new FileMenuFilter( - checkedFiles, - ((FileActivity) requireActivity()).getAccount(), - mContainerActivity, - getActivity() - ); - mf.filter(menu, mEnableSelectAll, true, mFileListOption.isAvailableOffline(), - mFileListOption.isSharedByLink()); - return true; - } - - /** - * Starts the corresponding action when a menu item is tapped by the user. - */ - @Override - public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - return onFileActionChosen(item.getItemId()); - } - - /** - * Restores UI. - */ - @Override - public void onDestroyActionMode(ActionMode mode) { - mActiveActionMode = null; - - // reset to previous color - requireActivity().getWindow().setStatusBarColor(mStatusBarColor); - - // show FAB on multi selection mode exit - if (!mHideFab) { - setFabEnabled(true); - } - ((FileDisplayActivity) mContainerActivity).showBottomNavBar(true); - - // Show sort options view when multi-selection mode finish - mSortOptionsView.setVisibility(View.VISIBLE); - } - - void storeStateIn(Bundle outState) { - outState.putBoolean(KEY_ACTION_MODE_CLOSED_BY_DRAWER, mActionModeClosedByDrawer); - if (mSelectionWhenActionModeClosedByDrawer != null) { - SparseBooleanArrayParcelable sbap = new SparseBooleanArrayParcelable( - mSelectionWhenActionModeClosedByDrawer - ); - outState.putParcelable(KEY_SELECTION_WHEN_CLOSED_BY_DRAWER, sbap); - } - } - - void loadStateFrom(Bundle savedInstanceState) { - mActionModeClosedByDrawer = savedInstanceState.getBoolean( - KEY_ACTION_MODE_CLOSED_BY_DRAWER, - mActionModeClosedByDrawer - ); - SparseBooleanArrayParcelable sbap = savedInstanceState.getParcelable( - KEY_SELECTION_WHEN_CLOSED_BY_DRAWER - ); - if (sbap != null) { - mSelectionWhenActionModeClosedByDrawer = sbap.getSparseBooleanArray(); - } - } - } - - private void clearLocalSearchView() { - FragmentExtKt.hideSoftKeyboard(this); - mFileListAdapter.clearFilterBySearch(); - if (mSearchView != null) { - mSearchView.onActionViewCollapsed(); - } - } - - /** - * Init listener that will handle interactions in multiple selection mode. - */ - private void setChoiceModeAsMultipleModal(Bundle savedInstanceState) { - setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); - mMultiChoiceModeListener = new MultiChoiceModeListener(); - if (savedInstanceState != null) { - mMultiChoiceModeListener.loadStateFrom(savedInstanceState); - } - setMultiChoiceModeListener(mMultiChoiceModeListener); - ((FileActivity) requireActivity()).addDrawerListener(mMultiChoiceModeListener); - } - - /** - * Saves the current listed folder - */ - @Override - public void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putParcelable(KEY_FILE, mFile); - outState.putParcelable(KEY_FILE_LIST_OPTION, mFileListOption); - - // If this fragment is used to show target folders where a selected file/folder can be - // copied/moved, multiple choice is disabled - if (mMultiChoiceModeListener != null) { - mMultiChoiceModeListener.storeStateIn(outState); - } - } - - /** - * Call this, when the user presses the up button. - *

- * Tries to move up the current folder one level. If the parent folder was removed from the - * database, it continues browsing up until finding an existing folders. - *

- * return Count of folder levels browsed up. - */ - public int onBrowseUp() { - OCFile parentDir; - int moveCount = 0; - - if (mFile != null) { - FileDataStorageManager storageManager = mContainerActivity.getStorageManager(); - - String parentPath = null; - if (mFile.getParentId() != FileDataStorageManager.ROOT_PARENT_ID) { - parentPath = new File(mFile.getRemotePath()).getParent(); - parentPath = parentPath.endsWith(File.separator) ? parentPath : - parentPath + File.separator; - parentDir = storageManager.getFileByPath(parentPath); - moveCount++; - } else { - parentDir = storageManager.getFileByPath(OCFile.ROOT_PATH); - } - while (parentDir == null) { - parentPath = new File(parentPath).getParent(); - parentPath = parentPath.endsWith(File.separator) ? parentPath : - parentPath + File.separator; - parentDir = storageManager.getFileByPath(parentPath); - moveCount++; - } // exit is granted because storageManager.getFileByPath("/") never returns null - - // FIXME: 13/10/2020 : New_arch: Av.Offline -// if (mFileListOption.isAvailableOffline() && !parentDir.isAvailableOffline()) { -// parentDir = storageManager.getFileByPath(OCFile.ROOT_PATH); -// } - - if (mFileListOption.isSharedByLink() && !parentDir.getSharedByLink()) { - parentDir = storageManager.getFileByPath(OCFile.ROOT_PATH); - } - - mFile = parentDir; - - listDirectoryWidthAnimationUp(mFile); - - onRefresh(false); - - // restore index and top position - restoreIndexAndTopPosition(); - - } // else - should never happen now - - return moveCount; - } - - private void listDirectoryWithAnimationDown(final OCFile file) { - if (isInPowerSaveMode()) { - listDirectory(file); - } else { - Animation fadeOutFront = AnimationUtils.loadAnimation(getContext(), R.anim.dir_fadeout_front); - Handler eventHandler = new Handler(); - - // This is a ugly hack for getting rid of the "ArrayOutOfBound" exception we get when we - // call listDirectory() from the Animation callback - eventHandler.postDelayed(() -> { - listDirectory(file); - Animation fadeInBack = AnimationUtils.loadAnimation(getContext(), R.anim.dir_fadein_back); - getListView().setAnimation(fadeInBack); - }, getResources().getInteger(R.integer.folder_animation_duration)); - getListView().startAnimation(fadeOutFront); - } - } - - private boolean isInPowerSaveMode() { - PowerManager powerManager = (PowerManager) requireActivity().getSystemService(Context.POWER_SERVICE); - return (powerManager != null) && powerManager.isPowerSaveMode(); - } - - private void listDirectoryWidthAnimationUp(final OCFile file) { - if (isInPowerSaveMode()) { - listDirectory(file); - } else { - if (getListView().getVisibility() == View.GONE) { - listDirectory(file); - Animation fadeInFront = AnimationUtils.loadAnimation(getContext(), R.anim.dir_fadein_front); - getListView().startAnimation(fadeInFront); - return; - } - - Handler eventHandler = new Handler(); - Animation fadeOutBack = AnimationUtils.loadAnimation(getContext(), R.anim.dir_fadeout_back); - - // This is a ugly hack for getting rid of the "ArrayOutOfBound" exception we get when we - // call listDirectory() from the Animation callback - eventHandler.postDelayed(() -> { - listDirectory(file); - Animation fadeInFront = AnimationUtils.loadAnimation(getContext(), R.anim.dir_fadein_front); - getListView().startAnimation(fadeInFront); - }, getResources().getInteger(R.integer.folder_animation_duration)); - getListView().startAnimation(fadeOutBack); - } - } - - @Override - public void onItemClick(AdapterView l, View v, int position, long id) { - OCFile file = (OCFile) mFileListAdapter.getItem(position); - if (file != null) { - if (file.isFolder()) { - listDirectoryWithAnimationDown(file); - // then, notify parent activity to let it update its state and view - mContainerActivity.onBrowsedDownTo(file); - // save index and top position - saveIndexAndTopPosition(position); - } else { /// Click on a file - if (PreviewImageFragment.canBePreviewed(file)) { - // preview image - it handles the sync, if needed - ((FileDisplayActivity) mContainerActivity).startImagePreview(file); - } else if (PreviewTextFragment.canBePreviewed(file)) { - ((FileDisplayActivity) mContainerActivity).startTextPreview(file); - mContainerActivity.getFileOperationsHelper().syncFile(file); - - } else if (PreviewAudioFragment.canBePreviewed(file)) { - // media preview - ((FileDisplayActivity) mContainerActivity).startAudioPreview(file, 0); - mContainerActivity.getFileOperationsHelper().syncFile(file); - - } else if (PreviewVideoFragment.canBePreviewed(file) && - !fileIsDownloading(file)) { - // FIXME: 13/10/2020 : New_arch: Av.Offline - // Available offline exception, don't initialize streaming - // if (!file.isAvailableLocally() && file.isAvailableOffline()) { - if (file.isAvailableLocally()) { - // sync file content, then open with external apps - ((FileDisplayActivity) mContainerActivity).startSyncThenOpen(file); - } else { - // media preview - ((FileDisplayActivity) mContainerActivity).startVideoPreview(file, 0); - } - - // If the file is already downloaded sync it, just to update it if there is a - // new available file version - if (file.isAvailableLocally()) { - mContainerActivity.getFileOperationsHelper().syncFile(file); - } - } else { - // sync file content, then open with external apps - ((FileDisplayActivity) mContainerActivity).startSyncThenOpen(file); - } - - } - - } else { - Timber.d("Null object in ListAdapter!!"); - } - - } - - /** - * @return 'true' if the file is being downloaded, 'false' otherwise. - */ - private boolean fileIsDownloading(OCFile file) { - return WorkManagerExtKt.isDownloadPending( - WorkManager.getInstance(getContext()), - ((FileActivity) mContainerActivity).getAccount(), - file - ); - } - - public void selectAll() { - for (int i = 0; i < mFileListAdapter.getCount(); i++) { - getListView().setItemChecked(i, true); - } - } - - public int getNoOfItems() { - return getListView().getCount(); - } - - /** - * Start the appropriate action(s) on the currently selected files given menu selected by the user. - * - * @param menuId Identifier of the action menu selected by the user - * @return 'true' if the menu selection started any action, 'false' otherwise. - */ - private boolean onFileActionChosen(int menuId) { - final ArrayList checkedFiles = mFileListAdapter.getCheckedItems(getListView()); - if (checkedFiles.size() <= 0) { - return false; - } - - if (checkedFiles.size() == 1) { - /// action only possible on a single file - OCFile singleFile = checkedFiles.get(0); - switch (menuId) { - case R.id.action_share_file: { - mContainerActivity.getFileOperationsHelper().showShareFile(singleFile); - mEnableSelectAll = false; - return true; - } - case R.id.action_open_file_with: { - if (!singleFile.isAvailableLocally()) { // Download the file - ((FileDisplayActivity) mContainerActivity).startDownloadForOpening(singleFile); - } else { - mContainerActivity.getFileOperationsHelper().openFile(singleFile); - } - return true; - } - case R.id.action_rename_file: { - RenameFileDialogFragment dialog = RenameFileDialogFragment.newInstance(singleFile); - dialog.show(getFragmentManager(), FileDetailFragment.FTAG_RENAME_FILE); - return true; - } - case R.id.action_see_details: { - if (mActiveActionMode != null) { - mActiveActionMode.finish(); - } - mContainerActivity.showDetails(singleFile); - return true; - } - case R.id.action_send_file: { - // Obtain the file - if (!singleFile.isAvailableLocally()) { // Download the file - Timber.d("%s : File must be downloaded", singleFile.getRemotePath()); - ((FileDisplayActivity) mContainerActivity).startDownloadForSending(singleFile); - } else { - mContainerActivity.getFileOperationsHelper().sendDownloadedFile(singleFile); - } - return true; - } - } - } - - /// actions possible on a batch of files - switch (menuId) { - case R.id.file_action_select_all: { - selectAll(); - return true; - } - case R.id.action_select_inverse: { - for (int i = 0; i < mFileListAdapter.getCount(); i++) { - if (getListView().isItemChecked(i)) { - getListView().setItemChecked(i, false); - } else { - getListView().setItemChecked(i, true); - } - } - return true; - } - case R.id.action_send_file: { - if (checkedFiles.size() > 1 && filesAreDown(checkedFiles)) { - mContainerActivity.getFileOperationsHelper().sendDownloadedFiles(checkedFiles); - } - return true; - } - case R.id.action_remove_file: { - RemoveFilesDialogFragment dialog = RemoveFilesDialogFragment.newInstance(checkedFiles); - dialog.show(getFragmentManager(), ConfirmationDialogFragment.FTAG_CONFIRMATION); - return true; - } - case R.id.action_download_file: - case R.id.action_sync_file: { - mContainerActivity.getFileOperationsHelper().syncFiles(checkedFiles); - return true; - } - case R.id.action_cancel_sync: { - ((FileDisplayActivity) mContainerActivity).cancelTransference(checkedFiles); - return true; - } - case R.id.action_set_available_offline: { - mContainerActivity.getFileOperationsHelper().toggleAvailableOffline(checkedFiles, true); - getListView().invalidateViews(); - return true; - } - case R.id.action_unset_available_offline: { - mContainerActivity.getFileOperationsHelper().toggleAvailableOffline(checkedFiles, false); - getListView().invalidateViews(); - invalidateActionMode(); - if (mFileListOption.isAvailableOffline()) { - onRefresh(); - } - return true; - } - case R.id.action_move: { - Intent action = new Intent(getActivity(), FolderPickerActivity.class); - action.putExtra(FolderPickerActivity.EXTRA_PICKER_OPTION, FolderPickerActivity.PickerMode.MOVE); - action.putParcelableArrayListExtra(FolderPickerActivity.EXTRA_FILES, checkedFiles); - requireActivity().startActivityForResult(action, FileDisplayActivity.REQUEST_CODE__MOVE_FILES); - return true; - } - case R.id.action_copy: - Intent action = new Intent(getActivity(), FolderPickerActivity.class); - action.putExtra(FolderPickerActivity.EXTRA_PICKER_OPTION, FolderPickerActivity.PickerMode.COPY); - action.putParcelableArrayListExtra(FolderPickerActivity.EXTRA_FILES, checkedFiles); - requireActivity().startActivityForResult(action, FileDisplayActivity.REQUEST_CODE__COPY_FILES); - return true; - default: - return false; - } - } - - /** - * Use this to query the {@link OCFile} that is currently - * being displayed by this fragment - * - * @return The currently viewed OCFile - */ - public OCFile getCurrentFile() { - return mFile; - } - - /** - * Calls {@link OCFileListFragment#listDirectory(OCFile)} with a null parameter - */ - public void listDirectory(boolean reloadData) { - if (reloadData) { - listDirectory(null); - } else { - getListView().invalidateViews(); - } - } - - /** - * Lists the given directory on the view. When the input parameter is null, - * it will either refresh the last known directory. list the root - * if there never was a directory. - * - * @param directory File to be listed - */ - public void listDirectory(OCFile directory) { - if (mContainerActivity == null) { - Timber.e("No container activity attached"); - return; - } - - FileDataStorageManager storageManager = mContainerActivity.getStorageManager(); - if (storageManager != null) { - - // Check input parameters for null - if (directory == null) { - if (mFile != null) { - directory = mFile; - } else { - directory = storageManager.getFileByPath(OCFile.ROOT_PATH); - if (directory == null) { - return; // no files, wait for sync - } - } - } - - // If that's not a directory -> List its parent - if (!directory.isFolder()) { - Timber.w("You see, that is not a directory -> %s", directory.toString()); - directory = storageManager.getFileById(directory.getParentId()); - } - - mFileListAdapter.swapDirectory(directory, storageManager); - if (mFile == null || !mFile.equals(directory)) { - mCurrentListView.setSelection(0); - } - mFile = directory; - - updateLayout(); - } - } - - private boolean filesAreDown(ArrayList checkedFiles) { - for (int i = 0; i < checkedFiles.size(); i++) { - if (!checkedFiles.get(i).isAvailableLocally()) { - Timber.d("%s : File must be downloaded", checkedFiles.get(i).getRemotePath()); - return false; - } - } - return true; - } - - private void updateLayout() { - if (!isShowingJustFolders()) { - int filesCount = 0, foldersCount = 0; - int count = mFileListAdapter.getCount(); - OCFile file; - for (int i = 0; i < count; i++) { - file = (OCFile) mFileListAdapter.getItem(i); - if (file.isFolder()) { - foldersCount++; - } else { - if (!file.isHidden()) { - filesCount++; - } - } - } - - if (count == 0) { - int emptyMessage; - if (mFileListOption == FileListOption.AV_OFFLINE) { - emptyMessage = R.string.file_list_empty_available_offline; - } else if (mFileListOption == FileListOption.SHARED_BY_LINK) { - emptyMessage = R.string.file_list_empty_shared_by_links; - } else { - emptyMessage = R.string.file_list_empty; - } - - setMessageForEmptyList(getString(emptyMessage)); - } - - // decide grid vs list view - OwnCloudVersion version = AccountUtils.getServerVersion(((FileActivity) mContainerActivity).getAccount()); - if (version != null && isGridViewPreferred(mFile)) { - switchToGridView(); - mSortOptionsView.setViewTypeSelected(ViewType.VIEW_TYPE_GRID); - } else { - switchToListView(); - mSortOptionsView.setViewTypeSelected(ViewType.VIEW_TYPE_LIST); - } - - // set footer text - setFooterText(generateFooterText(filesCount, foldersCount)); - } - invalidateActionMode(); - clearLocalSearchView(); - } - - private void invalidateActionMode() { - if (mActiveActionMode != null) { - mActiveActionMode.invalidate(); - } - } - - private String generateFooterText(int filesCount, int foldersCount) { - String output; - if (filesCount <= 0) { - if (foldersCount <= 0) { - output = ""; - - } else if (foldersCount == 1) { - output = getResources().getString(R.string.file_list__footer__folder); - - } else { // foldersCount > 1 - output = getResources().getString(R.string.file_list__footer__folders, foldersCount); - } - - } else if (filesCount == 1) { - if (foldersCount <= 0) { - output = getResources().getString(R.string.file_list__footer__file); - - } else if (foldersCount == 1) { - output = getResources().getString(R.string.file_list__footer__file_and_folder); - - } else { // foldersCount > 1 - output = getResources().getString(R.string.file_list__footer__file_and_folders, foldersCount); - } - } else { // filesCount > 1 - if (foldersCount <= 0) { - output = getResources().getString(R.string.file_list__footer__files, filesCount); - - } else if (foldersCount == 1) { - output = getResources().getString(R.string.file_list__footer__files_and_folder, filesCount); - - } else { // foldersCount > 1 - output = getResources().getString( - R.string.file_list__footer__files_and_folders, filesCount, foldersCount - ); - - } - } - return output; - } - - private void sortByName(boolean descending) { - mFileListAdapter.setSortOrder(FileStorageUtils.SORT_NAME, descending); - } - - private void sortByDate(boolean descending) { - mFileListAdapter.setSortOrder(FileStorageUtils.SORT_DATE, descending); - } - - private void sortBySize(boolean descending) { - mFileListAdapter.setSortOrder(FileStorageUtils.SORT_SIZE, descending); - } - - /** - * Determines if user set folder to grid or list view. If folder is not set itself, - * it finds a parent that is set (at least root is set). - * - * @param file Folder to check. - * @return 'true' is folder should be shown in grid mode, 'false' if list mode is preferred. - */ - private boolean isGridViewPreferred(OCFile file) { - if (file != null) { - OCFile fileToTest = file; - OCFile parentDir; - String parentPath = null; - FileDataStorageManager storageManager = mContainerActivity.getStorageManager(); - - SharedPreferences setting = - requireActivity().getSharedPreferences(GRID_IS_PREFERED_PREFERENCE, Context.MODE_PRIVATE); - - if (setting.contains(String.valueOf(fileToTest.getId()))) { - return setting.getBoolean(String.valueOf(fileToTest.getId()), false); - } else { - do { - if (fileToTest.getParentId() != FileDataStorageManager.ROOT_PARENT_ID) { - parentPath = new File(fileToTest.getRemotePath()).getParent(); - parentPath = parentPath.endsWith(File.separator) ? parentPath : - parentPath + File.separator; - parentDir = storageManager.getFileByPath(parentPath); - } else { - parentDir = storageManager.getFileByPath(OCFile.ROOT_PATH); - } - - while (parentDir == null) { - parentPath = new File(parentPath).getParent(); - parentPath = parentPath.endsWith(File.separator) ? parentPath : - parentPath + File.separator; - parentDir = storageManager.getFileByPath(parentPath); - } - fileToTest = parentDir; - } while (endWhile(parentDir, setting)); - return setting.getBoolean(String.valueOf(fileToTest.getId()), false); - } - } else { - return false; - } - } - - private boolean endWhile(OCFile parentDir, SharedPreferences setting) { - if (parentDir.getRemotePath().compareToIgnoreCase(OCFile.ROOT_PATH) == 0) { - return false; - } else { - return !setting.contains(String.valueOf(parentDir.getId())); - } - } - - public void setListAsPreferred() { - saveGridAsPreferred(false); - switchToListView(); - } - - public void setGridAsPreferred() { - saveGridAsPreferred(true); - switchToGridView(); - } - - private void saveGridAsPreferred(boolean setGrid) { - SharedPreferences setting = requireActivity().getSharedPreferences( - GRID_IS_PREFERED_PREFERENCE, Context.MODE_PRIVATE - ); - - SharedPreferences.Editor editor = setting.edit(); - editor.putBoolean(String.valueOf(mFile.getId()), setGrid); - editor.apply(); - } - - /** - * Show a temporary message in a Snackbar bound to the content view of the parent Activity - * - * @param messageResource Message to show. - */ - private void showSnackMessage(int messageResource) { - Snackbar snackbar = Snackbar.make( - requireActivity().findViewById(R.id.coordinator_layout), - messageResource, - Snackbar.LENGTH_LONG - ); - snackbar.show(); - } - - private void showSnackMessage(String messageResource) { - Snackbar snackbar = Snackbar.make( - requireActivity().findViewById(R.id.coordinator_layout), - messageResource, - Snackbar.LENGTH_LONG - ); - snackbar.show(); - } - - public void setSearchListener(SearchView searchView){ - searchView.setOnQueryTextFocusChangeListener(this); - searchView.setOnQueryTextListener(this); - } -} diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/helpers/UriUploader.java b/owncloudApp/src/main/java/com/owncloud/android/ui/helpers/UriUploader.java index 19901beead9..56a0978a64c 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/helpers/UriUploader.java +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/helpers/UriUploader.java @@ -101,7 +101,7 @@ public UriUploaderResultCode uploadUris() { } if (!contentUris.isEmpty()) { - /// content: uris will be copied to temporary files before calling {@link FileUploader} + /// content: uris will be copied to temporary files before calling the upload usecase copyThenUpload(contentUris.toArray(new Uri[0]), mUploadPath); // Listen to CopyAndUploadContentUrisTask before killing the app or a SecurityException may appear. diff --git a/owncloudApp/src/main/java/com/owncloud/android/usecases/UploadEnqueuedBy.kt b/owncloudApp/src/main/java/com/owncloud/android/usecases/UploadEnqueuedBy.kt index 759010bed26..52e23124828 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/usecases/UploadEnqueuedBy.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/usecases/UploadEnqueuedBy.kt @@ -18,7 +18,6 @@ */ package com.owncloud.android.usecases -import com.owncloud.android.operations.UploadFileOperation import com.owncloud.android.workers.UploadFileFromContentUriWorker @@ -35,15 +34,6 @@ enum class UploadEnqueuedBy { ENQUEUED_AS_CAMERA_UPLOAD_PICTURE, ENQUEUED_AS_CAMERA_UPLOAD_VIDEO; - fun fromLegacyCreatedBy(oldCreatedBy: Int): UploadEnqueuedBy { - return when (oldCreatedBy) { - UploadFileOperation.CREATED_BY_USER -> ENQUEUED_BY_USER - UploadFileOperation.CREATED_AS_CAMERA_UPLOAD_PICTURE -> ENQUEUED_AS_CAMERA_UPLOAD_PICTURE - UploadFileOperation.CREATED_AS_CAMERA_UPLOAD_VIDEO -> ENQUEUED_AS_CAMERA_UPLOAD_VIDEO - else -> ENQUEUED_BY_USER - } - } - fun toTransferTag(): String { return when (this) { ENQUEUED_BY_USER -> UploadFileFromContentUriWorker.TRANSFER_TAG_MANUAL_UPLOAD diff --git a/owncloudApp/src/main/java/com/owncloud/android/utils/Extras.java b/owncloudApp/src/main/java/com/owncloud/android/utils/Extras.java index abc11abbad5..ea3a1f024a4 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/utils/Extras.java +++ b/owncloudApp/src/main/java/com/owncloud/android/utils/Extras.java @@ -31,7 +31,6 @@ public class Extras { // from FileDownloader public static final String EXTRA_DOWNLOAD_RESULT = "RESULT"; - // from FileUploader public static final String EXTRA_OLD_REMOTE_PATH = "OLD_REMOTE_PATH"; public static final String EXTRA_OLD_FILE_PATH = "OLD_FILE_PATH"; public static final String EXTRA_UPLOAD_RESULT = "RESULT"; diff --git a/owncloudApp/src/main/java/com/owncloud/android/workers/CameraUploadsWorker.kt b/owncloudApp/src/main/java/com/owncloud/android/workers/CameraUploadsWorker.kt index 8b80f9e21d7..56e34700132 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/workers/CameraUploadsWorker.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/workers/CameraUploadsWorker.kt @@ -35,9 +35,8 @@ import com.owncloud.android.domain.camerauploads.model.UploadBehavior import com.owncloud.android.domain.camerauploads.usecases.GetCameraUploadsConfigurationUseCase import com.owncloud.android.domain.camerauploads.usecases.SavePictureUploadsConfigurationUseCase import com.owncloud.android.domain.camerauploads.usecases.SaveVideoUploadsConfigurationUseCase -import com.owncloud.android.operations.UploadFileOperation.CREATED_AS_CAMERA_UPLOAD_PICTURE -import com.owncloud.android.operations.UploadFileOperation.CREATED_AS_CAMERA_UPLOAD_VIDEO import com.owncloud.android.presentation.ui.settings.SettingsActivity +import com.owncloud.android.usecases.UploadEnqueuedBy import com.owncloud.android.usecases.UploadFileFromContentUriUseCase import com.owncloud.android.utils.MimetypeIconUtil import com.owncloud.android.utils.NotificationUtils @@ -143,8 +142,8 @@ class CameraUploadsWorker( accountName = folderBackUpConfiguration.accountName, behavior = folderBackUpConfiguration.behavior, createdByWorker = when (syncType) { - SyncType.PICTURE_UPLOADS -> CREATED_AS_CAMERA_UPLOAD_PICTURE - SyncType.VIDEO_UPLOADS -> CREATED_AS_CAMERA_UPLOAD_VIDEO + SyncType.PICTURE_UPLOADS -> UploadEnqueuedBy.ENQUEUED_AS_CAMERA_UPLOAD_PICTURE.ordinal + SyncType.VIDEO_UPLOADS -> UploadEnqueuedBy.ENQUEUED_AS_CAMERA_UPLOAD_VIDEO.ordinal } ) enqueueSingleUpload( diff --git a/owncloudApp/src/main/res/layout/files_folder_picker.xml b/owncloudApp/src/main/res/layout/files_folder_picker.xml index 7e839c2fbbd..d94ebaaaa2f 100644 --- a/owncloudApp/src/main/res/layout/files_folder_picker.xml +++ b/owncloudApp/src/main/res/layout/files_folder_picker.xml @@ -49,7 +49,6 @@ along with this program. If not, see . style="?android:buttonStyle" android:layout_width="50dp" android:layout_height="match_parent" - android:layout_alignParentEnd="true" android:src="@drawable/ic_home_black" android:visibility="gone" /> diff --git a/owncloudApp/src/main/res/layout/grid_image.xml b/owncloudApp/src/main/res/layout/grid_image.xml deleted file mode 100644 index 8ab4cd14985..00000000000 --- a/owncloudApp/src/main/res/layout/grid_image.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/owncloudApp/src/main/res/layout/list_fragment.xml b/owncloudApp/src/main/res/layout/list_fragment.xml deleted file mode 100644 index a130d63d42e..00000000000 --- a/owncloudApp/src/main/res/layout/list_fragment.xml +++ /dev/null @@ -1,160 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/owncloudApp/src/main/res/layout/list_fragment_expandable.xml b/owncloudApp/src/main/res/layout/list_fragment_expandable.xml index d2d112e4ffa..5c736d2ebd2 100755 --- a/owncloudApp/src/main/res/layout/list_fragment_expandable.xml +++ b/owncloudApp/src/main/res/layout/list_fragment_expandable.xml @@ -1,9 +1,4 @@ - + android:layout_height="32dp" />