diff --git a/app/src/androidTest/java/com/owncloud/android/ui/dialog/DialogFragmentIT.kt b/app/src/androidTest/java/com/owncloud/android/ui/dialog/DialogFragmentIT.kt index 4f039da6bf63..4357d54b2fa0 100644 --- a/app/src/androidTest/java/com/owncloud/android/ui/dialog/DialogFragmentIT.kt +++ b/app/src/androidTest/java/com/owncloud/android/ui/dialog/DialogFragmentIT.kt @@ -76,6 +76,7 @@ import com.owncloud.android.utils.theme.CapabilityUtils import com.owncloud.android.utils.theme.ViewThemeUtils import io.mockk.mockk import org.junit.After +import org.junit.Assert.fail import org.junit.Rule import org.junit.Test import java.net.URI @@ -491,7 +492,12 @@ class DialogFragmentIT : AbstractIT() { json ) - val capability = fda.capabilities.apply { + val optionalCapability = fda.capabilities + if (optionalCapability.isEmpty) { + fail("capabilities is empty") + } + + val capability = optionalCapability.get().apply { richDocuments = CapabilityBooleanType.TRUE richDocumentsDirectEditing = CapabilityBooleanType.TRUE richDocumentsTemplatesAvailable = CapabilityBooleanType.TRUE diff --git a/app/src/main/java/com/nextcloud/client/mixins/SessionMixin.kt b/app/src/main/java/com/nextcloud/client/mixins/SessionMixin.kt index 1fff6f653bdb..8acc663a58b1 100644 --- a/app/src/main/java/com/nextcloud/client/mixins/SessionMixin.kt +++ b/app/src/main/java/com/nextcloud/client/mixins/SessionMixin.kt @@ -14,6 +14,7 @@ import android.os.Bundle import com.nextcloud.client.account.User import com.nextcloud.client.account.UserAccountManager import com.nextcloud.utils.extensions.isAnonymous +import com.owncloud.android.lib.common.utils.Log_OC import com.owncloud.android.lib.resources.status.OCCapability import com.owncloud.android.utils.theme.CapabilityUtils import java.util.Optional @@ -29,10 +30,21 @@ class SessionMixin(private val activity: Activity, private val accountManager: U var currentAccount: Account = getDefaultAccount() private set - val capabilities: OCCapability? - get() = getUser() - .map { CapabilityUtils.getCapability(it, activity) } - .orElse(null) + companion object { + private const val TAG = "SessionMixin" + } + + fun getCapabilities(): Optional { + val optionalUser = getUser() + if (optionalUser.isEmpty) { + Log_OC.e(TAG, "user is empty, returning empty capabilities") + return Optional.empty() + } + + val user = optionalUser.get() + val capability = CapabilityUtils.getCapability(user, activity) + return Optional.of(capability) + } fun setAccount(account: Account) { val validAccount = (accountManager.setCurrentOwnCloudAccount(account.name)) diff --git a/app/src/main/java/com/nextcloud/ui/composeActivity/ComposeActivity.kt b/app/src/main/java/com/nextcloud/ui/composeActivity/ComposeActivity.kt index c35f8fa6a506..23462b051eb0 100644 --- a/app/src/main/java/com/nextcloud/ui/composeActivity/ComposeActivity.kt +++ b/app/src/main/java/com/nextcloud/ui/composeActivity/ComposeActivity.kt @@ -128,12 +128,17 @@ class ComposeActivity : DrawerActivity() { val dao = NextcloudDatabase.instance().assistantDao() val sessionId = (currentScreen as? ComposeDestination.AssistantScreen)?.sessionId val client = nextcloudClient ?: return + val optionalCapability = capabilities + if (optionalCapability.isEmpty) { + return + } + val capability = optionalCapability.get() AssistantScreen( composeViewModel = composeViewModel, viewModel = AssistantViewModel( accountName = userAccountManager.user.accountName, - remoteRepository = AssistantRemoteRepositoryImpl(client, capabilities), + remoteRepository = AssistantRemoteRepositoryImpl(client, capability), localRepository = AssistantLocalRepositoryImpl(dao), sessionIdArg = sessionId ), @@ -141,7 +146,7 @@ class ComposeActivity : DrawerActivity() { remoteRepository = ConversationRemoteRepositoryImpl(client) ), activity = this, - capability = capabilities + capability = capability ) } diff --git a/app/src/main/java/com/owncloud/android/ui/activity/BaseActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/BaseActivity.java index 82a0598be8ec..8275a125f60f 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/BaseActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/activity/BaseActivity.java @@ -164,7 +164,7 @@ protected void startAccountCreation() { * @return Capabilities of the server where the current OC account lives. Null if the account is not * set yet. */ - public OCCapability getCapabilities() { + public Optional getCapabilities() { return sessionMixin.getCapabilities(); } diff --git a/app/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java index 6fb5f421af7c..07d818fbbec2 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/activity/DrawerActivity.java @@ -325,7 +325,11 @@ private void themeBottomNavigationMenu() { @SuppressFBWarnings("RV") private void checkAssistantBottomNavigationMenu() { - boolean isAssistantAvailable = getCapabilities().getAssistant().isTrue(); + final var optionalCapabilities = getCapabilities(); + boolean isAssistantAvailable = false; + if (optionalCapabilities.isPresent()) { + isAssistantAvailable = optionalCapabilities.get().getAssistant().isTrue(); + } bottomNavigationView .getMenu() @@ -407,26 +411,29 @@ private void setupQuotaElement() { public void updateHeader() { final var account = getAccount(); boolean isClientBranded = getResources().getBoolean(R.bool.is_branded_client); - final OCCapability capability = getCapabilities(); + final var optionalCapability = getCapabilities(); + if (optionalCapability.isPresent()) { + final var capability = optionalCapability.get(); - if (capability != null && account != null && capability.getServerBackground() != null && !isClientBranded) { - int primaryColor = themeColorUtils.unchangedPrimaryColor(account, this); - String serverLogoURL = capability.getServerLogo(); - - // set background to primary color - LinearLayout drawerHeader = mNavigationViewHeader.findViewById(R.id.drawer_header_view); - drawerHeader.setBackgroundColor(primaryColor); - - if (!TextUtils.isEmpty(serverLogoURL) && URLUtil.isValidUrl(serverLogoURL)) { - Target target = createSVGLogoTarget(primaryColor, capability); - getClientRepository().getNextcloudClient(nextcloudClient -> { - GlideHelper.INSTANCE.loadIntoTarget(DrawerActivity.this, - nextcloudClient, - serverLogoURL, - target, - R.drawable.background); - return Unit.INSTANCE; - }); + if (account != null && capability.getServerBackground() != null && !isClientBranded) { + int primaryColor = themeColorUtils.unchangedPrimaryColor(account, this); + String serverLogoURL = capability.getServerLogo(); + + // set background to primary color + LinearLayout drawerHeader = mNavigationViewHeader.findViewById(R.id.drawer_header_view); + drawerHeader.setBackgroundColor(primaryColor); + + if (!TextUtils.isEmpty(serverLogoURL) && URLUtil.isValidUrl(serverLogoURL)) { + Target target = createSVGLogoTarget(primaryColor, capability); + getClientRepository().getNextcloudClient(nextcloudClient -> { + GlideHelper.INSTANCE.loadIntoTarget(DrawerActivity.this, + nextcloudClient, + serverLogoURL, + target, + R.drawable.background); + return Unit.INSTANCE; + }); + } } } @@ -506,12 +513,18 @@ private void showTopBanner(ConstraintLayout banner) { moreView.setOnClickListener(v -> LinkHelper.INSTANCE.openAppStore("Nextcloud", true, this)); assistantView.setOnClickListener(v -> startAssistantScreen()); - if (getCapabilities() != null && getCapabilities().getAssistant().isTrue()) { - assistantView.setVisibility(View.VISIBLE); - } else { - assistantView.setVisibility(View.GONE); + final var optionalCapabilities = getCapabilities(); + if (optionalCapabilities.isPresent()) { + final var capabilities = optionalCapabilities.get(); + + if (capabilities.getAssistant().isTrue()) { + assistantView.setVisibility(View.VISIBLE); + } else { + assistantView.setVisibility(View.GONE); + } } + List views = Arrays.asList(notesView, talkView, moreView, assistantView); int iconColor; @@ -574,13 +587,17 @@ private void setupDrawerMenu(NavigationView navigationView) { } private void filterDrawerMenu(final Menu menu, @NonNull final User user) { - OCCapability capability = getCapabilities(); + final var optionalCapability = getCapabilities(); + if (optionalCapability.isPresent()) { + final var capability = optionalCapability.get(); + + DrawerMenuUtil.filterTrashbinMenuItem(menu, capability); + DrawerMenuUtil.filterActivityMenuItem(menu, capability); + DrawerMenuUtil.filterGroupfoldersMenuItem(menu, capability); + DrawerMenuUtil.filterAssistantMenuItem(menu, capability, getResources()); + } DrawerMenuUtil.filterSearchMenuItems(menu, user, getResources()); - DrawerMenuUtil.filterTrashbinMenuItem(menu, capability); - DrawerMenuUtil.filterActivityMenuItem(menu, capability); - DrawerMenuUtil.filterGroupfoldersMenuItem(menu, capability); - DrawerMenuUtil.filterAssistantMenuItem(menu, capability, getResources()); DrawerMenuUtil.setupHomeMenuItem(menu, getResources()); DrawerMenuUtil.removeMenuItem(menu, R.id.nav_community, !getResources().getBoolean(R.bool.participate_enabled)); DrawerMenuUtil.removeMenuItem(menu, R.id.nav_shared, !getResources().getBoolean(R.bool.shared_enabled)); @@ -1351,11 +1368,14 @@ public void fetchExternalLinks(final boolean force) { Thread t = new Thread(() -> { // fetch capabilities as early as possible - final OCCapability capability = getCapabilities(); - if ((capability == null || capability.getAccountName() == null || !capability.getAccountName().isEmpty()) - && getStorageManager() != null) { - GetCapabilitiesOperation getCapabilities = new GetCapabilitiesOperation(getStorageManager()); - getCapabilities.execute(getBaseContext()); + final var optionalCapability = getCapabilities(); + if (optionalCapability.isPresent()) { + final var capability = optionalCapability.get(); + if ((capability.getAccountName() == null || !capability.getAccountName().isEmpty()) + && getStorageManager() != null) { + GetCapabilitiesOperation getCapabilities = new GetCapabilitiesOperation(getStorageManager()); + getCapabilities.execute(getBaseContext()); + } } if (getStorageManager() != null && CapabilityUtils.getCapability(user, this) diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/FileActivity.java index 47e079c1f67e..82111b449a5b 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/FileActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/activity/FileActivity.java @@ -88,7 +88,6 @@ import com.owncloud.android.ui.dialog.SslUntrustedCertDialog; import com.owncloud.android.ui.events.DialogEvent; import com.owncloud.android.ui.events.DialogEventType; -import com.owncloud.android.ui.events.FavoriteEvent; import com.owncloud.android.ui.fragment.FileDetailFragment; import com.owncloud.android.ui.fragment.FileDetailSharingFragment; import com.owncloud.android.ui.fragment.OCFileListFragment; @@ -887,20 +886,20 @@ private void onCreateShareViaLinkOperationFinish(CreateShareViaLinkOperation ope } else { // Detect Failure (403) --> maybe needs password String password = operation.getPassword(); - if (result.getCode() == RemoteOperationResult.ResultCode.SHARE_FORBIDDEN && - TextUtils.isEmpty(password) && - getCapabilities().getFilesSharingPublicEnabled().isUnknown()) { - // Was tried without password, but not sure that it's optional. - - // Try with password before giving up; see also ShareFileFragment#OnShareViaLinkListener - if (sharingFragment != null && sharingFragment.isAdded()) { - // only if added to the view hierarchy - - sharingFragment.requestPasswordForShareViaLink(true, - getCapabilities().getFilesSharingPublicAskForOptionalPassword() - .isTrue()); + final var optionalCapabilities = getCapabilities(); + + if (result.getCode() == RemoteOperationResult.ResultCode.SHARE_FORBIDDEN && TextUtils.isEmpty(password)) { + if (optionalCapabilities.isPresent()) { + final var capabilities = optionalCapabilities.get(); + if (capabilities.getFilesSharingPublicEnabled().isUnknown()) { + // Was tried without password, but not sure that it's optional. + // Try with password before giving up; see also ShareFileFragment#OnShareViaLinkListener + if (sharingFragment != null && sharingFragment.isAdded()) { + // only if added to the view hierarchy + sharingFragment.requestPasswordForShareViaLink(true, capabilities.getFilesSharingPublicAskForOptionalPassword().isTrue()); + } + } } - } else { if (sharingFragment != null) { sharingFragment.refreshSharesFromDB(); diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt index f137102a71d7..ca227b52597d 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/FileDisplayActivity.kt @@ -437,13 +437,16 @@ class FileDisplayActivity : private fun checkOutdatedServer() { val user = getUser() + val optionalCapability = capabilities + // show outdated warning if (user.isPresent && + optionalCapability.isPresent && CapabilityUtils.checkOutdatedWarning( getResources(), user.get().server.version, - capabilities.extendedSupport.isTrue, - capabilities.hasValidSubscription.isTrue + optionalCapability.get().extendedSupport.isTrue, + optionalCapability.get().hasValidSubscription.isTrue ) ) { DisplayUtils.showServerOutdatedSnackbar(this, Snackbar.LENGTH_LONG) @@ -1073,28 +1076,38 @@ class FileDisplayActivity : connectivityService.isNetworkAndServerAvailable { result: Boolean? -> if (result == true) { - val isValidFolderPath = remotePathBase?.let { checkFolderPath(it, capabilities, this) } - if (isValidFolderPath == false) { - DisplayUtils.showSnackMessage( - this, - R.string.file_name_validator_error_contains_reserved_names_or_invalid_characters + val optionalCapabilities = capabilities + if (optionalCapabilities.isPresent) { + val isValidFolderPath = + remotePathBase?.let { + checkFolderPath( + it, + optionalCapabilities.get(), + this + ) + } + if (isValidFolderPath == false) { + DisplayUtils.showSnackMessage( + this, + R.string.file_name_validator_error_contains_reserved_names_or_invalid_characters + ) + return@isNetworkAndServerAvailable + } + + FileUploadHelper.instance().uploadNewFiles( + user.orElseThrow( + Supplier { RuntimeException() } + ), + filePaths, + decryptedRemotePaths, + behaviour, + true, + UploadFileOperation.CREATED_BY_USER, + false, + false, + NameCollisionPolicy.ASK_USER ) - return@isNetworkAndServerAvailable } - - FileUploadHelper.Companion.instance().uploadNewFiles( - user.orElseThrow( - Supplier { RuntimeException() } - ), - filePaths, - decryptedRemotePaths, - behaviour, - true, - UploadFileOperation.CREATED_BY_USER, - false, - false, - NameCollisionPolicy.ASK_USER - ) } else { fileDataStorageManager.addCreateFileOfflineOperation(filePaths, decryptedRemotePaths) } @@ -2410,7 +2423,12 @@ class FileDisplayActivity : } private fun fetchRecommendedFilesIfNeeded(ignoreETag: Boolean, folder: OCFile?) { - if (folder?.isRootDirectory == false || capabilities == null || capabilities.recommendations.isFalse) { + val optionalCapabilities = capabilities + if (optionalCapabilities.isEmpty) { + return + } + + if (folder?.isRootDirectory == false || optionalCapabilities.get().recommendations.isFalse) { return } diff --git a/app/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt index 106c22183bbf..88429f01579a 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/FolderPickerActivity.kt @@ -376,14 +376,24 @@ open class FolderPickerActivity : updateNavigationElementsInActionBar() } + @Suppress("ReturnCount") private fun toggleChooseEnabled() { if (this is FilePickerActivity) { return } val selectedFolderPathTitle = getSelectedFolderPathTitle() + val optionalCapabilities = capabilities + if (optionalCapabilities.isEmpty) { + return + } + val isFolderPathValid = if (selectedFolderPathTitle != null) { - FileNameValidator.checkFolderPath(selectedFolderPathTitle, capabilities, this) + FileNameValidator.checkFolderPath( + selectedFolderPathTitle, + optionalCapabilities.get(), + this + ) } else { true } diff --git a/app/src/main/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java index 2e8a6a27cdbf..e49ef8e0458b 100755 --- a/app/src/main/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/activity/ReceiveExternalFilesActivity.java @@ -301,7 +301,12 @@ public void onSortingOrderChosen(FileSortOrder newSortOrder) { @Override public void selectFile(OCFile file) { if (file.isFolder()) { - String filenameErrorMessage = FileNameValidator.INSTANCE.checkFileName(file.getFileName(), getCapabilities(), this, null); + final var optionalCapabilities = getCapabilities(); + if (optionalCapabilities.isEmpty()) { + return; + } + + String filenameErrorMessage = FileNameValidator.INSTANCE.checkFileName(file.getFileName(), optionalCapabilities.get(), this, null); if (filenameErrorMessage != null) { DisplayUtils.showSnackMessage(this, filenameErrorMessage); return; diff --git a/app/src/main/java/com/owncloud/android/ui/asynctasks/FetchRemoteFileTask.java b/app/src/main/java/com/owncloud/android/ui/asynctasks/FetchRemoteFileTask.java index a26bb6ba95c1..c51f7d46463e 100644 --- a/app/src/main/java/com/owncloud/android/ui/asynctasks/FetchRemoteFileTask.java +++ b/app/src/main/java/com/owncloud/android/ui/asynctasks/FetchRemoteFileTask.java @@ -18,6 +18,7 @@ import com.owncloud.android.datamodel.OCFile; import com.owncloud.android.lib.common.operations.RemoteOperation; import com.owncloud.android.lib.common.operations.RemoteOperationResult; +import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.lib.resources.files.ReadFileRemoteOperation; import com.owncloud.android.lib.resources.files.SearchRemoteOperation; import com.owncloud.android.lib.resources.files.model.RemoteFile; @@ -28,6 +29,7 @@ import static com.owncloud.android.lib.resources.files.SearchRemoteOperation.SearchType.FILE_ID_SEARCH; public class FetchRemoteFileTask extends AsyncTask { + private static final String TAG = "FetchRemoteFileTask"; private final User user; private final String fileId; private final FileDataStorageManager storageManager; @@ -46,10 +48,17 @@ public FetchRemoteFileTask(User user, @Override protected String doInBackground(Void... voids) { + final var optionalCapabilities = fileDisplayActivity.getCapabilities(); + if (optionalCapabilities.isEmpty()) { + Log_OC.e(TAG, "cannot fetch remote file capability is null"); + return ""; + } + + SearchRemoteOperation searchRemoteOperation = new SearchRemoteOperation(fileId, FILE_ID_SEARCH, false, - fileDisplayActivity.getCapabilities()); + optionalCapabilities.get()); RemoteOperationResult remoteOperationResult = searchRemoteOperation.execute(user, fileDisplayActivity); if (remoteOperationResult.isSuccess() && remoteOperationResult.getData() != null) { diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetDialog.kt b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetDialog.kt index d67689722a45..aecc87bb6c97 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetDialog.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListBottomSheetDialog.kt @@ -103,14 +103,17 @@ class OCFileListBottomSheetDialog( @Suppress("ComplexCondition") private fun checkTemplateVisibility() { - val capability = fileActivity.capabilities - if (capability != null && - capability.richDocuments.isTrue && - capability.richDocumentsDirectEditing.isTrue && - capability.richDocumentsTemplatesAvailable.isTrue && - !file.isEncrypted - ) { - binding.templates.visibility = View.VISIBLE + val optionalCapabilities = fileActivity.capabilities + if (optionalCapabilities.isPresent) { + val capability = optionalCapabilities.get() + + if (capability.richDocuments.isTrue && + capability.richDocumentsDirectEditing.isTrue && + capability.richDocumentsTemplatesAvailable.isTrue && + !file.isEncrypted + ) { + binding.templates.visibility = View.VISIBLE + } } }