keepass2android/src/keepass2android/EntryEditActivity.cs

740 lines
24 KiB
C#

/*
This file is part of Keepass2Android, Copyright 2013 Philipp Crocoll. This file is based on Keepassdroid, Copyright Brian Pellin.
Keepass2Android 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 2 of the License, or
(at your option) any later version.
Keepass2Android 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 Keepass2Android. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.App;
using Android.Content;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.Preferences;
using KeePassLib.Utility;
using KeePassLib;
using Android.Text;
using KeePassLib.Security;
using Android.Content.PM;
using keepass2android.view;
using System.IO;
using System.Globalization;
namespace keepass2android
{
[Activity (Label = "@string/app_name", ConfigurationChanges=ConfigChanges.Orientation|ConfigChanges.KeyboardHidden, Theme="@style/NoTitleBar")]
public class EntryEditActivity : LockCloseActivity {
public const String KEY_ENTRY = "entry";
public const String KEY_PARENT = "parent";
public const int RESULT_OK_ICON_PICKER = (int)Result.FirstUser + 1000;
public const int RESULT_OK_PASSWORD_GENERATOR = RESULT_OK_ICON_PICKER + 1;
private PwEntry mEntry, mEntryInDatabase;
private bool mShowPassword = false;
private bool mIsNew;
private PwIcon mSelectedIconID;
private PwUuid mSelectedCustomIconID = PwUuid.Zero;
private bool mSelectedIcon = false;
public static void Launch(Activity act, PwEntry pw) {
Intent i = new Intent(act, typeof(EntryEditActivity));
i.PutExtra(KEY_ENTRY, pw.Uuid.ToHexString());
act.StartActivityForResult(i, 0);
}
public static void Launch(Activity act, PwGroup pw) {
Intent i = new Intent(act, typeof(EntryEditActivity));
PwGroup parent = pw;
i.PutExtra(KEY_PARENT, parent.Uuid.ToHexString());
act.StartActivityForResult(i, 0);
}
private ScrollView scroll;
protected override void OnCreate(Bundle savedInstanceState)
{
ISharedPreferences prefs = PreferenceManager.GetDefaultSharedPreferences(this);
mShowPassword = ! prefs.GetBoolean(GetString(Resource.String.maskpass_key), Resources.GetBoolean(Resource.Boolean.maskpass_default));
base.OnCreate(savedInstanceState);
SetContentView(Resource.Layout.entry_edit);
SetResult(KeePass.EXIT_NORMAL);
// Likely the app has been killed exit the activity
Database db = App.getDB();
if ( ! db.Open ) {
Finish();
return;
}
Intent i = Intent;
String uuidBytes = i.GetStringExtra(KEY_ENTRY);
PwUuid entryId = PwUuid.Zero;
if (uuidBytes != null)
entryId = new KeePassLib.PwUuid(MemUtil.HexStringToByteArray(uuidBytes));
PwGroup parentGroup = null;
if ( entryId == PwUuid.Zero ) {
String groupId = i.GetStringExtra(KEY_PARENT);
parentGroup = db.groups[new PwUuid(MemUtil.HexStringToByteArray(groupId))];
mEntryInDatabase = new PwEntry(true, true);
mEntryInDatabase.Strings.Set(PwDefs.UserNameField, new ProtectedString(
db.pm.MemoryProtection.ProtectUserName, db.pm.DefaultUserName));
/*KPDesktop
* ProtectedString psAutoGen;
PwGenerator.Generate(out psAutoGen, Program.Config.PasswordGenerator.AutoGeneratedPasswordsProfile,
null, Program.PwGeneratorPool);
psAutoGen = psAutoGen.WithProtection(pwDb.MemoryProtection.ProtectPassword);
pwe.Strings.Set(PwDefs.PasswordField, psAutoGen);
int nExpireDays = Program.Config.Defaults.NewEntryExpiresInDays;
if(nExpireDays >= 0)
{
pwe.Expires = true;
pwe.ExpiryTime = DateTime.Now.AddDays(nExpireDays);
}*/
if((parentGroup.IconId != PwIcon.Folder) && (parentGroup.IconId != PwIcon.FolderOpen) &&
(parentGroup.IconId != PwIcon.FolderPackage))
{
mEntryInDatabase.IconId = parentGroup.IconId; // Inherit icon from group
}
mEntryInDatabase.CustomIconUuid = parentGroup.CustomIconUuid;
/*
* KPDesktop
if(strDefaultSeq.Length == 0)
{
PwGroup pg = m_pwEntry.ParentGroup;
if(pg != null)
{
strDefaultSeq = pg.GetAutoTypeSequenceInherited();
if(strDefaultSeq.Length == 0)
{
if(PwDefs.IsTanEntry(m_pwEntry))
strDefaultSeq = PwDefs.DefaultAutoTypeSequenceTan;
else
strDefaultSeq = PwDefs.DefaultAutoTypeSequence;
}
}
}*/
mIsNew = true;
} else {
System.Diagnostics.Debug.Assert(entryId != null);
mEntryInDatabase = db.entries[entryId];
mIsNew = false;
}
mEntry = mEntryInDatabase.CloneDeep();
fillData();
View scrollView = FindViewById(Resource.Id.entry_scroll);
scrollView.ScrollBarStyle = ScrollbarStyles.InsideInset;
ImageButton iconButton = (ImageButton) FindViewById(Resource.Id.icon_button);
iconButton.Click += (sender, evt) => {
IconPickerActivity.Launch(this);
};
// Generate password button
Button generatePassword = (Button) FindViewById(Resource.Id.generate_button);
generatePassword.Click += (object sender, EventArgs e) => {
GeneratePasswordActivity.Launch(this);
};
// Save button
Button save = (Button) FindViewById(Resource.Id.entry_save);
save.Click += (object sender, EventArgs e) =>
{
EntryEditActivity act = this;
if (!validateBeforeSaving())
return;
PwEntry initialEntry = mEntryInDatabase.CloneDeep();
PwEntry newEntry = mEntryInDatabase;
//Clone history and re-assign:
newEntry.History = newEntry.History.CloneDeep();
//Based on KeePass Desktop
bool bCreateBackup = (!mIsNew);
if(bCreateBackup) newEntry.CreateBackup(null);
if (mSelectedIcon == false) {
if (mIsNew) {
newEntry.IconId = PwIcon.Key;
} else {
// Keep previous icon, if no new one was selected
}
}
else {
newEntry.IconId = mSelectedIconID;
newEntry.CustomIconUuid = mSelectedCustomIconID;
}
/* KPDesktop
if(m_cbCustomForegroundColor.Checked)
newEntry.ForegroundColor = m_clrForeground;
else newEntry.ForegroundColor = Color.Empty;
if(m_cbCustomBackgroundColor.Checked)
newEntry.BackgroundColor = m_clrBackground;
else newEntry.BackgroundColor = Color.Empty;
*/
newEntry.Strings.Set(PwDefs.TitleField, new ProtectedString(db.pm.MemoryProtection.ProtectTitle,
Util.getEditText(act, Resource.Id.entry_title)));
newEntry.Strings.Set(PwDefs.UserNameField, new ProtectedString(db.pm.MemoryProtection.ProtectUserName,
Util.getEditText(act, Resource.Id.entry_user_name)));
String pass = Util.getEditText(act, Resource.Id.entry_password);
byte[] password = StrUtil.Utf8.GetBytes(pass);
newEntry.Strings.Set(PwDefs.PasswordField, new ProtectedString(db.pm.MemoryProtection.ProtectPassword,
password));
MemUtil.ZeroByteArray(password);
newEntry.Strings.Set(PwDefs.UrlField, new ProtectedString(db.pm.MemoryProtection.ProtectUrl,
Util.getEditText(act, Resource.Id.entry_url)));
newEntry.Strings.Set(PwDefs.NotesField, new ProtectedString(db.pm.MemoryProtection.ProtectNotes,
Util.getEditText(act, Resource.Id.entry_comment)));
// Delete all non standard strings
var keys = newEntry.Strings.GetKeys();
foreach (String key in keys)
if (PwDefs.IsStandardField(key) == false)
newEntry.Strings.Remove(key);
LinearLayout container = (LinearLayout) FindViewById(Resource.Id.advanced_container);
for (int index = 0; index < container.ChildCount; index++) {
View view = container.GetChildAt(index);
TextView keyView = (TextView)view.FindViewById(Resource.Id.title);
String key = keyView.Text;
TextView valueView = (TextView)view.FindViewById(Resource.Id.value);
String value = valueView.Text;
CheckBox cb = (CheckBox)view.FindViewById(Resource.Id.protection);
bool protect = cb.Checked;
newEntry.Strings.Set(key, new ProtectedString(protect, value));
}
newEntry.Binaries = mEntry.Binaries;
newEntry.Expires = mEntry.Expires;
if (newEntry.Expires)
{
newEntry.ExpiryTime = mEntry.ExpiryTime;
}
newEntry.OverrideUrl = Util.getEditText(this,Resource.Id.entry_override_url);
List<string> vNewTags = StrUtil.StringToTags(Util.getEditText(this,Resource.Id.entry_tags));
newEntry.Tags.Clear();
foreach(string strTag in vNewTags) newEntry.AddTag(strTag);
/*KPDesktop
m_atConfig.Enabled = m_cbAutoTypeEnabled.Checked;
m_atConfig.ObfuscationOptions = (m_cbAutoTypeObfuscation.Checked ?
AutoTypeObfuscationOptions.UseClipboard :
AutoTypeObfuscationOptions.None);
SaveDefaultSeq();
newEntry.AutoType = m_atConfig;
*/
newEntry.Touch(true, false); // Touch *after* backup
StrUtil.NormalizeNewLines(newEntry.Strings, true);
bool bUndoBackup = false;
PwCompareOptions cmpOpt = (PwCompareOptions.NullEmptyEquivStd |
PwCompareOptions.IgnoreTimes);
if(bCreateBackup) cmpOpt |= PwCompareOptions.IgnoreLastBackup;
if(newEntry.EqualsEntry(initialEntry, cmpOpt, MemProtCmpMode.CustomOnly))
{
// No modifications at all => restore last mod time and undo backup
newEntry.LastModificationTime = initialEntry.LastModificationTime;
bUndoBackup = bCreateBackup;
}
else if(bCreateBackup)
{
// If only history items have been modified (deleted) => undo
// backup, but without restoring the last mod time
PwCompareOptions cmpOptNH = (cmpOpt | PwCompareOptions.IgnoreHistory);
if(newEntry.EqualsEntry(initialEntry, cmpOptNH, MemProtCmpMode.CustomOnly))
bUndoBackup = true;
}
if(bUndoBackup) newEntry.History.RemoveAt(newEntry.History.UCount - 1);
newEntry.MaintainBackups(db.pm);
//if ( newEntry.Strings.ReadSafe (PwDefs.TitleField).Equals(mEntry.Strings.ReadSafe (PwDefs.TitleField)) ) {
// SetResult(KeePass.EXIT_REFRESH);
//} else {
//it's safer to always update the title as we might add further information in the title like expiry etc.
SetResult(KeePass.EXIT_REFRESH_TITLE);
//}
RunnableOnFinish task;
OnFinish onFinish = new AfterSave(new Handler(), act);
if ( mIsNew ) {
task = AddEntry.getInstance(this, App.getDB(), newEntry, parentGroup, onFinish);
} else {
task = new UpdateEntry(this, App.getDB(), initialEntry, newEntry, onFinish);
}
ProgressTask pt = new ProgressTask(act, task, Resource.String.saving_database);
pt.run();
};
// Respect mask password setting
if (mShowPassword) {
EditText pass = (EditText) FindViewById(Resource.Id.entry_password);
EditText conf = (EditText) FindViewById(Resource.Id.entry_confpassword);
pass.InputType = InputTypes.ClassText | InputTypes.TextVariationVisiblePassword;
conf.InputType = InputTypes.ClassText | InputTypes.TextVariationVisiblePassword;
}
scroll = (ScrollView) FindViewById(Resource.Id.entry_scroll);
Button addButton = (Button) FindViewById(Resource.Id.add_advanced);
addButton.Visibility = ViewStates.Visible;
addButton.Click += (object sender, EventArgs e) =>
{
LinearLayout container = (LinearLayout) FindViewById(Resource.Id.advanced_container);
EntryEditSection ees = (EntryEditSection) LayoutInflater.Inflate(Resource.Layout.entry_edit_section, null);
ees.setData("", new ProtectedString(false, ""));
ees.getDeleteButton().Click += (senderEes, eEes) => deleteAdvancedString((View)senderEes);
container.AddView(ees);
// Scroll bottom
scroll.Post(() => {
scroll.FullScroll(FocusSearchDirection.Down);
});
};
((CheckBox)FindViewById(Resource.Id.entry_expires_checkbox)).CheckedChange += (object sender, CompoundButton.CheckedChangeEventArgs e) =>
{
mEntry.Expires = e.IsChecked;
if (e.IsChecked)
{
if (mEntry.ExpiryTime < DateTime.Now)
mEntry.ExpiryTime = DateTime.Now;
}
updateExpires();
};
}
void addBinaryOrAsk(string filename)
{
string strItem = UrlUtil.GetFileName(filename);
if(mEntry.Binaries.Get(strItem) != null)
{
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.SetTitle(GetString(Resource.String.AskOverwriteBinary_title));
builder.SetMessage(GetString(Resource.String.AskOverwriteBinary));
builder.SetPositiveButton(GetString(Resource.String.AskOverwriteBinary_yes), new EventHandler<DialogClickEventArgs>((dlgSender, dlgEvt) =>
{
addBinary(filename, true);
}));
builder.SetNegativeButton(GetString(Resource.String.AskOverwriteBinary_no), new EventHandler<DialogClickEventArgs>((dlgSender, dlgEvt) =>
{
addBinary(filename, false);
}));
builder.SetNeutralButton(GetString(Android.Resource.String.Cancel),
new EventHandler<DialogClickEventArgs>((dlgSender, dlgEvt) => {}));
Dialog dialog = builder.Create();
dialog.Show();
} else
addBinary(filename, true);
}
void addBinary(string filename, bool overwrite)
{
string strItem = UrlUtil.GetFileName(filename);
if (!overwrite)
{
string strFileName = UrlUtil.StripExtension(strItem);
string strExtension = "." + UrlUtil.GetExtension(strItem);
int nTry = 0;
while(true)
{
string strNewName = strFileName + nTry.ToString() + strExtension;
if(mEntry.Binaries.Get(strNewName) == null)
{
strItem = strNewName;
break;
}
++nTry;
}
}
try
{
byte[] vBytes = File.ReadAllBytes(filename);
if(vBytes != null)
{
ProtectedBinary pb = new ProtectedBinary(false, vBytes);
mEntry.Binaries.Set(strItem, pb);
}
}
catch(Exception exAttach)
{
Toast.MakeText(this, GetString(Resource.String.AttachFailed)+" "+exAttach.Message, ToastLength.Long).Show();
}
populateBinaries();
}
protected override void OnActivityResult(int requestCode, Result resultCode, Intent data)
{
switch ((int)resultCode)
{
case RESULT_OK_ICON_PICKER:
mSelectedIconID = (PwIcon) data.Extras.GetInt(IconPickerActivity.KEY_ICON_ID);
mSelectedCustomIconID = PwUuid.Zero;
String customIconIdString = data.Extras.GetString(IconPickerActivity.KEY_CUSTOM_ICON_ID);
if (!String.IsNullOrEmpty(customIconIdString))
mSelectedCustomIconID = new PwUuid(MemUtil.HexStringToByteArray(customIconIdString));
mSelectedIcon = true;
ImageButton currIconButton = (ImageButton) FindViewById(Resource.Id.icon_button);
//TODO: custom image
currIconButton.SetImageResource(Icons.iconToResId(mSelectedIconID));
break;
case RESULT_OK_PASSWORD_GENERATOR:
String generatedPassword = data.GetStringExtra("keepass2android.password.generated_password");
EditText password = (EditText) FindViewById(Resource.Id.entry_password);
EditText confPassword = (EditText) FindViewById(Resource.Id.entry_confpassword);
password.Text = generatedPassword;
confPassword.Text = generatedPassword;
break;
case (int)Result.Ok:
if (requestCode == Intents.REQUEST_CODE_FILE_BROWSE)
{
String filename = data.DataString;
if (filename != null) {
if (filename.StartsWith("file://")) {
filename = filename.Substring(7);
}
filename = Java.Net.URLDecoder.Decode(filename);
addBinaryOrAsk(filename);
}
}
break;
case (int)Result.Canceled:
break;
default:
break;
}
}
void populateBinaries()
{
ViewGroup binariesGroup = (ViewGroup)FindViewById(Resource.Id.binaries);
binariesGroup.RemoveAllViews();
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.FillParent, RelativeLayout.LayoutParams.WrapContent);
foreach (KeyValuePair<string, ProtectedBinary> pair in mEntry.Binaries)
{
String key = pair.Key;
Button binaryButton = new Button(this);
binaryButton.Text = key;
binaryButton.SetCompoundDrawablesWithIntrinsicBounds( Resources.GetDrawable(Android.Resource.Drawable.IcMenuDelete),null, null, null);
binaryButton.Click += (object sender, EventArgs e) =>
{
Button btnSender = (Button)(sender);
mEntry.Binaries.Remove(key);
populateBinaries();
};
binariesGroup.AddView(binaryButton,layoutParams);
}
Button addBinaryButton = new Button(this);
addBinaryButton.Text = GetString(Resource.String.add_binary);
addBinaryButton.SetCompoundDrawablesWithIntrinsicBounds( Resources.GetDrawable(Android.Resource.Drawable.IcMenuAdd) , null, null, null);
addBinaryButton.Click += (object sender, EventArgs e) =>
{
Util.showBrowseDialog("/mnt/sdcard", this);
};
binariesGroup.AddView(addBinaryButton,layoutParams);
FindViewById(Resource.Id.entry_binaries_label).Visibility = mEntry.Binaries.UCount > 0 ? ViewStates.Visible : ViewStates.Gone;
}
public override bool OnCreateOptionsMenu(IMenu menu) {
base.OnCreateOptionsMenu(menu);
MenuInflater inflater = MenuInflater;
inflater.Inflate(Resource.Menu.entry_edit, menu);
IMenuItem togglePassword = menu.FindItem(Resource.Id.menu_toggle_pass);
if ( mShowPassword ) {
togglePassword.SetTitle(Resource.String.menu_hide_password);
} else {
togglePassword.SetTitle(Resource.String.show_password);
}
return true;
}
public override bool OnOptionsItemSelected(IMenuItem item) {
switch ( item.ItemId ) {
/*case Resource.Id.menu_donate:
try {
Util.gotoUrl(this, Resource.String.donate_url);
} catch (ActivityNotFoundException) {
Toast.MakeText(this, Resource.String.error_failed_to_launch_link, ToastLength.Long).Show();
return false;
}
return true;*/
case Resource.Id.menu_toggle_pass:
if ( mShowPassword ) {
item.SetTitle(Resource.String.show_password);
mShowPassword = false;
} else {
item.SetTitle(Resource.String.menu_hide_password);
mShowPassword = true;
}
setPasswordStyle();
return true;
case Resource.Id.menu_cancel_edit:
Finish();
break;
}
return base.OnOptionsItemSelected(item);
}
private void setPasswordStyle() {
TextView password = (TextView) FindViewById(Resource.Id.entry_password);
TextView confpassword = (TextView) FindViewById(Resource.Id.entry_confpassword);
if ( mShowPassword ) {
password.InputType = InputTypes.ClassText | InputTypes.TextVariationVisiblePassword;
confpassword.InputType = InputTypes.ClassText | InputTypes.TextVariationVisiblePassword;
} else {
password.InputType = InputTypes.ClassText | InputTypes.TextVariationPassword;
confpassword.InputType = InputTypes.ClassText | InputTypes.TextVariationPassword;
}
}
void updateExpires()
{
if (mEntry.Expires)
{
populateText(Resource.Id.entry_expires, getDateTime(mEntry.ExpiryTime));
}
else
{
populateText(Resource.Id.entry_expires, GetString(Resource.String.never));
}
((CheckBox)FindViewById(Resource.Id.entry_expires_checkbox)).Checked = mEntry.Expires;
((EditText)FindViewById(Resource.Id.entry_expires)).Enabled = mEntry.Expires;
}
private void fillData() {
ImageButton currIconButton = (ImageButton) FindViewById(Resource.Id.icon_button);
App.getDB().drawFactory.assignDrawableTo(currIconButton, Resources, App.getDB().pm, mEntry.IconId, mEntry.CustomIconUuid);
populateText(Resource.Id.entry_title, mEntry.Strings.ReadSafe (PwDefs.TitleField));
populateText(Resource.Id.entry_user_name, mEntry.Strings.ReadSafe (PwDefs.UserNameField));
populateText(Resource.Id.entry_url, mEntry.Strings.ReadSafe (PwDefs.UrlField));
String password = mEntry.Strings.ReadSafe(PwDefs.PasswordField);
populateText(Resource.Id.entry_password, password);
populateText(Resource.Id.entry_confpassword, password);
setPasswordStyle();
populateText(Resource.Id.entry_comment, mEntry.Strings.ReadSafe (PwDefs.NotesField));
LinearLayout container = (LinearLayout) FindViewById(Resource.Id.advanced_container);
foreach (var pair in mEntry.Strings)
{
String key = pair.Key;
if (!PwDefs.IsStandardField(key)) {
EntryEditSection ees = (EntryEditSection) LayoutInflater.Inflate(Resource.Layout.entry_edit_section, null);
ees.setData(key, pair.Value);
ees.getDeleteButton().Click += (sender, e) => deleteAdvancedString((View)sender);
container.AddView(ees);
}
}
populateBinaries();
populateText(Resource.Id.entry_override_url, mEntry.OverrideUrl);
populateText(Resource.Id.entry_tags, StrUtil.TagsToString(mEntry.Tags, true));
updateExpires();
}
private String getDateTime(System.DateTime dt) {
return dt.ToString ("g", CultureInfo.CurrentUICulture);
}
public void deleteAdvancedString(View view) {
EntryEditSection section = (EntryEditSection) view.Parent;
LinearLayout container = (LinearLayout) FindViewById(Resource.Id.advanced_container);
for (int i = 0; i < container.ChildCount; i++) {
EntryEditSection ees = (EntryEditSection) container.GetChildAt(i);
if (ees == section) {
container.RemoveViewAt(i);
container.Invalidate();
break;
}
}
}
protected bool validateBeforeSaving() {
// Require title
String title = Util.getEditText(this, Resource.Id.entry_title);
if ( title.Length == 0 ) {
Toast.MakeText(this, Resource.String.error_title_required, ToastLength.Long).Show();
return false;
}
// Validate password
String pass = Util.getEditText(this, Resource.Id.entry_password);
String conf = Util.getEditText(this, Resource.Id.entry_confpassword);
if ( ! pass.Equals(conf) ) {
Toast.MakeText(this, Resource.String.error_pass_match, ToastLength.Long).Show();
return false;
}
// Validate expiry date
DateTime newExpiry = new DateTime();
if ((mEntry.Expires) && (!DateTime.TryParse( Util.getEditText(this,Resource.Id.entry_expires), out newExpiry)))
{
Toast.MakeText(this, Resource.String.error_invalid_expiry_date, ToastLength.Long).Show();
return false;
}
else
{
mEntry.ExpiryTime = newExpiry;
}
LinearLayout container = (LinearLayout) FindViewById(Resource.Id.advanced_container);
for (int i = 0; i < container.ChildCount; i++) {
EntryEditSection ees = (EntryEditSection) container.GetChildAt(i);
TextView keyView = (TextView) ees.FindViewById(Resource.Id.title);
string key = keyView.Text;
if (String.IsNullOrEmpty(key)) {
Toast.MakeText(this, Resource.String.error_string_key, ToastLength.Long).Show();
return false;
}
}
return true;
}
private void populateText(int viewId, String text) {
TextView tv = (TextView) FindViewById(viewId);
tv.Text = text;
}
private class AfterSave : OnFinish {
Activity act;
public AfterSave(Handler handler, Activity act): base(handler) {
this.act = act;
}
public override void run() {
if ( mSuccess ) {
act.Finish();
} else {
displayMessage(act);
}
}
}
}
}