From fdf284783ad99b3cf42d358d79e14931014c7ac6 Mon Sep 17 00:00:00 2001 From: alperozturk96 Date: Thu, 12 Feb 2026 16:01:08 +0100 Subject: [PATCH 1/2] app passcode m3 Signed-off-by: alperozturk96 --- .../nextcloud/client/di/ComponentsModule.java | 4 + .../ui/activity/ExtendedSettingsActivity.kt | 17 ++- .../android/ui/activity/SettingsActivity.java | 97 +++++------- .../android/ui/dialog/AppPassCodeDialog.kt | 143 ++++++++++++++++++ .../model/ExtendedSettingsActivityDialog.kt | 18 +-- .../main/res/layout/dialog_app_passcode.xml | 65 ++++++++ app/src/main/res/xml/preferences.xml | 2 +- 7 files changed, 273 insertions(+), 73 deletions(-) create mode 100644 app/src/main/java/com/owncloud/android/ui/dialog/AppPassCodeDialog.kt create mode 100644 app/src/main/res/layout/dialog_app_passcode.xml diff --git a/app/src/main/java/com/nextcloud/client/di/ComponentsModule.java b/app/src/main/java/com/nextcloud/client/di/ComponentsModule.java index 954fb16f8c3b..c6a483ad3b05 100644 --- a/app/src/main/java/com/nextcloud/client/di/ComponentsModule.java +++ b/app/src/main/java/com/nextcloud/client/di/ComponentsModule.java @@ -78,6 +78,7 @@ import com.owncloud.android.ui.activity.UploadListActivity; import com.owncloud.android.ui.activity.UserInfoActivity; import com.owncloud.android.ui.dialog.AccountRemovalDialog; +import com.owncloud.android.ui.dialog.AppPassCodeDialog; import com.owncloud.android.ui.dialog.ChooseRichDocumentsTemplateDialogFragment; import com.owncloud.android.ui.dialog.ChooseTemplateDialogFragment; import com.owncloud.android.ui.dialog.ConfirmationDialogFragment; @@ -427,6 +428,9 @@ abstract class ComponentsModule { @ContributesAndroidInjector abstract ThemeSelectionDialog themeSelectionDialog(); + @ContributesAndroidInjector + abstract AppPassCodeDialog appPassCodeDialog(); + @ContributesAndroidInjector abstract SharePasswordDialogFragment sharePasswordDialogFragment(); diff --git a/app/src/main/java/com/owncloud/android/ui/activity/ExtendedSettingsActivity.kt b/app/src/main/java/com/owncloud/android/ui/activity/ExtendedSettingsActivity.kt index d96c91f2c0c8..0ebbc7cdbc58 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/ExtendedSettingsActivity.kt +++ b/app/src/main/java/com/owncloud/android/ui/activity/ExtendedSettingsActivity.kt @@ -39,7 +39,8 @@ class ExtendedSettingsActivity : AppCompatActivity() { return } - dialogType.showDialog(this) + val dismissable = intent.getBooleanExtra(EXTRA_DISMISSABLE, true) + dialogType.showDialog(this, dismissable) dialogShown = true } @@ -49,12 +50,18 @@ class ExtendedSettingsActivity : AppCompatActivity() { } companion object { + private const val EXTRA_DISMISSABLE = "dismissable" private const val EXTRA_DIALOG_TYPE = "dialog_type" private const val KEY_DIALOG_SHOWN = "dialog_shown" - fun createIntent(context: Context, dialogType: ExtendedSettingsActivityDialog): Intent = - Intent(context, ExtendedSettingsActivity::class.java).apply { - putExtra(EXTRA_DIALOG_TYPE, dialogType.key) - } + @JvmOverloads + fun createIntent( + context: Context, + dialogType: ExtendedSettingsActivityDialog, + dismissable: Boolean = true + ): Intent = Intent(context, ExtendedSettingsActivity::class.java).apply { + putExtra(EXTRA_DIALOG_TYPE, dialogType.key) + putExtra(EXTRA_DISMISSABLE, dismissable) + } } } diff --git a/app/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java b/app/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java index 65e70c062977..0bb5a9243615 100644 --- a/app/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java +++ b/app/src/main/java/com/owncloud/android/ui/activity/SettingsActivity.java @@ -27,7 +27,6 @@ import android.os.Bundle; import android.os.Handler; import android.os.Looper; -import android.preference.ListPreference; import android.preference.Preference; import android.preference.PreferenceActivity; import android.preference.PreferenceCategory; @@ -65,7 +64,6 @@ import com.owncloud.android.lib.common.ExternalLinkType; import com.owncloud.android.lib.common.utils.Log_OC; import com.owncloud.android.providers.DocumentsStorageProvider; -import com.owncloud.android.ui.ListPreferenceDialog; import com.owncloud.android.ui.ThemeableSwitchPreference; import com.owncloud.android.ui.asynctasks.LoadingVersionNumberTask; import com.owncloud.android.ui.dialog.setupEncryption.SetupEncryptionDialogFragment; @@ -80,7 +78,6 @@ import com.owncloud.android.utils.theme.CapabilityUtils; import com.owncloud.android.utils.theme.ViewThemeUtils; -import java.util.ArrayList; import java.util.Objects; import javax.inject.Inject; @@ -129,7 +126,7 @@ public class SettingsActivity extends PreferenceActivity private Uri serverBaseUri; - private ListPreferenceDialog lock; + private Preference lock; private ThemeableSwitchPreference showHiddenFiles; private ThemeableSwitchPreference showEcosystemApps; private AppCompatDelegate delegate; @@ -212,9 +209,8 @@ public void onBackPressed() { private void showPasscodeDialogIfEnforceAppProtection() { if (MDMConfig.INSTANCE.enforceProtection(this) && Objects.equals(preferences.getLockPreference(), SettingsActivity.LOCK_NONE) && lock != null) { - lock.showDialog(); - lock.dismissible(false); - lock.enableCancelButton(false); + Intent intent = ExtendedSettingsActivity.Companion.createIntent(this, ExtendedSettingsActivityDialog.AppPasscode, false); + startActivityForResult(intent, ExtendedSettingsActivityDialog.AppPasscode.getResultId()); } } @@ -742,63 +738,33 @@ private void setupShowEcosystemAppsPreference(PreferenceCategory preferenceCateg private void setupLockPreference(PreferenceCategory preferenceCategoryDetails, boolean passCodeEnabled, boolean deviceCredentialsEnabled) { - boolean enforceProtection = MDMConfig.INSTANCE.enforceProtection(this); - lock = (ListPreferenceDialog) findPreference(PREFERENCE_LOCK); - int optionSize = 3; - if (enforceProtection) { - optionSize = 2; - } - + lock = findPreference(PREFERENCE_LOCK); if (lock != null && (passCodeEnabled || deviceCredentialsEnabled)) { - ArrayList lockEntries = new ArrayList<>(optionSize); - lockEntries.add(getString(R.string.prefs_lock_using_passcode)); - lockEntries.add(getString(R.string.prefs_lock_using_device_credentials)); - - ArrayList lockValues = new ArrayList<>(optionSize); - lockValues.add(LOCK_PASSCODE); - lockValues.add(LOCK_DEVICE_CREDENTIALS); - - if (!enforceProtection) { - lockEntries.add(getString(R.string.prefs_lock_none)); - lockValues.add(LOCK_NONE); - } + String currentLock = preferences.getLockPreference(); + updateLockSummary(lock, currentLock); - if (!passCodeEnabled) { - lockEntries.remove(getString(R.string.prefs_lock_using_passcode)); - lockValues.remove(LOCK_PASSCODE); - } else if (!deviceCredentialsEnabled || !DeviceCredentialUtils.areCredentialsAvailable(getApplicationContext())) { - lockEntries.remove(getString(R.string.prefs_lock_using_device_credentials)); - lockValues.remove(LOCK_DEVICE_CREDENTIALS); - } - - String[] lockEntriesArr = new String[lockEntries.size()]; - lockEntriesArr = lockEntries.toArray(lockEntriesArr); - String[] lockValuesArr = new String[lockValues.size()]; - lockValuesArr = lockValues.toArray(lockValuesArr); - - lock.setEntries(lockEntriesArr); - lock.setEntryValues(lockValuesArr); - lock.setSummary(lock.getEntry()); - - lock.setOnPreferenceChangeListener((preference, o) -> { - pendingLock = LOCK_NONE; - String oldValue = ((ListPreference) preference).getValue(); - String newValue = (String) o; - if (!oldValue.equals(newValue)) { - if (LOCK_NONE.equals(oldValue)) { - enableLock(newValue); - } else { - pendingLock = newValue; - disableLock(oldValue); - } - } - return false; + lock.setOnPreferenceClickListener(preference -> { + Intent intent = ExtendedSettingsActivity.Companion.createIntent(this, ExtendedSettingsActivityDialog.AppPasscode); + startActivityForResult(intent, ExtendedSettingsActivityDialog.AppPasscode.getResultId()); + return true; }); } else { preferenceCategoryDetails.removePreference(lock); } } + private void updateLockSummary(Preference lockPreference, String lockValue) { + String summary; + if (LOCK_PASSCODE.equals(lockValue)) { + summary = getString(R.string.prefs_lock_using_passcode); + } else if (LOCK_DEVICE_CREDENTIALS.equals(lockValue)) { + summary = getString(R.string.prefs_lock_using_device_credentials); + } else { + summary = getString(R.string.prefs_lock_none); + } + lockPreference.setSummary(summary); + } + private void setupAutoUploadCategory(PreferenceScreen preferenceScreen) { final PreferenceCategory preferenceCategorySyncedFolders = (PreferenceCategory) findPreference("synced_folders_category"); @@ -853,8 +819,10 @@ private void enableLock(String lock) { } private void changeLockSetting(String value) { - lock.setValue(value); - lock.setSummary(lock.getEntry()); + preferences.setLockPreference(value); + if (lock != null) { + updateLockSummary(lock, value); + } DocumentsStorageProvider.notifyRootsChanged(this); } @@ -1067,6 +1035,19 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { // needed for to change status bar color recreate(); } + } else if (requestCode == ExtendedSettingsActivityDialog.AppPasscode.getResultId() && data != null) { + String selectedLock = data.getStringExtra(ExtendedSettingsActivityDialog.AppPasscode.getKey()); + if (selectedLock != null) { + String currentLock = preferences.getLockPreference(); + if (!currentLock.equals(selectedLock)) { + if (LOCK_NONE.equals(currentLock)) { + enableLock(selectedLock); + } else { + pendingLock = selectedLock; + disableLock(currentLock); + } + } + } } else if (requestCode == REQ_ALL_FILES_ACCESS) { final PreferenceCategory preferenceCategorySync = (PreferenceCategory) findPreference("sync"); setupAllFilesAccessPreference(preferenceCategorySync); diff --git a/app/src/main/java/com/owncloud/android/ui/dialog/AppPassCodeDialog.kt b/app/src/main/java/com/owncloud/android/ui/dialog/AppPassCodeDialog.kt new file mode 100644 index 000000000000..611248b2cca2 --- /dev/null +++ b/app/src/main/java/com/owncloud/android/ui/dialog/AppPassCodeDialog.kt @@ -0,0 +1,143 @@ +/* + * Nextcloud - Android Client + * + * SPDX-FileCopyrightText: 2026 Alper Ozturk + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +package com.owncloud.android.ui.dialog + +import android.app.Dialog +import android.os.Bundle +import androidx.appcompat.app.AlertDialog +import androidx.core.os.bundleOf +import androidx.fragment.app.DialogFragment +import androidx.fragment.app.setFragmentResult +import com.google.android.material.button.MaterialButton +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.nextcloud.client.di.Injectable +import com.nextcloud.client.preferences.AppPreferences +import com.nextcloud.utils.extensions.setVisibleIf +import com.nextcloud.utils.mdm.MDMConfig +import com.owncloud.android.R +import com.owncloud.android.databinding.DialogAppPasscodeBinding +import com.owncloud.android.ui.activity.SettingsActivity +import com.owncloud.android.ui.model.ExtendedSettingsActivityDialog +import com.owncloud.android.utils.DeviceCredentialUtils +import com.owncloud.android.utils.theme.ViewThemeUtils +import javax.inject.Inject + +class AppPassCodeDialog : + DialogFragment(), + Injectable { + + @Inject + lateinit var preferences: AppPreferences + + @Inject + lateinit var viewThemeUtils: ViewThemeUtils + + private lateinit var binding: DialogAppPasscodeBinding + + override fun onStart() { + super.onStart() + val alertDialog = dialog as AlertDialog + + val positiveButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE) as? MaterialButton + positiveButton?.let { + viewThemeUtils.material.colorMaterialButtonPrimaryTonal(it) + } + + val dismissable = arguments?.getBoolean(ARG_DISMISSABLE, true) ?: true + isCancelable = dismissable + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + binding = DialogAppPasscodeBinding.inflate(layoutInflater) + + val currentLock = preferences.lockPreference ?: SettingsActivity.LOCK_NONE + val passCodeEnabled = resources.getBoolean(R.bool.passcode_enabled) + val deviceCredentialsEnabled = resources.getBoolean(R.bool.device_credentials_enabled) + val enforceProtection = MDMConfig.enforceProtection(requireContext()) + val deviceCredentialsAvailable = DeviceCredentialUtils.areCredentialsAvailable(requireContext()) + val dismissable = arguments?.getBoolean(ARG_DISMISSABLE, true) ?: true + + binding.lockPasscode.setVisibleIf(passCodeEnabled) + binding.lockDeviceCredentials.setVisibleIf(deviceCredentialsEnabled && deviceCredentialsAvailable) + binding.lockNone.setVisibleIf(!enforceProtection) + + setupTheme() + setCurrentSelection(currentLock) + setupListener() + + val builder = MaterialAlertDialogBuilder(requireContext()) + .setView(binding.root) + .setPositiveButton(R.string.common_ok) { _, _ -> + applySelection() + dismiss() + } + + if (!enforceProtection && dismissable) { + builder.setNegativeButton(R.string.common_cancel) { _, _ -> + dismiss() + } + } + + builder.setCancelable(dismissable) + + viewThemeUtils.dialog.colorMaterialAlertDialogBackground(requireContext(), builder) + + return builder.create() + } + + private fun setupTheme() { + viewThemeUtils.platform.apply { + colorTextView(binding.dialogTitle) + themeRadioButton(binding.lockPasscode) + themeRadioButton(binding.lockDeviceCredentials) + themeRadioButton(binding.lockNone) + } + } + + private fun setCurrentSelection(currentLock: String) { + val radioGroup = binding.lockRadioGroup + + when (currentLock) { + SettingsActivity.LOCK_PASSCODE -> radioGroup.check(R.id.lock_passcode) + SettingsActivity.LOCK_DEVICE_CREDENTIALS -> radioGroup.check(R.id.lock_device_credentials) + SettingsActivity.LOCK_NONE -> radioGroup.check(R.id.lock_none) + } + } + + private fun setupListener() { + binding.lockRadioGroup.setOnCheckedChangeListener { _, checkedId -> + val selectedLock = when (checkedId) { + R.id.lock_passcode -> SettingsActivity.LOCK_PASSCODE + R.id.lock_device_credentials -> SettingsActivity.LOCK_DEVICE_CREDENTIALS + R.id.lock_none -> SettingsActivity.LOCK_NONE + else -> SettingsActivity.LOCK_NONE + } + + currentSelection = selectedLock + } + } + + private var currentSelection: String? = null + + private fun applySelection() { + val selectedLock = currentSelection ?: return + + setFragmentResult( + ExtendedSettingsActivityDialog.AppPasscode.key, + bundleOf(ExtendedSettingsActivityDialog.AppPasscode.key to selectedLock) + ) + } + + companion object { + private const val ARG_DISMISSABLE = "dismissable" + + fun instance(dismissable: Boolean): AppPassCodeDialog = AppPassCodeDialog().apply { + arguments = bundleOf(ARG_DISMISSABLE to dismissable) + } + } +} diff --git a/app/src/main/java/com/owncloud/android/ui/model/ExtendedSettingsActivityDialog.kt b/app/src/main/java/com/owncloud/android/ui/model/ExtendedSettingsActivityDialog.kt index 4316d78f7755..b89ed944c40c 100644 --- a/app/src/main/java/com/owncloud/android/ui/model/ExtendedSettingsActivityDialog.kt +++ b/app/src/main/java/com/owncloud/android/ui/model/ExtendedSettingsActivityDialog.kt @@ -11,14 +11,16 @@ import android.app.Activity.RESULT_OK import android.content.Intent import com.nextcloud.ui.ChooseStorageLocationDialogFragment import com.owncloud.android.ui.activity.ExtendedSettingsActivity +import com.owncloud.android.ui.dialog.AppPassCodeDialog import com.owncloud.android.ui.dialog.ThemeSelectionDialog @Suppress("MagicNumber") enum class ExtendedSettingsActivityDialog(val tag: String, val key: String, val resultId: Int) { StorageLocation("choose_storage_location", "storage_selection_result", 13), - ThemeSelection("theme_selection", "theme_selection_result", 14); + ThemeSelection("theme_selection", "theme_selection_result", 14), + AppPasscode("app_passcode", "app_passcode_result", 15); - fun showDialog(activity: ExtendedSettingsActivity) { + fun showDialog(activity: ExtendedSettingsActivity, dismissable: Boolean = true) { activity.run { if (supportFragmentManager.findFragmentByTag(tag) != null) { return @@ -38,13 +40,11 @@ enum class ExtendedSettingsActivityDialog(val tag: String, val key: String, val finish() } - if (this@ExtendedSettingsActivityDialog == StorageLocation) { - ChooseStorageLocationDialogFragment() - } else { - ThemeSelectionDialog() - }.run { - show(supportFragmentManager, tag) - } + when (this@ExtendedSettingsActivityDialog) { + StorageLocation -> ChooseStorageLocationDialogFragment() + ThemeSelection -> ThemeSelectionDialog() + AppPasscode -> AppPassCodeDialog.instance(dismissable) + }.show(supportFragmentManager, tag) } } } diff --git a/app/src/main/res/layout/dialog_app_passcode.xml b/app/src/main/res/layout/dialog_app_passcode.xml new file mode 100644 index 000000000000..2a04f6d84180 --- /dev/null +++ b/app/src/main/res/layout/dialog_app_passcode.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index ff17922ff1f8..74655f678fa1 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -51,7 +51,7 @@ - Date: Thu, 12 Feb 2026 16:02:39 +0100 Subject: [PATCH 2/2] app passcode m3 Signed-off-by: alperozturk96 --- app/src/main/res/layout/dialog_app_passcode.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/layout/dialog_app_passcode.xml b/app/src/main/res/layout/dialog_app_passcode.xml index 2a04f6d84180..7f530e021170 100644 --- a/app/src/main/res/layout/dialog_app_passcode.xml +++ b/app/src/main/res/layout/dialog_app_passcode.xml @@ -18,7 +18,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="@dimen/standard_margin" - android:text="@string/prefs_lock" + android:text="@string/prefs_lock_title" android:textAppearance="?attr/textAppearanceHeadlineSmall" android:textColor="?attr/colorOnSurface" />