Splitted keepass2android project into

- keepass2android: UI stuff only
 - Kp2aBusinessLogic: Password-Database related algorithms (even though tied to android, no UI required here)

Removed dependencies of logic layer to static Application, Resource class or other UI stuff
Added MonoDroidUnitTesting (not yet used, will be used for testing logic layer)
This commit is contained in:
Philipp Crocoll 2013-06-14 06:14:50 +02:00
parent 9d8e10b236
commit 26575c4ba4
102 changed files with 5623 additions and 1184 deletions

2
.gitignore vendored
View File

@ -26,3 +26,5 @@
/src/java/KP2ASoftKeyboard/projectzip
/src/java/KP2ASoftKeyboard/createProjectZip.bat
Thumbs.db
/src/monodroid-unittesting/*.suo

View File

@ -1,6 +1,6 @@

Microsoft Visual Studio Solution File, Format Version 11.00
# Visual Studio 2010
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2012
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KeePassLib2Android", "KeePassLib2Android\KeePassLib2Android.csproj", "{545B4A6B-8BBA-4FBE-92FC-4AC060122A54}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "keepass2android", "keepass2android\keepass2android.csproj", "{A6CF8A86-37C1-4197-80FE-519DE2C842F5}"
@ -9,6 +9,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "kp2akeytransform", "kp2akey
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kp2aKeyboardBinding", "Kp2aKeyboardBinding\Kp2aKeyboardBinding.csproj", "{A8779D4D-7C49-4C2F-82BD-2CDC448391DA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kp2aBusinessLogic", "Kp2aBusinessLogic\Kp2aBusinessLogic.csproj", "{53A9CB7F-6553-4BC0-B56B-9410BB2E59AA}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonoDroidUnitTesting", "monodroid-unittesting\MonoDroidUnitTesting\MonoDroidUnitTesting.csproj", "{A5F8FB02-00E0-4335-91EF-AEAA2C2F3C48}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Kp2aUnitTests", "Kp2aUnitTests\Kp2aUnitTests.csproj", "{46B769B8-2C58-4138-9CC0-70E3AE3C9A3A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -20,6 +26,9 @@ Global
Release|Win32 = Release|Win32
Release|x64 = Release|x64
ReleaseNoNet|Any CPU = ReleaseNoNet|Any CPU
ReleaseNoNet|Mixed Platforms = ReleaseNoNet|Mixed Platforms
ReleaseNoNet|Win32 = ReleaseNoNet|Win32
ReleaseNoNet|x64 = ReleaseNoNet|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{545B4A6B-8BBA-4FBE-92FC-4AC060122A54}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
@ -40,6 +49,34 @@ Global
{545B4A6B-8BBA-4FBE-92FC-4AC060122A54}.Release|x64.Build.0 = Release|Any CPU
{545B4A6B-8BBA-4FBE-92FC-4AC060122A54}.ReleaseNoNet|Any CPU.ActiveCfg = ReleaseNoNet|Any CPU
{545B4A6B-8BBA-4FBE-92FC-4AC060122A54}.ReleaseNoNet|Any CPU.Build.0 = ReleaseNoNet|Any CPU
{545B4A6B-8BBA-4FBE-92FC-4AC060122A54}.ReleaseNoNet|Mixed Platforms.ActiveCfg = ReleaseNoNet|Any CPU
{545B4A6B-8BBA-4FBE-92FC-4AC060122A54}.ReleaseNoNet|Mixed Platforms.Build.0 = ReleaseNoNet|Any CPU
{545B4A6B-8BBA-4FBE-92FC-4AC060122A54}.ReleaseNoNet|Win32.ActiveCfg = ReleaseNoNet|Any CPU
{545B4A6B-8BBA-4FBE-92FC-4AC060122A54}.ReleaseNoNet|x64.ActiveCfg = ReleaseNoNet|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Debug|Mixed Platforms.Deploy.0 = Debug|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Debug|Win32.ActiveCfg = Debug|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Debug|Win32.Build.0 = Debug|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Debug|x64.ActiveCfg = Debug|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Debug|x64.Build.0 = Debug|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Release|Any CPU.Build.0 = Release|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Release|Win32.ActiveCfg = Release|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Release|Win32.Build.0 = Release|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Release|x64.ActiveCfg = Release|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Release|x64.Build.0 = Release|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.ReleaseNoNet|Any CPU.ActiveCfg = ReleaseNoNet|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.ReleaseNoNet|Any CPU.Build.0 = ReleaseNoNet|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.ReleaseNoNet|Mixed Platforms.ActiveCfg = ReleaseNoNet|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.ReleaseNoNet|Mixed Platforms.Build.0 = ReleaseNoNet|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.ReleaseNoNet|Mixed Platforms.Deploy.0 = ReleaseNoNet|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.ReleaseNoNet|Win32.ActiveCfg = ReleaseNoNet|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.ReleaseNoNet|x64.ActiveCfg = ReleaseNoNet|Any CPU
{A57B3ACE-5634-469A-88C4-858BB409F356}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A57B3ACE-5634-469A-88C4-858BB409F356}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A57B3ACE-5634-469A-88C4-858BB409F356}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
@ -58,24 +95,10 @@ Global
{A57B3ACE-5634-469A-88C4-858BB409F356}.Release|x64.Build.0 = Release|Any CPU
{A57B3ACE-5634-469A-88C4-858BB409F356}.ReleaseNoNet|Any CPU.ActiveCfg = Debug|Any CPU
{A57B3ACE-5634-469A-88C4-858BB409F356}.ReleaseNoNet|Any CPU.Build.0 = Debug|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Debug|Win32.ActiveCfg = Debug|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Debug|Win32.Build.0 = Debug|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Debug|x64.ActiveCfg = Debug|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Debug|x64.Build.0 = Debug|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Release|Any CPU.Build.0 = Release|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Release|Win32.ActiveCfg = Release|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Release|Win32.Build.0 = Release|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Release|x64.ActiveCfg = Release|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.Release|x64.Build.0 = Release|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.ReleaseNoNet|Any CPU.ActiveCfg = ReleaseNoNet|Any CPU
{A6CF8A86-37C1-4197-80FE-519DE2C842F5}.ReleaseNoNet|Any CPU.Build.0 = ReleaseNoNet|Any CPU
{A57B3ACE-5634-469A-88C4-858BB409F356}.ReleaseNoNet|Mixed Platforms.ActiveCfg = Release|Any CPU
{A57B3ACE-5634-469A-88C4-858BB409F356}.ReleaseNoNet|Mixed Platforms.Build.0 = Release|Any CPU
{A57B3ACE-5634-469A-88C4-858BB409F356}.ReleaseNoNet|Win32.ActiveCfg = Release|Any CPU
{A57B3ACE-5634-469A-88C4-858BB409F356}.ReleaseNoNet|x64.ActiveCfg = Release|Any CPU
{A8779D4D-7C49-4C2F-82BD-2CDC448391DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A8779D4D-7C49-4C2F-82BD-2CDC448391DA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A8779D4D-7C49-4C2F-82BD-2CDC448391DA}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
@ -94,7 +117,73 @@ Global
{A8779D4D-7C49-4C2F-82BD-2CDC448391DA}.Release|x64.Build.0 = Release|Any CPU
{A8779D4D-7C49-4C2F-82BD-2CDC448391DA}.ReleaseNoNet|Any CPU.ActiveCfg = Debug|Any CPU
{A8779D4D-7C49-4C2F-82BD-2CDC448391DA}.ReleaseNoNet|Any CPU.Build.0 = Debug|Any CPU
{A8779D4D-7C49-4C2F-82BD-2CDC448391DA}.ReleaseNoNet|Mixed Platforms.ActiveCfg = Release|Any CPU
{A8779D4D-7C49-4C2F-82BD-2CDC448391DA}.ReleaseNoNet|Mixed Platforms.Build.0 = Release|Any CPU
{A8779D4D-7C49-4C2F-82BD-2CDC448391DA}.ReleaseNoNet|Win32.ActiveCfg = Release|Any CPU
{A8779D4D-7C49-4C2F-82BD-2CDC448391DA}.ReleaseNoNet|x64.ActiveCfg = Release|Any CPU
{53A9CB7F-6553-4BC0-B56B-9410BB2E59AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{53A9CB7F-6553-4BC0-B56B-9410BB2E59AA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{53A9CB7F-6553-4BC0-B56B-9410BB2E59AA}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{53A9CB7F-6553-4BC0-B56B-9410BB2E59AA}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{53A9CB7F-6553-4BC0-B56B-9410BB2E59AA}.Debug|Win32.ActiveCfg = Debug|Any CPU
{53A9CB7F-6553-4BC0-B56B-9410BB2E59AA}.Debug|x64.ActiveCfg = Debug|Any CPU
{53A9CB7F-6553-4BC0-B56B-9410BB2E59AA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{53A9CB7F-6553-4BC0-B56B-9410BB2E59AA}.Release|Any CPU.Build.0 = Release|Any CPU
{53A9CB7F-6553-4BC0-B56B-9410BB2E59AA}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{53A9CB7F-6553-4BC0-B56B-9410BB2E59AA}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{53A9CB7F-6553-4BC0-B56B-9410BB2E59AA}.Release|Win32.ActiveCfg = Release|Any CPU
{53A9CB7F-6553-4BC0-B56B-9410BB2E59AA}.Release|x64.ActiveCfg = Release|Any CPU
{53A9CB7F-6553-4BC0-B56B-9410BB2E59AA}.ReleaseNoNet|Any CPU.ActiveCfg = Release|Any CPU
{53A9CB7F-6553-4BC0-B56B-9410BB2E59AA}.ReleaseNoNet|Any CPU.Build.0 = Release|Any CPU
{53A9CB7F-6553-4BC0-B56B-9410BB2E59AA}.ReleaseNoNet|Mixed Platforms.ActiveCfg = Release|Any CPU
{53A9CB7F-6553-4BC0-B56B-9410BB2E59AA}.ReleaseNoNet|Mixed Platforms.Build.0 = Release|Any CPU
{53A9CB7F-6553-4BC0-B56B-9410BB2E59AA}.ReleaseNoNet|Win32.ActiveCfg = Release|Any CPU
{53A9CB7F-6553-4BC0-B56B-9410BB2E59AA}.ReleaseNoNet|x64.ActiveCfg = Release|Any CPU
{A5F8FB02-00E0-4335-91EF-AEAA2C2F3C48}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A5F8FB02-00E0-4335-91EF-AEAA2C2F3C48}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A5F8FB02-00E0-4335-91EF-AEAA2C2F3C48}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{A5F8FB02-00E0-4335-91EF-AEAA2C2F3C48}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{A5F8FB02-00E0-4335-91EF-AEAA2C2F3C48}.Debug|Win32.ActiveCfg = Debug|Any CPU
{A5F8FB02-00E0-4335-91EF-AEAA2C2F3C48}.Debug|x64.ActiveCfg = Debug|Any CPU
{A5F8FB02-00E0-4335-91EF-AEAA2C2F3C48}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A5F8FB02-00E0-4335-91EF-AEAA2C2F3C48}.Release|Any CPU.Build.0 = Release|Any CPU
{A5F8FB02-00E0-4335-91EF-AEAA2C2F3C48}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{A5F8FB02-00E0-4335-91EF-AEAA2C2F3C48}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{A5F8FB02-00E0-4335-91EF-AEAA2C2F3C48}.Release|Win32.ActiveCfg = Release|Any CPU
{A5F8FB02-00E0-4335-91EF-AEAA2C2F3C48}.Release|x64.ActiveCfg = Release|Any CPU
{A5F8FB02-00E0-4335-91EF-AEAA2C2F3C48}.ReleaseNoNet|Any CPU.ActiveCfg = Release|Any CPU
{A5F8FB02-00E0-4335-91EF-AEAA2C2F3C48}.ReleaseNoNet|Any CPU.Build.0 = Release|Any CPU
{A5F8FB02-00E0-4335-91EF-AEAA2C2F3C48}.ReleaseNoNet|Mixed Platforms.ActiveCfg = Release|Any CPU
{A5F8FB02-00E0-4335-91EF-AEAA2C2F3C48}.ReleaseNoNet|Mixed Platforms.Build.0 = Release|Any CPU
{A5F8FB02-00E0-4335-91EF-AEAA2C2F3C48}.ReleaseNoNet|Win32.ActiveCfg = Release|Any CPU
{A5F8FB02-00E0-4335-91EF-AEAA2C2F3C48}.ReleaseNoNet|x64.ActiveCfg = Release|Any CPU
{46B769B8-2C58-4138-9CC0-70E3AE3C9A3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{46B769B8-2C58-4138-9CC0-70E3AE3C9A3A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{46B769B8-2C58-4138-9CC0-70E3AE3C9A3A}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
{46B769B8-2C58-4138-9CC0-70E3AE3C9A3A}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{46B769B8-2C58-4138-9CC0-70E3AE3C9A3A}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{46B769B8-2C58-4138-9CC0-70E3AE3C9A3A}.Debug|Mixed Platforms.Deploy.0 = Debug|Any CPU
{46B769B8-2C58-4138-9CC0-70E3AE3C9A3A}.Debug|Win32.ActiveCfg = Debug|Any CPU
{46B769B8-2C58-4138-9CC0-70E3AE3C9A3A}.Debug|x64.ActiveCfg = Debug|Any CPU
{46B769B8-2C58-4138-9CC0-70E3AE3C9A3A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{46B769B8-2C58-4138-9CC0-70E3AE3C9A3A}.Release|Any CPU.Build.0 = Release|Any CPU
{46B769B8-2C58-4138-9CC0-70E3AE3C9A3A}.Release|Any CPU.Deploy.0 = Release|Any CPU
{46B769B8-2C58-4138-9CC0-70E3AE3C9A3A}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{46B769B8-2C58-4138-9CC0-70E3AE3C9A3A}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{46B769B8-2C58-4138-9CC0-70E3AE3C9A3A}.Release|Mixed Platforms.Deploy.0 = Release|Any CPU
{46B769B8-2C58-4138-9CC0-70E3AE3C9A3A}.Release|Win32.ActiveCfg = Release|Any CPU
{46B769B8-2C58-4138-9CC0-70E3AE3C9A3A}.Release|x64.ActiveCfg = Release|Any CPU
{46B769B8-2C58-4138-9CC0-70E3AE3C9A3A}.ReleaseNoNet|Any CPU.ActiveCfg = Release|Any CPU
{46B769B8-2C58-4138-9CC0-70E3AE3C9A3A}.ReleaseNoNet|Any CPU.Build.0 = Release|Any CPU
{46B769B8-2C58-4138-9CC0-70E3AE3C9A3A}.ReleaseNoNet|Any CPU.Deploy.0 = Release|Any CPU
{46B769B8-2C58-4138-9CC0-70E3AE3C9A3A}.ReleaseNoNet|Mixed Platforms.ActiveCfg = Release|Any CPU
{46B769B8-2C58-4138-9CC0-70E3AE3C9A3A}.ReleaseNoNet|Mixed Platforms.Build.0 = Release|Any CPU
{46B769B8-2C58-4138-9CC0-70E3AE3C9A3A}.ReleaseNoNet|Mixed Platforms.Deploy.0 = Release|Any CPU
{46B769B8-2C58-4138-9CC0-70E3AE3C9A3A}.ReleaseNoNet|Win32.ActiveCfg = Release|Any CPU
{46B769B8-2C58-4138-9CC0-70E3AE3C9A3A}.ReleaseNoNet|x64.ActiveCfg = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(MonoDevelopProperties) = preSolution
StartupItem = keepass2android\keepass2android.csproj
@ -102,7 +191,7 @@ Global
$0.DotNetNamingPolicy = $1
$1.DirectoryNamespaceAssociation = None
$1.ResourceNamePolicy = FileFormatDefault
$0.TextStylePolicy = $2
$0.TextStylePolicy = $5
$2.FileWidth = 120
$2.TabsToSpaces = False
$2.inheritsSet = VisualStudio
@ -127,13 +216,11 @@ Global
$3.inheritsSet = Mono
$3.inheritsScope = text/x-csharp
$3.scope = text/x-csharp
$0.TextStylePolicy = $4
$4.FileWidth = 120
$4.TabsToSpaces = False
$4.inheritsSet = VisualStudio
$4.inheritsScope = text/plain
$4.scope = text/plain
$0.TextStylePolicy = $5
$5.inheritsSet = null
$5.scope = application/xml
$0.XmlFormattingPolicy = $6
@ -144,7 +231,4 @@ Global
$7.Text = @/*\nThis file is part of Keepass2Android, Copyright 2013 Philipp Crocoll.\n\n Keepass2Android is free software: you can redistribute it and/or modify\n it under the terms of the GNU General Public License as published by\n the Free Software Foundation, either version 2 of the License, or\n (at your option) any later version.\n\n Keepass2Android is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU General Public License for more details.\n\n You should have received a copy of the GNU General Public License\n along with Keepass2Android. If not, see <http://www.gnu.org/licenses/>.\n */\n
$7.IncludeInNewFiles = True
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,27 @@
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.Content.Res;
using KeePassLib;
using Android.Graphics.Drawables;
namespace keepass2android
{
public interface IDrawableFactory
{
void assignDrawableTo (ImageView iv, Resources res, PwDatabase db, PwIcon icon, PwUuid customIconId);
Drawable getIconDrawable(Resources res, PwDatabase db, PwIcon icon, PwUuid customIconId);
void Clear();
}
}

View File

@ -0,0 +1,35 @@
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.Serialization;
namespace keepass2android
{
public interface IKp2aApp
{
void SetShutdown();
Database GetDb();
void StoreOpenedFileAsRecent(IOConnectionInfo ioc, string keyfile);
Database CreateNewDatabase();
string GetResourceString(UiStringKey stringKey);
bool GetBooleanPreference(PreferenceKey key);
void AskYesNoCancel(UiStringKey titleKey, UiStringKey messageKey,
EventHandler<DialogClickEventArgs> yesHandler,
EventHandler<DialogClickEventArgs> noHandler,
EventHandler<DialogClickEventArgs> cancelHandler,
Context ctx);
}
}

View File

