From 4d806e9519c4261fafb64de4243845e3c3c5914f Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Wed, 4 Nov 2026 14:55:13 +0300 Subject: [PATCH 1/3] Rename .java to .kt Signed-off-by: alperozturk96 --- .../main/java/com/owncloud/android/{MainApp.java => MainApp.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename app/src/main/java/com/owncloud/android/{MainApp.java => MainApp.kt} (100%) diff --git a/app/src/main/java/com/owncloud/android/MainApp.java b/app/src/main/java/com/owncloud/android/MainApp.kt similarity index 100% rename from app/src/main/java/com/owncloud/android/MainApp.java rename to app/src/main/java/com/owncloud/android/MainApp.kt From 2017c940fb047e6f406827ecd0b50bcfeeac945c Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Wed, 4 Nov 2026 14:55:14 +0300 Subject: [PATCH 2/3] refactor(main-app): kotlin Signed-off-by: alperozturk96 # Conflicts: # app/src/main/java/com/owncloud/android/MainApp.kt --- .../com/nextcloud/client/NominatimClient.kt | 2 +- .../client/jobs/InternalTwoWaySyncWork.kt | 2 +- .../client/jobs/MediaFoldersDetectionWork.kt | 2 +- .../migrations/AutoUploadEntriesMigration.kt | 133 ++ .../notifications/AppNotificationChannels.kt | 87 ++ .../notifications/NotificationChannelSpec.kt | 17 + .../datasource/PlaybackDataSourceFactory.kt | 2 +- .../player/model/PlayerThumbnailLoader.kt | 2 +- .../model/file/PlaybackFilesRepository.kt | 2 +- .../nextcloud/ui/fileInfo/ImageDetailInfo.kt | 2 +- .../main/java/com/owncloud/android/MainApp.kt | 1344 ++++++----------- .../authentication/AuthenticatorActivity.kt | 2 +- .../GlobalPassCodeProtectionCallbacks.kt | 59 + .../signup/SignupWebViewLogin.kt | 2 +- .../android/datamodel/MediaProvider.kt | 2 +- .../android/datastorage/StoragePathFixer.kt | 50 + .../ui/activity/ExternalSiteWebView.kt | 2 +- .../ui/activity/FileDisplayActivity.kt | 20 +- .../ui/adapter/filelist/OCFileListAdapter.kt | 2 +- .../filelist/data/OCFileListAdapterHelper.kt | 2 +- .../StoragePermissionBannerActionHandler.kt | 2 +- .../fragment/OCFileListBottomSheetDialog.kt | 2 +- .../android/ui/fragment/OCFileListFragment.kt | 8 +- .../ui/preview/media/PreviewMediaActivity.kt | 2 +- .../android/utils/FileStorageUtils.kt | 6 +- .../client/jobs/BackgroundJobFactoryTest.kt | 4 +- .../utils/HumanReadableFormatterTest.kt | 12 +- .../extensions/FileStorageManagerTests.kt | 2 +- .../android/datamodel/MoveFilesTestBase.kt | 2 +- .../data/OCFileListAdapterHelperTest.kt | 6 +- 30 files changed, 861 insertions(+), 921 deletions(-) create mode 100644 app/src/main/java/com/nextcloud/client/migrations/AutoUploadEntriesMigration.kt create mode 100644 app/src/main/java/com/nextcloud/client/notifications/AppNotificationChannels.kt create mode 100644 app/src/main/java/com/nextcloud/client/notifications/NotificationChannelSpec.kt create mode 100644 app/src/main/java/com/owncloud/android/authentication/GlobalPassCodeProtectionCallbacks.kt create mode 100644 app/src/main/java/com/owncloud/android/datastorage/StoragePathFixer.kt diff --git a/app/src/main/java/com/nextcloud/client/NominatimClient.kt b/app/src/main/java/com/nextcloud/client/NominatimClient.kt index 7d8f35b07fb2..6e35ee893352 100644 --- a/app/src/main/java/com/nextcloud/client/NominatimClient.kt +++ b/app/src/main/java/com/nextcloud/client/NominatimClient.kt @@ -21,7 +21,7 @@ class NominatimClient constructor(geocoderBaseUrl: String, email: String) { private val reverseUrl = "${geocoderBaseUrl}reverse?format=jsonv2&email=${URLEncoder.encode(email, ENCODING_UTF_8)}" private fun doRequest(requestUrl: String): String? { - val request = Request.Builder().url(requestUrl).header(HEADER_USER_AGENT, MainApp.getUserAgent()).build() + val request = Request.Builder().url(requestUrl).header(HEADER_USER_AGENT, MainApp.userAgent).build() try { val response = client.newCall(request).execute() diff --git a/app/src/main/java/com/nextcloud/client/jobs/InternalTwoWaySyncWork.kt b/app/src/main/java/com/nextcloud/client/jobs/InternalTwoWaySyncWork.kt index 8a60b6c8b02f..1377555f2868 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/InternalTwoWaySyncWork.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/InternalTwoWaySyncWork.kt @@ -121,7 +121,7 @@ class InternalTwoWaySyncWork( return try { val freeSpaceLeft = file.freeSpace - val localFolder = File(storagePath, MainApp.getDataFolder()) + val localFolder = File(storagePath, MainApp.dataFolder) val localFolderSize = FileStorageUtils.getFolderSize(localFolder) val remoteFolderSize = folder.fileLength diff --git a/app/src/main/java/com/nextcloud/client/jobs/MediaFoldersDetectionWork.kt b/app/src/main/java/com/nextcloud/client/jobs/MediaFoldersDetectionWork.kt index 6dab98e734f0..6ddac351b4f1 100644 --- a/app/src/main/java/com/nextcloud/client/jobs/MediaFoldersDetectionWork.kt +++ b/app/src/main/java/com/nextcloud/client/jobs/MediaFoldersDetectionWork.kt @@ -67,7 +67,7 @@ class MediaFoldersDetectionWork( private const val ACCOUNT_NAME_GLOBAL = "global" private const val KEY_MEDIA_FOLDERS = "media_folders" const val NOTIFICATION_ID = "NOTIFICATION_ID" - private val DISABLE_DETECTION_CLICK = MainApp.getAuthority() + "_DISABLE_DETECTION_CLICK" + private val DISABLE_DETECTION_CLICK = MainApp.authority + "_DISABLE_DETECTION_CLICK" } private val randomIdGenerator = Random(clock.currentTime) diff --git a/app/src/main/java/com/nextcloud/client/migrations/AutoUploadEntriesMigration.kt b/app/src/main/java/com/nextcloud/client/migrations/AutoUploadEntriesMigration.kt new file mode 100644 index 000000000000..7bcf01ce6b43 --- /dev/null +++ b/app/src/main/java/com/nextcloud/client/migrations/AutoUploadEntriesMigration.kt @@ -0,0 +1,133 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +package com.nextcloud.client.migrations + +import android.content.Context +import android.view.WindowManager.BadTokenException +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.nextcloud.client.core.Clock +import com.nextcloud.client.preferences.AppPreferencesImpl +import com.owncloud.android.R +import com.owncloud.android.datamodel.MediaFolder +import com.owncloud.android.datamodel.MediaFolderType +import com.owncloud.android.datamodel.MediaProvider +import com.owncloud.android.datamodel.SyncedFolder +import com.owncloud.android.datamodel.SyncedFolderProvider +import com.owncloud.android.lib.common.utils.Log_OC +import com.owncloud.android.ui.navigation.NavigatorActivity +import com.owncloud.android.ui.navigation.NavigatorScreen + +object AutoUploadEntriesMigration { + + private val TAG: String = AutoUploadEntriesMigration::class.java.simpleName + private const val MEDIA_FOLDER_ITEM_LIMIT = 1 + + fun updateToAutoUpload(context: Context) { + val preferences = AppPreferencesImpl.fromContext(context) + if (!preferences.instantPictureUploadEnabled() && !preferences.instantVideoUploadEnabled()) { + return + } + + preferences.removeLegacyPreferences() + + try { + showAutoUploadAlertDialog(context) + } catch (e: BadTokenException) { + Log_OC.i(TAG, "Error showing Auto Upload Update dialog, so skipping it: " + e.message) + } + } + + private fun showAutoUploadAlertDialog(context: Context) { + MaterialAlertDialogBuilder(context, R.style.Theme_ownCloud_Dialog) + .setTitle(R.string.drawer_synced_folders) + .setMessage(R.string.synced_folders_new_info) + .setPositiveButton(R.string.drawer_open) { dialog, _ -> + val folderSyncIntent = NavigatorActivity.intent(context, NavigatorScreen.AutoUpload) + dialog.dismiss() + context.startActivity(folderSyncIntent) + } + .setNegativeButton(R.string.drawer_close) { dialog, _ -> dialog.dismiss() } + .setIcon(R.drawable.nav_synced_folders) + .create() + .show() + } + + fun cleanOldEntries(context: Context, clock: Clock) { + val preferences = AppPreferencesImpl.fromContext(context) + if (preferences.isLegacyClean) { + return + } + + val syncedFolderProvider = SyncedFolderProvider(context.contentResolver, preferences, clock) + val newestIds = syncedFolderProvider.syncedFolders + .groupBy { it.account to it.localPath } + .values + .map { duplicates -> duplicates.maxOf { it.id } } + + if (newestIds.isEmpty()) { + preferences.isLegacyClean = true + return + } + + val deletedCount = syncedFolderProvider.deleteSyncedFoldersNotInList(newestIds.toMutableList()) + if (deletedCount > 0) { + preferences.isLegacyClean = true + } + } + + fun updateAutoUploadEntries(context: Context, clock: Clock) { + val preferences = AppPreferencesImpl.fromContext(context) + if (preferences.isAutoUploadPathsUpdateEnabled) { + return + } + + SyncedFolderProvider(context.contentResolver, preferences, clock).updateAutoUploadPaths(context) + } + + fun splitOutAutoUploadEntries(context: Context, clock: Clock) { + val preferences = AppPreferencesImpl.fromContext(context) + if (preferences.isAutoUploadSplitEntriesEnabled) { + return + } + + Log_OC.i(TAG, "Migrate synced_folders records for image/video split") + val contentResolver = context.contentResolver + val syncedFolderProvider = SyncedFolderProvider(contentResolver, preferences, clock) + val imageMediaFolders = MediaProvider.getImageFolders(contentResolver, MEDIA_FOLDER_ITEM_LIMIT, null, true) + val videoMediaFolders = MediaProvider.getVideoFolders(contentResolver, MEDIA_FOLDER_ITEM_LIMIT, null, true) + val syncedFolders = syncedFolderProvider.syncedFolders + + syncedFolders.forEach { syncedFolder -> + Log_OC.i(TAG, "Migration check for synced_folders record: ${syncedFolder.id} - ${syncedFolder.localPath}") + syncedFolderProvider.storeCopyIfMediaFolder(syncedFolder, imageMediaFolders, MediaFolderType.IMAGE, "image") + syncedFolderProvider.storeCopyIfMediaFolder(syncedFolder, videoMediaFolders, MediaFolderType.VIDEO, "video") + } + + syncedFolders.forEach { syncedFolder -> + Log_OC.i(TAG, "Removing legacy synced_folders record: ${syncedFolder.id}") + syncedFolderProvider.deleteSyncedFolder(syncedFolder.id) + } + + preferences.isAutoUploadSplitEntriesEnabled = true + } + + private fun SyncedFolderProvider.storeCopyIfMediaFolder( + syncedFolder: SyncedFolder, + mediaFolders: List, + mediaType: MediaFolderType, + mediaTypeLabel: String + ) { + val isMediaFolder = mediaFolders.any { it.absolutePath != null && it.absolutePath == syncedFolder.localPath } + if (!isMediaFolder) { + return + } + + val newSyncedFolder = (syncedFolder.clone() as SyncedFolder).apply { setType(mediaType) } + val primaryKey = storeSyncedFolder(newSyncedFolder) + Log_OC.i(TAG, "Migrated $mediaTypeLabel synced_folders record: $primaryKey - ${newSyncedFolder.localPath}") + } +} diff --git a/app/src/main/java/com/nextcloud/client/notifications/AppNotificationChannels.kt b/app/src/main/java/com/nextcloud/client/notifications/AppNotificationChannels.kt new file mode 100644 index 000000000000..6805b4115dfb --- /dev/null +++ b/app/src/main/java/com/nextcloud/client/notifications/AppNotificationChannels.kt @@ -0,0 +1,87 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +package com.nextcloud.client.notifications + +import android.app.NotificationChannel +import android.app.NotificationManager +import android.content.Context +import com.owncloud.android.R +import com.owncloud.android.lib.common.utils.Log_OC +import com.owncloud.android.ui.notifications.NotificationUtils + +object AppNotificationChannels { + + private val TAG: String = AppNotificationChannels::class.java.simpleName + + private val channels = listOf( + NotificationChannelSpec( + NotificationUtils.NOTIFICATION_CHANNEL_DOWNLOAD, + R.string.notification_channel_download_name_short, + R.string.notification_channel_download_description + ), + NotificationChannelSpec( + NotificationUtils.NOTIFICATION_CHANNEL_UPLOAD, + R.string.notification_channel_upload_name_short, + R.string.notification_channel_upload_description + ), + NotificationChannelSpec( + NotificationUtils.NOTIFICATION_CHANNEL_MEDIA, + R.string.notification_channel_media_name, + R.string.notification_channel_media_description + ), + NotificationChannelSpec( + NotificationUtils.NOTIFICATION_CHANNEL_FILE_SYNC, + R.string.notification_channel_file_sync_name, + R.string.notification_channel_file_sync_description + ), + NotificationChannelSpec( + NotificationUtils.NOTIFICATION_CHANNEL_PUSH, + R.string.notification_channel_push_name, + R.string.notification_channel_push_description, + NotificationManager.IMPORTANCE_DEFAULT + ), + NotificationChannelSpec( + NotificationUtils.NOTIFICATION_CHANNEL_BACKGROUND_OPERATIONS, + R.string.notification_channel_background_operations_name, + R.string.notification_channel_background_operations_description + ), + NotificationChannelSpec( + NotificationUtils.NOTIFICATION_CHANNEL_GENERAL, + R.string.notification_channel_general_name, + R.string.notification_channel_general_description, + NotificationManager.IMPORTANCE_DEFAULT + ), + NotificationChannelSpec( + NotificationUtils.NOTIFICATION_CHANNEL_OFFLINE_OPERATIONS, + R.string.notification_channel_offline_operations_name_short, + R.string.notification_channel_offline_operations_description + ), + NotificationChannelSpec( + NotificationUtils.NOTIFICATION_CHANNEL_CONTENT_OBSERVER, + R.string.notification_channel_content_observer_name_short, + R.string.notification_channel_content_observer_description + ) + ) + + fun create(context: Context) { + val notificationManager = context.getSystemService(NotificationManager::class.java) + if (notificationManager == null) { + Log_OC.e(TAG, "Notification manager is null") + return + } + + channels.forEach { notificationManager.createNotificationChannel(it.toNotificationChannel(context)) } + notificationManager.deleteNotificationChannel(NotificationUtils.NOTIFICATION_CHANNEL_FILE_OBSERVER) + } + + private fun NotificationChannelSpec.toNotificationChannel(context: Context): NotificationChannel = + NotificationChannel(id, context.getString(nameRes), importance).apply { + description = context.getString(descriptionRes) + enableLights(false) + enableVibration(false) + } +} diff --git a/app/src/main/java/com/nextcloud/client/notifications/NotificationChannelSpec.kt b/app/src/main/java/com/nextcloud/client/notifications/NotificationChannelSpec.kt new file mode 100644 index 000000000000..699c63c3d948 --- /dev/null +++ b/app/src/main/java/com/nextcloud/client/notifications/NotificationChannelSpec.kt @@ -0,0 +1,17 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +package com.nextcloud.client.notifications + +import android.app.NotificationManager +import androidx.annotation.StringRes + +data class NotificationChannelSpec( + val id: String, + @StringRes val nameRes: Int, + @StringRes val descriptionRes: Int, + val importance: Int = NotificationManager.IMPORTANCE_LOW +) diff --git a/app/src/main/java/com/nextcloud/client/player/media3/datasource/PlaybackDataSourceFactory.kt b/app/src/main/java/com/nextcloud/client/player/media3/datasource/PlaybackDataSourceFactory.kt index f0998a79343e..bc33a34ea3c3 100644 --- a/app/src/main/java/com/nextcloud/client/player/media3/datasource/PlaybackDataSourceFactory.kt +++ b/app/src/main/java/com/nextcloud/client/player/media3/datasource/PlaybackDataSourceFactory.kt @@ -47,6 +47,6 @@ class PlaybackDataSourceFactory @Inject constructor( private fun createHttpDataSourceFactory(): HttpDataSource.Factory { val client = clientFactory.createNextcloudClient(accountManager.user).client return OkHttpDataSource.Factory(client) - .setUserAgent(MainApp.getUserAgent()) + .setUserAgent(MainApp.userAgent) } } diff --git a/app/src/main/java/com/nextcloud/client/player/model/PlayerThumbnailLoader.kt b/app/src/main/java/com/nextcloud/client/player/model/PlayerThumbnailLoader.kt index f4cf34b3955e..9fd10e5f5a49 100644 --- a/app/src/main/java/com/nextcloud/client/player/model/PlayerThumbnailLoader.kt +++ b/app/src/main/java/com/nextcloud/client/player/model/PlayerThumbnailLoader.kt @@ -71,7 +71,7 @@ class PlayerThumbnailLoader @Inject constructor( "${client.baseUri}/index.php/core/preview?fileId=${file.id}&x=$width&y=$height&a=1&mode=cover&forceIcon=0", LazyHeaders.Builder() .addHeader("Authorization", client.credentials) - .addHeader("User-Agent", MainApp.getUserAgent()) + .addHeader("User-Agent", MainApp.userAgent) .build() ) } diff --git a/app/src/main/java/com/nextcloud/client/player/model/file/PlaybackFilesRepository.kt b/app/src/main/java/com/nextcloud/client/player/model/file/PlaybackFilesRepository.kt index bd35c0afd2ab..e6d333f2d7e9 100644 --- a/app/src/main/java/com/nextcloud/client/player/model/file/PlaybackFilesRepository.kt +++ b/app/src/main/java/com/nextcloud/client/player/model/file/PlaybackFilesRepository.kt @@ -42,7 +42,7 @@ class PlaybackFilesRepository @Inject constructor( PlaybackCollection.GALLERY -> observeGalleryPlaybackFiles(fileType) PlaybackCollection.SHARED -> observeSharedPlaybackFiles(fileType) PlaybackCollection.ALBUM -> observeAlbumPlaybackFiles(fileType) - PlaybackCollection.FOLDER -> observeFolderPlaybackFiles(folderId, fileType, MainApp.isOnlyOnDevice()) + PlaybackCollection.FOLDER -> observeFolderPlaybackFiles(folderId, fileType, MainApp.isOnlyOnDevice) } private fun observeAlbumPlaybackFiles(fileType: PlaybackFileType): Flow = diff --git a/app/src/main/java/com/nextcloud/ui/fileInfo/ImageDetailInfo.kt b/app/src/main/java/com/nextcloud/ui/fileInfo/ImageDetailInfo.kt index 7e01e0f872a6..790c541a7bb1 100644 --- a/app/src/main/java/com/nextcloud/ui/fileInfo/ImageDetailInfo.kt +++ b/app/src/main/java/com/nextcloud/ui/fileInfo/ImageDetailInfo.kt @@ -145,7 +145,7 @@ class ImageDetailInfo(private val fragment: FileInfoFragment, private val viewTh longitude: Double, zoom: Double = 13.0 ) { - Configuration.getInstance().userAgentValue = MainApp.getUserAgent() + Configuration.getInstance().userAgentValue = MainApp.userAgent val location = GeoPoint(latitude, longitude) diff --git a/app/src/main/java/com/owncloud/android/MainApp.kt b/app/src/main/java/com/owncloud/android/MainApp.kt index d69af308fd42..1ccb075f138e 100644 --- a/app/src/main/java/com/owncloud/android/MainApp.kt +++ b/app/src/main/java/com/owncloud/android/MainApp.kt @@ -1,1033 +1,627 @@ /* * Nextcloud - Android Client * - * SPDX-FileCopyrightText: 2023 Alper Ozturk - * SPDX-FileCopyrightText: 2023 TSI-mc - * SPDX-FileCopyrightText: 2022-2023 Álvaro Brey - * SPDX-FileCopyrightText: 2016-2020 Tobias Kaminsky - * SPDX-FileCopyrightText: 2019 Chris Narkiewicz - * SPDX-FileCopyrightText: 2019 Alice Gaudon - * SPDX-FileCopyrightText: 2016 Andy Scherzinger - * SPDX-FileCopyrightText: 2015 ownCloud Inc. - * SPDX-FileCopyrightText: 2014 David A. Velasco - * SPDX-FileCopyrightText: 2013 María Asensio Valverde - * SPDX-License-Identifier: GPL-2.0-only AND (AGPL-3.0-or-later OR GPL-2.0-only) + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later */ -package com.owncloud.android; - -import android.annotation.SuppressLint; -import android.app.Activity; -import android.app.Application; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.content.BroadcastReceiver; -import android.content.ComponentName; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.res.Resources; -import android.net.ConnectivityManager; -import android.os.Bundle; -import android.os.Environment; -import android.os.StrictMode; -import android.text.TextUtils; -import android.view.WindowManager; - -import com.google.android.material.dialog.MaterialAlertDialogBuilder; -import com.nextcloud.appReview.InAppReviewHelper; -import com.nextcloud.client.account.User; -import com.nextcloud.client.account.UserAccountManager; -import com.nextcloud.client.appinfo.AppInfo; -import com.nextcloud.client.core.Clock; -import com.nextcloud.client.device.PowerManagementService; -import com.nextcloud.client.di.ActivityInjector; -import com.nextcloud.client.di.AppComponent; -import com.nextcloud.client.di.DaggerAppComponent; -import com.nextcloud.client.errorhandling.ExceptionHandler; -import com.nextcloud.client.jobs.BackgroundJobManager; -import com.nextcloud.client.logger.LegacyLoggerAdapter; -import com.nextcloud.client.logger.Logger; -import com.nextcloud.client.migrations.MigrationsManager; -import com.nextcloud.client.network.ConnectivityService; -import com.nextcloud.client.network.NetworkChangeListener; -import com.nextcloud.client.network.WalledCheckCache; -import com.nextcloud.client.onboarding.OnboardingService; -import com.nextcloud.client.preferences.AppPreferences; -import com.nextcloud.client.preferences.AppPreferencesImpl; -import com.nextcloud.client.preferences.DarkMode; -import com.nextcloud.client.systembars.SystemBarBackgroundCallbacks; -import com.nextcloud.ui.composeActivity.ComposeProcessTextAlias; -import com.nextcloud.utils.extensions.ContextExtensionsKt; -import com.nextcloud.utils.mdm.MDMConfig; -import com.nmc.android.ui.LauncherActivity; -import com.owncloud.android.authentication.PassCodeManager; -import com.owncloud.android.datamodel.ArbitraryDataProvider; -import com.owncloud.android.datamodel.ArbitraryDataProviderImpl; -import com.owncloud.android.datamodel.MediaFolder; -import com.owncloud.android.datamodel.MediaFolderType; -import com.owncloud.android.datamodel.MediaProvider; -import com.owncloud.android.datamodel.ReceiverFlag; -import com.owncloud.android.datamodel.SyncedFolder; -import com.owncloud.android.datamodel.SyncedFolderProvider; -import com.owncloud.android.datamodel.ThumbnailsCacheManager; -import com.owncloud.android.datamodel.UploadsStorageManager; -import com.owncloud.android.datastorage.DataStorageProvider; -import com.owncloud.android.datastorage.StoragePoint; -import com.owncloud.android.lib.common.OwnCloudClientManagerFactory; -import com.owncloud.android.lib.common.utils.Log_OC; -import com.owncloud.android.lib.resources.status.NextcloudVersion; -import com.owncloud.android.lib.resources.status.OCCapability; -import com.owncloud.android.lib.resources.status.OwnCloudVersion; -import com.owncloud.android.providers.documentstorage.DocumentsStorageProvider; -import com.owncloud.android.ui.navigation.NavigatorActivity; -import com.owncloud.android.ui.navigation.NavigatorScreen; -import com.owncloud.android.ui.notifications.NotificationUtils; -import com.owncloud.android.utils.FilesSyncHelper; -import com.owncloud.android.utils.PermissionUtil; -import com.owncloud.android.utils.ReceiversHelper; -import com.owncloud.android.utils.SecurityUtils; -import com.owncloud.android.utils.theme.CapabilityUtils; -import com.owncloud.android.utils.theme.ViewThemeUtils; - -import org.conscrypt.Conscrypt; -import org.greenrobot.eventbus.EventBus; - -import java.lang.ref.WeakReference; -import java.lang.reflect.Method; -import java.security.NoSuchAlgorithmException; -import java.security.Security; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; - -import javax.inject.Inject; -import javax.inject.Provider; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLEngine; - -import androidx.annotation.NonNull; -import androidx.annotation.StringRes; -import androidx.appcompat.app.AppCompatDelegate; -import androidx.core.util.Pair; -import androidx.lifecycle.Lifecycle; -import androidx.lifecycle.LifecycleEventObserver; -import androidx.lifecycle.ProcessLifecycleOwner; -import dagger.android.AndroidInjector; -import dagger.android.DispatchingAndroidInjector; -import dagger.android.HasAndroidInjector; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; - -import static com.owncloud.android.ui.activity.ContactsPreferenceActivity.PREFERENCE_CONTACTS_AUTOMATIC_BACKUP; - - -/** - * Main Application of the project. Contains methods to build the "static" strings. These strings were before constants - * in different classes. - */ -public class MainApp extends Application implements HasAndroidInjector, NetworkChangeListener { - public static final OwnCloudVersion OUTDATED_SERVER_VERSION = NextcloudVersion.nextcloud_30; - public static final OwnCloudVersion MINIMUM_SUPPORTED_SERVER_VERSION = NextcloudVersion.nextcloud_23; - - private static final String TAG = MainApp.class.getSimpleName(); - public static final String DOT = "."; - - private static WeakReference appContext; - - private static String storagePath; - - private static boolean mOnlyOnDevice; - private static boolean mOnlyPersonalFiles; - +package com.owncloud.android + +import android.app.Application +import android.content.BroadcastReceiver +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.content.pm.PackageInfo +import android.content.pm.PackageManager +import android.content.res.Resources +import android.os.StrictMode +import androidx.annotation.StringRes +import androidx.appcompat.app.AppCompatDelegate +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver +import androidx.lifecycle.ProcessLifecycleOwner +import com.nextcloud.appReview.InAppReviewHelper +import com.nextcloud.client.account.UserAccountManager +import com.nextcloud.client.appinfo.AppInfo +import com.nextcloud.client.core.Clock +import com.nextcloud.client.device.PowerManagementService +import com.nextcloud.client.di.ActivityInjector +import com.nextcloud.client.di.AppComponent +import com.nextcloud.client.di.DaggerAppComponent +import com.nextcloud.client.errorhandling.ExceptionHandler +import com.nextcloud.client.jobs.BackgroundJobManager +import com.nextcloud.client.logger.LegacyLoggerAdapter +import com.nextcloud.client.logger.Logger +import com.nextcloud.client.migrations.AutoUploadEntriesMigration +import com.nextcloud.client.migrations.MigrationsManager +import com.nextcloud.client.network.ConnectivityService +import com.nextcloud.client.network.NetworkChangeListener +import com.nextcloud.client.network.WalledCheckCache +import com.nextcloud.client.notifications.AppNotificationChannels +import com.nextcloud.client.onboarding.OnboardingService +import com.nextcloud.client.preferences.AppPreferences +import com.nextcloud.client.preferences.DarkMode +import com.nextcloud.client.systembars.SystemBarBackgroundCallbacks +import com.nextcloud.ui.composeActivity.ComposeProcessTextAlias +import com.nextcloud.utils.extensions.registerBroadcastReceiver +import com.nextcloud.utils.extensions.showToast +import com.nextcloud.utils.mdm.MDMConfig +import com.owncloud.android.authentication.GlobalPassCodeProtectionCallbacks +import com.owncloud.android.authentication.PassCodeManager +import com.owncloud.android.datamodel.ArbitraryDataProviderImpl +import com.owncloud.android.datamodel.ReceiverFlag +import com.owncloud.android.datamodel.SyncedFolderProvider +import com.owncloud.android.datamodel.ThumbnailsCacheManager +import com.owncloud.android.datamodel.UploadsStorageManager +import com.owncloud.android.datastorage.StoragePathFixer +import com.owncloud.android.lib.common.OwnCloudClientManagerFactory +import com.owncloud.android.lib.common.utils.Log_OC +import com.owncloud.android.lib.resources.status.NextcloudVersion +import com.owncloud.android.lib.resources.status.OwnCloudVersion +import com.owncloud.android.providers.documentstorage.DocumentsStorageProvider +import com.owncloud.android.ui.activity.ContactsPreferenceActivity +import com.owncloud.android.utils.FilesSyncHelper +import com.owncloud.android.utils.PermissionUtil +import com.owncloud.android.utils.ReceiversHelper +import com.owncloud.android.utils.SecurityUtils +import com.owncloud.android.utils.theme.ViewThemeUtils +import dagger.android.AndroidInjector +import dagger.android.DispatchingAndroidInjector +import dagger.android.HasAndroidInjector +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings +import org.conscrypt.Conscrypt +import org.greenrobot.eventbus.EventBus +import java.lang.ref.WeakReference +import java.security.NoSuchAlgorithmException +import java.security.Security +import javax.inject.Inject +import javax.inject.Provider +import javax.net.ssl.SSLContext + +open class MainApp : + Application(), + HasAndroidInjector, + NetworkChangeListener { @Inject - protected AppPreferences preferences; + lateinit var preferences: AppPreferences @Inject - protected DispatchingAndroidInjector dispatchingAndroidInjector; + lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector @Inject - protected UserAccountManager accountManager; + lateinit var accountManager: UserAccountManager @Inject - protected UploadsStorageManager uploadsStorageManager; + lateinit var uploadsStorageManager: UploadsStorageManager @Inject - protected OnboardingService onboarding; + lateinit var onboarding: OnboardingService @Inject - ConnectivityService connectivityService; + lateinit var connectivityService: ConnectivityService @Inject - SyncedFolderProvider syncedFolderProvider; - - @Inject PowerManagementService powerManagementService; + lateinit var syncedFolderProvider: SyncedFolderProvider @Inject - Logger logger; + lateinit var powerManagementService: PowerManagementService @Inject - AppInfo appInfo; + lateinit var logger: Logger @Inject - BackgroundJobManager backgroundJobManager; + lateinit var appInfo: AppInfo @Inject - Clock clock; + lateinit var backgroundJobManager: BackgroundJobManager @Inject - EventBus eventBus; + lateinit var clock: Clock @Inject - MigrationsManager migrationsManager; + lateinit var eventBus: EventBus @Inject - InAppReviewHelper inAppReviewHelper; + lateinit var migrationsManager: MigrationsManager @Inject - PassCodeManager passCodeManager; - - @Inject WalledCheckCache walledCheckCache; + lateinit var inAppReviewHelper: InAppReviewHelper - @Inject ComposeProcessTextAlias composeProcessTextAlias; - - // workaround because injection is initialized on onAttachBaseContext - // and getApplicationContext is null at that point, which crashes when getting current user - @Inject Provider viewThemeUtilsProvider; - private ViewThemeUtils viewThemeUtils; + @Inject + lateinit var passCodeManager: PassCodeManager - @SuppressWarnings("unused") - private boolean mBound; + @Inject + lateinit var walledCheckCache: WalledCheckCache - private static AppComponent appComponent; + @Inject + lateinit var composeProcessTextAlias: ComposeProcessTextAlias - /** - * Temporary hack - */ - private static void initGlobalContext(Context context) { - appContext = new WeakReference<>(context); - } + @Inject + lateinit var viewThemeUtilsProvider: Provider - /** - * Temporary getter replacing Dagger DI - * TODO: remove when cleaning DI in NContentObserverJob - */ - public AppPreferences getPreferences() { - return preferences; - } + private val restrictionsFilter = IntentFilter(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED) - /** - * Temporary getter replacing Dagger DI - * TODO: remove when cleaning DI in NContentObserverJob - */ - public PowerManagementService getPowerManagementService() { - return powerManagementService; + private val restrictionsReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + setProxyConfig() + } } - private String getAppProcessName() { - return Application.getProcessName(); + private val lifecycleEventObserver = LifecycleEventObserver { _, event -> + when (event) { + Lifecycle.Event.ON_START -> onAppForeground() + Lifecycle.Event.ON_STOP -> onAppBackground() + Lifecycle.Event.ON_RESUME -> onAppResume() + else -> Unit + } } - @Override - protected void attachBaseContext(Context base) { - super.attachBaseContext(base); - - initGlobalContext(this); - initDagger(); - - // we don't want to handle crashes occurring inside crash reporter activity/process; - // let the platform deal with those - final boolean isCrashReportingProcess = getAppProcessName().endsWith(":crash"); - - if (!isCrashReportingProcess && !appInfo.isDebugBuild()) { - Thread.UncaughtExceptionHandler defaultPlatformHandler = Thread.getDefaultUncaughtExceptionHandler(); + private val isCrashReportingProcess: Boolean + get() = getProcessName().endsWith(CRASH_REPORTING_PROCESS_SUFFIX) - if (defaultPlatformHandler != null) { - final ExceptionHandler crashReporter = new ExceptionHandler(this, - defaultPlatformHandler); - Thread.setDefaultUncaughtExceptionHandler(crashReporter); - } - } + override fun attachBaseContext(base: Context?) { + super.attachBaseContext(base) + initGlobalContext(this) + initDagger() + installCrashReporter() } - private void initDagger() { - appComponent = DaggerAppComponent.builder() + private fun initDagger() { + val component = DaggerAppComponent.builder() .application(this) - .build(); - - appComponent.inject(this); + .build() + appComponent = component + component.inject(this) } - /** - * USE SPARINGLY! This should only be used for injection of Theme classes in custom Views, and they need to - * be added as methods in the {@link AppComponent} itself. - *

- * Once we adopt Hilt this won't be necessary either, as View is a supported target in Hilt. - * - * @return the {@link AppComponent} for this app - */ - public static AppComponent getAppComponent() { - if (appComponent == null) { - throw new IllegalStateException("Dagger not initialized!"); + private fun installCrashReporter() { + if (isCrashReportingProcess || appInfo.isDebugBuild) { + return } - return appComponent; - } + val defaultPlatformHandler = Thread.getDefaultUncaughtExceptionHandler() ?: return + Thread.setDefaultUncaughtExceptionHandler(ExceptionHandler(this, defaultPlatformHandler)) + } @SuppressFBWarnings("ST") - @Override - public void onCreate() { - enableStrictMode(); - - viewThemeUtils = viewThemeUtilsProvider.get(); - - setAppTheme(preferences.getDarkThemeMode()); - super.onCreate(); - - ProcessLifecycleOwner.get().getLifecycle().addObserver(lifecycleEventObserver); - - insertConscrypt(); - - registerActivityLifecycleCallbacks(new ActivityInjector()); - registerActivityLifecycleCallbacks(new SystemBarBackgroundCallbacks(viewThemeUtilsProvider)); - - //update the app restart count when app is launched by the user - inAppReviewHelper.resetAndIncrementAppRestartCounter(); - - int startedMigrationsCount = migrationsManager.startMigration(); - logger.i(TAG, String.format(Locale.US, "Started %d migrations", startedMigrationsCount)); - - new SecurityUtils(); - - fixStoragePath(); - - checkCancelDownloadJobs(); - - MainApp.storagePath = preferences.getStoragePath(getApplicationContext().getFilesDir().getAbsolutePath()); - - OwnCloudClientManagerFactory.setUserAgent(getUserAgent()); - - if (isClientBrandedPlus()) { - setProxyConfig(); - ContextExtensionsKt.registerBroadcastReceiver(this, restrictionsReceiver, restrictionsFilter, ReceiverFlag.NotExported); - } else { - setProxyForNonBrandedPlusClients(); - } - - // initialise thumbnails cache on background thread - ThumbnailsCacheManager.initDiskCacheAsync(); - - - if (MDMConfig.INSTANCE.isLogEnabled(this)) { - // use app writable dir, no permissions needed - Log_OC.setLoggerImplementation(new LegacyLoggerAdapter(logger)); - Log_OC.d("Debug", "start logging"); - } - - try { - Method m = StrictMode.class.getMethod("disableDeathOnFileUriExposure"); - m.invoke(null); - } catch (Exception e) { - Log_OC.d("Debug", "Failed to disable uri exposure"); - } - - Log_OC.d(TAG, "scheduleContentObserverJob, called"); - backgroundJobManager.scheduleContentObserverJob(); - - initSyncOperations(this, - preferences, - uploadsStorageManager, - accountManager, - connectivityService, - powerManagementService, - backgroundJobManager, - clock, - viewThemeUtils, - walledCheckCache); - initContactsBackup(accountManager, backgroundJobManager); - notificationChannels(); - - if (backgroundJobManager != null) { - backgroundJobManager.scheduleMediaFoldersDetectionJob(); - backgroundJobManager.startMediaFoldersDetectionJob(); - backgroundJobManager.schedulePeriodicAutoUpload(); - backgroundJobManager.schedulePeriodicHealthStatus(); - - if (preferences.isTwoWaySyncEnabled()) { - backgroundJobManager.scheduleInternal2WaySync(preferences.getTwoWaySyncInterval()); - } + override fun onCreate() { + enableStrictMode() + val viewThemeUtils = viewThemeUtilsProvider.get() + setAppTheme(preferences.getDarkThemeMode()) + super.onCreate() + + ProcessLifecycleOwner.get().lifecycle.addObserver(lifecycleEventObserver) + insertConscrypt() + registerActivityLifecycleCallbacks(ActivityInjector()) + registerActivityLifecycleCallbacks(SystemBarBackgroundCallbacks(viewThemeUtilsProvider)) + inAppReviewHelper.resetAndIncrementAppRestartCounter() + startMigrations() + SecurityUtils() + StoragePathFixer(preferences).fixIfNeeded() + cancelDownloadJobsIfRequested() + storagePath = preferences.getStoragePath(applicationContext.filesDir.absolutePath) + OwnCloudClientManagerFactory.setUserAgent(userAgent) + initProxy() + ThumbnailsCacheManager.initDiskCacheAsync() + enableFileLoggingIfAllowed() + disableDeathOnFileUriExposure() + + Log_OC.d(TAG, "scheduleContentObserverJob, called") + backgroundJobManager.scheduleContentObserverJob() + + initSyncOperations( + this, + preferences, + uploadsStorageManager, + accountManager, + connectivityService, + powerManagementService, + backgroundJobManager, + clock, + viewThemeUtils, + walledCheckCache + ) + initContactsBackup(accountManager, backgroundJobManager) + AppNotificationChannels.create(getAppContext()) + scheduleBackgroundJobs() + registerActivityLifecycleCallbacks(GlobalPassCodeProtectionCallbacks(onboarding, passCodeManager)) - backgroundJobManager.startPeriodicallyOfflineOperation(); + if (!MDMConfig.sendFilesSupport(this)) { + disableDocumentsStorageProvider() } - registerGlobalPassCodeProtection(); - if (!MDMConfig.INSTANCE.sendFilesSupport(this)) { - disableDocumentsStorageProvider(); - } - connectivityService.addListener(this); + connectivityService.addListener(this) } - public void disableDocumentsStorageProvider() { - String packageName = getPackageName(); - String providerClassName = DocumentsStorageProvider.class.getName(); - ComponentName componentName = new ComponentName(packageName, providerClassName); - PackageManager packageManager = getPackageManager(); - packageManager.setComponentEnabledSetting(componentName, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); + fun disableDocumentsStorageProvider() { + packageManager.setComponentEnabledSetting( + ComponentName(this, DocumentsStorageProvider::class.java), + PackageManager.COMPONENT_ENABLED_STATE_DISABLED, + PackageManager.DONT_KILL_APP + ) } - private final LifecycleEventObserver lifecycleEventObserver = ((lifecycleOwner, event) -> { - if (event == Lifecycle.Event.ON_START) { - Log_OC.d(TAG, "APP IN FOREGROUND"); - composeProcessTextAlias.configure(); + private fun onAppForeground() { + Log_OC.d(TAG, "APP IN FOREGROUND") + composeProcessTextAlias.configure() - if (preferences.startAutoUploadOnStart()) { - FilesSyncHelper.startAutoUploadForEnabledSyncedFolders(syncedFolderProvider, - backgroundJobManager, - false); - preferences.setLastAutoUploadOnStartTime(System.currentTimeMillis()); - } - } else if (event == Lifecycle.Event.ON_STOP) { - passCodeManager.setCanAskPin(true); - Log_OC.d(TAG, "APP IN BACKGROUND"); - } else if (event == Lifecycle.Event.ON_RESUME) { - setProxyConfig(); - Log_OC.d(TAG, "APP ON RESUME"); + if (!preferences.startAutoUploadOnStart()) { + return } - }); - private void setProxyForNonBrandedPlusClients() { - try { - OwnCloudClientManagerFactory.setProxyHost(getResources().getString(R.string.proxy_host)); - OwnCloudClientManagerFactory.setProxyPort(getResources().getInteger(R.integer.proxy_port)); - } catch (Resources.NotFoundException e) { - Log_OC.d(TAG, "Error caught at setProxyForNonBrandedPlusClients: " + e); - } + FilesSyncHelper.startAutoUploadForEnabledSyncedFolders( + syncedFolderProvider, + backgroundJobManager, + overridePowerSaving = false + ) + preferences.setLastAutoUploadOnStartTime(System.currentTimeMillis()) } - public static boolean isClientBranded() { - return getAppContext().getResources().getBoolean(R.bool.is_branded_client); + private fun onAppBackground() { + passCodeManager.canAskPin = true + Log_OC.d(TAG, "APP IN BACKGROUND") } - public static boolean isClientBrandedPlus() { - return getAppContext().getResources().getBoolean(R.bool.is_branded_plus_client); + private fun onAppResume() { + setProxyConfig() + Log_OC.d(TAG, "APP ON RESUME") } - private final IntentFilter restrictionsFilter = new IntentFilter(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED); - - private final BroadcastReceiver restrictionsReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - setProxyConfig(); - } - }; + private fun startMigrations() { + val startedMigrationsCount = migrationsManager.startMigration() + logger.i(TAG, "Started $startedMigrationsCount migrations") + } - private void setProxyConfig() { - if (!isClientBrandedPlus()) { - Log_OC.d(TAG, "Proxy configuration cannot be set. Client is not branded plus."); - return; + private fun initProxy() { + if (!isClientBrandedPlus) { + setProxyForNonBrandedPlusClients() + return } - String host = MDMConfig.INSTANCE.getHost(this); - int port = MDMConfig.INSTANCE.getPort(this); - - if (TextUtils.isEmpty(host) || port == -1) { - Log_OC.d(TAG, "Proxy configuration cannot be found"); - return; - } + setProxyConfig() + registerBroadcastReceiver(restrictionsReceiver, restrictionsFilter, ReceiverFlag.NotExported) + } + private fun setProxyForNonBrandedPlusClients() { try { - OwnCloudClientManagerFactory.setProxyHost(host); - OwnCloudClientManagerFactory.setProxyPort(port); - - Log_OC.d(TAG, "Proxy configuration successfully set"); - } catch (Resources.NotFoundException e) { - Log_OC.e(TAG, "Proxy config cannot able to set due to: $e"); + OwnCloudClientManagerFactory.setProxyHost(resources.getString(R.string.proxy_host)) + OwnCloudClientManagerFactory.setProxyPort(resources.getInteger(R.integer.proxy_port)) + } catch (e: Resources.NotFoundException) { + Log_OC.d(TAG, "Error caught at setProxyForNonBrandedPlusClients: $e") } } - private void registerGlobalPassCodeProtection() { - registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() { - - @Override - public void onActivityCreated(@NonNull Activity activity, Bundle savedInstanceState) { - Log_OC.d(activity.getClass().getSimpleName(), "onCreate(Bundle) starting"); - onboarding.launchActivityIfNeeded(activity); - } - - @Override - public void onActivityStarted(@NonNull Activity activity) { - Log_OC.d(activity.getClass().getSimpleName(), "onStart() starting"); - } - - @Override - public void onActivityResumed(@NonNull Activity activity) { - Log_OC.d(activity.getClass().getSimpleName(), "onResume() starting"); - // we are checking activity is not launcher activity because there is timer in launcher - // which will reopen the passcode screen - if (!(activity instanceof LauncherActivity)) { - passCodeManager.onActivityResumed(activity); - } - } - - @Override - public void onActivityPaused(@NonNull Activity activity) { - Log_OC.d(activity.getClass().getSimpleName(), "onPause() ending"); - } - - @Override - public void onActivityStopped(@NonNull Activity activity) { - Log_OC.d(activity.getClass().getSimpleName(), "onStop() ending"); - // since we are not showing passcode on launch activity - // so we don't need to call the stopped method as well - if (!(activity instanceof LauncherActivity)) { - passCodeManager.onActivityStopped(activity); - } - } - - @Override - public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) { - Log_OC.d(activity.getClass().getSimpleName(), "onSaveInstanceState(Bundle) starting"); - } - - @Override - public void onActivityDestroyed(@NonNull Activity activity) { - Log_OC.d(activity.getClass().getSimpleName(), "onDestroy() ending"); - } - }); - } + private fun setProxyConfig() { + if (!isClientBrandedPlus) { + Log_OC.d(TAG, "Proxy configuration cannot be set. Client is not branded plus.") + return + } - public static void initContactsBackup(UserAccountManager accountManager, BackgroundJobManager backgroundJobManager) { - ArbitraryDataProvider arbitraryDataProvider = new ArbitraryDataProviderImpl(appContext.get()); - if (accountManager == null) { - return; + val host = MDMConfig.getHost(this) + val port = MDMConfig.getPort(this) + if (host.isEmpty() || port == UNDEFINED_PROXY_PORT) { + Log_OC.d(TAG, "Proxy configuration cannot be found") + return } - List users = accountManager.getAllUsers(); - for (User user : users) { - if (backgroundJobManager != null && arbitraryDataProvider.getBooleanValue(user, PREFERENCE_CONTACTS_AUTOMATIC_BACKUP)) { - backgroundJobManager.schedulePeriodicContactsBackup(user); - } + try { + OwnCloudClientManagerFactory.setProxyHost(host) + OwnCloudClientManagerFactory.setProxyPort(port) + Log_OC.d(TAG, "Proxy configuration successfully set") + } catch (e: Resources.NotFoundException) { + Log_OC.e(TAG, "Proxy config cannot able to set due to: \$e") } } - private void insertConscrypt() { - Security.insertProviderAt(Conscrypt.newProvider(), 1); + private fun insertConscrypt() { + Security.insertProviderAt(Conscrypt.newProvider(), HIGHEST_SECURITY_PROVIDER_POSITION) try { - Conscrypt.Version version = Conscrypt.version(); - Log_OC.i(TAG, "Using Conscrypt/" - + version.major() - + DOT - + version.minor() - + DOT + version.patch() - + " for TLS"); - SSLEngine engine = SSLContext.getDefault().createSSLEngine(); - Log_OC.i(TAG, "Enabled protocols: " + Arrays.toString(engine.getEnabledProtocols()) + " }"); - Log_OC.i(TAG, "Enabled ciphers: " + Arrays.toString(engine.getEnabledCipherSuites()) + " }"); - } catch (NoSuchAlgorithmException e) { - Log_OC.e(TAG, e.getMessage()); + val version = Conscrypt.version() + Log_OC.i(TAG, "Using Conscrypt/${version.major()}$DOT${version.minor()}$DOT${version.patch()} for TLS") + val engine = SSLContext.getDefault().createSSLEngine() + Log_OC.i(TAG, "Enabled protocols: ${engine.enabledProtocols.contentToString()} }") + Log_OC.i(TAG, "Enabled ciphers: ${engine.enabledCipherSuites.contentToString()} }") + } catch (e: NoSuchAlgorithmException) { + Log_OC.e(TAG, e.message) } } - @SuppressLint("ApplySharedPref") // commit is done on purpose to write immediately - private void fixStoragePath() { - if (!preferences.isStoragePathFixEnabled()) { - StoragePoint[] storagePoints = DataStorageProvider.getInstance().getAvailableStoragePoints(); - String storagePath = preferences.getStoragePath(""); - - if (TextUtils.isEmpty(storagePath)) { - if (preferences.getLastSeenVersionCode() != 0) { - // We already used the app, but no storage is set - fix that! - preferences.setStoragePath(Environment.getExternalStorageDirectory().getAbsolutePath()); - preferences.removeKeysMigrationPreference(); - } else { - // find internal storage path that's indexable - boolean set = false; - for (StoragePoint storagePoint : storagePoints) { - if (storagePoint.getStorageType() == StoragePoint.StorageType.INTERNAL && - storagePoint.getPrivacyType() == StoragePoint.PrivacyType.PUBLIC) { - preferences.setStoragePath(storagePoint.getPath()); - preferences.removeKeysMigrationPreference(); - set = true; - break; - } - } - - if (!set) { - for (StoragePoint storagePoint : storagePoints) { - if (storagePoint.getPrivacyType() == StoragePoint.PrivacyType.PUBLIC) { - preferences.setStoragePath(storagePoint.getPath()); - preferences.removeKeysMigrationPreference(); - break; - } - } - - } - } - preferences.setStoragePathFixEnabled(true); - } else { - preferences.removeKeysMigrationPreference(); - preferences.setStoragePathFixEnabled(true); - } + private fun enableStrictMode() { + if (!BuildConfig.DEBUG || !BuildConfig.RUNTIME_PERF_ANALYSIS) { + return } - } - private void enableStrictMode() { - if (BuildConfig.DEBUG && BuildConfig.RUNTIME_PERF_ANALYSIS) { - Log_OC.d(TAG, "Enabling StrictMode"); - StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() - .detectDiskReads() - .detectDiskWrites() - .detectAll() - .penaltyLog() - .build()); - StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() - .detectLeakedSqlLiteObjects() - .detectLeakedClosableObjects() - .penaltyLog() - .build()); - } + Log_OC.d(TAG, "Enabling StrictMode") + StrictMode.setThreadPolicy( + StrictMode.ThreadPolicy.Builder() + .detectDiskReads() + .detectDiskWrites() + .detectAll() + .penaltyLog() + .build() + ) + StrictMode.setVmPolicy( + StrictMode.VmPolicy.Builder() + .detectLeakedSqlLiteObjects() + .detectLeakedClosableObjects() + .penaltyLog() + .build() + ) } - private void checkCancelDownloadJobs() { - if (backgroundJobManager != null && preferences.shouldStopDownloadJobsOnStart()) { - backgroundJobManager.cancelAllFilesDownloadJobs(); - preferences.setStopDownloadJobsOnStart(false); + private fun enableFileLoggingIfAllowed() { + if (!MDMConfig.isLogEnabled(this)) { + return } + + Log_OC.setLoggerImplementation(LegacyLoggerAdapter(logger)) + Log_OC.d(DEBUG_TAG, "start logging") } - public static void initSyncOperations( - final Context context, - final AppPreferences preferences, - final UploadsStorageManager uploadsStorageManager, - final UserAccountManager accountManager, - final ConnectivityService connectivityService, - final PowerManagementService powerManagementService, - final BackgroundJobManager backgroundJobManager, - final Clock clock, - final ViewThemeUtils viewThemeUtils, - final WalledCheckCache walledCheckCache) { - updateToAutoUpload(context); - cleanOldEntries(clock); - updateAutoUploadEntries(clock); - - if (getAppContext() != null) { - if (PermissionUtil.checkStoragePermission(getAppContext())) { - splitOutAutoUploadEntries(clock, viewThemeUtils); - } else { - preferences.setAutoUploadSplitEntriesEnabled(true); - } + @Suppress("TooGenericExceptionCaught") + private fun disableDeathOnFileUriExposure() { + try { + StrictMode::class.java.getMethod("disableDeathOnFileUriExposure").invoke(null) + } catch (e: Exception) { + Log_OC.d(DEBUG_TAG, "Failed to disable uri exposure") } + } - if (!preferences.isAutoUploadInitialized()) { - preferences.setAutoUploadInit(true); + private fun cancelDownloadJobsIfRequested() { + if (!preferences.shouldStopDownloadJobsOnStart()) { + return } - FilesSyncHelper.restartUploadsIfNeeded( - uploadsStorageManager, - accountManager, - connectivityService, - powerManagementService); - - backgroundJobManager.scheduleOfflineSync(); - - ReceiversHelper.registerNetworkChangeReceiver(uploadsStorageManager, - accountManager, - connectivityService, - powerManagementService, - walledCheckCache); - - ReceiversHelper.registerPowerChangeReceiver(uploadsStorageManager, - accountManager, - connectivityService, - powerManagementService); - - ReceiversHelper.registerPowerSaveReceiver(uploadsStorageManager, - accountManager, - connectivityService, - powerManagementService); + backgroundJobManager.cancelAllFilesDownloadJobs() + preferences.setStopDownloadJobsOnStart(false) } - public static void notificationChannels() { - if (getAppContext() != null) { - Context context = getAppContext(); - NotificationManager notificationManager = (NotificationManager) - context.getSystemService(Context.NOTIFICATION_SERVICE); - - if (notificationManager != null) { - createChannel(notificationManager, NotificationUtils.NOTIFICATION_CHANNEL_DOWNLOAD, - R.string.notification_channel_download_name_short, - R.string.notification_channel_download_description, context); - - createChannel(notificationManager, NotificationUtils.NOTIFICATION_CHANNEL_UPLOAD, - R.string.notification_channel_upload_name_short, - R.string.notification_channel_upload_description, context, NotificationManager.IMPORTANCE_LOW); - - createChannel(notificationManager, NotificationUtils.NOTIFICATION_CHANNEL_MEDIA, - R.string.notification_channel_media_name, - R.string.notification_channel_media_description, context); - - createChannel(notificationManager, NotificationUtils.NOTIFICATION_CHANNEL_FILE_SYNC, - R.string.notification_channel_file_sync_name, - R.string.notification_channel_file_sync_description, context); - - notificationManager.deleteNotificationChannel(NotificationUtils.NOTIFICATION_CHANNEL_FILE_OBSERVER); - - createChannel(notificationManager, NotificationUtils.NOTIFICATION_CHANNEL_PUSH, - R.string.notification_channel_push_name, R.string - .notification_channel_push_description, context, NotificationManager.IMPORTANCE_DEFAULT); - - createChannel(notificationManager, NotificationUtils.NOTIFICATION_CHANNEL_BACKGROUND_OPERATIONS, - R.string.notification_channel_background_operations_name, R.string - .notification_channel_background_operations_description, context, NotificationManager.IMPORTANCE_LOW); - - createChannel(notificationManager, NotificationUtils.NOTIFICATION_CHANNEL_GENERAL, R.string - .notification_channel_general_name, R.string.notification_channel_general_description, - context, NotificationManager.IMPORTANCE_DEFAULT); - - createChannel(notificationManager, NotificationUtils.NOTIFICATION_CHANNEL_OFFLINE_OPERATIONS, - R.string.notification_channel_offline_operations_name_short, - R.string.notification_channel_offline_operations_description, context); - - createChannel(notificationManager, - NotificationUtils.NOTIFICATION_CHANNEL_CONTENT_OBSERVER, - R.string.notification_channel_content_observer_name_short, - R.string.notification_channel_content_observer_description, - context, - NotificationManager.IMPORTANCE_LOW); - } else { - Log_OC.e(TAG, "Notification manager is null"); - } + private fun scheduleBackgroundJobs() { + backgroundJobManager.scheduleMediaFoldersDetectionJob() + backgroundJobManager.startMediaFoldersDetectionJob() + backgroundJobManager.schedulePeriodicAutoUpload() + backgroundJobManager.schedulePeriodicHealthStatus() + + if (preferences.isTwoWaySyncEnabled()) { + backgroundJobManager.scheduleInternal2WaySync(preferences.getTwoWaySyncInterval()) } - } - private static void createChannel(NotificationManager notificationManager, - String channelId, int channelName, - int channelDescription, Context context) { - createChannel(notificationManager, channelId, channelName, channelDescription, context, - NotificationManager.IMPORTANCE_LOW); + backgroundJobManager.startPeriodicallyOfflineOperation() } - private static void createChannel(NotificationManager notificationManager, - String channelId, int channelName, - int channelDescription, Context context, int importance) { - if (getAppContext() != null) { - CharSequence name = context.getString(channelName); - String description = context.getString(channelDescription); - NotificationChannel channel = new NotificationChannel(channelId, name, importance); - - channel.setDescription(description); - channel.enableLights(false); - channel.enableVibration(false); - notificationManager.createNotificationChannel(channel); + override fun androidInjector(): AndroidInjector = dispatchingAndroidInjector + + override fun networkAndServerConnectionListener(isNetworkAndServerAvailable: Boolean) { + if (isNetworkAndServerAvailable) { + backgroundJobManager.startOfflineOperations() } } - public static String string(int id) { - return getAppContext().getString(id); + override fun onTerminate() { + super.onTerminate() + connectivityService.removeListener(this) + ReceiversHelper.shutdown() } - public static String string(int id, Object args) { - return getAppContext().getString(id, args); - } + companion object { + @JvmField + val OUTDATED_SERVER_VERSION: OwnCloudVersion = NextcloudVersion.nextcloud_30 - public static Context getAppContext() { - return MainApp.appContext.get(); - } + @JvmField + val MINIMUM_SUPPORTED_SERVER_VERSION: OwnCloudVersion = OwnCloudVersion.nextcloud_20 - public static void setAppContext(Context context) { - MainApp.appContext = new WeakReference<>(context); - } + const val DOT: String = "." - public static String getStoragePath() { - return MainApp.storagePath; - } + private val TAG: String = MainApp::class.java.simpleName + private const val DEBUG_TAG = "Debug" + private const val CRASH_REPORTING_PROCESS_SUFFIX = ":crash" + private const val UNDEFINED_PROXY_PORT = -1 + private const val HIGHEST_SECURITY_PROVIDER_POSITION = 1 + private const val NO_PACKAGE_INFO_FLAGS = 0 - public static void setStoragePath(String path) { - MainApp.storagePath = path; - } + private var appContext: WeakReference? = null + private var storagePath: String? = null + private var appComponent: AppComponent? = null - // Methods to obtain Strings referring app_name - // From AccountAuthenticator - // public static final String ACCOUNT_TYPE = "owncloud"; - public static String getAccountType(Context context) { - return context.getResources().getString(R.string.account_type); - } + @JvmStatic + var isOnlyOnDevice: Boolean = false + private set - // From AccountAuthenticator - // public static final String AUTHORITY = "org.owncloud"; - public static String getAuthority() { - return string(R.string.authority); - } + @JvmStatic + var isOnlyPersonFiles: Boolean = false + private set - // From AccountAuthenticator - // public static final String AUTH_TOKEN_TYPE = "org.owncloud"; - public static String getAuthTokenType() { - return string(R.string.authority); - } + private fun initGlobalContext(context: Context) { + appContext = WeakReference(context) + } - // From ProviderMeta - // public static final String DB_FILE = "owncloud.db"; - public static String getDBFile() { - return string(R.string.db_file); - } + @JvmStatic + fun getAppComponent(): AppComponent = checkNotNull(appComponent) { "Dagger not initialized!" } - // From ProviderMeta - // private final String mDatabaseName = "ownCloud"; - public static String getDBName() { - return string(R.string.db_name); - } + @JvmStatic + val isClientBranded: Boolean + get() = getAppContext().resources.getBoolean(R.bool.is_branded_client) - /** - * name of data_folder, e.g., "owncloud" - */ - public static String getDataFolder() { - return string(R.string.data_folder); - } + @JvmStatic + val isClientBrandedPlus: Boolean + get() = getAppContext().resources.getBoolean(R.bool.is_branded_plus_client) - public static void showOnlyFilesOnDevice(boolean state) { - mOnlyOnDevice = state; - } + @JvmStatic + fun initContactsBackup(accountManager: UserAccountManager?, backgroundJobManager: BackgroundJobManager?) { + if (accountManager == null || backgroundJobManager == null) { + return + } - public static void showOnlyPersonalFiles(boolean state) { - mOnlyPersonalFiles = state; - } + val arbitraryDataProvider = ArbitraryDataProviderImpl(getAppContext()) + accountManager.allUsers + .filter { + arbitraryDataProvider.getBooleanValue( + it, + ContactsPreferenceActivity.PREFERENCE_CONTACTS_AUTOMATIC_BACKUP + ) + } + .forEach { backgroundJobManager.schedulePeriodicContactsBackup(it) } + } + + @JvmStatic + fun initSyncOperations( + context: Context, + preferences: AppPreferences, + uploadsStorageManager: UploadsStorageManager, + accountManager: UserAccountManager, + connectivityService: ConnectivityService, + powerManagementService: PowerManagementService, + backgroundJobManager: BackgroundJobManager, + clock: Clock, + viewThemeUtils: ViewThemeUtils?, + walledCheckCache: WalledCheckCache? + ) { + val appContext = getAppContext() + AutoUploadEntriesMigration.updateToAutoUpload(context) + AutoUploadEntriesMigration.cleanOldEntries(appContext, clock) + AutoUploadEntriesMigration.updateAutoUploadEntries(appContext, clock) + + if (PermissionUtil.checkStoragePermission(appContext)) { + AutoUploadEntriesMigration.splitOutAutoUploadEntries(appContext, clock) + } else { + preferences.setAutoUploadSplitEntriesEnabled(true) + } - public static boolean isOnlyOnDevice() { - return mOnlyOnDevice; - } + if (!preferences.isAutoUploadInitialized()) { + preferences.setAutoUploadInit(true) + } - public static boolean isOnlyPersonFiles() { - return mOnlyPersonalFiles; - } + FilesSyncHelper.restartUploadsIfNeeded( + uploadsStorageManager, + accountManager, + connectivityService, + powerManagementService + ) - public static Integer getMenuItemId() { - if (MainApp.isOnlyPersonFiles()) { - return R.id.nav_personal_files; - } + backgroundJobManager.scheduleOfflineSync() - if (MainApp.isOnlyOnDevice()) { - return R.id.nav_on_device; + ReceiversHelper.registerNetworkChangeReceiver( + uploadsStorageManager, + accountManager, + connectivityService, + powerManagementService, + walledCheckCache + ) + ReceiversHelper.registerPowerChangeReceiver( + uploadsStorageManager, + accountManager, + connectivityService, + powerManagementService + ) + ReceiversHelper.registerPowerSaveReceiver( + uploadsStorageManager, + accountManager, + connectivityService, + powerManagementService + ) } - return null; - } - - public static String getUserAgent() { - // Mozilla/5.0 (Android) Nextcloud-android/2.1.0 - return getUserAgent(R.string.nextcloud_user_agent); - } + @JvmStatic + fun string(@StringRes id: Int): String = getAppContext().getString(id) - public static void showMessage(int messageId) { - ContextExtensionsKt.showToast(getAppContext(), messageId); - } + @JvmStatic + fun string(@StringRes id: Int, args: Any?): String = getAppContext().getString(id, args) - // user agent - private static String getUserAgent(@StringRes int agent) { - String appString = string(agent); - String brandedName = string(R.string.name_for_branded_user_agent); - String packageName = getAppContext().getPackageName(); - String version = ""; + @JvmStatic + fun getAppContext(): Context = appContext!!.get()!! - try { - PackageInfo pInfo = getAppContext().getPackageManager().getPackageInfo(packageName, 0); - if (pInfo != null) { - version = pInfo.versionName; - } - } catch (PackageManager.NameNotFoundException e) { - Log_OC.e(TAG, "Trying to get packageName", e.getCause()); + @JvmStatic + fun setAppContext(context: Context?) { + appContext = WeakReference(context) } - return String.format(appString, version, brandedName); - } - - private static void updateToAutoUpload(Context context) { - AppPreferences preferences = AppPreferencesImpl.fromContext(context); - if (preferences.instantPictureUploadEnabled() || preferences.instantVideoUploadEnabled()) { - preferences.removeLegacyPreferences(); + @JvmStatic + fun getStoragePath(): String? = storagePath - // show info pop-up - try { - showAutoUploadAlertDialog(context); - } catch (WindowManager.BadTokenException e) { - Log_OC.i(TAG, "Error showing Auto Upload Update dialog, so skipping it: " + e.getMessage()); - } + @JvmStatic + fun setStoragePath(path: String?) { + storagePath = path } - } + @JvmStatic + fun getAccountType(context: Context): String = context.getString(R.string.account_type) - private static void showAutoUploadAlertDialog(Context context) { - new MaterialAlertDialogBuilder(context, R.style.Theme_ownCloud_Dialog) - .setTitle(R.string.drawer_synced_folders) - .setMessage(R.string.synced_folders_new_info) - .setPositiveButton(R.string.drawer_open, (dialog, which) -> { - Intent folderSyncIntent = NavigatorActivity.Companion.intent(context, NavigatorScreen.AutoUpload.INSTANCE); - dialog.dismiss(); - context.startActivity(folderSyncIntent); - }) - .setNegativeButton(R.string.drawer_close, (dialog, which) -> dialog.dismiss()) - .setIcon(R.drawable.nav_synced_folders) - .create() - .show(); - } - - private static void updateAutoUploadEntries(Clock clock) { - // updates entries to reflect their true paths - Context context = getAppContext(); - AppPreferences preferences = AppPreferencesImpl.fromContext(context); - if (!preferences.isAutoUploadPathsUpdateEnabled()) { - SyncedFolderProvider syncedFolderProvider = - new SyncedFolderProvider(MainApp.getAppContext().getContentResolver(), preferences, clock); - syncedFolderProvider.updateAutoUploadPaths(appContext.get()); - } - } + @JvmStatic + val authority: String + get() = string(R.string.authority) - private static void splitOutAutoUploadEntries(Clock clock, - final ViewThemeUtils viewThemeUtils) { - Context context = getAppContext(); - AppPreferences preferences = AppPreferencesImpl.fromContext(context); - if (!preferences.isAutoUploadSplitEntriesEnabled()) { - // magic to split out existing synced folders in two when needed - // otherwise, we migrate them to their proper type (image or video) - Log_OC.i(TAG, "Migrate synced_folders records for image/video split"); - ContentResolver contentResolver = context.getContentResolver(); - - SyncedFolderProvider syncedFolderProvider = new SyncedFolderProvider(contentResolver, preferences, clock); - - final List imageMediaFolders = MediaProvider.getImageFolders(contentResolver, - 1, - null, - true); - final List videoMediaFolders = MediaProvider.getVideoFolders(contentResolver, - 1, - null, - true); - - ArrayList idsToDelete = new ArrayList<>(); - List syncedFolders = syncedFolderProvider.getSyncedFolders(); - long primaryKey; - SyncedFolder newSyncedFolder; - for (SyncedFolder syncedFolder : syncedFolders) { - idsToDelete.add(syncedFolder.getId()); - Log_OC.i(TAG, "Migration check for synced_folders record: " - + syncedFolder.getId() + " - " + syncedFolder.getLocalPath()); - - for (MediaFolder imageMediaFolder : imageMediaFolders) { - String absolutePathOfImageFolder = imageMediaFolder.absolutePath; - - if (absolutePathOfImageFolder != null) { - if (absolutePathOfImageFolder.equals(syncedFolder.getLocalPath())) { - newSyncedFolder = (SyncedFolder) syncedFolder.clone(); - newSyncedFolder.setType(MediaFolderType.IMAGE); - primaryKey = syncedFolderProvider.storeSyncedFolder(newSyncedFolder); - Log_OC.i(TAG, "Migrated image synced_folders record: " - + primaryKey + " - " + newSyncedFolder.getLocalPath()); - break; - } - } - } + @JvmStatic + val authTokenType: String + get() = string(R.string.authority) - for (MediaFolder videoMediaFolder : videoMediaFolders) { - String absolutePathOfVideoFolder = videoMediaFolder.absolutePath; + @JvmStatic + val dbFile: String + get() = string(R.string.db_file) - if (absolutePathOfVideoFolder != null) { - if (absolutePathOfVideoFolder.equals(syncedFolder.getLocalPath())) { - newSyncedFolder = (SyncedFolder) syncedFolder.clone(); - newSyncedFolder.setType(MediaFolderType.VIDEO); - primaryKey = syncedFolderProvider.storeSyncedFolder(newSyncedFolder); - Log_OC.i(TAG, "Migrated video synced_folders record: " - + primaryKey + " - " + newSyncedFolder.getLocalPath()); - break; - } - } + @JvmStatic + val dbName: String + get() = string(R.string.db_name) - } - } + @JvmStatic + val dataFolder: String + get() = string(R.string.data_folder) - for (long id : idsToDelete) { - Log_OC.i(TAG, "Removing legacy synced_folders record: " + id); - syncedFolderProvider.deleteSyncedFolder(id); - } + @JvmStatic + fun showOnlyFilesOnDevice(state: Boolean) { + isOnlyOnDevice = state + } - preferences.setAutoUploadSplitEntriesEnabled(true); + @JvmStatic + fun showOnlyPersonalFiles(state: Boolean) { + isOnlyPersonFiles = state } - } - private static void cleanOldEntries(Clock clock) { - // previous versions of application created broken entries in the SyncedFolderProvider - // database, and this cleans all that and leaves 1 (newest) entry per synced folder - - Context context = getAppContext(); - AppPreferences preferences = AppPreferencesImpl.fromContext(context); - - if (!preferences.isLegacyClean()) { - SyncedFolderProvider syncedFolderProvider = - new SyncedFolderProvider(context.getContentResolver(), preferences, clock); - - List syncedFolderList = syncedFolderProvider.getSyncedFolders(); - Map, Long> syncedFolders = new HashMap<>(); - for (SyncedFolder syncedFolder : syncedFolderList) { - Pair checkPair = new Pair<>(syncedFolder.getAccount(), syncedFolder.getLocalPath()); - if (syncedFolders.containsKey(checkPair)) { - Long folderId = syncedFolders.get(checkPair); - - if (folderId != null) { - if (syncedFolder.getId() > folderId) { - syncedFolders.put(checkPair, syncedFolder.getId()); - } - } - } else { - syncedFolders.put(checkPair, syncedFolder.getId()); - } + @JvmStatic + val menuItemId: Int? + get() = when { + isOnlyPersonFiles -> R.id.nav_personal_files + isOnlyOnDevice -> R.id.nav_on_device + else -> null } - ArrayList ids = new ArrayList<>(syncedFolders.values()); + @JvmStatic + val userAgent: String + get() = buildUserAgent(R.string.nextcloud_user_agent) - if (ids.size() > 0) { - int deletedCount = syncedFolderProvider.deleteSyncedFoldersNotInList(ids); - if (deletedCount > 0) { - preferences.setLegacyClean(true); - } - } else { - preferences.setLegacyClean(true); - } + @JvmStatic + fun showMessage(@StringRes messageId: Int) { + getAppContext().showToast(messageId) } - } - @Override - public AndroidInjector androidInjector() { - return dispatchingAndroidInjector; - } - - public static void setAppTheme(DarkMode mode) { - switch (mode) { - case LIGHT -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO); - case DARK -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES); - case SYSTEM -> AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM); + private fun buildUserAgent(@StringRes agentFormat: Int): String { + val appString = string(agentFormat) + val brandedName = string(R.string.name_for_branded_user_agent) + return appString.format(appVersionName(), brandedName) } - } - @Override - public void networkAndServerConnectionListener(boolean isNetworkAndServerAvailable) { - if (backgroundJobManager == null) { - Log_OC.d(TAG, "Offline operations terminated, backgroundJobManager cannot be null"); - return; + private fun appVersionName(): String? { + val context = getAppContext() + return try { + val packageInfo: PackageInfo? = + context.packageManager.getPackageInfo(context.packageName, NO_PACKAGE_INFO_FLAGS) + if (packageInfo != null) packageInfo.versionName else "" + } catch (e: PackageManager.NameNotFoundException) { + Log_OC.e(TAG, "Trying to get packageName", e.cause) + "" + } } - if (isNetworkAndServerAvailable) { - backgroundJobManager.startOfflineOperations(); + @JvmStatic + fun setAppTheme(mode: DarkMode) { + val nightMode = when (mode) { + DarkMode.LIGHT -> AppCompatDelegate.MODE_NIGHT_NO + DarkMode.DARK -> AppCompatDelegate.MODE_NIGHT_YES + DarkMode.SYSTEM -> AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM + } + AppCompatDelegate.setDefaultNightMode(nightMode) } } - - @Override - public void onTerminate() { - super.onTerminate(); - connectivityService.removeListener(this); - ReceiversHelper.shutdown(); - } } diff --git a/app/src/main/java/com/owncloud/android/authentication/AuthenticatorActivity.kt b/app/src/main/java/com/owncloud/android/authentication/AuthenticatorActivity.kt index 2ef83e482af3..e78e720d432d 100644 --- a/app/src/main/java/com/owncloud/android/authentication/AuthenticatorActivity.kt +++ b/app/src/main/java/com/owncloud/android/authentication/AuthenticatorActivity.kt @@ -343,7 +343,7 @@ open class AuthenticatorActivity : } private fun mdmWebLoginUrl(): String? { - if (!MainApp.isClientBrandedPlus()) { + if (!MainApp.isClientBrandedPlus) { return null } return MDMConfig.getBaseUrl(this).takeIf { it.isNotEmpty() }?.plus(WEB_LOGIN) diff --git a/app/src/main/java/com/owncloud/android/authentication/GlobalPassCodeProtectionCallbacks.kt b/app/src/main/java/com/owncloud/android/authentication/GlobalPassCodeProtectionCallbacks.kt new file mode 100644 index 000000000000..fc0b662cca7b --- /dev/null +++ b/app/src/main/java/com/owncloud/android/authentication/GlobalPassCodeProtectionCallbacks.kt @@ -0,0 +1,59 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +package com.owncloud.android.authentication + +import android.app.Activity +import android.app.Application.ActivityLifecycleCallbacks +import android.os.Bundle +import com.nextcloud.client.onboarding.OnboardingService +import com.nmc.android.ui.LauncherActivity +import com.owncloud.android.lib.common.utils.Log_OC + +class GlobalPassCodeProtectionCallbacks( + private val onboarding: OnboardingService, + private val passCodeManager: PassCodeManager +) : ActivityLifecycleCallbacks { + + override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { + activity.logLifecycle("onCreate(Bundle) starting") + onboarding.launchActivityIfNeeded(activity) + } + + override fun onActivityStarted(activity: Activity) { + activity.logLifecycle("onStart() starting") + } + + override fun onActivityResumed(activity: Activity) { + activity.logLifecycle("onResume() starting") + if (activity !is LauncherActivity) { + passCodeManager.onActivityResumed(activity) + } + } + + override fun onActivityPaused(activity: Activity) { + activity.logLifecycle("onPause() ending") + } + + override fun onActivityStopped(activity: Activity) { + activity.logLifecycle("onStop() ending") + if (activity !is LauncherActivity) { + passCodeManager.onActivityStopped(activity) + } + } + + override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) { + activity.logLifecycle("onSaveInstanceState(Bundle) starting") + } + + override fun onActivityDestroyed(activity: Activity) { + activity.logLifecycle("onDestroy() ending") + } + + private fun Activity.logLifecycle(message: String) { + Log_OC.d(javaClass.simpleName, message) + } +} diff --git a/app/src/main/java/com/owncloud/android/authentication/signup/SignupWebViewLogin.kt b/app/src/main/java/com/owncloud/android/authentication/signup/SignupWebViewLogin.kt index 3a2b68b8d5b9..f82660408fa7 100644 --- a/app/src/main/java/com/owncloud/android/authentication/signup/SignupWebViewLogin.kt +++ b/app/src/main/java/com/owncloud/android/authentication/signup/SignupWebViewLogin.kt @@ -46,7 +46,7 @@ class SignupWebViewLogin( allowFileAccess = false javaScriptEnabled = true domStorageEnabled = true - userAgentString = MainApp.getUserAgent() + userAgentString = MainApp.userAgent saveFormData = false savePassword = false } diff --git a/app/src/main/java/com/owncloud/android/datamodel/MediaProvider.kt b/app/src/main/java/com/owncloud/android/datamodel/MediaProvider.kt index 935cd78f81e1..aecb4645980d 100644 --- a/app/src/main/java/com/owncloud/android/datamodel/MediaProvider.kt +++ b/app/src/main/java/com/owncloud/android/datamodel/MediaProvider.kt @@ -134,7 +134,7 @@ object MediaProvider { private fun isStorageAccessGranted(activity: AppCompatActivity?, getWithoutActivity: Boolean): Boolean = getWithoutActivity || (activity != null && PermissionUtil.checkStoragePermission(activity.applicationContext)) - private fun appDataPath(): String = MainApp.getStoragePath() + File.separator + MainApp.getDataFolder() + private fun appDataPath(): String = MainApp.getStoragePath() + File.separator + MainApp.dataFolder private fun bucketSelection(bucketIdColumn: String, bucketId: String?): String = bucketIdColumn + SQL_EQUALS + bucketId diff --git a/app/src/main/java/com/owncloud/android/datastorage/StoragePathFixer.kt b/app/src/main/java/com/owncloud/android/datastorage/StoragePathFixer.kt new file mode 100644 index 000000000000..6f81eeab6bc9 --- /dev/null +++ b/app/src/main/java/com/owncloud/android/datastorage/StoragePathFixer.kt @@ -0,0 +1,50 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ +package com.owncloud.android.datastorage + +import android.os.Environment +import com.nextcloud.client.preferences.AppPreferences + +class StoragePathFixer(private val preferences: AppPreferences) { + + fun fixIfNeeded() { + if (preferences.isStoragePathFixEnabled) { + return + } + + if (preferences.getStoragePath("").isNullOrEmpty()) { + applyDefaultStoragePath() + } else { + preferences.removeKeysMigrationPreference() + } + + preferences.isStoragePathFixEnabled = true + } + + private fun applyDefaultStoragePath() { + if (preferences.lastSeenVersionCode != 0) { + saveStoragePath(Environment.getExternalStorageDirectory().absolutePath) + return + } + + findIndexableStoragePoint()?.let { saveStoragePath(it.path) } + } + + private fun findIndexableStoragePoint(): StoragePoint? { + val publicStoragePoints = DataStorageProvider.getInstance() + .availableStoragePoints + .filter { it.privacyType == StoragePoint.PrivacyType.PUBLIC } + + return publicStoragePoints.firstOrNull { it.storageType == StoragePoint.StorageType.INTERNAL } + ?: publicStoragePoints.firstOrNull() + } + + private fun saveStoragePath(path: String?) { + preferences.setStoragePath(path) + preferences.removeKeysMigrationPreference() + } +} diff --git a/app/src/main/java/com/owncloud/android/ui/activity/ExternalSiteWebView.kt b/app/src/main/java/com/owncloud/android/ui/activity/ExternalSiteWebView.kt index fbb4689cb40e..6b87722ca725 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/ExternalSiteWebView.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/ExternalSiteWebView.kt @@ -169,7 +169,7 @@ open class ExternalSiteWebView : FileActivity() { useWideViewPort = true loadWithOverviewMode = true - userAgentString = MainApp.getUserAgent() + userAgentString = MainApp.userAgent saveFormData = false allowFileAccess = false javaScriptEnabled = true 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 c1616b00515d..0385a900130a 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 @@ -610,7 +610,7 @@ class FileDisplayActivity : val listOfFiles = listOfFilesFragment if (listOfFiles != null && searchQuery.isEmpty()) { if (!listFragmentJustCreated) { - listOfFiles.listDirectory(getCurrentDir(), file, MainApp.isOnlyOnDevice()) + listOfFiles.listDirectory(getCurrentDir(), file, MainApp.isOnlyOnDevice) } listFragmentJustCreated = true } else { @@ -641,7 +641,7 @@ class FileDisplayActivity : !fragment.isSearchFragment && startFile == null -> { if (!listFragmentJustCreated) { - fragment.listDirectory(MainApp.isOnlyOnDevice()) + fragment.listDirectory(MainApp.isOnlyOnDevice) } fragment.registerFabListener() updateActionBarTitleAndHomeButton(currentDir) @@ -944,7 +944,7 @@ class FileDisplayActivity : if (message.isNullOrEmpty()) { val current = getCurrentDir() - fragment.listDirectory(current, file, MainApp.isOnlyOnDevice()) + fragment.listDirectory(current, file, MainApp.isOnlyOnDevice) file = current updateActionBarTitleAndHomeButton(null) } else { @@ -978,7 +978,7 @@ class FileDisplayActivity : * 3) Fallback to All Files */ override val menuItemId: Int - get() = MainApp.getMenuItemId() ?: listOfFilesFragment?.menuItemId ?: R.id.nav_all_files + get() = MainApp.menuItemId ?: listOfFilesFragment?.menuItemId ?: R.id.nav_all_files override fun setFile(file: OCFile?) { super.setFile(file) @@ -1074,7 +1074,7 @@ class FileDisplayActivity : val root = storageManager.getFileByPath(OCFile.ROOT_PATH) it.resetSearchAttributes() file = root - it.listDirectory(root, MainApp.isOnlyOnDevice()) + it.listDirectory(root, MainApp.isOnlyOnDevice) } binding.fabMain.setImageResource(R.drawable.ic_plus) @@ -1126,11 +1126,11 @@ class FileDisplayActivity : fun refreshCurrentDirectory() { val currentDir = getCurrentDir()?.let { storageManager.getFileByDecryptedRemotePath(it.remotePath) } - topFileListFragment?.listDirectory(currentDir, MainApp.isOnlyOnDevice()) + topFileListFragment?.listDirectory(currentDir, MainApp.isOnlyOnDevice) } fun updateListOfFilesFragment() { - listOfFilesFragment?.listDirectory(MainApp.isOnlyOnDevice()) + listOfFilesFragment?.listDirectory(MainApp.isOnlyOnDevice) } fun resetSearchView() { @@ -1647,7 +1647,7 @@ class FileDisplayActivity : val parent = fileList.parentFolderFinder .getParentOnFirstParentRemoved(removedFolderRemotePath, storageManager) ?: return - fileList.listDirectory(parent, MainApp.isOnlyOnDevice()) + fileList.listDirectory(parent, MainApp.isOnlyOnDevice) updateActionBarTitleAndHomeButton(parent) file = parent } @@ -1655,7 +1655,7 @@ class FileDisplayActivity : private fun listDirectoryIfSynced(fileList: OCFileListFragment, currentDir: OCFile, syncedRemotePath: String?) { if (currentDir.remotePath != syncedRemotePath) return - fileList.listDirectory(currentDir, MainApp.isOnlyOnDevice()) + fileList.listDirectory(currentDir, MainApp.isOnlyOnDevice) } private fun updateScrollingAfterSync(fileList: OCFileListFragment) { @@ -1699,7 +1699,7 @@ class FileDisplayActivity : if (syncState != EmptyListState.LOADING) return syncState = when { - MainApp.isOnlyOnDevice() -> EmptyListState.ONLY_ON_DEVICE + MainApp.isOnlyOnDevice -> EmptyListState.ONLY_ON_DEVICE listOfFilesFragment?.searchEvent?.searchType == SearchRemoteOperation.SearchType.FAVORITE_SEARCH -> SearchType.FAVORITE_SEARCH diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListAdapter.kt b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListAdapter.kt index ffc32e108c7b..a52d719bf86e 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListAdapter.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/OCFileListAdapter.kt @@ -382,7 +382,7 @@ class OCFileListAdapter( fun shouldShowHeader(): Boolean { val directory = currentDirectory - return !(directory == null || MainApp.isOnlyOnDevice()) && ( + return !(directory == null || MainApp.isOnlyOnDevice) && ( shouldShowRecommendedFiles() || headerBinder.shouldShowOpenInNotes(directory) || headerBinder.hasRichWorkspace(directory) diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/filelist/data/OCFileListAdapterHelper.kt b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/data/OCFileListAdapterHelper.kt index 4057c8847844..0aaec513129c 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/filelist/data/OCFileListAdapterHelper.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/filelist/data/OCFileListAdapterHelper.kt @@ -64,7 +64,7 @@ class OCFileListAdapterHelper { ): Pair, FileSortOrder> { val showHiddenFiles = preferences.isShowHiddenFilesEnabled() val hasMimeTypeFilter = limitToMimeType.isNotEmpty() - val isRootAndPersonalOnly = (OCFile.ROOT_PATH == directory.remotePath && MainApp.isOnlyPersonFiles()) + val isRootAndPersonalOnly = (OCFile.ROOT_PATH == directory.remotePath && MainApp.isOnlyPersonFiles) val rawResult = getFolderContent(directory, dataProvider, onlyOnDevice) val filtered = ArrayList(rawResult.size) diff --git a/app/src/main/java/com/owncloud/android/ui/adapter/storagePermissionBanner/StoragePermissionBannerActionHandler.kt b/app/src/main/java/com/owncloud/android/ui/adapter/storagePermissionBanner/StoragePermissionBannerActionHandler.kt index 4a5a8ad655c6..98c38b8d49ee 100644 --- a/app/src/main/java/com/owncloud/android/ui/adapter/storagePermissionBanner/StoragePermissionBannerActionHandler.kt +++ b/app/src/main/java/com/owncloud/android/ui/adapter/storagePermissionBanner/StoragePermissionBannerActionHandler.kt @@ -21,7 +21,7 @@ import com.owncloud.android.utils.PermissionUtil fun StoragePermissionWarningBannerBinding.setup(activity: Activity, descriptionId: Int) { description.text = activity.getString(descriptionId) - val isBrandedAndFlavourGplay = (MainApp.isClientBranded() && isFlavourGPlay()) + val isBrandedAndFlavourGplay = (MainApp.isClientBranded && isFlavourGPlay()) allFilesAccess.setVisibleIf(!PermissionUtil.checkAllFilesAccess() && !isBrandedAndFlavourGplay) allFilesAccess.setOnClickListener { activity.openAllFilesAccessSettings(REQ_ALL_FILES_ACCESS) } 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 22d4f255a9fb..272e593df341 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 @@ -124,7 +124,7 @@ class OCFileListBottomSheetDialog( filterActionsForOfflineOperations() checkCreateEncryptedFolderVisibility() - if (MainApp.isClientBranded() && isFlavourGPlay()) { + if (MainApp.isClientBranded && isFlavourGPlay()) { // this way we can have branded clients with that permission val hasPermission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { PermissionUtil.manifestHasAllFilesPermission(context) diff --git a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.kt b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.kt index 5e80cd21e333..ba5b88d8ae13 100644 --- a/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.kt +++ b/app/src/main/java/com/owncloud/android/ui/fragment/OCFileListFragment.kt @@ -298,7 +298,7 @@ open class OCFileListFragment : override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) setupFileList(savedInstanceState) - listDirectory(MainApp.isOnlyOnDevice()) + listDirectory(MainApp.isOnlyOnDevice) } override fun onViewStateRestored(savedInstanceState: Bundle?) { @@ -1054,7 +1054,7 @@ open class OCFileListFragment : } private fun reloadAndRestorePosition() { - listDirectory(currentFile, MainApp.isOnlyOnDevice()) + listDirectory(currentFile, MainApp.isOnlyOnDevice) onRefresh(false) restoreIndexAndTopPosition() } @@ -1066,7 +1066,7 @@ open class OCFileListFragment : resetSortAndGridButtons() } - listDirectory(folder, MainApp.isOnlyOnDevice()) + listDirectory(folder, MainApp.isOnlyOnDevice) containerActivity?.onBrowsedDownTo(folder) saveIndexAndTopPosition(position) } @@ -1174,7 +1174,7 @@ open class OCFileListFragment : isSearchFragment = false val folder = currentFile ?: return updateFabState() - listDirectory(folder, MainApp.isOnlyOnDevice()) + listDirectory(folder, MainApp.isOnlyOnDevice) } val adapterFiles: List diff --git a/app/src/main/java/com/owncloud/android/ui/preview/media/PreviewMediaActivity.kt b/app/src/main/java/com/owncloud/android/ui/preview/media/PreviewMediaActivity.kt index 154248d0014a..e8c7df4eb155 100644 --- a/app/src/main/java/com/owncloud/android/ui/preview/media/PreviewMediaActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/preview/media/PreviewMediaActivity.kt @@ -229,7 +229,7 @@ class PreviewMediaActivity : return loader.forVirtualFolder(virtualFolderType, mediaState) } - return loader.forFolder(file?.parentId, MainApp.isOnlyOnDevice()) + return loader.forFolder(file?.parentId, MainApp.isOnlyOnDevice) } private fun showViewPager(user: User, mediaFiles: List) { diff --git a/app/src/main/java/com/owncloud/android/utils/FileStorageUtils.kt b/app/src/main/java/com/owncloud/android/utils/FileStorageUtils.kt index f131ca87103b..ee40daa59b93 100644 --- a/app/src/main/java/com/owncloud/android/utils/FileStorageUtils.kt +++ b/app/src/main/java/com/owncloud/android/utils/FileStorageUtils.kt @@ -112,7 +112,7 @@ object FileStorageUtils { @JvmStatic fun getSavePath(accountName: String?): String = - joinPath(MainApp.getStoragePath(), MainApp.getDataFolder(), encodeAccountName(accountName)) + joinPath(MainApp.getStoragePath(), MainApp.dataFolder, encodeAccountName(accountName)) @JvmStatic fun getDefaultSavePathFor(accountName: String?, file: OCFile): String = @@ -121,7 +121,7 @@ object FileStorageUtils { @JvmStatic fun getTemporalPath(accountName: String?): String = joinPath( MainApp.getStoragePath(), - MainApp.getDataFolder(), + MainApp.dataFolder, StringConstants.TEMP, encodeAccountName(accountName) ) @@ -136,7 +136,7 @@ object FileStorageUtils { @JvmStatic fun getAppTempDirectoryPath(context: Context): String = - joinPath(context.filesDir, MainApp.getDataFolder(), StringConstants.TEMP) + File.separator + joinPath(context.filesDir, MainApp.dataFolder, StringConstants.TEMP) + File.separator private fun encodeAccountName(accountName: String?): String? = Uri.encode(accountName, ACCOUNT_NAME_ALLOWED_CHARS) diff --git a/app/src/test/java/com/nextcloud/client/jobs/BackgroundJobFactoryTest.kt b/app/src/test/java/com/nextcloud/client/jobs/BackgroundJobFactoryTest.kt index e636c304bb82..d75f0f2b4885 100644 --- a/app/src/test/java/com/nextcloud/client/jobs/BackgroundJobFactoryTest.kt +++ b/app/src/test/java/com/nextcloud/client/jobs/BackgroundJobFactoryTest.kt @@ -33,7 +33,7 @@ import com.owncloud.android.operations.factory.UploadFileOperationFactory import com.owncloud.android.utils.theme.ViewThemeUtils import io.mockk.every import io.mockk.mockk -import io.mockk.mockkStatic +import io.mockk.mockkObject import org.greenrobot.eventbus.EventBus import org.junit.Assert.assertNotNull import org.junit.Before @@ -119,7 +119,7 @@ class BackgroundJobFactoryTest { @Before fun setUp() { - mockkStatic(MainApp::class) + mockkObject(MainApp) every { MainApp.getAppContext() } returns context MockitoAnnotations.openMocks(this) diff --git a/app/src/test/java/com/nextcloud/utils/HumanReadableFormatterTest.kt b/app/src/test/java/com/nextcloud/utils/HumanReadableFormatterTest.kt index 6c9cf3d04aec..3f1af24af695 100644 --- a/app/src/test/java/com/nextcloud/utils/HumanReadableFormatterTest.kt +++ b/app/src/test/java/com/nextcloud/utils/HumanReadableFormatterTest.kt @@ -9,13 +9,14 @@ package com.nextcloud.utils import com.owncloud.android.MainApp import com.owncloud.android.R +import io.mockk.every +import io.mockk.mockkObject +import io.mockk.unmockkObject import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertNotEquals import org.junit.Before import org.junit.Test -import org.mockito.MockedStatic -import org.mockito.Mockito import java.text.DateFormat import java.util.Date import java.util.Locale @@ -25,14 +26,13 @@ class HumanReadableFormatterTest { private val pendingLabel = "Pending" - private lateinit var mainApp: MockedStatic private lateinit var defaultLocale: Locale private lateinit var defaultTimeZone: TimeZone @Before fun setUp() { - mainApp = Mockito.mockStatic(MainApp::class.java) - mainApp.`when` { MainApp.string(R.string.common_pending) }.thenReturn(pendingLabel) + mockkObject(MainApp) + every { MainApp.string(R.string.common_pending) } returns pendingLabel defaultLocale = Locale.getDefault() defaultTimeZone = TimeZone.getDefault() @@ -42,7 +42,7 @@ class HumanReadableFormatterTest { @After fun tearDown() { - mainApp.close() + unmockkObject(MainApp) Locale.setDefault(defaultLocale) TimeZone.setDefault(defaultTimeZone) } diff --git a/app/src/test/java/com/nextcloud/utils/extensions/FileStorageManagerTests.kt b/app/src/test/java/com/nextcloud/utils/extensions/FileStorageManagerTests.kt index 6d92d1452bad..f37646096d3b 100644 --- a/app/src/test/java/com/nextcloud/utils/extensions/FileStorageManagerTests.kt +++ b/app/src/test/java/com/nextcloud/utils/extensions/FileStorageManagerTests.kt @@ -93,7 +93,7 @@ class FileStorageManagerTests { val context = mockk(relaxed = true) every { context.contentResolver } returns contentResolver - mockkStatic(MainApp::class) + mockkObject(MainApp) every { MainApp.getAppContext() } returns context mockkObject(NextcloudDatabase.Companion) every { NextcloudDatabase.getInstance(any()) } returns database diff --git a/app/src/test/java/com/owncloud/android/datamodel/MoveFilesTestBase.kt b/app/src/test/java/com/owncloud/android/datamodel/MoveFilesTestBase.kt index 018d5a91c94b..2ca36fa5731e 100644 --- a/app/src/test/java/com/owncloud/android/datamodel/MoveFilesTestBase.kt +++ b/app/src/test/java/com/owncloud/android/datamodel/MoveFilesTestBase.kt @@ -50,7 +50,7 @@ abstract class MoveFilesTestBase { mockUser = mockk(relaxed = true) every { mockUser.accountName } returns ACCOUNT_NAME - mockkStatic(MainApp::class) + mockkObject(MainApp) every { MainApp.getAppContext() } returns mockk(relaxed = true) val mockDb = mockk(relaxed = true) diff --git a/app/src/test/java/com/owncloud/android/ui/adapter/filelist/data/OCFileListAdapterHelperTest.kt b/app/src/test/java/com/owncloud/android/ui/adapter/filelist/data/OCFileListAdapterHelperTest.kt index 3e35e7d08281..f16a3307e8e8 100644 --- a/app/src/test/java/com/owncloud/android/ui/adapter/filelist/data/OCFileListAdapterHelperTest.kt +++ b/app/src/test/java/com/owncloud/android/ui/adapter/filelist/data/OCFileListAdapterHelperTest.kt @@ -15,7 +15,7 @@ import com.owncloud.android.utils.FileSortOrder import com.owncloud.android.utils.MimeType import io.mockk.every import io.mockk.mockk -import io.mockk.mockkStatic +import io.mockk.mockkObject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.runBlocking import org.junit.Assert.assertEquals @@ -36,9 +36,9 @@ class OCFileListAdapterHelperTest { @OptIn(ExperimentalCoroutinesApi::class) @Before fun setUp() { - mockkStatic(MainApp::class) + mockkObject(MainApp) every { MainApp.getAppContext() } returns context - every { MainApp.isOnlyPersonFiles() } returns false + every { MainApp.isOnlyPersonFiles } returns false } private inner class Sut { From 04579ece0a0b998898f5e4f4053645b810f6befb Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Thu, 5 Nov 2026 10:02:20 +0300 Subject: [PATCH 3/3] wip Signed-off-by: alperozturk96 --- app/src/main/java/com/owncloud/android/MainApp.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/owncloud/android/MainApp.kt b/app/src/main/java/com/owncloud/android/MainApp.kt index 1ccb075f138e..1fd64fae75e5 100644 --- a/app/src/main/java/com/owncloud/android/MainApp.kt +++ b/app/src/main/java/com/owncloud/android/MainApp.kt @@ -411,7 +411,7 @@ open class MainApp : val OUTDATED_SERVER_VERSION: OwnCloudVersion = NextcloudVersion.nextcloud_30 @JvmField - val MINIMUM_SUPPORTED_SERVER_VERSION: OwnCloudVersion = OwnCloudVersion.nextcloud_20 + val MINIMUM_SUPPORTED_SERVER_VERSION: OwnCloudVersion = NextcloudVersion.nextcloud_23 const val DOT: String = "."