diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 08442f98..bdcc2dd6 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,5 +1,6 @@ - + + @@ -16,6 +18,7 @@ + android:theme="@style/AppTheme.NoActionBar" + tools:targetApi="26" /> + android:permission="android.permission.BIND_AUTOFILL_SERVICE" + tools:targetApi="26"> diff --git a/app/src/main/java/es/wolfi/app/passman/PassmanApp.java b/app/src/main/java/es/wolfi/app/passman/PassmanApp.java new file mode 100644 index 00000000..eb822ec9 --- /dev/null +++ b/app/src/main/java/es/wolfi/app/passman/PassmanApp.java @@ -0,0 +1,55 @@ +package es.wolfi.app.passman; + +import android.app.Activity; +import android.app.Application; +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +public class PassmanApp extends Application { + private Activity currentActivity; + + @Override + public void onCreate() { + super.onCreate(); + registerActivityLifecycleCallbacks(new ActivityLifecycleCallbacks() { + @Override + public void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {} + + @Override + public void onActivityStarted(@NonNull Activity activity) { + currentActivity = activity; + } + + @Override + public void onActivityResumed(@NonNull Activity activity) { + currentActivity = activity; + } + + @Override + public void onActivityPaused(@NonNull Activity activity) { + if (currentActivity == activity) { + currentActivity = null; + } + } + + @Override + public void onActivityStopped(@NonNull Activity activity) {} + + @Override + public void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {} + + @Override + public void onActivityDestroyed(@NonNull Activity activity) { + if (currentActivity == activity) { + currentActivity = null; + } + } + }); + } + + public Activity getCurrentActivity() { + return currentActivity; + } +} diff --git a/app/src/main/java/es/wolfi/app/passman/SettingValues.java b/app/src/main/java/es/wolfi/app/passman/SettingValues.java index 6f3af7a8..941f27aa 100644 --- a/app/src/main/java/es/wolfi/app/passman/SettingValues.java +++ b/app/src/main/java/es/wolfi/app/passman/SettingValues.java @@ -43,7 +43,9 @@ public enum SettingValues { KEY_STORE_ENCRYPTION_KEY("key_store_encryption_key"), CREDENTIAL_LABEL_SORT("credential_label_sort"), CASE_INSENSITIVE_CREDENTIAL_LABEL_SORT("case_insensitive_credential_label_sort"), - RESTORE_CUSTOM_CREDENTIAL_SORT_ORDER("restore_custom_credential_sort_order"); + RESTORE_CUSTOM_CREDENTIAL_SORT_ORDER("restore_custom_credential_sort_order"), + VAULT_AUTO_LOCK_DELAY("vault_auto_lock_delay"), + ENABLE_SCREENSHOT_PROTECTION("enable_screenshot_protection"); private final String name; diff --git a/app/src/main/java/es/wolfi/app/passman/VaultLockManager.java b/app/src/main/java/es/wolfi/app/passman/VaultLockManager.java new file mode 100644 index 00000000..ed40e6fd --- /dev/null +++ b/app/src/main/java/es/wolfi/app/passman/VaultLockManager.java @@ -0,0 +1,174 @@ +package es.wolfi.app.passman; + +import android.app.Activity; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.os.Build; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; + +import androidx.core.app.NotificationCompat; +import androidx.core.app.NotificationManagerCompat; + +import es.wolfi.app.passman.activities.BaseActivity; +import es.wolfi.app.passman.activities.PasswordListActivity; +import es.wolfi.passman.API.Vault; + +public class VaultLockManager { + private static final String TAG = "VaultLockManager"; + private static final String CHANNEL_ID = "vault_security"; + private static final int NOTIFICATION_ID = 1001; + + private static VaultLockManager instance; + private final Handler handler = new Handler(Looper.getMainLooper()); + private Runnable lockRunnable; + private Runnable countdownRunnable; + private int timeoutMinutes = 0; + private long lastInteractionTime = 0; + private final PassmanApp passmanApp; + + private VaultLockManager(PassmanApp passmanApp) { + this.passmanApp = passmanApp; + createNotificationChannel(); + } + + public static synchronized VaultLockManager getInstance(PassmanApp passmanApp) { + if (instance == null) { + instance = new VaultLockManager(passmanApp); + } + return instance; + } + + private void createNotificationChannel() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + CharSequence name = passmanApp.getString(R.string.security_channel_name); + int importance = NotificationManager.IMPORTANCE_DEFAULT; + NotificationChannel channel = new NotificationChannel(CHANNEL_ID, name, importance); + NotificationManager notificationManager = passmanApp.getSystemService(NotificationManager.class); + if (notificationManager != null) { + notificationManager.createNotificationChannel(channel); + } + } + } + + public void updateConfig(int minutes) { + this.timeoutMinutes = minutes; + resetTimer(); + } + + public void resetTimer() { + lastInteractionTime = System.currentTimeMillis(); + + handler.removeCallbacks(lockRunnable); + handler.removeCallbacks(countdownRunnable); + + hideOverlayOnCurrentActivity(); + + if (timeoutMinutes <= 0) { + return; + } + + long totalDelayMillis = (long) timeoutMinutes * 60 * 1000; + long countdownStartMillis = totalDelayMillis - 3000; + + if (totalDelayMillis <= 3000) { + // If timeout is very short, just lock without countdown for now to avoid complexity + lockRunnable = this::lockActiveVault; + handler.postDelayed(lockRunnable, totalDelayMillis); + } else { + countdownRunnable = () -> startCountdown(3); + handler.postDelayed(countdownRunnable, countdownStartMillis); + } + } + + public void checkLockOnResume() { + if (timeoutMinutes <= 0) return; + + long currentTime = System.currentTimeMillis(); + long elapsedMillis = currentTime - lastInteractionTime; + long timeoutMillis = (long) timeoutMinutes * 60 * 1000; + + if (elapsedMillis >= timeoutMillis) { + Log.d(TAG, "Lock timeout exceeded during background/pause, locking now"); + lockActiveVault(); + } else { + // Recalculate remaining time and reschedule + resetTimer(); + // Adjust the timer to account for elapsed time + handler.removeCallbacks(lockRunnable); + handler.removeCallbacks(countdownRunnable); + + long remainingMillis = timeoutMillis - elapsedMillis; + if (remainingMillis <= 3000) { + lockRunnable = this::lockActiveVault; + handler.postDelayed(lockRunnable, remainingMillis); + } else { + countdownRunnable = () -> startCountdown(3); + handler.postDelayed(countdownRunnable, remainingMillis - 3000); + } + } + } + + private void startCountdown(int seconds) { + if (seconds <= 0) { + lockActiveVault(); + return; + } + + showOverlayOnCurrentActivity(seconds); + + countdownRunnable = () -> startCountdown(seconds - 1); + handler.postDelayed(countdownRunnable, 1000); + } + + private void showOverlayOnCurrentActivity(int seconds) { + Activity currentActivity = passmanApp.getCurrentActivity(); + if (currentActivity instanceof BaseActivity baseActivity) { + baseActivity.showCountdownOverlay(seconds); + } + } + + private void hideOverlayOnCurrentActivity() { + Activity currentActivity = passmanApp.getCurrentActivity(); + if (currentActivity instanceof BaseActivity baseActivity) { + baseActivity.hideCountdownOverlay(); + } + } + + public void lockActiveVault() { + hideOverlayOnCurrentActivity(); + + SingleTon ton = SingleTon.getTon(); + Vault vault = (Vault) ton.getExtra(SettingValues.ACTIVE_VAULT.toString()); + if (vault != null && vault.is_unlocked()) { + Log.d(TAG, "Locking active vault"); + vault.lock(); + ton.addExtra(SettingValues.ACTIVE_VAULT.toString(), vault); + ton.addExtra(vault.guid, vault); + + postLockedNotification(); + + Activity currentActivity = passmanApp.getCurrentActivity(); + if (currentActivity instanceof PasswordListActivity passwordListActivity) { + passwordListActivity.runOnUiThread(passwordListActivity::lockVault); + } + } + } + + private void postLockedNotification() { + NotificationCompat.Builder builder = new NotificationCompat.Builder(passmanApp, CHANNEL_ID) + .setSmallIcon(R.mipmap.ic_launcher) + .setContentTitle(passmanApp.getString(R.string.vault_locked_notification_title)) + .setContentText(passmanApp.getString(R.string.vault_locked_notification_text)) + .setPriority(NotificationCompat.PRIORITY_DEFAULT) + .setAutoCancel(true); + + NotificationManagerCompat notificationManager = NotificationManagerCompat.from(passmanApp); + try { + notificationManager.notify(NOTIFICATION_ID, builder.build()); + } catch (SecurityException e) { + Log.e(TAG, "Notification permission missing", e); + } + } +} diff --git a/app/src/main/java/es/wolfi/app/passman/activities/AutofillInteractionActivity.java b/app/src/main/java/es/wolfi/app/passman/activities/AutofillInteractionActivity.java index e195ba34..1c1176ef 100644 --- a/app/src/main/java/es/wolfi/app/passman/activities/AutofillInteractionActivity.java +++ b/app/src/main/java/es/wolfi/app/passman/activities/AutofillInteractionActivity.java @@ -34,7 +34,6 @@ import android.widget.Toast; import androidx.annotation.RequiresApi; -import androidx.appcompat.app.AppCompatActivity; import java.util.ArrayList; import java.util.Set; @@ -51,7 +50,7 @@ import es.wolfi.passman.API.Vault; @RequiresApi(api = Build.VERSION_CODES.O) -public class AutofillInteractionActivity extends AppCompatActivity implements +public class AutofillInteractionActivity extends BaseActivity implements VaultLockScreenFragment.VaultUnlockInteractionListener, CredentialItemFragment.OnListFragmentInteractionListener { public final static String LOG_TAG = "AutofillInteractionAct."; diff --git a/app/src/main/java/es/wolfi/app/passman/activities/BaseActivity.java b/app/src/main/java/es/wolfi/app/passman/activities/BaseActivity.java new file mode 100644 index 00000000..f0f17bb3 --- /dev/null +++ b/app/src/main/java/es/wolfi/app/passman/activities/BaseActivity.java @@ -0,0 +1,78 @@ +package es.wolfi.app.passman.activities; + +import android.content.SharedPreferences; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.TextView; + +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; + +import es.wolfi.app.passman.PassmanApp; +import es.wolfi.app.passman.R; +import es.wolfi.app.passman.SettingValues; +import es.wolfi.app.passman.VaultLockManager; + +public abstract class BaseActivity extends AppCompatActivity { + private View countdownOverlay; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + applyScreenshotProtection(); + } + + @Override + public void onUserInteraction() { + super.onUserInteraction(); + hideCountdownOverlay(); + VaultLockManager.getInstance((PassmanApp) getApplication()).resetTimer(); + } + + @Override + protected void onResume() { + super.onResume(); + applyScreenshotProtection(); + VaultLockManager.getInstance((PassmanApp) getApplication()).checkLockOnResume(); + } + + public void applyScreenshotProtection() { + SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this); + boolean enabled = settings.getBoolean(SettingValues.ENABLE_SCREENSHOT_PROTECTION.toString(), true); + if (enabled) { + getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE); + } else { + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE); + } + } + + public void showCountdownOverlay(int secondsLeft) { + runOnUiThread(() -> { + if (countdownOverlay == null) { + countdownOverlay = LayoutInflater.from(this).inflate(R.layout.layout_lock_countdown, null); + addContentView( + countdownOverlay, + new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + ); + } + countdownOverlay.setVisibility(View.VISIBLE); + TextView textView = countdownOverlay.findViewById(R.id.lock_countdown_text); + textView.setText(getString(R.string.vault_locking_in, secondsLeft)); + }); + } + + public void hideCountdownOverlay() { + runOnUiThread(() -> { + if (countdownOverlay != null) { + countdownOverlay.setVisibility(View.GONE); + } + }); + } +} diff --git a/app/src/main/java/es/wolfi/app/passman/activities/LoginActivity.java b/app/src/main/java/es/wolfi/app/passman/activities/LoginActivity.java index 9c9a6580..2afee080 100644 --- a/app/src/main/java/es/wolfi/app/passman/activities/LoginActivity.java +++ b/app/src/main/java/es/wolfi/app/passman/activities/LoginActivity.java @@ -35,11 +35,9 @@ import android.widget.EditText; import android.widget.ImageView; import android.widget.LinearLayout; -import android.widget.ScrollView; import android.widget.Spinner; import android.widget.Toast; -import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; import com.koushikdutta.async.future.FutureCallback; @@ -61,12 +59,11 @@ import es.wolfi.app.passman.SettingValues; import es.wolfi.app.passman.SingleTon; import es.wolfi.app.passman.databinding.ActivityLoginBinding; -import es.wolfi.app.passman.databinding.ContentLegacyLoginBinding; import es.wolfi.passman.API.Core; import es.wolfi.utils.KeyStoreUtils; import es.wolfi.utils.SSOUtils; -public class LoginActivity extends AppCompatActivity { +public class LoginActivity extends BaseActivity { public final static String LOG_TAG = "LoginActivity"; Spinner input_protocol; diff --git a/app/src/main/java/es/wolfi/app/passman/activities/PasswordListActivity.java b/app/src/main/java/es/wolfi/app/passman/activities/PasswordListActivity.java index 8ce49b3d..40951fd3 100644 --- a/app/src/main/java/es/wolfi/app/passman/activities/PasswordListActivity.java +++ b/app/src/main/java/es/wolfi/app/passman/activities/PasswordListActivity.java @@ -68,10 +68,12 @@ import java.util.Objects; import es.wolfi.app.passman.OfflineStorage; +import es.wolfi.app.passman.PassmanApp; import es.wolfi.app.passman.R; import es.wolfi.app.passman.SettingValues; import es.wolfi.app.passman.SettingsCache; import es.wolfi.app.passman.SingleTon; +import es.wolfi.app.passman.VaultLockManager; import es.wolfi.app.passman.fragments.CredentialAddFragment; import es.wolfi.app.passman.fragments.CredentialDisplayFragment; import es.wolfi.app.passman.fragments.CredentialEditFragment; @@ -87,7 +89,7 @@ import es.wolfi.utils.KeyStoreUtils; import es.wolfi.utils.ProgressUtils; -public class PasswordListActivity extends AppCompatActivity implements +public class PasswordListActivity extends BaseActivity implements VaultFragment.OnListFragmentInteractionListener, CredentialItemFragment.OnListFragmentInteractionListener, VaultLockScreenFragment.VaultUnlockInteractionListener, @@ -145,6 +147,7 @@ public void onClick(View view) { KeyStoreUtils.initialize(settings); new OfflineStorage(getBaseContext()); initialAuthentication(false); + VaultLockManager.getInstance((PassmanApp) getApplication()).updateConfig(settings.getInt(SettingValues.VAULT_AUTO_LOCK_DELAY.toString(), 0)); } private void initialAuthentication(boolean skipKeyguard) { @@ -172,6 +175,14 @@ public void onCompleted(Exception e, Boolean loggedIn) { if (loggedIn) { showVaults(); + if ( + Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU + && settings.getInt(SettingValues.VAULT_AUTO_LOCK_DELAY.toString(), 0) > 0 + ) { + if (checkSelfPermission(android.Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { + requestPermissions(new String[]{android.Manifest.permission.POST_NOTIFICATIONS}, 101); + } + } } else { // If not logged in, show login form! Intent intent = new Intent(PasswordListActivity.this, LoginActivity.class); @@ -368,8 +379,9 @@ void showUnlockVault() { .commitAllowingStateLoss(); } - void lockVault() { + public void lockVault() { final Vault vault = (Vault) ton.getExtra(SettingValues.ACTIVE_VAULT.toString()); + if (vault == null) return; vault.lock(); ton.removeExtra(vault.guid); ton.addExtra(vault.guid, vault); @@ -384,6 +396,7 @@ void lockVault() { } onBackPressed(); + applyScreenshotProtection(); } public void addCredentialToCurrentLocalVaultList(Credential credential) { @@ -557,6 +570,7 @@ public void applyNewSettings(boolean doRebirth) { Toast.makeText(this, R.string.successfully_saved, Toast.LENGTH_SHORT).show(); updateShortcuts(); + applyScreenshotProtection(); if (doRebirth) { triggerRebirth(this); @@ -570,6 +584,14 @@ public void onCompleted(Exception e, Boolean loggedIn) { if (loggedIn) { showVaults(); + if ( + Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU + && settings.getInt(SettingValues.VAULT_AUTO_LOCK_DELAY.toString(), 0) > 0 + ) { + if (checkSelfPermission(android.Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { + requestPermissions(new String[]{android.Manifest.permission.POST_NOTIFICATIONS}, 101); + } + } } else { // If not logged in, show login form! Intent intent = new Intent(PasswordListActivity.this, LoginActivity.class); diff --git a/app/src/main/java/es/wolfi/app/passman/activities/ScanQRCodeActivity.java b/app/src/main/java/es/wolfi/app/passman/activities/ScanQRCodeActivity.java index 2b61f858..a599045a 100644 --- a/app/src/main/java/es/wolfi/app/passman/activities/ScanQRCodeActivity.java +++ b/app/src/main/java/es/wolfi/app/passman/activities/ScanQRCodeActivity.java @@ -15,7 +15,6 @@ import android.widget.Toast; import androidx.annotation.NonNull; -import androidx.appcompat.app.AppCompatActivity; import androidx.camera.core.AspectRatio; import androidx.camera.core.CameraSelector; import androidx.camera.core.ImageAnalysis; @@ -37,7 +36,7 @@ import es.wolfi.app.passman.R; import es.wolfi.utils.QrCodeAnalyzer; -public class ScanQRCodeActivity extends AppCompatActivity { +public class ScanQRCodeActivity extends BaseActivity { public final static String LOG_TAG = ScanQRCodeActivity.class.getSimpleName(); diff --git a/app/src/main/java/es/wolfi/app/passman/activities/ShortcutActivity.java b/app/src/main/java/es/wolfi/app/passman/activities/ShortcutActivity.java index e23d2fc1..0858278f 100644 --- a/app/src/main/java/es/wolfi/app/passman/activities/ShortcutActivity.java +++ b/app/src/main/java/es/wolfi/app/passman/activities/ShortcutActivity.java @@ -26,12 +26,10 @@ import android.os.Bundle; import android.widget.Toast; -import androidx.appcompat.app.AppCompatActivity; - import es.wolfi.app.passman.R; import es.wolfi.utils.PasswordGenerator; -public class ShortcutActivity extends AppCompatActivity { +public class ShortcutActivity extends BaseActivity { public final static String LOG_TAG = "ShortcutActivity"; public final static String GENERATE_PASSWORD_ID = "es.wolfi.app.passman.generate_password"; public final static String GENERATE_PASSWORD_INTENT_ACTION = "custom.actions.intent.GENERATE_PASSWORD"; diff --git a/app/src/main/java/es/wolfi/app/passman/fragments/SettingsFragment.java b/app/src/main/java/es/wolfi/app/passman/fragments/SettingsFragment.java index f97ddd7e..0b00ba58 100644 --- a/app/src/main/java/es/wolfi/app/passman/fragments/SettingsFragment.java +++ b/app/src/main/java/es/wolfi/app/passman/fragments/SettingsFragment.java @@ -65,10 +65,12 @@ import java.util.Set; import es.wolfi.app.passman.OfflineStorage; +import es.wolfi.app.passman.PassmanApp; import es.wolfi.app.passman.R; import es.wolfi.app.passman.SettingValues; import es.wolfi.app.passman.SettingsCache; import es.wolfi.app.passman.SingleTon; +import es.wolfi.app.passman.VaultLockManager; import es.wolfi.app.passman.activities.PasswordListActivity; import es.wolfi.passman.API.Vault; import es.wolfi.utils.KeyStoreUtils; @@ -87,6 +89,7 @@ public class SettingsFragment extends Fragment { EditText settings_nextcloud_password; MaterialCheckBox settings_app_start_password_switch; + MaterialCheckBox settings_enable_screenshot_protection_switch; MaterialCheckBox settings_password_generator_shortcut_switch; MaterialCheckBox settings_password_generator_use_uppercase_switch; @@ -111,6 +114,7 @@ public class SettingsFragment extends Fragment { EditText request_connect_timeout_value; EditText request_response_timeout_value; + EditText vault_auto_lock_delay_value; Button clear_offline_cache_button; SharedPreferences settings; @@ -152,6 +156,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, settings_nextcloud_password = view.findViewById(R.id.settings_nextcloud_password); settings_app_start_password_switch = view.findViewById(R.id.settings_app_start_password_switch); + settings_enable_screenshot_protection_switch = view.findViewById(R.id.settings_enable_screenshot_protection_switch); settings_password_generator_shortcut_switch = view.findViewById(R.id.settings_password_generator_shortcut_switch); settings_password_generator_use_uppercase_switch = view.findViewById(R.id.settings_password_generator_use_uppercase_switch); @@ -177,6 +182,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, request_connect_timeout_value = view.findViewById(R.id.request_connect_timeout_value); request_response_timeout_value = view.findViewById(R.id.request_response_timeout_value); + vault_auto_lock_delay_value = view.findViewById(R.id.vault_auto_lock_delay_value); clear_offline_cache_button = view.findViewById(R.id.clear_offline_cache_button); clear_offline_cache_button.setOnClickListener(this.getClearOfflineCacheButtonListener()); @@ -219,6 +225,7 @@ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { settings_nextcloud_password.setText(KeyStoreUtils.getString(SettingValues.PASSWORD.toString(), null)); settings_app_start_password_switch.setChecked(settings.getBoolean(SettingValues.ENABLE_APP_START_DEVICE_PASSWORD.toString(), false)); + settings_enable_screenshot_protection_switch.setChecked(settings.getBoolean(SettingValues.ENABLE_SCREENSHOT_PROTECTION.toString(), true)); passwordGenerator = new PasswordGenerator(context); @@ -291,6 +298,7 @@ public void onClick(View view) { request_connect_timeout_value.setText(String.valueOf(settings.getInt(SettingValues.REQUEST_CONNECT_TIMEOUT.toString(), 15))); request_response_timeout_value.setText(String.valueOf(settings.getInt(SettingValues.REQUEST_RESPONSE_TIMEOUT.toString(), 120))); + vault_auto_lock_delay_value.setText(String.valueOf(settings.getInt(SettingValues.VAULT_AUTO_LOCK_DELAY.toString(), 0))); clear_offline_cache_button.setText(String.format("%s (%s)", getString(R.string.clear_offline_cache), OfflineStorage.getInstance().getSize())); } @@ -345,6 +353,7 @@ public void onClick(View view) { SingleTon ton = SingleTon.getTon(); settings.edit().putBoolean(SettingValues.ENABLE_APP_START_DEVICE_PASSWORD.toString(), settings_app_start_password_switch.isChecked()).commit(); + settings.edit().putBoolean(SettingValues.ENABLE_SCREENSHOT_PROTECTION.toString(), settings_enable_screenshot_protection_switch.isChecked()).commit(); settings.edit().putBoolean(SettingValues.ENABLE_PASSWORD_GENERATOR_SHORTCUT.toString(), settings_password_generator_shortcut_switch.isChecked()).commit(); @@ -370,6 +379,10 @@ public void onClick(View view) { settings.edit().putInt(SettingValues.REQUEST_CONNECT_TIMEOUT.toString(), Integer.parseInt(request_connect_timeout_value.getText().toString())).commit(); settings.edit().putInt(SettingValues.REQUEST_RESPONSE_TIMEOUT.toString(), Integer.parseInt(request_response_timeout_value.getText().toString())).commit(); + int autoLockDelay = Integer.parseInt(vault_auto_lock_delay_value.getText().toString()); + settings.edit().putInt(SettingValues.VAULT_AUTO_LOCK_DELAY.toString(), autoLockDelay).commit(); + VaultLockManager.getInstance((PassmanApp) getActivity().getApplication()).updateConfig(autoLockDelay); + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { settings.edit().putBoolean(SettingValues.ENABLE_AUTOFILL_MANUAL_SEARCH_FALLBACK.toString(), enable_autofill_manual_search_fallback.isChecked()).commit(); if (default_autofill_vault.getSelectedItem() == null || default_autofill_vault.getSelectedItem().toString().equals(getContext().getString(R.string.automatically))) { diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml index 7ef08b72..bc284640 100644 --- a/app/src/main/res/layout/fragment_settings.xml +++ b/app/src/main/res/layout/fragment_settings.xml @@ -68,7 +68,7 @@ style="@style/Label" android:layout_width="match_parent" android:layout_height="wrap_content" - android:text="@string/app_start_password" /> + android:text="@string/app_security" /> + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4c57c683..dd7974fb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -45,6 +45,7 @@ Add custom field Unlock Passman Please authenticate to access Passman + App security App start password Enable Android user authentication on app start Wait while encrypting … @@ -67,6 +68,12 @@ Enable credential list icons Credential icon Clear clipboard (after seconds) + Vault auto-lock (after minutes) + Hide app content in recents, screenshots and on non-secure displays + Vault will be locked in %1$d seconds + Vault Locked + The active vault has been automatically locked due to inactivity. + Vault Security Generate password Generate a random password and copy to clipboard Enable password generator app shortcut