do not reset activity state in OnNewIntent if the activity hasn't even been initialized. closes #2869, closes #2888

This commit is contained in:
Philipp Crocoll
2025-06-17 14:04:15 +02:00
parent e1f26fb045
commit e2babde1fa

View File

@@ -131,7 +131,8 @@ namespace keepass2android
ISharedPreferences _prefs; ISharedPreferences _prefs;
private bool _starting; private bool _starting;
private OtpInfo _otpInfo; private bool _resumeCompleted;
private OtpInfo _otpInfo;
private IOConnectionInfo _otpAuxIoc; private IOConnectionInfo _otpAuxIoc;
private ChallengeInfo _chalInfo; private ChallengeInfo _chalInfo;
private byte[] _challengeSecret; private byte[] _challengeSecret;
@@ -1569,7 +1570,9 @@ namespace keepass2android
} }
private bool hasRequestedKeyboardActivation = false; private bool hasRequestedKeyboardActivation = false;
protected override void OnStart()
protected override void OnStart()
{ {
base.OnStart(); base.OnStart();
_starting = true; _starting = true;
@@ -1644,60 +1647,65 @@ namespace keepass2android
if (intent != null) if (intent != null)
{ {
if (intent.HasExtra(Intents.OtpExtraKey)) if (intent.HasExtra(Intents.OtpExtraKey))
{ {
string otp = intent.GetStringExtra(Intents.OtpExtraKey); string otp = intent.GetStringExtra(Intents.OtpExtraKey);
_keepPasswordInOnResume = true; _keepPasswordInOnResume = true;
if (KeyProviderTypes.Contains(KeyProviders.Otp)) if (KeyProviderTypes.Contains(KeyProviders.Otp))
{
if (_otpInfo == null)
{
//Entering OTPs not yet initialized:
_pendingOtps.Add(otp);
UpdateKeyProviderUiState();
}
else
{
//Entering OTPs is initialized. Write OTP into first empty field:
bool foundEmptyField = false;
foreach (int otpId in _otpTextViewIds)
{
EditText otpEdit = FindViewById<EditText>(otpId);
if ((otpEdit.Visibility == ViewStates.Visible) && String.IsNullOrEmpty(otpEdit.Text))
{
otpEdit.Text = otp;
foundEmptyField = true;
break;
}
}
//did we find a field?
if (!foundEmptyField)
{
App.Kp2a.ShowMessage(this, GetString(Resource.String.otp_discarded_no_space), MessageSeverity.Error);
}
}
Spinner passwordModeSpinner = FindViewById<Spinner>(Resource.Id.password_mode_spinner);
if (passwordModeSpinner.SelectedItemPosition != (int)KeyProviders.Otp)
{
passwordModeSpinner.SetSelection((int)KeyProviders.Otp);
}
}
else
{
//assume the key should be used as static password
FindViewById<EditText>(Resource.Id.password_edit).Text += otp;
}
}
else
{
// if the activity is launched twice and the first initialization hasn't even finished, we cannot
// reset the state and re-initialize the activity.
// This can happen with autofill in some cases (#2869)
if (_resumeCompleted)
{ {
ResetState();
if (_otpInfo == null) GetIocFromLaunchIntent(intent);
{ InitializeAfterSetIoc();
//Entering OTPs not yet initialized: OnStart();
_pendingOtps.Add(otp);
UpdateKeyProviderUiState();
}
else
{
//Entering OTPs is initialized. Write OTP into first empty field:
bool foundEmptyField = false;
foreach (int otpId in _otpTextViewIds)
{
EditText otpEdit = FindViewById<EditText>(otpId);
if ((otpEdit.Visibility == ViewStates.Visible) && String.IsNullOrEmpty(otpEdit.Text))
{
otpEdit.Text = otp;
foundEmptyField = true;
break;
}
}
//did we find a field?
if (!foundEmptyField)
{
App.Kp2a.ShowMessage(this, GetString(Resource.String.otp_discarded_no_space), MessageSeverity.Error);
}
}
Spinner passwordModeSpinner = FindViewById<Spinner>(Resource.Id.password_mode_spinner);
if (passwordModeSpinner.SelectedItemPosition != (int)KeyProviders.Otp)
{
passwordModeSpinner.SetSelection((int)KeyProviders.Otp);
}
} }
else
{ }
//assume the key should be used as static password
FindViewById<EditText>(Resource.Id.password_edit).Text += otp;
}
}
else
{
ResetState();
GetIocFromLaunchIntent(intent);
InitializeAfterSetIoc();
OnStart();
}
} }
} }
@@ -1736,148 +1744,150 @@ namespace keepass2android
protected override View? SnackbarAnchorView => FindViewById(Resource.Id.main_content); protected override View? SnackbarAnchorView => FindViewById(Resource.Id.main_content);
protected override void OnResume() protected override void OnResume()
{ {
base.OnResume(); base.OnResume();
_activityDesign.ReapplyTheme();
Kp2aLog.Log("starting: " + _starting + ", Finishing: " + IsFinishing + ", _performingLoad: " + _activityDesign.ReapplyTheme();
_performingLoad);
CheckBox cbOfflineMode = (CheckBox)FindViewById(Resource.Id.work_offline); Kp2aLog.Log("starting: " + _starting + ", Finishing: " + IsFinishing + ", _performingLoad: " +
App.Kp2a.OfflineMode = _performingLoad);
cbOfflineMode.Checked =
App.Kp2a
.OfflineModePreference; //this won't overwrite new user settings because every change is directly saved in settings
LinearLayout offlineModeContainer = FindViewById<LinearLayout>(Resource.Id.work_offline_container);
var cachingFileStorage = App.Kp2a.GetFileStorage(_ioConnection) as CachingFileStorage;
if ((cachingFileStorage != null) && cachingFileStorage.IsCached(_ioConnection))
{
offlineModeContainer.Visibility = ViewStates.Visible;
}
else
{
offlineModeContainer.Visibility = ViewStates.Gone;
App.Kp2a.OfflineMode = false;
}
CheckBox cbOfflineMode = (CheckBox)FindViewById(Resource.Id.work_offline);
App.Kp2a.OfflineMode =
cbOfflineMode.Checked =
View killButton = FindViewById(Resource.Id.kill_app); App.Kp2a
if (PreferenceManager.GetDefaultSharedPreferences(this) .OfflineModePreference; //this won't overwrite new user settings because every change is directly saved in settings
.GetBoolean(GetString(Resource.String.show_kill_app_key), false)) LinearLayout offlineModeContainer = FindViewById<LinearLayout>(Resource.Id.work_offline_container);
{ var cachingFileStorage = App.Kp2a.GetFileStorage(_ioConnection) as CachingFileStorage;
killButton.Click += (sender, args) => if ((cachingFileStorage != null) && cachingFileStorage.IsCached(_ioConnection))
{
_killOnDestroy = true;
SetResult(Result.Canceled);
Finish();
};
killButton.Visibility = ViewStates.Visible;
}
else
{
killButton.Visibility = ViewStates.Gone;
}
TryGetOtpFromClipboard();
if (!_keepPasswordInOnResume)
{
if (
_lastOnPauseTime <
DateTime.Now -
TimeSpan.FromSeconds(
5) //only clear when user left the app for more than 5 seconds (allows to use Yubiclip, also allows to switch shortly to another app)
&&
PreferenceManager.GetDefaultSharedPreferences(this)
.GetBoolean(GetString(Resource.String.ClearPasswordOnLeave_key), true))
{
ClearEnteredPassword();
}
}
_keepPasswordInOnResume = false;
MakePasswordMaskedOrVisible();
UpdateOkButtonState();
if (KeyProviderTypes.Contains(KeyProviders.Challenge))
{
FindViewById(Resource.Id.otpInitView).Visibility =
_challengeSecret == null ? ViewStates.Visible : ViewStates.Gone;
}
//use !IsFinishing to make sure we're not starting another activity when we're already finishing (e.g. due to TaskComplete in OnActivityResult)
//use !performingLoad to make sure we're not already loading the database (after ActivityResult from File-Prepare-Activity; this would cause _loadDbFileTask to exist when we reload later!)
if ( !IsFinishing && !_performingLoad)
{ {
offlineModeContainer.Visibility = ViewStates.Visible;
}
// OnResume is run every time the activity comes to the foreground. This code should only run when the activity is started (OnStart), but must else
// be run in OnResume rather than OnStart so that it always occurrs after OnActivityResult (when re-creating a killed activity, OnStart occurs before OnActivityResult) {
if (_starting) offlineModeContainer.Visibility = ViewStates.Gone;
{ App.Kp2a.OfflineMode = false;
}
_starting = false;
//database not yet loaded.
//check if pre-loading is enabled but wasn't started yet:
if (_loadDbFileTask == null && View killButton = FindViewById(Resource.Id.kill_app);
_prefs.GetBoolean(GetString(Resource.String.PreloadDatabaseEnabled_key), true)) if (PreferenceManager.GetDefaultSharedPreferences(this)
{ .GetBoolean(GetString(Resource.String.show_kill_app_key), false))
// Create task to kick off file loading while the user enters the password {
_loadDbFileTask = Task.Factory.StartNew(PreloadDbFile); killButton.Click += (sender, args) =>
_loadDbTaskOffline = App.Kp2a.OfflineMode; {
} _killOnDestroy = true;
} SetResult(Result.Canceled);
Finish();
};
killButton.Visibility = ViewStates.Visible;
}
else
{
killButton.Visibility = ViewStates.Gone;
}
TryGetOtpFromClipboard();
if (!_keepPasswordInOnResume)
{
if (
_lastOnPauseTime <
DateTime.Now -
TimeSpan.FromSeconds(
5) //only clear when user left the app for more than 5 seconds (allows to use Yubiclip, also allows to switch shortly to another app)
&&
PreferenceManager.GetDefaultSharedPreferences(this)
.GetBoolean(GetString(Resource.String.ClearPasswordOnLeave_key), true))
{
ClearEnteredPassword();
}
} }
if (compositeKeyForImmediateLoad != null)
{
//reload the database (without most other stuff performed in PerformLoadDatabase.
// We're assuming that the db file (and if appropriate also the key file) are still available
// and there's no need to re-init the file storage. if it is, loading will fail and the user has
// to retry with typing the full password, but that's intended to avoid showing the password to a
// a potentially unauthorized user (feature request https://keepass2android.codeplex.com/workitem/274)
Handler handler = new Handler();
OnFinish onFinish = new AfterLoad(handler, this, _ioConnection);
_performingLoad = true;
LoadDb task = new LoadDb(this, App.Kp2a, _ioConnection, _loadDbFileTask, compositeKeyForImmediateLoad, GetKeyProviderString(),
onFinish, false, _makeCurrent);
_loadDbFileTask = null; // prevent accidental re-use
new ProgressTask(App.Kp2a, this, task).Run();
compositeKeyForImmediateLoad = null; //don't reuse or keep in memory
}
else
{
bool showKeyboard = true;
_keepPasswordInOnResume = false;
EditText pwd = (EditText) FindViewById(Resource.Id.password_edit);
pwd.PostDelayed(() => MakePasswordMaskedOrVisible();
{
InputMethodManager keyboard = (InputMethodManager) GetSystemService(InputMethodService); UpdateOkButtonState();
if (showKeyboard)
{ if (KeyProviderTypes.Contains(KeyProviders.Challenge))
pwd.RequestFocus(); {
keyboard.ShowSoftInput(pwd, 0); FindViewById(Resource.Id.otpInitView).Visibility =
} _challengeSecret == null ? ViewStates.Visible : ViewStates.Gone;
else }
keyboard.HideSoftInputFromWindow(pwd.WindowToken, HideSoftInputFlags.ImplicitOnly);
}, 50); //use !IsFinishing to make sure we're not starting another activity when we're already finishing (e.g. due to TaskComplete in OnActivityResult)
} //use !performingLoad to make sure we're not already loading the database (after ActivityResult from File-Prepare-Activity; this would cause _loadDbFileTask to exist when we reload later!)
if (!IsFinishing && !_performingLoad)
{
// OnResume is run every time the activity comes to the foreground. This code should only run when the activity is started (OnStart), but must
// be run in OnResume rather than OnStart so that it always occurrs after OnActivityResult (when re-creating a killed activity, OnStart occurs before OnActivityResult)
if (_starting)
{
_starting = false;
//database not yet loaded.
//check if pre-loading is enabled but wasn't started yet:
if (_loadDbFileTask == null &&
_prefs.GetBoolean(GetString(Resource.String.PreloadDatabaseEnabled_key), true))
{
// Create task to kick off file loading while the user enters the password
_loadDbFileTask = Task.Factory.StartNew(PreloadDbFile);
_loadDbTaskOffline = App.Kp2a.OfflineMode;
}
}
}
if (compositeKeyForImmediateLoad != null)
{
//reload the database (without most other stuff performed in PerformLoadDatabase.
// We're assuming that the db file (and if appropriate also the key file) are still available
// and there's no need to re-init the file storage. if it is, loading will fail and the user has
// to retry with typing the full password, but that's intended to avoid showing the password to a
// a potentially unauthorized user (feature request https://keepass2android.codeplex.com/workitem/274)
Handler handler = new Handler();
OnFinish onFinish = new AfterLoad(handler, this, _ioConnection);
_performingLoad = true;
LoadDb task = new LoadDb(this, App.Kp2a, _ioConnection, _loadDbFileTask, compositeKeyForImmediateLoad, GetKeyProviderString(),
onFinish, false, _makeCurrent);
_loadDbFileTask = null; // prevent accidental re-use
new ProgressTask(App.Kp2a, this, task).Run();
compositeKeyForImmediateLoad = null; //don't reuse or keep in memory
}
else
{
bool showKeyboard = true;
EditText pwd = (EditText)FindViewById(Resource.Id.password_edit);
pwd.PostDelayed(() =>
{
InputMethodManager keyboard = (InputMethodManager)GetSystemService(InputMethodService);
if (showKeyboard)
{
pwd.RequestFocus();
keyboard.ShowSoftInput(pwd, 0);
}
else
keyboard.HideSoftInputFromWindow(pwd.WindowToken, HideSoftInputFlags.ImplicitOnly);
}, 50);
}
_resumeCompleted = true;
} }
private void TryGetOtpFromClipboard() private void TryGetOtpFromClipboard()