@ -0,0 +1,86 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{53A9CB7F-6553-4BC0-B56B-9410BB2E59AA}</ProjectGuid>
<ProjectTypeGuids>{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>keepass2android</RootNamespace>
<AssemblyName>Kp2aBusinessLogic</AssemblyName>
<FileAlignment>512</FileAlignment>
<AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile>
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Mono.Android" />
<Reference Include="mscorlib" />
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="PreferenceKey.cs" />
<Compile Include="UiStringKey.cs" />
<Compile Include="database\Database.cs" />
<Compile Include="database\edit\ActionOnFinish.cs" />
<Compile Include="database\edit\AddEntry.cs" />
<Compile Include="database\edit\AddGroup.cs" />
<Compile Include="database\edit\CreateDB.cs" />
<Compile Include="database\edit\DeleteEntry.cs" />
<Compile Include="database\edit\DeleteGroup.cs" />
<Compile Include="database\edit\DeleteRunnable.cs" />
<Compile Include="database\edit\FileOnFinish.cs" />
<Compile Include="database\edit\LoadDB.cs" />
<Compile Include="database\edit\OnFinish.cs" />
<Compile Include="database\edit\RunnableOnFinish.cs" />
<Compile Include="database\edit\SaveDB.cs" />
<Compile Include="database\edit\SetPassword.cs" />
<Compile Include="database\edit\UpdateEntry.cs" />
<Compile Include="IKp2aApp.cs" />
<Compile Include="IDrawableFactory.cs" />
<Compile Include="KeyFileException.cs" />
<Compile Include="ProgressTask.cs" />
<Compile Include="PwGroupEqualityFromIdComparer.cs" />
<Compile Include="PwUuidEqualityComparer.cs" />
<Compile Include="Resources\Resource.Designer.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SearchDbHelper.cs" />
<Compile Include="UpdateStatus.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\KeePassLib2Android\KeePassLib2Android.csproj">
<Project>{545b4a6b-8bba-4fbe-92fc-4ac060122a54}</Project>
<Name>KeePassLib2Android</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@ -0,0 +1,20 @@
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;
namespace keepass2android
{
public enum PreferenceKey
{
remember_keyfile,
UseFileTransactions
}
}

View File

@ -35,19 +35,20 @@ namespace keepass2android
private Handler mHandler;
private RunnableOnFinish mTask;
private ProgressDialog mPd;
private IKp2aApp mApp;
public ProgressTask(Context ctx, RunnableOnFinish task, int messageId) {
mCtx = ctx;
public ProgressTask(IKp2aApp app, Context ctx, RunnableOnFinish task, UiStringKey messageKey) {
mTask = task;
mHandler = new Handler();
mApp = app;
// Show process dialog
mPd = new ProgressDialog(mCtx);
mPd.SetTitle(ctx.GetText(Resource.String.progress_title));
mPd.SetMessage(ctx.GetText(messageId));
mPd = new ProgressDialog(ctx);
mPd.SetTitle(mApp.GetResourceString(UiStringKey.progress_title));
mPd.SetMessage(mApp.GetResourceString(messageKey));
// Set code to run when this is finished
mTask.setStatus(new UpdateStatus(ctx, mHandler, mPd));
mTask.setStatus(new UpdateStatus(mApp, mHandler, mPd));
mTask.mFinish = new AfterTask(task.mFinish, mHandler, mPd);
}

View File

@ -0,0 +1,34 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Android.App;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Kp2aBusinessLogic")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Kp2aBusinessLogic")]
[assembly: AssemblyCopyright("Copyright © 2013")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
// Add some common permissions, these can be removed if not needed
[assembly: UsesPermission(Android.Manifest.Permission.Internet)]
[assembly: UsesPermission(Android.Manifest.Permission.WriteExternalStorage)]

View File

@ -31,7 +31,7 @@ using KeePassLib;
namespace keepass2android
{
class PwUuidEqualityComparer: IEqualityComparer<PwUuid>
public class PwUuidEqualityComparer: IEqualityComparer<PwUuid>
{
#region IEqualityComparer implementation
public bool Equals (PwUuid x, PwUuid y)

View File

View File

@ -37,11 +37,11 @@ namespace keepass2android
{
public class SearchDbHelper
{
private IKp2aApp mApp;
private readonly Context mCtx;
public SearchDbHelper(Context ctx) {
mCtx = ctx;
public SearchDbHelper(IKp2aApp app) {
mApp = app;
}
@ -60,7 +60,7 @@ namespace keepass2android
new Regex(sp.SearchString);
}
string strGroupName = mCtx.GetString(Resource.String.search_results) + " (\"" + sp.SearchString + "\")";
string strGroupName = mApp.GetResourceString(UiStringKey.search_results) + " (\"" + sp.SearchString + "\")";
PwGroup pgResults = new PwGroup(true, true, strGroupName, PwIcon.EMailSearch);
pgResults.IsVirtual = true;
@ -87,7 +87,7 @@ namespace keepass2android
new Regex(sp.SearchString);
}
string strGroupName = mCtx.GetString(Resource.String.search_results) + " (\"" + sp.SearchString + "\")";
string strGroupName = mApp.GetResourceString(UiStringKey.search_results) + " (\"" + sp.SearchString + "\")";
PwGroup pgResults = new PwGroup(true, true, strGroupName, PwIcon.EMailSearch);
pgResults.IsVirtual = true;
@ -109,7 +109,7 @@ namespace keepass2android
public PwGroup searchForHost(Database database, String url, bool allowSubdomains)
{
String host = extractHost(url);
string strGroupName = mCtx.GetString(Resource.String.search_results) + " (\"" + host + "\")";
string strGroupName = mApp.GetResourceString(UiStringKey.search_results) + " (\"" + host + "\")";
PwGroup pgResults = new PwGroup(true, true, strGroupName, PwIcon.EMailSearch);
pgResults.IsVirtual = true;
if (String.IsNullOrWhiteSpace(host))

View File

@ -0,0 +1,28 @@
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;
namespace keepass2android
{
public enum UiStringKey
{
AskDeletePermanentlyGroup,
progress_title,
AskDeletePermanentlyEntry,
search_results,
AskDeletePermanently_title,
saving_database,
keyfile_does_not_exist,
RecycleBin,
progress_create,
loading_database
}
}

View File

@ -32,28 +32,28 @@ namespace keepass2android
{
public class UpdateStatus: IStatusLogger {
private ProgressDialog mPD;
private Context mCtx;
IKp2aApp mApp;
private Handler mHandler;
public UpdateStatus() {
}
public UpdateStatus(Context ctx, Handler handler, ProgressDialog pd) {
mCtx = ctx;
public UpdateStatus(IKp2aApp app, Handler handler, ProgressDialog pd) {
mApp = app;
mPD = pd;
mHandler = handler;
}
public void updateMessage(int resId) {
if ( mCtx != null && mPD != null && mHandler != null ) {
mHandler.Post( () => {mPD.SetMessage(mCtx.GetString(resId));});
public void updateMessage(UiStringKey stringKey) {
if ( mApp != null && mPD != null && mHandler != null ) {
mHandler.Post( () => {mPD.SetMessage(mApp.GetResourceString(stringKey));});
}
}
public void updateMessage (String message)
{
if ( mCtx != null && mPD != null && mHandler != null ) {
if ( mApp!= null && mPD != null && mHandler != null ) {
mHandler.Post(() => {mPD.SetMessage(message); } );
}
}

View File

@ -46,11 +46,25 @@ namespace keepass2android
public DateTime mLastChangeDate;
public SearchDbHelper searchHelper;
public DrawableFactory drawFactory = new DrawableFactory();
public IDrawableFactory drawableFactory;
IKp2aApp app;
public Database(IDrawableFactory drawableFactory, IKp2aApp app)
{
this.drawableFactory = drawableFactory;
this.app = app;
}
private bool loaded = false;
private bool mReloadRequested = false;
private bool mReloadRequested = false;
public bool ReloadRequested
{
get { return mReloadRequested; }
set { mReloadRequested = value; }
}
public bool Loaded {
get { return loaded;}
@ -84,41 +98,8 @@ namespace keepass2android
return System.IO.File.GetLastWriteTimeUtc(mIoc.Path) > mLastChangeDate;
}
public void CheckForOpenFileChanged(Activity activity)
{
if (DidOpenFileChange())
{
if (mReloadRequested)
{
activity.SetResult(KeePass.EXIT_RELOAD_DB);
activity.Finish();
}
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.SetTitle(activity.GetString(Resource.String.AskReloadFile_title));
builder.SetMessage(activity.GetString(Resource.String.AskReloadFile));
builder.SetPositiveButton(activity.GetString(Android.Resource.String.Yes), new EventHandler<DialogClickEventArgs>((dlgSender, dlgEvt) =>
{
mReloadRequested = true;
activity.SetResult(KeePass.EXIT_RELOAD_DB);
activity.Finish();
}));
builder.SetNegativeButton(activity.GetString(Android.Resource.String.No), new EventHandler<DialogClickEventArgs>((dlgSender, dlgEvt) =>
{
}));
Dialog dialog = builder.Create();
dialog.Show();
}
}
public void LoadData(Context ctx, IOConnectionInfo iocInfo, String password, String keyfile, UpdateStatus status)
public void LoadData(IKp2aApp app, IOConnectionInfo iocInfo, String password, String keyfile, UpdateStatus status)
{
mIoc = iocInfo;
@ -154,7 +135,7 @@ namespace keepass2android
Loaded = true;
pm = pwDatabase;
searchHelper = new SearchDbHelper(ctx);
searchHelper = new SearchDbHelper(app);
}
bool quickUnlockEnabled = false;
@ -207,8 +188,8 @@ namespace keepass2android
public void SaveData(Context ctx) {
ISharedPreferences prefs = Android.Preferences.PreferenceManager.GetDefaultSharedPreferences(ctx);
pm.UseFileTransactions = prefs.GetBoolean(ctx.GetString(Resource.String.UseFileTransactions_key), true);
pm.UseFileTransactions = app.GetBooleanPreference(PreferenceKey.UseFileTransactions);
pm.Save(null);
}
@ -257,7 +238,7 @@ namespace keepass2android
groups.Clear();
entries.Clear();
dirty.Clear();
drawFactory.Clear();
drawableFactory.Clear();
root = null;
pm = null;

View File

@ -39,18 +39,18 @@ namespace keepass2android
private IOConnectionInfo mIoc;
private bool mDontSave;
private Context mCtx;
private IKp2aApp mApp;
public CreateDB(Context ctx, IOConnectionInfo ioc, OnFinish finish, bool dontSave): base(finish) {
public CreateDB(IKp2aApp app, Context ctx, IOConnectionInfo ioc, OnFinish finish, bool dontSave): base(finish) {
mCtx = ctx;
mIoc = ioc;
mDontSave = dontSave;
mApp = app;
}
public override void run() {
// Create new database record
Database db = new Database();
App.setDB(db);
Database db = mApp.CreateNewDatabase();
db.pm = new KeePassLib.PwDatabase();
//Key will be changed/created immediately after creation:
@ -66,7 +66,7 @@ namespace keepass2android
db.root = db.pm.RootGroup;
db.mIoc = mIoc;
db.Loaded = true;
db.searchHelper = new SearchDbHelper(mCtx);
db.searchHelper = new SearchDbHelper(mApp);
// Add a couple default groups
AddGroup internet = AddGroup.getInstance(mCtx, db, "Internet", 1, db.pm.RootGroup, null, true);

View File

@ -31,12 +31,12 @@ using KeePassLib;
namespace keepass2android
{
public class DeleteEntry : DeleteRunnable {
private PwEntry mEntry;
public DeleteEntry(Context ctx, Database db, PwEntry entry, OnFinish finish):base(finish) {
private PwEntry mEntry;
public DeleteEntry(Context ctx, IKp2aApp app, PwEntry entry, OnFinish finish):base(finish, app) {
mCtx = ctx;
mDb = db;
mDb = app.GetDb();
mEntry = entry;
}
@ -49,11 +49,11 @@ namespace keepass2android
}
}
protected override int QuestionsResourceId
protected override UiStringKey QuestionsResourceId
{
get
{
return Resource.String.AskDeletePermanentlyEntry;
return UiStringKey.AskDeletePermanentlyEntry;
}
}
@ -86,7 +86,7 @@ namespace keepass2android
}
} else {
// Let's not bother recovering from a failure to save a deleted entry. It is too much work.
App.setShutdown();
mApp.SetShutdown();
}
}, this.mFinish);
@ -109,7 +109,7 @@ namespace keepass2android
mDb.dirty.Add(pgRecycleBin);
} else {
// Let's not bother recovering from a failure to save a deleted entry. It is too much work.
App.setShutdown();
mApp.SetShutdown();
}
}, this.mFinish);

View File

@ -34,28 +34,33 @@ namespace keepass2android
public class DeleteGroup : DeleteRunnable {
private PwGroup mGroup;
private GroupBaseActivity mAct;
private Activity mAct;
protected bool mDontSave;
public DeleteGroup(Context ctx, Database db, PwGroup group, GroupBaseActivity act, OnFinish finish):base(finish) {
setMembers(ctx, db, group, act, false);
public DeleteGroup(Context ctx, IKp2aApp app, PwGroup group, Activity act, OnFinish finish)
: base(finish, app)
{
setMembers(ctx, app, group, act, false);
}
public DeleteGroup(Context ctx, Database db, PwGroup group, GroupBaseActivity act, OnFinish finish, bool dontSave):base(finish) {
/*
public DeleteGroup(Context ctx, Database db, PwGroup group, Activity act, OnFinish finish, bool dontSave)
: base(finish)
{
setMembers(ctx, db, group, act, dontSave);
}
public DeleteGroup(Context ctx, Database db, PwGroup group, OnFinish finish, bool dontSave):base(finish) {
setMembers(ctx, db, group, null, dontSave);
}
private void setMembers(Context ctx, Database db, PwGroup group, GroupBaseActivity act, bool dontSave) {
base.setMembers(ctx, db);
*/
private void setMembers(Context ctx, IKp2aApp app, PwGroup group, Activity act, bool dontSave)
{
base.setMembers(ctx, app.GetDb());
mGroup = group;
mAct = act;
mDontSave = dontSave;
}
public override bool CanRecycle
@ -66,11 +71,11 @@ namespace keepass2android
}
}
protected override int QuestionsResourceId
protected override UiStringKey QuestionsResourceId
{
get
{
return Resource.String.AskDeletePermanentlyGroup;
return UiStringKey.AskDeletePermanentlyGroup;
}
}
@ -92,7 +97,7 @@ namespace keepass2android
PwDeletedObject pdo = new PwDeletedObject(pg.Uuid, DateTime.Now);
pd.DeletedObjects.Add(pdo);
mFinish = new AfterDeletePermanently(mFinish, mDb, mGroup);
mFinish = new AfterDeletePermanently(mFinish, mApp, mGroup);
}
else // Recycle
{
@ -113,7 +118,7 @@ namespace keepass2android
mDb.dirty.Add(pgParent);
} else {
// Let's not bother recovering from a failure to save a deleted group. It is too much work.
App.setShutdown();
mApp.SetShutdown();
}
}, this.mFinish);
}
@ -126,31 +131,31 @@ namespace keepass2android
private class AfterDeletePermanently : OnFinish {
Database mDb;
IKp2aApp mApp;
PwGroup mGroup;
public AfterDeletePermanently(OnFinish finish, Database db, PwGroup group):base(finish) {
this.mDb = db;
public AfterDeletePermanently(OnFinish finish, IKp2aApp app, PwGroup group):base(finish) {
this.mApp = app;
this.mGroup = group;
}
public override void run() {
if ( mSuccess ) {
// Remove from group global
mDb.groups.Remove(mGroup.Uuid);
mApp.GetDb().groups.Remove(mGroup.Uuid);
// Remove group from the dirty global (if it is present), not a big deal if this fails (doesn't throw)
mDb.dirty.Remove(mGroup);
mApp.GetDb().dirty.Remove(mGroup);
// Mark parent dirty
PwGroup parent = mGroup.ParentGroup;
if ( parent != null ) {
mDb.dirty.Add(parent);
mApp.GetDb().dirty.Add(parent);
}
} else {
// Let's not bother recovering from a failure to save a deleted group. It is too much work.
App.setShutdown();
mApp.SetShutdown();
}
base.run();

View File

@ -1,146 +1,142 @@
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;
namespace keepass2android
{
public abstract class DeleteRunnable : RunnableOnFinish
{
public DeleteRunnable(OnFinish finish):base(finish)
{
}
protected Database mDb;
protected Context mCtx;
protected void setMembers(Context ctx, Database db)
{
mCtx = ctx;
mDb = db;
}
private bool mDeletePermanently = true;
public bool DeletePermanently
{
get
{
return mDeletePermanently;
}
set
{
mDeletePermanently = value;
}
}
public abstract bool CanRecycle
{
get;
}
protected bool CanRecycleGroup(PwGroup pgParent)
{
bool bShiftPressed = false;
PwDatabase pd = mDb.pm;
PwGroup pgRecycleBin = pd.RootGroup.FindGroup(pd.RecycleBinUuid, true);
bool bPermanent = false;
if (pgParent != null)
{
if (pd.RecycleBinEnabled == false)
bPermanent = true;
else if (bShiftPressed)
bPermanent = true;
else if (pgRecycleBin == null)
{
} // Recycle
else if (pgParent == pgRecycleBin)
bPermanent = true;
else if (pgParent.IsContainedIn(pgRecycleBin))
bPermanent = true;
}
return !bPermanent;
}
protected void EnsureRecycleBin(ref PwGroup pgRecycleBin,
ref bool bGroupListUpdateRequired)
{
if ((mDb == null) || (mDb.pm == null)) { return; }
if(pgRecycleBin == mDb.pm.RootGroup)
{
pgRecycleBin = null;
}
if(pgRecycleBin == null)
{
pgRecycleBin = new PwGroup(true, true, mCtx.GetString(Resource.String.RecycleBin),
PwIcon.TrashBin);
pgRecycleBin.EnableAutoType = false;
pgRecycleBin.EnableSearching = false;
pgRecycleBin.IsExpanded = false;
mDb.pm.RootGroup.AddGroup(pgRecycleBin, true);
mDb.pm.RecycleBinUuid = pgRecycleBin.Uuid;
bGroupListUpdateRequired = true;
}
else { System.Diagnostics.Debug.Assert(pgRecycleBin.Uuid.EqualsValue(mDb.pm.RecycleBinUuid)); }
}
protected abstract int QuestionsResourceId
{
get;
}
public void start()
{
if (CanRecycle)
{
AlertDialog.Builder builder = new AlertDialog.Builder(mCtx);
builder.SetTitle(mCtx.GetString(Resource.String.AskDeletePermanently_title));
builder.SetMessage(mCtx.GetString(QuestionsResourceId));
builder.SetPositiveButton(Resource.String.yes, new EventHandler<DialogClickEventArgs>((dlgSender, dlgEvt) =>
{
DeletePermanently = true;
ProgressTask pt = new ProgressTask(mCtx, this, Resource.String.saving_database);
pt.run();
}));
builder.SetNegativeButton(Resource.String.no, new EventHandler<DialogClickEventArgs>((dlgSender, dlgEvt) => {
DeletePermanently = false;
ProgressTask pt = new ProgressTask(mCtx, this, Resource.String.saving_database);
pt.run();
}));
builder.SetNeutralButton(mCtx.GetString(Android.Resource.String.Cancel),
new EventHandler<DialogClickEventArgs>((dlgSender, dlgEvt) => {}));
Dialog dialog = builder.Create();
dialog.Show();
} else
{
ProgressTask pt = new ProgressTask(mCtx, this, Resource.String.saving_database);
pt.run();
}
}
}
}
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;
namespace keepass2android
{
public abstract class DeleteRunnable : RunnableOnFinish
{
public DeleteRunnable(OnFinish finish, IKp2aApp app):base(finish)
{
mApp = app;
}
protected IKp2aApp mApp;
protected Database mDb;
protected Context mCtx;
protected void setMembers(Context ctx, Database db)
{
mCtx = ctx;
mDb = db;
}
private bool mDeletePermanently = true;
public bool DeletePermanently
{
get
{
return mDeletePermanently;
}
set
{
mDeletePermanently = value;
}
}
public abstract bool CanRecycle
{
get;
}
protected bool CanRecycleGroup(PwGroup pgParent)
{
bool bShiftPressed = false;
PwDatabase pd = mDb.pm;
PwGroup pgRecycleBin = pd.RootGroup.FindGroup(pd.RecycleBinUuid, true);
bool bPermanent = false;
if (pgParent != null)
{
if (pd.RecycleBinEnabled == false)
bPermanent = true;
else if (bShiftPressed)
bPermanent = true;
else if (pgRecycleBin == null)
{
} // Recycle
else if (pgParent == pgRecycleBin)
bPermanent = true;
else if (pgParent.IsContainedIn(pgRecycleBin))
bPermanent = true;
}
return !bPermanent;
}
protected void EnsureRecycleBin(ref PwGroup pgRecycleBin,
ref bool bGroupListUpdateRequired)
{
if ((mDb == null) || (mDb.pm == null)) { return; }
if(pgRecycleBin == mDb.pm.RootGroup)
{
pgRecycleBin = null;
}
if(pgRecycleBin == null)
{
pgRecycleBin = new PwGroup(true, true, mApp.GetResourceString(UiStringKey.RecycleBin),
PwIcon.TrashBin);
pgRecycleBin.EnableAutoType = false;
pgRecycleBin.EnableSearching = false;
pgRecycleBin.IsExpanded = false;
mDb.pm.RootGroup.AddGroup(pgRecycleBin, true);
mDb.pm.RecycleBinUuid = pgRecycleBin.Uuid;
bGroupListUpdateRequired = true;
}
else { System.Diagnostics.Debug.Assert(pgRecycleBin.Uuid.EqualsValue(mDb.pm.RecycleBinUuid)); }
}
protected abstract UiStringKey QuestionsResourceId
{
get;
}
public void start()
{
if (CanRecycle)
{
mApp.AskYesNoCancel(UiStringKey.AskDeletePermanently_title,
QuestionsResourceId,
new EventHandler<DialogClickEventArgs>((dlgSender, dlgEvt) =>
{
DeletePermanently = true;
ProgressTask pt = new ProgressTask(mApp, mCtx, this, UiStringKey.saving_database);
pt.run();
}),
new EventHandler<DialogClickEventArgs>((dlgSender, dlgEvt) => {
DeletePermanently = false;
ProgressTask pt = new ProgressTask(mApp, mCtx, this, UiStringKey.saving_database);
pt.run();
}),
new EventHandler<DialogClickEventArgs>((dlgSender, dlgEvt) => {}),
mCtx);
} else
{
ProgressTask pt = new ProgressTask(mApp, mCtx, this, UiStringKey.saving_database);
pt.run();
}
}
}
}

View File

@ -35,32 +35,32 @@ namespace keepass2android
private IOConnectionInfo mIoc;
private String mPass;
private String mKey;
private Database mDb;
private IKp2aApp mApp;
private Context mCtx;
private bool mRememberKeyfile;
public LoadDB(Database db, Context ctx, IOConnectionInfo ioc, String pass, String key, OnFinish finish): base(finish)
public LoadDB(IKp2aApp app, Context ctx, IOConnectionInfo ioc, String pass, String key, OnFinish finish): base(finish)
{
mDb = db;
mApp = app;
mCtx = ctx;
mIoc = ioc;
mPass = pass;
mKey = key;
ISharedPreferences prefs = PreferenceManager.GetDefaultSharedPreferences(ctx);
mRememberKeyfile = prefs.GetBoolean(ctx.GetString(Resource.String.keyfile_key), ctx.Resources.GetBoolean(Resource.Boolean.keyfile_default));
mRememberKeyfile = app.GetBooleanPreference(PreferenceKey.remember_keyfile);
}
public override void run ()
{
try {
mDb.LoadData (mCtx, mIoc, mPass, mKey, mStatus);
mApp.GetDb().LoadData (mApp, mIoc, mPass, mKey, mStatus);
saveFileData (mIoc, mKey);
} catch (KeyFileException) {
finish(false, /*TODO Localize: use Keepass error text KPRes.KeyFileError (including "or invalid format")*/ mCtx.GetString(Resource.String.keyfile_does_not_exist));
finish(false, /*TODO Localize: use Keepass error text KPRes.KeyFileError (including "or invalid format")*/ mApp.GetResourceString(UiStringKey.keyfile_does_not_exist));
}
catch (Exception e) {
finish(false, "An error occured: " + e.Message);
@ -102,13 +102,12 @@ namespace keepass2android
}
private void saveFileData(IOConnectionInfo ioc, String key) {
FileDbHelper db = App.fileDbHelper;
if ( ! mRememberKeyfile ) {
key = "";
}
db.createFile(ioc, key);
if (!mRememberKeyfile)
{
key = "";
}
mApp.StoreOpenedFileAsRecent(ioc, key);
}

View File

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{46B769B8-2C58-4138-9CC0-70E3AE3C9A3A}</ProjectGuid>
<ProjectTypeGuids>{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Kp2aUnitTests</RootNamespace>
<AssemblyName>Kp2aUnitTests</AssemblyName>
<FileAlignment>512</FileAlignment>
<AndroidApplication>true</AndroidApplication>
<AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile>
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AndroidUseSharedRuntime>True</AndroidUseSharedRuntime>
<AndroidLinkMode>None</AndroidLinkMode>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<AndroidUseSharedRuntime>False</AndroidUseSharedRuntime>
<AndroidLinkMode>SdkOnly</AndroidLinkMode>
</PropertyGroup>
<ItemGroup>
<Reference Include="Mono.Android" />
<Reference Include="mscorlib" />
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="MainActivity.cs" />
<Compile Include="Resources\Resource.Designer.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<None Include="Resources\AboutResources.txt" />
<None Include="Assets\AboutAssets.txt" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Layout\Main.axml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Values\Strings.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\Drawable\Icon.png" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\monodroid-unittesting\MonoDroidUnitTesting\MonoDroidUnitTesting.csproj">
<Project>{a5f8fb02-00e0-4335-91ef-aeaa2c2f3c48}</Project>
<Name>MonoDroidUnitTesting</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@ -0,0 +1,27 @@
using System;
using Android.App;
using Android.Content;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using Android.OS;
using MonoDroidUnitTesting;
using System.Reflection;
namespace Kp2aUnitTests
{
[Activity(Label = "MonoDroidUnit", MainLauncher = true, Icon = "@drawable/icon")]
public class MainActivity : GuiTestRunnerActivity
{
protected override TestRunner CreateTestRunner()
{
TestRunner runner = new TestRunner();
// Run all tests from this assembly
runner.AddTests(Assembly.GetExecutingAssembly());
return runner;
}
}
}

View File

@ -0,0 +1,34 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Android.App;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Kp2aUnitTests")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Kp2aUnitTests")]
[assembly: AssemblyCopyright("Copyright © 2013")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]
// Add some common permissions, these can be removed if not needed
[assembly: UsesPermission(Android.Manifest.Permission.Internet)]
[assembly: UsesPermission(Android.Manifest.Permission.WriteExternalStorage)]

View File

@ -0,0 +1,50 @@
Images, layout descriptions, binary blobs and string dictionaries can be included
in your application as resource files. Various Android APIs are designed to
operate on the resource IDs instead of dealing with images, strings or binary blobs
directly.
For example, a sample Android app that contains a user interface layout (main.xml),
an internationalization string table (strings.xml) and some icons (drawable-XXX/icon.png)
would keep its resources in the "Resources" directory of the application:
Resources/
drawable-hdpi/
icon.png
drawable-ldpi/
icon.png
drawable-mdpi/
icon.png
layout/
main.xml
values/
strings.xml
In order to get the build system to recognize Android resources, set the build action to
"AndroidResource". The native Android APIs do not operate directly with filenames, but
instead operate on resource IDs. When you compile an Android application that uses resources,
the build system will package the resources for distribution and generate a class called
"Resource" that contains the tokens for each one of the resources included. For example,
for the above Resources layout, this is what the Resource class would expose:
public class Resource {
public class drawable {
public const int icon = 0x123;
}
public class layout {
public const int main = 0x456;
}
public class strings {
public const int first_string = 0xabc;
public const int second_string = 0xbcd;
}
}
You would then use R.drawable.icon to reference the drawable/icon.png file, or Resource.layout.main
to reference the layout/main.xml file, or Resource.strings.first_string to reference the first
string in the dictionary file values/strings.xml.

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<Button
android:id="@+id/MyButton"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="@string/Hello"
/>
</LinearLayout>

View File

@ -0,0 +1,112 @@
#pragma warning disable 1591
//------------------------------------------------------------------------------
// <auto-generated>
// Dieser Code wurde von einem Tool generiert.
// Laufzeitversion:4.0.30319.18046
//
// Änderungen an dieser Datei können falsches Verhalten verursachen und gehen verloren, wenn
// der Code erneut generiert wird.
// </auto-generated>
//------------------------------------------------------------------------------
[assembly: global::Android.Runtime.ResourceDesignerAttribute("Kp2aUnitTests.Resource", IsApplication=true)]
namespace Kp2aUnitTests
{
[System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")]
public partial class Resource
{
static Resource()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
}
public static void UpdateIdValues()
{
}
public partial class Attribute
{
static Attribute()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
}
private Attribute()
{
}
}
public partial class Drawable
{
// aapt resource value: 0x7f020000
public const int Icon = 2130837504;
static Drawable()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
}
private Drawable()
{
}
}
public partial class Id
{
// aapt resource value: 0x7f050000
public const int MyButton = 2131034112;
static Id()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
}
private Id()
{
}
}
public partial class Layout
{
// aapt resource value: 0x7f030000
public const int Main = 2130903040;
static Layout()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
}
private Layout()
{
}
}
public partial class String
{
// aapt resource value: 0x7f040001
public const int ApplicationName = 2130968577;
// aapt resource value: 0x7f040000
public const int Hello = 2130968576;
static String()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
}
private String()
{
}
}
}
}
#pragma warning restore 1591

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="Hello">Hello World, Click Me!</string>
<string name="ApplicationName">Kp2aUnitTests</string>
</resources>

View File

@ -101,7 +101,7 @@ namespace keepass2android
Context appCtx = ApplicationContext;
Database db = App.getDB();
Database db = App.Kp2a.GetDb();
// Likely the app has been killed exit the activity
if (! db.Loaded)
{
@ -135,8 +135,8 @@ namespace keepass2android
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);
UpdateEntry update = new UpdateEntry(this, App.Kp2a.GetDb(), backupEntry, mEntry, null);
ProgressTask pt = new ProgressTask(App.Kp2a, this, update, UiStringKey.saving_database);
pt.run();
}
fillData(false);
@ -152,9 +152,9 @@ namespace keepass2android
Android.Util.Log.Debug("DEBUG", "Requesting copy to clipboard for Uuid=" + mEntry.Uuid.ToHexString());
/*foreach (PwUuid key in App.getDB().entries.Keys)
/*foreach (PwUuid key in App.Kp2a.GetDb().entries.Keys)
{
Android.Util.Log.Debug("DEBUG",key.ToHexString() + " -> " + App.getDB().entries[key].Uuid.ToHexString());
Android.Util.Log.Debug("DEBUG",key.ToHexString() + " -> " + App.Kp2a.GetDb().entries[key].Uuid.ToHexString());
}*/
if (closeAfterCreate)
@ -163,20 +163,6 @@ namespace keepass2android
Finish();
}
}
private class AfterSave : OnFinish {
public AfterSave(Handler handler):base(handler) {
}
public override void run() {
base.run();
}
};
private String getDateTime(System.DateTime dt) {
@ -402,7 +388,7 @@ namespace keepass2android
ImageView iv = (ImageView)FindViewById(Resource.Id.entry_icon);
if (iv != null)
{
App.getDB().drawFactory.assignDrawableTo(iv, Resources, App.getDB().pm, mEntry.IconId, mEntry.CustomIconUuid);
App.Kp2a.GetDb().drawableFactory.assignDrawableTo(iv, Resources, App.Kp2a.GetDb().pm, mEntry.IconId, mEntry.CustomIconUuid);
}
//populateText(Resource.Id.entry_title, mEntry.Strings.ReadSafe(PwDefs.TitleField));
@ -634,7 +620,7 @@ namespace keepass2android
}
return true;
case Resource.Id.menu_lock:
App.setShutdown();
App.Kp2a.SetShutdown();
SetResult(KeePass.EXIT_LOCK);
Finish();
return true;

