Compare commits

..

93 Commits

Author SHA1 Message Date
Kyle Spearrin
58246f72dd version bump 2017-02-10 19:31:37 -05:00
Kyle Spearrin
b90ce2a2af token refresh bad requests are to be treated as unauthorized 2017-02-10 19:16:23 -05:00
Kyle Spearrin
4a0fc5ca0e crash fixes 2017-02-09 22:06:39 -05:00
Kyle Spearrin
c29d902b8e Autofill service alert when adding new login for the first time and autofill isnt turned on. Added fi language to project. 2017-02-09 21:43:03 -05:00
Kyle Spearrin
ab629c2048 autofill intent is only valid for limited time 2017-02-09 21:06:47 -05:00
Kyle Spearrin
e970ca49e8 Clear intent from autofill. Background app when back button on lock page. 2017-02-09 18:12:34 -05:00
Kyle Spearrin
99e78092ed dont compare the creds uri since it could be a different, equivalent domain 2017-02-09 00:20:29 -05:00
Kyle Spearrin
4af91b5ab6 Update ios extension to use new login service lookup by uristring 2017-02-09 00:12:09 -05:00
Kyle Spearrin
539121070a Added equivalent domain checks to autofill listing filter. centralized logic in login service. 2017-02-08 23:58:37 -05:00
Kyle Spearrin
2a1bd92e1a sync domain settings 2017-02-08 22:04:07 -05:00
Kyle Spearrin
2c1ebc0439 setup settings api repository 2017-02-08 21:19:09 -05:00
Kyle Spearrin
2d605f5dfb remove old sync helper for removing ciphers (was for inc syncing) 2017-02-08 20:45:56 -05:00
Kyle Spearrin
0cd09cf03a setup new settings service 2017-02-08 20:44:35 -05:00
Kyle Spearrin
3ad1e8a3ba set up user settings data table and access repository 2017-02-08 20:39:37 -05:00
Kyle Spearrin
230722945e move settings pages to modals instead of navigation pages 2017-02-08 19:18:34 -05:00
Kyle Spearrin
a429dcf978 Fix wrong key when decrypting cipher with mac 2017-02-08 19:07:38 -05:00
Kyle Spearrin
0131031ac4 Skip event if no package name 2017-02-08 18:19:59 -05:00
Kyle Spearrin
a418fc810a log out when checking account reivison if authentication issue 2017-02-08 00:19:30 -05:00
Kyle Spearrin
e71adbd26d null checks when error handling 2017-02-07 21:56:28 -05:00
Kyle Spearrin
8a525aee8a check old auth bearer for logged in status as well 2017-02-07 21:19:23 -05:00
Kyle Spearrin
463b0fa28a remove incremental syncs and move to full syncs with revision checks 2017-02-06 23:40:24 -05:00
Kyle Spearrin
007ebadf16 removed root var 2017-02-06 22:02:29 -05:00
Kyle Spearrin
c7af81bf0c Cleanup hacks because of Intent LaunchedFromHistory bug 2017-02-06 19:39:07 -05:00
Kyle Spearrin
749508871b Handle all exceptions from API calls 2017-02-06 09:55:35 -05:00
Kyle Spearrin
d112e0ea42 two-factor login re-worked with new auth flow 2017-02-06 09:39:07 -05:00
Kyle Spearrin
54f8771a9c better error parsing 2017-02-06 09:15:10 -05:00
Kyle Spearrin
0a3c83288e Added AutofillService to GA 2017-02-05 23:59:43 -05:00
Kyle Spearrin
52a866147e Optimized startup tasks to only happen when necessary. Added some GA telemetry to autofill. 2017-02-05 23:55:58 -05:00
Igetin
6629eaf485 Added Finnish translations (#37)
* Added strings for Finnish translation

* Added Finnish App Store description

* Added Finnish captions for screenshots

* Added Finnish Play Store description

* Added Finnish captions for screenshots
2017-02-05 07:16:29 -05:00
Kyle Spearrin
74239521cd HandleTokenStateAsync before each API call for refresh and auth bearer migration 2017-02-04 23:31:37 -05:00
Kyle Spearrin
8ae95c4e30 dont read line at end of publisher script 2017-02-04 21:53:56 -05:00
Kyle Spearrin
c31e191d7e add backslashes 2017-02-04 21:40:46 -05:00
Kyle Spearrin
c3134f779d args for increment version script 2017-02-04 21:35:02 -05:00
Kyle Spearrin
d4749c139b increment version script 2017-02-04 21:19:54 -05:00
Kyle Spearrin
63d6c32063 play store creds 2017-02-04 20:52:20 -05:00
Kyle Spearrin
08299902e3 switch to google creds 2017-02-04 19:05:39 -05:00
Kyle Spearrin
db04d6e642 Create google play publisher console application 2017-02-04 12:04:57 -05:00
Kyle Spearrin
6ddbd77009 encrypted keystore for CI builds 2017-02-04 01:33:16 -05:00
Kyle Spearrin
4a4779fc63 Converted auth to identity server endpoints and utilize bearer2 access token 2017-02-04 01:12:25 -05:00
Kyle Spearrin
46bb8d2cb5 added back FromAutofillService functionality 2017-02-03 23:21:40 -05:00
Kyle Spearrin
31b2eeb293 remove lots of mainpage code since we allow closing of autofill page now 2017-02-03 00:26:55 -05:00
Kyle Spearrin
8e9becd579 dont main page on sleep 2017-02-03 00:12:53 -05:00
Kyle Spearrin
d067de086d autofill fixes 2017-02-02 23:36:40 -05:00
Kyle Spearrin
8c6d395d89 remove uri extra after captured 2017-02-02 23:05:24 -05:00
Kyle Spearrin
f66b26a866 beta title 2017-02-02 22:30:34 -05:00
Kyle Spearrin
83f00d69ce added close button to autofill list page. if uri is a website on api level < 21, do not autofill and present modal for copying 2017-02-02 22:20:45 -05:00
Kyle Spearrin
8b2923b56d Add more browser support for fetching URL 2017-02-02 19:39:00 -05:00
Kyle Spearrin
46af313c25 AutoFillServiceDescription 2017-02-01 22:03:35 -05:00
Kyle Spearrin
85dda759ec updated autofill service tools page with new tutorial images 2017-02-01 21:55:00 -05:00
Kyle Spearrin
27fb44277f Accessibility service setup pages 2017-02-01 00:38:35 -05:00
Kyle Spearrin
ea1aafbab2 WIP on accessibility service 2017-01-31 22:53:32 -05:00
Kyle Spearrin
2c446f939e accessibility service WIP 2017-01-31 20:45:51 -05:00
Kyle Spearrin
47e427a851 wip autofill tweaks 2017-01-31 00:30:41 -05:00
Kyle Spearrin
95b8efae20 set color for autofill notification 2017-01-30 23:41:39 -05:00
Kyle Spearrin
53774735d4 autofill wip 2017-01-30 23:33:02 -05:00
Kyle Spearrin
36c6c5a35e Accessibility service WIP 2017-01-30 19:26:39 -05:00
Kyle Spearrin
0beb07c87e Update CONTRIBUTING.md 2017-01-29 11:32:48 -05:00
Kyle Spearrin
64fd8e3be9 autofill service WIP 2017-01-28 23:58:26 -05:00
Kyle Spearrin
45c516ea3f cleanup on autofill 2017-01-27 23:32:48 -05:00
Kyle Spearrin
26667c0a59 autofill WIP into main activity. created login selection page 2017-01-27 23:13:28 -05:00
Kyle Spearrin
61e0379eb3 autofill cleanup WIP 2017-01-23 23:32:52 -05:00
Kyle Spearrin
759df9bdd5 Autofill WIP 2017-01-23 21:28:38 -05:00
Johannes Grönvall
33e7ca08d8 Changes (#32)
Refined the Swedish translations
2017-01-13 08:26:40 -05:00
Primokorn
c3d0d8bf63 Update AppResources.fr.resx (#33) 2017-01-13 08:26:21 -05:00
Kyle Spearrin
8387f1e204 Update packages 2017-01-05 23:54:14 -05:00
Kyle Spearrin
fe778293c1 renaming files for Site => Login refactor 2017-01-03 00:25:17 -05:00
Kyle Spearrin
991afb7722 Reactor rename Sites => Logins 2017-01-03 00:17:15 -05:00
Kyle Spearrin
a176542114 ARM64 architecutre on app extension 2016-12-31 11:47:35 -05:00
Kyle Spearrin
a3f555e816 added new translations for credits page 2016-12-30 22:49:58 -05:00
Kyle Spearrin
dae5453e13 Added translators to credits page 2016-12-30 22:44:17 -05:00
Kyle Spearrin
082826287e Added french translation to the build 2016-12-30 22:39:21 -05:00
Primokorn
7418691cf3 Create french CAPTIONS.md (#28) 2016-12-30 22:28:03 -05:00
Primokorn
29e72de64b Create french COPY.md (#29)
* Create french COPY.md

* Update COPY.md
2016-12-30 22:27:52 -05:00
Primokorn
4955b4a4a8 Create french CAPTIONS.md (#30) 2016-12-30 22:27:12 -05:00
Primokorn
7c76595314 Create french COPY.md (#31) 2016-12-30 22:26:57 -05:00
Primokorn
1495003103 Create AppResources.fr.resx (#27) 2016-12-30 10:42:39 -05:00
Peter Karlsson
fc1b74d48f Fix and missed translations (#26) 2016-12-29 02:39:11 -05:00
Kyle Spearrin
830d0e9e7a readme updates 2016-12-29 00:40:09 -05:00
Kyle Spearrin
7310c06162 readme updates 2016-12-29 00:38:53 -05:00
Kyle Spearrin
045ccaa219 readme updates 2016-12-29 00:26:14 -05:00
Peter Karlsson
9d6a276342 Additional strings update (#25) 2016-12-26 21:31:31 -05:00
Kyle Spearrin
9204d25b62 Applied i18n strings to missing parts in app extension 2016-12-26 21:22:55 -05:00
Kyle Spearrin
6c847292c7 simplified chinese used for all chinese languages for now 2016-12-26 14:38:18 -05:00
Kyle Spearrin
b2712119d1 Chinese support on iOS 2016-12-26 13:39:14 -05:00
Kyle Spearrin
7728046309 layout fixes 2016-12-26 11:30:57 -05:00
Kyle Spearrin
17e18a2a7a Added contains and clear implementations for iOS Settings 2016-12-26 10:49:34 -05:00
Kyle Spearrin
ce8bedb340 zh-Hans fix 2016-12-24 22:44:47 -05:00
Kyle Spearrin
14dc42e148 Fixes for language resources 2016-12-24 22:43:50 -05:00
Peter Karlsson
442c2294e9 Update AppResources.sv.resx (#23) 2016-12-24 22:19:24 -05:00
Peter Karlsson
5334514d55 Swedish translation (#22) 2016-12-24 20:43:06 -05:00
Kyle Spearrin
0d5b431e6a version bump 2016-12-24 11:57:37 -05:00
Kyle Spearrin
8b10ee0028 better error handling in base repo 2016-12-24 11:47:29 -05:00
Kyle Spearrin
9682abdded HttpService abstraction with CustomAndroidClientHandler to handle xamarin android bug with error response body 2016-12-24 10:54:18 -05:00
202 changed files with 8205 additions and 1948 deletions

33
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,33 @@
Code contributions are welcome! Please commit any pull requests against the `master` branch.
# Internationalization (i18n)
If you are interested in helping translate the bitwarden mobile app into another language, please follow these steps
when creating your pull request:
1. Create a new resource file under `/src/App/Resources` by copy/pasting the master English file, `AppResources.resx`.
2. Rename your `AppResources.resx` copy to include the proper .NET culture code for your language (typically the two
letter code, ex. `sv`). You can find a list of culture codes here: <http://timtrott.co.uk/culture-codes/>. For
example, if I want to create a new translation for Swedish, I will rename my `AppResources.resx` copy to
`AppResources.sv.resx`.
3. Open the `AppResources.XX.resx` file for your newly created language and start translating the `<value>` tags for
each `<data>` element. The `<data>` and `<comment>` properties should not be translated and remain in English.
4. Repeat the same process for the store `COPY.md` and `CAPTIONS.md` files in `/store/apple` and `/store/google` by
creating a new folder for your language in each. Do not copy over the `assets` and `screenshots` folders to your new
language. We will update these based on your translations provided in `CAPTIONS.md`. Finally, do not translate the
titles in the markdown files (ex. `# Name` and `# Screenshot 1`). These are only for reference.
5. If you have a Xamarin development environment setup, test your translations to make sure they look correct in the
app on iOS and Android. Sometimes the UI can break due to translations taking up more space than the original UI was
built for. If possible, use a shorter or abbreviated version of the word/sentence to accomedate the available space.
If you are unable to accomedate the avaialable space for a particular translation, just let us know in your pull
request comments. If you are unable to test your translations, just let us know in your pull request comments so
that we can check it for you.
6. Be sure to watch for [future changes](https://github.com/bitwarden/mobile/commits/master/src/App/Resources/AppResources.resx)
to the `/src/App/Resources/AppResources.resx` file so that your translation will stay up to date.
You can find an example of a proper translation pull request here: <https://github.com/bitwarden/mobile/pull/22/files>
You can read more about localizing a Xamarin.Forms app here:
<https://developer.xamarin.com/guides/xamarin-forms/advanced/localization/>
TIP: If you have Visual Studio installed, it provides a nice tabular UI for editing `resx` XML files.

View File

@@ -7,8 +7,21 @@
The bitwarden mobile application is written in C# with Xamarin Android, Xamarin iOS, and Xamarin Forms.
# Build/Run
**Requirements**
- [Visual Studio w/ Xamarin -or- Xamarin Studio](https://store.xamarin.com/)
By default the app is targeting the production API. If you are running the [Core](https://github.com/bitwarden/core) API locally,
you'll need to switch the extension to target your local API. Open `src/App/Utilities/ApiHttpClient.cs` and set `BaseAddress` to your local
API instance (ex. `new Uri("http://localhost:4000")`).
After restoring the nuget packages, you can now build and run the app.
# Contribute
Code contributions are welcome! Visual Studio or Xamarin Studio is required to work on this project. Please commit any pull requests against the `master` branch.
Learn more about how to contribute by reading the [`CONTRIBUTING.md`](CONTRIBUTING.md) file.
Security audits and feedback are welcome. Please open an issue or email us privately if the report is sensitive in nature.

View File

@@ -23,6 +23,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS.Test", "test\iOS.Test\i
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Android.Test", "test\Android.Test\Android.Test.csproj", "{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "store", "store", "{92470CBD-9047-4C3C-8EA3-D972D6622D84}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "google", "google", "{2E399654-26A2-46F6-B9CA-1B496A3F370A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Publisher", "store\google\Publisher\Publisher.csproj", "{428CACAB-CC26-4F41-9062-1E4A9BC82640}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
@@ -411,6 +417,54 @@ Global
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Release|x86.ActiveCfg = Release|Any CPU
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Release|x86.Build.0 = Release|Any CPU
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE}.Release|x86.Deploy.0 = Release|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.Ad-Hoc|Any CPU.ActiveCfg = Release|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.Ad-Hoc|Any CPU.Build.0 = Release|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.Ad-Hoc|ARM.ActiveCfg = Release|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.Ad-Hoc|ARM.Build.0 = Release|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Release|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.Ad-Hoc|iPhoneSimulator.Build.0 = Release|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.Ad-Hoc|x64.ActiveCfg = Release|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.Ad-Hoc|x64.Build.0 = Release|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.Ad-Hoc|x86.ActiveCfg = Release|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.Ad-Hoc|x86.Build.0 = Release|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.AppStore|Any CPU.ActiveCfg = Release|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.AppStore|Any CPU.Build.0 = Release|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.AppStore|ARM.ActiveCfg = Release|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.AppStore|ARM.Build.0 = Release|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.AppStore|iPhone.ActiveCfg = Release|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.AppStore|iPhone.Build.0 = Release|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.AppStore|iPhoneSimulator.ActiveCfg = Release|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.AppStore|iPhoneSimulator.Build.0 = Release|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.AppStore|x64.ActiveCfg = Release|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.AppStore|x64.Build.0 = Release|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.AppStore|x86.ActiveCfg = Release|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.AppStore|x86.Build.0 = Release|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.Debug|Any CPU.Build.0 = Debug|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.Debug|ARM.ActiveCfg = Debug|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.Debug|ARM.Build.0 = Debug|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.Debug|iPhone.Build.0 = Debug|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.Debug|x64.ActiveCfg = Debug|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.Debug|x64.Build.0 = Debug|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.Debug|x86.ActiveCfg = Debug|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.Debug|x86.Build.0 = Debug|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.Release|Any CPU.ActiveCfg = Release|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.Release|Any CPU.Build.0 = Release|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.Release|ARM.ActiveCfg = Release|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.Release|ARM.Build.0 = Release|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.Release|iPhone.ActiveCfg = Release|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.Release|iPhone.Build.0 = Release|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.Release|x64.ActiveCfg = Release|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.Release|x64.Build.0 = Release|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.Release|x86.ActiveCfg = Release|Any CPU
{428CACAB-CC26-4F41-9062-1E4A9BC82640}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -424,5 +478,7 @@ Global
{B2538ADA-B605-4D6F-ACD2-62A409680F84} = {EC730FD9-F623-4B6C-B503-95CDCFBCF277}
{6702027A-F726-4149-863E-7CB924674B9A} = {0D790714-ECF8-4A83-BE4A-E9C84DD1BB5D}
{FA507A17-D4E3-46DF-ACD8-D7E6D7D4E3AE} = {0D790714-ECF8-4A83-BE4A-E9C84DD1BB5D}
{2E399654-26A2-46F6-B9CA-1B496A3F370A} = {92470CBD-9047-4C3C-8EA3-D972D6622D84}
{428CACAB-CC26-4F41-9062-1E4A9BC82640} = {2E399654-26A2-46F6-B9CA-1B496A3F370A}
EndGlobalSection
EndGlobal

Binary file not shown.

View File

@@ -76,11 +76,11 @@
<Private>True</Private>
</Reference>
<Reference Include="Acr.UserDialogs, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Acr.UserDialogs.6.3.2\lib\MonoAndroid10\Acr.UserDialogs.dll</HintPath>
<HintPath>..\..\packages\Acr.UserDialogs.6.3.3\lib\MonoAndroid10\Acr.UserDialogs.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Acr.UserDialogs.Interface, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Acr.UserDialogs.6.3.2\lib\MonoAndroid10\Acr.UserDialogs.Interface.dll</HintPath>
<HintPath>..\..\packages\Acr.UserDialogs.6.3.3\lib\MonoAndroid10\Acr.UserDialogs.Interface.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="AndHUD, Version=1.2.0.0, Culture=neutral, processorArchitecture=MSIL">
@@ -92,7 +92,7 @@
<Private>True</Private>
</Reference>
<Reference Include="FormsViewGroup, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xamarin.Forms.2.3.3.175\lib\MonoAndroid10\FormsViewGroup.dll</HintPath>
<HintPath>..\..\packages\Xamarin.Forms.2.3.3.180\lib\MonoAndroid10\FormsViewGroup.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="HockeySDK, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
@@ -194,27 +194,28 @@
<Private>True</Private>
</Reference>
<Reference Include="SQLitePCLRaw.batteries_green, Version=1.0.0.0, Culture=neutral, PublicKeyToken=a84b7dcfb1391f7f, processorArchitecture=MSIL">
<HintPath>..\..\packages\SQLitePCLRaw.bundle_green.1.1.1\lib\MonoAndroid\SQLitePCLRaw.batteries_green.dll</HintPath>
<HintPath>..\..\packages\SQLitePCLRaw.bundle_green.1.1.2\lib\MonoAndroid\SQLitePCLRaw.batteries_green.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="SQLitePCLRaw.batteries_v2, Version=1.0.0.0, Culture=neutral, PublicKeyToken=8226ea5df37bcae9, processorArchitecture=MSIL">
<HintPath>..\..\packages\SQLitePCLRaw.bundle_green.1.1.1\lib\MonoAndroid\SQLitePCLRaw.batteries_v2.dll</HintPath>
<HintPath>..\..\packages\SQLitePCLRaw.bundle_green.1.1.2\lib\MonoAndroid\SQLitePCLRaw.batteries_v2.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="SQLitePCLRaw.core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1488e028ca7ab535, processorArchitecture=MSIL">
<HintPath>..\..\packages\SQLitePCLRaw.core.1.1.1\lib\MonoAndroid\SQLitePCLRaw.core.dll</HintPath>
<HintPath>..\..\packages\SQLitePCLRaw.core.1.1.2\lib\MonoAndroid\SQLitePCLRaw.core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="SQLitePCLRaw.lib.e_sqlite3, Version=1.0.0.0, Culture=neutral, PublicKeyToken=e4ad490600e2234c, processorArchitecture=MSIL">
<HintPath>..\..\packages\SQLitePCLRaw.lib.e_sqlite3.android.1.1.1\lib\MonoAndroid\SQLitePCLRaw.lib.e_sqlite3.dll</HintPath>
<HintPath>..\..\packages\SQLitePCLRaw.lib.e_sqlite3.android.1.1.2\lib\MonoAndroid\SQLitePCLRaw.lib.e_sqlite3.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="SQLitePCLRaw.provider.e_sqlite3, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9c301db686d0bd12, processorArchitecture=MSIL">
<HintPath>..\..\packages\SQLitePCLRaw.provider.e_sqlite3.android.1.1.1\lib\MonoAndroid\SQLitePCLRaw.provider.e_sqlite3.dll</HintPath>
<HintPath>..\..\packages\SQLitePCLRaw.provider.e_sqlite3.android.1.1.2\lib\MonoAndroid\SQLitePCLRaw.provider.e_sqlite3.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Net.Http" />
<Reference Include="Validation, Version=2.3.0.0, Culture=neutral, PublicKeyToken=2fc06f0d701809a7, processorArchitecture=MSIL">
<HintPath>..\..\packages\Validation.2.3.7\lib\dotnet\Validation.dll</HintPath>
<Private>True</Private>
@@ -252,19 +253,19 @@
<Private>True</Private>
</Reference>
<Reference Include="Xamarin.Forms.Core, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xamarin.Forms.2.3.3.175\lib\MonoAndroid10\Xamarin.Forms.Core.dll</HintPath>
<HintPath>..\..\packages\Xamarin.Forms.2.3.3.180\lib\MonoAndroid10\Xamarin.Forms.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Xamarin.Forms.Platform, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xamarin.Forms.2.3.3.175\lib\MonoAndroid10\Xamarin.Forms.Platform.dll</HintPath>
<HintPath>..\..\packages\Xamarin.Forms.2.3.3.180\lib\MonoAndroid10\Xamarin.Forms.Platform.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Xamarin.Forms.Platform.Android, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xamarin.Forms.2.3.3.175\lib\MonoAndroid10\Xamarin.Forms.Platform.Android.dll</HintPath>
<HintPath>..\..\packages\Xamarin.Forms.2.3.3.180\lib\MonoAndroid10\Xamarin.Forms.Platform.Android.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Xamarin.Forms.Xaml, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xamarin.Forms.2.3.3.175\lib\MonoAndroid10\Xamarin.Forms.Xaml.dll</HintPath>
<HintPath>..\..\packages\Xamarin.Forms.2.3.3.180\lib\MonoAndroid10\Xamarin.Forms.Xaml.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Xamarin.GooglePlayServices.Analytics, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
@@ -298,11 +299,13 @@
</ItemGroup>
<ItemGroup>
<Compile Include="AutofillActivity.cs" />
<Compile Include="AutofillCredentials.cs" />
<Compile Include="Controls\CustomSearchBarRenderer.cs" />
<Compile Include="Controls\CustomButtonRenderer.cs" />
<Compile Include="Controls\ExtendedButtonRenderer.cs" />
<Compile Include="Controls\ExtendedTabbedPageRenderer.cs" />
<Compile Include="Controls\ExtendedTableViewRenderer.cs" />
<Compile Include="CustomAndroidClientHandler.cs" />
<Compile Include="HockeyAppCrashManagerListener.cs" />
<Compile Include="AutofillService.cs" />
<Compile Include="Controls\ExtendedEditorRenderer.cs" />
@@ -311,6 +314,7 @@
<Compile Include="Controls\ExtendedTextCellRenderer.cs" />
<Compile Include="Controls\ExtendedPickerRenderer.cs" />
<Compile Include="Controls\ExtendedEntryRenderer.cs" />
<Compile Include="Services\HttpService.cs" />
<Compile Include="Services\LocalizeService.cs" />
<Compile Include="MainApplication.cs" />
<Compile Include="Resources\Resource.Designer.cs" />
@@ -328,9 +332,11 @@
<Compile Include="SplashActivity.cs" />
</ItemGroup>
<ItemGroup>
<None Include="8bit.keystore.enc" />
<None Include="app.config">
<SubType>Designer</SubType>
</None>
<None Include="increment-version.ps1" />
<None Include="packages.config">
<SubType>Designer</SubType>
</None>
@@ -742,6 +748,87 @@
<ItemGroup>
<AndroidEnvironment Include="EnvironmentVariables.txt" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\notification_sm.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-hdpi\notification_sm.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xhdpi\notification_sm.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxhdpi\notification_sm.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxxhdpi\notification_sm.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\accessibility_step1.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\accessibility_step2.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxhdpi\accessibility_step1.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xhdpi\accessibility_step1.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-hdpi\accessibility_step1.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxhdpi\accessibility_step2.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xhdpi\accessibility_step2.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-hdpi\accessibility_step2.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxhdpi\accessibility_notification.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xhdpi\accessibility_notification.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-hdpi\accessibility_notification.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\accessibility_notification.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxhdpi\accessibility_notification_icon.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xhdpi\accessibility_notification_icon.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-hdpi\accessibility_notification_icon.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\accessibility_notification_icon.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\values\strings.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxxhdpi\close.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xxhdpi\close.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-xhdpi\close.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable-hdpi\close.png" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\close.png" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath)\Xamarin\Android\Xamarin.Android.CSharp.targets" />
<Import Project="..\..\packages\Xamarin.Android.Support.Vector.Drawable.23.3.0\build\Xamarin.Android.Support.Vector.Drawable.targets" Condition="Exists('..\..\packages\Xamarin.Android.Support.Vector.Drawable.23.3.0\build\Xamarin.Android.Support.Vector.Drawable.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
@@ -750,10 +837,10 @@
</PropertyGroup>
<Error Condition="!Exists('..\..\packages\Xamarin.Android.Support.Vector.Drawable.23.3.0\build\Xamarin.Android.Support.Vector.Drawable.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Xamarin.Android.Support.Vector.Drawable.23.3.0\build\Xamarin.Android.Support.Vector.Drawable.targets'))" />
<Error Condition="!Exists('..\..\packages\Xamarin.GooglePlayServices.Basement.29.0.0.2\build\Xamarin.GooglePlayServices.Basement.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Xamarin.GooglePlayServices.Basement.29.0.0.2\build\Xamarin.GooglePlayServices.Basement.targets'))" />
<Error Condition="!Exists('..\..\packages\Xamarin.Forms.2.3.3.175\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Xamarin.Forms.2.3.3.175\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets'))" />
<Error Condition="!Exists('..\..\packages\Xamarin.Forms.2.3.3.180\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Xamarin.Forms.2.3.3.180\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets'))" />
</Target>
<Import Project="..\..\packages\Xamarin.GooglePlayServices.Basement.29.0.0.2\build\Xamarin.GooglePlayServices.Basement.targets" Condition="Exists('..\..\packages\Xamarin.GooglePlayServices.Basement.29.0.0.2\build\Xamarin.GooglePlayServices.Basement.targets')" />
<Import Project="..\..\packages\Xamarin.Forms.2.3.3.175\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets" Condition="Exists('..\..\packages\Xamarin.Forms.2.3.3.175\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets')" />
<Import Project="..\..\packages\Xamarin.Forms.2.3.3.180\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets" Condition="Exists('..\..\packages\Xamarin.Forms.2.3.3.180\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.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">

View File

@@ -1,58 +1,102 @@
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 Bit.App.Models;
namespace Bit.Android
{
//[Activity(Label = "Autofill", LaunchMode = global::Android.Content.PM.LaunchMode.SingleInstance, Theme = "@style/android:Theme.Material.Light")]
[Activity(Theme = "@style/BitwardenTheme.Splash",
Label = "bitwarden",
Icon = "@drawable/icon",
WindowSoftInputMode = SoftInput.StateHidden)]
public class AutofillActivity : Activity
{
private string _lastQueriedUri;
public static AutofillCredentials LastCredentials { get; set; }
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
var url = Intent.GetStringExtra("url");
_lastQueriedUrl = url;
//StartActivityForResult(Kp2aControl.GetQueryEntryIntent(url), 123);
LaunchMainActivity(Intent, 932473);
}
string _lastQueriedUrl;
protected override void OnNewIntent(Intent intent)
{
base.OnNewIntent(intent);
LaunchMainActivity(intent, 489729);
}
protected override void OnDestroy()
{
base.OnDestroy();
}
protected override void OnResume()
{
base.OnResume();
if(!Intent.HasExtra("uri"))
{
Finish();
return;
}
Intent.RemoveExtra("uri");
}
protected override void OnActivityResult(int requestCode, [GeneratedEnum] Result resultCode, Intent data)
{
base.OnActivityResult(requestCode, resultCode, data);
if(data == null)
{
LastCredentials = null;
}
else
{
try
{
if(data.GetStringExtra("canceled") != null)
{
LastCredentials = null;
}
else
{
var uri = data.GetStringExtra("uri");
var username = data.GetStringExtra("username");
var password = data.GetStringExtra("password");
try
{
// TODO: lookup site
LastReceivedCredentials = new Credentials { User = "username", Password = "12345678", Url = _lastQueriedUrl };
LastCredentials = new AutofillCredentials
{
Username = username,
Password = password,
Uri = uri,
LastUri = _lastQueriedUri
};
}
}
catch
{
LastCredentials = null;
}
}
catch(Exception e)
{
//Android.Util.Log.Debug("KP2AAS", "Exception while receiving credentials: " + e.ToString());
}
finally
Finish();
}
private void LaunchMainActivity(Intent callingIntent, int requestCode)
{
_lastQueriedUri = callingIntent?.GetStringExtra("uri");
if(_lastQueriedUri == null)
{
Finish();
return;
}
}
public static Credentials LastReceivedCredentials;
public class Credentials
{
public string User;
public string Password;
public string Url;
var intent = new Intent(this, typeof(MainActivity));
intent.PutExtra("uri", _lastQueriedUri);
intent.PutExtra("ts", Java.Lang.JavaSystem.CurrentTimeMillis());
StartActivityForResult(intent, requestCode);
}
}
}
}

View File

@@ -0,0 +1,10 @@
namespace Bit.Android
{
public class AutofillCredentials
{
public string Username { get; set; }
public string Password { get; set; }
public string Uri { get; set; }
public string LastUri { get; set; }
}
}

View File

@@ -1,100 +1,104 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Android.AccessibilityServices;
using Android.App;
using Android.Content;
using Android.Graphics;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Android.Views.Accessibility;
using Android.Widget;
namespace Bit.Android
{
//[Service(Permission = "android.permission.BIND_ACCESSIBILITY_SERVICE", Label = "bitwarden")]
//[IntentFilter(new string[] { "android.accessibilityservice.AccessibilityService" })]
//[MetaData("android.accessibilityservice", Resource = "@xml/accessibilityservice")]
[Service(Permission = "android.permission.BIND_ACCESSIBILITY_SERVICE", Label = "bitwarden")]
[IntentFilter(new string[] { "android.accessibilityservice.AccessibilityService" })]
[MetaData("android.accessibilityservice", Resource = "@xml/accessibilityservice")]
public class AutofillService : AccessibilityService
{
private const int autoFillNotificationId = 0;
private const string androidAppPrefix = "androidapp://";
private const int AutoFillNotificationId = 34573;
private const string SystemUiPackage = "com.android.systemui";
private const string BitwardenPackage = "com.x8bit.bitwarden";
private const string BitwardenWebsite = "bitwarden.com";
public static bool Enabled { get; set; } = false;
private static Dictionary<string, string[]> BrowserPackages => new Dictionary<string, string[]>
{
{ "com.android.chrome", new string[] { "url_bar" } },
{ "com.chrome.beta", new string[] { "url_bar" } },
{ "com.android.browser", new string[] { "url" } },
{ "com.brave.browser", new string[] { "url_bar" } },
{ "com.opera.browser", new string[] { "url_field" } },
{ "com.opera.browser.beta", new string[] { "url_field" } },
{ "com.opera.mini.native", new string[] { "url_field" } },
{ "com.chrome.dev", new string[] { "url_bar" } },
{ "com.chrome.canary", new string[] { "url_bar" } },
{ "com.google.android.apps.chrome", new string[] { "url_bar" } },
{ "com.google.android.apps.chrome_dev", new string[] { "url_bar" } },
{ "org.iron.srware", new string[] { "url_bar" } },
{ "com.sec.android.app.sbrowser", new string[] { "sbrowser_url_bar" } },
{ "com.yandex.browser", new string[] { "bro_common_omnibox_host", "bro_common_omnibox_edit_text" } },
{ "org.mozilla.firefox", new string[] { "url_bar_title" } },
{ "org.mozilla.firefox_beta", new string[] { "url_bar_title" } },
{ "com.ghostery.android.ghostery",new string[] { "search_field" } },
{ "org.adblockplus.browser", new string[] { "url_bar_title" } },
{ "com.htc.sense.browser", new string[] { "title" } },
{ "com.amazon.cloud9", new string[] { "url" } },
{ "mobi.mgeek.TunnyBrowser", new string[] { "title" } },
{ "com.nubelacorp.javelin", new string[] { "enterUrl" } },
{ "com.jerky.browser2", new string[] { "enterUrl" } },
{ "com.mx.browser", new string[] { "address_editor_with_progress" } },
{ "com.mx.browser.tablet", new string[] { "address_editor_with_progress"} },
{ "com.linkbubble.playstore", new string[] { "url_text" }}
};
public override void OnAccessibilityEvent(AccessibilityEvent e)
{
var eventType = e.EventType;
var package = e.PackageName;
switch(eventType)
Enabled = true;
var root = RootInActiveWindow;
if(string.IsNullOrWhiteSpace(e.PackageName) || e.PackageName == SystemUiPackage ||
root?.PackageName != e.PackageName)
{
return;
}
switch(e.EventType)
{
case EventTypes.ViewTextSelectionChanged:
//if(e.Source.Password && string.IsNullOrWhiteSpace(e.Source.Text))
//{
// var bundle = new Bundle();
// bundle.PutCharSequence(AccessibilityNodeInfo.ActionArgumentSetTextCharsequence, "mypassword");
// e.Source.PerformAction(global::Android.Views.Accessibility.Action.SetText, bundle);
//}
break;
case EventTypes.WindowContentChanged:
case EventTypes.WindowStateChanged:
if(e.PackageName == "com.android.systemui")
var cancelNotification = true;
if(e.PackageName == BitwardenPackage)
{
CancelNotification();
break;
}
var root = RootInActiveWindow;
if((ExistsNodeOrChildren(root, n => n.WindowId == e.WindowId) && !ExistsNodeOrChildren(root, n => (n.ViewIdResourceName != null) && (n.ViewIdResourceName.StartsWith("com.android.systemui")))))
var passwordNodes = GetWindowNodes(root, e, n => n.Password);
if(passwordNodes.Any())
{
bool cancelNotification = true;
var allEditTexts = GetNodeOrChildren(root, n => { return IsEditText(n); });
var usernameEdit = allEditTexts.TakeWhile(edit => (edit.Password == false)).LastOrDefault();
string searchString = androidAppPrefix + root.PackageName;
string url = androidAppPrefix + root.PackageName;
if(root.PackageName == "com.android.chrome")
var uri = GetUri(root);
if(uri.Contains(BitwardenWebsite))
{
var addressField = root.FindAccessibilityNodeInfosByViewId("com.android.chrome:id/url_bar").FirstOrDefault();
UrlFromAddressField(ref url, addressField);
}
else if(root.PackageName == "com.android.browser")
{
var addressField = root.FindAccessibilityNodeInfosByViewId("com.android.browser:id/url").FirstOrDefault();
UrlFromAddressField(ref url, addressField);
break;
}
var emptyPasswordFields = GetNodeOrChildren(root, n => { return IsPasswordField(n); }).ToList();
if(emptyPasswordFields.Any())
if(NeedToAutofill(AutofillActivity.LastCredentials, uri))
{
if((AutofillActivity.LastReceivedCredentials != null) && IsSame(AutofillActivity.LastReceivedCredentials.Url, url))
{
//Android.Util.Log.Debug("KP2AAS", "Filling credentials for " + url);
FillPassword(url, usernameEdit, emptyPasswordFields);
}
else
{
//Android.Util.Log.Debug("KP2AAS", "Notif for " + url);
if(AutofillActivity.LastReceivedCredentials != null)
{
//Android.Util.Log.Debug("KP2AAS", LookupCredentialsActivity.LastReceivedCredentials.Url);
//Android.Util.Log.Debug("KP2AAS", url);
}
AskFillPassword(url, usernameEdit, emptyPasswordFields);
cancelNotification = false;
}
var allEditTexts = GetWindowNodes(root, e, n => EditText(n));
var usernameEditText = allEditTexts.TakeWhile(n => !n.Password).LastOrDefault();
FillCredentials(usernameEditText, passwordNodes);
}
if(cancelNotification)
else
{
((NotificationManager)GetSystemService(NotificationService)).Cancel(autoFillNotificationId);
//Android.Util.Log.Debug("KP2AAS", "Cancel notif");
NotifyToAutofill(uri);
cancelNotification = false;
}
AutofillActivity.LastCredentials = null;
}
if(cancelNotification)
{
CancelNotification();
}
break;
default:
@@ -107,123 +111,163 @@ namespace Bit.Android
}
private static void UrlFromAddressField(ref string url, AccessibilityNodeInfo addressField)
protected override void OnServiceConnected()
{
if(addressField != null)
{
url = addressField.Text;
if(!url.Contains("://"))
url = "http://" + url;
}
base.OnServiceConnected();
Enabled = true;
}
private bool IsSame(string url1, string url2)
public override void OnDestroy()
{
if(url1.StartsWith("androidapp://"))
return url1 == url2;
//return KeePassLib.Utility.UrlUtil.GetHost(url1) == KeePassLib.Utility.UrlUtil.GetHost(url2);
base.OnDestroy();
Enabled = false;
}
private void CancelNotification()
{
var notificationManager = ((NotificationManager)GetSystemService(NotificationService));
notificationManager.Cancel(AutoFillNotificationId);
}
private string GetUri(AccessibilityNodeInfo root)
{
var uri = string.Concat(App.Constants.AndroidAppProtocol, root.PackageName);
if(BrowserPackages.ContainsKey(root.PackageName))
{
foreach(var addressViewId in BrowserPackages[root.PackageName])
{
var addressNode = root.FindAccessibilityNodeInfosByViewId(
$"{root.PackageName}:id/{addressViewId}").FirstOrDefault();
if(addressNode == null)
{
continue;
}
uri = ExtractUri(uri, addressNode);
break;
}
}
return uri;
}
private string ExtractUri(string uri, AccessibilityNodeInfo addressNode)
{
if(addressNode != null)
{
uri = addressNode.Text;
if(!uri.Contains("://"))
{
uri = string.Concat("http://", uri);
}
else if(Build.VERSION.SdkInt <= BuildVersionCodes.KitkatWatch)
{
var parts = uri.Split(new string[] { ". " }, StringSplitOptions.None);
if(parts.Length > 1)
{
var urlPart = parts.FirstOrDefault(p => p.StartsWith("http"));
if(urlPart != null)
{
uri = urlPart.Trim();
}
}
}
}
return uri;
}
/// <summary>
/// Check to make sure it is ok to autofill still on the current screen
/// </summary>
private bool NeedToAutofill(AutofillCredentials creds, string currentUriString)
{
if(creds == null)
{
return false;
}
Uri lastUri, currentUri;
if(Uri.TryCreate(creds.LastUri, UriKind.Absolute, out lastUri) &&
Uri.TryCreate(currentUriString, UriKind.Absolute, out currentUri) &&
lastUri.Host == currentUri.Host)
{
return true;
}
return false;
}
private static bool IsPasswordField(AccessibilityNodeInfo n)
private static bool EditText(AccessibilityNodeInfo n)
{
//if (n.Password) Android.Util.Log.Debug(_logTag, "pwdx with " + (n.Text == null ? "null" : n.Text));
var res = n.Password && string.IsNullOrEmpty(n.Text);
// if (n.Password) Android.Util.Log.Debug(_logTag, "pwd with " + n.Text + res);
return res;
return n.ClassName != null && n.ClassName.Contains("EditText");
}
private static bool IsEditText(AccessibilityNodeInfo n)
private void NotifyToAutofill(string uri)
{
//it seems like n.Editable is not a good check as this is false for some fields which are actually editable, at least in tests with Chrome.
return (n.ClassName != null) && (n.ClassName.Contains("EditText"));
}
private void AskFillPassword(string url, AccessibilityNodeInfo usernameEdit, IEnumerable<AccessibilityNodeInfo> passwordFields)
{
var runSearchIntent = new Intent(this, typeof(AutofillActivity));
runSearchIntent.PutExtra("url", url);
runSearchIntent.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop);
var pending = PendingIntent.GetActivity(this, 0, runSearchIntent, PendingIntentFlags.UpdateCurrent);
var targetName = url;
if(url.StartsWith(androidAppPrefix))
{
var packageName = url.Substring(androidAppPrefix.Length);
try
{
var appInfo = PackageManager.GetApplicationInfo(packageName, 0);
targetName = (string)(appInfo != null ? PackageManager.GetApplicationLabel(appInfo) : packageName);
}
catch(Exception e)
{
//Android.Util.Log.Debug(_logTag, e.ToString());
targetName = packageName;
}
}
else
{
//targetName = KeePassLib.Utility.UrlUtil.GetHost(url);
}
var intent = new Intent(this, typeof(AutofillActivity));
intent.PutExtra("uri", uri);
intent.SetFlags(ActivityFlags.NewTask | ActivityFlags.SingleTop | ActivityFlags.ClearTop);
var pendingIntent = PendingIntent.GetActivity(this, 0, intent, PendingIntentFlags.UpdateCurrent);
var builder = new Notification.Builder(this);
//TODO icon
//TODO plugin icon
builder.SetSmallIcon(Resource.Drawable.icon)
.SetContentText("Content Text")
.SetContentTitle("Content Title")
builder.SetSmallIcon(Resource.Drawable.notification_sm)
.SetContentTitle(App.Resources.AppResources.BitwardenAutofillService)
.SetContentText(App.Resources.AppResources.BitwardenAutofillServiceNotificationContent)
.SetTicker(App.Resources.AppResources.BitwardenAutofillServiceNotificationContent)
.SetWhen(Java.Lang.JavaSystem.CurrentTimeMillis())
.SetTicker("Ticker Text")
.SetVisibility(NotificationVisibility.Secret)
.SetContentIntent(pending);
.SetContentIntent(pendingIntent);
if(Build.VERSION.SdkInt > BuildVersionCodes.KitkatWatch)
{
builder.SetVisibility(NotificationVisibility.Secret)
.SetColor(global::Android.Support.V4.Content.ContextCompat.GetColor(ApplicationContext,
Resource.Color.primary));
}
var notificationManager = (NotificationManager)GetSystemService(NotificationService);
notificationManager.Notify(autoFillNotificationId, builder.Build());
notificationManager.Notify(AutoFillNotificationId, builder.Build());
}
private void FillPassword(string url, AccessibilityNodeInfo usernameEdit, IEnumerable<AccessibilityNodeInfo> passwordFields)
private void FillCredentials(AccessibilityNodeInfo usernameNode, IEnumerable<AccessibilityNodeInfo> passwordNodes)
{
FillDataInTextField(usernameEdit, AutofillActivity.LastReceivedCredentials.User);
foreach(var pwd in passwordFields)
FillDataInTextField(pwd, AutofillActivity.LastReceivedCredentials.Password);
AutofillActivity.LastReceivedCredentials = null;
FillEditText(usernameNode, AutofillActivity.LastCredentials.Username);
foreach(var n in passwordNodes)
{
FillEditText(n, AutofillActivity.LastCredentials.Password);
}
}
private static void FillDataInTextField(AccessibilityNodeInfo edit, string newValue)
private static void FillEditText(AccessibilityNodeInfo editTextNode, string value)
{
Bundle b = new Bundle();
b.PutString(AccessibilityNodeInfo.ActionArgumentSetTextCharsequence, newValue);
edit.PerformAction(global::Android.Views.Accessibility.Action.SetText, b);
if(editTextNode == null || value == null)
{
return;
}
var bundle = new Bundle();
bundle.PutString(AccessibilityNodeInfo.ActionArgumentSetTextCharsequence, value);
editTextNode.PerformAction(global::Android.Views.Accessibility.Action.SetText, bundle);
}
private bool ExistsNodeOrChildren(AccessibilityNodeInfo n, Func<AccessibilityNodeInfo, bool> p)
{
return GetNodeOrChildren(n, p).Any();
}
private IEnumerable<AccessibilityNodeInfo> GetNodeOrChildren(AccessibilityNodeInfo n, Func<AccessibilityNodeInfo, bool> p)
private IEnumerable<AccessibilityNodeInfo> GetWindowNodes(AccessibilityNodeInfo n,
AccessibilityEvent e, Func<AccessibilityNodeInfo, bool> p)
{
if(n != null)
{
if(p(n))
if(n.WindowId == e.WindowId && !(n.ViewIdResourceName?.StartsWith(SystemUiPackage) ?? false) && p(n))
{
yield return n;
}
for(int i = 0; i < n.ChildCount; i++)
{
foreach(var x in GetNodeOrChildren(n.GetChild(i), p))
foreach(var node in GetWindowNodes(n.GetChild(i), e, p))
{
yield return x;
yield return node;
}
}
}
}
}
}
}

View File

@@ -0,0 +1,774 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Android.Runtime;
using Java.IO;
using Java.Net;
using Java.Security;
using Java.Security.Cert;
using Javax.Net.Ssl;
namespace Xamarin.Android.Net
{
/// <summary>
/// A custom implementation of <see cref="System.Net.Http.HttpClientHandler"/> which internally uses <see cref="Java.Net.HttpURLConnection"/>
/// (or its HTTPS incarnation) to send HTTP requests.
/// </summary>
/// <remarks>
/// <para>Instance of this class is used to configure <see cref="System.Net.Http.HttpClient"/> instance
/// in the following way:
///
/// <example>
/// var handler = new AndroidClientHandler {
/// UseCookies = true,
/// AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip,
/// };
///
/// var httpClient = new HttpClient (handler);
/// var response = httpClient.GetAsync ("http://example.com")?.Result as AndroidHttpResponseMessage;
/// </example></para>
/// <para>
/// The class supports pre-authentication of requests albeit in a slightly "manual" way. Namely, whenever a request to a server requiring authentication
/// is made and no authentication credentials are provided in the <see cref="PreAuthenticationData"/> property (which is usually the case on the first
/// request), the <see cref="RequestNeedsAuthorization"/> property will return <c>true</c> and the <see cref="RequestedAuthentication"/> property will
/// contain all the authentication information gathered from the server. The application must then fill in the blanks (i.e. the credentials) and re-send
/// the request configured to perform pre-authentication. The reason for this manual process is that the underlying Java HTTP client API supports only a
/// single, VM-wide, authentication handler which cannot be configured to handle credentials for several requests. AndroidClientHandler, therefore, implements
/// the authentication in managed .NET code. Message handler supports both Basic and Digest authentication. If an authentication scheme that's not supported
/// by AndroidClientHandler is requested by the server, the application can provide its own authentication module (<see cref="AuthenticationData"/>,
/// <see cref="PreAuthenticationData"/>) to handle the protocol authorization.</para>
/// <para>AndroidClientHandler also supports requests to servers with "invalid" (e.g. self-signed) SSL certificates. Since this process is a bit convoluted using
/// the Java APIs, AndroidClientHandler defines two ways to handle the situation. First, easier, is to store the necessary certificates (either CA or server certificates)
/// in the <see cref="TrustedCerts"/> collection or, after deriving a custom class from AndroidClientHandler, by overriding one or more methods provided for this purpose
/// (<see cref="ConfigureTrustManagerFactory"/>, <see cref="ConfigureKeyManagerFactory"/> and <see cref="ConfigureKeyStore"/>). The former method should be sufficient
/// for most use cases, the latter allows the application to provide fully customized key store, trust manager and key manager, if needed. Note that the instance of
/// AndroidClientHandler configured to accept an "invalid" certificate from the particular server will most likely fail to validate certificates from other servers (even
/// if they use a certificate with a fully validated trust chain) unless you store the CA certificates from your Android system in <see cref="TrustedCerts"/> along with
/// the self-signed certificate(s).</para>
/// </remarks>
public class CustomAndroidClientHandler : HttpClientHandler
{
sealed class RequestRedirectionState
{
public Uri NewUrl;
public int RedirectCounter;
public HttpMethod Method;
}
internal const string LOG_APP = "monodroid-net";
const string GZIP_ENCODING = "gzip";
const string DEFLATE_ENCODING = "deflate";
const string IDENTITY_ENCODING = "identity";
static readonly HashSet<string> known_content_headers = new HashSet<string>(StringComparer.OrdinalIgnoreCase) {
"Allow",
"Content-Disposition",
"Content-Encoding",
"Content-Language",
"Content-Length",
"Content-Location",
"Content-MD5",
"Content-Range",
"Content-Type",
"Expires",
"Last-Modified"
};
static readonly List<IAndroidAuthenticationModule> authModules = new List<IAndroidAuthenticationModule> {
new AuthModuleBasic (),
// COMMENTED OUT: Kyle
//new AuthModuleDigest ()
};
bool disposed;
// Now all hail Java developers! Get this... HttpURLClient defaults to accepting AND
// uncompressing the gzip content encoding UNLESS you set the Accept-Encoding header to ANY
// value. So if we set it to 'gzip' below we WILL get gzipped stream but HttpURLClient will NOT
// uncompress it any longer, doh. And they don't support 'deflate' so we need to handle it ourselves.
bool decompress_here;
/// <summary>
/// <para>
/// Gets or sets the pre authentication data for the request. This property must be set by the application
/// before the request is made. Generally the value can be taken from <see cref="RequestedAuthentication"/>
/// after the initial request, without any authentication data, receives the authorization request from the
/// server. The application must then store credentials in instance of <see cref="AuthenticationData"/> and
/// assign the instance to this propery before retrying the request.
/// </para>
/// <para>
/// The property is never set by AndroidClientHandler.
/// </para>
/// </summary>
/// <value>The pre authentication data.</value>
public AuthenticationData PreAuthenticationData { get; set; }
/// <summary>
/// If the website requires authentication, this property will contain data about each scheme supported
/// by the server after the response. Note that unauthorized request will return a valid response - you
/// need to check the status code and and (re)configure AndroidClientHandler instance accordingly by providing
/// both the credentials and the authentication scheme by setting the <see cref="PreAuthenticationData"/>
/// property. If AndroidClientHandler is not able to detect the kind of authentication scheme it will store an
/// instance of <see cref="AuthenticationData"/> with its <see cref="AuthenticationData.Scheme"/> property
/// set to <c>AuthenticationScheme.Unsupported</c> and the application will be responsible for providing an
/// instance of <see cref="IAndroidAuthenticationModule"/> which handles this kind of authorization scheme
/// (<see cref="AuthenticationData.AuthModule"/>
/// </summary>
public IList<AuthenticationData> RequestedAuthentication { get; private set; }
/// <summary>
/// Server authentication response indicates that the request to authorize comes from a proxy if this property is <c>true</c>.
/// All the instances of <see cref="AuthenticationData"/> stored in the <see cref="RequestedAuthentication"/> property will
/// have their <see cref="AuthenticationData.UseProxyAuthentication"/> preset to the same value as this property.
/// </summary>
public bool ProxyAuthenticationRequested { get; private set; }
/// <summary>
/// If <c>true</c> then the server requested authorization and the application must use information
/// found in <see cref="RequestedAuthentication"/> to set the value of <see cref="PreAuthenticationData"/>
/// </summary>
public bool RequestNeedsAuthorization
{
get { return RequestedAuthentication?.Count > 0; }
}
/// <summary>
/// <para>
/// If the request is to the server protected with a self-signed (or otherwise untrusted) SSL certificate, the request will
/// fail security chain verification unless the application provides either the CA certificate of the entity which issued the
/// server's certificate or, alternatively, provides the server public key. Whichever the case, the certificate(s) must be stored
/// in this property in order for AndroidClientHandler to configure the request to accept the server certificate.</para>
/// <para>AndroidClientHandler uses a custom <see cref="KeyStore"/> and <see cref="TrustManagerFactory"/> to configure the connection.
/// If, however, the application requires finer control over the SSL configuration (e.g. it implements its own TrustManager) then
/// it should leave this property empty and instead derive a custom class from AndroidClientHandler and override, as needed, the
/// <see cref="ConfigureTrustManagerFactory"/>, <see cref="ConfigureKeyManagerFactory"/> and <see cref="ConfigureKeyStore"/> methods
/// instead</para>
/// </summary>
/// <value>The trusted certs.</value>
public IList<Certificate> TrustedCerts { get; set; }
protected override void Dispose(bool disposing)
{
disposed = true;
base.Dispose(disposing);
}
protected void AssertSelf()
{
if(!disposed)
return;
throw new ObjectDisposedException(nameof(AndroidClientHandler));
}
string EncodeUrl(Uri url)
{
if(url == null)
return String.Empty;
if(String.IsNullOrEmpty(url.Query))
return Uri.EscapeUriString(url.ToString());
// UriBuilder takes care of encoding everything properly
var bldr = new UriBuilder(url);
if(url.IsDefaultPort)
bldr.Port = -1; // Avoids adding :80 or :443 to the host name in the result
// bldr.Uri.ToString () would ruin the good job UriBuilder did
return bldr.ToString();
}
/// <summary>
/// Creates, configures and processes an asynchronous request to the indicated resource.
/// </summary>
/// <returns>Task in which the request is executed</returns>
/// <param name="request">Request provided by <see cref="System.Net.Http.HttpClient"/></param>
/// <param name="cancellationToken">Cancellation token.</param>
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
AssertSelf();
if(request == null)
throw new ArgumentNullException(nameof(request));
if(!request.RequestUri.IsAbsoluteUri)
throw new ArgumentException("Must represent an absolute URI", "request");
var redirectState = new RequestRedirectionState
{
NewUrl = request.RequestUri,
RedirectCounter = 0,
Method = request.Method
};
while(true)
{
URL java_url = new URL(EncodeUrl(redirectState.NewUrl));
URLConnection java_connection = java_url.OpenConnection();
HttpURLConnection httpConnection = await SetupRequestInternal(request, java_connection);
HttpResponseMessage response = await ProcessRequest(request, java_url, httpConnection, cancellationToken, redirectState);
if(response != null)
return response;
if(redirectState.NewUrl == null)
throw new InvalidOperationException("Request redirected but no new URI specified");
request.Method = redirectState.Method;
}
}
Task<HttpResponseMessage> ProcessRequest(HttpRequestMessage request, URL javaUrl, HttpURLConnection httpConnection, CancellationToken cancellationToken, RequestRedirectionState redirectState)
{
cancellationToken.ThrowIfCancellationRequested();
httpConnection.InstanceFollowRedirects = false; // We handle it ourselves
RequestedAuthentication = null;
ProxyAuthenticationRequested = false;
return DoProcessRequest(request, javaUrl, httpConnection, cancellationToken, redirectState);
}
async Task<HttpResponseMessage> DoProcessRequest(HttpRequestMessage request, URL javaUrl, HttpURLConnection httpConnection, CancellationToken cancellationToken, RequestRedirectionState redirectState)
{
if(cancellationToken.IsCancellationRequested)
{
cancellationToken.ThrowIfCancellationRequested();
}
try
{
await Task.WhenAny(
httpConnection.ConnectAsync(),
Task.Run(() => { cancellationToken.WaitHandle.WaitOne(); }))
.ConfigureAwait(false);
}
catch(Java.Net.ConnectException ex)
{
// Wrap it nicely in a "standard" exception so that it's compatible with HttpClientHandler
throw new WebException(ex.Message, ex, WebExceptionStatus.ConnectFailure, null);
}
if(cancellationToken.IsCancellationRequested)
{
httpConnection.Disconnect();
cancellationToken.ThrowIfCancellationRequested();
}
cancellationToken.Register(httpConnection.Disconnect);
if(httpConnection.DoOutput)
{
using(var stream = await request.Content.ReadAsStreamAsync())
{
await stream.CopyToAsync(httpConnection.OutputStream, 4096, cancellationToken)
.ConfigureAwait(false);
}
}
if(cancellationToken.IsCancellationRequested)
{
httpConnection.Disconnect();
cancellationToken.ThrowIfCancellationRequested();
}
var statusCode = await Task.Run(() => (HttpStatusCode)httpConnection.ResponseCode).ConfigureAwait(false);
var connectionUri = new Uri(httpConnection.URL.ToString());
// If the request was redirected we need to put the new URL in the request
request.RequestUri = connectionUri;
var ret = new AndroidHttpResponseMessage(javaUrl, httpConnection)
{
RequestMessage = request,
ReasonPhrase = httpConnection.ResponseMessage,
StatusCode = statusCode,
};
bool disposeRet;
if(HandleRedirect(statusCode, httpConnection, redirectState, out disposeRet))
{
if(disposeRet)
{
ret.Dispose();
ret = null;
}
return ret;
}
switch(statusCode)
{
case HttpStatusCode.Unauthorized:
case HttpStatusCode.ProxyAuthenticationRequired:
// We don't resend the request since that would require new set of credentials if the
// ones provided in Credentials are invalid (or null) and that, in turn, may require asking the
// user which is not something that should be taken care of by us and in this
// context. The application should be responsible for this.
// HttpClientHandler throws an exception in this instance, but I think it's not a good
// idea. We'll return the response message with all the information required by the
// application to fill in the blanks and provide the requested credentials instead.
//
// We return the body of the response too, but the Java client will throw
// a FileNotFound exception if we attempt to access the input stream.
// Instead we try to read the error stream and return an default message if the error stream isn't readable.
ret.Content = GetErrorContent(httpConnection, new StringContent("Unauthorized", Encoding.ASCII));
CopyHeaders(httpConnection, ret);
if(ret.Headers.WwwAuthenticate != null)
{
ProxyAuthenticationRequested = false;
CollectAuthInfo(ret.Headers.WwwAuthenticate);
}
else if(ret.Headers.ProxyAuthenticate != null)
{
ProxyAuthenticationRequested = true;
CollectAuthInfo(ret.Headers.ProxyAuthenticate);
}
// COMMENTED OUT: Kyle
//ret.RequestedAuthentication = RequestedAuthentication;
return ret;
}
if(!IsErrorStatusCode(statusCode))
{
ret.Content = GetContent(httpConnection, httpConnection.InputStream);
}
else
{
// For 400 >= response code <= 599 the Java client throws the FileNotFound exception when attempting to read from the input stream.
// Instead we try to read the error stream and return an empty string if the error stream isn't readable.
ret.Content = GetErrorContent(httpConnection, new StringContent(String.Empty, Encoding.ASCII));
}
CopyHeaders(httpConnection, ret);
IEnumerable<string> cookieHeaderValue;
if(!UseCookies || CookieContainer == null || !ret.Headers.TryGetValues("Set-Cookie", out cookieHeaderValue) || cookieHeaderValue == null)
{
return ret;
}
try
{
CookieContainer.SetCookies(connectionUri, String.Join(",", cookieHeaderValue));
}
catch(Exception ex)
{
// We don't want to terminate the response because of a bad cookie, hence just reporting
// the issue. We might consider adding a virtual method to let the user handle the
// issue, but not sure if it's really needed. Set-Cookie header will be part of the
// header collection so the user can always examine it if they spot an error.
}
return ret;
}
HttpContent GetErrorContent(HttpURLConnection httpConnection, HttpContent fallbackContent)
{
var contentStream = httpConnection.ErrorStream;
if(contentStream != null)
{
return GetContent(httpConnection, contentStream);
}
return fallbackContent;
}
HttpContent GetContent(URLConnection httpConnection, Stream contentStream)
{
Stream inputStream = new BufferedStream(contentStream);
if(decompress_here)
{
string[] encodings = httpConnection.ContentEncoding?.Split(',');
if(encodings != null)
{
if(encodings.Contains(GZIP_ENCODING, StringComparer.OrdinalIgnoreCase))
inputStream = new GZipStream(inputStream, CompressionMode.Decompress);
else if(encodings.Contains(DEFLATE_ENCODING, StringComparer.OrdinalIgnoreCase))
inputStream = new DeflateStream(inputStream, CompressionMode.Decompress);
}
}
return new StreamContent(inputStream);
}
bool HandleRedirect(HttpStatusCode redirectCode, HttpURLConnection httpConnection, RequestRedirectionState redirectState, out bool disposeRet)
{
if(!AllowAutoRedirect)
{
disposeRet = false;
return true; // We shouldn't follow and there's no data to fetch, just return
}
disposeRet = true;
redirectState.NewUrl = null;
switch(redirectCode)
{
case HttpStatusCode.MultipleChoices: // 300
break;
case HttpStatusCode.Moved: // 301
case HttpStatusCode.Redirect: // 302
case HttpStatusCode.SeeOther: // 303
redirectState.Method = HttpMethod.Get;
break;
case HttpStatusCode.TemporaryRedirect: // 307
break;
default:
if((int)redirectCode >= 300 && (int)redirectCode < 400)
throw new InvalidOperationException($"HTTP Redirection status code {redirectCode} ({(int)redirectCode}) not supported");
return false;
}
IDictionary<string, IList<string>> headers = httpConnection.HeaderFields;
IList<string> locationHeader;
if(!headers.TryGetValue("Location", out locationHeader) || locationHeader == null || locationHeader.Count == 0)
throw new InvalidOperationException($"HTTP connection redirected with code {redirectCode} ({(int)redirectCode}) but no Location header found in response");
redirectState.RedirectCounter++;
if(redirectState.RedirectCounter >= MaxAutomaticRedirections)
throw new WebException($"Maximum automatic redirections exceeded (allowed {MaxAutomaticRedirections}, redirected {redirectState.RedirectCounter} times)");
Uri location = new Uri(locationHeader[0], UriKind.Absolute);
redirectState.NewUrl = location;
return true;
}
bool IsErrorStatusCode(HttpStatusCode statusCode)
{
return (int)statusCode >= 400 && (int)statusCode <= 599;
}
void CollectAuthInfo(HttpHeaderValueCollection<AuthenticationHeaderValue> headers)
{
var authData = new List<AuthenticationData>(headers.Count);
foreach(AuthenticationHeaderValue ahv in headers)
{
var data = new AuthenticationData
{
Scheme = GetAuthScheme(ahv.Scheme),
// COMMENTED OUT: Kyle
//Challenge = $"{ahv.Scheme} {ahv.Parameter}",
UseProxyAuthentication = ProxyAuthenticationRequested
};
authData.Add(data);
}
RequestedAuthentication = authData.AsReadOnly();
}
AuthenticationScheme GetAuthScheme(string scheme)
{
if(String.Compare("basic", scheme, StringComparison.OrdinalIgnoreCase) == 0)
return AuthenticationScheme.Basic;
if(String.Compare("digest", scheme, StringComparison.OrdinalIgnoreCase) == 0)
return AuthenticationScheme.Digest;
return AuthenticationScheme.Unsupported;
}
void CopyHeaders(HttpURLConnection httpConnection, HttpResponseMessage response)
{
IDictionary<string, IList<string>> headers = httpConnection.HeaderFields;
foreach(string key in headers.Keys)
{
if(key == null) // First header entry has null key, it corresponds to the response message
continue;
HttpHeaders item_headers;
string kind;
if(known_content_headers.Contains(key))
{
kind = "content";
item_headers = response.Content.Headers;
}
else
{
kind = "response";
item_headers = response.Headers;
}
item_headers.TryAddWithoutValidation(key, headers[key]);
}
}
/// <summary>
/// Configure the <see cref="HttpURLConnection"/> before the request is sent. This method is meant to be overriden
/// by applications which need to perform some extra configuration steps on the connection. It is called with all
/// the request headers set, pre-authentication performed (if applicable) but before the request body is set
/// (e.g. for POST requests). The default implementation in AndroidClientHandler does nothing.
/// </summary>
/// <param name="request">Request data</param>
/// <param name="conn">Pre-configured connection instance</param>
protected virtual Task SetupRequest(HttpRequestMessage request, HttpURLConnection conn)
{
return Task.Factory.StartNew(AssertSelf);
}
/// <summary>
/// Configures the key store. The <paramref name="keyStore"/> parameter is set to instance of <see cref="KeyStore"/>
/// created using the <see cref="KeyStore.DefaultType"/> type and with populated with certificates provided in the <see cref="TrustedCerts"/>
/// property. AndroidClientHandler implementation simply returns the instance passed in the <paramref name="keyStore"/> parameter
/// </summary>
/// <returns>The key store.</returns>
/// <param name="keyStore">Key store to configure.</param>
protected virtual KeyStore ConfigureKeyStore(KeyStore keyStore)
{
AssertSelf();
return keyStore;
}
/// <summary>
/// Create and configure an instance of <see cref="KeyManagerFactory"/>. The <paramref name="keyStore"/> parameter is set to the
/// return value of the <see cref="ConfigureKeyStore"/> method, so it might be null if the application overrode the method and provided
/// no key store. It will not be <c>null</c> when the default implementation is used. The application can return <c>null</c> here since
/// KeyManagerFactory is not required for the custom SSL configuration, but it might be used by the application to implement a more advanced
/// mechanism of key management.
/// </summary>
/// <returns>The key manager factory or <c>null</c>.</returns>
/// <param name="keyStore">Key store.</param>
protected virtual KeyManagerFactory ConfigureKeyManagerFactory(KeyStore keyStore)
{
AssertSelf();
return null;
}
/// <summary>
/// Create and configure an instance of <see cref="TrustManagerFactory"/>. The <paramref name="keyStore"/> parameter is set to the
/// return value of the <see cref="ConfigureKeyStore"/> method, so it might be null if the application overrode the method and provided
/// no key store. It will not be <c>null</c> when the default implementation is used. The application can return <c>null</c> from this
/// method in which case AndroidClientHandler will create its own instance of the trust manager factory provided that the <see cref="TrustCerts"/>
/// list contains at least one valid certificate. If there are no valid certificates and this method returns <c>null</c>, no custom
/// trust manager will be created since that would make all the HTTPS requests fail.
/// </summary>
/// <returns>The trust manager factory.</returns>
/// <param name="keyStore">Key store.</param>
protected virtual TrustManagerFactory ConfigureTrustManagerFactory(KeyStore keyStore)
{
AssertSelf();
return null;
}
void AppendEncoding(string encoding, ref List<string> list)
{
if(list == null)
list = new List<string>();
if(list.Contains(encoding))
return;
list.Add(encoding);
}
async Task<HttpURLConnection> SetupRequestInternal(HttpRequestMessage request, URLConnection conn)
{
if(conn == null)
throw new ArgumentNullException(nameof(conn));
var httpConnection = conn.JavaCast<HttpURLConnection>();
if(httpConnection == null)
throw new InvalidOperationException($"Unsupported URL scheme {conn.URL.Protocol}");
httpConnection.RequestMethod = request.Method.ToString();
// SSL context must be set up as soon as possible, before adding any content or
// headers. Otherwise Java won't use the socket factory
SetupSSL(httpConnection as HttpsURLConnection);
if(request.Content != null)
AddHeaders(httpConnection, request.Content.Headers);
AddHeaders(httpConnection, request.Headers);
List<string> accept_encoding = null;
decompress_here = false;
if((AutomaticDecompression & DecompressionMethods.GZip) != 0)
{
AppendEncoding(GZIP_ENCODING, ref accept_encoding);
decompress_here = true;
}
if((AutomaticDecompression & DecompressionMethods.Deflate) != 0)
{
AppendEncoding(DEFLATE_ENCODING, ref accept_encoding);
decompress_here = true;
}
if(AutomaticDecompression == DecompressionMethods.None)
{
accept_encoding?.Clear();
AppendEncoding(IDENTITY_ENCODING, ref accept_encoding); // Turns off compression for the Java client
}
if(accept_encoding?.Count > 0)
httpConnection.SetRequestProperty("Accept-Encoding", String.Join(",", accept_encoding));
if(UseCookies && CookieContainer != null)
{
string cookieHeaderValue = CookieContainer.GetCookieHeader(request.RequestUri);
if(!String.IsNullOrEmpty(cookieHeaderValue))
httpConnection.SetRequestProperty("Cookie", cookieHeaderValue);
}
HandlePreAuthentication(httpConnection);
await SetupRequest(request, httpConnection);
SetupRequestBody(httpConnection, request);
return httpConnection;
}
void SetupSSL(HttpsURLConnection httpsConnection)
{
if(httpsConnection == null)
return;
KeyStore keyStore = KeyStore.GetInstance(KeyStore.DefaultType);
keyStore.Load(null, null);
bool gotCerts = TrustedCerts?.Count > 0;
if(gotCerts)
{
for(int i = 0; i < TrustedCerts.Count; i++)
{
Certificate cert = TrustedCerts[i];
if(cert == null)
continue;
keyStore.SetCertificateEntry($"ca{i}", cert);
}
}
keyStore = ConfigureKeyStore(keyStore);
KeyManagerFactory kmf = ConfigureKeyManagerFactory(keyStore);
TrustManagerFactory tmf = ConfigureTrustManagerFactory(keyStore);
if(tmf == null)
{
// If there are no certs and no trust manager factory, we can't use a custom manager
// because it will cause all the HTTPS requests to fail because of unverified trust
// chain
if(!gotCerts)
return;
tmf = TrustManagerFactory.GetInstance(TrustManagerFactory.DefaultAlgorithm);
tmf.Init(keyStore);
}
SSLContext context = SSLContext.GetInstance("TLS");
context.Init(kmf?.GetKeyManagers(), tmf.GetTrustManagers(), null);
httpsConnection.SSLSocketFactory = context.SocketFactory;
}
void HandlePreAuthentication(HttpURLConnection httpConnection)
{
AuthenticationData data = PreAuthenticationData;
if(!PreAuthenticate || data == null)
return;
ICredentials creds = data.UseProxyAuthentication ? Proxy?.Credentials : Credentials;
if(creds == null)
{
return;
}
IAndroidAuthenticationModule auth = data.Scheme == AuthenticationScheme.Unsupported ? data.AuthModule : authModules.Find(m => m?.Scheme == data.Scheme);
if(auth == null)
{
return;
}
Authorization authorization = auth.Authenticate(data.Challenge, httpConnection, creds);
if(authorization == null)
{
return;
}
httpConnection.SetRequestProperty(data.UseProxyAuthentication ? "Proxy-Authorization" : "Authorization", authorization.Message);
}
void AddHeaders(HttpURLConnection conn, HttpHeaders headers)
{
if(headers == null)
return;
foreach(KeyValuePair<string, IEnumerable<string>> header in headers)
{
conn.SetRequestProperty(header.Key, header.Value != null ? String.Join(",", header.Value) : String.Empty);
}
}
void SetupRequestBody(HttpURLConnection httpConnection, HttpRequestMessage request)
{
if(request.Content == null)
{
// Pilfered from System.Net.Http.HttpClientHandler:SendAync
if(HttpMethod.Post.Equals(request.Method) || HttpMethod.Put.Equals(request.Method) || HttpMethod.Delete.Equals(request.Method))
{
// Explicitly set this to make sure we're sending a "Content-Length: 0" header.
// This fixes the issue that's been reported on the forums:
// http://forums.xamarin.com/discussion/17770/length-required-error-in-http-post-since-latest-release
httpConnection.SetRequestProperty("Content-Length", "0");
}
return;
}
httpConnection.DoOutput = true;
long? contentLength = request.Content.Headers.ContentLength;
if(contentLength != null)
httpConnection.SetFixedLengthStreamingMode((int)contentLength);
else
httpConnection.SetChunkedStreamingMode(0);
}
}
sealed class AuthModuleBasic : IAndroidAuthenticationModule
{
public AuthenticationScheme Scheme { get; } = AuthenticationScheme.Basic;
public string AuthenticationType { get; } = "Basic";
public bool CanPreAuthenticate { get; } = true;
public Authorization Authenticate(string challenge, HttpURLConnection request, ICredentials credentials)
{
string header = challenge?.Trim();
if(credentials == null || String.IsNullOrEmpty(header))
return null;
if(header.IndexOf("basic", StringComparison.OrdinalIgnoreCase) == -1)
return null;
return InternalAuthenticate(request, credentials);
}
public Authorization PreAuthenticate(HttpURLConnection request, ICredentials credentials)
{
return InternalAuthenticate(request, credentials);
}
Authorization InternalAuthenticate(HttpURLConnection request, ICredentials credentials)
{
if(request == null || credentials == null)
return null;
NetworkCredential cred = credentials.GetCredential(new Uri(request.URL.ToString()), AuthenticationType.ToLowerInvariant());
if(cred == null)
return null;
if(String.IsNullOrEmpty(cred.UserName))
return null;
string domain = cred.Domain?.Trim();
string response = String.Empty;
// If domain is set, MS sends "domain\user:password".
if(!String.IsNullOrEmpty(domain))
response = domain + "\\";
response += cred.UserName + ":" + cred.Password;
return new Authorization($"{AuthenticationType} {Convert.ToBase64String(Encoding.ASCII.GetBytes(response))}");
}
}
}

View File

@@ -14,6 +14,7 @@ using System.Reflection;
using Xamarin.Forms.Platform.Android;
using Xamarin.Forms;
using System.Threading.Tasks;
using Bit.App.Models.Page;
namespace Bit.Android
{
@@ -27,6 +28,27 @@ namespace Bit.Android
protected override void OnCreate(Bundle bundle)
{
string uri = null;
if(!Intent.Flags.HasFlag(ActivityFlags.LaunchedFromHistory) && Intent.HasExtra("uri") && Intent.HasExtra("ts"))
{
var tsDiff = Java.Lang.JavaSystem.CurrentTimeMillis() - Intent.GetLongExtra("ts", 0);
if(tsDiff < 5000)
{
uri = Intent.GetStringExtra("uri");
}
// Attempt to clear intent for future
Intent.ReplaceExtras(new Bundle());
Intent.SetAction(string.Empty);
Intent.SetData(null);
Intent.SetFlags(0);
}
if(!Resolver.IsSet)
{
MainApplication.SetIoc(Application);
}
var policy = new StrictMode.ThreadPolicy.Builder().PermitAll().Build();
StrictMode.SetThreadPolicy(policy);
@@ -54,8 +76,8 @@ namespace Bit.Android
typeof(Color).GetProperty("Accent", BindingFlags.Public | BindingFlags.Static)
.SetValue(null, Color.FromHex("d2d6de"));
LoadApplication(new App.App(
uri,
Resolver.Resolve<IAuthService>(),
Resolver.Resolve<IConnectivity>(),
Resolver.Resolve<IUserDialogs>(),
@@ -65,12 +87,55 @@ namespace Bit.Android
Resolver.Resolve<ISettings>(),
Resolver.Resolve<ILockService>(),
Resolver.Resolve<IGoogleAnalyticsService>(),
Resolver.Resolve<ILocalizeService>()));
Resolver.Resolve<ILocalizeService>(),
Resolver.Resolve<IAppInfoService>()));
MessagingCenter.Subscribe<Xamarin.Forms.Application>(Xamarin.Forms.Application.Current, "RateApp", (sender) =>
{
RateApp();
});
MessagingCenter.Subscribe<Xamarin.Forms.Application>(Xamarin.Forms.Application.Current, "Accessibility", (sender) =>
{
OpenAccessibilitySettings();
});
MessagingCenter.Subscribe<Xamarin.Forms.Application, VaultListPageModel.Login>(
Xamarin.Forms.Application.Current, "Autofill", (sender, args) =>
{
ReturnCredentials(args);
});
MessagingCenter.Subscribe<Xamarin.Forms.Application>(Xamarin.Forms.Application.Current, "BackgroundApp", (sender) =>
{
MoveTaskToBack(true);
});
}
private void ReturnCredentials(VaultListPageModel.Login login)
{
Intent data = new Intent();
if(login == null)
{
data.PutExtra("canceled", "true");
}
else
{
data.PutExtra("uri", login.Uri.Value);
data.PutExtra("username", login.Username);
data.PutExtra("password", login.Password.Value);
}
if(Parent == null)
{
SetResult(Result.Ok, data);
}
else
{
Parent.SetResult(Result.Ok, data);
}
Finish();
}
protected override void OnPause()
@@ -105,8 +170,12 @@ namespace Bit.Android
protected override void OnResume()
{
Console.WriteLine("A OnResume");
base.OnResume();
Console.WriteLine("A OnResume");
// workaround for app compat bug
// ref https://bugzilla.xamarin.com/show_bug.cgi?id=36907
Task.Delay(10).Wait();
}
public void RateApp()
@@ -140,5 +209,11 @@ namespace Bit.Android
intent.AddFlags(flags);
return intent;
}
private void OpenAccessibilitySettings()
{
var intent = new Intent(global::Android.Provider.Settings.ActionAccessibilitySettings);
StartActivity(intent);
}
}
}

View File

@@ -43,7 +43,7 @@ namespace Bit.Android
if(!Resolver.IsSet)
{
SetIoc();
SetIoc(this);
}
}
@@ -178,16 +178,16 @@ namespace Bit.Android
}
}
private void SetIoc()
public static void SetIoc(Application application)
{
UserDialogs.Init(this);
UserDialogs.Init(application);
var container = new UnityContainer();
container
// Android Stuff
.RegisterInstance(ApplicationContext)
.RegisterInstance<Application>(this)
.RegisterInstance(application.ApplicationContext)
.RegisterInstance<Application>(application)
// Services
.RegisterType<IDatabaseService, DatabaseService>(new ContainerControlledLifetimeManager())
.RegisterType<ISqlService, SqlService>(new ContainerControlledLifetimeManager())
@@ -196,7 +196,7 @@ namespace Bit.Android
.RegisterType<IKeyDerivationService, BouncyCastleKeyDerivationService>(new ContainerControlledLifetimeManager())
.RegisterType<IAuthService, AuthService>(new ContainerControlledLifetimeManager())
.RegisterType<IFolderService, FolderService>(new ContainerControlledLifetimeManager())
.RegisterType<ISiteService, SiteService>(new ContainerControlledLifetimeManager())
.RegisterType<ILoginService, LoginService>(new ContainerControlledLifetimeManager())
.RegisterType<ISyncService, SyncService>(new ContainerControlledLifetimeManager())
.RegisterType<IClipboardService, ClipboardService>(new ContainerControlledLifetimeManager())
.RegisterType<IPushNotificationListener, PushNotificationListener>(new ContainerControlledLifetimeManager())
@@ -209,15 +209,20 @@ namespace Bit.Android
.RegisterType<IDeviceInfoService, DeviceInfoService>(new ContainerControlledLifetimeManager())
.RegisterType<ILocalizeService, LocalizeService>(new ContainerControlledLifetimeManager())
.RegisterType<ILogService, LogService>(new ContainerControlledLifetimeManager())
.RegisterType<IHttpService, HttpService>(new ContainerControlledLifetimeManager())
.RegisterType<ITokenService, TokenService>(new ContainerControlledLifetimeManager())
.RegisterType<ISettingsService, SettingsService>(new ContainerControlledLifetimeManager())
// Repositories
.RegisterType<IFolderRepository, FolderRepository>(new ContainerControlledLifetimeManager())
.RegisterType<IFolderApiRepository, FolderApiRepository>(new ContainerControlledLifetimeManager())
.RegisterType<ISiteRepository, SiteRepository>(new ContainerControlledLifetimeManager())
.RegisterType<ISiteApiRepository, SiteApiRepository>(new ContainerControlledLifetimeManager())
.RegisterType<IAuthApiRepository, AuthApiRepository>(new ContainerControlledLifetimeManager())
.RegisterType<ILoginRepository, LoginRepository>(new ContainerControlledLifetimeManager())
.RegisterType<ILoginApiRepository, LoginApiRepository>(new ContainerControlledLifetimeManager())
.RegisterType<IConnectApiRepository, ConnectApiRepository>(new ContainerControlledLifetimeManager())
.RegisterType<IDeviceApiRepository, DeviceApiRepository>(new ContainerControlledLifetimeManager())
.RegisterType<IAccountsApiRepository, AccountsApiRepository>(new ContainerControlledLifetimeManager())
.RegisterType<ICipherApiRepository, CipherApiRepository>(new ContainerControlledLifetimeManager())
.RegisterType<ISettingsRepository, SettingsRepository>(new ContainerControlledLifetimeManager())
.RegisterType<ISettingsApiRepository, SettingsApiRepository>(new ContainerControlledLifetimeManager())
// Other
.RegisterInstance(CrossSettings.Current, new ContainerControlledLifetimeManager())
.RegisterInstance(CrossConnectivity.Current, new ContainerControlledLifetimeManager())

View File

@@ -1,13 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.x8bit.bitwarden" android:versionName="1.2.0" android:installLocation="auto" android:versionCode="9">
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="23" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="com.samsung.android.providers.context.permission.WRITE_USE_APP_FEATURE_SURVEY" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="com.x8bit.bitwarden.permission.C2D_MESSAGE" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<application android:label="bitwarden" android:theme="@style/BitwardenTheme"></application>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.x8bit.bitwarden" android:versionName="1.3.0" android:installLocation="auto" android:versionCode="101">
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="23" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="com.samsung.android.providers.context.permission.WRITE_USE_APP_FEATURE_SURVEY" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="com.x8bit.bitwarden.permission.C2D_MESSAGE" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<application android:label="bitwarden" android:theme="@style/BitwardenTheme">
</application>
</manifest>

View File

@@ -2281,487 +2281,505 @@ namespace Bit.Android
public const int abc_textfield_search_material = 2130837578;
// aapt resource value: 0x7f02004b
public const int cloudup = 2130837579;
public const int accessibility_notification = 2130837579;
// aapt resource value: 0x7f02004c
public const int cogs = 2130837580;
public const int accessibility_notification_icon = 2130837580;
// aapt resource value: 0x7f02004d
public const int cogs_selected = 2130837581;
public const int accessibility_step1 = 2130837581;
// aapt resource value: 0x7f02004e
public const int common_full_open_on_phone = 2130837582;
public const int accessibility_step2 = 2130837582;
// aapt resource value: 0x7f02004f
public const int common_google_signin_btn_icon_dark = 2130837583;
public const int close = 2130837583;
// aapt resource value: 0x7f020050
public const int common_google_signin_btn_icon_dark_disabled = 2130837584;
public const int cloudup = 2130837584;
// aapt resource value: 0x7f020051
public const int common_google_signin_btn_icon_dark_focused = 2130837585;
public const int cogs = 2130837585;
// aapt resource value: 0x7f020052
public const int common_google_signin_btn_icon_dark_normal = 2130837586;
public const int cogs_selected = 2130837586;
// aapt resource value: 0x7f020053
public const int common_google_signin_btn_icon_dark_pressed = 2130837587;
public const int common_full_open_on_phone = 2130837587;
// aapt resource value: 0x7f020054
public const int common_google_signin_btn_icon_light = 2130837588;
public const int common_google_signin_btn_icon_dark = 2130837588;
// aapt resource value: 0x7f020055
public const int common_google_signin_btn_icon_light_disabled = 2130837589;
public const int common_google_signin_btn_icon_dark_disabled = 2130837589;
// aapt resource value: 0x7f020056
public const int common_google_signin_btn_icon_light_focused = 2130837590;
public const int common_google_signin_btn_icon_dark_focused = 2130837590;
// aapt resource value: 0x7f020057
public const int common_google_signin_btn_icon_light_normal = 2130837591;
public const int common_google_signin_btn_icon_dark_normal = 2130837591;
// aapt resource value: 0x7f020058
public const int common_google_signin_btn_icon_light_pressed = 2130837592;
public const int common_google_signin_btn_icon_dark_pressed = 2130837592;
// aapt resource value: 0x7f020059
public const int common_google_signin_btn_text_dark = 2130837593;
public const int common_google_signin_btn_icon_light = 2130837593;
// aapt resource value: 0x7f02005a
public const int common_google_signin_btn_text_dark_disabled = 2130837594;
public const int common_google_signin_btn_icon_light_disabled = 2130837594;
// aapt resource value: 0x7f02005b
public const int common_google_signin_btn_text_dark_focused = 2130837595;
public const int common_google_signin_btn_icon_light_focused = 2130837595;
// aapt resource value: 0x7f02005c
public const int common_google_signin_btn_text_dark_normal = 2130837596;
public const int common_google_signin_btn_icon_light_normal = 2130837596;
// aapt resource value: 0x7f02005d
public const int common_google_signin_btn_text_dark_pressed = 2130837597;
public const int common_google_signin_btn_icon_light_pressed = 2130837597;
// aapt resource value: 0x7f02005e
public const int common_google_signin_btn_text_light = 2130837598;
public const int common_google_signin_btn_text_dark = 2130837598;
// aapt resource value: 0x7f02005f
public const int common_google_signin_btn_text_light_disabled = 2130837599;
public const int common_google_signin_btn_text_dark_disabled = 2130837599;
// aapt resource value: 0x7f020060
public const int common_google_signin_btn_text_light_focused = 2130837600;
public const int common_google_signin_btn_text_dark_focused = 2130837600;
// aapt resource value: 0x7f020061
public const int common_google_signin_btn_text_light_normal = 2130837601;
public const int common_google_signin_btn_text_dark_normal = 2130837601;
// aapt resource value: 0x7f020062
public const int common_google_signin_btn_text_light_pressed = 2130837602;
public const int common_google_signin_btn_text_dark_pressed = 2130837602;
// aapt resource value: 0x7f020063
public const int common_ic_googleplayservices = 2130837603;
public const int common_google_signin_btn_text_light = 2130837603;
// aapt resource value: 0x7f020064
public const int common_plus_signin_btn_icon_dark = 2130837604;
public const int common_google_signin_btn_text_light_disabled = 2130837604;
// aapt resource value: 0x7f020065
public const int common_plus_signin_btn_icon_dark_disabled = 2130837605;
public const int common_google_signin_btn_text_light_focused = 2130837605;
// aapt resource value: 0x7f020066
public const int common_plus_signin_btn_icon_dark_focused = 2130837606;
public const int common_google_signin_btn_text_light_normal = 2130837606;
// aapt resource value: 0x7f020067
public const int common_plus_signin_btn_icon_dark_normal = 2130837607;
public const int common_google_signin_btn_text_light_pressed = 2130837607;
// aapt resource value: 0x7f020068
public const int common_plus_signin_btn_icon_dark_pressed = 2130837608;
public const int common_ic_googleplayservices = 2130837608;
// aapt resource value: 0x7f020069
public const int common_plus_signin_btn_icon_light = 2130837609;
public const int common_plus_signin_btn_icon_dark = 2130837609;
// aapt resource value: 0x7f02006a
public const int common_plus_signin_btn_icon_light_disabled = 2130837610;
public const int common_plus_signin_btn_icon_dark_disabled = 2130837610;
// aapt resource value: 0x7f02006b
public const int common_plus_signin_btn_icon_light_focused = 2130837611;
public const int common_plus_signin_btn_icon_dark_focused = 2130837611;
// aapt resource value: 0x7f02006c
public const int common_plus_signin_btn_icon_light_normal = 2130837612;
public const int common_plus_signin_btn_icon_dark_normal = 2130837612;
// aapt resource value: 0x7f02006d
public const int common_plus_signin_btn_icon_light_pressed = 2130837613;
public const int common_plus_signin_btn_icon_dark_pressed = 2130837613;
// aapt resource value: 0x7f02006e
public const int common_plus_signin_btn_text_dark = 2130837614;
public const int common_plus_signin_btn_icon_light = 2130837614;
// aapt resource value: 0x7f02006f
public const int common_plus_signin_btn_text_dark_disabled = 2130837615;
public const int common_plus_signin_btn_icon_light_disabled = 2130837615;
// aapt resource value: 0x7f020070
public const int common_plus_signin_btn_text_dark_focused = 2130837616;
public const int common_plus_signin_btn_icon_light_focused = 2130837616;
// aapt resource value: 0x7f020071
public const int common_plus_signin_btn_text_dark_normal = 2130837617;
public const int common_plus_signin_btn_icon_light_normal = 2130837617;
// aapt resource value: 0x7f020072
public const int common_plus_signin_btn_text_dark_pressed = 2130837618;
public const int common_plus_signin_btn_icon_light_pressed = 2130837618;
// aapt resource value: 0x7f020073
public const int common_plus_signin_btn_text_light = 2130837619;
public const int common_plus_signin_btn_text_dark = 2130837619;
// aapt resource value: 0x7f020074
public const int common_plus_signin_btn_text_light_disabled = 2130837620;
public const int common_plus_signin_btn_text_dark_disabled = 2130837620;
// aapt resource value: 0x7f020075
public const int common_plus_signin_btn_text_light_focused = 2130837621;
public const int common_plus_signin_btn_text_dark_focused = 2130837621;
// aapt resource value: 0x7f020076
public const int common_plus_signin_btn_text_light_normal = 2130837622;
public const int common_plus_signin_btn_text_dark_normal = 2130837622;
// aapt resource value: 0x7f020077
public const int common_plus_signin_btn_text_light_pressed = 2130837623;
public const int common_plus_signin_btn_text_dark_pressed = 2130837623;
// aapt resource value: 0x7f020078
public const int design_fab_background = 2130837624;
public const int common_plus_signin_btn_text_light = 2130837624;
// aapt resource value: 0x7f020079
public const int design_snackbar_background = 2130837625;
public const int common_plus_signin_btn_text_light_disabled = 2130837625;
// aapt resource value: 0x7f02007a
public const int envelope = 2130837626;
public const int common_plus_signin_btn_text_light_focused = 2130837626;
// aapt resource value: 0x7f02007b
public const int eye = 2130837627;
public const int common_plus_signin_btn_text_light_normal = 2130837627;
// aapt resource value: 0x7f02007c
public const int eye_slash = 2130837628;
public const int common_plus_signin_btn_text_light_pressed = 2130837628;
// aapt resource value: 0x7f02007d
public const int fa_lock = 2130837629;
public const int design_fab_background = 2130837629;
// aapt resource value: 0x7f02007e
public const int fa_lock_selected = 2130837630;
public const int design_snackbar_background = 2130837630;
// aapt resource value: 0x7f02007f
public const int fingerprint = 2130837631;
public const int envelope = 2130837631;
// aapt resource value: 0x7f020080
public const int fingerprint_white = 2130837632;
public const int eye = 2130837632;
// aapt resource value: 0x7f020081
public const int folder = 2130837633;
public const int eye_slash = 2130837633;
// aapt resource value: 0x7f020082
public const int globe = 2130837634;
public const int fa_lock = 2130837634;
// aapt resource value: 0x7f020083
public const int hockeyapp_btn_background = 2130837635;
public const int fa_lock_selected = 2130837635;
// aapt resource value: 0x7f020084
public const int ic_audiotrack = 2130837636;
public const int fingerprint = 2130837636;
// aapt resource value: 0x7f020085
public const int ic_audiotrack_light = 2130837637;
public const int fingerprint_white = 2130837637;
// aapt resource value: 0x7f020086
public const int ic_bluetooth_grey = 2130837638;
public const int folder = 2130837638;
// aapt resource value: 0x7f020087
public const int ic_bluetooth_white = 2130837639;
public const int globe = 2130837639;
// aapt resource value: 0x7f020088
public const int ic_cast_dark = 2130837640;
public const int hockeyapp_btn_background = 2130837640;
// aapt resource value: 0x7f020089
public const int ic_cast_disabled_light = 2130837641;
public const int ic_audiotrack = 2130837641;
// aapt resource value: 0x7f02008a
public const int ic_cast_grey = 2130837642;
public const int ic_audiotrack_light = 2130837642;
// aapt resource value: 0x7f02008b
public const int ic_cast_light = 2130837643;
public const int ic_bluetooth_grey = 2130837643;
// aapt resource value: 0x7f02008c
public const int ic_cast_off_light = 2130837644;
public const int ic_bluetooth_white = 2130837644;
// aapt resource value: 0x7f02008d
public const int ic_cast_on_0_light = 2130837645;
public const int ic_cast_dark = 2130837645;
// aapt resource value: 0x7f02008e
public const int ic_cast_on_1_light = 2130837646;
public const int ic_cast_disabled_light = 2130837646;
// aapt resource value: 0x7f02008f
public const int ic_cast_on_2_light = 2130837647;
public const int ic_cast_grey = 2130837647;
// aapt resource value: 0x7f020090
public const int ic_cast_on_light = 2130837648;
public const int ic_cast_light = 2130837648;
// aapt resource value: 0x7f020091
public const int ic_cast_white = 2130837649;
public const int ic_cast_off_light = 2130837649;
// aapt resource value: 0x7f020092
public const int ic_close_dark = 2130837650;
public const int ic_cast_on_0_light = 2130837650;
// aapt resource value: 0x7f020093
public const int ic_close_light = 2130837651;
public const int ic_cast_on_1_light = 2130837651;
// aapt resource value: 0x7f020094
public const int ic_collapse = 2130837652;
public const int ic_cast_on_2_light = 2130837652;
// aapt resource value: 0x7f020095
public const int ic_collapse_00000 = 2130837653;
public const int ic_cast_on_light = 2130837653;
// aapt resource value: 0x7f020096
public const int ic_collapse_00001 = 2130837654;
public const int ic_cast_white = 2130837654;
// aapt resource value: 0x7f020097
public const int ic_collapse_00002 = 2130837655;
public const int ic_close_dark = 2130837655;
// aapt resource value: 0x7f020098
public const int ic_collapse_00003 = 2130837656;
public const int ic_close_light = 2130837656;
// aapt resource value: 0x7f020099
public const int ic_collapse_00004 = 2130837657;
public const int ic_collapse = 2130837657;
// aapt resource value: 0x7f02009a
public const int ic_collapse_00005 = 2130837658;
public const int ic_collapse_00000 = 2130837658;
// aapt resource value: 0x7f02009b
public const int ic_collapse_00006 = 2130837659;
public const int ic_collapse_00001 = 2130837659;
// aapt resource value: 0x7f02009c
public const int ic_collapse_00007 = 2130837660;
public const int ic_collapse_00002 = 2130837660;
// aapt resource value: 0x7f02009d
public const int ic_collapse_00008 = 2130837661;
public const int ic_collapse_00003 = 2130837661;
// aapt resource value: 0x7f02009e
public const int ic_collapse_00009 = 2130837662;
public const int ic_collapse_00004 = 2130837662;
// aapt resource value: 0x7f02009f
public const int ic_collapse_00010 = 2130837663;
public const int ic_collapse_00005 = 2130837663;
// aapt resource value: 0x7f0200a0
public const int ic_collapse_00011 = 2130837664;
public const int ic_collapse_00006 = 2130837664;
// aapt resource value: 0x7f0200a1
public const int ic_collapse_00012 = 2130837665;
public const int ic_collapse_00007 = 2130837665;
// aapt resource value: 0x7f0200a2
public const int ic_collapse_00013 = 2130837666;
public const int ic_collapse_00008 = 2130837666;
// aapt resource value: 0x7f0200a3
public const int ic_collapse_00014 = 2130837667;
public const int ic_collapse_00009 = 2130837667;
// aapt resource value: 0x7f0200a4
public const int ic_collapse_00015 = 2130837668;
public const int ic_collapse_00010 = 2130837668;
// aapt resource value: 0x7f0200a5
public const int ic_errorstatus = 2130837669;
public const int ic_collapse_00011 = 2130837669;
// aapt resource value: 0x7f0200a6
public const int ic_expand = 2130837670;
public const int ic_collapse_00012 = 2130837670;
// aapt resource value: 0x7f0200a7
public const int ic_expand_00000 = 2130837671;
public const int ic_collapse_00013 = 2130837671;
// aapt resource value: 0x7f0200a8
public const int ic_expand_00001 = 2130837672;
public const int ic_collapse_00014 = 2130837672;
// aapt resource value: 0x7f0200a9
public const int ic_expand_00002 = 2130837673;
public const int ic_collapse_00015 = 2130837673;
// aapt resource value: 0x7f0200aa
public const int ic_expand_00003 = 2130837674;
public const int ic_errorstatus = 2130837674;
// aapt resource value: 0x7f0200ab
public const int ic_expand_00004 = 2130837675;
public const int ic_expand = 2130837675;
// aapt resource value: 0x7f0200ac
public const int ic_expand_00005 = 2130837676;
public const int ic_expand_00000 = 2130837676;
// aapt resource value: 0x7f0200ad
public const int ic_expand_00006 = 2130837677;
public const int ic_expand_00001 = 2130837677;
// aapt resource value: 0x7f0200ae
public const int ic_expand_00007 = 2130837678;
public const int ic_expand_00002 = 2130837678;
// aapt resource value: 0x7f0200af
public const int ic_expand_00008 = 2130837679;
public const int ic_expand_00003 = 2130837679;
// aapt resource value: 0x7f0200b0
public const int ic_expand_00009 = 2130837680;
public const int ic_expand_00004 = 2130837680;
// aapt resource value: 0x7f0200b1
public const int ic_expand_00010 = 2130837681;
public const int ic_expand_00005 = 2130837681;
// aapt resource value: 0x7f0200b2
public const int ic_expand_00011 = 2130837682;
public const int ic_expand_00006 = 2130837682;
// aapt resource value: 0x7f0200b3
public const int ic_expand_00012 = 2130837683;
public const int ic_expand_00007 = 2130837683;
// aapt resource value: 0x7f0200b4
public const int ic_expand_00013 = 2130837684;
public const int ic_expand_00008 = 2130837684;
// aapt resource value: 0x7f0200b5
public const int ic_expand_00014 = 2130837685;
public const int ic_expand_00009 = 2130837685;
// aapt resource value: 0x7f0200b6
public const int ic_expand_00015 = 2130837686;
public const int ic_expand_00010 = 2130837686;
// aapt resource value: 0x7f0200b7
public const int ic_media_pause = 2130837687;
public const int ic_expand_00011 = 2130837687;
// aapt resource value: 0x7f0200b8
public const int ic_media_play = 2130837688;
public const int ic_expand_00012 = 2130837688;
// aapt resource value: 0x7f0200b9
public const int ic_media_route_disabled_mono_dark = 2130837689;
public const int ic_expand_00013 = 2130837689;
// aapt resource value: 0x7f0200ba
public const int ic_media_route_off_mono_dark = 2130837690;
public const int ic_expand_00014 = 2130837690;
// aapt resource value: 0x7f0200bb
public const int ic_media_route_on_0_mono_dark = 2130837691;
public const int ic_expand_00015 = 2130837691;
// aapt resource value: 0x7f0200bc
public const int ic_media_route_on_1_mono_dark = 2130837692;
public const int ic_media_pause = 2130837692;
// aapt resource value: 0x7f0200bd
public const int ic_media_route_on_2_mono_dark = 2130837693;
public const int ic_media_play = 2130837693;
// aapt resource value: 0x7f0200be
public const int ic_media_route_on_mono_dark = 2130837694;
public const int ic_media_route_disabled_mono_dark = 2130837694;
// aapt resource value: 0x7f0200bf
public const int ic_pause_dark = 2130837695;
public const int ic_media_route_off_mono_dark = 2130837695;
// aapt resource value: 0x7f0200c0
public const int ic_pause_light = 2130837696;
public const int ic_media_route_on_0_mono_dark = 2130837696;
// aapt resource value: 0x7f0200c1
public const int ic_play_dark = 2130837697;
public const int ic_media_route_on_1_mono_dark = 2130837697;
// aapt resource value: 0x7f0200c2
public const int ic_play_light = 2130837698;
public const int ic_media_route_on_2_mono_dark = 2130837698;
// aapt resource value: 0x7f0200c3
public const int ic_speaker_dark = 2130837699;
public const int ic_media_route_on_mono_dark = 2130837699;
// aapt resource value: 0x7f0200c4
public const int ic_speaker_group_dark = 2130837700;
public const int ic_pause_dark = 2130837700;
// aapt resource value: 0x7f0200c5
public const int ic_speaker_group_light = 2130837701;
public const int ic_pause_light = 2130837701;
// aapt resource value: 0x7f0200c6
public const int ic_speaker_light = 2130837702;
public const int ic_play_dark = 2130837702;
// aapt resource value: 0x7f0200c7
public const int ic_successstatus = 2130837703;
public const int ic_play_light = 2130837703;
// aapt resource value: 0x7f0200c8
public const int ic_tv_dark = 2130837704;
public const int ic_speaker_dark = 2130837704;
// aapt resource value: 0x7f0200c9
public const int ic_tv_light = 2130837705;
public const int ic_speaker_group_dark = 2130837705;
// aapt resource value: 0x7f0200ca
public const int icon = 2130837706;
public const int ic_speaker_group_light = 2130837706;
// aapt resource value: 0x7f0200cb
public const int ion_chevron_right = 2130837707;
public const int ic_speaker_light = 2130837707;
// aapt resource value: 0x7f0200cc
public const int lightbulb = 2130837708;
public const int ic_successstatus = 2130837708;
// aapt resource value: 0x7f0200cd
public const int list_selector = 2130837709;
public const int ic_tv_dark = 2130837709;
// aapt resource value: 0x7f0200ce
public const int @lock = 2130837710;
public const int ic_tv_light = 2130837710;
// aapt resource value: 0x7f0200cf
public const int logo = 2130837711;
public const int icon = 2130837711;
// aapt resource value: 0x7f0200d0
public const int more = 2130837712;
public const int ion_chevron_right = 2130837712;
// aapt resource value: 0x7f0200d1
public const int mr_dialog_material_background_dark = 2130837713;
public const int lightbulb = 2130837713;
// aapt resource value: 0x7f0200d2
public const int mr_dialog_material_background_light = 2130837714;
public const int list_selector = 2130837714;
// aapt resource value: 0x7f0200d3
public const int mr_ic_audiotrack_light = 2130837715;
public const int @lock = 2130837715;
// aapt resource value: 0x7f0200d4
public const int mr_ic_cast_dark = 2130837716;
public const int logo = 2130837716;
// aapt resource value: 0x7f0200d5
public const int mr_ic_cast_light = 2130837717;
public const int more = 2130837717;
// aapt resource value: 0x7f0200d6
public const int mr_ic_close_dark = 2130837718;
public const int mr_dialog_material_background_dark = 2130837718;
// aapt resource value: 0x7f0200d7
public const int mr_ic_close_light = 2130837719;
public const int mr_dialog_material_background_light = 2130837719;
// aapt resource value: 0x7f0200d8
public const int mr_ic_media_route_connecting_mono_dark = 2130837720;
public const int mr_ic_audiotrack_light = 2130837720;
// aapt resource value: 0x7f0200d9
public const int mr_ic_media_route_connecting_mono_light = 2130837721;
public const int mr_ic_cast_dark = 2130837721;
// aapt resource value: 0x7f0200da
public const int mr_ic_media_route_mono_dark = 2130837722;
public const int mr_ic_cast_light = 2130837722;
// aapt resource value: 0x7f0200db
public const int mr_ic_media_route_mono_light = 2130837723;
public const int mr_ic_close_dark = 2130837723;
// aapt resource value: 0x7f0200dc
public const int mr_ic_pause_dark = 2130837724;
public const int mr_ic_close_light = 2130837724;
// aapt resource value: 0x7f0200dd
public const int mr_ic_pause_light = 2130837725;
public const int mr_ic_media_route_connecting_mono_dark = 2130837725;
// aapt resource value: 0x7f0200de
public const int mr_ic_play_dark = 2130837726;
public const int mr_ic_media_route_connecting_mono_light = 2130837726;
// aapt resource value: 0x7f0200df
public const int mr_ic_play_light = 2130837727;
// aapt resource value: 0x7f0200eb
public const int notification_template_icon_bg = 2130837739;
public const int mr_ic_media_route_mono_dark = 2130837727;
// aapt resource value: 0x7f0200e0
public const int plus = 2130837728;
public const int mr_ic_media_route_mono_light = 2130837728;
// aapt resource value: 0x7f0200e1
public const int refresh = 2130837729;
public const int mr_ic_pause_dark = 2130837729;
// aapt resource value: 0x7f0200e2
public const int roundedbg = 2130837730;
public const int mr_ic_pause_light = 2130837730;
// aapt resource value: 0x7f0200e3
public const int roundedbgdark = 2130837731;
public const int mr_ic_play_dark = 2130837731;
// aapt resource value: 0x7f0200e4
public const int splash_screen = 2130837732;
public const int mr_ic_play_light = 2130837732;
// aapt resource value: 0x7f0200e5
public const int star = 2130837733;
public const int notification_sm = 2130837733;
// aapt resource value: 0x7f0200f1
public const int notification_template_icon_bg = 2130837745;
// aapt resource value: 0x7f0200e6
public const int star_selected = 2130837734;
public const int plus = 2130837734;
// aapt resource value: 0x7f0200e7
public const int tools = 2130837735;
public const int refresh = 2130837735;
// aapt resource value: 0x7f0200e8
public const int tools_selected = 2130837736;
public const int roundedbg = 2130837736;
// aapt resource value: 0x7f0200e9
public const int upload = 2130837737;
public const int roundedbgdark = 2130837737;
// aapt resource value: 0x7f0200ea
public const int user = 2130837738;
public const int splash_screen = 2130837738;
// aapt resource value: 0x7f0200eb
public const int star = 2130837739;
// aapt resource value: 0x7f0200ec
public const int star_selected = 2130837740;
// aapt resource value: 0x7f0200ed
public const int tools = 2130837741;
// aapt resource value: 0x7f0200ee
public const int tools_selected = 2130837742;
// aapt resource value: 0x7f0200ef
public const int upload = 2130837743;
// aapt resource value: 0x7f0200f0
public const int user = 2130837744;
static Drawable()
{
@@ -3675,6 +3693,9 @@ namespace Bit.Android
// aapt resource value: 0x7f080047
public const int ApplicationName = 2131230791;
// aapt resource value: 0x7f08008f
public const int AutoFillServiceDescription = 2131230863;
// aapt resource value: 0x7f080046
public const int Hello = 2131230790;

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 347 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 531 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 404 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 586 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 773 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 564 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 312 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 364 B

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="AutoFillServiceDescription">
The allow bitwarden to auto-fill into other Android apps and on the web through your browser, enable the bitwarden
accessibility service by tapping the toggle switch above, then press OK on the confirmation pop-up. You can then press
the back button twice to return to the main bitwarden app.
</string>
</resources>

View File

@@ -1,7 +1,8 @@
<?xml version="1.0" encoding="utf-8" ?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFeedbackType="feedbackSpoken"
android:description="@string/AutoFillServiceDescription"
android:accessibilityEventTypes="typeWindowStateChanged|typeWindowContentChanged"
android:accessibilityFeedbackType="feedbackGeneric"
android:accessibilityFlags="flagDefault"
android:notificationTimeout="100"
android:canRetrieveWindowContent="true"/>

View File

@@ -10,5 +10,7 @@ namespace Bit.Android.Services
public string Build => AndroidApp.Context.ApplicationContext.PackageManager
.GetPackageInfo(AndroidApp.Context.PackageName, 0).VersionCode.ToString();
public bool AutofillServiceEnabled => AutofillService.Enabled;
}
}

View File

@@ -44,7 +44,7 @@ namespace Bit.Android.Services
public void TrackExtensionEvent(string eventName, string label = null)
{
throw new NotSupportedException();
TrackEvent("AutofillService", eventName, label);
}
public void TrackEvent(string category, string eventName, string label = null)

View File

@@ -0,0 +1,12 @@
using System;
using Xamarin.Android.Net;
using Bit.App;
using Bit.App.Abstractions;
namespace Bit.Android.Services
{
public class HttpService : IHttpService
{
public ApiHttpClient Client => new ApiHttpClient(new CustomAndroidClientHandler());
}
}

View File

@@ -52,22 +52,30 @@ namespace Bit.Android.Services
Console.WriteLine("Android Language:" + androidLanguage);
var netLanguage = androidLanguage;
// certain languages need to be converted to CultureInfo equivalent
switch(androidLanguage)
if(netLanguage.StartsWith("zh"))
{
case "ms-BN": // "Malaysian (Brunei)" not supported .NET culture
case "ms-MY": // "Malaysian (Malaysia)" not supported .NET culture
case "ms-SG": // "Malaysian (Singapore)" not supported .NET culture
netLanguage = "ms"; // closest supported
break;
case "in-ID": // "Indonesian (Indonesia)" has different code in .NET
netLanguage = "id-ID"; // correct code for .NET
break;
case "gsw-CH": // "Schwiizertüütsch (Swiss German)" not supported .NET culture
netLanguage = "de-CH"; // closest supported
break;
// add more application-specific cases here (if required)
// ONLY use cultures that have been tested and known to work
// simplified chinese used for all for now
netLanguage = "zh-Hans";
}
else
{
// certain languages need to be converted to CultureInfo equivalent
switch(androidLanguage)
{
case "ms-BN": // "Malaysian (Brunei)" not supported .NET culture
case "ms-MY": // "Malaysian (Malaysia)" not supported .NET culture
case "ms-SG": // "Malaysian (Singapore)" not supported .NET culture
netLanguage = "ms"; // closest supported
break;
case "in-ID": // "Indonesian (Indonesia)" has different code in .NET
netLanguage = "id-ID"; // correct code for .NET
break;
case "gsw-CH": // "Schwiizertüütsch (Swiss German)" not supported .NET culture
netLanguage = "de-CH"; // closest supported
break;
// add more application-specific cases here (if required)
// ONLY use cultures that have been tested and known to work
}
}
Console.WriteLine(".NET Language/Locale:" + netLanguage);

View File

@@ -0,0 +1,8 @@
$rootPath = $args[0];
$newVersionCode = $args[1];
$xml=New-Object XML;
$xml.Load($rootPath + "\src\Android\Properties\AndroidManifest.xml");
$node=$xml.SelectNodes("/manifest");
$node.SetAttribute("android:versionCode", $newVersionCode);
$xml.Save($rootPath + "\src\Android\Properties\AndroidManifest.xml");

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Acr.Support" version="2.1.0" targetFramework="monoandroid60" />
<package id="Acr.UserDialogs" version="6.3.2" targetFramework="monoandroid60" />
<package id="Acr.UserDialogs" version="6.3.3" targetFramework="monoandroid60" />
<package id="AndHUD" version="1.2.0" targetFramework="monoandroid60" />
<package id="BouncyCastle" version="1.8.1" targetFramework="monoandroid60" />
<package id="CommonServiceLocator" version="1.3" targetFramework="monoandroid60" />
@@ -19,10 +19,10 @@
<package id="SQLitePCL.bundle_green" version="0.9.3" targetFramework="monoandroid60" />
<package id="SQLitePCL.plugin.sqlite3.android" version="0.9.3" targetFramework="monoandroid60" />
<package id="SQLitePCL.raw" version="0.9.3" targetFramework="monoandroid60" />
<package id="SQLitePCLRaw.bundle_green" version="1.1.1" targetFramework="monoandroid60" />
<package id="SQLitePCLRaw.core" version="1.1.1" targetFramework="monoandroid60" />
<package id="SQLitePCLRaw.lib.e_sqlite3.android" version="1.1.1" targetFramework="monoandroid60" />
<package id="SQLitePCLRaw.provider.e_sqlite3.android" version="1.1.1" targetFramework="monoandroid60" />
<package id="SQLitePCLRaw.bundle_green" version="1.1.2" targetFramework="monoandroid60" />
<package id="SQLitePCLRaw.core" version="1.1.2" targetFramework="monoandroid60" />
<package id="SQLitePCLRaw.lib.e_sqlite3.android" version="1.1.2" targetFramework="monoandroid60" />
<package id="SQLitePCLRaw.provider.e_sqlite3.android" version="1.1.2" targetFramework="monoandroid60" />
<package id="Unity" version="3.5.1405-prerelease" targetFramework="monoandroid60" />
<package id="Validation" version="2.3.7" targetFramework="monoandroid60" />
<package id="Xam.Plugin.Connectivity" version="2.2.12" targetFramework="monoandroid60" />
@@ -36,7 +36,7 @@
<package id="Xamarin.Android.Support.v7.MediaRouter" version="23.3.0" targetFramework="monoandroid60" />
<package id="Xamarin.Android.Support.v7.RecyclerView" version="23.3.0" targetFramework="monoandroid60" />
<package id="Xamarin.Android.Support.Vector.Drawable" version="23.3.0" targetFramework="monoandroid60" />
<package id="Xamarin.Forms" version="2.3.3.175" targetFramework="monoandroid60" />
<package id="Xamarin.Forms" version="2.3.3.180" targetFramework="monoandroid60" />
<package id="Xamarin.GooglePlayServices.Analytics" version="29.0.0.2" targetFramework="monoandroid60" />
<package id="Xamarin.GooglePlayServices.Base" version="29.0.0.2" targetFramework="monoandroid60" />
<package id="Xamarin.GooglePlayServices.Basement" version="29.0.0.2" targetFramework="monoandroid60" />

View File

@@ -1,5 +1,6 @@
using System.Threading.Tasks;
using Bit.App.Models.Api;
using System;
namespace Bit.App.Abstractions
{
@@ -7,5 +8,6 @@ namespace Bit.App.Abstractions
{
Task<ApiResult> PostRegisterAsync(RegisterRequest requestObj);
Task<ApiResult> PostPasswordHintAsync(PasswordHintRequest requestObj);
Task<ApiResult<DateTime?>> GetAccountRevisionDate();
}
}

View File

@@ -3,9 +3,8 @@ using Bit.App.Models.Api;
namespace Bit.App.Abstractions
{
public interface IAuthApiRepository
public interface IConnectApiRepository
{
Task<ApiResult<TokenResponse>> PostTokenAsync(TokenRequest requestObj);
Task<ApiResult<TokenResponse>> PostTokenTwoFactorAsync(TokenTwoFactorRequest requestObj);
}
}

View File

@@ -8,6 +8,6 @@ namespace Bit.App.Abstractions
public interface IFolderRepository : IRepository<FolderData, string>
{
Task<IEnumerable<FolderData>> GetAllByUserIdAsync(string userId);
Task DeleteWithSiteUpdateAsync(string id, DateTime revisionDate);
Task DeleteWithLoginUpdateAsync(string id, DateTime revisionDate);
}
}

View File

@@ -5,7 +5,7 @@ using Bit.App.Models.Api;
namespace Bit.App.Abstractions
{
public interface ISiteApiRepository : IApiRepository<SiteRequest, SiteResponse, string>
public interface ILoginApiRepository : IApiRepository<LoginRequest, LoginResponse, string>
{
}
}

View File

@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Bit.App.Models.Data;
namespace Bit.App.Abstractions
{
public interface ILoginRepository : IRepository<LoginData, string>
{
Task<IEnumerable<LoginData>> GetAllByUserIdAsync(string userId);
Task<IEnumerable<LoginData>> GetAllByUserIdAsync(string userId, bool favorite);
}
}

View File

@@ -12,6 +12,7 @@ namespace Bit.App.Abstractions
Task<IEnumerable<T>> GetAllAsync();
Task UpdateAsync(T obj);
Task InsertAsync(T obj);
Task UpsertAsync(T obj);
Task DeleteAsync(TId id);
Task DeleteAsync(T obj);
}

View File

@@ -0,0 +1,10 @@
using System.Threading.Tasks;
using Bit.App.Models.Api;
namespace Bit.App.Abstractions
{
public interface ISettingsApiRepository
{
Task<ApiResult<DomainsResponse>> GetDomains(bool excluded = false);
}
}

View File

@@ -0,0 +1,8 @@
using Bit.App.Models.Data;
namespace Bit.App.Abstractions
{
public interface ISettingsRepository : IRepository<SettingsData, string>
{
}
}

View File

@@ -1,13 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Bit.App.Models.Data;
namespace Bit.App.Abstractions
{
public interface ISiteRepository : IRepository<SiteData, string>
{
Task<IEnumerable<SiteData>> GetAllByUserIdAsync(string userId);
Task<IEnumerable<SiteData>> GetAllByUserIdAsync(string userId, bool favorite);
}
}

View File

@@ -4,5 +4,6 @@
{
string Build { get; }
string Version { get; }
bool AutofillServiceEnabled { get; }
}
}

View File

@@ -1,13 +1,12 @@
using System.Threading.Tasks;
using Bit.App.Models.Api;
using System;
namespace Bit.App.Abstractions
{
public interface IAuthService
{
bool IsAuthenticated { get; }
bool IsAuthenticatedTwoFactor { get; }
string Token { get; set; }
string UserId { get; set; }
string PreviousUserId { get; }
bool UserIdChanged { get; }
@@ -16,6 +15,5 @@ namespace Bit.App.Abstractions
void LogOut();
Task<ApiResult<TokenResponse>> TokenPostAsync(TokenRequest request);
Task<ApiResult<TokenResponse>> TokenTwoFactorPostAsync(TokenTwoFactorRequest request);
}
}

View File

@@ -0,0 +1,7 @@
namespace Bit.App.Abstractions
{
public interface IHttpService
{
ApiHttpClient Client { get; }
}
}

View File

@@ -0,0 +1,17 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Bit.App.Models;
using Bit.App.Models.Api;
namespace Bit.App.Abstractions
{
public interface ILoginService
{
Task<Login> GetByIdAsync(string id);
Task<IEnumerable<Login>> GetAllAsync();
Task<IEnumerable<Login>> GetAllAsync(bool favorites);
Task<IEnumerable<Login>> GetAllAsync(string uriString);
Task<ApiResult<LoginResponse>> SaveAsync(Login login);
Task<ApiResult> DeleteAsync(string id);
}
}

View File

@@ -0,0 +1,10 @@
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Bit.App.Abstractions
{
public interface ISettingsService
{
Task<IEnumerable<IEnumerable<string>>> GetEquivalentDomainsAsync();
}
}

View File

@@ -1,16 +0,0 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Bit.App.Models;
using Bit.App.Models.Api;
namespace Bit.App.Abstractions
{
public interface ISiteService
{
Task<Site> GetByIdAsync(string id);
Task<IEnumerable<Site>> GetAllAsync();
Task<IEnumerable<Site>> GetAllAsync(bool favorites);
Task<ApiResult<SiteResponse>> SaveAsync(Site site);
Task<ApiResult> DeleteAsync(string id);
}
}

View File

@@ -8,9 +8,8 @@ namespace Bit.App.Abstractions
bool SyncInProgress { get; }
Task<bool> SyncAsync(string id);
Task<bool> SyncDeleteFolderAsync(string id, DateTime revisionDate);
Task<bool> SyncDeleteSiteAsync(string id);
Task<bool> FullSyncAsync();
Task<bool> IncrementalSyncAsync(TimeSpan syncThreshold);
Task<bool> IncrementalSyncAsync();
Task<bool> SyncDeleteLoginAsync(string id);
Task<bool> FullSyncAsync(bool forceSync = false);
Task<bool> FullSyncAsync(TimeSpan syncThreshold, bool forceSync = false);
}
}

