diff --git a/owncloudApp/src/main/AndroidManifest.xml b/owncloudApp/src/main/AndroidManifest.xml index ba709a1c1f0..4df13b7519a 100644 --- a/owncloudApp/src/main/AndroidManifest.xml +++ b/owncloudApp/src/main/AndroidManifest.xml @@ -80,8 +80,8 @@ + android:exported="true" + android:theme="@style/Theme.ownCloud.Splash"> @@ -89,15 +89,33 @@ + android:windowSoftInputMode="adjustPan"> + + + + + + + + + + + + android:exported="true" + android:taskAffinity=""> @@ -216,11 +234,11 @@ + android:windowSoftInputMode="adjustResize"> @@ -259,4 +277,4 @@ - \ No newline at end of file + diff --git a/owncloudApp/src/main/java/com/owncloud/android/MainApp.kt b/owncloudApp/src/main/java/com/owncloud/android/MainApp.kt index 9cb6c6ad38c..e65b7c5ed73 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/MainApp.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/MainApp.kt @@ -1,4 +1,4 @@ -/* +/** * ownCloud Android client application * * @author masensio @@ -7,6 +7,7 @@ * @author Christian Schabesberger * @author David Crespo Ríos * @author Juan Carlos Garrote Gascón + * @author Fernando Sanz Velasco * Copyright (C) 2022 ownCloud GmbH. * * This program is free software: you can redistribute it and/or modify diff --git a/owncloudApp/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.kt b/owncloudApp/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.kt index 09ec5a4279c..cb01f45edb8 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/datamodel/FileDataStorageManager.kt @@ -114,8 +114,6 @@ import java.io.FileOutputStream import java.io.IOException import java.io.InputStream import java.io.OutputStream -import java.util.ArrayList -import java.util.HashSet import java.util.Vector class FileDataStorageManager { @@ -311,6 +309,15 @@ class FileDataStorageManager { return file } + fun getFileByPrivateLink(privateLink: String): OCFile? { + val cursor = getFileCursorForValue(FILE_PRIVATE_LINK, privateLink) ?: return null + val file: OCFile? = if (cursor.moveToFirst()) { + createFileInstance(cursor) + } else null + cursor.close() + return file + } + fun fileExists(id: Long): Boolean = fileExists(_ID, id.toString()) fun fileExists(path: String): Boolean = fileExists(FILE_PATH, path) @@ -1106,6 +1113,7 @@ class FileDataStorageManager { isDownloading = it.getIntFromColumnOrThrow(FILE_IS_DOWNLOADING) == 1 etagInConflict = it.getStringFromColumnOrThrow(FILE_ETAG_IN_CONFLICT) privateLink = it.getStringFromColumnOrThrow(FILE_PRIVATE_LINK) + owner = it.getStringFromColumnOrThrow(FILE_ACCOUNT_OWNER) } } diff --git a/owncloudApp/src/main/java/com/owncloud/android/datamodel/OCFile.java b/owncloudApp/src/main/java/com/owncloud/android/datamodel/OCFile.java index 6c0fa4559a8..97de031d9f0 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/datamodel/OCFile.java +++ b/owncloudApp/src/main/java/com/owncloud/android/datamodel/OCFile.java @@ -116,6 +116,7 @@ public static AvailableOfflineStatus fromValue(int value) { private boolean mSharedByLink; private boolean mSharedWithSharee; private String mPrivateLink; + private String mOwner; /** * URI to the local path of the file contents, if stored in the device; cached after first call @@ -125,7 +126,7 @@ public static AvailableOfflineStatus fromValue(int value) { /** * Exportable URI to the local path of the file contents, if stored in the device. - * + *

* Cached after first call, until changed. */ private Uri mExposedFileUri; @@ -133,7 +134,7 @@ public static AvailableOfflineStatus fromValue(int value) { /** * Create new {@link OCFile} with given path. - * + *

* The path received must be URL-decoded. Path separator must be File.separator, and it must be the first * character in 'path'. * @@ -179,6 +180,7 @@ private OCFile(Parcel source) { mEtagInConflict = source.readString(); mSharedWithSharee = source.readInt() == 1; mPrivateLink = source.readString(); + mOwner = source.readString(); } @@ -206,6 +208,7 @@ public void writeToParcel(Parcel dest, int flags) { dest.writeString(mEtagInConflict); dest.writeInt(mSharedWithSharee ? 1 : 0); dest.writeString(mPrivateLink); + dest.writeString(mOwner); } /** @@ -352,7 +355,7 @@ public long getModificationTimestamp() { /** * Set a UNIX timestamp of the time the time the file was modified. - * + *

* To update with the value returned by the server in every synchronization of the properties * of this file. * @@ -374,7 +377,7 @@ public long getModificationTimestampAtLastSyncForData() { /** * Set a UNIX timestamp of the time the time the file was modified. - * + *

* To update with the value returned by the server in every synchronization of THE CONTENTS * of this file. * @@ -396,7 +399,7 @@ public String getFileName() { /** * Sets the name of the file - * + *

* Does nothing if the new name is null, empty or includes "/" ; or if the file is the root * directory */ @@ -507,6 +510,7 @@ public long getParentId() { /** * get remote path of parent file + * * @return remote path */ public String getParentRemotePath() { @@ -547,7 +551,7 @@ public AvailableOfflineStatus getAvailableOfflineStatus() { } /** - * @return 'True' when + * @return 'True' when */ public boolean isAvailableOffline() { return (mAvailableOfflineStatus != AvailableOfflineStatus.NOT_AVAILABLE_OFFLINE); @@ -651,8 +655,8 @@ public boolean isText() { } /** - * @param type Type to match in the file MIME type; it's MUST include the trailing "/" - * @return 'True' if the file MIME type matches the received parameter in the type part. + * @param type Type to match in the file MIME type; it's MUST include the trailing "/" + * @return 'True' if the file MIME type matches the received parameter in the type part. */ private boolean isOfType(String type) { return ( @@ -750,4 +754,12 @@ public void copyLocalPropertiesFrom(OCFile sourceFile) { setTreeEtag(sourceFile.getTreeEtag()); setEtagInConflict(sourceFile.getEtagInConflict()); } + + public String getOwner() { + return mOwner; + } + + public void setOwner(String owner) { + mOwner = (owner == null) ? "" : owner; + } } diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileActivity.java b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileActivity.java index bccba2ebc95..44e78a1b56c 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileActivity.java +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileActivity.java @@ -73,6 +73,8 @@ public class FileActivity extends DrawerActivity public static final String EXTRA_ACCOUNT = "com.owncloud.android.ui.activity.ACCOUNT"; public static final String EXTRA_FROM_NOTIFICATION = "com.owncloud.android.ui.activity.FROM_NOTIFICATION"; + public static final String EXTRA_ALREADY_HANDLED_DEEP_LINK = + "com.owncloud.android.ui.activity.ALREADY_HANDLED_DEEP_LINK"; public static final String EXTRA_FILE_LIST_OPTION = "EXTRA_FILE_LIST_OPTION"; private static final String KEY_WAITING_FOR_OP_ID = "WAITING_FOR_OP_ID"; @@ -98,6 +100,8 @@ public class FileActivity extends DrawerActivity */ private boolean mFromNotification; + private boolean mAlreadyHandledDeepLink = false; + /** * Messages handler associated to the main thread and the life cycle of the activity */ @@ -131,6 +135,7 @@ protected void onCreate(Bundle savedInstanceState) { if (savedInstanceState != null) { mFile = savedInstanceState.getParcelable(FileActivity.EXTRA_FILE); mFromNotification = savedInstanceState.getBoolean(FileActivity.EXTRA_FROM_NOTIFICATION); + mAlreadyHandledDeepLink = savedInstanceState.getBoolean(FileActivity.EXTRA_ALREADY_HANDLED_DEEP_LINK); mFileOperationsHelper.setOpIdWaitingFor( savedInstanceState.getLong(KEY_WAITING_FOR_OP_ID, Long.MAX_VALUE) ); @@ -214,6 +219,7 @@ protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putParcelable(FileActivity.EXTRA_FILE, mFile); outState.putBoolean(FileActivity.EXTRA_FROM_NOTIFICATION, mFromNotification); + outState.putBoolean(FileActivity.EXTRA_ALREADY_HANDLED_DEEP_LINK, mAlreadyHandledDeepLink); outState.putLong(KEY_WAITING_FOR_OP_ID, mFileOperationsHelper.getOpIdWaitingFor()); if (getSupportActionBar() != null && getSupportActionBar().getTitle() != null) { // Null check in case the actionbar is used in ActionBar.NAVIGATION_MODE_LIST @@ -247,6 +253,14 @@ public boolean fromNotification() { return mFromNotification; } + public void setAlreadyHandledDeepLink(boolean alreadyHandledDeepLink) { + mAlreadyHandledDeepLink = alreadyHandledDeepLink; + } + + public boolean isAlreadyHandledDeepLink() { + return mAlreadyHandledDeepLink; + } + public OperationsServiceBinder getOperationsServiceBinder() { return mOperationsServiceBinder; } diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt index 7afd278a38b..4363aaf927d 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt @@ -7,8 +7,9 @@ * @author Christian Schabesberger * @author Shashvat Kedia * @author Abel García de Prada + * @author Fernando Sanz Velasco * Copyright (C) 2011 Bartek Przybylski - * Copyright (C) 2020 ownCloud GmbH. + * Copyright (C) 2022 ownCloud GmbH. * * * This program is free software: you can redistribute it and/or modify @@ -51,6 +52,7 @@ import com.owncloud.android.AppRater import com.owncloud.android.BuildConfig import com.owncloud.android.MainApp import com.owncloud.android.R +import com.owncloud.android.authentication.AccountUtils import com.owncloud.android.databinding.ActivityMainBinding import com.owncloud.android.datamodel.FileDataStorageManager import com.owncloud.android.datamodel.OCFile @@ -225,6 +227,11 @@ class FileDisplayActivity : FileActivity(), FileFragment.ContainerActivity, OnEn createMinFragments() } + val dataIntent: Uri? = intent.data + dataIntent?.let { + handleDeepLink(dataIntent) + } + setBackgroundText() } @@ -415,7 +422,12 @@ class FileDisplayActivity : FileActivity(), FileFragment.ContainerActivity, OnEn fun refreshListOfFilesFragment(reloadData: Boolean) { val fileListFragment = listOfFilesFragment - fileListFragment?.listDirectory(reloadData) + if (intent.data == null || isAlreadyHandledDeepLink) { + fileListFragment?.listDirectory(reloadData) + } else { + fileListFragment?.listDirectory(getFileDiscovered(intent.data)) + } + } override fun onCreateOptionsMenu(menu: Menu): Boolean { @@ -1647,6 +1659,47 @@ class FileDisplayActivity : FileActivity(), FileFragment.ContainerActivity, OnEn manageOptionLockSelected(type) } + private fun handleDeepLink(uri: Uri?) { + if (uri != null && AccountUtils.getAccounts(applicationContext).isEmpty()) { + showMessageInSnackbar(message = getString(R.string.no_account_configured)) + } else if (uri != null && AccountUtils.getAccounts(applicationContext).size == 1) { + getFileDiscovered(uri).let { oCFile -> + if (oCFile != null) { + manageItem(oCFile) + } else { + showMessageInSnackbar(message = getString(R.string.no_file_found)) + } + } + } + } + + private fun getFileDiscovered(uri: Uri?): OCFile? { + return if (storageManager != null) { + storageManager.getFileByPrivateLink(uri.toString()) + } else { + null + } + } + + private fun manageItem(file: OCFile) { + onBrowsedDownTo(file) + setFile(file) + account = AccountUtils.getOwnCloudAccountByName(this, file.owner) + + if (file.isFolder) { + refreshListOfFilesFragment(true) + return + } + + if (PreviewImageFragment.canBePreviewed(file)) { + showDetails(file) + } else { + initFragmentsWithFile() + } + + isAlreadyHandledDeepLink = true + } + companion object { private const val TAG_LIST_OF_FILES = "LIST_OF_FILES" private const val TAG_SECOND_FRAGMENT = "SECOND_FRAGMENT" diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java b/owncloudApp/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java index 850ce3904f9..082711bfe5d 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/fragment/FileDetailFragment.java @@ -53,6 +53,9 @@ import com.owncloud.android.ui.controller.TransferProgressController; import com.owncloud.android.ui.dialog.RemoveFilesDialogFragment; import com.owncloud.android.ui.dialog.RenameFileDialogFragment; +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.utils.DisplayUtils; import com.owncloud.android.utils.MimetypeIconUtil; import com.owncloud.android.utils.PreferenceUtils; @@ -329,11 +332,33 @@ public void onClick(View v) { ((FileDisplayActivity) mContainerActivity).cancelTransference(getFile()); break; } + case R.id.fdIcon: { + displayFile(getFile()); + } default: Timber.e("Incorrect view clicked!"); } } + private void displayFile(OCFile 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 { + // sync file content, then open with external apps + ((FileDisplayActivity) mContainerActivity).startSyncThenOpen(file); + } + } + /** * Check if the fragment was created with an empty layout. An empty fragment can't show file details, must be * replaced. @@ -424,6 +449,7 @@ private void setFiletype(OCFile file) { } ImageView iv = getView().findViewById(R.id.fdIcon); + iv.setOnClickListener(this); if (iv != null) { Bitmap thumbnail; diff --git a/owncloudApp/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java b/owncloudApp/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java index 0ccae12d9f0..276e06a9d57 100644 --- a/owncloudApp/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java +++ b/owncloudApp/src/main/java/com/owncloud/android/ui/helpers/FileOperationsHelper.java @@ -224,6 +224,7 @@ public void syncFile(OCFile file) { intent.setAction(OperationsService.ACTION_SYNC_FILE); intent.putExtra(OperationsService.EXTRA_ACCOUNT, mFileActivity.getAccount()); intent.putExtra(OperationsService.EXTRA_REMOTE_PATH, file.getRemotePath()); + mWaitingForOpId = mFileActivity.getOperationsServiceBinder().queueNewOperation(intent); } else { diff --git a/owncloudApp/src/main/res/layout/file_details_fragment.xml b/owncloudApp/src/main/res/layout/file_details_fragment.xml index 275fc2d8cf2..2d170bf6a2d 100644 --- a/owncloudApp/src/main/res/layout/file_details_fragment.xml +++ b/owncloudApp/src/main/res/layout/file_details_fragment.xml @@ -45,8 +45,8 @@ android:layout_width="0dp" android:layout_height="0dp" android:ellipsize="end" - android:paddingStart="@dimen/standard_margin" android:gravity="center_vertical" + android:paddingStart="@dimen/standard_margin" android:text="" android:textAppearance="?android:attr/textAppearanceLarge" app:layout_constraintBottom_toBottomOf="@id/fdIcon" diff --git a/owncloudApp/src/main/res/values/setup.xml b/owncloudApp/src/main/res/values/setup.xml index 02dc17f0344..4c72ef5b03b 100644 --- a/owncloudApp/src/main/res/values/setup.xml +++ b/owncloudApp/src/main/res/values/setup.xml @@ -120,4 +120,8 @@ true + + * + /f/ + diff --git a/owncloudApp/src/main/res/values/strings.xml b/owncloudApp/src/main/res/values/strings.xml index cd80b155e84..1bf357b9542 100644 --- a/owncloudApp/src/main/res/values/strings.xml +++ b/owncloudApp/src/main/res/values/strings.xml @@ -671,4 +671,8 @@ Proceed Release note icon + + No account configured previously + No file found +