/* 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 . */ 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 KeePassLib; using Android.Text.Format; using KeePassLib.Utility; using Java.Util; using Android.Preferences; using Android.Text.Method; using Android.Util; using System.Globalization; using Android.Content.PM; using KeePassLib.Security; using keepass2android.view; using Android.Webkit; namespace keepass2android { [Activity (Label = "@string/app_name", ConfigurationChanges=ConfigChanges.Orientation|ConfigChanges.KeyboardHidden, Theme="@style/NoTitleBar")] public class EntryActivity : LockCloseActivity { public const String KEY_ENTRY = "entry"; public const String KEY_REFRESH_POS = "refresh_pos"; public const String KEY_CLOSE_AFTER_CREATE = "close_after_create"; public static void Launch(Activity act, PwEntry pw, int pos) { Launch(act, pw, pos, false); } public static void Launch(Activity act, PwEntry pw, int pos, bool closeAfterCreate) { Intent i; i = new Intent(act, typeof(EntryActivity)); i.PutExtra(KEY_ENTRY, pw.Uuid.ToHexString()); i.PutExtra(KEY_REFRESH_POS, pos); i.PutExtra(KEY_CLOSE_AFTER_CREATE, closeAfterCreate); act.StartActivityForResult(i,0); } protected PwEntry mEntry; private bool mShowPassword; private int mPos; protected void setEntryView() { SetContentView(Resource.Layout.entry_view); } protected void setupEditButtons() { Button edit = (Button) FindViewById(Resource.Id.entry_edit); edit.Click += (sender, e) => { EntryEditActivity.Launch(this, mEntry); }; } 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); setEntryView(); Context appCtx = ApplicationContext; Database db = App.getDB(); // Likely the app has been killed exit the activity if (! db.Loaded) { Finish(); return; } SetResult(KeePass.EXIT_NORMAL); Intent i = Intent; PwUuid uuid = new PwUuid(MemUtil.HexStringToByteArray(i.GetStringExtra(KEY_ENTRY))); mPos = i.GetIntExtra(KEY_REFRESH_POS, -1); bool closeAfterCreate = i.GetBooleanExtra(KEY_CLOSE_AFTER_CREATE, false); mEntry = db.entries [uuid]; // Refresh Menu contents in case onCreateMenuOptions was called before mEntry was set ActivityCompat.invalidateOptionsMenu(this); // Update last access time. mEntry.Touch(false); if (PwDefs.IsTanEntry(mEntry) && prefs.GetBoolean(GetString(Resource.String.TanExpiresOnUse_key), Resources.GetBoolean(Resource.Boolean.TanExpiresOnUse_default)) && ((mEntry.Expires == false) || mEntry.ExpiryTime > DateTime.Now)) { PwEntry backupEntry = mEntry.CloneDeep(); mEntry.ExpiryTime = DateTime.Now; mEntry.Expires = true; mEntry.Touch(true); requiresRefresh(); Handler handler = new Handler(); UpdateEntry update = new UpdateEntry(this, App.getDB(), backupEntry, mEntry, new AfterSave(handler)); ProgressTask pt = new ProgressTask(this, update, Resource.String.saving_database); pt.run(); } fillData(false); setupEditButtons(); Intent showNotIntent = new Intent(this, typeof(CopyToClipboardService)); Intent.SetAction(Intents.SHOW_NOTIFICATION); showNotIntent.PutExtra(KEY_ENTRY, mEntry.Uuid.ToHexString()); StartService(showNotIntent); Android.Util.Log.Debug("DEBUG", "Requesting copy to clipboard for Uuid=" + mEntry.Uuid.ToHexString()); /*foreach (PwUuid key in App.getDB().entries.Keys) { Android.Util.Log.Debug("DEBUG",key.ToHexString() + " -> " + App.getDB().entries[key].Uuid.ToHexString()); }*/ if (closeAfterCreate) { Finish(); } } private class AfterSave : OnFinish { public AfterSave(Handler handler):base(handler) { } public override void run() { base.run(); } }; private String getDateTime(System.DateTime dt) { return dt.ToString ("g", CultureInfo.CurrentUICulture); } String concatTags(List tags) { StringBuilder sb = new StringBuilder(); foreach (string tag in tags) { sb.Append(tag); sb.Append(", "); } if (tags.Count > 0) sb.Remove(sb.Length-2,2); return sb.ToString(); } void populateExtraStrings(bool trimList) { ViewGroup extraGroup = (ViewGroup)FindViewById(Resource.Id.extra_strings); if (trimList) { extraGroup.RemoveAllViews(); } bool hasExtraFields = false; foreach (KeyValuePair pair in mEntry.Strings) { String key = pair.Key; if (!PwDefs.IsStandardField(key)) { View view = new EntrySection(this, null, key, pair.Value.ReadString()); extraGroup.AddView(view); hasExtraFields = true; } } FindViewById(Resource.Id.entry_extra_strings_label).Visibility = hasExtraFields ? ViewStates.Visible : ViewStates.Gone; } string writeBinaryToFile(string key) { ProtectedBinary pb = mEntry.Binaries.Get(key); System.Diagnostics.Debug.Assert(pb != null); if(pb == null) throw new ArgumentException(); ISharedPreferences prefs = PreferenceManager.GetDefaultSharedPreferences(this); string binaryDirectory = prefs.GetString(GetString(Resource.String.BinaryDirectory_key),GetString(Resource.String.BinaryDirectory_default)); var targetFile = new Java.IO.File(binaryDirectory,key); Java.IO.File parent = targetFile.ParentFile; if (parent == null || (parent.Exists() && ! parent.IsDirectory)) { Toast.MakeText(this, Resource.String.error_invalid_path, ToastLength.Long).Show(); return null; } if (! parent.Exists()) { // Create parent dircetory if (! parent.Mkdirs()) { Toast.MakeText(this, Resource.String.error_could_not_create_parent, ToastLength.Long).Show(); return null; } } string filename = targetFile.AbsolutePath; byte[] pbData = pb.ReadData(); try { System.IO.File.WriteAllBytes(filename, pbData); } catch(Exception exWrite) { Toast.MakeText(this, GetString(Resource.String.SaveAttachment_Failed, new Java.Lang.Object[]{ filename}) +exWrite.Message,ToastLength.Long).Show(); return null; } finally { MemUtil.ZeroByteArray(pbData); } return filename; } void openBinaryFile(string filename) { String theMIMEType = getMimeType(filename); if (theMIMEType != null) { Intent theIntent = new Intent(Intent.ActionView); theIntent.AddFlags(ActivityFlags.NewTask | ActivityFlags.ExcludeFromRecents); theIntent.SetDataAndType(Android.Net.Uri.FromFile(new Java.IO.File(filename)), theMIMEType); try { StartActivity(theIntent); } catch (ActivityNotFoundException anfe) { //ignore Toast.MakeText(this, "Couldn't open file", ToastLength.Short).Show(); } } } void populateBinaries(bool trimList) { ViewGroup binariesGroup = (ViewGroup)FindViewById(Resource.Id.binaries); if (trimList) { binariesGroup.RemoveAllViews(); } foreach (KeyValuePair pair in mEntry.Binaries) { String key = pair.Key; Button binaryButton = new Button(this); RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.FillParent, RelativeLayout.LayoutParams.WrapContent); binaryButton.Text = key; binaryButton.SetCompoundDrawablesWithIntrinsicBounds( Resources.GetDrawable(Android.Resource.Drawable.IcMenuSave),null, null, null); binaryButton.Click += (object sender, EventArgs e) => { Button btnSender = (Button)(sender); string newFilename = writeBinaryToFile(btnSender.Text); if (newFilename != null) { Toast.MakeText(this, GetString(Resource.String.SaveAttachment_doneMessage, new Java.Lang.Object[]{newFilename}), ToastLength.Short).Show(); openBinaryFile(newFilename); } }; binariesGroup.AddView(binaryButton,layoutParams); } FindViewById(Resource.Id.entry_binaries_label).Visibility = mEntry.Binaries.UCount > 0 ? ViewStates.Visible : ViewStates.Gone; } // url = file path or whatever suitable URL you want. public static String getMimeType(String url) { String type = null; String extension = MimeTypeMap.GetFileExtensionFromUrl(url); if (extension != null) { MimeTypeMap mime = MimeTypeMap.Singleton; type = mime.GetMimeTypeFromExtension(extension); } return type; } protected void fillData(bool trimList) { ImageView iv = (ImageView)FindViewById(Resource.Id.entry_icon); App.getDB().drawFactory.assignDrawableTo(iv, Resources, App.getDB().pm, mEntry.IconId, mEntry.CustomIconUuid); //populateText(Resource.Id.entry_title, mEntry.Strings.ReadSafe(PwDefs.TitleField)); var button = ((Button)FindViewById(Resource.Id.entry_title)); button.Text = mEntry.Strings.ReadSafe(PwDefs.TitleField); button.Click += (object sender, EventArgs e) => { Finish(); }; populateText(Resource.Id.entry_user_name, Resource.Id.entry_user_name_label, mEntry.Strings.ReadSafe(PwDefs.UserNameField)); populateText(Resource.Id.entry_url, Resource.Id.entry_url_label, mEntry.Strings.ReadSafe(PwDefs.UrlField)); populateText(Resource.Id.entry_password, Resource.Id.entry_password_label, mEntry.Strings.ReadSafe(PwDefs.PasswordField)); setPasswordStyle(); populateText(Resource.Id.entry_created, Resource.Id.entry_created_label, getDateTime(mEntry.CreationTime)); populateText(Resource.Id.entry_modified, Resource.Id.entry_modified_label, getDateTime(mEntry.LastModificationTime)); populateText(Resource.Id.entry_accessed, Resource.Id.entry_accessed_label, getDateTime(mEntry.LastAccessTime)); if (mEntry.Expires) { populateText(Resource.Id.entry_expires, Resource.Id.entry_expires_label, getDateTime(mEntry.ExpiryTime)); } else { populateText(Resource.Id.entry_expires, Resource.Id.entry_expires_label, Resource.String.never); } populateText(Resource.Id.entry_comment, Resource.Id.entry_comment_label, mEntry.Strings.ReadSafe(PwDefs.NotesField)); populateText(Resource.Id.entry_tags, Resource.Id.entry_tags_label, concatTags(mEntry.Tags)); populateText(Resource.Id.entry_override_url, Resource.Id.entry_override_url_label, mEntry.OverrideUrl); populateExtraStrings(trimList); populateBinaries(trimList); } private void populateText(int viewId, int headerViewId,int resId) { View header = FindViewById(headerViewId); TextView tv = (TextView)FindViewById(viewId); header.Visibility = tv.Visibility = ViewStates.Visible; tv.SetText (resId); } private void populateText(int viewId, int headerViewId, String text) { View header = FindViewById(headerViewId); TextView tv = (TextView)FindViewById(viewId); if (String.IsNullOrEmpty(text)) { header.Visibility = tv.Visibility = ViewStates.Gone; } else { header.Visibility = tv.Visibility = ViewStates.Visible; tv.Text = text; } } void requiresRefresh () { Intent ret = new Intent (); ret.PutExtra (KEY_REFRESH_POS, mPos); SetResult (KeePass.EXIT_REFRESH, ret); } protected override void OnActivityResult(int requestCode, Result resultCode, Intent data) { base.OnActivityResult(requestCode, resultCode, data); if ( resultCode == KeePass.EXIT_REFRESH || resultCode == KeePass.EXIT_REFRESH_TITLE ) { fillData(true); if ( resultCode == KeePass.EXIT_REFRESH_TITLE ) { requiresRefresh (); } } } public override bool OnCreateOptionsMenu(IMenu menu) { base.OnCreateOptionsMenu(menu); MenuInflater inflater = MenuInflater; inflater.Inflate(Resource.Menu.entry, 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); } IMenuItem gotoUrl = menu.FindItem(Resource.Id.menu_goto_url); //Disabled IMenuItem copyUser = menu.FindItem(Resource.Id.menu_copy_user); //Disabled IMenuItem copyPass = menu.FindItem(Resource.Id.menu_copy_pass); // In API >= 11 onCreateOptionsMenu may be called before onCreate completes // so mEntry may not be set if (mEntry == null) { gotoUrl.SetVisible(false); //Disabled copyUser.SetVisible(false); //Disabled copyPass.SetVisible(false); } else { String url = mEntry.Strings.ReadSafe (PwDefs.UrlField); if (String.IsNullOrEmpty(url)) { // disable button if url is not available gotoUrl.SetVisible(false); } if ( String.IsNullOrEmpty(mEntry.Strings.ReadSafe(PwDefs.UserNameField ))) { // disable button if username is not available //Disabled copyUser.SetVisible(false); } if ( String.IsNullOrEmpty(mEntry.Strings.ReadSafe(PwDefs.PasswordField ))) { // disable button if password is not available //Disabled copyPass.SetVisible(false); } } return true; } private void setPasswordStyle() { TextView password = (TextView) FindViewById(Resource.Id.entry_password); if ( mShowPassword ) { password.TransformationMethod = null; } else { password.TransformationMethod = PasswordTransformationMethod.Instance; } } 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_goto_url: String url; url = mEntry.Strings.ReadSafe (PwDefs.UserNameField); // Default http:// if no protocol specified if ( ! url.Contains("://") ) { url = "http://" + url; } try { Util.gotoUrl(this, url); } catch (ActivityNotFoundException) { Toast.MakeText(this, Resource.String.no_url_handler, ToastLength.Long).Show(); } return true; /* TODO: required? case Resource.Id.menu_copy_user: timeoutCopyToClipboard(mEntry.Strings.ReadSafe (PwDefs.UserNameField)); return true; case Resource.Id.menu_copy_pass: timeoutCopyToClipboard(mEntry.Strings.ReadSafe (PwDefs.UserNameField)); return true; */ case Resource.Id.menu_lock: App.setShutdown(); SetResult(KeePass.EXIT_LOCK); Finish(); return true; } return base.OnOptionsItemSelected(item); } } }