View File

@@ -0,0 +1,19 @@
using System;
namespace Bit.App.Abstractions
{
public interface ITokenService
{
string Token { get; set; }
string RefreshToken { get; set; }
[Obsolete("Old auth scheme")]
string AuthBearer { get; set; }
DateTime TokenExpiration { get; }
bool TokenExpired { get; }
TimeSpan TokenTimeRemaining { get; }
bool TokenNeedsRefresh { get; }
string TokenUserId { get; }
string TokenEmail { get; }
string TokenName { get; }
}
}

View File

@@ -14,11 +14,15 @@ using Acr.UserDialogs;
using XLabs.Ioc;
using System.Reflection;
using Bit.App.Resources;
using System.Threading;
namespace Bit.App
{
public class App : Application
{
private const string LastBuildKey = "LastBuild";
private string _uri;
private readonly IDatabaseService _databaseService;
private readonly IConnectivity _connectivity;
private readonly IUserDialogs _userDialogs;
@@ -29,8 +33,10 @@ namespace Bit.App
private readonly ILockService _lockService;
private readonly IGoogleAnalyticsService _googleAnalyticsService;
private readonly ILocalizeService _localizeService;
private readonly IAppInfoService _appInfoService;
public App(
string uri,
IAuthService authService,
IConnectivity connectivity,
IUserDialogs userDialogs,
@@ -40,8 +46,10 @@ namespace Bit.App
ISettings settings,
ILockService lockService,
IGoogleAnalyticsService googleAnalyticsService,
ILocalizeService localizeService)
ILocalizeService localizeService,
IAppInfoService appInfoService)
{
_uri = uri;
_databaseService = databaseService;
_connectivity = connectivity;
_userDialogs = userDialogs;
@@ -52,11 +60,16 @@ namespace Bit.App
_lockService = lockService;
_googleAnalyticsService = googleAnalyticsService;
_localizeService = localizeService;
_appInfoService = appInfoService;
SetCulture();
SetStyles();
if(authService.IsAuthenticated)
if(authService.IsAuthenticated && _uri != null)
{
MainPage = new ExtendedNavigationPage(new VaultAutofillListLoginsPage(_uri));
}
else if(authService.IsAuthenticated)
{
MainPage = new MainPage();
}
@@ -68,7 +81,7 @@ namespace Bit.App
MessagingCenter.Subscribe<Application, bool>(Current, "Resumed", async (sender, args) =>
{
await CheckLockAsync(args);
await Task.Run(() => IncrementalSyncAsync()).ConfigureAwait(false);
await Task.Run(() => FullSyncAsync()).ConfigureAwait(false);
});
MessagingCenter.Subscribe<Application, bool>(Current, "Lock", (sender, args) =>
@@ -80,14 +93,29 @@ namespace Bit.App
{
Device.BeginInvokeOnMainThread(() => Logout(args));
});
MessagingCenter.Subscribe<Application>(Current, "SetMainPage", (sender) =>
{
SetMainPageFromAutofill();
});
}
protected async override void OnStart()
{
// Handle when your app starts
await CheckLockAsync(false);
_databaseService.CreateTables();
await Task.Run(() => FullSyncAsync()).ConfigureAwait(false);
if(string.IsNullOrWhiteSpace(_uri))
{
var lastBuild = _settings.GetValueOrDefault<string>(LastBuildKey);
if(InDebugMode() || lastBuild == null || lastBuild != _appInfoService.Build)
{
_settings.AddOrUpdateValue(LastBuildKey, _appInfoService.Build);
_databaseService.CreateTables();
}
await Task.Run(() => FullSyncAsync()).ConfigureAwait(false);
}
Debug.WriteLine("OnStart");
}
@@ -97,6 +125,7 @@ namespace Bit.App
// Handle when your app sleeps
Debug.WriteLine("OnSleep");
SetMainPageFromAutofill();
if(Device.OS == TargetPlatform.Android && !TopPageIsLock())
{
_settings.AddOrUpdateValue(Constants.LastActivityDate, DateTime.UtcNow);
@@ -124,44 +153,31 @@ namespace Bit.App
{
lockPinPage.PinControl.Entry.FocusWithDelay();
}
if(Device.OS == TargetPlatform.Android)
{
await Task.Run(() => FullSyncAsync()).ConfigureAwait(false);
}
}
private async Task IncrementalSyncAsync()
private bool InDebugMode()
{
if(_connectivity.IsConnected)
#if DEBUG
return true;
#else
return false;
#endif
}
private void SetMainPageFromAutofill()
{
if(Device.OS != TargetPlatform.Android || string.IsNullOrWhiteSpace(_uri))
{
var attempt = 0;
do
{
try
{
await _syncService.IncrementalSyncAsync(TimeSpan.FromMinutes(30)).ConfigureAwait(false);
break;
}
catch(WebException)
{
Debug.WriteLine("Failed to incremental sync.");
if(attempt >= 1)
{
break;
}
else
{
await Task.Delay(1000);
}
attempt++;
}
catch(Exception e) when(e is TaskCanceledException || e is OperationCanceledException)
{
Debug.WriteLine("Cancellation exception.");
break;
}
} while(attempt <= 1);
}
else
{
Debug.WriteLine("Not connected.");
return;
}
MainPage = new MainPage();
_uri = null;
}
private async Task FullSyncAsync()
@@ -173,7 +189,7 @@ namespace Bit.App
{
try
{
await _syncService.FullSyncAsync().ConfigureAwait(false);
await _syncService.FullSyncAsync(TimeSpan.FromMinutes(30)).ConfigureAwait(false);
break;
}
catch(WebException)

View File

@@ -35,8 +35,13 @@
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Compile Include="Abstractions\Repositories\ISettingsApiRepository.cs" />
<Compile Include="Abstractions\Repositories\IAccountsApiRepository.cs" />
<Compile Include="Abstractions\Repositories\IDeviceApiRepository.cs" />
<Compile Include="Abstractions\Repositories\ISettingsRepository.cs" />
<Compile Include="Abstractions\Services\ISettingsService.cs" />
<Compile Include="Abstractions\Services\ITokenService.cs" />
<Compile Include="Abstractions\Services\IHttpService.cs" />
<Compile Include="Abstractions\Services\IDeviceInfoService.cs" />
<Compile Include="Abstractions\Services\IGoogleAnalyticsService.cs" />
<Compile Include="Abstractions\Services\IAppInfoService.cs" />
@@ -46,7 +51,7 @@
<Compile Include="Abstractions\Services\IKeyDerivationService.cs" />
<Compile Include="Abstractions\Services\ILogService.cs" />
<Compile Include="Abstractions\Services\IReflectionService.cs" />
<Compile Include="Abstractions\Services\ISiteService.cs" />
<Compile Include="Abstractions\Services\ILoginService.cs" />
<Compile Include="Abstractions\Services\IFolderService.cs" />
<Compile Include="App.cs" />
<Compile Include="Abstractions\Services\ISecureStorageService.cs" />
@@ -71,6 +76,7 @@
<Compile Include="Controls\FormPickerCell.cs" />
<Compile Include="Controls\FormEntryCell.cs" />
<Compile Include="Controls\PinControl.cs" />
<Compile Include="Controls\VaultListViewCell.cs" />
<Compile Include="Enums\LockType.cs" />
<Compile Include="Enums\CipherType.cs" />
<Compile Include="Enums\PushType.cs" />
@@ -83,25 +89,26 @@
<Compile Include="Models\Api\Request\FolderRequest.cs" />
<Compile Include="Models\Api\Request\DeviceRequest.cs" />
<Compile Include="Models\Api\Request\RegisterRequest.cs" />
<Compile Include="Models\Api\Request\SiteRequest.cs" />
<Compile Include="Models\Api\Request\LoginRequest.cs" />
<Compile Include="Models\Api\Request\PasswordHintRequest.cs" />
<Compile Include="Models\Api\Request\TokenRequest.cs" />
<Compile Include="Models\Api\Request\TokenTwoFactorRequest.cs" />
<Compile Include="Models\Api\Response\CipherHistoryResponse.cs" />
<Compile Include="Models\Api\Response\CipherResponse.cs" />
<Compile Include="Models\Api\Response\DomainsResponse.cs" />
<Compile Include="Models\Api\Response\ErrorResponse.cs" />
<Compile Include="Models\Api\Response\FolderResponse.cs" />
<Compile Include="Models\Api\Response\ListResponse.cs" />
<Compile Include="Models\Api\Response\DeviceResponse.cs" />
<Compile Include="Models\Api\Response\SiteResponse.cs" />
<Compile Include="Models\Api\Response\LoginResponse.cs" />
<Compile Include="Models\Api\Response\TokenResponse.cs" />
<Compile Include="Models\Api\Response\ProfileResponse.cs" />
<Compile Include="Models\Api\SiteDataModel.cs" />
<Compile Include="Models\Api\LoginDataModel.cs" />
<Compile Include="Models\Cipher.cs" />
<Compile Include="Models\CipherString.cs" />
<Compile Include="Models\Data\SettingsData.cs" />
<Compile Include="Models\Data\FolderData.cs" />
<Compile Include="Abstractions\IDataObject.cs" />
<Compile Include="Models\Data\SiteData.cs" />
<Compile Include="Models\Data\LoginData.cs" />
<Compile Include="Models\DomainName.cs" />
<Compile Include="Models\Folder.cs" />
<Compile Include="Models\Page\AppExtensionPageModel.cs" />
@@ -110,9 +117,10 @@
<Compile Include="Models\Page\PasswordGeneratorPageModel.cs" />
<Compile Include="Models\PlatformCulture.cs" />
<Compile Include="Models\PushNotification.cs" />
<Compile Include="Models\Site.cs" />
<Compile Include="Models\Page\VaultViewSitePageModel.cs" />
<Compile Include="Models\Login.cs" />
<Compile Include="Models\Page\VaultViewLoginPageModel.cs" />
<Compile Include="Pages\HomePage.cs" />
<Compile Include="Pages\Lock\BaseLockPage.cs" />
<Compile Include="Pages\Lock\LockPasswordPage.cs" />
<Compile Include="Pages\LoginTwoFactorPage.cs" />
<Compile Include="Pages\PasswordHintPage.cs" />
@@ -133,22 +141,25 @@
<Compile Include="Pages\Settings\SettingsSyncPage.cs" />
<Compile Include="Pages\Settings\SettingsPage.cs" />
<Compile Include="Pages\Settings\SettingsListFoldersPage.cs" />
<Compile Include="Pages\Vault\VaultAutofillListLoginsPage.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Abstractions\Repositories\ISiteRepository.cs" />
<Compile Include="Abstractions\Repositories\ILoginRepository.cs" />
<Compile Include="Repositories\SettingsApiRepository.cs" />
<Compile Include="Repositories\ApiRepository.cs" />
<Compile Include="Repositories\AccountsApiRepository.cs" />
<Compile Include="Repositories\ConnectApiRepository.cs" />
<Compile Include="Repositories\BaseApiRepository.cs" />
<Compile Include="Abstractions\Repositories\IApiRepository.cs" />
<Compile Include="Abstractions\Repositories\IFolderApiRepository.cs" />
<Compile Include="Abstractions\Repositories\ISiteApiRepository.cs" />
<Compile Include="Repositories\AuthApiRepository.cs" />
<Compile Include="Abstractions\Repositories\IAuthApiRepository.cs" />
<Compile Include="Abstractions\Repositories\ILoginApiRepository.cs" />
<Compile Include="Abstractions\Repositories\IConnectApiRepository.cs" />
<Compile Include="Repositories\DeviceApiRepository.cs" />
<Compile Include="Repositories\CipherApiRepository.cs" />
<Compile Include="Abstractions\Repositories\ICipherApiRepository.cs" />
<Compile Include="Repositories\SiteApiRepository.cs" />
<Compile Include="Repositories\SettingsRepository.cs" />
<Compile Include="Repositories\LoginApiRepository.cs" />
<Compile Include="Repositories\FolderApiRepository.cs" />
<Compile Include="Repositories\SiteRepository.cs" />
<Compile Include="Repositories\LoginRepository.cs" />
<Compile Include="Repositories\FolderRepository.cs" />
<Compile Include="Abstractions\Repositories\IFolderRepository.cs" />
<Compile Include="Abstractions\Repositories\IRepository.cs" />
@@ -162,11 +173,28 @@
<DesignTime>True</DesignTime>
<DependentUpon>AppResources.es.resx</DependentUpon>
</Compile>
<Compile Include="Resources\AppResources.zh.Designer.cs">
<Compile Include="Resources\AppResources.fi.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>AppResources.zh.resx</DependentUpon>
<DependentUpon>AppResources.fi.resx</DependentUpon>
</Compile>
<Compile Include="Resources\AppResources.fr.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>AppResources.fr.resx</DependentUpon>
</Compile>
<Compile Include="Resources\AppResources.sv.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>AppResources.sv.resx</DependentUpon>
</Compile>
<Compile Include="Resources\AppResources.zh-Hans.Designer.cs">
<AutoGen>True</AutoGen>
<DesignTime>True</DesignTime>
<DependentUpon>AppResources.zh-Hans.resx</DependentUpon>
</Compile>
<Compile Include="Services\SettingsService.cs" />
<Compile Include="Services\TokenService.cs" />
<Compile Include="Services\AppIdService.cs" />
<Compile Include="Abstractions\Services\ILockService.cs" />
<Compile Include="Services\LockService.cs" />
@@ -178,16 +206,16 @@
<Compile Include="Abstractions\Services\ISyncService.cs" />
<Compile Include="Abstractions\Services\IPasswordGenerationService.cs" />
<Compile Include="Services\SyncService.cs" />
<Compile Include="Services\SiteService.cs" />
<Compile Include="Services\LoginService.cs" />
<Compile Include="Services\AuthService.cs" />
<Compile Include="Services\CryptoService.cs" />
<Compile Include="Models\Page\VaultListPageModel.cs" />
<Compile Include="Pages\LoginPage.cs" />
<Compile Include="Pages\Settings\SettingsAddFolderPage.cs" />
<Compile Include="Pages\Vault\VaultAddSitePage.cs" />
<Compile Include="Pages\Vault\VaultViewSitePage.cs" />
<Compile Include="Pages\Vault\VaultEditSitePage.cs" />
<Compile Include="Pages\Vault\VaultListSitesPage.cs" />
<Compile Include="Pages\Vault\VaultAddLoginPage.cs" />
<Compile Include="Pages\Vault\VaultViewLoginPage.cs" />
<Compile Include="Pages\Vault\VaultEditLoginPage.cs" />
<Compile Include="Pages\Vault\VaultListLoginsPage.cs" />
<Compile Include="Services\PasswordGenerationService.cs" />
<Compile Include="Utilities\Extentions.cs" />
<Compile Include="Utilities\ExtendedObservableCollection.cs" />
@@ -203,19 +231,31 @@
<Generator>PublicResXFileCodeGenerator</Generator>
<LastGenOutput>AppResources.es.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Include="Resources\AppResources.zh.resx">
<EmbeddedResource Include="Resources\AppResources.fi.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>AppResources.zh.Designer.cs</LastGenOutput>
<LastGenOutput>AppResources.fi.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Include="Resources\AppResources.fr.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>AppResources.fr.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Include="Resources\AppResources.sv.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>AppResources.sv.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Include="Resources\AppResources.zh-Hans.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>AppResources.zh-Hans.Designer.cs</LastGenOutput>
</EmbeddedResource>
<EmbeddedResource Include="Resources\public_suffix_list.dat" />
</ItemGroup>
<ItemGroup>
<Reference Include="Acr.UserDialogs, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Acr.UserDialogs.6.3.2\lib\portable-win+net45+wp8+win8+wpa81\Acr.UserDialogs.dll</HintPath>
<HintPath>..\..\packages\Acr.UserDialogs.6.3.3\lib\portable-win+net45+wp8+win8+wpa81\Acr.UserDialogs.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Acr.UserDialogs.Interface, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Acr.UserDialogs.6.3.2\lib\portable-win+net45+wp8+win8+wpa81\Acr.UserDialogs.Interface.dll</HintPath>
<HintPath>..\..\packages\Acr.UserDialogs.6.3.3\lib\portable-win+net45+wp8+win8+wpa81\Acr.UserDialogs.Interface.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="HockeySDK, Version=1.0.6103.22141, Culture=neutral, processorArchitecture=MSIL">
@@ -306,15 +346,15 @@
<Private>True</Private>
</Reference>
<Reference Include="SQLitePCLRaw.batteries_green, Version=1.0.0.0, Culture=neutral, PublicKeyToken=a84b7dcfb1391f7f, processorArchitecture=MSIL">
<HintPath>..\..\packages\SQLitePCLRaw.bundle_green.1.1.1\lib\portable-net45+netcore45+wpa81+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLitePCLRaw.batteries_green.dll</HintPath>
<HintPath>..\..\packages\SQLitePCLRaw.bundle_green.1.1.2\lib\portable-net45+netcore45+wpa81+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLitePCLRaw.batteries_green.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="SQLitePCLRaw.batteries_v2, Version=1.0.0.0, Culture=neutral, PublicKeyToken=8226ea5df37bcae9, processorArchitecture=MSIL">
<HintPath>..\..\packages\SQLitePCLRaw.bundle_green.1.1.1\lib\portable-net45+netcore45+wpa81+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLitePCLRaw.batteries_v2.dll</HintPath>
<HintPath>..\..\packages\SQLitePCLRaw.bundle_green.1.1.2\lib\portable-net45+netcore45+wpa81+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLitePCLRaw.batteries_v2.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="SQLitePCLRaw.core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=1488e028ca7ab535, processorArchitecture=MSIL">
<HintPath>..\..\packages\SQLitePCLRaw.core.1.1.1\lib\portable-net45+netcore45+wpa81+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLitePCLRaw.core.dll</HintPath>
<HintPath>..\..\packages\SQLitePCLRaw.core.1.1.2\lib\portable-net45+netcore45+wpa81+MonoAndroid10+MonoTouch10+Xamarin.iOS10\SQLitePCLRaw.core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Validation, Version=2.3.0.0, Culture=neutral, PublicKeyToken=2fc06f0d701809a7, processorArchitecture=MSIL">
@@ -322,15 +362,15 @@
<Private>True</Private>
</Reference>
<Reference Include="Xamarin.Forms.Core, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xamarin.Forms.2.3.3.175\lib\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.Core.dll</HintPath>
<HintPath>..\..\packages\Xamarin.Forms.2.3.3.180\lib\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.Core.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Xamarin.Forms.Platform, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xamarin.Forms.2.3.3.175\lib\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.Platform.dll</HintPath>
<HintPath>..\..\packages\Xamarin.Forms.2.3.3.180\lib\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.Platform.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Xamarin.Forms.Xaml, Version=2.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>..\..\packages\Xamarin.Forms.2.3.3.175\lib\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.Xaml.dll</HintPath>
<HintPath>..\..\packages\Xamarin.Forms.2.3.3.180\lib\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.Xaml.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="XLabs.Ioc, Version=2.0.5782.12218, Culture=neutral, processorArchitecture=MSIL">
@@ -348,12 +388,12 @@
<Compile Include="Services\PushNotificationListener.cs" />
</ItemGroup>
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\Portable\$(TargetFrameworkVersion)\Microsoft.Portable.CSharp.targets" />
<Import Project="..\..\packages\Xamarin.Forms.2.3.3.175\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets" Condition="Exists('..\..\packages\Xamarin.Forms.2.3.3.175\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets')" />
<Import Project="..\..\packages\Xamarin.Forms.2.3.3.180\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets" Condition="Exists('..\..\packages\Xamarin.Forms.2.3.3.180\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets')" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('..\..\packages\Xamarin.Forms.2.3.3.175\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Xamarin.Forms.2.3.3.175\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets'))" />
<Error Condition="!Exists('..\..\packages\Xamarin.Forms.2.3.3.180\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\..\packages\Xamarin.Forms.2.3.3.180\build\portable-win+net45+wp80+win81+wpa81+MonoAndroid10+Xamarin.iOS10+xamarinmac20\Xamarin.Forms.targets'))" />
</Target>
<!-- 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.

View File

@@ -2,6 +2,8 @@
{
public static class Constants
{
public const string AndroidAppProtocol = "androidapp://";
public const string SettingFingerprintUnlockOn = "setting:fingerprintUnlockOn";
public const string SettingPinUnlockOn = "setting:pinUnlockOn";
public const string SettingLockSeconds = "setting:lockSeconds";

View File

@@ -0,0 +1,40 @@
using Bit.App.Models.Page;
using System;
using Xamarin.Forms;
namespace Bit.App.Controls
{
public class VaultListViewCell : LabeledDetailCell
{
private Action<VaultListPageModel.Login> _moreClickedAction;
public static readonly BindableProperty LoginParameterProperty = BindableProperty.Create(nameof(LoginParameter),
typeof(VaultListPageModel.Login), typeof(VaultListViewCell), null);
public VaultListViewCell(Action<VaultListPageModel.Login> moreClickedAction)
{
_moreClickedAction = moreClickedAction;
SetBinding(LoginParameterProperty, new Binding("."));
Label.SetBinding<VaultListPageModel.Login>(Label.TextProperty, s => s.Name);
Detail.SetBinding<VaultListPageModel.Login>(Label.TextProperty, s => s.Username);
Button.Image = "more";
Button.Command = new Command(() => ShowMore());
Button.BackgroundColor = Color.Transparent;
BackgroundColor = Color.White;
}
public VaultListPageModel.Login LoginParameter
{
get { return GetValue(LoginParameterProperty) as VaultListPageModel.Login; }
set { SetValue(LoginParameterProperty, value); }
}
private void ShowMore()
{
_moreClickedAction?.Invoke(LoginParameter);
}
}
}

View File

@@ -3,6 +3,6 @@
public enum CipherType : short
{
Folder = 0,
Site = 1
Login = 1
}
}

View File

@@ -4,7 +4,7 @@
{
SyncCipherUpdate = 0,
SyncCipherCreate = 1,
SyncSiteDelete = 2,
SyncLoginDelete = 2,
SyncFolderDelete = 3,
SyncCiphers = 4
}

View File

@@ -1,6 +1,6 @@
namespace Bit.App.Models.Api
{
public class SiteDataModel
public class LoginDataModel
{
public string Name { get; set; }
public string Uri { get; set; }

View File

@@ -0,0 +1,24 @@
namespace Bit.App.Models.Api
{
public class LoginRequest
{
public LoginRequest(Login login)
{
FolderId = login.FolderId;
Name = login.Name?.EncryptedString;
Uri = login.Uri?.EncryptedString;
Username = login.Username?.EncryptedString;
Password = login.Password?.EncryptedString;
Notes = login.Notes?.EncryptedString;
Favorite = login.Favorite;
}
public string FolderId { get; set; }
public string Name { get; set; }
public string Uri { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string Notes { get; set; }
public bool Favorite { get; set; }
}
}

View File

@@ -1,24 +0,0 @@
namespace Bit.App.Models.Api
{
public class SiteRequest
{
public SiteRequest(Site site)
{
FolderId = site.FolderId;
Name = site.Name?.EncryptedString;
Uri = site.Uri?.EncryptedString;
Username = site.Username?.EncryptedString;
Password = site.Password?.EncryptedString;
Notes = site.Notes?.EncryptedString;
Favorite = site.Favorite;
}
public string FolderId { get; set; }
public string Name { get; set; }
public string Uri { get; set; }
public string Username { get; set; }
public string Password { get; set; }
public string Notes { get; set; }
public bool Favorite { get; set; }
}
}

View File

@@ -1,9 +1,49 @@
namespace Bit.App.Models.Api
using System;
using System.Collections.Generic;
namespace Bit.App.Models.Api
{
public class TokenRequest
{
public string Email { get; set; }
public string MasterPasswordHash { get; set; }
public string Token { get; set; }
public int? Provider { get; set; }
[Obsolete]
public string OldAuthBearer { get; set; }
public DeviceRequest Device { get; set; }
public IDictionary<string, string> ToIdentityTokenRequest()
{
var dict = new Dictionary<string, string>
{
{ "grant_type", "password" },
{ "username", Email },
{ "password", MasterPasswordHash },
{ "scope", "api offline_access" },
{ "client_id", "mobile" }
};
if(OldAuthBearer != null)
{
dict.Add("OldAuthBearer", OldAuthBearer);
}
if(Device != null)
{
dict.Add("DeviceType", Device.Type.ToString());
dict.Add("DeviceIdentifier", Device.Identifier);
dict.Add("DeviceName", Device.Name);
dict.Add("DevicePushToken", Device.PushToken);
}
if(Token != null && Provider.HasValue)
{
dict.Add("TwoFactorToken", Token);
dict.Add("TwoFactorProvider", Provider.Value.ToString());
}
return dict;
}
}
}

View File

@@ -1,9 +0,0 @@
namespace Bit.App.Models.Api
{
public class TokenTwoFactorRequest
{
public string Code { get; set; }
public string Provider { get; set; }
public DeviceRequest Device { get; set; }
}
}

View File

@@ -0,0 +1,17 @@
using System.Collections.Generic;
namespace Bit.App.Models.Api
{
public class DomainsResponse
{
public IEnumerable<IEnumerable<string>> EquivalentDomains { get; set; }
public IEnumerable<GlobalDomains> GlobalEquivalentDomains { get; set; }
public class GlobalDomains
{
public byte Type { get; set; }
public IEnumerable<string> Domains { get; set; }
public bool Excluded { get; set; }
}
}
}

View File

@@ -2,7 +2,7 @@
namespace Bit.App.Models.Api
{
public class SiteResponse
public class LoginResponse
{
public string Id { get; set; }
public string FolderId { get; set; }

View File

@@ -1,8 +1,18 @@
namespace Bit.App.Models.Api
using Newtonsoft.Json;
using System.Collections.Generic;
namespace Bit.App.Models.Api
{
public class TokenResponse
{
public string Token { get; set; }
public ProfileResponse Profile { get; set; }
[JsonProperty("access_token")]
public string AccessToken { get; set; }
[JsonProperty("expires_in")]
public long ExpiresIn { get; set; }
[JsonProperty("refresh_token")]
public string RefreshToken { get; set; }
[JsonProperty("token_type")]
public string TokenType { get; set; }
public List<int> TwoFactorProviders { get; set; }
}
}

View File

@@ -33,7 +33,7 @@ namespace Bit.App.Models.Data
throw new ArgumentException(nameof(cipher.Type));
}
var data = cipher.Data.ToObject<SiteDataModel>();
var data = cipher.Data.ToObject<LoginDataModel>();
Id = cipher.Id;
UserId = userId;

View File

@@ -6,46 +6,46 @@ using Bit.App.Models.Api;
namespace Bit.App.Models.Data
{
[Table("Site")]
public class SiteData : IDataObject<string>
public class LoginData : IDataObject<string>
{
public SiteData()
public LoginData()
{ }
public SiteData(Site site, string userId)
public LoginData(Login login, string userId)
{
Id = site.Id;
FolderId = site.FolderId;
Id = login.Id;
FolderId = login.FolderId;
UserId = userId;
Name = site.Name?.EncryptedString;
Uri = site.Uri?.EncryptedString;
Username = site.Username?.EncryptedString;
Password = site.Password?.EncryptedString;
Notes = site.Notes?.EncryptedString;
Favorite = site.Favorite;
Name = login.Name?.EncryptedString;
Uri = login.Uri?.EncryptedString;
Username = login.Username?.EncryptedString;
Password = login.Password?.EncryptedString;
Notes = login.Notes?.EncryptedString;
Favorite = login.Favorite;
}
public SiteData(SiteResponse site, string userId)
public LoginData(LoginResponse login, string userId)
{
Id = site.Id;
FolderId = site.FolderId;
Id = login.Id;
FolderId = login.FolderId;
UserId = userId;
Name = site.Name;
Uri = site.Uri;
Username = site.Username;
Password = site.Password;
Notes = site.Notes;
Favorite = site.Favorite;
RevisionDateTime = site.RevisionDate;
Name = login.Name;
Uri = login.Uri;
Username = login.Username;
Password = login.Password;
Notes = login.Notes;
Favorite = login.Favorite;
RevisionDateTime = login.RevisionDate;
}
public SiteData(CipherResponse cipher, string userId)
public LoginData(CipherResponse cipher, string userId)
{
if(cipher.Type != Enums.CipherType.Site)
if(cipher.Type != Enums.CipherType.Login)
{
throw new ArgumentException(nameof(cipher.Type));
}
var data = cipher.Data.ToObject<SiteDataModel>();
var data = cipher.Data.ToObject<LoginDataModel>();
Id = cipher.Id;
FolderId = cipher.FolderId;
@@ -72,9 +72,9 @@ namespace Bit.App.Models.Data
public bool Favorite { get; set; }
public DateTime RevisionDateTime { get; set; } = DateTime.UtcNow;
public Site ToSite()
public Login ToLogin()
{
return new Site(this);
return new Login(this);
}
}
}

View File

@@ -0,0 +1,16 @@
using SQLite;
using Bit.App.Abstractions;
namespace Bit.App.Models.Data
{
[Table("Settings")]
public class SettingsData : IDataObject<string>
{
public SettingsData()
{ }
[PrimaryKey]
public string Id { get; set; }
public string EquivalentDomains { get; set; }
}
}

View File

@@ -3,12 +3,12 @@ using Bit.App.Models.Data;
namespace Bit.App.Models
{
public class Site : Cipher
public class Login : Cipher
{
public Site()
public Login()
{ }
public Site(SiteData data)
public Login(LoginData data)
{
Id = data.Id;
FolderId = data.FolderId;
@@ -20,7 +20,7 @@ namespace Bit.App.Models
Favorite = data.Favorite;
}
public Site(SiteResponse response)
public Login(LoginResponse response)
{
Id = response.Id;
FolderId = response.FolderId;
@@ -39,14 +39,14 @@ namespace Bit.App.Models
public CipherString Notes { get; set; }
public bool Favorite { get; set; }
public SiteRequest ToSiteRequest()
public LoginRequest ToLoginRequest()
{
return new SiteRequest(this);
return new LoginRequest(this);
}
public SiteData ToSiteData(string userId)
public LoginData ToLoginData(string userId)
{
return new SiteData(this, userId);
return new LoginData(this, userId);
}
}
}

View File

@@ -6,16 +6,18 @@ namespace Bit.App.Models.Page
{
public class VaultListPageModel
{
public class Site
public class Login
{
public Site(Models.Site site)
private string _baseDomain;
public Login(Models.Login login)
{
Id = site.Id;
FolderId = site.FolderId;
Name = site.Name?.Decrypt();
Username = site.Username?.Decrypt() ?? " ";
Password = new Lazy<string>(() => site.Password?.Decrypt());
Uri = new Lazy<string>(() => site.Uri?.Decrypt());
Id = login.Id;
FolderId = login.FolderId;
Name = login.Name?.Decrypt();
Username = login.Username?.Decrypt() ?? " ";
Password = new Lazy<string>(() => login.Password?.Decrypt());
Uri = new Lazy<string>(() => login.Uri?.Decrypt());
}
public string Id { get; set; }
@@ -26,7 +28,7 @@ namespace Bit.App.Models.Page
public Lazy<string> Uri { get; set; }
}
public class Folder : List<Site>
public class Folder : List<Login>
{
public Folder(Models.Folder folder)
{
@@ -34,9 +36,9 @@ namespace Bit.App.Models.Page
Name = folder.Name?.Decrypt();
}
public Folder(List<Site> sites)
public Folder(List<Login> logins)
{
AddRange(sites);
AddRange(logins);
}
public string Id { get; set; }

View File

@@ -5,7 +5,7 @@ using Xamarin.Forms;
namespace Bit.App.Models.Page
{
public class VaultViewSitePageModel : INotifyPropertyChanged
public class VaultViewLoginPageModel : INotifyPropertyChanged
{
private string _name;
private string _username;
@@ -15,7 +15,7 @@ namespace Bit.App.Models.Page
private bool _revealPassword;
private string _uriHost;
public VaultViewSitePageModel() { }
public VaultViewLoginPageModel() { }
public event PropertyChangedEventHandler PropertyChanged;
@@ -176,13 +176,13 @@ namespace Bit.App.Models.Page
public string ShowHideText => RevealPassword ? AppResources.Hide : AppResources.Show;
public ImageSource ShowHideImage => RevealPassword ? ImageSource.FromFile("eye_slash") : ImageSource.FromFile("eye");
public void Update(Site site)
public void Update(Login login)
{
Name = site.Name?.Decrypt();
Username = site.Username?.Decrypt();
Password = site.Password?.Decrypt();
Uri = site.Uri?.Decrypt();
Notes = site.Notes?.Decrypt();
Name = login.Name?.Decrypt();
Username = login.Username?.Decrypt();
Password = login.Password?.Decrypt();
Uri = login.Uri?.Decrypt();
Notes = login.Notes?.Decrypt();
}
}
}

View File

@@ -0,0 +1,41 @@
using System.Threading.Tasks;
using Acr.UserDialogs;
using Bit.App.Controls;
using Bit.App.Resources;
using Xamarin.Forms;
using XLabs.Ioc;
namespace Bit.App.Pages
{
public class BaseLockPage : ExtendedContentPage
{
public BaseLockPage()
: base(false, false)
{
UserDialogs = Resolver.Resolve<IUserDialogs>();
}
protected IUserDialogs UserDialogs { get; set; }
protected override bool OnBackButtonPressed()
{
if(Device.OS == TargetPlatform.Android)
{
MessagingCenter.Send(Application.Current, "BackgroundApp");
}
return true;
}
protected async Task LogoutAsync()
{
if(!await UserDialogs.ConfirmAsync(AppResources.LogoutConfirmation, null, AppResources.Yes, AppResources.Cancel))
{
return;
}
MessagingCenter.Send(Application.Current, "Logout", (string)null);
}
}
}

View File

@@ -1,6 +1,5 @@
using System;
using System.Threading.Tasks;
using Acr.UserDialogs;
using Bit.App.Controls;
using Bit.App.Resources;
using Xamarin.Forms;
@@ -10,19 +9,16 @@ using Plugin.Settings.Abstractions;
namespace Bit.App.Pages
{
public class LockFingerprintPage : ExtendedContentPage
public class LockFingerprintPage : BaseLockPage
{
private readonly IFingerprint _fingerprint;
private readonly IUserDialogs _userDialogs;
private readonly ISettings _settings;
private readonly bool _checkFingerprintImmediately;
public LockFingerprintPage(bool checkFingerprintImmediately)
: base(false, false)
{
_checkFingerprintImmediately = checkFingerprintImmediately;
_fingerprint = Resolver.Resolve<IFingerprint>();
_userDialogs = Resolver.Resolve<IUserDialogs>();
_settings = Resolver.Resolve<ISettings>();
Init();
@@ -68,11 +64,6 @@ namespace Bit.App.Pages
Content = stackLayout;
}
protected override bool OnBackButtonPressed()
{
return true;
}
protected override void OnAppearing()
{
base.OnAppearing();
@@ -83,16 +74,6 @@ namespace Bit.App.Pages
}
}
public async Task LogoutAsync()
{
if(!await _userDialogs.ConfirmAsync(AppResources.LogoutConfirmation, null, AppResources.Yes, AppResources.Cancel))
{
return;
}
MessagingCenter.Send(Application.Current, "Logout", (string)null);
}
public async Task CheckFingerprintAsync()
{
var result = await _fingerprint.AuthenticateAsync(AppResources.FingerprintDirection);

View File

@@ -1,6 +1,5 @@
using System;
using System.Threading.Tasks;
using Acr.UserDialogs;
using Bit.App.Abstractions;
using Bit.App.Resources;
using Xamarin.Forms;
@@ -11,19 +10,16 @@ using Plugin.Settings.Abstractions;
namespace Bit.App.Pages
{
public class LockPasswordPage : ExtendedContentPage
public class LockPasswordPage : BaseLockPage
{
private readonly IAuthService _authService;
private readonly ISettings _settings;
private readonly IUserDialogs _userDialogs;
private readonly ICryptoService _cryptoService;
public LockPasswordPage()
: base(false, false)
{
_authService = Resolver.Resolve<IAuthService>();
_settings = Resolver.Resolve<ISettings>();
_userDialogs = Resolver.Resolve<IUserDialogs>();
_cryptoService = Resolver.Resolve<ICryptoService>();
Init();
@@ -100,11 +96,6 @@ namespace Bit.App.Pages
var task = CheckPasswordAsync();
}
protected override bool OnBackButtonPressed()
{
return true;
}
protected override void OnAppearing()
{
base.OnAppearing();
@@ -130,20 +121,10 @@ namespace Bit.App.Pages
{
// TODO: keep track of invalid attempts and logout?
_userDialogs.Alert(AppResources.InvalidMasterPassword);
UserDialogs.Alert(AppResources.InvalidMasterPassword);
PasswordCell.Entry.Text = string.Empty;
PasswordCell.Entry.Focus();
}
}
private async Task LogoutAsync()
{
if(!await _userDialogs.ConfirmAsync(AppResources.LogoutConfirmation, null, AppResources.Yes, AppResources.Cancel))
{
return;
}
MessagingCenter.Send(Application.Current, "Logout", (string)null);
}
}
}

View File

@@ -11,17 +11,14 @@ using Bit.App.Controls;
namespace Bit.App.Pages
{
public class LockPinPage : ExtendedContentPage
public class LockPinPage : BaseLockPage
{
private readonly IAuthService _authService;
private readonly IUserDialogs _userDialogs;
private readonly ISettings _settings;
public LockPinPage()
: base(false, false)
{
_authService = Resolver.Resolve<IAuthService>();
_userDialogs = Resolver.Resolve<IUserDialogs>();
_settings = Resolver.Resolve<ISettings>();
Init();
@@ -79,11 +76,6 @@ namespace Bit.App.Pages
PinControl.Entry.Focus();
}
protected override bool OnBackButtonPressed()
{
return true;
}
protected override void OnAppearing()
{
base.OnAppearing();
@@ -102,20 +94,10 @@ namespace Bit.App.Pages
{
// TODO: keep track of invalid attempts and logout?
_userDialogs.Alert(AppResources.InvalidPIN);
UserDialogs.Alert(AppResources.InvalidPIN);
Model.PIN = string.Empty;
PinControl.Entry.Focus();
}
}
private async Task LogoutAsync()
{
if(!await _userDialogs.ConfirmAsync(AppResources.LogoutConfirmation, null, AppResources.Yes, AppResources.Cancel))
{
return;
}
MessagingCenter.Send(Application.Current, "Logout", (string)null);
}
}
}

View File

@@ -17,6 +17,7 @@ namespace Bit.App.Pages
{
private ICryptoService _cryptoService;
private IAuthService _authService;
private ITokenService _tokenService;
private IDeviceInfoService _deviceInfoService;
private IAppIdService _appIdService;
private IUserDialogs _userDialogs;
@@ -32,6 +33,7 @@ namespace Bit.App.Pages
_email = email;
_cryptoService = Resolver.Resolve<ICryptoService>();
_authService = Resolver.Resolve<IAuthService>();
_tokenService = Resolver.Resolve<ITokenService>();
_deviceInfoService = Resolver.Resolve<IDeviceInfoService>();
_appIdService = Resolver.Resolve<IAppIdService>();
_userDialogs = Resolver.Resolve<IUserDialogs>();
@@ -195,10 +197,18 @@ namespace Bit.App.Pages
return;
}
if(response.Result.TwoFactorProviders != null && response.Result.TwoFactorProviders.Count > 0)
{
_googleAnalyticsService.TrackAppEvent("LoggedIn To Two-step");
await Navigation.PushAsync(new LoginTwoFactorPage(request.Email, request.MasterPasswordHash, key));
return;
}
_cryptoService.Key = key;
_authService.Token = response.Result.Token;
_authService.UserId = response.Result?.Profile?.Id;
_authService.Email = response.Result?.Profile?.Email;
_tokenService.Token = response.Result.AccessToken;
_tokenService.RefreshToken = response.Result.RefreshToken;
_authService.UserId = _tokenService.TokenUserId;
_authService.Email = _tokenService.TokenEmail;
_settings.AddOrUpdateValue(Constants.LastLoginEmail, _authService.Email);
_googleAnalyticsService.RefreshUserId();
_googleAnalyticsService.TrackAppEvent("LoggedIn");
@@ -208,15 +218,8 @@ namespace Bit.App.Pages
_pushNotification.Register();
}
if(_authService.IsAuthenticatedTwoFactor)
{
await Navigation.PushAsync(new LoginTwoFactorPage());
}
else
{
var task = Task.Run(async () => await _syncService.FullSyncAsync());
Application.Current.MainPage = new MainPage();
}
var task = Task.Run(async () => await _syncService.FullSyncAsync(true));
Application.Current.MainPage = new MainPage();
}
}
}

View File

@@ -8,6 +8,8 @@ using Xamarin.Forms;
using XLabs.Ioc;
using Acr.UserDialogs;
using System.Threading.Tasks;
using Plugin.Settings.Abstractions;
using PushNotification.Plugin.Abstractions;
namespace Bit.App.Pages
{
@@ -15,20 +17,35 @@ namespace Bit.App.Pages
{
private ICryptoService _cryptoService;
private IAuthService _authService;
private ITokenService _tokenService;
private IDeviceInfoService _deviceInfoService;
private IAppIdService _appIdService;
private IUserDialogs _userDialogs;
private ISyncService _syncService;
private ISettings _settings;
private IGoogleAnalyticsService _googleAnalyticsService;
private IPushNotification _pushNotification;
private readonly string _email;
private readonly string _masterPasswordHash;
private readonly byte[] _key;
public LoginTwoFactorPage()
public LoginTwoFactorPage(string email, string masterPasswordHash, byte[] key)
: base(updateActivity: false)
{
_email = email;
_masterPasswordHash = masterPasswordHash;
_key = key;
_cryptoService = Resolver.Resolve<ICryptoService>();
_authService = Resolver.Resolve<IAuthService>();
_tokenService = Resolver.Resolve<ITokenService>();
_deviceInfoService = Resolver.Resolve<IDeviceInfoService>();
_appIdService = Resolver.Resolve<IAppIdService>();
_userDialogs = Resolver.Resolve<IUserDialogs>();
_syncService = Resolver.Resolve<ISyncService>();
_settings = Resolver.Resolve<ISettings>();
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
_pushNotification = Resolver.Resolve<IPushNotification>();
Init();
}
@@ -134,15 +151,17 @@ namespace Bit.App.Pages
return;
}
var request = new TokenTwoFactorRequest
var request = new TokenRequest
{
Code = CodeCell.Entry.Text.Replace(" ", ""),
Provider = "Authenticator",
Email = _email,
MasterPasswordHash = _masterPasswordHash,
Token = CodeCell.Entry.Text.Replace(" ", ""),
Provider = 0, // Authenticator app (only 1 provider for now, so hard coded)
Device = new DeviceRequest(_appIdService, _deviceInfoService)
};
_userDialogs.ShowLoading(AppResources.ValidatingCode, MaskType.Black);
var response = await _authService.TokenTwoFactorPostAsync(request);
var response = await _authService.TokenPostAsync(request);
_userDialogs.HideLoading();
if(!response.Succeeded)
{
@@ -150,11 +169,21 @@ namespace Bit.App.Pages
return;
}
_authService.Token = response.Result.Token;
_authService.UserId = response.Result.Profile.Id;
_authService.Email = response.Result.Profile.Email;
_cryptoService.Key = _key;
_tokenService.Token = response.Result.AccessToken;
_tokenService.RefreshToken = response.Result.RefreshToken;
_authService.UserId = _tokenService.TokenUserId;
_authService.Email = _tokenService.TokenEmail;
_settings.AddOrUpdateValue(Constants.LastLoginEmail, _authService.Email);
_googleAnalyticsService.RefreshUserId();
_googleAnalyticsService.TrackAppEvent("LoggedIn From Two-step");
var task = Task.Run(async () => await _syncService.FullSyncAsync());
if(Device.OS == TargetPlatform.Android)
{
_pushNotification.Register();
}
var task = Task.Run(async () => await _syncService.FullSyncAsync(true));
Application.Current.MainPage = new MainPage();
}
}

View File

@@ -12,8 +12,8 @@ namespace Bit.App.Pages
TintColor = Color.FromHex("3c8dbc");
var settingsNavigation = new ExtendedNavigationPage(new SettingsPage());
var favoritesNavigation = new ExtendedNavigationPage(new VaultListSitesPage(true));
var vaultNavigation = new ExtendedNavigationPage(new VaultListSitesPage(false));
var favoritesNavigation = new ExtendedNavigationPage(new VaultListLoginsPage(true));
var vaultNavigation = new ExtendedNavigationPage(new VaultListLoginsPage(false));
var toolsNavigation = new ExtendedNavigationPage(new ToolsPage());
favoritesNavigation.Title = AppResources.Favorites;

View File

@@ -74,6 +74,11 @@ namespace Bit.App.Pages
Spacing = 0
};
if(Device.OS == TargetPlatform.iOS)
{
ToolbarItems.Add(new DismissModalToolBarItem(this, AppResources.Cancel));
}
Title = AppResources.About;
Content = new ScrollView { Content = stackLayout };
}

View File

@@ -22,7 +22,13 @@ namespace Bit.App.Pages
EnableSelection = false,
Root = new TableRoot
{
new TableSection("Icons")
new TableSection(AppResources.Translations)
{
new CustomViewCell(@"@felixqu - Chinese
@Primokorn - French
@King-Tut-Tut - Swedish")
},
new TableSection(AppResources.Icons)
{
new CustomViewCell(@"Tools by Alex Auda Samora from the Noun Project
Fingerprint by masterpage.com from the Noun Project")

View File

@@ -133,7 +133,7 @@ namespace Bit.App.Pages
return;
}
// TODO: Validate the delete operation. ex. Cannot delete a folder that has sites in it?
// TODO: Validate the delete operation. ex. Cannot delete a folder that has logins in it?
if(!await _userDialogs.ConfirmAsync(AppResources.DoYouReallyWantToDelete, null, AppResources.Yes, AppResources.No))
{

View File

@@ -104,6 +104,11 @@ namespace Bit.App.Pages
bugLabel.WidthRequest = stackLayout.Bounds.Width - bugLabel.Bounds.Left * 2;
};
if(Device.OS == TargetPlatform.iOS)
{
ToolbarItems.Add(new DismissModalToolBarItem(this, AppResources.Cancel));
}
Title = AppResources.HelpAndFeedback;
Content = new ScrollView { Content = stackLayout };
}

View File

@@ -38,6 +38,11 @@ namespace Bit.App.Pages
listView.ItemSelected += FolderSelected;
listView.ItemTemplate = new DataTemplate(() => new SettingsFolderListViewCell(this));
if(Device.OS == TargetPlatform.iOS)
{
ToolbarItems.Add(new DismissModalToolBarItem(this, AppResources.Cancel));
}
Title = AppResources.Folders;
Content = listView;
}

View File

@@ -244,12 +244,12 @@ namespace Bit.App.Pages
private void SyncCell_Tapped(object sender, EventArgs e)
{
Navigation.PushAsync(new SettingsSyncPage());
Navigation.PushModalAsync(new ExtendedNavigationPage(new SettingsSyncPage()));
}
private void AboutCell_Tapped(object sender, EventArgs e)
{
Navigation.PushAsync(new SettingsAboutPage());
Navigation.PushModalAsync(new ExtendedNavigationPage(new SettingsAboutPage()));
}
private void RateCell_Tapped(object sender, EventArgs e)
@@ -268,7 +268,7 @@ namespace Bit.App.Pages
private void HelpCell_Tapped(object sender, EventArgs e)
{
Navigation.PushAsync(new SettingsHelpPage());
Navigation.PushModalAsync(new ExtendedNavigationPage(new SettingsHelpPage()));
}
private void LockCell_Tapped(object sender, EventArgs e)
@@ -342,7 +342,7 @@ namespace Bit.App.Pages
cell.On = false;
var pinPage = new SettingsPinPage();
pinPage.OnPinEntered += PinEntered;
Navigation.PushAsync(pinPage);
Navigation.PushModalAsync(new ExtendedNavigationPage(pinPage));
}
else if(!cell.On)
{
@@ -353,7 +353,8 @@ namespace Bit.App.Pages
private void PinEntered(object sender, EventArgs args)
{
var page = sender as SettingsPinPage;
page.Navigation.PopAsync();
page.PinControl.Entry.Unfocus();
page.Navigation.PopModalAsync();
_authService.PIN = page.Model.PIN;
@@ -369,7 +370,7 @@ namespace Bit.App.Pages
private void FoldersCell_Tapped(object sender, EventArgs e)
{
Navigation.PushAsync(new SettingsListFoldersPage());
Navigation.PushModalAsync(new ExtendedNavigationPage(new SettingsListFoldersPage()));
}
private string GetLockOptionsDetailsText()

View File

@@ -56,6 +56,11 @@ namespace Bit.App.Pages
PinControl.Label.GestureRecognizers.Add(tgr);
instructionLabel.GestureRecognizers.Add(tgr);
if(Device.OS == TargetPlatform.iOS)
{
ToolbarItems.Add(new DismissModalToolBarItem(this, AppResources.Cancel));
}
Title = AppResources.SetPIN;
Content = stackLayout;
Content.GestureRecognizers.Add(tgr);

View File

@@ -57,6 +57,11 @@ namespace Bit.App.Pages
Padding = new Thickness(15, 0)
};
if(Device.OS == TargetPlatform.iOS)
{
ToolbarItems.Add(new DismissModalToolBarItem(this, AppResources.Cancel));
}
Title = AppResources.Sync;
Content = stackLayout;
}
@@ -94,7 +99,7 @@ namespace Bit.App.Pages
}
_userDialogs.ShowLoading(AppResources.Syncing, MaskType.Black);
var succeeded = await _syncService.FullSyncAsync();
var succeeded = await _syncService.FullSyncAsync(true);
_userDialogs.HideLoading();
if(succeeded)
{

View File

@@ -10,62 +10,204 @@ namespace Bit.App.Pages
public class ToolsAutofillServicePage : ExtendedContentPage
{
private readonly IGoogleAnalyticsService _googleAnalyticsService;
private readonly IAppInfoService _appInfoService;
public ToolsAutofillServicePage()
{
_googleAnalyticsService = Resolver.Resolve<IGoogleAnalyticsService>();
_appInfoService = Resolver.Resolve<IAppInfoService>();
Init();
}
public StackLayout EnabledStackLayout { get; set; }
public StackLayout DisabledStackLayout { get; set; }
public ScrollView ScrollView { get; set; }
public void Init()
{
var serviceLabel = new Label
var enabledFs = new FormattedString();
var statusSpan = new Span { Text = string.Concat(AppResources.Status, " ") };
enabledFs.Spans.Add(statusSpan);
enabledFs.Spans.Add(new Span
{
Text = AppResources.AutofillDescription,
VerticalOptions = LayoutOptions.Start,
HorizontalOptions = LayoutOptions.Center,
HorizontalTextAlignment = TextAlignment.Center,
LineBreakMode = LineBreakMode.WordWrap,
Text = AppResources.Enabled,
ForegroundColor = Color.Green,
FontAttributes = FontAttributes.Bold,
FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Label))
};
});
var comingSoonLabel = new Label
var statusEnabledLabel = new Label
{
Text = AppResources.ComingSoon,
VerticalOptions = LayoutOptions.CenterAndExpand,
HorizontalOptions = LayoutOptions.Center,
FormattedText = enabledFs,
HorizontalTextAlignment = TextAlignment.Center,
LineBreakMode = LineBreakMode.WordWrap,
FontSize = Device.GetNamedSize(NamedSize.Large, typeof(Label)),
FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Label)),
TextColor = Color.Black
};
var progressButton = new ExtendedButton
var disabledFs = new FormattedString();
disabledFs.Spans.Add(statusSpan);
disabledFs.Spans.Add(new Span
{
Text = AppResources.SeeDevProgress,
Text = AppResources.Disabled,
ForegroundColor = Color.FromHex("c62929"),
FontAttributes = FontAttributes.Bold,
FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Label))
});
var statusDisabledLabel = new Label
{
FormattedText = disabledFs,
HorizontalTextAlignment = TextAlignment.Center,
LineBreakMode = LineBreakMode.WordWrap,
FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Label)),
TextColor = Color.Black
};
var step1Label = new Label
{
Text = AppResources.BitwardenAutofillServiceStep1,
HorizontalTextAlignment = TextAlignment.Center,
LineBreakMode = LineBreakMode.WordWrap,
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label)),
TextColor = Color.Black
};
var step1Image = new Image
{
Source = "accessibility_step1",
HorizontalOptions = LayoutOptions.Center,
Margin = new Thickness(0, 20, 0, 0),
WidthRequest = 300,
HeightRequest = 98
};
var step2Label = new Label
{
Text = AppResources.BitwardenAutofillServiceStep2,
HorizontalTextAlignment = TextAlignment.Center,
LineBreakMode = LineBreakMode.WordWrap,
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label)),
TextColor = Color.Black
};
var step2Image = new Image
{
Source = "accessibility_step2",
HorizontalOptions = LayoutOptions.Center,
Margin = new Thickness(0, 20, 0, 0),
WidthRequest = 300,
HeightRequest = 67
};
var stepsStackLayout = new StackLayout
{
Children = { statusDisabledLabel, step1Image, step1Label, step2Image, step2Label },
Orientation = StackOrientation.Vertical,
Spacing = 10,
VerticalOptions = LayoutOptions.CenterAndExpand,
HorizontalOptions = LayoutOptions.Center
};
var notificationsLabel = new Label
{
Text = AppResources.BitwardenAutofillServiceNotification,
HorizontalTextAlignment = TextAlignment.Center,
LineBreakMode = LineBreakMode.WordWrap,
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label)),
TextColor = Color.Black
};
var tapNotificationImage = new Image
{
Source = "accessibility_notification",
HorizontalOptions = LayoutOptions.Center,
Margin = new Thickness(0, 20, 0, 0),
WidthRequest = 300,
HeightRequest = 74
};
var tapNotificationIcon = new Image
{
Source = "accessibility_notification_icon",
HorizontalOptions = LayoutOptions.Center,
Margin = new Thickness(0, 20, 0, 0),
WidthRequest = 300,
HeightRequest = 54
};
var notificationsStackLayout = new StackLayout
{
Children = { statusEnabledLabel, tapNotificationIcon, tapNotificationImage, notificationsLabel },
Orientation = StackOrientation.Vertical,
Spacing = 10,
VerticalOptions = LayoutOptions.CenterAndExpand,
HorizontalOptions = LayoutOptions.Center
};
DisabledStackLayout = new StackLayout
{
Children = { BuildServiceLabel(), stepsStackLayout, BuildGoButton() },
Orientation = StackOrientation.Vertical,
Spacing = 20,
Padding = new Thickness(20, 30),
VerticalOptions = LayoutOptions.FillAndExpand
};
EnabledStackLayout = new StackLayout
{
Children = { BuildServiceLabel(), notificationsStackLayout, BuildGoButton() },
Orientation = StackOrientation.Vertical,
Spacing = 20,
Padding = new Thickness(20, 30),
VerticalOptions = LayoutOptions.FillAndExpand
};
ScrollView = new ScrollView { Content = DisabledStackLayout };
if(Device.OS == TargetPlatform.iOS)
{
ToolbarItems.Add(new DismissModalToolBarItem(this, AppResources.Close));
}
Title = AppResources.AutofillService;
Content = ScrollView;
}
protected override void OnAppearing()
{
base.OnAppearing();
ScrollView.Content = _appInfoService.AutofillServiceEnabled ? EnabledStackLayout : DisabledStackLayout;
}
private Label BuildServiceLabel()
{
return new Label
{
Text = AppResources.AutofillDescription,
VerticalOptions = LayoutOptions.Start,
HorizontalTextAlignment = TextAlignment.Center,
LineBreakMode = LineBreakMode.WordWrap,
FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label))
};
}
private ExtendedButton BuildGoButton()
{
return new ExtendedButton
{
Text = AppResources.BitwardenAutofillServiceOpenSettings,
Command = new Command(() =>
{
_googleAnalyticsService.TrackAppEvent("SeeAutofillProgress");
Device.OpenUri(new Uri("https://github.com/bitwarden/mobile/issues/1"));
_googleAnalyticsService.TrackAppEvent("OpenAccessibilitySettings");
MessagingCenter.Send(Application.Current, "Accessibility");
}),
VerticalOptions = LayoutOptions.End,
HorizontalOptions = LayoutOptions.Fill,
Style = (Style)Application.Current.Resources["btn-primary"],
FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Button))
};
var stackLayout = new StackLayout
{
Children = { serviceLabel, comingSoonLabel, progressButton },
Orientation = StackOrientation.Vertical,
Spacing = 10,
Padding = new Thickness(20, 30),
VerticalOptions = LayoutOptions.FillAndExpand
};
Title = AppResources.AutofillService;
Content = new ScrollView { Content = stackLayout };
}
}
}

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