View File

@ -50,7 +50,7 @@ namespace keepass2android
EntryEditActivityState State
{
get { return App.entryEditActivityState; }
get { return App.Kp2a.entryEditActivityState; }
}
public static void Launch(Activity act, PwEntry pw, AppTask appTask) {
@ -98,7 +98,7 @@ namespace keepass2android
mCloseForReload = false;
// Likely the app has been killed exit the activity
Database db = App.getDB();
Database db = App.Kp2a.GetDb();
if (! db.Open)
{
Finish();
@ -113,7 +113,7 @@ namespace keepass2android
} else
{
App.entryEditActivityState = new EntryEditActivityState();
App.Kp2a.entryEditActivityState = new EntryEditActivityState();
ISharedPreferences prefs = PreferenceManager.GetDefaultSharedPreferences(this);
State.mShowPassword = ! prefs.GetBoolean(GetString(Resource.String.maskpass_key), Resources.GetBoolean(Resource.Boolean.maskpass_default));
@ -293,7 +293,7 @@ namespace keepass2android
void SaveEntry()
{
Database db = App.getDB();
Database db = App.Kp2a.GetDb();
EntryEditActivity act = this;
if (!validateBeforeSaving())
@ -392,11 +392,11 @@ namespace keepass2android
},closeOrShowError);
if ( State.mIsNew ) {
runnable = AddEntry.getInstance(this, App.getDB(), newEntry, State.parentGroup, afterAddEntry);
runnable = AddEntry.getInstance(this, App.Kp2a.GetDb(), newEntry, State.parentGroup, afterAddEntry);
} else {
runnable = new UpdateEntry(this, App.getDB(), initialEntry, newEntry, closeOrShowError);
runnable = new UpdateEntry(this, App.Kp2a.GetDb(), initialEntry, newEntry, closeOrShowError);
}
ProgressTask pt = new ProgressTask(act, runnable, Resource.String.saving_database);
ProgressTask pt = new ProgressTask(App.Kp2a, act, runnable, UiStringKey.saving_database);
pt.run();
@ -404,7 +404,7 @@ namespace keepass2android
void UpdateEntryFromUi(PwEntry entry)
{
Database db = App.getDB();
Database db = App.Kp2a.GetDb();
EntryEditActivity act = this;
entry.Strings.Set(PwDefs.TitleField, new ProtectedString(db.pm.MemoryProtection.ProtectTitle,
@ -625,7 +625,7 @@ namespace keepass2android
String generatedPassword = data.GetStringExtra("keepass2android.password.generated_password");
byte[] password = StrUtil.Utf8.GetBytes(generatedPassword);
State.mEntry.Strings.Set(PwDefs.PasswordField, new ProtectedString(App.getDB().pm.MemoryProtection.ProtectPassword,
State.mEntry.Strings.Set(PwDefs.PasswordField, new ProtectedString(App.Kp2a.GetDb().pm.MemoryProtection.ProtectPassword,
password));
MemUtil.ZeroByteArray(password);
@ -813,7 +813,7 @@ namespace keepass2android
private void fillData() {
ImageButton currIconButton = (ImageButton) FindViewById(Resource.Id.icon_button);
App.getDB().drawFactory.assignDrawableTo(currIconButton, Resources, App.getDB().pm, State.mEntry.IconId, State.mEntry.CustomIconUuid);
App.Kp2a.GetDb().drawableFactory.assignDrawableTo(currIconButton, Resources, App.Kp2a.GetDb().pm, State.mEntry.IconId, State.mEntry.CustomIconUuid);
populateText(Resource.Id.entry_title, State.mEntry.Strings.ReadSafe (PwDefs.TitleField));
populateText(Resource.Id.entry_user_name, State.mEntry.Strings.ReadSafe (PwDefs.UserNameField));

View File

@ -54,7 +54,7 @@ namespace keepass2android
Intent i;
// Need to use PwDatabase since group may be null
PwDatabase db = App.getDB ().pm;
PwDatabase db = App.Kp2a.GetDb().pm;
if (db == null) {
// Reached if db is null
@ -85,7 +85,7 @@ namespace keepass2android
protected void setupButtons()
{
addGroupEnabled = true;
addEntryEnabled = !mGroup.Uuid.EqualsValue(App.getDB().root.Uuid);
addEntryEnabled = !mGroup.Uuid.EqualsValue(App.Kp2a.GetDb().root.Uuid);
}
protected override void OnCreate (Bundle savedInstanceState)
@ -103,7 +103,7 @@ namespace keepass2android
PwUuid id = retrieveGroupId (intent);
Database db = App.getDB ();
Database db = App.Kp2a.GetDb();
if (id == null) {
mGroup = db.root;
} else {
@ -184,8 +184,8 @@ namespace keepass2android
int GroupIconID = data.Extras.GetInt(GroupEditActivity.KEY_ICON_ID);
GroupBaseActivity act = this;
Handler handler = new Handler();
AddGroup task = AddGroup.getInstance(this, App.getDB(), GroupName, GroupIconID, mGroup, new RefreshTask(handler, this), false);
ProgressTask pt = new ProgressTask(act, task, Resource.String.saving_database);
AddGroup task = AddGroup.getInstance(this, App.Kp2a.GetDb(), GroupName, GroupIconID, mGroup, new RefreshTask(handler, this), false);
ProgressTask pt = new ProgressTask(App.Kp2a, act, task, UiStringKey.saving_database);
pt.run();
break;

View File

@ -91,7 +91,7 @@ namespace keepass2android
}
public void refreshIfDirty() {
Database db = App.getDB();
Database db = App.Kp2a.GetDb();
if ( db.dirty.Contains(mGroup) ) {
db.dirty.Remove(mGroup);
BaseAdapter adapter = (BaseAdapter) ListAdapter;
@ -115,7 +115,7 @@ namespace keepass2android
mAppTask = AppTask.GetTaskInOnCreate(savedInstanceState, Intent);
// Likely the app has been killed exit the activity
if ( ! App.getDB().Loaded ) {
if ( ! App.Kp2a.GetDb().Loaded ) {
Finish();
return;
}
@ -183,7 +183,7 @@ namespace keepass2android
protected void setGroupIcon() {
if (mGroup != null) {
Drawable drawable = App.getDB().drawFactory.getIconDrawable(Resources, App.getDB().pm, mGroup.IconId, mGroup.CustomIconUuid);
Drawable drawable = App.Kp2a.GetDb().drawableFactory.getIconDrawable(Resources, App.Kp2a.GetDb().pm, mGroup.IconId, mGroup.CustomIconUuid);
ImageView iv = (ImageView) FindViewById(Resource.Id.icon);
if (iv != null)
iv.SetImageDrawable(drawable);
@ -237,7 +237,7 @@ namespace keepass2android
return true;
case Resource.Id.menu_lock:
App.setShutdown();
App.Kp2a.SetShutdown();
SetResult(KeePass.EXIT_LOCK);
Finish();
return true;
@ -303,7 +303,7 @@ namespace keepass2android
ActivityCompat.invalidateOptionsMenu(this);
// Mark all groups as dirty now to refresh them on load
Database db = App.getDB();
Database db = App.Kp2a.GetDb();
db.markAllGroupsAsDirty();
// We'll manually refresh this group so we can remove it
db.dirty.Remove(mGroup);
@ -349,7 +349,7 @@ namespace keepass2android
Toast.MakeText(act, "Unrecoverable error: " + mMessage, ToastLength.Long).Show();
});
App.setShutdown();
App.Kp2a.SetShutdown();
act.Finish();
}
}

View File

@ -37,7 +37,7 @@ namespace keepass2android
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
mIoc = App.getDB().mIoc;
mIoc = App.Kp2a.GetDb().mIoc;
}
@ -48,9 +48,11 @@ namespace keepass2android
if (TimeoutHelper.checkShutdown(this, mIoc))
return;
App.getDB().CheckForOpenFileChanged(this);
App.Kp2a.CheckForOpenFileChanged(this);
}
}

View File

@ -42,7 +42,7 @@ namespace keepass2android
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
mIoc = App.getDB().mIoc;
mIoc = App.Kp2a.GetDb().mIoc;
}
public LockCloseListActivity (IntPtr javaReference, JniHandleOwnership transfer)
@ -58,7 +58,7 @@ namespace keepass2android
if (TimeoutHelper.checkShutdown(this, mIoc))
return;
App.getDB().CheckForOpenFileChanged(this);
App.Kp2a.CheckForOpenFileChanged(this);
}
}

View File

@ -39,7 +39,7 @@ namespace keepass2android
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
mIoc = App.getDB().mIoc;
mIoc = App.Kp2a.GetDb().mIoc;
}
protected override void OnResume() {

View File

@ -134,7 +134,7 @@ namespace keepass2android
void unloadDatabase()
{
App.getDB().Clear();
App.Kp2a.GetDb().Clear();
StopService(new Intent(this, typeof(QuickUnlockForegroundService)));
}
@ -142,8 +142,8 @@ namespace keepass2android
{
SetResult(KeePass.EXIT_LOCK);
setEditText(Resource.Id.password, "");
if (App.getDB().QuickUnlockEnabled)
App.getDB().Locked = true;
if (App.Kp2a.GetDb().QuickUnlockEnabled)
App.Kp2a.GetDb().Locked = true;
else
{
unloadDatabase();
@ -159,18 +159,18 @@ namespace keepass2android
bool tryStartQuickUnlock()
{
if (!App.getDB().QuickUnlockEnabled)
if (!App.Kp2a.GetDb().QuickUnlockEnabled)
return false;
if (App.getDB().pm.MasterKey.ContainsType(typeof(KcpPassword)) == false)
if (App.Kp2a.GetDb().pm.MasterKey.ContainsType(typeof(KcpPassword)) == false)
return false;
KcpPassword kcpPassword = (KcpPassword)App.getDB().pm.MasterKey.GetUserKey(typeof(KcpPassword));
KcpPassword kcpPassword = (KcpPassword)App.Kp2a.GetDb().pm.MasterKey.GetUserKey(typeof(KcpPassword));
String password = kcpPassword.Password.ReadString();
if (password.Length == 0)
return false;
App.getDB().Locked = true;
App.Kp2a.GetDb().Locked = true;
Intent i = new Intent(this, typeof(QuickUnlock));
PutIoConnectionToIntent(mIoConnection, i);
@ -181,7 +181,7 @@ namespace keepass2android
public void StartQuickUnlockForegroundService()
{
if (App.getDB().QuickUnlockEnabled)
if (App.Kp2a.GetDb().QuickUnlockEnabled)
{
StartService(new Intent(this, typeof(QuickUnlockForegroundService)));
}
@ -235,26 +235,26 @@ namespace keepass2android
Finish();
break;
case KeePass.EXIT_QUICK_UNLOCK:
App.getDB().Locked = false;
App.Kp2a.GetDb().Locked = false;
LaunchNextActivity();
break;
case KeePass.EXIT_RELOAD_DB:
//if the activity was killed, fill password/keyfile so the user can directly hit load again
if (App.getDB().Loaded)
if (App.Kp2a.GetDb().Loaded)
{
if (App.getDB().pm.MasterKey.ContainsType(typeof(KcpPassword)))
if (App.Kp2a.GetDb().pm.MasterKey.ContainsType(typeof(KcpPassword)))
{
KcpPassword kcpPassword = (KcpPassword)App.getDB().pm.MasterKey.GetUserKey(typeof(KcpPassword));
KcpPassword kcpPassword = (KcpPassword)App.Kp2a.GetDb().pm.MasterKey.GetUserKey(typeof(KcpPassword));
String password = kcpPassword.Password.ReadString();
setEditText(Resource.Id.password, password);
}
if (App.getDB().pm.MasterKey.ContainsType(typeof(KcpKeyFile)))
if (App.Kp2a.GetDb().pm.MasterKey.ContainsType(typeof(KcpKeyFile)))
{
KcpKeyFile kcpKeyfile = (KcpKeyFile)App.getDB().pm.MasterKey.GetUserKey(typeof(KcpKeyFile));
KcpKeyFile kcpKeyfile = (KcpKeyFile)App.Kp2a.GetDb().pm.MasterKey.GetUserKey(typeof(KcpKeyFile));
setEditText(Resource.Id.pass_keyfile, kcpKeyfile.Path);
}
@ -367,15 +367,15 @@ namespace keepass2android
unloadDatabase();
// Clear the shutdown flag
App.clearShutdown();
App.Kp2a.clearShutdown();
CheckBox cbQuickUnlock = (CheckBox)FindViewById(Resource.Id.enable_quickunlock);
App.getDB().QuickUnlockEnabled = cbQuickUnlock.Checked;
App.getDB().QuickUnlockKeyLength = int.Parse(prefs.GetString(GetString(Resource.String.QuickUnlockLength_key), GetString(Resource.String.QuickUnlockLength_default)));
App.Kp2a.GetDb().QuickUnlockEnabled = cbQuickUnlock.Checked;
App.Kp2a.GetDb().QuickUnlockKeyLength = int.Parse(prefs.GetString(GetString(Resource.String.QuickUnlockLength_key), GetString(Resource.String.QuickUnlockLength_default)));
Handler handler = new Handler();
LoadDB task = new LoadDB(App.getDB(), this, mIoConnection, pass, key, new AfterLoad(handler, this));
ProgressTask pt = new ProgressTask(this, task, Resource.String.loading_database);
LoadDB task = new LoadDB(App.Kp2a, this, mIoConnection, pass, key, new AfterLoad(handler, this));
ProgressTask pt = new ProgressTask(App.Kp2a, this, task, UiStringKey.loading_database);
pt.run();
};
@ -463,20 +463,20 @@ namespace keepass2android
// If the application was shutdown make sure to clear the password field, if it
// was saved in the instance state
if (App.isShutdown()) {
if (App.Kp2a.isShutdown()) {
lockDatabase();
}
// Clear the shutdown flag
App.clearShutdown();
App.Kp2a.clearShutdown();
if (startedWithActivityResult)
return;
if (App.getDB().Loaded && (App.getDB().mIoc != null)
&& (mIoConnection != null) && (App.getDB().mIoc.GetDisplayName() == mIoConnection.GetDisplayName()))
if (App.Kp2a.GetDb().Loaded && (App.Kp2a.GetDb().mIoc != null)
&& (mIoConnection != null) && (App.Kp2a.GetDb().mIoc.GetDisplayName() == mIoConnection.GetDisplayName()))
{
if (App.getDB().Locked == false)
if (App.Kp2a.GetDb().Locked == false)
{
LaunchNextActivity();
}
@ -499,7 +499,7 @@ namespace keepass2android
private String getKeyFile(String filename) {
if ( mRememberKeyfile ) {
FileDbHelper dbHelp = App.fileDbHelper;
FileDbHelper dbHelp = App.Kp2a.fileDbHelper;
String keyfile = dbHelp.getFileByName(filename);

View File

@ -44,7 +44,7 @@ namespace keepass2android
base.OnCreate(bundle);
Intent i = Intent;
mIoc = App.getDB().mIoc;
mIoc = App.Kp2a.GetDb().mIoc;
if (mIoc == null)
{
@ -55,10 +55,10 @@ namespace keepass2android
SetContentView(Resource.Layout.QuickUnlock);
if (App.getDB().pm.Name != "")
if (App.Kp2a.GetDb().pm.Name != "")
{
FindViewById(Resource.Id.filename_label).Visibility = ViewStates.Invisible;
((TextView)FindViewById(Resource.Id.qu_filename)).Text = App.getDB().pm.Name;
((TextView)FindViewById(Resource.Id.qu_filename)).Text = App.Kp2a.GetDb().pm.Name;
} else
{
((TextView)FindViewById(Resource.Id.qu_filename)).Text = mIoc.Path;
@ -68,7 +68,7 @@ namespace keepass2android
TextView txtLabel = (TextView)FindViewById(Resource.Id.QuickUnlock_label);
ISharedPreferences prefs = PreferenceManager.GetDefaultSharedPreferences(this);
int quickUnlockLength = App.getDB().QuickUnlockKeyLength;
int quickUnlockLength = App.Kp2a.GetDb().QuickUnlockKeyLength;
txtLabel.Text = GetString(Resource.String.QuickUnlock_label, new Java.Lang.Object[]{quickUnlockLength});
@ -84,7 +84,7 @@ namespace keepass2android
Button btnUnlock = (Button)FindViewById(Resource.Id.QuickUnlock_button);
btnUnlock.Click += (object sender, EventArgs e) =>
{
KcpPassword kcpPassword = (KcpPassword)App.getDB().pm.MasterKey.GetUserKey(typeof(KcpPassword));
KcpPassword kcpPassword = (KcpPassword)App.Kp2a.GetDb().pm.MasterKey.GetUserKey(typeof(KcpPassword));
String password = kcpPassword.Password.ReadString();
String expectedPasswordPart = password.Substring(Math.Max(0,password.Length-quickUnlockLength),Math.Min(password.Length, quickUnlockLength));
if (pwd.Text == expectedPasswordPart)
@ -112,7 +112,7 @@ namespace keepass2android
{
base.OnResume();
if ( ! App.getDB().Loaded ) {
if ( ! App.Kp2a.GetDb().Loaded ) {
SetResult(KeePass.EXIT_CHANGE_DB);
Finish();
return;

File diff suppressed because it is too large Load Diff

View File

@ -82,8 +82,8 @@ namespace keepass2android
}
SetPassword sp = new SetPassword(Context, App.getDB(), pass, keyfile, new AfterSave(this, mFinish, new Handler()));
ProgressTask pt = new ProgressTask(Context, sp, Resource.String.saving_database);
SetPassword sp = new SetPassword(Context, App.Kp2a.GetDb(), pass, keyfile, new AfterSave(this, mFinish, new Handler()));
ProgressTask pt = new ProgressTask(App.Kp2a, Context, sp, UiStringKey.saving_database);
pt.run();
};

View File

@ -63,7 +63,7 @@ namespace keepass2android
SetResult(KeePass.EXIT_CLOSE_AFTER_TASK_COMPLETE);
mDb = App.getDB();
mDb = App.Kp2a.GetDb();
String searchUrl = ((SearchUrlTask)mAppTask).UrlToSearchFor;

View File

@ -597,7 +597,7 @@ namespace KeePass.Util.Spr
if(!MightDeref(str)) return str;
SprContext ctx = new SprContext(pe,
App.getDB().pm,
App.Kp2a.GetDb().pm,
SprCompileFlags.Deref);
// ctx.ForcePlainTextPasswords = false;

View File

@ -26,6 +26,8 @@ using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Widget;
using KeePassLib.Serialization;
using Android.Preferences;
namespace keepass2android
{
@ -47,15 +49,181 @@ namespace keepass2android
}
#endif
///Application class for Keepass2Android: Contains static Database variable to be used by all components.
public class Kp2aApp: IKp2aApp
{
public bool isShutdown()
{
return shutdown;
}
public void SetShutdown()
{
shutdown = true;
}
public void clearShutdown()
{
shutdown = false;
}
private Database db;
private bool shutdown = false;
/// <summary>
/// See comments to EntryEditActivityState.
/// </summary>
internal EntryEditActivityState entryEditActivityState = null;
public FileDbHelper fileDbHelper;
public Database GetDb()
{
if (db == null)
{
db = CreateNewDatabase();
}
return db;
}
public bool GetBooleanPreference(PreferenceKey key)
{
Context ctx = Application.Context;
ISharedPreferences prefs = PreferenceManager.GetDefaultSharedPreferences(ctx);
switch (key)
{
case PreferenceKey.remember_keyfile:
return prefs.GetBoolean(ctx.Resources.GetString(Resource.String.keyfile_key), ctx.Resources.GetBoolean(Resource.Boolean.keyfile_default));
case PreferenceKey.UseFileTransactions:
return prefs.GetBoolean(ctx.Resources.GetString(Resource.String.UseFileTransactions_key), true);
default:
throw new Exception("unexpected key!");
}
}
public void CheckForOpenFileChanged(Activity activity)
{
if (db.DidOpenFileChange())
{
if (db.ReloadRequested)
{
activity.SetResult(KeePass.EXIT_RELOAD_DB);
activity.Finish();
}
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.SetTitle(activity.GetString(Resource.String.AskReloadFile_title));
builder.SetMessage(activity.GetString(Resource.String.AskReloadFile));
builder.SetPositiveButton(activity.GetString(Android.Resource.String.Yes), new EventHandler<DialogClickEventArgs>((dlgSender, dlgEvt) =>
{
db.ReloadRequested = true;
activity.SetResult(KeePass.EXIT_RELOAD_DB);
activity.Finish();
}));
builder.SetNegativeButton(activity.GetString(Android.Resource.String.No), new EventHandler<DialogClickEventArgs>((dlgSender, dlgEvt) =>
{
}));
Dialog dialog = builder.Create();
dialog.Show();
}
}
public void StoreOpenedFileAsRecent(IOConnectionInfo ioc, string keyfile)
{
fileDbHelper.createFile(ioc, keyfile);
}
public string GetResourceString(UiStringKey key)
{
var field = typeof (Resource.String).GetField(key.ToString());
if (field == null)
throw new Exception("Invalid key " + key);
return App.Context.GetString((int)field.GetValue(null));
}
public void AskYesNoCancel(UiStringKey titleKey, UiStringKey messageKey,
EventHandler<DialogClickEventArgs> yesHandler,
EventHandler<DialogClickEventArgs> noHandler,
EventHandler<DialogClickEventArgs> cancelHandler,
Context ctx)
{
AlertDialog.Builder builder = new AlertDialog.Builder(ctx);
builder.SetTitle(GetResourceString(titleKey));
builder.SetMessage(GetResourceString(messageKey));
builder.SetPositiveButton(Resource.String.yes, yesHandler);
builder.SetNegativeButton(Resource.String.no, noHandler);
builder.SetNeutralButton(ctx.GetString(Android.Resource.String.Cancel),
cancelHandler);
Dialog dialog = builder.Create();
dialog.Show();
}
internal void OnTerminate()
{
if (db != null)
{
db.Clear();
}
if (fileDbHelper != null && fileDbHelper.isOpen())
{
fileDbHelper.close();
}
}
internal void OnCreate(Application app)
{
fileDbHelper = new FileDbHelper(app);
fileDbHelper.open();
#if DEBUG
foreach (UiStringKey key in Enum.GetValues(typeof(UiStringKey)))
{
GetResourceString(key);
}
#else
this should case a compiler error when switching to release (and thus ensure DEBUG is defined in DEBUG)
#endif
}
public Database CreateNewDatabase()
{
db = new Database(new DrawableFactory(), this);
return db;
}
}
///Application class for Keepass2Android: Contains static Database variable to be used by all components.
#if NoNet
[Application(Debuggable=false, Label=AppNames.AppName)]
#else
#if RELEASE
#if RELEASE
[Application(Debuggable=false, Label=AppNames.AppName)]
#else
[Application(Debuggable=true, Label=AppNames.AppName)]
#endif
#else
[Application(Debuggable = true, Label = AppNames.AppName)]
#endif
#endif
public class App : Application {
@ -64,63 +232,42 @@ namespace keepass2android
{
}
private static Database db;
private static bool shutdown = false;
private static readonly Kp2aApp instance = new Kp2aApp();
/// <summary>
/// See comments to EntryEditActivityState.
/// </summary>
internal static EntryEditActivityState entryEditActivityState = null;
public static FileDbHelper fileDbHelper;
public static Database getDB() {
if ( db == null ) {
db = new Database();
}
return db;
}
public static void setDB(Database d) {
db = d;
}
public static bool isShutdown() {
return shutdown;
}
public static void setShutdown() {
shutdown = true;
}
public static void clearShutdown() {
shutdown = false;
}
// Explicit static constructor to tell C# compiler
// not to mark type as beforefieldinit
static App()
{
}
private App()
{
}
public static Kp2aApp Kp2a
{
get
{
return instance;
}
}
public override void OnCreate() {
base.OnCreate();
Android.Util.Log.Debug("DEBUG","Creating application");
fileDbHelper = new FileDbHelper(this);
fileDbHelper.open();
instance.OnCreate(this);
}
public override void OnTerminate() {
base.OnTerminate();
Android.Util.Log.Debug("DEBUG","Terminating application");
if ( db != null ) {
db.Clear();
}
if ( fileDbHelper != null && fileDbHelper.isOpen() ) {
fileDbHelper.close();
}
instance.OnTerminate();
}
}
}
}

View File

@ -171,10 +171,11 @@ namespace keepass2android
new LaunchGroupActivity(IOConnectionInfo.FromPath(filename), this), this);
// Create the new database
CreateDB create = new CreateDB(this, IOConnectionInfo.FromPath(filename), password, true);
CreateDB create = new CreateDB(App.Kp2a, this, IOConnectionInfo.FromPath(filename), password, true);
ProgressTask createTask = new ProgressTask(
App.Kp2a,
this, create,
Resource.String.progress_create);
UiStringKey.progress_create);
createTask.run();
@ -216,7 +217,7 @@ namespace keepass2android
}
mDbHelper = App.fileDbHelper;
mDbHelper = App.Kp2a.fileDbHelper;
if (mDbHelper.hasRecentFiles())
{
recentMode = true;
@ -317,7 +318,7 @@ namespace keepass2android
public override void run() {
if (mSuccess) {
// Add to recent files
FileDbHelper dbHelper = App.fileDbHelper;
FileDbHelper dbHelper = App.Kp2a.fileDbHelper;
//TODO: getFilename always returns "" -> bug?
dbHelper.createFile(mIoc, getFilename());
@ -484,9 +485,9 @@ namespace keepass2android
if (!createdWithActivityResult)
{
if ((Intent.Action == Intent.ActionSend) && (App.getDB().Loaded))
if ((Intent.Action == Intent.ActionSend) && (App.Kp2a.GetDb().Loaded))
{
PasswordActivity.Launch(this, App.getDB().mIoc , mAppTask);
PasswordActivity.Launch(this, App.Kp2a.GetDb().mIoc , mAppTask);
} else
{

View File

@ -33,7 +33,7 @@ using Android.Graphics;
namespace keepass2android
{
public class DrawableFactory
public class DrawableFactory: IDrawableFactory
{
private static Drawable blank = null;
private static int blankWidth = -1;

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
@ -77,13 +77,12 @@
<Reference Include="Mono.Android.Support.v4" />
</ItemGroup>
<ItemGroup>
<Compile Include="icons\DrawableFactory.cs" />
<Compile Include="icons\Icons.cs" />
<Compile Include="Resources\Resource.designer.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="KeePass.cs" />
<Compile Include="database\edit\OnFinish.cs" />
<Compile Include="database\edit\FileOnFinish.cs" />
<Compile Include="app\App.cs" />
<Compile Include="Database.cs" />
<Compile Include="fileselect\FileSelectActivity.cs" />
<Compile Include="fileselect\FileDbHelper.cs" />
<Compile Include="Utils\Util.cs" />
@ -92,12 +91,6 @@
<Compile Include="fileselect\BrowserDialog.cs" />
<Compile Include="timeout\TimeoutHelper.cs" />
<Compile Include="timers\Timeout.cs" />
<Compile Include="database\edit\LoadDB.cs" />
<Compile Include="database\edit\RunnableOnFinish.cs" />
<Compile Include="UpdateStatus.cs" />
<Compile Include="ProgressTask.cs" />
<Compile Include="icons\DrawableFactory.cs" />
<Compile Include="search\SearchDbHelper.cs" />
<Compile Include="GroupActivity.cs" />
<Compile Include="GroupBaseActivity.cs" />
<Compile Include="LockCloseListActivity.cs" />
@ -111,16 +104,10 @@
<Compile Include="settings\PrefsUtil.cs" />
<Compile Include="views\PwEntryView.cs" />
<Compile Include="views\GroupHeaderView.cs" />
<Compile Include="PwUuidEqualityComparer.cs" />
<Compile Include="icons\Icons.cs" />
<Compile Include="BitmapDrawableCompat.cs" />
<Compile Include="GroupEditActivity.cs" />
<Compile Include="database\edit\AddGroup.cs" />
<Compile Include="database\edit\SaveDB.cs" />
<Compile Include="EntryEditActivity.cs" />
<Compile Include="LockCloseActivity.cs" />
<Compile Include="database\edit\AddEntry.cs" />
<Compile Include="database\edit\UpdateEntry.cs" />
<Compile Include="EntryActivity.cs" />
<Compile Include="GeneratePasswordActivity.cs" />
<Compile Include="password\PasswordGenerator.cs" />
@ -134,20 +121,13 @@
<Compile Include="LockingPreferenceActivity.cs" />
<Compile Include="SetPasswordDialog.cs" />
<Compile Include="CancelDialog.cs" />
<Compile Include="database\edit\SetPassword.cs" />
<Compile Include="database\edit\CreateDB.cs" />
<Compile Include="AboutDialog.cs" />
<Compile Include="settings\AppSettingsActivity.cs" />
<Compile Include="settings\RoundsPreference.cs" />
<Compile Include="KeyFileException.cs" />
<Compile Include="PasswordActivity.cs" />
<Compile Include="database\edit\DeleteGroup.cs" />
<Compile Include="PwGroupEqualityFromIdComparer.cs" />
<Compile Include="database\edit\DeleteEntry.cs" />
<Compile Include="search\SearchResults.cs" />
<Compile Include="compat\EditorCompat.cs" />
<Compile Include="compat\ActivityCompat.cs" />
<Compile Include="database\edit\ActionOnFinish.cs" />
<Compile Include="ShareUrlResults.cs" />
<Compile Include="services\TimeoutService.cs" />
<Compile Include="services\CopyToClipboardService.cs" />
@ -157,7 +137,6 @@
<Compile Include="LifecycleDebugActivity.cs" />
<Compile Include="services\QuickUnlockForegroundService.cs" />
<Compile Include="AssemblyInfo.cs" />
<Compile Include="database\edit\DeleteRunnable.cs" />
<Compile Include="views\FileSelectButtons.cs" />
<Compile Include="EntryEditActivityState.cs" />
<Compile Include="AttachmentContentProvider.cs" />
@ -657,62 +636,17 @@
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Novell\Novell.MonoDroid.CSharp.targets" />
<ItemGroup>
<Folder Include="database\" />
<Folder Include="database\edit\" />
<Folder Include="app\" />
<Folder Include="fileselect\" />
<Folder Include="Utils\" />
<Folder Include="intents\" />
<Folder Include="views\" />
<Folder Include="timeout\" />
<Folder Include="timers\" />
<Folder Include="services\" />
<Folder Include="icons\" />
<Folder Include="search\" />
<Folder Include="settings\" />
<Folder Include="password\" />
<Folder Include="compat\" />
<Folder Include="Resources\menu-v11\" />
<Folder Include="Resources\drawable-hdpi\" />
<Folder Include="Resources\drawable-ldpi\" />
<Folder Include="Resources\drawable-mdpi\" />
<Folder Include="Resources\drawable-xhdpi\" />
<Folder Include="Resources\drawable-xxhdpi\" />
<Folder Include="Resources\drawable-v11\" />
<Folder Include="Resources\values-de\" />
<Folder Include="Resources\values-ca\" />
<Folder Include="Resources\values-cs\" />
<Folder Include="Resources\values-da\" />
<Folder Include="Resources\values-es\" />
<Folder Include="Resources\values-fr\" />
<Folder Include="Resources\values-hu\" />
<Folder Include="Resources\values-it\" />
<Folder Include="Resources\values-ja\" />
<Folder Include="Resources\values-nl\" />
<Folder Include="Resources\values-nn\" />
<Folder Include="Resources\values-pl\" />
<Folder Include="Resources\values-pt-rBR\" />
<Folder Include="Resources\values-ru\" />
<Folder Include="Resources\values-sk\" />
<Folder Include="Resources\values-uk\" />
<Folder Include="Resources\values-zh-rCN\" />
<Folder Include="Resources\values-zh-rTW\" />
<Folder Include="SupportLib\" />
<Folder Include="Assets\" />
<Folder Include="libs\" />
<Folder Include="libs\armeabi-v7a\" />
<Folder Include="libs\armeabi\" />
<Folder Include="libs\mips\" />
<Folder Include="Resources\values-v14\" />
<Folder Include="Resources\layout-v14\" />
<Folder Include="Resources\anim\" />
<Folder Include="Utils\Spr\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\KeePassLib2Android\KeePassLib2Android.csproj">
<Project>{545B4A6B-8BBA-4FBE-92FC-4AC060122A54}</Project>
<Name>KeePassLib2Android</Name>
</ProjectReference>
<ProjectReference Include="..\Kp2aBusinessLogic\Kp2aBusinessLogic.csproj">
<Project>{53a9cb7f-6553-4bc0-b56b-9410bb2e59aa}</Project>
<Name>Kp2aBusinessLogic</Name>
</ProjectReference>
<ProjectReference Include="..\kp2akeytransform\kp2akeytransform.csproj">
<Project>{A57B3ACE-5634-469A-88C4-858BB409F356}</Project>
<Name>kp2akeytransform</Name>

View File

@ -47,7 +47,7 @@ namespace keepass2android.search
SetResult(KeePass.EXIT_NORMAL);
mDb = App.getDB();
mDb = App.Kp2a.GetDb();
// Likely the app has been killed exit the activity
if ( ! mDb.Open ) {

View File

@ -84,7 +84,7 @@ namespace keepass2android
PwEntry entry;
try
{
entry = App.getDB().entries[entryId];
entry = App.Kp2a.GetDb().entries[entryId];
}
catch(Exception)
{
@ -271,7 +271,7 @@ namespace keepass2android
static string GetStringAndReplacePlaceholders(PwEntry entry, string key)
{
String value = entry.Strings.ReadSafe(key);
value = SprEngine.Compile(value, new SprContext(entry, App.getDB().pm, SprCompileFlags.All));
value = SprEngine.Compile(value, new SprContext(entry, App.Kp2a.GetDb().pm, SprCompileFlags.All));
return value;
}

View File

@ -66,7 +66,7 @@ namespace keepass2android
private void timeout(Context context) {
Log.Debug(TAG, "Timeout");
App.setShutdown();
App.Kp2a.SetShutdown();
NotificationManager nm = (NotificationManager) GetSystemService(NotificationService);
nm.CancelAll();

View File

@ -52,7 +52,7 @@ namespace keepass2android
bool value = (bool) e.NewValue;
if ( ! value ) {
FileDbHelper helper = App.fileDbHelper;
FileDbHelper helper = App.Kp2a.fileDbHelper;
helper.deleteAllKeys();
}
@ -60,12 +60,12 @@ namespace keepass2android
return;
};
Database db = App.getDB();
Database db = App.Kp2a.GetDb();
if ( db.Open ) {
Preference rounds = FindPreference(GetString(Resource.String.rounds_key));
rounds.PreferenceChange += (object sender, Preference.PreferenceChangeEventArgs e) =>
{
setRounds(App.getDB(), e.Preference);
setRounds(App.Kp2a.GetDb(), e.Preference);
return;
};
@ -80,7 +80,7 @@ namespace keepass2android
Handler handler = new Handler();
SaveDB save = new SaveDB(this, App.getDB(), new ActionOnFinish( (success, message) =>
SaveDB save = new SaveDB(this, App.Kp2a.GetDb(), new ActionOnFinish( (success, message) =>
{
if (!success)
{
@ -89,7 +89,7 @@ namespace keepass2android
Toast.MakeText(this, message, ToastLength.Long).Show();
}
}));
ProgressTask pt = new ProgressTask(this, save, Resource.String.saving_database);
ProgressTask pt = new ProgressTask(App.Kp2a, this, save, UiStringKey.saving_database);
pt.run();
};
@ -104,7 +104,7 @@ namespace keepass2android
Handler handler = new Handler();
SaveDB save = new SaveDB(this, App.getDB(), new ActionOnFinish( (success, message) =>
SaveDB save = new SaveDB(this, App.Kp2a.GetDb(), new ActionOnFinish( (success, message) =>
{
if (!success)
{
@ -113,7 +113,7 @@ namespace keepass2android
Toast.MakeText(this, message, ToastLength.Long).Show();
}
}));
ProgressTask pt = new ProgressTask(this, save, Resource.String.saving_database);
ProgressTask pt = new ProgressTask(App.Kp2a, this, save, UiStringKey.saving_database);
pt.run();
};

View File

@ -43,7 +43,7 @@ namespace keepass2android.settings
mRoundsView = (TextView) view.FindViewById(Resource.Id.rounds);
Database db = App.getDB();
Database db = App.Kp2a.GetDb();
mPM = db.pm;
ulong numRounds = mPM.KeyEncryptionRounds;
mRoundsView.Text = numRounds.ToString();
@ -84,8 +84,8 @@ namespace keepass2android.settings
mPM.KeyEncryptionRounds = rounds;
Handler handler = new Handler();
SaveDB save = new SaveDB(Context, App.getDB(), new AfterSave(Context, handler, oldRounds, this));
ProgressTask pt = new ProgressTask(Context, save, Resource.String.saving_database);
SaveDB save = new SaveDB(Context, App.Kp2a.GetDb(), new AfterSave(Context, handler, oldRounds, this));
ProgressTask pt = new ProgressTask(App.Kp2a, Context, save, UiStringKey.saving_database);
pt.run();
}

View File

@ -46,14 +46,14 @@ namespace keepass2android
EditorCompat.apply(edit);
if ( App.getDB().Open ) {
if ( App.Kp2a.GetDb().Open ) {
Timeout.start(act);
}
}
public static void resume(Activity act) {
if ( App.getDB().Loaded ) {
if ( App.Kp2a.GetDb().Loaded ) {
Timeout.cancel(act);
}
@ -81,7 +81,7 @@ namespace keepass2android
long diff = cur_time - timeout_start;
if (diff >= timeout) {
// We have timed out
App.setShutdown();
App.Kp2a.SetShutdown();
}
}
@ -92,8 +92,8 @@ namespace keepass2android
}
public static bool checkShutdown(Activity act, IOConnectionInfo ioc) {
if (( App.getDB().Loaded && (App.isShutdown() || App.getDB().Locked) )
|| (iocChanged(ioc, App.getDB().mIoc))) //file was changed from ActionSend-Intent
if (( App.Kp2a.GetDb().Loaded && (App.Kp2a.isShutdown() || App.Kp2a.GetDb().Locked) )
|| (iocChanged(ioc, App.Kp2a.GetDb().mIoc))) //file was changed from ActionSend-Intent
{
act.SetResult(KeePass.EXIT_LOCK);
act.Finish();

View File

@ -88,10 +88,10 @@ namespace keepass2android.view
bool isExpired = pw.Expires && pw.ExpiryTime < DateTime.Now;
if (isExpired)
{
App.getDB().drawFactory.assignDrawableTo(iv, Resources, App.getDB().pm, PwIcon.Expired, PwUuid.Zero);
App.Kp2a.GetDb().drawableFactory.assignDrawableTo(iv, Resources, App.Kp2a.GetDb().pm, PwIcon.Expired, PwUuid.Zero);
} else
{
App.getDB().drawFactory.assignDrawableTo(iv, Resources, App.getDB().pm, pw.IconId, pw.CustomIconUuid);
App.Kp2a.GetDb().drawableFactory.assignDrawableTo(iv, Resources, App.Kp2a.GetDb().pm, pw.IconId, pw.CustomIconUuid);
}
String title = pw.Strings.ReadSafe(PwDefs.TitleField);
@ -156,7 +156,7 @@ namespace keepass2android.view
return true;
case MENU_DELETE:
Handler handler = new Handler();
DeleteEntry task = new DeleteEntry(Context, App.getDB(), mPw, new GroupBaseActivity.RefreshTask(handler, mAct));
DeleteEntry task = new DeleteEntry(Context, App.Kp2a, mPw, new GroupBaseActivity.RefreshTask(handler, mAct));
task.start();
return true;

View File

@ -75,7 +75,7 @@ namespace keepass2android.view
mPw = pw;
ImageView iv = (ImageView) gv.FindViewById(Resource.Id.group_icon);
App.getDB().drawFactory.assignDrawableTo(iv, Resources, App.getDB().pm, pw.IconId, pw.CustomIconUuid);
App.Kp2a.GetDb().drawableFactory.assignDrawableTo(iv, Resources, App.Kp2a.GetDb().pm, pw.IconId, pw.CustomIconUuid);
mTv.Text = pw.Name;
}
@ -109,7 +109,7 @@ namespace keepass2android.view
case MENU_DELETE:
Handler handler = new Handler();
DeleteGroup task = new DeleteGroup(Context, App.getDB(), mPw, mAct, new GroupBaseActivity.AfterDeleteGroup(handler, mAct));
DeleteGroup task = new DeleteGroup(Context, App.Kp2a, mPw, mAct, new GroupBaseActivity.AfterDeleteGroup(handler, mAct));
task.start();
return true;
default:

View File

@ -0,0 +1,5 @@
repo: 565cf64def9d1f2d13a7d87ecfe2ff4cb2d1065d
node: 5c51c8609ec16c51d6e6607173a28bba73b0bc9a
branch: default
latesttag: 1.2
latesttagdistance: 1

View File

@ -0,0 +1,7 @@
syntax: glob
*.suo
*/bin/
*/obj/
TestResults/
*.user
_ReSharper.*/

View File

@ -0,0 +1,3 @@
9d5c11392ed4ab5213a8e81249fcd602f5e003fc 1.0rc1
d1e7ac53602cfcb61a2b90e864546fa33f205e11 1.1
35717f159371e3f8cf433c1427413364100e6361 1.2

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<TestSettings name="Local" id="eaf7275b-c64b-4b20-abd8-cd6201e40d49" xmlns="http://microsoft.com/schemas/VisualStudio/TeamTest/2010">
<Description>These are default test settings for a local test run.</Description>
<Deployment enabled="false" />
<Execution>
<TestTypeSpecific />
<AgentRule name="Execution Agents">
</AgentRule>
</Execution>
</TestSettings>

View File

@ -0,0 +1,44 @@

Microsoft Visual Studio Solution File, Format Version 11.00
# Visual Studio 2010
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonoDroidUnitTestingExample", "MonoDroidUnitTestingExample\MonoDroidUnitTestingExample.csproj", "{18E159D7-9F30-436C-9AF4-590866AA9AA3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestTests", "TestTests\TestTests.csproj", "{2E8723FA-E025-4278-A9B4-E7BA476C79BC}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A839A224-BDF6-4196-9784-24D2FF5D3DB1}"
ProjectSection(SolutionItems) = preProject
Local.testsettings = Local.testsettings
MonoDroidUnit.vsmdi = MonoDroidUnit.vsmdi
TraceAndTestImpact.testsettings = TraceAndTestImpact.testsettings
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonoDroidUnitTesting", "MonoDroidUnitTesting\MonoDroidUnitTesting.csproj", "{A5F8FB02-00E0-4335-91EF-AEAA2C2F3C48}"
EndProject
Global
GlobalSection(TestCaseManagementSettings) = postSolution
CategoryFile = MonoDroidUnit.vsmdi
EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{18E159D7-9F30-436C-9AF4-590866AA9AA3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{18E159D7-9F30-436C-9AF4-590866AA9AA3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{18E159D7-9F30-436C-9AF4-590866AA9AA3}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
{18E159D7-9F30-436C-9AF4-590866AA9AA3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{18E159D7-9F30-436C-9AF4-590866AA9AA3}.Release|Any CPU.Build.0 = Release|Any CPU
{18E159D7-9F30-436C-9AF4-590866AA9AA3}.Release|Any CPU.Deploy.0 = Release|Any CPU
{2E8723FA-E025-4278-A9B4-E7BA476C79BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2E8723FA-E025-4278-A9B4-E7BA476C79BC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2E8723FA-E025-4278-A9B4-E7BA476C79BC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2E8723FA-E025-4278-A9B4-E7BA476C79BC}.Release|Any CPU.Build.0 = Release|Any CPU
{A5F8FB02-00E0-4335-91EF-AEAA2C2F3C48}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A5F8FB02-00E0-4335-91EF-AEAA2C2F3C48}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A5F8FB02-00E0-4335-91EF-AEAA2C2F3C48}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A5F8FB02-00E0-4335-91EF-AEAA2C2F3C48}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,216 @@
//
// Copyright (C) 2012 Maya Studios (http://mayastudios.com)
//
// This file is part of MonoDroidUnitTesting.
//
// MonoDroidUnitTesting is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// MonoDroidUnitTesting 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 Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with MonoDroidUnitTesting. If not, see <http://www.gnu.org/licenses/>.
//
using System;
using System.Collections.Generic;
using System.Globalization;
namespace Microsoft.VisualStudio.TestTools.UnitTesting {
// For Object.Equals(Object, Object) see:
// http://stackoverflow.com/a/1451459/614177
public static class Assert {
[Obsolete("Don't use 'Assert.Equals()'. Use 'Assert.AreEqual()' instead.")]
public static new bool Equals(object objA, object objB) {
throw new NotSupportedException("Don't use 'Assert.Equals()'. Use 'Assert.AreEqual()' instead.");
}
public static void AreEqual<T>(T expected, T actual, string message = null, params object[] parameters) {
if (!EqualityComparer<T>.Default.Equals(expected, actual)) {
throw new NotEqualException(expected, actual, message, parameters);
}
}
public static void AreNotEqual<T>(T notExpected, T actual, string message = null, params object[] parameters) {
if (EqualityComparer<T>.Default.Equals(notExpected, actual)) {
throw new EqualsException(notExpected, message, parameters);
}
}
public static void AreEqual(double expected, double actual, double delta,
string message = null, params object[] parameters) {
if (Math.Abs(expected - actual) > delta) {
throw new NotEqualException(expected, actual, message, parameters);
}
}
public static void AreNotEqual(double notExpected, double actual, double delta,
string message = null, params object[] parameters) {
if (Math.Abs(notExpected - actual) <= delta) {
throw new EqualsException(notExpected, message, parameters);
}
}
public static void AreEqual(float expected, float actual, float delta,
string message = null, params object[] parameters) {
if (Math.Abs(expected - actual) > delta) {
throw new NotEqualException(expected, actual, message, parameters);
}
}
public static void AreNotEqual(float notExpected, float actual, float delta,
string message = null, params object[] parameters) {
if (Math.Abs(notExpected - actual) <= delta) {
throw new EqualsException(notExpected, message, parameters);
}
}
public static void AreEqual(string expected, string actual,
string message = null, params object[] parameters) {
AreEqual(expected, actual, false, message, parameters);
}
public static void AreNotEqual(string notExpected, string actual,
string message = null, params object[] parameters) {
AreNotEqual(notExpected, actual, false, message, parameters);
}
public static void AreEqual(string expected, string actual, bool ignoreCase,
string message = null, params object[] parameters) {
AreEqual(expected, actual, ignoreCase, CultureInfo.InvariantCulture, message, parameters);
}
public static void AreNotEqual(string notExpected, string actual, bool ignoreCase,
string message = null, params object[] parameters) {
AreNotEqual(notExpected, actual, ignoreCase, CultureInfo.InvariantCulture, message, parameters);
}
public static void AreEqual(string expected, string actual, bool ignoreCase, CultureInfo culture,
string message = null, params object[] parameters) {
if (string.Compare(expected, actual, ignoreCase, culture) != 0) {
throw new NotEqualException(expected, actual, message, parameters);
}
}
public static void AreNotEqual(string notExpected, string actual, bool ignoreCase, CultureInfo culture,
string message = null, params object[] parameters) {
if (string.Compare(notExpected, actual, ignoreCase, culture) == 0) {
throw new EqualsException(notExpected, message, parameters);
}
}
public static void AreSame(object expected, object actual,
string message = null, params object[] parameters) {
if (!object.ReferenceEquals(expected, actual)) {
throw new AssertFailedException("'" + expected + "' and '" + actual + "' are not reference equal", message, parameters);
}
}
public static void AreNotSame(object notExpected, object actual,
string message = null, params object[] parameters) {
if (object.ReferenceEquals(notExpected, actual)) {
throw new AssertFailedException("'" + notExpected + "' and '" + actual + "' are reference equal", message, parameters);
}
}
public static void IsTrue(bool condition,
string message = null, params object[] parameters) {
if (!condition) {
throw new NotEqualException(true, condition, message, parameters);
}
}
public static void IsFalse(bool condition,
string message = null, params object[] parameters) {
if (condition) {
throw new NotEqualException(false, condition, message, parameters);
}
}
public static void IsNull(object value,
string message = null, params object[] parameters) {
if (value != null) {
throw new NotEqualException(null, value, message, parameters);
}
}
public static void IsNotNull(object value,
string message = null, params object[] parameters) {
if (value == null) {
throw new EqualsException(null, message, parameters);
}
}
internal static bool CheckIsInstanceOfType(object value, Type expectedType) {
return expectedType.IsAssignableFrom(value.GetType());
}
public static void IsInstanceOfType(object value, Type expectedType,
string message = null, params object[] parameters) {
if (expectedType == null) {
throw new NullTestArgumentException("expectedType");
}
if (value == null) {
throw new AssertFailedException("'null' is no instance of " + expectedType, message, parameters);
}
if (!CheckIsInstanceOfType(value, expectedType)) {
throw new AssertFailedException("'" + value + "' (Type: " + value.GetType() + ") is no instance of " + expectedType, message, parameters);
}
}
public static void IsNotInstanceOfType(object value, Type wrongType,
string message = null, params object[] parameters) {
if (value == null) {
// "null" has no type and therefore is not an instance of "wrongType".
return;
}
if (wrongType == null) {
throw new NullTestArgumentException("wrongType");
}
if (wrongType.IsAssignableFrom(value.GetType())) {
throw new AssertFailedException("'" + value + "' (Type: " + value.GetType() + ") is an instance of " + wrongType, message, parameters);
}
}
public static void Fail(string message = null, params object[] parameters) {
throw new AssertFailedException(message, parameters);
}
public static void Inconclusive(string message = null, params object[] parameters) {
// Basically the same as "Fail". Used in auto generated (template) code.
throw new AssertInconclusiveException(message, parameters);
}
//
// Summary:
// In a string, replaces null characters ('\0') with "\\0".
//
// Parameters:
// input:
// The string in which to search for and replace null characters.
//
// Returns:
// The converted string with null characters replaced by "\\0".
public static string ReplaceNullChars(string input) {
if (input == null) {
return null;
}
return input.Replace("\0", "\\0");
}
}
}

View File

@ -0,0 +1,89 @@
//
// Copyright (C) 2012 Maya Studios (http://mayastudios.com)
//
// This file is part of MonoDroidUnitTesting.
//
// MonoDroidUnitTesting is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// MonoDroidUnitTesting 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 Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with MonoDroidUnitTesting. If not, see <http://www.gnu.org/licenses/>.
//
using System;
namespace Microsoft.VisualStudio.TestTools.UnitTesting {
public class UnitTestAssertException : Exception {
public UnitTestAssertException() : base() { }
public UnitTestAssertException(string message) : base(message) { }
public UnitTestAssertException(string message, Exception innerException) : base(message, innerException) { }
public UnitTestAssertException(string msg, object[] parameters)
: base(ConstructMessage(msg, parameters)) { }
public UnitTestAssertException(string explanation, string msg, object[] parameters)
: base(ConstructMessage(explanation, msg, parameters)) { }
private static string ConstructMessage(string message, object[] parameters) {
if (message == null || message == "") {
return "";
}
if (parameters != null && parameters.Length != 0) {
message = String.Format(message, parameters);
}
return message;
}
private static string ConstructMessage(string explanation, string message, object[] parameters) {
message = ConstructMessage(message, parameters);
if (message != "") {
message += " ";
}
return message + explanation;
}
}
public class AssertInconclusiveException : UnitTestAssertException {
public AssertInconclusiveException(string msg, object[] parameters)
: base(msg, parameters) { }
}
public class AssertFailedException : UnitTestAssertException {
public AssertFailedException(string msg, params object[] parameters)
: base(msg, parameters) { }
public AssertFailedException(string explanation, string msg, object[] parameters)
: base(explanation, msg, parameters) { }
}
public class NotEqualException : AssertFailedException {
public NotEqualException(object expected, object actual, string message, object[] parameters)
: base("Expected '" + expected + "' but got '" + actual + "'", message, parameters) { }
}
public class EqualsException : AssertFailedException {
public EqualsException(object notExpected, string message, object[] parameters)
: base("Got unexpected '" + notExpected + "'", message, parameters) { }
}
public class InvalidTestArgumentException : AssertFailedException {
public InvalidTestArgumentException(string paramName, string reason)
: base("The parameter '{0}' is invalid. {1}", paramName, reason) { }
}
public class NullTestArgumentException : InvalidTestArgumentException {
public NullTestArgumentException(string paramName)
: base("The parameter '{0}' is invalid. The value cannot be null.", paramName) { }
}
}

View File

@ -0,0 +1,369 @@
//
// Copyright (C) 2012 Maya Studios (http://mayastudios.com)
//
// This file is part of MonoDroidUnitTesting.
//
// MonoDroidUnitTesting is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// MonoDroidUnitTesting 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 Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with MonoDroidUnitTesting. If not, see <http://www.gnu.org/licenses/>.
//
using System;
using System.Collections;
using System.Collections.Generic;
namespace Microsoft.VisualStudio.TestTools.UnitTesting {
// Summary:
// Verifies true/false propositions associated with collections in unit tests.
public static class CollectionAssert {
/// <summary>
/// Verifies that all elements in the specified collection are instances of the specified type. The assertion
/// fails if there exists one element in the collection for which the specified type is not found in its
/// inheritance hierarchy.
/// </summary>
public static void AllItemsAreInstancesOfType(ICollection collection, Type expectedType,
string message = null, params object[] parameters) {
if (collection == null) {
throw new NullTestArgumentException("collection");
}
if (expectedType == null) {
throw new NullTestArgumentException("expectedType");
}
int index = 0;
foreach (object item in collection) {
if (item == null) {
throw new AssertFailedException("Element at index {0} is (null). Expected type: <{1}>.".Format(index, expectedType.FullName), message, parameters);
}
if (!Assert.CheckIsInstanceOfType(item, expectedType)) {
throw new AssertFailedException(
"Element at index {0} is not of expecteds type. Expected type: {1}. Actual type: {2}.".FormatValues(index, expectedType.FullName, item.GetType().FullName),
message, parameters);
}
index++;
}
}
/// <summary>
/// Verifies that all items in the specified collection are not null. The assertion fails if any element is null.
/// </summary>
public static void AllItemsAreNotNull(ICollection collection, string message = null, params object[] parameters) {
if (collection == null) {
throw new NullTestArgumentException("collection");
}
int index = 0;
foreach (object item in collection) {
if (item == null) {
throw new AssertFailedException("Element at index {0} is (null).".Format(index), message, parameters);
}
index++;
}
}
/// <summary>
/// Verifies that all items in the specified collection are unique. The assertion fails if any two elements in the
/// collection are equal.
/// </summary>
public static void AllItemsAreUnique(ICollection collection, string message = null, params object[] parameters) {
if (collection == null) {
throw new NullTestArgumentException("collection");
}
HashSet<object> items = new HashSet<object>();
foreach (object item in collection) {
if (!items.Add(item)) {
throw new AssertFailedException("Duplicate item found: {0}".FormatValues(item), message, parameters);
}
}
}
private delegate bool ComparatorFunc(object expected, object actual);
private static void AreEqual(ICollection expected, ICollection actual, string message, object[] parameters,
ComparatorFunc comparator) {
if (object.ReferenceEquals(expected, actual)) {
return;
}
if (expected == null) {
throw new NullTestArgumentException("expected");
}
if (actual == null) {
throw new NullTestArgumentException("actual");
}
if (expected.Count != actual.Count) {
throw new AssertFailedException("Expected collection with " + expected.Count + " items but got " + actual.Count + " items.", message, parameters);
}
IEnumerator expectedIter = expected.GetEnumerator();
IEnumerator actualIter = actual.GetEnumerator();
int index = 0;
while (expectedIter.MoveNext()) {
Assert.IsTrue(actualIter.MoveNext(), message, parameters);
if (!comparator(expectedIter.Current, actualIter.Current)) {
throw new AssertFailedException("Elements at index {0} do not match.".Format(index), message, parameters);
}
index++;
}
Assert.IsFalse(actualIter.MoveNext());
}
/// <summary>
/// Verifies that two specified collections are equal, using <see cref="object.Equals()"/> to compare the values of
/// elements. The assertion fails if the collections are not equal.
/// </summary>
public static void AreEqual(ICollection expected, ICollection actual, string message = null, params object[] parameters) {
AreEqual(expected, actual, message, parameters, (a, b) => { return object.Equals(a, b); });
}
/// <summary>
/// Verifies that two specified collections are equal, using the specified method to compare the values of
/// elements. The assertion fails if the collections are not equal.
/// </summary>
public static void AreEqual(ICollection expected, ICollection actual, IComparer comparer, string message = null, params object[] parameters) {
AreEqual(expected, actual, message, parameters, (a, b) => { return comparer.Compare(a, b) == 0; });
}
public static void AreNotEqual(ICollection notExpected, ICollection actual, string message = null, params object[] parameters) {
bool areEqual = true;
try {
AreEqual(notExpected, actual);
}
catch (AssertFailedException) {
areEqual = false;
}
if (areEqual) {
throw new AssertFailedException("Collections are equal.", message, parameters);
}
}
public static void AreNotEqual(ICollection notExpected, ICollection actual, IComparer comparer, string message = null, params object[] parameters) {
bool areEqual = true;
try {
AreEqual(notExpected, actual, comparer);
}
catch (AssertFailedException) {
areEqual = false;
}
if (areEqual) {
throw new AssertFailedException("Collections are equal.", message, parameters);
}
}
/// <summary>
/// Verifies that two specified collections are equivalent. The assertion fails if the collections are not
/// equivalent. Two collections are equivalent if they have the same elements in the same quantity, but in any
/// order. Elements are equal if their values are equal, not if they refer to the same object.
/// </summary>
public static void AreEquivalent(ICollection expected, ICollection actual, string message = null, params object[] parameters) {
if (object.ReferenceEquals(expected, actual)) {
return;
}
if (expected == null) {
throw new NullTestArgumentException("expected");
}
if (actual == null) {
throw new NullTestArgumentException("actual");
}
if (expected.Count != actual.Count) {
throw new AssertFailedException("Expected collection with " + expected.Count + " items but got " + actual.Count + " items.", message, parameters);
}
Dictionary<object, int> expectedQunatities = new Dictionary<object, int>(expected.Count);
foreach (object obj in expected) {
int quant;
if (expectedQunatities.TryGetValue(obj, out quant)) {
expectedQunatities[obj] = quant + 1;
}
else {
expectedQunatities[obj] = 1;
}
}
Dictionary<object, int> actualQunatities = new Dictionary<object, int>(actual.Count);
foreach (object obj in actual) {
int quant;
if (actualQunatities.TryGetValue(obj, out quant)) {
actualQunatities[obj] = quant + 1;
}
else {
actualQunatities[obj] = 1;
}
}
if (expectedQunatities.Count != actualQunatities.Count) {
throw new AssertFailedException("Expected " + expectedQunatities.Count + " unique items but got " + actualQunatities.Count, message, parameters);
}
foreach (KeyValuePair<object, int> entry in expectedQunatities) {
int actualQuant;
if (!actualQunatities.TryGetValue(entry.Key, out actualQuant)) {
throw new AssertFailedException("Expected '" + entry.Key + "' not in collection.", message, parameters);
}
if (entry.Value != actualQuant) {
throw new AssertFailedException("Expected to find '" + entry.Key + "' " + entry.Value + " times but got: " + actualQuant, message, parameters);
}
}
}
/// <summary>
/// Verifies that two specified collections are not equivalent. The assertion fails if the collections are
/// equivalent. Two collections are equivalent if they have the same elements in the same quantity, but in any
/// order. Elements are equal if their values are equal, not if they refer to the same object.
/// </summary>
public static void AreNotEquivalent(ICollection expected, ICollection actual, string message = null, params object[] parameters) {
bool areEquivalent = true;
try {
AreEquivalent(expected, actual);
}
catch (AssertFailedException) {
areEquivalent = false;
}
if (areEquivalent) {
throw new AssertFailedException("Collections are equivalent.", message, parameters);
}
}
/// <summary>
/// Verifies that the specified collection contains the specified element. The assertion fails if the element is
/// not found in the collection.
/// </summary>
public static void Contains(ICollection collection, object element, string message = null, params object[] parameters) {
if (collection == null) {
throw new NullTestArgumentException("collection");
}
foreach (object obj in collection) {
if (object.Equals(obj, element)) {
return;
}
}
throw new AssertFailedException("Collection does not contain {0}.".FormatValues(element), message, parameters);
}
/// <summary>
/// Verifies that the specified collection does not contain the specified element. The assertion fails if the
/// element is found in the collection.
/// </summary>
public static void DoesNotContain(ICollection collection, object element, string message = null, params object[] parameters) {
if (collection == null) {
throw new NullTestArgumentException("collection");
}
foreach (object obj in collection) {
if (object.Equals(obj, element)) {
throw new AssertFailedException("'" + element + "' is part of the collection.", message, parameters);
}
}
}
/// <summary>
/// Verifies that the first collection is a subset of the second collection. One collection is a subset of another
/// collection if every element in the first collection also appears in the second collection. An element that
/// appears in the first collection more than once must appear in the second collection as many times, or more, as
/// it does in the first collection. The second collection may have elements that are not in the first collection,
/// but that is not required.
/// </summary>
public static void IsSubsetOf(ICollection subset, ICollection superset, string message = null, params object[] parameters) {
if (subset == null) {
throw new NullTestArgumentException("subset");
}
if (superset == null) {
throw new NullTestArgumentException("superset");
}
Dictionary<object, int> subsetQuantities = new Dictionary<object, int>(subset.Count);
foreach (object obj in subset) {
int quant;
if (subsetQuantities.TryGetValue(obj, out quant)) {
subsetQuantities[obj] = quant + 1;
}
else {
subsetQuantities[obj] = 1;
}
}
Dictionary<object, int> supersetQuantities = new Dictionary<object, int>(superset.Count);
foreach (object obj in superset) {
int quant;
if (supersetQuantities.TryGetValue(obj, out quant)) {
supersetQuantities[obj] = quant + 1;
}
else {
supersetQuantities[obj] = 1;
}
}
foreach (KeyValuePair<object, int> entry in subsetQuantities) {
int superQuant;
if (!supersetQuantities.TryGetValue(entry.Key, out superQuant)) {
throw new AssertFailedException("Entry '" + entry.Key + "' not contained in super set.", message, parameters);
}
if (superQuant < entry.Value) {
throw new AssertFailedException("Entry '" + entry.Key + " is only contained " + superQuant + " times in super set but " + entry.Value + " times in sub set.", message, parameters);
}
}
}
/// <summary>
/// Verifies that the first collection is not a subset of the second collection. One collection is a subset of
/// another collection if every element in the first collection also appears in the second collection. An element
/// that appears in the first collection more than once must appear in the second collection as many times, or more,
/// as it does in the first collection. The second collection may have elements that are not in the first
/// collection, but that is not required.
/// </summary>
public static void IsNotSubsetOf(ICollection subset, ICollection superset, string message = null, params object[] parameters) {
if (subset == null) {
throw new NullTestArgumentException("subset");
}
if (superset == null) {
throw new NullTestArgumentException("superset");
}
bool isSubset = true;
try {
IsSubsetOf(subset, superset);
}
catch (AssertFailedException) {
isSubset = false;
}
if (isSubset) {
throw new AssertFailedException("Is subset of superset.", message, parameters);
}
}
}
}

View File

@ -0,0 +1,103 @@
//
// Copyright (C) 2012 Maya Studios (http://mayastudios.com)
//
// This file is part of MonoDroidUnitTesting.
//
// MonoDroidUnitTesting is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// MonoDroidUnitTesting 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 Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with MonoDroidUnitTesting. If not, see <http://www.gnu.org/licenses/>.
//
using System.Text.RegularExpressions;
namespace Microsoft.VisualStudio.TestTools.UnitTesting {
// Summary:
// Verifies true/false propositions associated with strings in unit tests.
public static class StringAssert {
/// <summary>
/// Verifies that the first string contains the second string.
/// </summary>
public static void Contains(string value, string substring, string message = null, params object[] parameters) {
if (value == null) {
throw new NullTestArgumentException("value");
}
if (substring == null) {
throw new NullTestArgumentException("substring");
}
if (!value.Contains(substring)) {
throw new AssertFailedException("'" + value + "' does not contain '" + substring + "'", message, parameters);
}
}
/// <summary>
/// Verifies that the first string begins with the second string
/// </summary>
public static void StartsWith(string value, string substring, string message = null, params object[] parameters) {
if (value == null) {
throw new NullTestArgumentException("value");
}
if (substring == null) {
throw new NullTestArgumentException("substring");
}
if (!value.StartsWith(substring)) {
throw new AssertFailedException("'" + value + "' does not start with '" + substring + "'", message, parameters);
}
}
/// <summary>
/// Verifies that the first string ends with the second string.
/// </summary>
public static void EndsWith(string value, string substring, string message = null, params object[] parameters) {
if (value == null) {
throw new NullTestArgumentException("value");
}
if (substring == null) {
throw new NullTestArgumentException("substring");
}
if (!value.EndsWith(substring)) {
throw new AssertFailedException("'" + value + "' does not end with '" + substring + "'", message, parameters);
}
}
/// <summary>
/// Verifies that the specified string (or parts of it) matches the regular expression.
/// </summary>
public static void Matches(string value, Regex pattern, string message = null, params object[] parameters) {
if (value == null) {
throw new NullTestArgumentException("value");
}
if (pattern == null) {
throw new NullTestArgumentException("pattern");
}
if (!pattern.IsMatch(value)) {
throw new AssertFailedException("Pattern does not match '" + value + "'", message, parameters);
}
}
/// <summary>
/// Verifies that the specified string does not match the regular expression.
/// </summary>
public static void DoesNotMatch(string value, Regex pattern, string message = null, params object[] parameters) {
if (value == null) {
throw new NullTestArgumentException("value");
}
if (pattern == null) {
throw new NullTestArgumentException("pattern");
}
if (pattern.IsMatch(value)) {
throw new AssertFailedException("Pattern does match '" + value + "'", message, parameters);
}
}
}
}

View File

@ -0,0 +1,90 @@
//
// Copyright (C) 2012 Maya Studios (http://mayastudios.com)
//
// This file is part of MonoDroidUnitTesting.
//
// MonoDroidUnitTesting is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// MonoDroidUnitTesting 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 Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with MonoDroidUnitTesting. If not, see <http://www.gnu.org/licenses/>.
//
using System;
namespace Microsoft.VisualStudio.TestTools.UnitTesting {
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public sealed class TestClassAttribute : Attribute { }
// Summary:
// Used to identify test methods. This class cannot be inherited.
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public sealed class TestMethodAttribute : Attribute { }
// Summary:
// Identifies the method to run before the test to allocate and configure resources
// needed by all tests in the test class. This class cannot be inherited.
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public sealed class TestInitializeAttribute : Attribute { }
// Summary:
// Identifies a method that contains code that must be used after the test has
// run and to free resources obtained by all the tests in the test class. This
// class cannot be inherited.
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public sealed class TestCleanupAttribute : Attribute { }
// Summary:
// Identifies a method that contains code that must be used before any of the
// tests in the test class have run and to allocate resources to be used by
// the test class. This class cannot be inherited.
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public sealed class ClassInitializeAttribute : Attribute { }
// Summary:
// Identifies a method that contains code to be used after all the tests in
// the test class have run and to free resources obtained by the test class.
// This class cannot be inherited.
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public sealed class ClassCleanupAttribute : Attribute { }
/// <summary>
/// Indicates that an exception is expected during test method execution. This class cannot be inherited.
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public sealed class ExpectedExceptionAttribute : Attribute {
/// <summary>
/// The expected exception type.
/// </summary>
public Type ExceptionType { get; private set; }
/// <summary>
/// Whether derived exception types are allowed as well. Defaults to <c>false</c>.
/// </summary>
public bool AllowDerivedTypes { get; set; }
/// <summary>
/// Initializes a new instance of this.
/// </summary>
/// <param name="exceptionType">An expected type of exception to be thrown by a method.</param>
/// <param name="noExceptionMessage">describes the exception; note that the execption message is NOT compared against this value.</param>
public ExpectedExceptionAttribute(Type exceptionType, string noExceptionMessage = "") {
if (exceptionType == null) {
throw new ArgumentNullException("exceptionType");
}
if (!typeof(Exception).IsAssignableFrom(exceptionType)) {
throw new ArgumentException("Must derive from type 'Exception'.", "exceptionType");
}
this.ExceptionType = exceptionType;
}
}
}

View File

@ -0,0 +1,157 @@
//
// Copyright (C) 2012 Maya Studios (http://mayastudios.com)
//
// This file is part of MonoDroidUnitTesting.
//
// MonoDroidUnitTesting is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// MonoDroidUnitTesting 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 Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with MonoDroidUnitTesting. If not, see <http://www.gnu.org/licenses/>.
//
using System;
using System.IO;
using System.Reflection;
using Android.App;
using Android.Content;
using Android.Graphics;
using Android.Graphics.Drawables;
using Android.OS;
using Android.Preferences;
using Android.Views;
using Android.Widget;
using MonoDroidUnitTesting.Utils;
namespace MonoDroidUnitTesting {
public abstract class AbstractResultActivity : Activity {
protected const string LOG_TAG = "MonoDroid Unit Test";
protected const string ACTIVITY_PREFS_NAME = "last_activity";
private const string ASSEMBLY_NAMESPACE = "MonoDroidUnitTesting";
private const int RESULT_BAR_HEIGHT = 8;
private readonly string m_iconFileNamePrefix;
public Drawable IconOutcomeInconclusive { get; private set; }
public Drawable IconOutcomePassed { get; private set; }
public Drawable IconOutcomeError { get; private set; }
protected ResultBarView ResultBar { get; private set; }
protected AbstractResultActivity(string iconFileNamePrefix) {
this.m_iconFileNamePrefix = iconFileNamePrefix;
}
protected override void OnCreate(Bundle bundle) {
base.OnCreate(bundle);
this.IconOutcomePassed = GetDrawable(this.m_iconFileNamePrefix + "outcome_passed.png");
this.IconOutcomeError = GetDrawable(this.m_iconFileNamePrefix + "outcome_error.png");
this.IconOutcomeInconclusive = GetDrawable(this.m_iconFileNamePrefix + "outcome_inconclusive.png");
LinearLayout mainLayout = new LinearLayout(this);
mainLayout.Orientation = Orientation.Vertical;
this.ResultBar = new ResultBarView(this);
this.ResultBar.LayoutParameters = LayoutParams.ForLL(height: RESULT_BAR_HEIGHT);
mainLayout.AddView(this.ResultBar);
View mainView = CreateMainView();
mainView.LayoutParameters = LayoutParams.ForLL();
mainLayout.AddView(mainView);
SetContentView(mainLayout);
}
private Drawable GetDrawable(string filename) {
// As long as MonoDroid doesn't support Android resources in library projects we need to fall back to regular
// .NET resources.
Assembly assembly = Assembly.GetExecutingAssembly();
Stream resStream = assembly.GetManifestResourceStream(ASSEMBLY_NAMESPACE + ".Resources.Drawable." + filename);
return new BitmapDrawable(this.Resources, resStream);
}
protected abstract View CreateMainView();
private int m_preferredListItemHeight = -1;
public int GetPreferredListItemHeight() {
if (this.m_preferredListItemHeight == -1) {
this.m_preferredListItemHeight = GetPreferredListItemHeight(this);
}
return this.m_preferredListItemHeight;
}
public static int GetPreferredListItemHeight(Context ctx) {
return Dimension.FromResId(Android.Resource.Attribute.ListPreferredItemHeight)
.ToPixels(ctx, Orientation.Vertical);
}
public Drawable GetIconForState(TestState state) {
switch (state) {
case TestState.NotYetRun:
case TestState.Inconclusive:
return this.IconOutcomeInconclusive;
case TestState.Passed:
return this.IconOutcomePassed;
case TestState.Failed:
return this.IconOutcomeError;
default:
throw new Exception("Unexpected");
}
}
public static ISharedPreferences GetPreferences() {
return PreferenceManager.GetDefaultSharedPreferences(Application.Context);
}
public class ResultBarView : View {
private Paint m_paint = new Paint();
public Color BarColor {
get { return this.m_paint.Color; }
set {
this.m_paint.Color = value;
Invalidate();
}
}
public ResultBarView(Context context) : base(context) {
this.BarColor = Color.Green;
}
public void SetColorByState(TestState state) {
switch (state) {
case TestState.Failed:
this.BarColor = Color.Red;
break;
case TestState.Inconclusive:
this.BarColor = Color.Yellow;
break;
case TestState.Passed:
this.BarColor = Color.Green;
break;
default:
this.BarColor = Color.Blue;
break;
}
}
protected override void OnDraw(Canvas canvas) {
canvas.DrawRect(canvas.ClipBounds, this.m_paint);
}
}
}
}

View File

@ -0,0 +1,87 @@
//
// Copyright (C) 2012 Maya Studios (http://mayastudios.com)
//
// This file is part of MonoDroidUnitTesting.
//
// MonoDroidUnitTesting is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// MonoDroidUnitTesting 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 Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with MonoDroidUnitTesting. If not, see <http://www.gnu.org/licenses/>.
//
using Android.Graphics;
using Android.Graphics.Drawables;
using Android.Text;
using Android.Util;
using Android.Views;
using Android.Widget;
using MonoDroidUnitTesting.Utils;
namespace MonoDroidUnitTesting {
public abstract class AbstractResultListActivity<T> : AbstractResultActivity {
protected ArrayAdapter<T> ListAdapter { get; private set; }
protected AbstractResultListActivity(string iconFileNamePrefix) : base(iconFileNamePrefix) { }
protected override View CreateMainView() {
LinearLayout mainLayout = new LinearLayout(this);
mainLayout.Orientation = Orientation.Vertical;
View headerView = CreateHeaderView();
if (headerView != null) {
mainLayout.AddView(headerView);
}
ListView listView = new ListView(this);
this.ListAdapter = CreateListAdapter();
listView.Adapter = this.ListAdapter;
listView.CacheColorHint = Color.Transparent; // IMPORTANT! Otherwise there is flickering!
listView.ItemClick += (s, e) => OnItemClicked(e);
mainLayout.AddView(listView);
return mainLayout;
}
protected virtual View CreateHeaderView() {
return null;
}
protected abstract ArrayAdapter<T> CreateListAdapter();
protected abstract void OnItemClicked(AdapterView.ItemClickEventArgs e);
public abstract class TestResultAdapter : ArrayAdapter<T> {
protected AbstractResultListActivity<T> Activity { get; private set; }
public TestResultAdapter(AbstractResultListActivity<T> activity) : base(activity, 0) {
this.Activity = activity;
}
protected abstract TestState GetStateFor(T item);
protected abstract string GetHTMLDescriptionFor(T item);
public override View GetView(int position, View convertView, ViewGroup parent) {
ResultListItemView view = convertView as ResultListItemView;
if (view == null) {
view = new ResultListItemView(this.Context);
}
T item = GetItem(position);
view.SetIcon(this.Activity.GetIconForState(GetStateFor(item)));
view.SetHtml(GetHTMLDescriptionFor(item));
return view;
}
}
}
}

View File

@ -0,0 +1,328 @@
//
// Copyright (C) 2012 Maya Studios (http://mayastudios.com)
//
// This file is part of MonoDroidUnitTesting.
//
// MonoDroidUnitTesting is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// MonoDroidUnitTesting 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 Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with MonoDroidUnitTesting. If not, see <http://www.gnu.org/licenses/>.
//
using System;
using System.Reflection;
using Android.App;
using Android.Content;
using Android.Graphics;
using Android.OS;
using Android.Util;
using Android.Views;
using Android.Widget;
using MonoDroidUnitTesting.Utils;
namespace MonoDroidUnitTesting {
public abstract class GuiTestRunnerActivity : AbstractResultListActivity<TestClass> {
internal static TestRunner TestRunner { get; private set; }
private ResultListItemView m_headerView;
private MethodOutcome m_headerOutcome;
public GuiTestRunnerActivity() : base("class_") { }
protected override void OnCreate(Bundle bundle) {
TestRunner = null;
base.OnCreate(bundle);
}
protected override View CreateHeaderView() {
this.m_headerView = new ResultListItemView(this);
this.m_headerView.LayoutParameters = LayoutParams.ForLL(marginBottom: 10);
this.m_headerView.SetIcon(this.IconOutcomeError);
this.m_headerView.SetBackgroundColor(new Color(110, 3, 15));
this.m_headerView.Clickable = true;
this.m_headerView.Click += OnHeaderClick;
this.m_headerView.Visibility = ViewStates.Gone;
return this.m_headerView;
}
protected override void OnDestroy() {
TestRunner = null;
base.OnDestroy();
}
protected override void OnStart() {
base.OnStart();
new Handler().Post(this.RunTests);
}
protected virtual void OnTestRunStarted() { }
protected virtual void OnTestRunEnded() { }
private void RunTests() {
this.Title = "Unit Tests for " + new AssemblyName(this.GetType().Assembly.FullName).Name;
if (TestRunner == null) {
try {
OnTestRunStarted();
}
catch (Exception e) {
MethodInfo method = GetType().GetMethod("OnTestRunStarted", BindingFlags.NonPublic|BindingFlags.Instance);
this.m_headerOutcome = new MethodOutcome(method);
this.m_headerOutcome.SetOutcome(e);
this.m_headerView.SetHtml(TestClassResultActivity.GetHTMLDescriptionFor(this.m_headerOutcome));
this.m_headerView.Visibility = ViewStates.Visible;
this.ResultBar.SetColorByState(TestState.Failed);
Toast.MakeText(this, "OnTestRunStarted() notification failed.", ToastLength.Long).Show();
return;
}
this.m_headerView.Visibility = ViewStates.Gone;
this.ResultBar.SetColorByState(TestState.Running);
AsyncTestRunner.Run(this, this.CreateTestRunner, this.OnTestRunFinished);
}
}
protected abstract TestRunner CreateTestRunner();
private void OnHeaderClick(object sender, EventArgs e) {
if (this.m_headerOutcome == null) {
return;
}
TestMethodResultActivity.StartActivity(this, this.m_headerOutcome);
}
private static int CompareResults(TestClass a, TestClass b) {
if (a.State == b.State) {
return a.Class.Name.CompareTo(b.Class.Name);
}
return a.State.CompareToForSorting(b.State);
}
private void OnTestRunFinished(TestRunner runner) {
if (runner == null) {
Toast.MakeText(this, "Error", ToastLength.Long).Show();
RunOnTestRunEnded();
return;
}
bool testRunNotificationOk = RunOnTestRunEnded();
if (runner.State == TestState.Passed && testRunNotificationOk) {
Toast.MakeText(this, "Finished. All tests passed.", ToastLength.Long).Show();
}
else {
Toast.MakeText(this, "Finished with some errors.", ToastLength.Long).Show();
}
if (testRunNotificationOk) {
this.ResultBar.SetColorByState(runner.State);
}
TestRunner = runner;
this.ListAdapter.Clear();
foreach (TestClass testClass in runner.GetTestClassesSorted(CompareResults)) {
this.ListAdapter.Add(testClass);
}
// Restore previous activity
if (!TestMethodResultActivity.RestoreActivity(this)) {
TestClassResultActivity.RestoreActivity(this);
}
}
private bool RunOnTestRunEnded() {
try {
OnTestRunEnded();
return true;
}
catch (Exception e) {
MethodInfo method = GetType().GetMethod("OnTestRunEnded", BindingFlags.NonPublic | BindingFlags.Instance);
this.m_headerOutcome = new MethodOutcome(method);
this.m_headerOutcome.SetOutcome(e);
this.m_headerView.SetHtml(TestClassResultActivity.GetHTMLDescriptionFor(this.m_headerOutcome));
this.m_headerView.Visibility = ViewStates.Visible;
this.ResultBar.SetColorByState(TestState.Failed);
return false;
}
}
protected override void OnResume() {
base.OnResume();
if (TestRunner != null) {
// Only remember this view if the test run finished and therefore the previous activity has been restored.
ISharedPreferencesEditor e = GetPreferences().Edit();
e.PutString(ACTIVITY_PREFS_NAME, "");
e.Commit();
}
}
// NOTE: We need to use "int" for result value and parameter type.
// See:
// * https://bugzilla.xamarin.com/show_bug.cgi?id=5980
// * https://bugzilla.xamarin.com/show_bug.cgi?id=5981
private class AsyncTestRunner : AsyncTask<int, int, int>, ITestResultHandler {
private readonly Func<TestRunner> m_testRunnerCreatorFunc;
private readonly Action<TestRunner> m_finishedHandler;
private readonly Handler m_guiHandler = new Handler();
private readonly ProgressDialog m_dialog;
private int m_curProgress = 0;
private int m_curSecondaryProgress = 0;
private TestRunner m_runner = null;
private AsyncTestRunner(Context ctx, Func<TestRunner> testRunnerCreatorFunc, Action<TestRunner> finishedHandler) {
this.m_testRunnerCreatorFunc = testRunnerCreatorFunc;
this.m_finishedHandler = finishedHandler;
this.m_dialog = new ProgressDialog(ctx);
this.m_dialog.SetProgressStyle(ProgressDialogStyle.Horizontal);
this.m_dialog.Indeterminate = true;
this.m_dialog.SetCancelable(false);
this.m_dialog.SetMessage("Running unit tests...");
}
public static void Run(Context ctx, Func<TestRunner> testRunnerCreatorFunc, Action<TestRunner> finishedHandler) {
AsyncTestRunner runner = new AsyncTestRunner(ctx, testRunnerCreatorFunc, finishedHandler);
runner.Execute();
}
protected override void OnPreExecute() {
this.m_dialog.Show();
}
protected override int RunInBackground(params int[] @params) {
try {
this.m_runner = this.m_testRunnerCreatorFunc();
this.m_guiHandler.PostAtFrontOfQueue(this.InitProgressBar);
this.m_runner.RunTests(this);
}
catch (Exception e) {
Log.Error(LOG_TAG, e.ToString());
this.m_runner = null;
}
return 0;
}
private void InitProgressBar() {
this.m_dialog.Indeterminate = false;
this.m_dialog.Progress = 0;
this.m_dialog.SecondaryProgress = 0;
this.m_dialog.Max = this.m_runner.TestMethodCount;
}
protected override void OnProgressUpdate(params int[] values) {
this.m_dialog.Progress = values[0];
this.m_dialog.SecondaryProgress = values[1];
}
protected override void OnPostExecute(int result) {
OnFinished();
}
protected override void OnCancelled() {
OnFinished();
}
private void OnFinished() {
this.m_dialog.Hide();
this.m_finishedHandler(this.m_runner);
}
public void OnTestRunStarted(TestRunner runner) { }
public void OnTestRunEnded(TestRunner runner) { }
public void OnTestClassTestStarted(TestClass testClass, int testClassIndex) {
this.m_curSecondaryProgress += testClass.TestMethodCount;
PublishProgress(this.m_curProgress, this.m_curSecondaryProgress);
}
public void OnTestClassError(TestClass testClass, int testClassIndex) {
this.m_curProgress = this.m_curSecondaryProgress;
PublishProgress(this.m_curProgress, this.m_curSecondaryProgress);
}
public void OnTestClassTestEnded(TestClass testClass, int testClassIndex) {
}
public void OnTestMethodStarted(TestMethod testMethod, int testMethodIndex) {
}
public void OnTestMethodEnded(TestMethod testMethod, int testMethodIndex) {
this.m_curProgress++;
PublishProgress(this.m_curProgress, this.m_curSecondaryProgress);
}
}
protected override ArrayAdapter<TestClass> CreateListAdapter() {
return new TestClassesAdapter(this);
}
protected override void OnItemClicked(AdapterView.ItemClickEventArgs e) {
TestClass testClass = this.ListAdapter.GetItem(e.Position);
TestClassResultActivity.StartActivity(this, testClass);
}
private class TestClassesAdapter : TestResultAdapter {
public TestClassesAdapter(GuiTestRunnerActivity activity) : base(activity) { }
protected override TestState GetStateFor(TestClass testClass) {
return testClass.State;
}
protected override string GetHTMLDescriptionFor(TestClass testClass) {
string text = "<b>" + testClass.Class.Name + "</b><br>";
switch (testClass.State) {
case TestState.NotYetRun:
text += "Not run";
break;
case TestState.Passed:
text += "All tests <font color=green>passed</font> (" + testClass.TestMethodCount + ")";
break;
case TestState.Failed:
text += testClass.GetStateCount(TestState.Failed) + " of " + testClass.TestMethodCount + " <font color=red>failed</font>";
break;
case TestState.Inconclusive:
text += testClass.GetStateCount(TestState.Inconclusive) + " of " + testClass.TestMethodCount + " <font color=red>inconclusive</font>";
break;
default:
throw new Exception("Unexpected");
}
return text;
}
}
}
}

View File

@ -0,0 +1,88 @@
//
// Copyright (C) 2012 Maya Studios (http://mayastudios.com)
//
// This file is part of MonoDroidUnitTesting.
//
// MonoDroidUnitTesting is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// MonoDroidUnitTesting 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 Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with MonoDroidUnitTesting. If not, see <http://www.gnu.org/licenses/>.
//
using System;
using Android.Util;
namespace MonoDroidUnitTesting {
public class LogTestRunner : ITestResultHandler {
private const string TAG = "Unit Test Runner";
private static string FormatException(string message, Exception e) {
return message + "\n " + string.Join("\n ", e.ToString().Split('\n'));
}
void ITestResultHandler.OnTestRunStarted(TestRunner runner) {
Log.Info(TAG, string.Format("Found {0} test classes...", runner.TestClassCount));
}
void ITestResultHandler.OnTestRunEnded(TestRunner runner) {
Log.Info(TAG, "Test run finished");
}
void ITestResultHandler.OnTestClassTestStarted(TestClass testClass, int testClassIndex) {
Log.Info(TAG, string.Format("Running test class {0} ({1} of {2})", testClass.Class.Name, testClassIndex,
testClass.TestMethodCount));
}
void ITestResultHandler.OnTestClassError(TestClass testClass, int testClassIndex) {
switch (testClass.ErrorType) {
case TestClassErrorType.ConstructorError:
Log.Error(TAG, FormatException("Could not create instance of class " + testClass.Class.Name, testClass.TestClassError));
break;
case TestClassErrorType.ClassInitializerError:
Log.Error(TAG, FormatException("Error in class initializing method of class " + testClass.Class.Name, testClass.TestClassError));
break;
case TestClassErrorType.ClassCleanupError:
Log.Error(TAG, FormatException("Error in class cleanup method of class " + testClass.Class.Name, testClass.TestClassError));
break;
default:
Log.Error(TAG, FormatException("Error (" + testClass.ErrorType + ") in class " + testClass.Class.Name, testClass.TestClassError));
break;
}
}
void ITestResultHandler.OnTestClassTestEnded(TestClass testClass, int testClassIndex) {
string headerMsg = string.Format("Test run completed. Results: {0}/{1} passed",
testClass.GetStateCount(TestState.Passed), testClass.TestMethodCount);
if (testClass.State == TestState.Passed) {
Log.Info(TAG, headerMsg);
}
else {
Log.Error(TAG, headerMsg);
}
}
void ITestResultHandler.OnTestMethodStarted(TestMethod testMethod, int testMethodIndex) {
// No op
}
void ITestResultHandler.OnTestMethodEnded(TestMethod testMethod, int testMethodIndex) {
if (testMethod.State == TestState.Passed) {
Log.Info(TAG, "Test passed: " + testMethod.Method.Name);
}
else {
Log.Error(TAG, FormatException("Error in test method " + testMethod.Method.Name, testMethod.OutcomeError.Exception));
}
}
}
}

View File

@ -0,0 +1,63 @@
//
// Copyright (C) 2012 Maya Studios (http://mayastudios.com)
//
// This file is part of MonoDroidUnitTesting.
//
// MonoDroidUnitTesting is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// MonoDroidUnitTesting 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 Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with MonoDroidUnitTesting. If not, see <http://www.gnu.org/licenses/>.
//
using Android.Content;
using Android.Graphics.Drawables;
using Android.Text;
using Android.Util;
using Android.Views;
using Android.Widget;
namespace MonoDroidUnitTesting {
internal class ResultListItemView : LinearLayout {
private readonly ImageView m_iconView;
private readonly TextView m_textView;
public ResultListItemView(Context ctx) : base(ctx) {
this.Orientation = Orientation.Horizontal;
int preferredListItemHeight = AbstractResultActivity.GetPreferredListItemHeight(ctx);
this.m_iconView = new ImageView(this.Context);
this.m_iconView.SetAdjustViewBounds(true);
this.m_iconView.LayoutParameters = MonoDroidUnitTesting.Utils.LayoutParams.ForLL(
width: LayoutParams.WrapContent, margin: 7, gravity: GravityFlags.Center);
this.m_iconView.SetMinimumHeight(preferredListItemHeight / 2);
this.m_iconView.SetMaxHeight(preferredListItemHeight / 2);
AddView(this.m_iconView);
this.m_textView = new TextView(this.Context);
this.m_textView.LayoutParameters = MonoDroidUnitTesting.Utils.LayoutParams.ForLL();
this.m_textView.SetTextSize(ComplexUnitType.Px, preferredListItemHeight / 4);
AddView(this.m_textView);
}
public void SetIcon(Drawable icon) {
this.m_iconView.SetImageDrawable(icon);
}
public void SetHtml(string htmlCode) {
this.m_textView.TextFormatted = Html.FromHtml(htmlCode);
}
public void SetText(string text) {
this.m_textView.Text = text;
}
}
}

View File

@ -0,0 +1,144 @@
//
// Copyright (C) 2012 Maya Studios (http://mayastudios.com)
//
// This file is part of MonoDroidUnitTesting.
//
// MonoDroidUnitTesting is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// MonoDroidUnitTesting 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 Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with MonoDroidUnitTesting. If not, see <http://www.gnu.org/licenses/>.
//
using System;
using Android.App;
using Android.Content;
using Android.Widget;
namespace MonoDroidUnitTesting {
[Activity]
public class TestClassResultActivity : AbstractResultListActivity<TestMethod> {
private const string INTENT_PARAM_NAME = "MonoDroidUnitTest.TestClass";
private TestClass m_testClass;
public TestClassResultActivity() : base("method_") { }
private static int CompareResults(TestMethod a, TestMethod b) {
if (a.State == b.State) {
return a.Method.Name.CompareTo(b.Method.Name);
}
return a.State.CompareToForSorting(b.State);
}
internal static void StartActivity(Context ctx, TestClass testClass) {
Intent i = new Intent(ctx, typeof(TestClassResultActivity));
i.PutExtra(INTENT_PARAM_NAME, testClass.Class.AssemblyQualifiedName);
ctx.StartActivity(i);
}
protected override void OnStart() {
base.OnStart();
if (GuiTestRunnerActivity.TestRunner == null) {
// Only happens during deployment.
return;
}
string testClassName = this.Intent.GetCharSequenceExtra(INTENT_PARAM_NAME);
TestClass testClass = GuiTestRunnerActivity.TestRunner.GetTestClass(testClassName);
this.Title = "Results for " + testClass.Class.Name;
this.ResultBar.SetColorByState(testClass.State);
this.m_testClass = testClass;
this.ListAdapter.Clear();
foreach (TestMethod testMethod in testClass.GetTestMethodsSorted(CompareResults)) {
this.ListAdapter.Add(testMethod);
}
}
protected override ArrayAdapter<TestMethod> CreateListAdapter() {
return new TestMethodsAdapter(this);
}
protected override void OnItemClicked(AdapterView.ItemClickEventArgs e) {
TestMethod testMethod = this.ListAdapter.GetItem(e.Position);
TestMethodResultActivity.StartActivity(this, testMethod);
}
protected override void OnResume() {
base.OnResume();
ISharedPreferencesEditor e = GetPreferences().Edit();
e.PutString(ACTIVITY_PREFS_NAME, typeof(TestClassResultActivity).Name);
e.PutString(INTENT_PARAM_NAME, this.m_testClass.Class.AssemblyQualifiedName);
e.Commit();
}
internal static bool RestoreActivity(Context ctx) {
var prefs = GetPreferences();
if (prefs.GetString(ACTIVITY_PREFS_NAME, "") != typeof(TestClassResultActivity).Name) {
return false;
}
string className = prefs.GetString(INTENT_PARAM_NAME, "");
TestClass testClass = GuiTestRunnerActivity.TestRunner.GetTestClass(className);
if (testClass == null) {
// Class is not longer under test or not set
return true;
}
StartActivity(ctx, testClass);
return true;
}
internal static string GetHTMLDescriptionFor(MethodOutcome testMethod) {
string text = "<b>" + testMethod.Method.Name + "()</b><br>";
switch (testMethod.State) {
case TestState.NotYetRun:
text += "Skipped";
break;
case TestState.Passed:
text += "<font color=green>passed</font>";
break;
case TestState.Failed:
text += "<font color=red>failed</font> with " + testMethod.OutcomeError.Exception.GetType().Name;
break;
case TestState.Inconclusive:
text += "<font color=red>inconclusive</font>";
break;
default:
throw new Exception("Unexpected");
}
return text;
}
private class TestMethodsAdapter : TestResultAdapter {
public TestMethodsAdapter(TestClassResultActivity activity) : base(activity) { }
protected override TestState GetStateFor(TestMethod testMethod) {
return testMethod.State;
}
protected override string GetHTMLDescriptionFor(TestMethod testMethod) {
return TestClassResultActivity.GetHTMLDescriptionFor(testMethod);
}
}
}
}

View File

@ -0,0 +1,475 @@
//
// Copyright (C) 2012 Maya Studios (http://mayastudios.com)
//
// This file is part of MonoDroidUnitTesting.
//
// MonoDroidUnitTesting is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// MonoDroidUnitTesting 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 Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with MonoDroidUnitTesting. If not, see <http://www.gnu.org/licenses/>.
//
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using Android.App;
using Android.Content;
using Android.Graphics;
using Android.Text;
using Android.Text.Method;
using Android.Util;
using Android.Views;
using Android.Widget;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MonoDroidUnitTesting.Utils;
namespace MonoDroidUnitTesting {
[Activity]
public class TestMethodResultActivity : AbstractResultActivity {
private const string INTENT_CLASS_PARAM = "MonoDroidUnitTest.TestClass";
private const string INTENT_METHOD_PARAM = "MonoDroidUnitTest.TestMethod";
private MethodOutcome m_testMethod;
private ImageView m_outcomeIcon;
private TextView m_headerText;
private TextView m_resultText;
private LinearLayout m_stackTraceInfo;
private TextView m_stackTraceText;
public TestMethodResultActivity() : base("method_") { }
protected override View CreateMainView() {
LinearLayout mainLayout = new LinearLayout(this);
mainLayout.Orientation = Orientation.Vertical;
CreateHeaderSection(mainLayout);
CreateResultSection(mainLayout);
CreateStackTraceSection(mainLayout);
return mainLayout;
}
private void CreateHeaderSection(LinearLayout mainLayout) {
LinearLayout headerLayout = new LinearLayout(this);
headerLayout.Orientation = Orientation.Horizontal;
headerLayout.LayoutParameters = LayoutParams.ForLL(marginBottom: 24);
this.m_outcomeIcon = new ImageView(this);
this.m_outcomeIcon.SetAdjustViewBounds(true);
this.m_outcomeIcon.LayoutParameters = LayoutParams.ForLL(width: LayoutParams.WrapContent, margin: 7,
gravity: GravityFlags.Top|GravityFlags.CenterHorizontal);
this.m_outcomeIcon.SetMinimumHeight(GetPreferredListItemHeight() / 2);
this.m_outcomeIcon.SetMaxHeight(GetPreferredListItemHeight() / 2);
headerLayout.AddView(this.m_outcomeIcon);
this.m_headerText = new TextView(this);
this.m_headerText.LayoutParameters = LayoutParams.ForLL();
this.m_headerText.SetTextSize(ComplexUnitType.Px, GetPreferredListItemHeight() / 4);
headerLayout.AddView(this.m_headerText);
mainLayout.AddView(headerLayout);
}
private void CreateResultSection(LinearLayout mainLayout) {
this.m_resultText = new TextView(this);
this.m_resultText.LayoutParameters = LayoutParams.ForLL(margin: 7, marginBottom: 24);
this.m_resultText.SetTextSize(ComplexUnitType.Px, GetPreferredListItemHeight() / 4);
this.m_resultText.SetPadding(5, 2, 5, 2);
this.m_resultText.SetBackgroundColor(new Color(35, 35, 35));
mainLayout.AddView(this.m_resultText);
}
private void CreateStackTraceSection(LinearLayout mainLayout) {
this.m_stackTraceInfo = new LinearLayout(this);
this.m_stackTraceInfo.Orientation = Orientation.Vertical;
this.m_stackTraceInfo.LayoutParameters = LayoutParams.ForLL(weight: 1);
Button logButton = new Button(this);
logButton.Text = "Dump exception to logcat";
logButton.Click += (s, e) => DumpStackTrace();
logButton.LayoutParameters = LayoutParams.ForLL();
this.m_stackTraceInfo.AddView(logButton);
if (!AreLineNumbersAvailable()) {
TextView noteText = new TextView(this);
noteText.LayoutParameters = LayoutParams.ForLL(margin: 7, marginTop: 0);
noteText.TextFormatted = Html.FromHtml("<b>Line numbers are unavailable because no debugger is attached.");
noteText.SetPadding(5, 2, 5, 2);
noteText.Gravity = GravityFlags.Center;
noteText.SetBackgroundColor(new Color(110, 3, 15));
this.m_stackTraceInfo.AddView(noteText);
}
this.m_stackTraceText = new TextView(this);
this.m_stackTraceText.Typeface = Typeface.Monospace;
this.m_stackTraceText.SetTextSize(ComplexUnitType.Px, GetPreferredListItemHeight() / 6);
this.m_stackTraceText.LayoutParameters = LayoutParams.ForLL(weight: 1);
this.m_stackTraceText.VerticalScrollBarEnabled = true;
this.m_stackTraceText.MovementMethod = new ScrollingMovementMethod();
// Necessary so that the text color doesn't get darkened when scrolling the text view
this.m_stackTraceText.SetTextColor(Color.White);
this.m_stackTraceInfo.AddView(this.m_stackTraceText);
mainLayout.AddView(this.m_stackTraceInfo);
}
private static bool AreLineNumbersAvailable() {
try {
throw new Exception();
}
catch (Exception e) {
StackTrace st = new StackTrace(e, true);
return (st.GetFrame(0).GetFileName() != null);
}
}
private void DumpStackTrace() {
// NOTE: Don't dump "this.m_stackTraceText.Text" as this text has specifically modified to be displayed.
Log.Info(LOG_TAG, this.m_testMethod.OutcomeError.ToString());
Toast toast = Toast.MakeText(this, "Exception dumped under tag '" + LOG_TAG + "'", ToastLength.Long);
toast.Show();
}
private static MethodOutcome s_testMethodParam = null;
internal static void StartActivity(Context ctx, MethodOutcome testMethod) {
s_testMethodParam = testMethod;
Intent i = new Intent(ctx, typeof(TestMethodResultActivity));
//i.PutExtra(INTENT_CLASS_PARAM, testMethod.Class.Class.AssemblyQualifiedName);
//i.PutExtra(INTENT_METHOD_PARAM, testMethod.Method.Name);
ctx.StartActivity(i);
}
protected override void OnStart() {
base.OnStart();
/*string testClassName = this.Intent.GetCharSequenceExtra(INTENT_CLASS_PARAM);
TestClass testClass = GuiTestRunnerActivity.TestRunner.GetTestClass(testClassName);
string testMethodName = this.Intent.GetCharSequenceExtra(INTENT_METHOD_PARAM);
TestMethod testMethod = testClass.GetTestMethod(testMethodName);*/
if (s_testMethodParam != null) {
FillActivity(s_testMethodParam);
}
}
private void FillActivity(MethodOutcome testMethod) {
this.m_testMethod = testMethod;
this.Title = "Result for " + this.m_testMethod.Method.Name;
this.ResultBar.SetColorByState(this.m_testMethod.State);
FillHeaderSection();
FillResultSection();
FillStackTraceSection();
}
private void FillHeaderSection() {
this.m_outcomeIcon.SetImageDrawable(GetIconForState(this.m_testMethod.State));
string headerText = "<b>" + this.m_testMethod.Method.Name + "()</b><br>"
+ "<small>Class: " + this.m_testMethod.Method.DeclaringType.Name + "<br>"
+ "Namespace: " + this.m_testMethod.Method.DeclaringType.Namespace + "</small>";
this.m_headerText.TextFormatted = Html.FromHtml(headerText);
}
private void FillResultSection() {
StringBuilder resultText = new StringBuilder();
resultText.Append("<b>Outcome: ");
switch (this.m_testMethod.State) {
case TestState.NotYetRun:
resultText.Append("Skipped");
break;
case TestState.Passed:
resultText.Append("<font color=green>passed</font>");
break;
case TestState.Failed:
resultText.Append("<font color=red>failed</font>");
break;
case TestState.Inconclusive:
resultText.Append("<font color=red>inconclusive</font>");
break;
default:
resultText.Append(this.m_testMethod.State.ToString());
break;
}
if (this.m_testMethod.OutcomeError != null) {
resultText.Append("</b><br><small>").Append(this.m_testMethod.OutcomeError.Message);
}
this.m_resultText.TextFormatted = Html.FromHtml(resultText.ToString());
}
private void FillStackTraceSection() {
if (this.m_testMethod.OutcomeError == null) {
this.m_stackTraceInfo.Visibility = ViewStates.Gone;
}
else {
this.m_stackTraceText.Text = CreateExceptionString(this.m_testMethod.OutcomeError, this.m_stackTraceText);
this.m_stackTraceInfo.Visibility = ViewStates.Visible;
}
}
private static string CreateExceptionString(TestError e, TextView tv) {
List<string> lines = new List<string>();
FormatException(e, lines, false);
int maxChar = FindCharacterLineCount(tv);
StringBuilder sb = new StringBuilder(512);
foreach (string line in lines) {
if (line == "") {
sb.AppendLine();
}
else {
sb.Append(WordWrap(line, maxChar));
}
}
return sb.ToString();
}
private static List<StackFrame> FilterStackTrace(Exception e) {
StackTrace st = new StackTrace(e, true);
List<StackFrame> stackFrames = new List<StackFrame>(st.GetFrames());
// Last stack frame is always "MonoMethod.Invoke()". Remove it.
stackFrames.RemoveAt(stackFrames.Count - 1);
// Search for first unit testing api frame
int index = -1;
foreach (StackFrame sf in stackFrames) {
MethodBase method = sf.GetMethod();
if (method.DeclaringType.Namespace != typeof(Assert).Namespace) {
break;
}
index++;
}
if (index != -1) {
stackFrames.RemoveRange(0, index);
}
return stackFrames;
}
// Make special string to be displayed on an Android display
private static void FormatException(TestError e, List<string> lines, bool isInnerException) {
lines.Add(e.Exception.GetType().Name + ":");
// We don't add the message here as it has already been written above the stack trace.
int index = 0;
IEnumerable<StackFrame> frames;
if (isInnerException) {
StackTrace st = new StackTrace(e.Exception, true);
frames = st.GetFrames();
}
else {
frames = FilterStackTrace(e.Exception);
}
foreach (StackFrame sf in frames) {
MethodBase method = sf.GetMethod();
bool isTestMethod = (method.GetCustomAttributes(typeof(TestMethodAttribute), true).Length != 0);
lines.Add(string.Format(" at {0}.{1}({2})", method.DeclaringType.Name, method.Name,
(method.GetParameters().Length == 0 ? "" : "..."))
);
if (sf.GetFileName() != null) {
// line numbers and file names are available (only when debugger is attached)
string filename = sf.GetFileName();
filename = filename.Substring(filename.LastIndexOfAny(new char[] { '\\', '/' }) + 1);
lines.Add(string.Format(" in {0}:{1}", filename, sf.GetFileLineNumber()));
}
index++;
}
if (e.Exception.InnerException != null) {
lines.Add("");
lines.Add("------- inner exception ---------");
lines.Add("");
FormatException(new TestError(e.Exception.InnerException), lines, true);
}
}
private static int FindCharacterLineCount(TextView tv) {
int viewWidth = tv.Context.Resources.DisplayMetrics.WidthPixels;
float charWidth = tv.Paint.MeasureText("M");
// -1 to be on the safe side
return (int)(viewWidth / charWidth - 1);
}
// The word wrapping methods are from:
// http://www.codeproject.com/Articles/51488/Implementing-Word-Wrap-in-C
/// <summary>
/// Word wraps the given text to fit within the specified width.
/// </summary>
/// <param name="text">Text to be word wrapped</param>
/// <param name="width">Width, in characters, to which the text
/// should be word wrapped</param>
/// <returns>The modified text</returns>
private static string WordWrap(string text, int width) {
int pos, next;
StringBuilder sb = new StringBuilder();
// Lucidity check
if (width < 1)
return text;
// Parse each line of text
for (pos = 0; pos < text.Length; pos = next) {
// Find end of line
int eol = text.IndexOf('\n', pos);
if (eol == -1)
next = eol = text.Length;
else
next = eol + 1;
// Copy this line of text, breaking into smaller lines as needed
if (eol > pos) {
int indent = 0;
while (pos + indent < eol && Char.IsWhiteSpace(text[pos + indent])) {
indent++;
}
indent += 2;
bool isFirstLine = true;
do {
int len = eol - pos;
if (isFirstLine) {
if (len > width) {
len = BreakLine(text, pos, width);
}
}
else {
if (len > width - indent) {
len = BreakLine(text, pos, width - indent);
}
}
if (!isFirstLine) {
sb.Append(' ', indent);
}
sb.Append(text, pos, len);
sb.Append("\n");
// Trim whitespace following break
pos += len;
while (pos < eol && text[pos] == ' ') {
pos++;
}
isFirstLine = false;
}
while (eol > pos);
}
else {
// Empty line
sb.Append('\n');
}
}
return sb.ToString();
}
/// <summary>
/// Locates position to break the given line so as to avoid
/// breaking words.
/// </summary>
/// <param name="text">String that contains line of text</param>
/// <param name="pos">Index where line of text starts</param>
/// <param name="max">Maximum line length</param>
/// <returns>The modified line length</returns>
private static int BreakLine(string text, int pos, int max) {
// Find last whitespace in line
int i = max;
while (i >= 0) {
switch (text[pos + i]) {
case ' ':
case '.':
case '(':
goto After;
}
i--;
}
After:
// If no whitespace found, break at maximum length
if (i < 0)
return max;
// Find start of whitespace
while (i >= 0 && text[pos + i] == ' ') {
i--;
}
// Return length of text before whitespace
return i + 1;
}
protected override void OnResume() {
base.OnResume();
ISharedPreferencesEditor e = GetPreferences().Edit();
e.PutString(ACTIVITY_PREFS_NAME, typeof(TestMethodResultActivity).Name);
TestMethod testMethod = this.m_testMethod as TestMethod;
if (testMethod != null) {
// Only for actual test methods. No way for just test outcomes.
e.PutString(INTENT_CLASS_PARAM, testMethod.Class.Class.AssemblyQualifiedName);
e.PutString(INTENT_METHOD_PARAM, testMethod.Method.Name);
}
e.Commit();
}
internal static bool RestoreActivity(Context ctx) {
var prefs = GetPreferences();
if (prefs.GetString(ACTIVITY_PREFS_NAME, "") != typeof(TestMethodResultActivity).Name) {
return false;
}
string className = prefs.GetString(INTENT_CLASS_PARAM, "");
TestClass testClass = GuiTestRunnerActivity.TestRunner.GetTestClass(className);
if (testClass == null) {
// Class is not longer under test or not set
return true;
}
string methodName = prefs.GetString(INTENT_METHOD_PARAM, "");
TestMethod testMethod = testClass.GetTestMethod(methodName);
if (testMethod == null) {
// Test class no longer contains this test method
return true;
}
StartActivity(ctx, testMethod);
return true;
}
}
}

View File

@ -0,0 +1,165 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

View File

@ -0,0 +1,94 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.30703</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{A5F8FB02-00E0-4335-91EF-AEAA2C2F3C48}</ProjectGuid>
<ProjectTypeGuids>{EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>MonoDroidUnitTesting</RootNamespace>
<AssemblyName>MonoDroidUnitTesting</AssemblyName>
<FileAlignment>512</FileAlignment>
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
<AndroidSupportedAbis>armeabi</AndroidSupportedAbis>
<AndroidStoreUncompressedFileExtensions />
<TargetFrameworkVersion>v2.2</TargetFrameworkVersion>
<MandroidI18n />
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Mono.Android" />
<Reference Include="System" />
<Reference Include="System.Core" />
</ItemGroup>
<ItemGroup>
<Compile Include="API\Assert.cs" />
<Compile Include="API\AssertFailedException.cs" />
<Compile Include="API\CollectionAssert.cs" />
<Compile Include="API\StringAssert.cs" />
<Compile Include="Frontends\ResultListItemView.cs" />
<Compile Include="Runner\MethodOutcome.cs" />
<Compile Include="Runner\TestError.cs" />
<Compile Include="Utils\LayoutParams.cs" />
<Compile Include="Utils\StringExtensions.cs" />
<Compile Include="API\TestAttributes.cs" />
<Compile Include="Frontends\AbstractResultActivity.cs" />
<Compile Include="Frontends\AbstractResultListActivity.cs" />
<Compile Include="Utils\Dimension.cs" />
<Compile Include="Frontends\GuiTestRunnerActivity.cs" />
<Compile Include="Frontends\TestClassResultActivity.cs" />
<Compile Include="Frontends\TestMethodResultActivity.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Frontends\LogTestRunner.cs" />
<Compile Include="Runner\TestClass.cs" />
<Compile Include="Runner\TestMethod.cs" />
<Compile Include="Runner\TestState.cs" />
<Compile Include="Runner\TestRunner.cs" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\Drawable\outcome_passed.png" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\Drawable\outcome_error.png" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\Drawable\outcome_inconclusive.png" />
</ItemGroup>
<ItemGroup>
<None Include="LICENSE.lgplv3.txt" />
<Content Include="README.txt" />
<EmbeddedResource Include="Resources\Drawable\class_outcome_error.png" />
<EmbeddedResource Include="Resources\Drawable\method_outcome_passed.png" />
<EmbeddedResource Include="Resources\Drawable\class_outcome_inconclusive.png" />
<EmbeddedResource Include="Resources\Drawable\class_outcome_passed.png" />
<EmbeddedResource Include="Resources\Drawable\method_outcome_error.png" />
<EmbeddedResource Include="Resources\Drawable\method_outcome_inconclusive.png" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Novell\Novell.MonoDroid.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@ -0,0 +1,29 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("MonoDroidUnitTesting")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("MonoDroidUnitTesting")]
[assembly: AssemblyCopyright("Copyright © 2012")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: ComVisible(false)]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@ -0,0 +1,19 @@
To use this library, create a new Mono for Android Application project, reference this library and then change the code
of the new main activity to something like this:
[Activity(Label = "MonoDroidUnit", MainLauncher = true, Icon = "@drawable/icon")]
public class Activity1 : GuiTestRunnerActivity {
protected override TestRunner CreateTestRunner() {
TestRunner runner = new TestRunner();
// Run all tests from this assembly
runner.AddTests(Assembly.GetExecutingAssembly());
return runner;
}
}
You need to inherit from "GuiTestRunnerActivity" and implement the method "CreateTestRunner()".
IMPORTANT: Mono for Android (at least in version 4.2.3) doesn't provide any file names and line numbers in stack traces
when running without a debugger attached. If you need line numbers in your stack traces, make sure you run your
unit test with debugger attached.

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

View File

@ -0,0 +1,78 @@
//
// Copyright (C) 2012 Maya Studios (http://mayastudios.com)
//
// This file is part of MonoDroidUnitTesting.
//
// MonoDroidUnitTesting is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// MonoDroidUnitTesting 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 Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with MonoDroidUnitTesting. If not, see <http://www.gnu.org/licenses/>.
//
using System;
using System.Reflection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace MonoDroidUnitTesting {
/// <summary>
/// Represents a test method.
/// </summary>
public class MethodOutcome {
/// <summary>
/// The test method.
/// </summary>
public MethodInfo Method { get; private set; }
/// <summary>
/// The state (outcome) of the test.
/// </summary>
public TestState State { get; protected set; }
/// <summary>
/// The exception that lead to the failure of the test, if it failed. Is <c>null</c> for passed tests.
/// </summary>
public TestError OutcomeError { get; private set; }
public MethodOutcome(MethodInfo method) {
this.Method = method;
Reset();
}
/// <summary>
/// Usually this method shouldn't be invoked directly. Use <see cref="TestClass.Reset"/> instead.
/// </summary>
public virtual void Reset() {
this.State = TestState.NotYetRun;
this.OutcomeError = null;
}
/// <summary>
/// Manually set the outcome of this method. If you use <see cref="Run"/>, you don't need to call this method.
/// </summary>
/// <param name="outcomeError">the exception resulted from calling the method. <c>null</c> if the method didn't
/// throw an exception.</param>
public void SetOutcome(Exception outcomeError) {
if (outcomeError == null) {
this.OutcomeError = null;
this.State = TestState.Passed;
}
else {
if (outcomeError is AssertInconclusiveException) {
this.State = TestState.Inconclusive;
}
else {
this.State = TestState.Failed;
}
this.OutcomeError = new TestError(outcomeError);
}
}
}
}

View File

@ -0,0 +1,235 @@
//
// Copyright (C) 2012 Maya Studios (http://mayastudios.com)
//
// This file is part of MonoDroidUnitTesting.
//
// MonoDroidUnitTesting is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// MonoDroidUnitTesting 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 Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with MonoDroidUnitTesting. If not, see <http://www.gnu.org/licenses/>.
//
using System;
using System.Collections.Generic;
using System.Reflection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace MonoDroidUnitTesting {
/// <summary>
/// Represents a collection of test methods.
/// </summary>
public class TestClass {
public Type Class { get; private set; }
private readonly SortedDictionary<string, TestMethod> m_testMethods = new SortedDictionary<string, TestMethod>();
public IEnumerable<TestMethod> TestMethods {
get {
foreach (var item in this.m_testMethods) {
yield return item.Value;
}
}
}
public int TestMethodCount {
get { return this.m_testMethods.Count; }
}
private static readonly int OUTCOME_TYPE_COUNT = Enum.GetValues(typeof(TestState)).Length;
private readonly Dictionary<TestState, int> m_stateCount = new Dictionary<TestState, int>();
public MethodInfo ClassSetupMethod { get; private set; }
public MethodInfo ClassTeardownMethod { get; private set; }
public MethodInfo SetupMethod { get; private set; }
public MethodInfo TeardownMethod { get; private set; }
public TestState State { get; private set; }
public Exception TestClassError { get; private set; }
public TestClassErrorType? ErrorType { get; private set; }
internal TestClass(Type @class, bool addTestMethods) {
this.Class = @class;
Reset();
foreach (MethodInfo method in @class.GetMethods()) {
if (!IsValidTestMethod(method)) {
// Invalid method
continue;
}
if (method.GetCustomAttributes(typeof(TestMethodAttribute), true).Length != 0) {
if (addTestMethods) {
AddTestMethod(method);
}
}
else if (method.GetCustomAttributes(typeof(TestInitializeAttribute), true).Length != 0) {
this.SetupMethod = method;
}
else if (method.GetCustomAttributes(typeof(TestCleanupAttribute), true).Length != 0) {
this.TeardownMethod = method;
}
else if (method.GetCustomAttributes(typeof(ClassInitializeAttribute), true).Length != 0) {
this.ClassSetupMethod = method;
}
else if (method.GetCustomAttributes(typeof(ClassCleanupAttribute), true).Length != 0) {
this.ClassTeardownMethod = method;
}
}
}
private static bool IsValidTestMethod(MethodInfo method) {
return ( !method.IsAbstract && !method.IsConstructor && !method.IsGenericMethod
&& method.GetParameters().Length == 0 && method.IsPublic);
}
internal void AddAllTestMethods() {
foreach (MethodInfo method in this.Class.GetMethods()) {
if (!IsValidTestMethod(method)) {
// Invalid method
continue;
}
if (method.GetCustomAttributes(typeof(TestMethodAttribute), true).Length != 0) {
AddTestMethod(method);
}
}
}
internal void AddTestMethod(MethodInfo method) {
if (method.DeclaringType != this.Class) {
throw new ArgumentException();
}
if (this.m_testMethods.ContainsKey(method.Name)) {
return; // just ignore this
}
if (!IsValidTestMethod(method)) {
throw new ArgumentException("Method " + method.Name + " is not a valid test method.");
}
this.m_testMethods.Add(method.Name, new TestMethod(this, method));
this.m_stateCount[TestState.NotYetRun] = this.m_stateCount[TestState.NotYetRun] + 1;
}
internal void Run(ITestResultHandler resultHandler, int testClassIndex) {
this.State = TestState.Running;
resultHandler.OnTestClassTestStarted(this, testClassIndex);
object testClassInstance;
try {
testClassInstance = Activator.CreateInstance(this.Class);
}
catch (Exception e) {
if (e is TargetInvocationException && e.InnerException != null) {
e = e.InnerException;
}
this.State = TestState.Failed;
this.TestClassError = e;
this.ErrorType = TestClassErrorType.ConstructorError;
resultHandler.OnTestClassError(this, testClassIndex);
return;
}
if (this.ClassSetupMethod != null) {
var e = TestMethod.InvokeTestMethod(testClassInstance, this.ClassSetupMethod);
if (e != null) {
this.State = TestState.Failed;
this.TestClassError = e;
this.ErrorType = TestClassErrorType.ClassInitializerError;
resultHandler.OnTestClassError(this, testClassIndex);
return;
}
}
int testMethodIndex = 1;
this.m_stateCount[TestState.Running] = 1;
foreach (TestMethod testMethod in this.TestMethods) {
testMethod.Run(testClassInstance, testMethodIndex, resultHandler);
this.m_stateCount[testMethod.State] = this.m_stateCount[testMethod.State] + 1;
this.m_stateCount[TestState.NotYetRun] = this.m_stateCount[TestState.NotYetRun] - 1;
testMethodIndex++;
}
this.m_stateCount[TestState.Running] = 0;
if (this.ClassTeardownMethod != null) {
var e = TestMethod.InvokeTestMethod(testClassInstance, this.ClassTeardownMethod);
if (e != null) {
this.State = TestState.Failed;
this.TestClassError = e;
this.ErrorType = TestClassErrorType.ClassCleanupError;
resultHandler.OnTestClassError(this, testClassIndex);
return;
}
}
if (GetStateCount(TestState.Failed) != 0) {
this.State = TestState.Failed;
}
else if (GetStateCount(TestState.Inconclusive) != 0) {
this.State = TestState.Inconclusive;
}
else {
this.State = TestState.Passed;
}
resultHandler.OnTestClassTestEnded(this, testClassIndex);
}
public int GetStateCount(TestState outcomeType) {
return this.m_stateCount[outcomeType];
}
internal void Reset() {
foreach (TestMethod method in this.TestMethods) {
method.Reset();
}
for (int i = 0; i < OUTCOME_TYPE_COUNT; i++) {
this.m_stateCount[(TestState)i] = 0;
}
this.m_stateCount[TestState.NotYetRun] = this.TestMethodCount;
this.State = TestState.NotYetRun;
this.TestClassError = null;
this.ErrorType = null;
}
public TestMethod GetTestMethod(string name) {
TestMethod testMethod;
if (this.m_testMethods.TryGetValue(name, out testMethod)) {
return testMethod;
}
return null;
}
public List<TestMethod> GetTestMethodsSorted(Comparison<TestMethod> comparer) {
List<TestMethod> sorted = new List<TestMethod>(this.m_testMethods.Values);
sorted.Sort(comparer);
return sorted;
}
}
public enum TestClassErrorType {
ConstructorError,
ClassInitializerError,
ClassCleanupError
}
}

View File

@ -0,0 +1,57 @@
//
// Copyright (C) 2012 Maya Studios (http://mayastudios.com)
//
// This file is part of MonoDroidUnitTesting.
//
// MonoDroidUnitTesting is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// MonoDroidUnitTesting 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 Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with MonoDroidUnitTesting. If not, see <http://www.gnu.org/licenses/>.
//
using System;
using System.Reflection;
namespace MonoDroidUnitTesting {
public class TestError {
/// <summary>
/// The exception that's representing this error.
/// </summary>
public Exception Exception { get; private set; }
/// <summary>
/// The error message of this exception. Note that the message may differ from <c>this.Exception.Message</c>, if
/// the exception is a Java exception. In this case, this property contains the actual message (and should therefore
/// be preferred over <c>Exception.Message</c>).
/// </summary>
public string Message { get; private set; }
public TestError(Exception e) {
while (e is TargetInvocationException && e.InnerException != null) {
e = e.InnerException;
}
this.Exception = e;
if (e is Java.Lang.Throwable) {
// Throwable provides a new Message property (hiding the Message property of System.Exception), containing the
// actual message.
this.Message = ((Java.Lang.Throwable)e).Message;
}
else {
this.Message = e.Message;
}
}
public override string ToString() {
return this.Exception.ToString();
}
}
}

View File

@ -0,0 +1,114 @@
//
// Copyright (C) 2012 Maya Studios (http://mayastudios.com)
//
// This file is part of MonoDroidUnitTesting.
//
// MonoDroidUnitTesting is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// MonoDroidUnitTesting 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 Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with MonoDroidUnitTesting. If not, see <http://www.gnu.org/licenses/>.
//
using System;
using System.Reflection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace MonoDroidUnitTesting {
/// <summary>
/// Represents a test method.
/// </summary>
public class TestMethod : MethodOutcome {
private static readonly object[] NO_PARAMS = new object[0];
/// <summary>
/// The test class this test method belongs to. Never null.
/// </summary>
public TestClass Class { get; private set; }
internal TestMethod(TestClass testClass, MethodInfo method) : base(method) {
this.Class = testClass;
}
/// <summary>
/// Usually this method shouldn't be invoked directly. Use <see cref="TestClass.Run"/> instead. If no test class has
/// been specified in the constructor, this method only executes the method itself (but no class setup or teardown
/// methods).
/// </summary>
public void Run(object testClassInstance, int testMethodIndex, ITestResultHandler resultHandler) {
this.State = TestState.Running;
resultHandler.OnTestMethodStarted(this, testMethodIndex);
Exception outcomeError = null;
if (this.Class.SetupMethod != null) {
outcomeError = InvokeTestMethod(testClassInstance, this.Class.SetupMethod);
}
if (outcomeError == null) {
// No error in the setup method
outcomeError = InvokeTestMethod(testClassInstance, this.Method);
if (outcomeError != null && IsExpectedException(this.Method, outcomeError)) {
outcomeError = null;
}
if (this.Class.TeardownMethod != null) {
var e = InvokeTestMethod(testClassInstance, this.Class.TeardownMethod);
// Make sure we don't override the actual failure reson.
if (e != null && outcomeError == null) {
outcomeError = e;
}
}
}
SetOutcome(outcomeError);
resultHandler.OnTestMethodEnded(this, testMethodIndex);
}
private static bool IsExpectedException(MethodInfo method, Exception e) {
var attributes = method.GetCustomAttributes(typeof(ExpectedExceptionAttribute), true);
if (attributes.Length == 0) {
return false;
}
Type type = e.GetType();
foreach (ExpectedExceptionAttribute attribute in attributes) {
if (attribute.AllowDerivedTypes) {
if (attribute.ExceptionType.IsAssignableFrom(type)) {
return true;
}
}
else {
if (type == attribute.ExceptionType) {
return true;
}
}
}
return false;
}
internal static Exception InvokeTestMethod(object testClassInstance, MethodInfo method) {
try {
method.Invoke(testClassInstance, NO_PARAMS);
return null;
}
catch (Exception e) {
if (e is TargetInvocationException && e.InnerException != null) {
return e.InnerException;
}
return e;
}
}
}
}

View File

@ -0,0 +1,227 @@
//
// Copyright (C) 2012 Maya Studios (http://mayastudios.com)
//
// This file is part of MonoDroidUnitTesting.
//
// MonoDroidUnitTesting is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// MonoDroidUnitTesting 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 Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with MonoDroidUnitTesting. If not, see <http://www.gnu.org/licenses/>.
//
using System;
using System.Collections.Generic;
using System.Reflection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace MonoDroidUnitTesting {
public class TestRunner {
private readonly SortedDictionary<string, TestClass> m_testClasses = new SortedDictionary<string, TestClass>();
/// <summary>
/// All registered test classes.
/// </summary>
public IEnumerable<TestClass> TestClasses {
get {
foreach (var item in this.m_testClasses) {
yield return item.Value;
}
}
}
public int TestClassCount {
get {
return this.m_testClasses.Count;
}
}
public int TestMethodCount {
get {
int count = 0;
foreach (TestClass testClass in this.TestClasses) {
count += testClass.TestMethodCount;
}
return count;
}
}
public TestState State {
get {
if (this.m_isRunning) {
return TestState.Running;
}
TestState state = TestState.Passed;
foreach (TestClass testClass in this.TestClasses) {
switch (testClass.State) {
case TestState.Failed:
return TestState.Failed;
case TestState.Inconclusive:
// Don't return here. State may be "Failed" after all.
state = TestState.Inconclusive;
break;
// Ignore all other states
}
}
return state;
}
}
private static List<Type> GetAllTestClasses(Assembly assembly) {
List<Type> testClasses = new List<Type>();
foreach (Type type in assembly.GetTypes()) {
if (IsTestClass(type)) {
testClasses.Add(type);
}
}
return testClasses;
}
private static bool IsTestClass(Type type) {
return (type.IsClass && type.GetCustomAttributes(typeof(TestClassAttribute), true).Length != 0);
}
private bool m_isRunning = false;
/// <summary>
/// Adds all test classes in the specified assembly. Test classes must have the <c>[TestClass]</c> attribute.
/// </summary>
public void AddTests(Assembly assembly) {
AddTests(GetAllTestClasses(assembly));
}
/// <summary>
/// Adds all test classes in the specified namespace in the specified assembly. Test classes must have the
/// <c>[TestClass]</c> attribute.
/// </summary>
public void AddTests(Assembly assembly, string @namespace) {
List<Type> testClasses = new List<Type>();
foreach (Type testClass in GetAllTestClasses(assembly)) {
if (testClass.Namespace == @namespace) {
testClasses.Add(testClass);
}
}
AddTests(testClasses);
}
/// <summary>
/// Adds the specified types as test classes.
/// </summary>
/// <param name="testClasses"></param>
public void AddTests(params Type[] testClasses) {
// NOTE: We don't care if the class is not a test class. If the user wants this, so be it.
List<Type> list = new List<Type>(testClasses.Length);
list.AddRange(testClasses);
AddTests(list);
}
public void AddTests(List<Type> testClasses) {
foreach (Type testClassType in testClasses) {
TestClass testClass;
if (this.m_testClasses.TryGetValue(testClassType.AssemblyQualifiedName, out testClass)) {
testClass.AddAllTestMethods();
}
else {
testClass = new TestClass(testClassType, true);
this.m_testClasses[testClassType.AssemblyQualifiedName] = testClass;
}
}
}
public void AddTests(params MethodInfo[] testMethods) {
foreach (MethodInfo testMethod in testMethods) {
TestClass testClass;
if (!this.m_testClasses.TryGetValue(testMethod.DeclaringType.AssemblyQualifiedName, out testClass)) {
testClass = new TestClass(testMethod.DeclaringType, false);
this.m_testClasses[testMethod.DeclaringType.AssemblyQualifiedName] = testClass;
}
testClass.AddTestMethod(testMethod);
}
}
public void AddTests(params Action[] testMethods) {
MethodInfo[] methodInfos = new MethodInfo[testMethods.Length];
int x = 0;
foreach (Action method in testMethods) {
methodInfos[x] = method.Method;
x++;
}
AddTests(methodInfos);
}
public void RunTests(ITestResultHandler resultHandler) {
lock (this) {
if (this.m_isRunning) {
throw new InvalidOperationException("Tests are currently running.");
}
this.m_isRunning = true;
// Reset all test classes
foreach (var item in this.m_testClasses) {
item.Value.Reset();
}
resultHandler.OnTestRunStarted(this);
int index = 1;
foreach (var item in this.m_testClasses) {
item.Value.Run(resultHandler, index);
index++;
}
resultHandler.OnTestRunEnded(this);
this.m_isRunning = false;
}
}
public List<TestClass> GetTestClassesSorted(Comparison<TestClass> comparer) {
List<TestClass> sorted = new List<TestClass>(this.m_testClasses.Values);
sorted.Sort(comparer);
return sorted;
}
public TestClass GetTestClass(string assemblyQualifiedName) {
TestClass testClass;
if (this.m_testClasses.TryGetValue(assemblyQualifiedName, out testClass)) {
return testClass;
}
return null;
}
}
public interface ITestResultHandler {
void OnTestRunStarted(TestRunner runner);
void OnTestRunEnded(TestRunner runner);
void OnTestClassTestStarted(TestClass testClass, int testClassIndex);
/// <summary>
/// Fired when a class error occurred. Will be fired instead of <see cref="TestClassTestEnded"/>. After being fired,
/// no more test from the test class will be run.
/// </summary>
void OnTestClassError(TestClass testClass, int testClassIndex);
void OnTestClassTestEnded(TestClass testClass, int testClassIndex);
void OnTestMethodStarted(TestMethod testMethod, int testMethodIndex);
void OnTestMethodEnded(TestMethod testMethod, int testMethodIndex);
}
}

View File

@ -0,0 +1,63 @@
//
// Copyright (C) 2012 Maya Studios (http://mayastudios.com)
//
// This file is part of MonoDroidUnitTesting.
//
// MonoDroidUnitTesting is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// MonoDroidUnitTesting 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 Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with MonoDroidUnitTesting. If not, see <http://www.gnu.org/licenses/>.
//
using System;
namespace MonoDroidUnitTesting {
/// <summary>
/// Represents the outcome of a test method.
/// </summary>
public enum TestState {
NotYetRun,
Running,
Passed,
Failed,
Inconclusive
}
public static class TestStateExtensions {
private static int GetOrderId(TestState state) {
switch (state) {
case TestState.Failed:
return 0;
case TestState.Inconclusive:
return 1;
case TestState.NotYetRun:
return 2;
case TestState.Running:
return 3;
case TestState.Passed:
return 4;
default:
throw new Exception("Unexpected " + state);
}
}
public static int CompareToForSorting(this TestState a, TestState b) {
if (a == b) {
return 0;
}
int aOrderId = GetOrderId(a);
int bOrderId = GetOrderId(b);
return aOrderId - bOrderId;
}
}
}

View File

@ -0,0 +1,245 @@
//
// Copyright (C) 2012 Maya Studios (http://mayastudios.com)
//
// This file is part of MonoDroidUnitTesting.
//
// MonoDroidUnitTesting is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// MonoDroidUnitTesting 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 Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with MonoDroidUnitTesting. If not, see <http://www.gnu.org/licenses/>.
//
using System;
using Android.App;
using Android.Content;
using Android.Util;
using Android.Widget;
namespace Android.Views {
internal struct Dimension {
public DimensionUnit Unit { get; private set; }
public float Value { get; private set; }
public Dimension(float val, DimensionUnit unit) : this() {
this.Value = val;
this.Unit = unit;
}
public Dimension(TypedValue val) : this() {
if (val.Type != DataType.Dimension) {
throw new ArgumentException("Wrong type: " + val.Type);
}
ComplexUnitType unit = (ComplexUnitType)((val.Data >> (int)ComplexUnitType.Shift) & (int)ComplexUnitType.Mask);
switch (unit) {
case ComplexUnitType.Dip:
this.Unit = DimensionUnit.Dp;
break;
case ComplexUnitType.Sp:
this.Unit = DimensionUnit.Sp;
break;
case ComplexUnitType.Px:
this.Unit = DimensionUnit.Px;
break;
case ComplexUnitType.In:
this.Unit = DimensionUnit.In;
break;
case ComplexUnitType.Mm:
this.Unit = DimensionUnit.Mm;
break;
case ComplexUnitType.Pt:
this.Unit = DimensionUnit.Pt;
break;
default:
throw new Exception("Unexpected: " + (int)unit);
}
this.Value = TypedValue.ComplexToFloat(val.Data);
}
private static Context ResolveCtx(Context ctx) {
return (ctx != null) ? ctx : Application.Context;
}
public float ConvertToValue(DimensionUnit destUnit, Context ctx = null,
Orientation orientation = Orientation.Horizontal) {
return ConvertToValue(destUnit, ResolveCtx(ctx).Resources.DisplayMetrics, orientation);
}
public float ConvertToValue(DimensionUnit destUnit, DisplayMetrics dm,
Orientation orientation = Orientation.Horizontal) {
if (destUnit == this.Unit) {
return this.Value;
}
float pixels = ToSubPixels(dm, orientation);
if (destUnit == DimensionUnit.Px) {
return pixels;
}
return FromSubPixels(pixels, destUnit, dm, orientation);
}
public Dimension ConvertToDimension(DimensionUnit destUnit, Context ctx = null,
Orientation orientation = Orientation.Horizontal) {
return ConvertToDimension(destUnit, ResolveCtx(ctx).Resources.DisplayMetrics, orientation);
}
public Dimension ConvertToDimension(DimensionUnit destUnit, DisplayMetrics dm,
Orientation orientation = Orientation.Horizontal) {
if (destUnit == this.Unit) {
return this;
}
return new Dimension(ConvertToValue(destUnit, dm, orientation), destUnit);
}
/// <summary>
/// Returns the scale factor for converting from the source unit to pixels (when multiplying)
/// </summary>
private static float GetScaleFor(DisplayMetrics dm, DimensionUnit srcUnit, Orientation orientation) {
switch (srcUnit) {
case DimensionUnit.Dp:
return dm.Density;
case DimensionUnit.Sp:
return dm.ScaledDensity;
case DimensionUnit.Px:
return 1.0f;
case DimensionUnit.In:
if (orientation == Orientation.Horizontal) {
return dm.Xdpi;
}
else {
return dm.Ydpi;
}
case DimensionUnit.Mm:
if (orientation == Orientation.Horizontal) {
return dm.Xdpi * (1.0f / 25.4f);
}
else {
return dm.Ydpi * (1.0f / 25.4f);
}
case DimensionUnit.Pt:
if (orientation == Orientation.Horizontal) {
return dm.Xdpi * (1.0f / 72f);
}
else {
return dm.Ydpi * (1.0f / 72f);
}
default:
throw new Exception("Unexpected: " + srcUnit);
}
}
private float ToSubPixels(DisplayMetrics dm, Orientation destOrientation) {
if (this.Unit == DimensionUnit.Px) {
return this.Value;
}
float scale = GetScaleFor(dm, this.Unit, destOrientation);
return this.Value * scale;
}
public int ToPixels(Context ctx = null, Orientation destOrientation = Orientation.Horizontal) {
return ToPixels(ResolveCtx(ctx).Resources.DisplayMetrics, destOrientation);
}
public int ToPixels(DisplayMetrics dm, Orientation destOrientation = Orientation.Horizontal) {
if (this.Unit == DimensionUnit.Px) {
return (int)this.Value;
}
return (int)(ToSubPixels(dm, destOrientation) + 0.5f);
}
private static float FromSubPixels(float pixels, DimensionUnit destUnit, DisplayMetrics dm,
Orientation srcOrientation) {
if (destUnit == DimensionUnit.Px) {
return pixels;
}
float scale = GetScaleFor(dm, destUnit, srcOrientation);
return (pixels / scale);
}
public static Dimension FromPixels(int pixels, DimensionUnit destUnit, Context ctx = null,
Orientation srcOrientation = Orientation.Horizontal) {
return FromPixels(pixels, destUnit, ResolveCtx(ctx).Resources.DisplayMetrics, srcOrientation);
}
public static Dimension FromPixels(int pixels, DimensionUnit destUnit, DisplayMetrics dm,
Orientation srcOrientation = Orientation.Horizontal) {
if (destUnit == DimensionUnit.Px) {
return new Dimension(pixels, DimensionUnit.Px);
}
return new Dimension(FromSubPixels(pixels, destUnit, dm, srcOrientation), destUnit);
}
public static Dimension FromResId(int resId, Context ctx = null) {
TypedValue val = new TypedValue();
if (!ResolveCtx(ctx).Theme.ResolveAttribute(resId, val, true)) {
throw new ArgumentException("Could not resolve resource ID #" + resId);
}
return new Dimension(val);
}
}
internal enum DimensionUnit {
/// <summary>
/// Density-independent Pixels - An abstract unit that is based on the physical density of the screen. These units
/// are relative to a 160 dpi (dots per inch) screen, on which 1dp is roughly equal to 1px. When running on a higher
/// density screen, the number of pixels used to draw 1dp is scaled up by a factor appropriate for the screen's dpi.
/// Likewise, when on a lower density screen, the number of pixels used for 1dp is scaled down. The ratio of
/// dp-to-pixel will change with the screen density, but not necessarily in direct proportion. Using dp units
/// (instead of px units) is a simple solution to making the view dimensions in your layout resize properly for
/// different screen densities. In other words, it provides consistency for the real-world sizes of your UI
/// elements across different devices.
/// </summary>
Dp,
/// <summary>
/// Scale-independent Pixels - This is like the dp unit, but it is also scaled by the user's font size preference.
/// It is recommend you use this unit when specifying font sizes, so they will be adjusted for both the screen
/// density and the user's preference.
/// </summary>
Sp,
/// <summary>
/// Points - 1/72 of an inch based on the physical size of the screen.
/// </summary>
Pt,
/// <summary>
/// Pixels - Corresponds to actual pixels on the screen. This unit of measure is not recommended because the actual
/// representation can vary across devices; each devices may have a different number of pixels per inch and may
/// have more or fewer total pixels available on the screen.
/// </summary>
Px,
/// <summary>
/// Millimeters - Based on the physical size of the screen.
/// </summary>
Mm,
/// <summary>
/// Inches - Based on the physical size of the screen.
/// </summary>
In
}
}

Some files were not shown because too many files have changed in this diff Show More