Kotlinized EntryActivity

This commit is contained in:
J-Jamet
2019-07-07 16:37:57 +02:00
parent e8b341c69f
commit 7dfe85450d
6 changed files with 730 additions and 738 deletions

View File

@@ -1,484 +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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.activities;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.widget.ImageView;
import android.widget.TextView;
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.element.Database;
import com.kunzisoft.keepass.database.element.EntryVersioned;
import com.kunzisoft.keepass.database.element.PwNodeId;
import com.kunzisoft.keepass.database.element.security.ProtectedString;
import com.kunzisoft.keepass.education.EntryActivityEducation;
import com.kunzisoft.keepass.notifications.NotificationCopyingService;
import com.kunzisoft.keepass.notifications.NotificationField;
import com.kunzisoft.keepass.settings.PreferencesUtil;
import com.kunzisoft.keepass.settings.SettingsAutofillActivity;
import com.kunzisoft.keepass.timeout.ClipboardHelper;
import com.kunzisoft.keepass.timeout.TimeoutHelper;
import com.kunzisoft.keepass.utils.EmptyUtils;
import com.kunzisoft.keepass.utils.MenuUtil;
import com.kunzisoft.keepass.utils.Util;
import com.kunzisoft.keepass.view.EntryContentsView;
import java.util.ArrayList;
import java.util.Date;
import kotlin.Unit;
import kotlin.jvm.functions.Function2;
import static com.kunzisoft.keepass.settings.PreferencesUtil.isClipboardNotificationsEnable;
import static com.kunzisoft.keepass.settings.PreferencesUtil.isFirstTimeAskAllowCopyPasswordAndProtectedFields;
public class EntryActivity extends LockingHideActivity {
private final static String TAG = EntryActivity.class.getName();
public static final String KEY_ENTRY = "entry";
private ImageView titleIconView;
private TextView titleView;
private EntryContentsView entryContentsView;
private Toolbar toolbar;
protected EntryVersioned mEntry;
private boolean mShowPassword;
private ClipboardHelper clipboardHelper;
private boolean firstLaunchOfActivity;
private int iconColor;
public static void launch(Activity activity, EntryVersioned pw, boolean readOnly) {
if (TimeoutHelper.INSTANCE.checkTimeAndLockIfTimeout(activity)) {
Intent intent = new Intent(activity, EntryActivity.class);
intent.putExtra(KEY_ENTRY, pw.getNodeId());
ReadOnlyHelper.INSTANCE.putReadOnlyInIntent(intent, readOnly);
activity.startActivityForResult(intent, EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.entry_view);
toolbar = findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
assert getSupportActionBar() != null;
getSupportActionBar().setHomeAsUpIndicator(R.drawable.ic_close_white_24dp);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
Database db = App.Companion.getCurrentDatabase();
setReadOnly(db.isReadOnly() || getReadOnly());
mShowPassword = !PreferencesUtil.isPasswordMask(this);
// Get Entry from UUID
Intent i = getIntent();
PwNodeId keyEntry;
try {
keyEntry = i.getParcelableExtra(KEY_ENTRY);
mEntry = db.getEntryById(keyEntry);
} catch (ClassCastException e) {
Log.e(TAG, "Unable to retrieve the entry key");
}
if (mEntry == null) {
Toast.makeText(this, R.string.entry_not_found, Toast.LENGTH_LONG).show();
finish();
return;
}
// Update last access time.
mEntry.touch(false, false);
// Retrieve the textColor to tint the icon
int[] attrs = {R.attr.textColorInverse};
TypedArray ta = getTheme().obtainStyledAttributes(attrs);
iconColor = ta.getColor(0, Color.WHITE);
// Refresh Menu contents in case onCreateMenuOptions was called before mEntry was set
invalidateOptionsMenu();
// Get views
titleIconView = findViewById(R.id.entry_icon);
titleView = findViewById(R.id.entry_title);
entryContentsView = findViewById(R.id.entry_contents);
entryContentsView.applyFontVisibilityToFields(PreferencesUtil.fieldFontIsInVisibility(this));
// Init the clipboard helper
clipboardHelper = new ClipboardHelper(this);
firstLaunchOfActivity = true;
}
@Override
protected void onResume() {
super.onResume();
// Fill data in resume to update from EntryEditActivity
fillData();
invalidateOptionsMenu();
Database database = App.Companion.getCurrentDatabase();
// Start to manage field reference to copy a value from ref
database.startManageEntry(mEntry);
boolean containsUsernameToCopy =
mEntry.getUsername().length() > 0;
boolean containsPasswordToCopy =
(mEntry.getPassword().length() > 0
&& PreferencesUtil.allowCopyPasswordAndProtectedFields(this));
boolean containsExtraFieldToCopy =
(mEntry.allowExtraFields()
&& ((mEntry.containsCustomFields()
&& mEntry.containsCustomFieldsNotProtected())
|| (mEntry.containsCustomFields()
&& mEntry.containsCustomFieldsProtected()
&& PreferencesUtil.allowCopyPasswordAndProtectedFields(this))
)
);
// If notifications enabled in settings
// Don't if application timeout
if (firstLaunchOfActivity && isClipboardNotificationsEnable(getApplicationContext())) {
if (containsUsernameToCopy
|| containsPasswordToCopy
|| containsExtraFieldToCopy
) {
// username already copied, waiting for user's action before copy password.
Intent intent = new Intent(this, NotificationCopyingService.class);
intent.setAction(NotificationCopyingService.ACTION_NEW_NOTIFICATION);
if (mEntry.getTitle() != null)
intent.putExtra(NotificationCopyingService.EXTRA_ENTRY_TITLE, mEntry.getTitle());
// Construct notification fields
ArrayList<NotificationField> notificationFields = new ArrayList<>();
// Add username if exists to notifications
if (containsUsernameToCopy)
notificationFields.add(
new NotificationField(
NotificationField.NotificationFieldId.USERNAME,
mEntry.getUsername(),
getResources()));
// Add password to notifications
if (containsPasswordToCopy) {
notificationFields.add(
new NotificationField(
NotificationField.NotificationFieldId.PASSWORD,
mEntry.getPassword(),
getResources()));
}
// Add extra fields
if (containsExtraFieldToCopy) {
try {
mEntry.getFields().doActionToAllCustomProtectedField(new Function2<String, ProtectedString, Unit>() {
private int anonymousFieldNumber = 0;
@Override
public Unit invoke(String key, ProtectedString value) {
//If value is not protected or allowed
if (!value.isProtected() || PreferencesUtil.allowCopyPasswordAndProtectedFields(EntryActivity.this)) {
notificationFields.add(
new NotificationField(
NotificationField.NotificationFieldId.getAnonymousFieldId()[anonymousFieldNumber],
value.toString(),
key,
getResources()));
anonymousFieldNumber++;
}
return null;
}
});
} catch (ArrayIndexOutOfBoundsException e) {
Log.w(TAG, "Only " + NotificationField.NotificationFieldId.getAnonymousFieldId().length +
" anonymous notifications are available");
}
}
// Add notifications
intent.putParcelableArrayListExtra(NotificationCopyingService.EXTRA_FIELDS, notificationFields);
startService(intent);
}
database.stopManageEntry(mEntry);
}
firstLaunchOfActivity = false;
}
protected void fillData() {
Database database = App.Companion.getCurrentDatabase();
database.startManageEntry(mEntry);
// Assign title icon
database.getDrawFactory().assignDatabaseIconTo(this, titleIconView, mEntry.getIcon(), iconColor);
// Assign title text
titleView.setText(mEntry.getVisualTitle());
// Assign basic fields
entryContentsView.assignUserName(mEntry.getUsername());
entryContentsView.assignUserNameCopyListener(view ->
clipboardHelper.timeoutCopyToClipboard(mEntry.getUsername(),
getString(R.string.copy_field, getString(R.string.entry_user_name)))
);
boolean allowCopyPassword = PreferencesUtil.allowCopyPasswordAndProtectedFields(this);
entryContentsView.assignPassword(mEntry.getPassword(), allowCopyPassword);
if (allowCopyPassword) {
entryContentsView.assignPasswordCopyListener(view ->
clipboardHelper.timeoutCopyToClipboard(mEntry.getPassword(),
getString(R.string.copy_field, getString(R.string.entry_password)))
);
} else {
// If dialog not already shown
if (isFirstTimeAskAllowCopyPasswordAndProtectedFields(this)) {
entryContentsView.assignPasswordCopyListener(v -> {
String message = getString(R.string.allow_copy_password_warning) +
"\n\n" +
getString(R.string.clipboard_warning);
AlertDialog warningDialog = new AlertDialog.Builder(EntryActivity.this)
.setMessage(message).create();
warningDialog.setButton(AlertDialog.BUTTON1, getText(android.R.string.ok),
(dialog, which) -> {
PreferencesUtil.setAllowCopyPasswordAndProtectedFields(EntryActivity.this, true);
dialog.dismiss();
fillData();
});
warningDialog.setButton(AlertDialog.BUTTON2, getText(android.R.string.cancel),
(dialog, which) -> {
PreferencesUtil.setAllowCopyPasswordAndProtectedFields(EntryActivity.this, false);
dialog.dismiss();
fillData();
});
warningDialog.show();
});
} else {
entryContentsView.assignPasswordCopyListener(null);
}
}
entryContentsView.assignURL(mEntry.getUrl());
entryContentsView.setHiddenPasswordStyle(!mShowPassword);
entryContentsView.assignComment(mEntry.getNotes());
// Assign custom fields
if (mEntry.allowExtraFields()) {
entryContentsView.clearExtraFields();
mEntry.getFields().doActionToAllCustomProtectedField((label, value) -> {
boolean showAction = (!value.isProtected() || PreferencesUtil.allowCopyPasswordAndProtectedFields(EntryActivity.this));
entryContentsView.addExtraField(label, value, showAction, view ->
clipboardHelper.timeoutCopyToClipboard(
value.toString(),
getString(R.string.copy_field, label)
)
);
return null;
});
}
// Assign dates
entryContentsView.assignCreationDate(mEntry.getCreationTime().getDate());
entryContentsView.assignModificationDate(mEntry.getLastModificationTime().getDate());
entryContentsView.assignLastAccessDate(mEntry.getLastAccessTime().getDate());
Date expires = mEntry.getExpiryTime().getDate();
if ( mEntry.isExpires() ) {
entryContentsView.assignExpiresDate(expires);
} else {
entryContentsView.assignExpiresDate(getString(R.string.never));
}
database.stopManageEntry(mEntry);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE:
// Not directly get the entry from intent data but from database
fillData();
break;
}
}
private void changeShowPasswordIcon(MenuItem togglePassword) {
if ( mShowPassword ) {
togglePassword.setTitle(R.string.menu_hide_password);
togglePassword.setIcon(R.drawable.ic_visibility_off_white_24dp);
} else {
togglePassword.setTitle(R.string.menu_showpass);
togglePassword.setIcon(R.drawable.ic_visibility_white_24dp);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
MenuInflater inflater = getMenuInflater();
MenuUtil.INSTANCE.contributionMenuInflater(inflater, menu);
inflater.inflate(R.menu.entry, menu);
inflater.inflate(R.menu.database_lock, menu);
if (getReadOnly()) {
MenuItem edit = menu.findItem(R.id.menu_edit);
if (edit != null)
edit.setVisible(false);
}
MenuItem togglePassword = menu.findItem(R.id.menu_toggle_pass);
if (entryContentsView != null && togglePassword != null) {
if (entryContentsView.isPasswordPresent() || entryContentsView.atLeastOneFieldProtectedPresent()) {
changeShowPasswordIcon(togglePassword);
} else {
togglePassword.setVisible(false);
}
}
MenuItem gotoUrl = menu.findItem(R.id.menu_goto_url);
if (gotoUrl != null) {
// In API >= 11 onCreateOptionsMenu may be called before onCreate completes
// so mEntry may not be set
if (mEntry == null) {
gotoUrl.setVisible(false);
} else {
String url = mEntry.getUrl();
if (EmptyUtils.INSTANCE.isNullOrEmpty(url)) {
// disable button if url is not available
gotoUrl.setVisible(false);
}
}
}
// Show education views
new Handler().post(() -> performedNextEducation(new EntryActivityEducation(this), menu));
return true;
}
private void performedNextEducation(EntryActivityEducation entryActivityEducation,
Menu menu) {
if (entryContentsView != null
&& entryContentsView.isUserNamePresent()
&& entryActivityEducation.checkAndPerformedEntryCopyEducation(
findViewById(R.id.entry_user_name_action_image),
tapTargetView -> {
clipboardHelper.timeoutCopyToClipboard(mEntry.getUsername(),
getString(R.string.copy_field,
getString(R.string.entry_user_name)));
return null;
},
tapTargetView -> {
// Launch autofill settings
startActivity(new Intent(EntryActivity.this, SettingsAutofillActivity.class));
return null;
})
);
else if (toolbar.findViewById(R.id.menu_edit) != null
&& entryActivityEducation.checkAndPerformedEntryEditEducation(
toolbar.findViewById(R.id.menu_edit),
tapTargetView -> {
onOptionsItemSelected(menu.findItem(R.id.menu_edit));
return null;
},
tapTargetView -> {
// Open Keepass doc to create field references
startActivity(new Intent(Intent.ACTION_VIEW,
Uri.parse(getString(R.string.field_references_url))));
return null;
})
);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch ( item.getItemId() ) {
case R.id.menu_contribute:
return MenuUtil.INSTANCE.onContributionItemSelected(this);
case R.id.menu_toggle_pass:
mShowPassword = !mShowPassword;
changeShowPasswordIcon(item);
entryContentsView.setHiddenPasswordStyle(!mShowPassword);
return true;
case R.id.menu_edit:
EntryEditActivity.launch(EntryActivity.this, mEntry);
return true;
case R.id.menu_goto_url:
String url;
url = mEntry.getUrl();
// Default http:// if no protocol specified
if ( ! url.contains("://") ) {
url = "http://" + url;
}
try {
Util.gotoUrl(this, url);
} catch (ActivityNotFoundException e) {
Toast.makeText(this, R.string.no_url_handler, Toast.LENGTH_LONG).show();
}
return true;
case R.id.menu_lock:
lockAndExit();
return true;
case android.R.id.home :
finish(); // close this activity and return to preview activity (if there is any)
}
return super.onOptionsItemSelected(item);
}
@Override
public void finish() {
// Transit data in previous Activity after an update
/*
TODO Slowdown when add entry as result
Intent intent = new Intent();
intent.putExtra(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY, mEntry);
setResult(EntryEditActivity.UPDATE_ENTRY_RESULT_CODE, intent);
*/
super.finish();
}
}

View File

@@ -0,0 +1,389 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.kunzisoft.keepass.activities
import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.Intent
import android.graphics.Color
import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.support.v7.app.AlertDialog
import android.support.v7.widget.Toolbar
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.ImageView
import android.widget.TextView
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.element.EntryVersioned
import com.kunzisoft.keepass.database.element.PwNodeId
import com.kunzisoft.keepass.education.EntryActivityEducation
import com.kunzisoft.keepass.notifications.NotificationEntryCopyManager
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.settings.PreferencesUtil.isFirstTimeAskAllowCopyPasswordAndProtectedFields
import com.kunzisoft.keepass.settings.SettingsAutofillActivity
import com.kunzisoft.keepass.timeout.ClipboardHelper
import com.kunzisoft.keepass.timeout.TimeoutHelper
import com.kunzisoft.keepass.utils.MenuUtil
import com.kunzisoft.keepass.utils.Util
import com.kunzisoft.keepass.view.EntryContentsView
class EntryActivity : LockingHideActivity() {
private var titleIconView: ImageView? = null
private var titleView: TextView? = null
private var entryContentsView: EntryContentsView? = null
private var toolbar: Toolbar? = null
private var mEntry: EntryVersioned? = null
private var mShowPassword: Boolean = false
private var clipboardHelper: ClipboardHelper? = null
private var firstLaunchOfActivity: Boolean = false
private var iconColor: Int = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.entry_view)
toolbar = findViewById(R.id.toolbar)
setSupportActionBar(toolbar)
supportActionBar?.setHomeAsUpIndicator(R.drawable.ic_close_white_24dp)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayShowHomeEnabled(true)
val currentDatabase = App.currentDatabase
readOnly = currentDatabase.isReadOnly || readOnly
mShowPassword = !PreferencesUtil.isPasswordMask(this)
// Get Entry from UUID
try {
val keyEntry: PwNodeId<*> = intent.getParcelableExtra(KEY_ENTRY)
mEntry = currentDatabase.getEntryById(keyEntry)
} catch (e: ClassCastException) {
Log.e(TAG, "Unable to retrieve the entry key")
}
if (mEntry == null) {
Toast.makeText(this, R.string.entry_not_found, Toast.LENGTH_LONG).show()
finish()
return
}
// Update last access time.
mEntry?.touch(modified = false, touchParents = false)
// Retrieve the textColor to tint the icon
iconColor = theme.
obtainStyledAttributes(intArrayOf(R.attr.textColorInverse))
.getColor(0, Color.WHITE)
// Refresh Menu contents in case onCreateMenuOptions was called before mEntry was set
invalidateOptionsMenu()
// Get views
titleIconView = findViewById(R.id.entry_icon)
titleView = findViewById(R.id.entry_title)
entryContentsView = findViewById(R.id.entry_contents)
entryContentsView?.applyFontVisibilityToFields(PreferencesUtil.fieldFontIsInVisibility(this))
// Init the clipboard helper
clipboardHelper = ClipboardHelper(this)
firstLaunchOfActivity = true
}
override fun onResume() {
super.onResume()
mEntry?.let { entry ->
// Fill data in resume to update from EntryEditActivity
fillEntryDataInContentsView(entry)
// Refresh Menu
invalidateOptionsMenu()
// Manage entry copy to start notification if allowed
NotificationEntryCopyManager.launchNotificationIfAllowed(this,
firstLaunchOfActivity,
entry)
}
firstLaunchOfActivity = false
}
private fun fillEntryDataInContentsView(entry: EntryVersioned) {
val database = App.currentDatabase
database.startManageEntry(entry)
// Assign title icon
database.drawFactory.assignDatabaseIconTo(this, titleIconView, entry.icon, iconColor)
// Assign title text
titleView?.text = entry.getVisualTitle()
// Assign basic fields
entryContentsView?.assignUserName(entry.username)
entryContentsView?.assignUserNameCopyListener(View.OnClickListener {
clipboardHelper?.timeoutCopyToClipboard(entry.username,
getString(R.string.copy_field,
getString(R.string.entry_user_name)))
})
val allowCopyPassword = PreferencesUtil.allowCopyPasswordAndProtectedFields(this)
entryContentsView?.assignPassword(entry.password, allowCopyPassword)
if (allowCopyPassword) {
entryContentsView?.assignPasswordCopyListener(View.OnClickListener {
clipboardHelper?.timeoutCopyToClipboard(entry.password,
getString(R.string.copy_field,
getString(R.string.entry_password)))
})
} else {
// If dialog not already shown
if (isFirstTimeAskAllowCopyPasswordAndProtectedFields(this)) {
entryContentsView?.assignPasswordCopyListener(View.OnClickListener {
val message = getString(R.string.allow_copy_password_warning) +
"\n\n" +
getString(R.string.clipboard_warning)
val warningDialog = AlertDialog.Builder(this@EntryActivity)
.setMessage(message).create()
warningDialog.setButton(AlertDialog.BUTTON1, getText(android.R.string.ok)
) { dialog, _ ->
PreferencesUtil.setAllowCopyPasswordAndProtectedFields(this@EntryActivity, true)
dialog.dismiss()
fillEntryDataInContentsView(entry)
}
warningDialog.setButton(AlertDialog.BUTTON2, getText(android.R.string.cancel)
) { dialog, _ ->
PreferencesUtil.setAllowCopyPasswordAndProtectedFields(this@EntryActivity, false)
dialog.dismiss()
fillEntryDataInContentsView(entry)
}
warningDialog.show()
})
} else {
entryContentsView?.assignPasswordCopyListener(null)
}
}
entryContentsView?.assignURL(entry.url)
entryContentsView?.setHiddenPasswordStyle(!mShowPassword)
entryContentsView?.assignComment(entry.notes)
// Assign custom fields
if (entry.allowExtraFields()) {
entryContentsView?.clearExtraFields()
entry.fields.doActionToAllCustomProtectedField { label, value ->
val showAction = !value.isProtected || PreferencesUtil.allowCopyPasswordAndProtectedFields(this@EntryActivity)
entryContentsView?.addExtraField(label, value, showAction, View.OnClickListener {
clipboardHelper?.timeoutCopyToClipboard(
value.toString(),
getString(R.string.copy_field, label)
)
})
}
}
// Assign dates
entry.creationTime.date?.let {
entryContentsView?.assignCreationDate(it)
}
entry.lastModificationTime.date?.let {
entryContentsView?.assignModificationDate(it)
}
entry.lastAccessTime.date?.let {
entryContentsView?.assignLastAccessDate(it)
}
val expires = entry.expiryTime.date
if (entry.isExpires && expires != null) {
entryContentsView?.assignExpiresDate(expires)
} else {
entryContentsView?.assignExpiresDate(getString(R.string.never))
}
database.stopManageEntry(entry)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE ->
// Not directly get the entry from intent data but from database
mEntry?.let {
fillEntryDataInContentsView(it)
}
}
}
private fun changeShowPasswordIcon(togglePassword: MenuItem?) {
if (mShowPassword) {
togglePassword?.setTitle(R.string.menu_hide_password)
togglePassword?.setIcon(R.drawable.ic_visibility_off_white_24dp)
} else {
togglePassword?.setTitle(R.string.menu_showpass)
togglePassword?.setIcon(R.drawable.ic_visibility_white_24dp)
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu)
val inflater = menuInflater
MenuUtil.contributionMenuInflater(inflater, menu)
inflater.inflate(R.menu.entry, menu)
inflater.inflate(R.menu.database_lock, menu)
if (readOnly) {
menu.findItem(R.id.menu_edit)?.isVisible = false
}
val togglePassword = menu.findItem(R.id.menu_toggle_pass)
entryContentsView?.let {
if (it.isPasswordPresent || it.atLeastOneFieldProtectedPresent()) {
changeShowPasswordIcon(togglePassword)
} else {
togglePassword?.isVisible = false
}
}
val gotoUrl = menu.findItem(R.id.menu_goto_url)
gotoUrl?.apply {
// In API >= 11 onCreateOptionsMenu may be called before onCreate completes
// so mEntry may not be set
if (mEntry == null) {
isVisible = false
} else {
if (mEntry?.url?.isEmpty() != false) {
// disable button if url is not available
isVisible = false
}
}
}
// Show education views
Handler().post { performedNextEducation(EntryActivityEducation(this), menu) }
return true
}
private fun performedNextEducation(entryActivityEducation: EntryActivityEducation,
menu: Menu) {
if (entryContentsView?.isUserNamePresent == true
&& entryActivityEducation.checkAndPerformedEntryCopyEducation(
findViewById(R.id.entry_user_name_action_image),
{
clipboardHelper?.timeoutCopyToClipboard(mEntry!!.username,
getString(R.string.copy_field,
getString(R.string.entry_user_name)))
},
{
// Launch autofill settings
startActivity(Intent(this@EntryActivity, SettingsAutofillActivity::class.java))
}))
else if (toolbar?.findViewById<View>(R.id.menu_edit) != null && entryActivityEducation.checkAndPerformedEntryEditEducation(
toolbar!!.findViewById(R.id.menu_edit),
{
onOptionsItemSelected(menu.findItem(R.id.menu_edit))
},
{
// Open Keepass doc to create field references
startActivity(Intent(Intent.ACTION_VIEW,
Uri.parse(getString(R.string.field_references_url))))
}))
;
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.menu_contribute -> return MenuUtil.onContributionItemSelected(this)
R.id.menu_toggle_pass -> {
mShowPassword = !mShowPassword
changeShowPasswordIcon(item)
entryContentsView?.setHiddenPasswordStyle(!mShowPassword)
return true
}
R.id.menu_edit -> {
EntryEditActivity.launch(this@EntryActivity, mEntry)
return true
}
R.id.menu_goto_url -> {
var url: String = mEntry?.url ?: ""
// Default http:// if no protocol specified
if (!url.contains("://")) {
url = "http://$url"
}
try {
Util.gotoUrl(this, url)
} catch (e: ActivityNotFoundException) {
Toast.makeText(this, R.string.no_url_handler, Toast.LENGTH_LONG).show()
}
return true
}
R.id.menu_lock -> {
lockAndExit()
return true
}
android.R.id.home -> finish() // close this activity and return to preview activity (if there is any)
}
return super.onOptionsItemSelected(item)
}
override fun finish() {
// Transit data in previous Activity after an update
/*
TODO Slowdown when add entry as result
Intent intent = new Intent();
intent.putExtra(EntryEditActivity.ADD_OR_UPDATE_ENTRY_KEY, mEntry);
setResult(EntryEditActivity.UPDATE_ENTRY_RESULT_CODE, intent);
*/
super.finish()
}
companion object {
private val TAG = EntryActivity::class.java.name
const val KEY_ENTRY = "entry"
fun launch(activity: Activity, pw: EntryVersioned, readOnly: Boolean) {
if (TimeoutHelper.checkTimeAndLockIfTimeout(activity)) {
val intent = Intent(activity, EntryActivity::class.java)
intent.putExtra(KEY_ENTRY, pw.nodeId)
ReadOnlyHelper.putReadOnlyInIntent(intent, readOnly)
activity.startActivityForResult(intent, EntryEditActivity.ADD_OR_UPDATE_ENTRY_REQUEST_CODE)
}
}
}
}

