diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.java b/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.java deleted file mode 100644 index 5ee760dcb..000000000 --- a/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.java +++ /dev/null @@ -1,567 +0,0 @@ -/* - * Copyright 2017 Brian Pellin, Jeremy Jamet / Kunzisoft. - * - * This file is part of KeePass DX. - * - * KeePass DX is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * KeePass DX is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with KeePass DX. If not, see . - * - */ -package com.kunzisoft.keepass.activities; - -import android.app.Activity; -import android.content.Intent; -import android.content.res.TypedArray; -import android.graphics.Color; -import android.os.Bundle; -import android.os.Handler; -import android.support.v7.widget.Toolbar; -import android.util.Log; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; -import android.view.View; -import android.view.ViewGroup; -import android.widget.EditText; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.ScrollView; -import android.widget.Toast; - -import com.kunzisoft.keepass.R; -import com.kunzisoft.keepass.activities.lock.LockingHideActivity; -import com.kunzisoft.keepass.app.App; -import com.kunzisoft.keepass.database.action.node.ActionNodeValues; -import com.kunzisoft.keepass.database.action.node.AddEntryRunnable; -import com.kunzisoft.keepass.database.action.node.AfterActionNodeFinishRunnable; -import com.kunzisoft.keepass.database.action.node.UpdateEntryRunnable; -import com.kunzisoft.keepass.database.element.Database; -import com.kunzisoft.keepass.database.element.EntryVersioned; -import com.kunzisoft.keepass.database.element.GroupVersioned; -import com.kunzisoft.keepass.database.element.PwDate; -import com.kunzisoft.keepass.database.element.PwIcon; -import com.kunzisoft.keepass.database.element.PwIconStandard; -import com.kunzisoft.keepass.database.element.PwNodeId; -import com.kunzisoft.keepass.database.element.security.ProtectedString; -import com.kunzisoft.keepass.dialogs.GeneratePasswordDialogFragment; -import com.kunzisoft.keepass.dialogs.IconPickerDialogFragment; -import com.kunzisoft.keepass.education.EntryEditActivityEducation; -import com.kunzisoft.keepass.settings.PreferencesUtil; -import com.kunzisoft.keepass.tasks.ActionRunnable; -import com.kunzisoft.keepass.timeout.TimeoutHelper; -import com.kunzisoft.keepass.utils.MenuUtil; -import com.kunzisoft.keepass.utils.Util; -import com.kunzisoft.keepass.view.EntryEditCustomField; - -import org.jetbrains.annotations.NotNull; - -import static com.kunzisoft.keepass.dialogs.IconPickerDialogFragment.KEY_ICON_STANDARD; - -public class EntryEditActivity extends LockingHideActivity - implements IconPickerDialogFragment.IconPickerListener, - GeneratePasswordDialogFragment.GeneratePasswordListener { - - private static final String TAG = EntryEditActivity.class.getName(); - - // Keys for current Activity - public static final String KEY_ENTRY = "entry"; - public static final String KEY_PARENT = "parent"; - - // Keys for callback - public static final int ADD_ENTRY_RESULT_CODE = 31; - public static final int UPDATE_ENTRY_RESULT_CODE = 32; - public static final int ADD_OR_UPDATE_ENTRY_REQUEST_CODE = 7129; - public static final String ADD_OR_UPDATE_ENTRY_KEY = "ADD_OR_UPDATE_ENTRY_KEY"; - - private Database database; - - protected EntryVersioned mEntry; - protected GroupVersioned mParent; - protected EntryVersioned mNewEntry; - protected boolean mIsNew; - protected PwIconStandard mSelectedIconStandard; - - // Views - private ScrollView scrollView; - private EditText entryTitleView; - private ImageView entryIconView; - private EditText entryUserNameView; - private EditText entryUrlView; - private EditText entryPasswordView; - private EditText entryConfirmationPasswordView; - private View generatePasswordView; - private EditText entryCommentView; - private ViewGroup entryExtraFieldsContainer; - private View addNewFieldView; - private View saveView; - private int iconColor; - - // Education - private EntryEditActivityEducation entryEditActivityEducation; - - /** - * Launch EntryEditActivity to update an existing entry - * - * @param activity from activity - * @param pwEntry Entry to update - */ - public static void launch(Activity activity, EntryVersioned pwEntry) { - if (TimeoutHelper.INSTANCE.checkTimeAndLockIfTimeout(activity)) { - Intent intent = new Intent(activity, EntryEditActivity.class); - intent.putExtra(KEY_ENTRY, pwEntry.getNodeId()); - activity.startActivityForResult(intent, ADD_OR_UPDATE_ENTRY_REQUEST_CODE); - } - } - - /** - * Launch EntryEditActivity to add a new entry - * - * @param activity from activity - * @param pwGroup Group who will contains new entry - */ - public static void launch(Activity activity, GroupVersioned pwGroup) { - if (TimeoutHelper.INSTANCE.checkTimeAndLockIfTimeout(activity)) { - Intent intent = new Intent(activity, EntryEditActivity.class); - intent.putExtra(KEY_PARENT, pwGroup.getNodeId()); - activity.startActivityForResult(intent, ADD_OR_UPDATE_ENTRY_REQUEST_CODE); - } - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.entry_edit); - - Toolbar toolbar = findViewById(R.id.toolbar); - setSupportActionBar(toolbar); - assert getSupportActionBar() != null; - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - getSupportActionBar().setDisplayShowHomeEnabled(true); - - scrollView = findViewById(R.id.entry_edit_scroll); - scrollView.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET); - - entryTitleView = findViewById(R.id.entry_edit_title); - entryIconView = findViewById(R.id.entry_edit_icon_button); - entryUserNameView = findViewById(R.id.entry_edit_user_name); - entryUrlView = findViewById(R.id.entry_edit_url); - entryPasswordView = findViewById(R.id.entry_edit_password); - entryConfirmationPasswordView = findViewById(R.id.entry_edit_confirmation_password); - entryCommentView = findViewById(R.id.entry_edit_notes); - entryExtraFieldsContainer = findViewById(R.id.entry_edit_advanced_container); - - // Focus view to reinitialize timeout - resetAppTimeoutWhenViewFocusedOrChanged( - entryTitleView, - entryIconView, - entryUserNameView, - entryUrlView, - entryPasswordView, - entryConfirmationPasswordView, - entryCommentView, - entryExtraFieldsContainer); - - // Likely the app has been killed exit the activity - database = App.Companion.getCurrentDatabase(); - - // Retrieve the textColor to tint the icon - int[] attrs = {android.R.attr.textColorPrimary}; - TypedArray ta = getTheme().obtainStyledAttributes(attrs); - iconColor = ta.getColor(0, Color.WHITE); - - mSelectedIconStandard = database.getIconFactory().getUnknownIcon(); - - Intent intent = getIntent(); - // Entry is retrieve, it's an entry to update - PwNodeId keyEntry = intent.getParcelableExtra(KEY_ENTRY); - if (keyEntry != null) { - mIsNew = false; - mEntry = database.getEntryById(keyEntry); - if (mEntry != null) { - mParent = mEntry.getParent(); - fillData(); - } - } - - // Parent is retrieve, it's a new entry to create - PwNodeId keyParent = intent.getParcelableExtra(KEY_PARENT); - if (keyParent != null) { - mIsNew = true; - mEntry = database.createEntry(); - mParent = database.getGroupById(keyParent); - // Add the default icon - database.getDrawFactory().assignDefaultDatabaseIconTo(this, entryIconView, iconColor); - } - - // Close the activity if entry or parent can't be retrieve - if (mEntry == null || mParent == null) { - finish(); - return; - } - - // Assign title - setTitle((mIsNew) ? getString(R.string.add_entry) : getString(R.string.edit_entry)); - - // Retrieve the icon after an orientation change - if (savedInstanceState != null - && savedInstanceState.containsKey(KEY_ICON_STANDARD)) { - iconPicked(savedInstanceState); - } - - // Add listener to the icon - entryIconView.setOnClickListener(v -> - IconPickerDialogFragment.launch(EntryEditActivity.this)); - - // Generate password button - generatePasswordView = findViewById(R.id.entry_edit_generate_button); - generatePasswordView.setOnClickListener(v -> openPasswordGenerator()); - - // Save button - saveView = findViewById(R.id.entry_edit_save); - saveView.setOnClickListener(v -> saveEntry()); - - if (mEntry.allowExtraFields()) { - addNewFieldView = findViewById(R.id.entry_edit_add_new_field); - addNewFieldView.setVisibility(View.VISIBLE); - addNewFieldView.setOnClickListener(v -> addNewCustomField()); - } - - // Verify the education views - entryEditActivityEducation = new EntryEditActivityEducation(this); - new Handler().post(() -> performedNextEducation(entryEditActivityEducation)); - } - - private void performedNextEducation(EntryEditActivityEducation entryEditActivityEducation) { - if (entryEditActivityEducation.checkAndPerformedGeneratePasswordEducation( - generatePasswordView, - tapTargetView -> { - openPasswordGenerator(); - return null; - }, - tapTargetView -> { - performedNextEducation(entryEditActivityEducation); - return null; - } - )); - else if (mEntry.allowExtraFields() - && !mEntry.containsCustomFields() - && entryEditActivityEducation.checkAndPerformedEntryNewFieldEducation( - addNewFieldView, - tapTargetView -> { - addNewCustomField(); - return null; - }, - tapTargetView -> null) - ); - } - - /** - * Open the password generator fragment - */ - private void openPasswordGenerator() { - GeneratePasswordDialogFragment generatePasswordDialogFragment = new GeneratePasswordDialogFragment(); - generatePasswordDialogFragment.show(getSupportFragmentManager(), "PasswordGeneratorFragment"); - } - - /** - * Add a new view to fill in the information of the customized field - */ - private void addNewCustomField() { - EntryEditCustomField entryEditCustomField = new EntryEditCustomField(EntryEditActivity.this); - entryEditCustomField.setData("", new ProtectedString(false, "")); - boolean visibilityFontActivated = PreferencesUtil.fieldFontIsInVisibility(this); - entryEditCustomField.setFontVisibility(visibilityFontActivated); - entryExtraFieldsContainer.addView(entryEditCustomField); - - // Scroll bottom - scrollView.post(() -> scrollView.fullScroll(ScrollView.FOCUS_DOWN)); - } - - /** - * Saves the new entry or update an existing entry in the database - */ - private void saveEntry() { - if (!validateBeforeSaving()) { - return; - } - // Clone the entry - mNewEntry = new EntryVersioned(mEntry); - - populateEntryWithViewInfo(mNewEntry); - - // Open a progress dialog and save entry - ActionRunnable task; - AfterActionNodeFinishRunnable afterActionNodeFinishRunnable = - new AfterActionNodeFinishRunnable() { - @Override - public void onActionNodeFinish(@NotNull ActionNodeValues actionNodeValues) { - if (actionNodeValues.getSuccess()) - finish(); - } - }; - if ( mIsNew ) { - task = new AddEntryRunnable(EntryEditActivity.this, - database, - mNewEntry, - mParent, - afterActionNodeFinishRunnable, - !getReadOnly()); - } else { - task = new UpdateEntryRunnable(EntryEditActivity.this, - database, - mEntry, - mNewEntry, - afterActionNodeFinishRunnable, - !getReadOnly()); - } - new Thread(task).start(); - } - - - - /** - * Utility class to retrieve a validation or an error with a message - */ - private class ErrorValidation { - static final int unknownMessage = -1; - - boolean isValidate = false; - int messageId = unknownMessage; - - void showValidationErrorIfNeeded() { - if (!isValidate && messageId != unknownMessage) - Toast.makeText(EntryEditActivity.this, messageId, Toast.LENGTH_LONG).show(); - } - } - - /** - * Validate or not the entry form - * - * @return ErrorValidation An error with a message or a validation without message - */ - protected ErrorValidation validate() { - ErrorValidation errorValidation = new ErrorValidation(); - - // Require title - String title = entryTitleView.getText().toString(); - if ( title.length() == 0 ) { - errorValidation.messageId = R.string.error_title_required; - return errorValidation; - } - - // Validate password - String pass = entryPasswordView.getText().toString(); - String conf = entryConfirmationPasswordView.getText().toString(); - if ( ! pass.equals(conf) ) { - errorValidation.messageId = R.string.error_pass_match; - return errorValidation; - } - - // Validate extra fields - if (mEntry.allowExtraFields()) { - for (int i = 0; i < entryExtraFieldsContainer.getChildCount(); i++) { - EntryEditCustomField entryEditCustomField = (EntryEditCustomField) entryExtraFieldsContainer.getChildAt(i); - String key = entryEditCustomField.getLabel(); - if (key == null || key.length() == 0) { - errorValidation.messageId = R.string.error_string_key; - return errorValidation; - } - } - } - - errorValidation.isValidate = true; - return errorValidation; - } - - /** - * Launch a validation with {@link #validate()} and show the error if present - * - * @return true if the form was validate or false if not - */ - protected boolean validateBeforeSaving() { - ErrorValidation errorValidation = validate(); - errorValidation.showValidationErrorIfNeeded(); - return errorValidation.isValidate; - } - - private void populateEntryWithViewInfo(EntryVersioned newEntry) { - - database.startManageEntry(newEntry); - - newEntry.setLastAccessTime(new PwDate()); - newEntry.setLastModificationTime(new PwDate()); - - newEntry.setTitle(entryTitleView.getText().toString()); - newEntry.setIcon(retrieveIcon()); - - newEntry.setUrl(entryUrlView.getText().toString()); - newEntry.setUsername(entryUserNameView.getText().toString()); - newEntry.setNotes(entryCommentView.getText().toString()); - newEntry.setPassword(entryPasswordView.getText().toString()); - - if (newEntry.allowExtraFields()) { - // Delete all extra strings - newEntry.removeAllCustomFields(); - // Add extra fields from views - for (int i = 0; i < entryExtraFieldsContainer.getChildCount(); i++) { - EntryEditCustomField view = (EntryEditCustomField) entryExtraFieldsContainer.getChildAt(i); - String key = view.getLabel(); - String value = view.getValue(); - boolean protect = view.isProtected(); - newEntry.addExtraField(key, new ProtectedString(protect, value)); - } - } - - database.stopManageEntry(newEntry); - } - - /** - * Retrieve the icon by the selection, or the first icon in the list if the entry is new or the last one - */ - private PwIcon retrieveIcon() { - - if (!mSelectedIconStandard.isUnknown()) - return mSelectedIconStandard; - else { - if (mIsNew) { - return database.getIconFactory().getKeyIcon(); - } - else { - // Keep previous icon, if no new one was selected - return mEntry.getIcon(); - } - } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - super.onCreateOptionsMenu(menu); - - MenuInflater inflater = getMenuInflater(); - inflater.inflate(R.menu.database_lock, menu); - MenuUtil.INSTANCE.contributionMenuInflater(inflater, menu); - - return true; - } - - public boolean onOptionsItemSelected(MenuItem item) { - switch ( item.getItemId() ) { - case R.id.menu_lock: - lockAndExit(); - return true; - - case R.id.menu_contribute: - return MenuUtil.INSTANCE.onContributionItemSelected(this); - - case android.R.id.home: - finish(); - } - - return super.onOptionsItemSelected(item); - } - - private void assignIconView() { - database.getDrawFactory() - .assignDatabaseIconTo( - this, - entryIconView, - mEntry.getIcon(), - iconColor); - } - - protected void fillData() { - - assignIconView(); - - // Don't start the field reference manager, we want to see the raw ref - App.Companion.getCurrentDatabase().stopManageEntry(mEntry); - - entryTitleView.setText(mEntry.getTitle()); - entryUserNameView.setText(mEntry.getUsername()); - entryUrlView.setText(mEntry.getUrl()); - String password = mEntry.getPassword(); - entryPasswordView.setText(password); - entryConfirmationPasswordView.setText(password); - entryCommentView.setText(mEntry.getNotes()); - - boolean visibilityFontActivated = PreferencesUtil.fieldFontIsInVisibility(this); - if (visibilityFontActivated) { - Util.applyFontVisibilityTo(this, entryUserNameView); - Util.applyFontVisibilityTo(this, entryPasswordView); - Util.applyFontVisibilityTo(this, entryConfirmationPasswordView); - Util.applyFontVisibilityTo(this, entryCommentView); - } - - if (mEntry.allowExtraFields()) { - LinearLayout container = findViewById(R.id.entry_edit_advanced_container); - mEntry.getFields().doActionToAllCustomProtectedField((key, value) -> { - EntryEditCustomField entryEditCustomField = new EntryEditCustomField(EntryEditActivity.this); - entryEditCustomField.setData(key, value); - entryEditCustomField.setFontVisibility(visibilityFontActivated); - container.addView(entryEditCustomField); - return null; - }); - } - } - - @Override - public void iconPicked(Bundle bundle) { - mSelectedIconStandard = bundle.getParcelable(KEY_ICON_STANDARD); - mEntry.setIcon(mSelectedIconStandard); - assignIconView(); - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - if (!mSelectedIconStandard.isUnknown()) { - outState.putParcelable(KEY_ICON_STANDARD, mSelectedIconStandard); - super.onSaveInstanceState(outState); - } - } - - @Override - public void acceptPassword(Bundle bundle) { - String generatedPassword = bundle.getString(GeneratePasswordDialogFragment.KEY_PASSWORD_ID); - entryPasswordView.setText(generatedPassword); - entryConfirmationPasswordView.setText(generatedPassword); - - new Handler().post(() -> performedNextEducation(entryEditActivityEducation)); - } - - @Override - public void cancelPassword(Bundle bundle) { - // Do nothing here - } - - @Override - public void finish() { - // Assign entry callback as a result in all case - try { - if (mNewEntry != null) { - Bundle bundle = new Bundle(); - Intent intentEntry = new Intent(); - bundle.putParcelable(ADD_OR_UPDATE_ENTRY_KEY, mNewEntry); - intentEntry.putExtras(bundle); - if (mIsNew) { - setResult(ADD_ENTRY_RESULT_CODE, intentEntry); - } else { - setResult(UPDATE_ENTRY_RESULT_CODE, intentEntry); - } - } - super.finish(); - } catch (Exception e) { - // Exception when parcelable can't be done - Log.e(TAG, "Cant add entry as result", e); - } - } -} diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt new file mode 100644 index 000000000..4797af08b --- /dev/null +++ b/app/src/main/java/com/kunzisoft/keepass/activities/EntryEditActivity.kt @@ -0,0 +1,542 @@ +/* + * Copyright 2019 Jeremy Jamet / Kunzisoft. + * + * This file is part of KeePass DX. + * + * KeePass DX is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * KeePass DX is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with KeePass DX. If not, see . + */ +package com.kunzisoft.keepass.activities + +import android.app.Activity +import android.content.Intent +import android.graphics.Color +import android.os.Bundle +import android.os.Handler +import android.support.v7.widget.Toolbar +import android.util.Log +import android.view.Menu +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup +import android.widget.EditText +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.ScrollView +import android.widget.Toast + +import com.kunzisoft.keepass.R +import com.kunzisoft.keepass.activities.lock.LockingHideActivity +import com.kunzisoft.keepass.app.App +import com.kunzisoft.keepass.database.action.node.ActionNodeValues +import com.kunzisoft.keepass.database.action.node.AddEntryRunnable +import com.kunzisoft.keepass.database.action.node.AfterActionNodeFinishRunnable +import com.kunzisoft.keepass.database.action.node.UpdateEntryRunnable +import com.kunzisoft.keepass.database.element.Database +import com.kunzisoft.keepass.database.element.EntryVersioned +import com.kunzisoft.keepass.database.element.GroupVersioned +import com.kunzisoft.keepass.database.element.PwDate +import com.kunzisoft.keepass.database.element.PwIcon +import com.kunzisoft.keepass.database.element.PwIconStandard +import com.kunzisoft.keepass.database.element.PwNodeId +import com.kunzisoft.keepass.database.element.security.ProtectedString +import com.kunzisoft.keepass.dialogs.GeneratePasswordDialogFragment +import com.kunzisoft.keepass.dialogs.IconPickerDialogFragment +import com.kunzisoft.keepass.education.EntryEditActivityEducation +import com.kunzisoft.keepass.settings.PreferencesUtil +import com.kunzisoft.keepass.tasks.ActionRunnable +import com.kunzisoft.keepass.timeout.TimeoutHelper +import com.kunzisoft.keepass.utils.MenuUtil +import com.kunzisoft.keepass.utils.Util +import com.kunzisoft.keepass.view.EntryEditCustomField + +import com.kunzisoft.keepass.dialogs.IconPickerDialogFragment.KEY_ICON_STANDARD + +class EntryEditActivity : LockingHideActivity(), IconPickerDialogFragment.IconPickerListener, GeneratePasswordDialogFragment.GeneratePasswordListener { + + private var mDatabase: Database? = null + + private var mEntry: EntryVersioned? = null + private var mParent: GroupVersioned? = null + private var mNewEntry: EntryVersioned? = null + private var mIsNew: Boolean = false + private var mSelectedIconStandard: PwIconStandard? = null + + // Views + private var scrollView: ScrollView? = null + private var entryTitleView: EditText? = null + private var entryIconView: ImageView? = null + private var entryUserNameView: EditText? = null + private var entryUrlView: EditText? = null + private var entryPasswordView: EditText? = null + private var entryConfirmationPasswordView: EditText? = null + private var generatePasswordView: View? = null + private var entryCommentView: EditText? = null + private var entryExtraFieldsContainer: ViewGroup? = null + private var addNewFieldView: View? = null + private var saveView: View? = null + private var iconColor: Int = 0 + + // View validation message + private var validationErrorMessageId = UNKNOWN_MESSAGE + + // Education + private var entryEditActivityEducation: EntryEditActivityEducation? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.entry_edit) + + val toolbar = findViewById(R.id.toolbar) + setSupportActionBar(toolbar) + supportActionBar?.setDisplayHomeAsUpEnabled(true) + supportActionBar?.setDisplayShowHomeEnabled(true) + + scrollView = findViewById(R.id.entry_edit_scroll) + scrollView?.scrollBarStyle = View.SCROLLBARS_INSIDE_INSET + + entryTitleView = findViewById(R.id.entry_edit_title) + entryIconView = findViewById(R.id.entry_edit_icon_button) + entryUserNameView = findViewById(R.id.entry_edit_user_name) + entryUrlView = findViewById(R.id.entry_edit_url) + entryPasswordView = findViewById(R.id.entry_edit_password) + entryConfirmationPasswordView = findViewById(R.id.entry_edit_confirmation_password) + entryCommentView = findViewById(R.id.entry_edit_notes) + entryExtraFieldsContainer = findViewById(R.id.entry_edit_advanced_container) + + // Focus view to reinitialize timeout + resetAppTimeoutWhenViewFocusedOrChanged( + entryTitleView, + entryIconView, + entryUserNameView, + entryUrlView, + entryPasswordView, + entryConfirmationPasswordView, + entryCommentView, + entryExtraFieldsContainer) + + // Likely the app has been killed exit the activity + mDatabase = App.currentDatabase + + // Retrieve the textColor to tint the icon + iconColor = theme + .obtainStyledAttributes(intArrayOf(android.R.attr.textColorPrimary)) + .getColor(0, Color.WHITE) + + mSelectedIconStandard = mDatabase?.iconFactory?.unknownIcon + + // Entry is retrieve, it's an entry to update + intent.getParcelableExtra>(KEY_ENTRY)?.let { + mIsNew = false + mEntry = mDatabase?.getEntryById(it) + mEntry?.let { entry -> + mParent = entry.parent + fillEntryDataInContentsView(entry) + } + } + + // Parent is retrieve, it's a new entry to create + intent.getParcelableExtra>(KEY_PARENT)?.let { + mIsNew = true + mEntry = mDatabase?.createEntry() + mParent = mDatabase?.getGroupById(it) + // Add the default icon + mDatabase?.drawFactory?.assignDefaultDatabaseIconTo(this, entryIconView, iconColor) + } + + // Close the activity if entry or parent can't be retrieve + if (mEntry == null || mParent == null) { + finish() + return + } + + // Assign title + title = if (mIsNew) getString(R.string.add_entry) else getString(R.string.edit_entry) + + // Retrieve the icon after an orientation change + savedInstanceState?.let { + if (it.containsKey(KEY_ICON_STANDARD)) { + iconPicked(it) + } + } + + // Add listener to the icon + entryIconView?.setOnClickListener { IconPickerDialogFragment.launch(this@EntryEditActivity) } + + // Generate password button + generatePasswordView = findViewById(R.id.entry_edit_generate_button) + generatePasswordView?.setOnClickListener { openPasswordGenerator() } + + // Save button + saveView = findViewById(R.id.entry_edit_save) + mEntry?.let { entry -> + saveView?.setOnClickListener { saveEntry(entry) } + } + + if (mEntry?.allowExtraFields() == true) { + addNewFieldView = findViewById(R.id.entry_edit_add_new_field) + addNewFieldView?.apply { + visibility = View.VISIBLE + setOnClickListener { addNewCustomField() } + } + } + + // Verify the education views + entryEditActivityEducation = EntryEditActivityEducation(this) + entryEditActivityEducation?.let { + Handler().post { performedNextEducation(it) } + } + } + + private fun performedNextEducation(entryEditActivityEducation: EntryEditActivityEducation) { + if (generatePasswordView != null + && entryEditActivityEducation.checkAndPerformedGeneratePasswordEducation( + generatePasswordView!!, + { + openPasswordGenerator() + }, + { + performedNextEducation(entryEditActivityEducation) + } + )) + else if (mEntry != null + && mEntry!!.allowExtraFields() + && !mEntry!!.containsCustomFields() + && addNewFieldView != null + && entryEditActivityEducation.checkAndPerformedEntryNewFieldEducation( + addNewFieldView!!, + { + addNewCustomField() + })) + ; + } + + /** + * Open the password generator fragment + */ + private fun openPasswordGenerator() { + GeneratePasswordDialogFragment().show(supportFragmentManager, "PasswordGeneratorFragment") + } + + /** + * Add a new view to fill in the information of the customized field + */ + private fun addNewCustomField() { + val entryEditCustomField = EntryEditCustomField(this@EntryEditActivity) + entryEditCustomField.setData("", ProtectedString(false, "")) + val visibilityFontActivated = PreferencesUtil.fieldFontIsInVisibility(this) + entryEditCustomField.setFontVisibility(visibilityFontActivated) + entryExtraFieldsContainer?.addView(entryEditCustomField) + + // Scroll bottom + scrollView?.post { scrollView?.fullScroll(ScrollView.FOCUS_DOWN) } + } + + /** + * Saves the new entry or update an existing entry in the database + */ + private fun saveEntry(entry: EntryVersioned) { + + // Launch a validation and show the error if present + if (!isValid()) { + if (validationErrorMessageId != UNKNOWN_MESSAGE) + Toast.makeText(this@EntryEditActivity, validationErrorMessageId, Toast.LENGTH_LONG).show() + return + } + // Clone the entry + mDatabase?.let { database -> + mNewEntry = EntryVersioned(entry) + mNewEntry?.let { newEntry -> + populateEntryWithViewInfo(newEntry) + + // Open a progress dialog and save entry + var task: ActionRunnable? = null + val afterActionNodeFinishRunnable = object : AfterActionNodeFinishRunnable() { + override fun onActionNodeFinish(actionNodeValues: ActionNodeValues) { + if (actionNodeValues.success) + finish() + } + } + if (mIsNew) { + mParent?.let { parent -> + task = AddEntryRunnable(this@EntryEditActivity, + database, + newEntry, + parent, + afterActionNodeFinishRunnable, + !readOnly) + } + + } else { + task = UpdateEntryRunnable(this@EntryEditActivity, + database, + entry, + newEntry, + afterActionNodeFinishRunnable, + !readOnly) + } + Thread(task).start() + } + } + } + + /** + * Validate or not the entry form + * + * @return ErrorValidation An error with a message or a validation without message + */ + private fun isValid(): Boolean { + + // Require title + if (entryTitleView?.text.toString().isEmpty()) { + validationErrorMessageId = R.string.error_title_required + return false + } + + // Validate password + if (entryPasswordView?.text.toString() != entryConfirmationPasswordView?.text.toString()) { + validationErrorMessageId = R.string.error_pass_match + return false + } + + // Validate extra fields + if (mEntry?.allowExtraFields() == true) { + entryExtraFieldsContainer?.let { + for (i in 0 until it.childCount) { + val entryEditCustomField = it.getChildAt(i) as EntryEditCustomField + val key = entryEditCustomField.label + if (key == null || key.isEmpty()) { + validationErrorMessageId = R.string.error_string_key + return false + } + } + } + } + return true + } + + private fun populateEntryWithViewInfo(newEntry: EntryVersioned) { + + mDatabase?.startManageEntry(newEntry) + + newEntry.lastAccessTime = PwDate() + newEntry.lastModificationTime = PwDate() + + newEntry.title = entryTitleView?.text.toString() + newEntry.icon = retrieveIcon() + + newEntry.url = entryUrlView?.text.toString() + newEntry.username = entryUserNameView?.text.toString() + newEntry.notes = entryCommentView?.text.toString() + newEntry.password = entryPasswordView?.text.toString() + + if (newEntry.allowExtraFields()) { + // Delete all extra strings + newEntry.removeAllCustomFields() + // Add extra fields from views + entryExtraFieldsContainer?.let { + for (i in 0 until it.childCount) { + val view = it.getChildAt(i) as EntryEditCustomField + val key = view.label + val value = view.value + val protect = view.isProtected + newEntry.addExtraField(key, ProtectedString(protect, value)) + } + } + } + + mDatabase?.stopManageEntry(newEntry) + } + + /** + * Retrieve the icon by the selection, or the first icon in the list if the entry is new or the last one + */ + private fun retrieveIcon(): PwIcon { + + return if (mSelectedIconStandard?.isUnknown != true) + mSelectedIconStandard + else { + if (mIsNew) { + mDatabase?.iconFactory?.keyIcon + } else { + // Keep previous icon, if no new one was selected + mEntry?.icon + } + } ?: PwIconStandard() + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + super.onCreateOptionsMenu(menu) + + val inflater = menuInflater + inflater.inflate(R.menu.database_lock, menu) + MenuUtil.contributionMenuInflater(inflater, menu) + + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.menu_lock -> { + lockAndExit() + return true + } + + R.id.menu_contribute -> return MenuUtil.onContributionItemSelected(this) + + android.R.id.home -> finish() + } + + return super.onOptionsItemSelected(item) + } + + private fun assignIconView() { + mEntry?.icon?.let { + mDatabase?.drawFactory?.assignDatabaseIconTo( + this, + entryIconView, + it, + iconColor) + } + } + + private fun fillEntryDataInContentsView(entry: EntryVersioned) { + + assignIconView() + + // Don't start the field reference manager, we want to see the raw ref + mDatabase?.stopManageEntry(entry) + + entryTitleView?.setText(entry.title) + entryUserNameView?.setText(entry.username) + entryUrlView?.setText(entry.url) + val password = entry.password + entryPasswordView?.setText(password) + entryConfirmationPasswordView?.setText(password) + entryCommentView?.setText(entry.notes) + + val visibilityFontActivated = PreferencesUtil.fieldFontIsInVisibility(this) + if (visibilityFontActivated) { + Util.applyFontVisibilityTo(this, entryUserNameView) + Util.applyFontVisibilityTo(this, entryPasswordView) + Util.applyFontVisibilityTo(this, entryConfirmationPasswordView) + Util.applyFontVisibilityTo(this, entryCommentView) + } + + if (entry.allowExtraFields()) { + val container = findViewById(R.id.entry_edit_advanced_container) + entry.fields.doActionToAllCustomProtectedField { key, value -> + val entryEditCustomField = EntryEditCustomField(this@EntryEditActivity) + entryEditCustomField.setData(key, value) + entryEditCustomField.setFontVisibility(visibilityFontActivated) + container.addView(entryEditCustomField) + } + } + } + + override fun iconPicked(bundle: Bundle) { + mSelectedIconStandard = bundle.getParcelable(KEY_ICON_STANDARD) + mSelectedIconStandard?.let { + mEntry?.icon = it + } + assignIconView() + } + + override fun onSaveInstanceState(outState: Bundle) { + if (!mSelectedIconStandard!!.isUnknown) { + outState.putParcelable(KEY_ICON_STANDARD, mSelectedIconStandard) + super.onSaveInstanceState(outState) + } + } + + override fun acceptPassword(bundle: Bundle) { + bundle.getString(GeneratePasswordDialogFragment.KEY_PASSWORD_ID)?.let { + entryPasswordView?.setText(it) + entryConfirmationPasswordView?.setText(it) + } + + entryEditActivityEducation?.let { + Handler().post { performedNextEducation(it) } + } + } + + override fun cancelPassword(bundle: Bundle) { + // Do nothing here + } + + override fun finish() { + // Assign entry callback as a result in all case + try { + mNewEntry?.let { + val bundle = Bundle() + val intentEntry = Intent() + bundle.putParcelable(ADD_OR_UPDATE_ENTRY_KEY, mNewEntry) + intentEntry.putExtras(bundle) + if (mIsNew) { + setResult(ADD_ENTRY_RESULT_CODE, intentEntry) + } else { + setResult(UPDATE_ENTRY_RESULT_CODE, intentEntry) + } + } + super.finish() + } catch (e: Exception) { + // Exception when parcelable can't be done + Log.e(TAG, "Cant add entry as result", e) + } + } + + companion object { + + private val TAG = EntryEditActivity::class.java.name + + // Keys for current Activity + const val KEY_ENTRY = "entry" + const val KEY_PARENT = "parent" + + // Keys for callback + const val ADD_ENTRY_RESULT_CODE = 31 + const val UPDATE_ENTRY_RESULT_CODE = 32 + const val ADD_OR_UPDATE_ENTRY_REQUEST_CODE = 7129 + const val ADD_OR_UPDATE_ENTRY_KEY = "ADD_OR_UPDATE_ENTRY_KEY" + + const val UNKNOWN_MESSAGE = -1 + + /** + * Launch EntryEditActivity to update an existing entry + * + * @param activity from activity + * @param pwEntry Entry to update + */ + fun launch(activity: Activity, pwEntry: EntryVersioned) { + if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) { + val intent = Intent(activity, EntryEditActivity::class.java) + intent.putExtra(KEY_ENTRY, pwEntry.nodeId) + activity.startActivityForResult(intent, ADD_OR_UPDATE_ENTRY_REQUEST_CODE) + } + } + + /** + * Launch EntryEditActivity to add a new entry + * + * @param activity from activity + * @param pwGroup Group who will contains new entry + */ + fun launch(activity: Activity, pwGroup: GroupVersioned) { + if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) { + val intent = Intent(activity, EntryEditActivity::class.java) + intent.putExtra(KEY_PARENT, pwGroup.nodeId) + activity.startActivityForResult(intent, ADD_OR_UPDATE_ENTRY_REQUEST_CODE) + } + } + } +} diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.java b/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.java index 4a0266b63..931019011 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.java +++ b/app/src/main/java/com/kunzisoft/keepass/activities/GroupActivity.java @@ -298,7 +298,7 @@ public class GroupActivity extends LockingActivity .show(getSupportFragmentManager(), GroupEditDialogFragment.TAG_CREATE_GROUP)); addNodeButtonView.setAddEntryClickListener(v -> - EntryEditActivity.launch(GroupActivity.this, mCurrentGroup)); + EntryEditActivity.Companion.launch(GroupActivity.this, mCurrentGroup)); // Search suggestion searchSuggestionAdapter = new SearchEntryCursorAdapter(this, database); @@ -570,7 +570,7 @@ public class GroupActivity extends LockingActivity GroupEditDialogFragment.TAG_CREATE_GROUP); break; case ENTRY: - EntryEditActivity.launch(GroupActivity.this, (EntryVersioned) node); + EntryEditActivity.Companion.launch(GroupActivity.this, (EntryVersioned) node); break; } return true; diff --git a/app/src/main/java/com/kunzisoft/keepass/activities/lock/LockingActivity.kt b/app/src/main/java/com/kunzisoft/keepass/activities/lock/LockingActivity.kt index 6849211e3..dfd61bc85 100644 --- a/app/src/main/java/com/kunzisoft/keepass/activities/lock/LockingActivity.kt +++ b/app/src/main/java/com/kunzisoft/keepass/activities/lock/LockingActivity.kt @@ -163,9 +163,9 @@ abstract class LockingActivity : StylishActivity() { /** * To reset the app timeout when a view is focused or changed */ - protected fun resetAppTimeoutWhenViewFocusedOrChanged(vararg views: View) { + protected fun resetAppTimeoutWhenViewFocusedOrChanged(vararg views: View?) { views.forEach { - it.setOnFocusChangeListener { _, hasFocus -> + it?.setOnFocusChangeListener { _, hasFocus -> if (hasFocus) { TimeoutHelper.checkTimeAndLockIfTimeoutOrResetTimeout(this) }