View File

@@ -503,7 +503,7 @@ public class GroupActivity extends LockingActivity
EntryVersioned entry = ((EntryVersioned) node); EntryVersioned entry = ((EntryVersioned) node);
EntrySelectionHelper.INSTANCE.doEntrySelectionAction(getIntent(), EntrySelectionHelper.INSTANCE.doEntrySelectionAction(getIntent(),
() -> { () -> {
EntryActivity.launch(GroupActivity.this, entry, getReadOnly()); EntryActivity.Companion.launch(GroupActivity.this, entry, getReadOnly());
return null; return null;
}, },
() -> { () -> {

View File

@@ -0,0 +1,90 @@
package com.kunzisoft.keepass.notifications
import android.content.Context
import android.content.Intent
import android.util.Log
import com.kunzisoft.keepass.app.App
import com.kunzisoft.keepass.database.element.EntryVersioned
import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.settings.PreferencesUtil
import java.util.*
object NotificationEntryCopyManager {
fun launchNotificationIfAllowed(context: Context, firstLaunch: Boolean, entry: EntryVersioned) {
// Start to manage field reference to copy a value from ref
val database = App.currentDatabase
database.startManageEntry(entry)
val containsUsernameToCopy = entry.username.isNotEmpty()
val containsPasswordToCopy = entry.password.isNotEmpty()
&& PreferencesUtil.allowCopyPasswordAndProtectedFields(context)
val containsExtraFieldToCopy = entry.allowExtraFields()
&& (entry.containsCustomFields()
&& entry.containsCustomFieldsNotProtected()
|| (entry.containsCustomFields()
&& entry.containsCustomFieldsProtected()
&& PreferencesUtil.allowCopyPasswordAndProtectedFields(context))
)
// If notifications enabled in settings
// Don't if application timeout
if (firstLaunch
&& PreferencesUtil.isClipboardNotificationsEnable(context.applicationContext)) {
if (containsUsernameToCopy || containsPasswordToCopy || containsExtraFieldToCopy) {
// username already copied, waiting for user's action before copy password.
val intent = Intent(context, NotificationCopyingService::class.java)
intent.action = NotificationCopyingService.ACTION_NEW_NOTIFICATION
intent.putExtra(NotificationCopyingService.EXTRA_ENTRY_TITLE, entry.title)
// Construct notification fields
val notificationFields = ArrayList<NotificationField>()
// Add username if exists to notifications
if (containsUsernameToCopy)
notificationFields.add(
NotificationField(
NotificationField.NotificationFieldId.USERNAME,
entry.username,
context.resources))
// Add password to notifications
if (containsPasswordToCopy) {
notificationFields.add(
NotificationField(
NotificationField.NotificationFieldId.PASSWORD,
entry.password,
context.resources))
}
// Add extra fields
if (containsExtraFieldToCopy) {
try {
entry.fields.doActionToAllCustomProtectedField(object : (String, ProtectedString) -> Unit {
private var anonymousFieldNumber = 0
override fun invoke(key: String, value: ProtectedString) {
//If value is not protected or allowed
if (!value.isProtected
|| PreferencesUtil.allowCopyPasswordAndProtectedFields(context)) {
notificationFields.add(
NotificationField(
NotificationField.NotificationFieldId.getAnonymousFieldId()[anonymousFieldNumber],
value.toString(),
key,
context.resources))
anonymousFieldNumber++
}
}
})
} catch (e: ArrayIndexOutOfBoundsException) {
Log.w("NotificationEntryCopyMg", "Only " + NotificationField.NotificationFieldId.getAnonymousFieldId().size +
" anonymous notifications are available")
}
}
// Add notifications
intent.putParcelableArrayListExtra(NotificationCopyingService.EXTRA_FIELDS, notificationFields)
context.startService(intent)
}
}
database.stopManageEntry(entry)
}
}

View File

@@ -1,253 +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 <http://www.gnu.org/licenses/>.
*
*/
package com.kunzisoft.keepass.view;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.support.v4.content.ContextCompat;
import android.text.method.PasswordTransformationMethod;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.kunzisoft.keepass.R;
import com.kunzisoft.keepass.database.element.security.ProtectedString;
import com.kunzisoft.keepass.utils.Util;
import java.text.DateFormat;
import java.util.Date;
public class EntryContentsView extends LinearLayout {
private boolean fontInVisibility;
private int colorAccent;
private View userNameContainerView;
private TextView userNameView;
private ImageView userNameActionView;
private View passwordContainerView;
private TextView passwordView;
private ImageView passwordActionView;
private View urlContainerView;
private TextView urlView;
private View commentContainerView;
private TextView commentView;
private ViewGroup extrasView;
private DateFormat dateFormat;
private DateFormat timeFormat;
private TextView creationDateView;
private TextView modificationDateView;
private TextView lastAccessDateView;
private TextView expiresDateView;
public EntryContentsView(Context context) {
this(context, null);
}
public EntryContentsView(Context context, AttributeSet attrs) {
super(context, attrs);
fontInVisibility = false;
dateFormat = android.text.format.DateFormat.getDateFormat(context);
timeFormat = android.text.format.DateFormat.getTimeFormat(context);
inflate(context);
int[] attrColorAccent = {R.attr.colorAccentCompat};
TypedArray taColorAccent = context.getTheme().obtainStyledAttributes(attrColorAccent);
this.colorAccent = taColorAccent.getColor(0, Color.BLACK);
taColorAccent.recycle();
}
private void inflate(Context context) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
assert inflater != null;
inflater.inflate(R.layout.entry_view_contents, this);
userNameContainerView = findViewById(R.id.entry_user_name_container);
userNameView = findViewById(R.id.entry_user_name);
userNameActionView = findViewById(R.id.entry_user_name_action_image);
passwordContainerView = findViewById(R.id.entry_password_container);
passwordView = findViewById(R.id.entry_password);
passwordActionView = findViewById(R.id.entry_password_action_image);
urlContainerView = findViewById(R.id.entry_url_container);
urlView = findViewById(R.id.entry_url);
commentContainerView = findViewById(R.id.entry_notes_container);
commentView = findViewById(R.id.entry_notes);
extrasView = findViewById(R.id.extra_strings);
creationDateView = findViewById(R.id.entry_created);
modificationDateView = findViewById(R.id.entry_modified);
lastAccessDateView = findViewById(R.id.entry_accessed);
expiresDateView = findViewById(R.id.entry_expires);
}
public void applyFontVisibilityToFields(boolean fontInVisibility) {
this.fontInVisibility = fontInVisibility;
}
public void assignUserName(String userName) {
if (userName != null && !userName.isEmpty()) {
userNameContainerView.setVisibility(VISIBLE);
userNameView.setText(userName);
if (fontInVisibility)
Util.applyFontVisibilityTo(getContext(), userNameView);
} else {
userNameContainerView.setVisibility(GONE);
}
}
public void assignUserNameCopyListener(OnClickListener onClickListener) {
userNameActionView.setOnClickListener(onClickListener);
}
public boolean isUserNamePresent() {
return userNameContainerView.getVisibility() == VISIBLE;
}
public void assignPassword(String password, boolean allowCopyPassword) {
if (password != null && !password.isEmpty()) {
passwordContainerView.setVisibility(VISIBLE);
passwordView.setText(password);
if (fontInVisibility)
Util.applyFontVisibilityTo(getContext(), passwordView);
if (!allowCopyPassword) {
passwordActionView.setColorFilter(ContextCompat.getColor(getContext(), R.color.grey_dark));
} else {
passwordActionView.setColorFilter(colorAccent);
}
} else {
passwordContainerView.setVisibility(GONE);
}
}
public void assignPasswordCopyListener(OnClickListener onClickListener) {
if (onClickListener == null)
setClickable(false);
passwordActionView.setOnClickListener(onClickListener);
}
public boolean isPasswordPresent() {
return passwordContainerView.getVisibility() == VISIBLE;
}
public boolean atLeastOneFieldProtectedPresent() {
for (int i = 0; i < extrasView.getChildCount(); i++) {
View childCustomView = extrasView.getChildAt(i);
if (childCustomView instanceof EntryCustomFieldProtected)
return true;
}
return false;
}
public void setHiddenPasswordStyle(boolean hiddenStyle) {
if ( !hiddenStyle ) {
passwordView.setTransformationMethod(null);
} else {
passwordView.setTransformationMethod(PasswordTransformationMethod.getInstance());
}
// Hidden style for custom fields
for (int i = 0; i < extrasView.getChildCount(); i++) {
View childCustomView = extrasView.getChildAt(i);
if (childCustomView instanceof EntryCustomFieldProtected)
((EntryCustomFieldProtected) childCustomView).setHiddenPasswordStyle(hiddenStyle);
}
}
public void assignURL(String url) {
if (url != null && !url.isEmpty()) {
urlContainerView.setVisibility(VISIBLE);
urlView.setText(url);
} else {
urlContainerView.setVisibility(GONE);
}
}
public void assignComment(String comment) {
if (comment != null && !comment.isEmpty()) {
commentContainerView.setVisibility(VISIBLE);
commentView.setText(comment);
if (fontInVisibility)
Util.applyFontVisibilityTo(getContext(), commentView);
} else {
commentContainerView.setVisibility(GONE);
}
}
public void addExtraField(String title, ProtectedString value, boolean showAction, OnClickListener onActionClickListener) {
EntryCustomField entryCustomField;
if (value.isProtected())
entryCustomField = new EntryCustomFieldProtected(getContext(), null, title, value, showAction, onActionClickListener);
else
entryCustomField = new EntryCustomField(getContext(), null, title, value, showAction, onActionClickListener);
entryCustomField.applyFontVisibility(fontInVisibility);
extrasView.addView(entryCustomField);
}
public void clearExtraFields() {
extrasView.removeAllViews();
}
private String getDateTime(Date date) {
return dateFormat.format(date) + " " + timeFormat.format(date);
}
public void assignCreationDate(Date date) {
creationDateView.setText(getDateTime(date));
}
public void assignModificationDate(Date date) {
modificationDateView.setText(getDateTime(date));
}
public void assignLastAccessDate(Date date) {
lastAccessDateView.setText(getDateTime(date));
}
public void assignExpiresDate(Date date) {
expiresDateView.setText(getDateTime(date));
}
public void assignExpiresDate(String constString) {
expiresDateView.setText(constString);
}
@Override
protected LayoutParams generateDefaultLayoutParams() {
return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
}
}

View File

@@ -0,0 +1,250 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
package com.kunzisoft.keepass.view
import android.content.Context
import android.graphics.Color
import android.support.v4.content.ContextCompat
import android.text.method.PasswordTransformationMethod
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import com.kunzisoft.keepass.R
import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.utils.Util
import java.text.DateFormat
import java.util.*
class EntryContentsView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : LinearLayout(context, attrs) {
private var fontInVisibility: Boolean = false
private val colorAccent: Int
private var userNameContainerView: View? = null
private var userNameView: TextView? = null
private var userNameActionView: ImageView? = null
private var passwordContainerView: View? = null
private var passwordView: TextView? = null
private var passwordActionView: ImageView? = null
private var urlContainerView: View? = null
private var urlView: TextView? = null
private var commentContainerView: View? = null
private var commentView: TextView? = null
private var extrasView: ViewGroup? = null
private val dateFormat: DateFormat
private val timeFormat: DateFormat
private var creationDateView: TextView? = null
private var modificationDateView: TextView? = null
private var lastAccessDateView: TextView? = null
private var expiresDateView: TextView? = null
val isUserNamePresent: Boolean
get() = userNameContainerView!!.visibility == View.VISIBLE
val isPasswordPresent: Boolean
get() = passwordContainerView!!.visibility == View.VISIBLE
init {
fontInVisibility = false
dateFormat = android.text.format.DateFormat.getDateFormat(context)
timeFormat = android.text.format.DateFormat.getTimeFormat(context)
inflate(context)
val attrColorAccent = intArrayOf(R.attr.colorAccentCompat)
val taColorAccent = context.theme.obtainStyledAttributes(attrColorAccent)
this.colorAccent = taColorAccent.getColor(0, Color.BLACK)
taColorAccent.recycle()
}
private fun inflate(context: Context) {
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
inflater.inflate(R.layout.entry_view_contents, this)
userNameContainerView = findViewById(R.id.entry_user_name_container)
userNameView = findViewById(R.id.entry_user_name)
userNameActionView = findViewById(R.id.entry_user_name_action_image)
passwordContainerView = findViewById(R.id.entry_password_container)
passwordView = findViewById(R.id.entry_password)
passwordActionView = findViewById(R.id.entry_password_action_image)
urlContainerView = findViewById(R.id.entry_url_container)
urlView = findViewById(R.id.entry_url)
commentContainerView = findViewById(R.id.entry_notes_container)
commentView = findViewById(R.id.entry_notes)
extrasView = findViewById(R.id.extra_strings)
creationDateView = findViewById(R.id.entry_created)
modificationDateView = findViewById(R.id.entry_modified)
lastAccessDateView = findViewById(R.id.entry_accessed)
expiresDateView = findViewById(R.id.entry_expires)
}
fun applyFontVisibilityToFields(fontInVisibility: Boolean) {
this.fontInVisibility = fontInVisibility
}
fun assignUserName(userName: String?) {
if (userName != null && userName.isNotEmpty()) {
userNameContainerView?.visibility = View.VISIBLE
userNameView?.apply {
text = userName
if (fontInVisibility)
Util.applyFontVisibilityTo(context, this)
}
} else {
userNameContainerView?.visibility = View.GONE
}
}
fun assignUserNameCopyListener(onClickListener: OnClickListener) {
userNameActionView?.setOnClickListener(onClickListener)
}
fun assignPassword(password: String?, allowCopyPassword: Boolean) {
if (password != null && password.isNotEmpty()) {
passwordContainerView?.visibility = View.VISIBLE
passwordView?.apply {
text = password
if (fontInVisibility)
Util.applyFontVisibilityTo(context, this)
}
if (!allowCopyPassword) {
passwordActionView?.setColorFilter(ContextCompat.getColor(context, R.color.grey_dark))
} else {
passwordActionView?.setColorFilter(colorAccent)
}
} else {
passwordContainerView?.visibility = View.GONE
}
}
fun assignPasswordCopyListener(onClickListener: OnClickListener?) {
if (onClickListener == null)
isClickable = false
passwordActionView?.setOnClickListener(onClickListener)
}
fun atLeastOneFieldProtectedPresent(): Boolean {
extrasView?.let {
for (i in 0 until it.childCount) {
val childCustomView = it.getChildAt(i)
if (childCustomView is EntryCustomFieldProtected)
return true
}
}
return false
}
fun setHiddenPasswordStyle(hiddenStyle: Boolean) {
if (!hiddenStyle) {
passwordView?.transformationMethod = null
} else {
passwordView?.transformationMethod = PasswordTransformationMethod.getInstance()
}
// Hidden style for custom fields
extrasView?.let {
for (i in 0 until it.childCount) {
val childCustomView = it.getChildAt(i)
if (childCustomView is EntryCustomFieldProtected)
childCustomView.setHiddenPasswordStyle(hiddenStyle)
}
}
}
fun assignURL(url: String?) {
if (url != null && url.isNotEmpty()) {
urlContainerView?.visibility = View.VISIBLE
urlView?.text = url
} else {
urlContainerView?.visibility = View.GONE
}
}
fun assignComment(comment: String?) {
if (comment != null && comment.isNotEmpty()) {
commentContainerView?.visibility = View.VISIBLE
commentView?.apply {
text = comment
if (fontInVisibility)
Util.applyFontVisibilityTo(context, this)
}
} else {
commentContainerView?.visibility = View.GONE
}
}
fun addExtraField(title: String, value: ProtectedString, showAction: Boolean, onActionClickListener: OnClickListener) {
val entryCustomField: EntryCustomField
if (value.isProtected)
entryCustomField = EntryCustomFieldProtected(context, null, title, value, showAction, onActionClickListener)
else
entryCustomField = EntryCustomField(context, null, title, value, showAction, onActionClickListener)
entryCustomField.applyFontVisibility(fontInVisibility)
extrasView?.addView(entryCustomField)
}
fun clearExtraFields() {
extrasView?.removeAllViews()
}
private fun getDateTime(date: Date): String {
return dateFormat.format(date) + " " + timeFormat.format(date)
}
fun assignCreationDate(date: Date) {
creationDateView?.text = getDateTime(date)
}
fun assignModificationDate(date: Date) {
modificationDateView?.text = getDateTime(date)
}
fun assignLastAccessDate(date: Date) {
lastAccessDateView?.text = getDateTime(date)
}
fun assignExpiresDate(date: Date) {
expiresDateView?.text = getDateTime(date)
}
fun assignExpiresDate(constString: String) {
expiresDateView?.text = constString
}
override fun generateDefaultLayoutParams(): LayoutParams {
return LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
}
}